yams/tests/scripts/analyze_simeon_adaptivity.py
Trevon 6b5b6647bd
[04/14] Repository hygiene and reviewable configuration/retrieval contracts (#144)
* chore(repo): land the retrieval contract and drop root cruft

Lands work that had been sitting uncommitted in the working tree:

- docs/architecture/retrieval_contracts.md plus its executable form,
  tests/benchmarks/retrieval_contract.py and test_retrieval_contract.py,
  now registered in meson as retrieval_contract_unit (suite: policy).
  .gitignore keeps them tracked beneath the tests/benchmarks/*.py ignore.
- AGENTS.md: the Development Memory section, and the product default
  recorded as hybrid_assist + shadow with topology-off as the control,
  which is what search_topology_stage already does.
  docs/benchmarks/index.md gains the matching search_shadow_cost plan.
- .gitleaks.toml, and a Secret scan workflow that actually runs it on
  push and pull requests (nothing in .github invoked gitleaks before).

Deletes three root files with zero references anywhere in the tree:
bench_profile.json (ad-hoc profiling leftover), CppProperties.json
(Visual Studio config; the repo standardises on Meson), and spdlog.pc
(a hand-copied spdlog 1.12 pkg-config with prefix=/usr/local that would
mislead any PKG_CONFIG_PATH lookup; spdlog comes from Conan).

docs/plans/ (two p2p designs already shipped in 5a53cdfa and d012d95b)
was ingested into YAMS with task=desloppification source=plan, hydrated
via yams cat and byte-compared before removal, per the new AGENTS.md
rule; tests/scripts/analyze_simeon_adaptivity.py no longer points at a
plans directory.

Also reflows two stale line anchors in raw_environment_allowlist.txt
for tools/yams-cli/main.cpp (shifted by 11c39157) so the policy suite
passes again; no new environment writes.

Not included: the external/yams-repo submodule pointer in the working
tree was a stale local clone two commits behind, not an intended bump.

Signed-off-by: trvon <me@trevon.dev>

* test(policy): make the configuration-authority ratchet bidirectional

check_configuration_authority.py only reported keys and TOML readers
present in code but missing from the allowlists, so the reviewed
YAMS_* surface could grow but never shrink: production_environment_keys.txt
even said removals "may remain grandfathered". An allowlist that only
accretes stops describing the code.

The policy now also fails on allowlist entries with no remaining
occurrence in src/, include/, or tools/, naming the lines to delete.
Key removals in later commits therefore have to update the allowlist in
the same change, and the count in the success banner becomes a real
measure of the environment surface (432 today).

stale_entries() exposes the two stale sets for tooling; three unit
tests cover stale key, stale reader, and the returned names.

Signed-off-by: trvon <me@trevon.dev>

* tools(policy): report where every configuration key is referenced

Adds tests/scripts/report_config_key_usage.py: for each YAMS_* key in
production_environment_keys.txt and each dotted TOML key literally
resolved in ConfigResolver.cpp, count whole-token references per tree
bucket (src, test, bench, docs, plugin; build output and the allowlists
themselves excluded) and assign a verdict: KEEP when anything outside
product code exercises or documents the key, CANDIDATE when only
product code mentions it, NOT_IN_SRC when the allowlist is stale.

The report is evidence, not a decision. From here on a key deletion
must paste its CANDIDATE rows into the change description; the first
audit pass hand-listed keys that turned out to have bench and test
consumers, which is exactly the mistake a mechanical inventory avoids.

Today: 432 env keys (215 CANDIDATE, 217 KEEP), 117 TOML keys
(103 CANDIDATE, 14 KEEP). Registered as config_key_usage_report_unit.

The report counts only files git tracks (git ls-files) when the root is a
work tree, so an ignored local bench harness cannot turn a CANDIDATE into a
KEEP on one machine and not another; outside a work tree it falls back to
scanning the directory, which is what the synthetic-tree test exercises.

Signed-off-by: trvon <me@trevon.dev>

---------

Signed-off-by: trvon <me@trevon.dev>
2026-09-12 10:09:18 -04:00

403 lines
14 KiB
Python
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Phase 0 report for the simeon adaptivity matrix.
Reads JSONL cells produced by `run_simeon_adaptivity_matrix.sh` and emits:
* Per-corpus pivot tables: backend x alpha vs MRR / Recall@10 / nDCG@10.
* Fusion-contribution summary (dense_contrib vs bm25_contrib).
* Promotion-bar verdicts for the candidate levers defined in the plan
(plans live in YAMS with task/source=plan metadata, per AGENTS.md).
Usage (with uv):
uv run tests/scripts/analyze_simeon_adaptivity.py \
--matrix-dir bench_results/simeon_adaptivity/<run-id> \
--output build/benchmarks/simeon_adaptivity_report.md
"""
from __future__ import annotations
import argparse
import json
import math
import statistics
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Iterable
@dataclass
class Cell:
backend: str
alpha: float
corpus: str
path: Path
rows: list[dict[str, Any]] = field(default_factory=list)
@property
def successful(self) -> list[dict[str, Any]]:
return [r for r in self.rows if _is_success(r)]
def _is_success(row: dict[str, Any]) -> bool:
if isinstance(row.get("success"), bool):
return bool(row.get("success"))
return str(row.get("status", "")).strip().lower() == "ok"
def _get(row: dict[str, Any], path: Iterable[str], default: float = 0.0) -> float:
cur: Any = row
for key in path:
if not isinstance(cur, dict) or key not in cur:
return default
cur = cur[key]
try:
val = float(cur)
if math.isnan(val) or math.isinf(val):
return default
return val
except (TypeError, ValueError):
return default
def _mean(values: list[float]) -> float:
return statistics.fmean(values) if values else 0.0
def _parse_cell_path(matrix_dir: Path, jsonl_path: Path) -> tuple[str, float, str] | None:
try:
rel = jsonl_path.relative_to(matrix_dir)
except ValueError:
return None
parts = rel.parts
if len(parts) < 4 or parts[-1] != "bench.jsonl":
return None
backend = parts[0]
alpha_seg = parts[1]
corpus = parts[2]
if not alpha_seg.startswith("alpha_"):
return None
try:
alpha = float(alpha_seg.removeprefix("alpha_"))
except ValueError:
return None
return backend, alpha, corpus
def load_cells(matrix_dir: Path) -> list[Cell]:
cells: list[Cell] = []
for jsonl in sorted(matrix_dir.rglob("bench.jsonl")):
parsed = _parse_cell_path(matrix_dir, jsonl)
if not parsed:
continue
backend, alpha, corpus = parsed
rows: list[dict[str, Any]] = []
with jsonl.open("r", encoding="utf-8") as handle:
for line_no, line in enumerate(handle, start=1):
line = line.strip()
if not line:
continue
try:
rows.append(json.loads(line))
except json.JSONDecodeError as exc:
print(
f"warn: {jsonl}:{line_no}: invalid JSON ({exc}); skipping",
flush=True,
)
cells.append(Cell(backend=backend, alpha=alpha, corpus=corpus, path=jsonl, rows=rows))
return cells
def summarize_cell(cell: Cell) -> dict[str, Any]:
rows = cell.successful
return {
"backend": cell.backend,
"alpha": cell.alpha,
"corpus": cell.corpus,
"path": str(cell.path),
"n_rows": len(cell.rows),
"n_success": len(rows),
"hybrid_mrr": _mean([_get(r, ["hybrid", "mrr"]) for r in rows]),
"hybrid_recall_at_k": _mean(
[_get(r, ["hybrid", "recall_at_k"]) for r in rows]
),
"hybrid_ndcg_at_k": _mean([_get(r, ["hybrid", "ndcg_at_k"]) for r in rows]),
"keyword_mrr": _mean([_get(r, ["keyword", "mrr"]) for r in rows]),
"keyword_recall_at_k": _mean(
[_get(r, ["keyword", "recall_at_k"]) for r in rows]
),
"fusion_alpha": _mean([_get(r, ["fusion_alpha"]) for r in rows]),
"dense_contrib": _mean([_get(r, ["dense_contrib"]) for r in rows]),
"bm25_contrib": _mean([_get(r, ["bm25_contrib"]) for r in rows]),
"other_contrib": _mean([_get(r, ["other_contrib"]) for r in rows]),
}
def pivot_by_corpus(
summaries: list[dict[str, Any]],
) -> dict[str, dict[tuple[str, float], dict[str, Any]]]:
pivot: dict[str, dict[tuple[str, float], dict[str, Any]]] = {}
for s in summaries:
pivot.setdefault(s["corpus"], {})[(s["backend"], s["alpha"])] = s
return pivot
def _fmt(val: float, digits: int = 4) -> str:
return "n/a" if val is None else f"{val:.{digits}f}"
def render_pivot_tables(
pivot: dict[str, dict[tuple[str, float], dict[str, Any]]],
) -> list[str]:
lines: list[str] = []
for corpus in sorted(pivot.keys()):
lines.append(f"### corpus = `{corpus}`")
lines.append("")
lines.append(
"| backend | α (vector_weight) | rows | MRR (hybrid) | Recall@10 (hybrid) | nDCG@10 (hybrid) | dense_contrib | bm25_contrib |"
)
lines.append(
"| --- | --- | --- | --- | --- | --- | --- | --- |"
)
cells = pivot[corpus]
for key in sorted(cells.keys(), key=lambda k: (k[0], k[1])):
s = cells[key]
lines.append(
"| {backend} | {alpha} | {n} | {mrr} | {recall} | {ndcg} | {dense} | {bm25} |".format(
backend=s["backend"],
alpha=_fmt(s["alpha"], 2),
n=s["n_success"],
mrr=_fmt(s["hybrid_mrr"]),
recall=_fmt(s["hybrid_recall_at_k"]),
ndcg=_fmt(s["hybrid_ndcg_at_k"]),
dense=_fmt(s["dense_contrib"], 3),
bm25=_fmt(s["bm25_contrib"], 3),
)
)
lines.append("")
return lines
def evaluate_p0_hybrid_alpha(
pivot: dict[str, dict[tuple[str, float], dict[str, Any]]],
) -> tuple[bool, list[str]]:
"""P0: some α ≠ default improves MRR by ≥ +0.03 on at least one corpus
without regressing the others > 0.01."""
notes: list[str] = []
improvements: list[tuple[str, float, float, float]] = []
regressions: list[tuple[str, float, float, float]] = []
for corpus, cells in pivot.items():
simeon_cells = {
alpha: s for (backend, alpha), s in cells.items() if backend == "simeon"
}
if not simeon_cells:
continue
baseline_alpha = min(simeon_cells.keys(), key=lambda a: abs(a - 0.5))
baseline_mrr = simeon_cells[baseline_alpha]["hybrid_mrr"]
for alpha, s in simeon_cells.items():
if alpha == baseline_alpha:
continue
delta = s["hybrid_mrr"] - baseline_mrr
if delta >= 0.03:
improvements.append((corpus, alpha, delta, s["hybrid_mrr"]))
elif delta <= -0.01:
regressions.append((corpus, alpha, delta, s["hybrid_mrr"]))
if not improvements:
notes.append("No α sweep improved hybrid MRR by ≥ +0.03 on any corpus.")
return False, notes
winning_alphas = {alpha for _, alpha, _, _ in improvements}
disqualifying = [r for r in regressions if r[1] in winning_alphas]
for corpus, alpha, delta, mrr in improvements:
notes.append(
f"`{corpus}` α={alpha:.2f}: +{delta:.3f} MRR vs. α≈0.5 baseline (MRR={mrr:.4f})"
)
for corpus, alpha, delta, mrr in disqualifying:
notes.append(
f"regression: `{corpus}` α={alpha:.2f}: Δ={delta:.3f} MRR (MRR={mrr:.4f})"
)
passed = bool(improvements) and not disqualifying
return passed, notes
def evaluate_p1_idf(
summaries: list[dict[str, Any]],
) -> tuple[bool, list[str]]:
"""P1 IDF bar: simeon's dense_contrib is consistently swamped, i.e.,
bm25_contrib >> dense_contrib on prose corpora (proxy for head-term
dominance). We cannot compute per-token energy from the current JSONL;
flag this as a proxy check and note explicit follow-up."""
notes: list[str] = []
simeon = [s for s in summaries if s["backend"] == "simeon"]
if not simeon:
notes.append("No simeon cells — cannot evaluate P1.")
return False, notes
suspicious = [
s
for s in simeon
if s["dense_contrib"] < 0.1 and s["bm25_contrib"] > 0.8
]
for s in suspicious:
notes.append(
f"`{s['corpus']}` α={s['alpha']:.2f}: dense_contrib={s['dense_contrib']:.3f} "
f"bm25_contrib={s['bm25_contrib']:.3f} — simeon not contributing to fused rank"
)
if not suspicious:
notes.append(
"No cells show dense_contrib<0.1 with bm25_contrib>0.8; head-term dominance "
"proxy not triggered."
)
return False, notes
notes.append(
"Proxy triggered. Full P1 bar (top-5 tokens >30% of sketch energy) requires "
"per-token energy emission — follow-up item."
)
return True, notes
def evaluate_p2_intent(summaries: list[dict[str, Any]]) -> tuple[bool, list[str]]:
"""P2: needs `query_intent_bucket` column; skip until present."""
return False, [
"Skipped: `query_intent_bucket` not yet emitted by bench JSONL; see Phase 0.1 follow-up.",
]
def evaluate_p3_rerank(cells: list[Cell]) -> tuple[bool, list[str]]:
"""P3: deferred by plan."""
return False, [
"Deferred: P3 learned reranker waits on P0/P1 results and RelevanceLabelStore volume."
]
def render_promotion_bars(
pivot: dict[str, dict[tuple[str, float], dict[str, Any]]],
summaries: list[dict[str, Any]],
cells: list[Cell],
) -> list[str]:
lines: list[str] = []
lines.append("## Promotion bars")
lines.append("")
lines.append("| Lever | Verdict | Notes |")
lines.append("| --- | --- | --- |")
passed_p0, notes_p0 = evaluate_p0_hybrid_alpha(pivot)
passed_p1, notes_p1 = evaluate_p1_idf(summaries)
passed_p2, notes_p2 = evaluate_p2_intent(summaries)
passed_p3, notes_p3 = evaluate_p3_rerank(cells)
def _verdict(ok: bool) -> str:
return "PASS" if ok else "FAIL"
def _fold_notes(xs: list[str]) -> str:
return "<br>".join(xs) if xs else "—"
lines.append(
f"| P0 hybrid α | {_verdict(passed_p0)} | {_fold_notes(notes_p0)} |"
)
lines.append(f"| P1 IDF | {_verdict(passed_p1)} | {_fold_notes(notes_p1)} |")
lines.append(f"| P2 query-intent n-gram | SKIP | {_fold_notes(notes_p2)} |")
lines.append(f"| P3 learned rerank | SKIP | {_fold_notes(notes_p3)} |")
lines.append("")
return lines
def build_report(matrix_dir: Path, cells: list[Cell]) -> str:
summaries = [summarize_cell(c) for c in cells]
pivot = pivot_by_corpus(summaries)
skipped_log = matrix_dir / "skipped_cells.log"
skipped_lines: list[str] = []
if skipped_log.exists():
skipped_lines = [
ln.strip() for ln in skipped_log.read_text().splitlines() if ln.strip()
]
manifest_path = matrix_dir / "matrix_manifest.env"
manifest_kv: dict[str, str] = {}
if manifest_path.exists():
for line in manifest_path.read_text().splitlines():
if "=" in line:
k, v = line.split("=", 1)
manifest_kv[k.strip()] = v.strip()
lines: list[str] = []
lines.append("# Simeon adaptivity — Phase 0 report")
lines.append("")
lines.append(f"Matrix dir: `{matrix_dir}`")
lines.append("")
if manifest_kv:
lines.append("## Manifest")
lines.append("")
for k in sorted(manifest_kv.keys()):
lines.append(f"- `{k}` = `{manifest_kv[k]}`")
lines.append("")
lines.append("## Pivot tables")
lines.append("")
lines.extend(render_pivot_tables(pivot))
lines.extend(render_promotion_bars(pivot, summaries, cells))
if skipped_lines:
lines.append("## Skipped cells")
lines.append("")
for ln in skipped_lines:
lines.append(f"- {ln}")
lines.append("")
lines.append("## Raw cell summaries")
lines.append("")
lines.append("```json")
lines.append(json.dumps(summaries, indent=2, sort_keys=True))
lines.append("```")
lines.append("")
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--matrix-dir",
required=True,
help="Directory produced by run_simeon_adaptivity_matrix.sh",
)
parser.add_argument(
"--output",
required=True,
help="Destination Markdown report path",
)
parser.add_argument(
"--json-out",
default=None,
help="Optional JSON summary dump (cell-level summaries)",
)
args = parser.parse_args()
matrix_dir = Path(args.matrix_dir).resolve()
output = Path(args.output).resolve()
if not matrix_dir.is_dir():
print(f"error: matrix dir not found: {matrix_dir}", flush=True)
return 1
cells = load_cells(matrix_dir)
if not cells:
print(f"error: no bench.jsonl cells found under {matrix_dir}", flush=True)
return 2
report = build_report(matrix_dir, cells)
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(report, encoding="utf-8")
print(f"wrote report: {output}")
if args.json_out:
json_path = Path(args.json_out).resolve()
json_path.parent.mkdir(parents=True, exist_ok=True)
summaries = [summarize_cell(c) for c in cells]
json_path.write_text(json.dumps(summaries, indent=2, sort_keys=True))
print(f"wrote json summary: {json_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())