Skip to content

Precompute comment-clustering columns at ETL (skeleton_hash, simhash/minhash, stance) #59

Description

@ekim1394

Problem

This is the heaviest live work the browser does. The docket Comments tab and the orchestration/fidelity panels cluster form letters live, in the browser: clean → strip contacts → skeleton → md5GROUP BY key, with a sorted-token near-dup tier on top. Cost scales O(rows) — a high-volume docket hashes every comment on the main thread on every view.

On top of that, the docket breakdown runs a second client-side agglomeration pass (lib/comments/templates.ts agglomerateTemplates): single-linkage merge of near-dup clusters by token-set Jaccard (≥0.8) to collapse wording variants into template families. That pass is a deliberately cheap stand-in for SimHash/MinHash + LSH.

Proposal

Precompute at ETL, on the comment rows:

  • skeleton_hash (template tier), simhash/minhash (near-dup tier), word_count, is_placeholder — clustering collapses from live O(rows) hashing to GROUP BY skeleton_hash
  • Per-template stance + confidence keyed by skeleton_hash — today stance is a JS phrase-lexicon re-scored per view
  • The heavy near-dup the client only approximates: 64-bit SimHash/MinHash + Hamming-band LSH, which holds up to rewording in a way the current sorted-token key doesn't
  • Per-docket cluster summary rollup so getCommentVolumeAndClusters / getCommentClustersMultiTier read a small artifact instead of scanning partitions

Why it's safe to swap in

The transform spec lives in lib/text/normalize.ts as two parallel implementations of the same logic (TS for the browser, DuckDB SQL builders for in-WASM execution). That duality is deliberate: a precomputed column can be swapped in without changing clustering behaviour, because the pipeline runs byte-for-byte the same normalization the client does.

Payoff

  • skeleton_hash is global, so once it's a column, cross-docket campaign detection (same template across agencies) and per-docket cluster rollups are both one GROUP BY away.
  • Precomputed SimHash/MinHash makes the client-side agglomerateTemplates pass unnecessary — template families fall out of a single GROUP BY on a baked near-dup band.

Implementation notes (from §4 of the doc)

  • Keep the partition pruning: comment partitions are Hive-partitioned agency/docket/year/month, and buildCommentsSource reads only matching part-0.parquet files. Keep derived comment columns inside those partitions; don't centralize them into one flat file the client must scan.

Dependencies

  • The submitter-name ETL gap issue: recovering first_name/last_name/organization activates the sqlStripName signature-masking stage, which sharpens this clustering (form letters identical but for who signed collapse into one cluster).

Source

§3 of the front-end compute architecture doc.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions