router/semantic: slice 23 corpus fast-path reconciliation — DeriveFastPath over the real router replaces the regex mirror, factory/merge validated, corpus rebuilt (dataset_hash unchanged), drift diagnostic and rerun tooling

This commit is contained in:
2026-09-08 04:02:00 +04:00
parent bb6bd8efb9
commit be71ac406b
12 changed files with 8460 additions and 7890 deletions
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""
Slice 23 emit: five-way residual non-action semantic router — data files
========================================================================
Slice 23 reconciles corpus fast-path metadata with the production router
(TryFastPath over stage-0 grammars). The corpus builder no longer mirrors the
grammars by hand; fast_path_resolved is derived from the router, so this emit
flags exactly the rows the router genuinely leaves for the general cascade.
This script only repackages the frozen dev pool for the Go legacy baseline and
the Python experiment, writing into /tmp/mvn-s23 so the slice-22 artifacts
stay untouched. Logic is slice22_emit.py verbatim; only OUT_DIR differs.
/tmp/mvn-s23/pool.json residual non-action dev rows: idx, text, n_text,
route, tags, cv_fold, split_group, family_id,
source_id (1509 rows)
/tmp/mvn-s23/ood.json residual ACTION dev rows (720): same shape; OOD
probes only, never primary metrics
/tmp/mvn-s23/stats.json population summary (routes, families, folds)
"""
import json
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import slice18_sparse # noqa: E402 (normalize_match_text, load_data, filters)
OUT_DIR = "/tmp/mvn-s23"
ROUTES = ["conversation", "knowledge", "memory_write", "system", "uncertain"]
def main():
meta, examples = slice18_sparse.load_data()
dev = slice18_sparse.filter_dev_pool(examples)
print(f"dev pool: {len(dev)} rows "
f"(meta declares dev_count={meta.get('dev_count')})")
rows = []
for i, e in enumerate(dev):
if not e["fast_path_resolved"]:
rows.append({
"idx": i,
"text": e["text"],
"n_text": slice18_sparse.normalize_match_text(e["text"]),
"route": e["route"],
"tags": sorted(set(e.get("tags", []))),
"cv_fold": e["cv_fold"],
"split_group": e["split_group"],
"family_id": e["family_id"],
"source_id": e["source_id"],
})
na = [r for r in rows if r["route"] != "action"]
ood = [r for r in rows if r["route"] == "action"]
print(f"residual rows: {len(rows)} non-action: {len(na)} action(OOD): {len(ood)}")
by_route = {}
for r in na:
by_route[r["route"]] = by_route.get(r["route"], 0) + 1
print("routes:", by_route)
assert sum(by_route.values()) == len(na)
assert set(ROUTES) == set(by_route), "route set must be the five-way"
by_family = {}
for r in na:
by_family[r["family_id"]] = by_family.get(r["family_id"], 0) + 1
by_fold = {}
for r in na:
by_fold[r["cv_fold"]] = by_fold.get(r["cv_fold"], 0) + 1
print(f"family_ids: {len(by_family)} split_groups: {len(set(r['split_group'] for r in na))}")
print("folds:", by_fold)
os.makedirs(OUT_DIR, exist_ok=True)
with open(os.path.join(OUT_DIR, "pool.json"), "w") as f:
json.dump(na, f, ensure_ascii=False, indent=1)
with open(os.path.join(OUT_DIR, "ood.json"), "w") as f:
json.dump(ood, f, ensure_ascii=False, indent=1)
with open(os.path.join(OUT_DIR, "stats.json"), "w") as f:
json.dump({
"dev_count": len(dev),
"residual_count": len(rows),
"non_action_count": len(na),
"action_ood_count": len(ood),
"routes": by_route,
"family_ids": len(by_family),
"folds": by_fold,
"top_family": dict(sorted(by_family.items(), key=lambda kv: -kv[1])[:15]),
}, f, ensure_ascii=False, indent=1)
print(f"wrote {OUT_DIR}/{{pool,ood,stats}}.json")
if __name__ == "__main__":
main()