"""Run ONE query through the eSCR retrieval pipeline (loads the persisted index on Thor). dense (BGE bf16, numpy cosine) + BM25 -> RRF -> cross-encoder rerank -> top-K docs. Prints top-K as JSON: case_name, neutral_citation, date, disposition, bench_strength, passage. Run on Thor: Q="your query" python3 18_query_escr.py """ import json, os, re, time import numpy as np, torch from sentence_transformers import SentenceTransformer, CrossEncoder from rank_bm25 import BM25Okapi QUERY = os.environ["Q"]; TOPK = int(os.getenv("TOPK", "10")) K = 60; CAND = 40; BGE_Q = "Represent this sentence for searching relevant passages: " def tok(s): return re.sub(r"[^a-z0-9 ]", " ", (s or "").lower()).split() print("loading index...", flush=True) chunks = [json.loads(l) for l in open("escr_chunks.jsonl")] texts = [c["text"] for c in chunks]; chunk_doc = [c["doc_id"] for c in chunks] M = np.load("escr_vectors.npy") meta = {} for l in open("escr_meta.jsonl"): m = json.loads(l); meta[m["doc_id"]] = m print(f"{len(chunks)} chunks, {len(meta)} docs", flush=True) st = SentenceTransformer("BAAI/bge-small-en-v1.5", device="cuda", model_kwargs={"torch_dtype": torch.bfloat16}) ce = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2", device="cuda") t = time.time(); bm25 = BM25Okapi([tok(t_) for t_ in texts]); print(f"bm25 built in {time.time()-t:.0f}s", flush=True) qv = st.encode(BGE_Q + QUERY, normalize_embeddings=True, convert_to_numpy=True).astype(np.float32) dense = M @ qv d_top = np.argpartition(-dense, 60)[:60]; d_top = d_top[np.argsort(-dense[d_top])] bs = bm25.get_scores(tok(QUERY)) b_top = sorted(range(len(bs)), key=lambda i: bs[i], reverse=True)[:60] from collections import defaultdict sc = defaultdict(float) for r, ci in enumerate(d_top): sc[int(ci)] += 1.0 / (K + r) for r, ci in enumerate(b_top): if bs[ci] > 0: sc[ci] += 1.0 / (K + r) cand = [ci for ci, _ in sorted(sc.items(), key=lambda x: x[1], reverse=True)[:CAND]] rr = ce.predict([(QUERY, texts[ci]) for ci in cand]) best = {} for ci, s in zip(cand, rr): d = chunk_doc[ci] if d not in best or s > best[d][0]: best[d] = (float(s), ci) out = [] for d, (s, ci) in sorted(best.items(), key=lambda x: x[1][0], reverse=True)[:TOPK]: m = meta.get(d, {}) out.append({"neutral_citation": m.get("neutral_citation"), "case_name": m.get("case_name"), "date": m.get("date"), "disposition": m.get("disposition"), "bench_strength": m.get("bench_strength"), "rr": round(s, 2), "passage": re.sub(r"\s+", " ", texts[ci])[:300]}) print("RESULTS_JSON:" + json.dumps(out, ensure_ascii=False)) for i, o in enumerate(out): print(f"{i+1}. [{o['neutral_citation']}] {o['case_name']} ({o['date']}, {o['disposition']}) rr={o['rr']}")