File size: 2,506 Bytes
1d9bd9b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/env python3
"""Build doc-level HELD-headnote vectors (the $0 clean-representation arm).

One BGE-small vector per judgment whose reporter headnote (`held`) exceeds 40 chars —
clean reporter English instead of OCR body chunks. Consumed by Corpus.held_search;
a top-rank hit earns a boost the cross-encoder cannot veto (agent.py doctrine lane).

Previously this script lived only on the Mac; recreated in-repo so the corpus build is
self-contained. Matches the shipped artifact's recipe: iterate meta rows IN FILE ORDER
(including duplicate rows — the shipped 24,327-vector artifact was built that way),
truncate held to 1,800 chars, embed PLAIN (passage side), L2-normalize.

If synthetic_headnotes.jsonl exists (backfill_headnotes.py output), synthetic held
texts are included for docs whose reporter headnote is missing — extending the arm
over the 1970s-80s crater.

Run:  python phase1/scripts/build_held_vectors.py [data_dir]   (CPU, ~15-30 min)
Out:  <data_dir>/held_vectors.npy (float32 L2-normalized), held_docids.json
"""
import json, os, sys, time

import numpy as np

data_dir = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("THEMIS_DATA", "phase1/data/thor_artifacts")
t0 = time.time()

texts, docids, seen_syn = [], [], set()
for line in open(os.path.join(data_dir, "escr_meta.jsonl"), encoding="utf-8"):
    m = json.loads(line)
    h = str(m.get("held") or "")
    if len(h) > 40:
        texts.append(h[:1800]); docids.append(m["doc_id"])

syn = os.path.join(data_dir, "synthetic_headnotes.jsonl")
if os.path.exists(syn):
    have = set(docids)
    for line in open(syn, encoding="utf-8"):
        r = json.loads(line)
        h = str(r.get("held") or "")
        if len(h) > 40 and r["doc_id"] not in have and r["doc_id"] not in seen_syn:
            texts.append(h[:1800]); docids.append(r["doc_id"]); seen_syn.add(r["doc_id"])
    print(f"[held] +{len(seen_syn)} synthetic headnotes", flush=True)

print(f"[held] embedding {len(texts)} headnotes ...", flush=True)
from sentence_transformers import SentenceTransformer
st = SentenceTransformer("BAAI/bge-small-en-v1.5", device="cpu")
V = st.encode(texts, batch_size=256, normalize_embeddings=True,
              convert_to_numpy=True, show_progress_bar=False).astype(np.float32)
np.save(os.path.join(data_dir, "held_vectors.npy"), V)
json.dump(docids, open(os.path.join(data_dir, "held_docids.json"), "w"))
print(f"[held] wrote {V.shape} -> held_vectors.npy + held_docids.json | {time.time()-t0:.0f}s", flush=True)