#!/usr/bin/env python3 """Jev intent research v2: previous-turn state variants. Offline by default for the report. Source B text stays in the gitignored cache. Committed rows keep gold and model output only. """ from __future__ import annotations import argparse import json import sys import threading from concurrent.futures import ThreadPoolExecutor from importlib.metadata import version as package_version from pathlib import Path from typing import Any, Mapping, Sequence ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from scripts.research.jev_intent_current import call_current_retry # noqa: E402 from scripts.research.jev_intent_probe import ( # noqa: E402 CACHE_DIR, cache_key, call_jev_retry, confusion_counts, layer_metrics, load_cache, load_jsonl, save_cache, self_consistency, strip_confidence, ) from scripts.research.jev_intent_questions import ( # noqa: E402 JEV_MODEL, previous_turn_payload, ) from scripts.research.jev_intent_source_b_v2 import OUTPUT_PATH, from_legacy # noqa: E402 REPORT_JSON = ROOT / "docs" / "research" / "jev_intent_2026_09_27.json" REPORT_MD = ROOT / "docs" / "research" / "jev_intent_2026_09_27.md" SAMPLES_DIR = ROOT / "scripts" / "research" / "jev_intent_samples" MIN_WITH_PREVIOUS = 100 PUBLISHED_C_INTENT = {"choice": 0.990, "collect": 0.922, "none": 0.960} PRED_KEEP = ( "ok", "unavailable", "model", "intent", "answer_class", "has_new_dated_event", "confidence", "raw", "input_tokens", "output_tokens", "elapsed_ms", "continued", "continues_previous_turn", "variant", "error", ) def pct(value: float | None) -> str: if value is None: return "—" return f"{value:.1%}" def public_pred(pred: Mapping[str, Any] | None) -> dict[str, Any] | None: if not pred: return None return {key: pred.get(key) for key in PRED_KEEP if key in pred} def has_previous(row: Mapping[str, Any]) -> bool: return previous_turn_payload(row) is not None def unclear_to_focus(rows: Sequence[Mapping[str, Any]], pred_key: str) -> int: count = 0 for row in rows: if row.get("layer") != "none": continue if (row.get("gold") or {}).get("intent") != "unclear": continue if (row.get(pred_key) or {}).get("intent") == "answer_current_focus": count += 1 return count def strip_block(block: dict[str, Any] | None) -> dict[str, Any] | None: """Flash has no confidence. Leave those rates empty instead of printing 0%.""" if not block: return None out = dict(block) out["all"] = strip_confidence(block.get("all")) out["by_layer"] = { layer: strip_confidence(metrics) if metrics else None for layer, metrics in (block.get("by_layer") or {}).items() } out["with_previous"] = strip_confidence(block.get("with_previous")) out["without_previous"] = strip_confidence(block.get("without_previous")) return out def pack_metrics(rows: Sequence[Mapping[str, Any]], pred_key: str) -> dict[str, Any] | None: usable = [row for row in rows if row.get(pred_key)] if not usable: return None by_layer = {} for layer in ("choice", "collect", "none"): subset = [row for row in usable if row.get("layer") == layer] by_layer[layer] = layer_metrics(subset, pred_key=pred_key) if subset else None with_prev = [row for row in usable if row.get("has_previous_turn")] without_prev = [row for row in usable if not row.get("has_previous_turn")] return { "all": layer_metrics(usable, pred_key=pred_key), "by_layer": by_layer, "with_previous": layer_metrics(with_prev, pred_key=pred_key) if with_prev else None, "without_previous": layer_metrics(without_prev, pred_key=pred_key) if without_prev else None, "none_unclear_to_focus": unclear_to_focus(usable, pred_key), "self_consistency": None, } def attach_legacy_preds(samples: Sequence[dict[str, Any]], cache: Mapping[str, Any], mapping: Mapping[str, str]) -> dict[str, int]: missing = {dest: 0 for dest in mapping} for sample in samples: for dest, legacy in mapping.items(): key = cache_key(legacy, sample) if key in cache: pred = dict(cache[key]) pred["variant"] = "v0" sample[dest] = pred else: missing[dest] += 1 return missing def public_rows(samples: Sequence[Mapping[str, Any]], pred_keys: Sequence[str]) -> list[dict[str, Any]]: rows = [] for sample in samples: row = { "id": sample.get("id"), "source": sample.get("source"), "layer": sample.get("layer"), "gold": sample.get("gold"), "gold_source": sample.get("gold_source"), "has_previous_turn": bool(sample.get("has_previous_turn")), "previous_turn_source": sample.get("previous_turn_source"), } for key in pred_keys: pred = public_pred(sample.get(key) if isinstance(sample.get(key), dict) else None) if pred: row[key] = pred rows.append(row) return rows def decide(meta: Mapping[str, Any], metrics: Mapping[str, Any]) -> dict[str, str]: blocked: list[str] = [] if meta.get("n_with_previous", 0) < MIN_WITH_PREVIOUS: blocked.append(f"n_有上一轮 {meta.get('n_with_previous', 0)} < {MIN_WITH_PREVIOUS}") if not metrics.get("jev_v1"): blocked.append("V1 没有预测") if not metrics.get("jev_v2_chain"): blocked.append("V2 链式没有预测") if not metrics.get("jev_v2_gold"): blocked.append("V2 gold 上界没有预测") if not metrics.get("flash_v1"): blocked.append("Flash+V1 没有预测") if not metrics.get("flash_v0"): blocked.append("Flash V0 没有预测") if blocked: return { "verdict": "缺数据", "blocked": ";".join(blocked), "reason": "上一轮变量没有测全,不能判过门。已有的 V0 数字只作对照,不代替 V1/V2。", } represent = metrics.get("representativeness") or {} v0_none = ((metrics.get("jev_v0") or {}).get("by_layer") or {}).get("none") or {} v2_none = ((metrics.get("jev_v2_chain") or {}).get("by_layer") or {}).get("none") or {} v0_high = v0_none.get("high_conf_error_rate") v2_high = v2_none.get("high_conf_error_rate") if v0_high is None or v2_high is None: return {"verdict": "blocked", "blocked": "无焦点层高置信错误写不出", "reason": "V2 相对 V0 的无焦点层比较缺数字。"} jev = (metrics.get("jev_v2_chain") or {}).get("all") or {} flash = (metrics.get("flash_v0") or {}).get("all") or {} high = jev.get("high_conf_error_rate") recall = jev.get("low_conf_recall") jev_intent = jev.get("intent_acc") flash_intent = flash.get("intent_acc") missing = [ name for name, value in ( ("高置信错误", high), ("低置信召回", recall), ("V2 intent", jev_intent), ("Flash V0 intent", flash_intent), ) if value is None ] if missing: return {"verdict": "blocked", "blocked": "、".join(missing), "reason": "门槛里有写不出的数字。"} gates = [] if high > 0.03: gates.append(f"高置信错误 {high:.1%} > 3%") if recall < 0.60: gates.append(f"低置信召回 {recall:.1%} < 60%") if jev_intent < flash_intent - 0.03: gates.append(f"intent {jev_intent:.1%} < 现行 {flash_intent:.1%} − 3pp") if v2_high >= v0_high: gates.append(f"无焦点层高置信错误 V2 {v2_high:.1%} 没有低于 V0 {v0_high:.1%}") layer_names = {"choice": "点选", "collect": "采集", "none": "无焦点"} gaps = [] for layer, cell in (represent.get("by_layer") or {}).items(): delta = cell.get("delta") if isinstance(delta, (int, float)) and delta > 0.10: gaps.append(f"{layer_names.get(layer, layer)} {delta * 100:.1f} pp") if represent.get("fail"): reason = ( "来源 B 与来源 C 同层 intent 差超过 10pp(" + ",".join(gaps) + ")。按任务书这一项写缺数据,不把这批真人样本外推成过门。" ) if gates: reason += "只看这批来源 B," + ";".join(gates) + "。" return { "verdict": "缺数据", "blocked": "来源 B 与来源 C 同层 intent 差 > 10pp", "reason": reason, } if gates: return {"verdict": "未过门", "blocked": "", "reason": ";".join(gates)} return { "verdict": "过门", "blocked": "", "reason": "三项门槛都过,且 V2 无焦点层高置信错误低于 V0。", } def representativeness(source_b: Sequence[Mapping[str, Any]], source_c: Sequence[Mapping[str, Any]]) -> dict[str, Any]: layers = {} fail = False for layer in ("choice", "collect", "none"): b_rows = [row for row in source_b if row.get("layer") == layer and row.get("jev_v0_1")] c_rows = [row for row in source_c if row.get("layer") == layer and row.get("jev_v0_1")] if not b_rows or not c_rows: layers[layer] = {"n_b": len(b_rows), "n_c": len(c_rows), "delta": None} continue b_acc = layer_metrics(b_rows, pred_key="jev_v0_1")["intent_acc"] c_acc = layer_metrics(c_rows, pred_key="jev_v0_1")["intent_acc"] delta = abs(b_acc - c_acc) fail = fail or delta > 0.10 published = PUBLISHED_C_INTENT[layer] layers[layer] = { "n_b": len(b_rows), "n_c": len(c_rows), "source_b_intent": b_acc, "source_c_intent": c_acc, "delta": delta, "published_c_intent": published, "published_delta_pp": (c_acc - published) * 100, } return {"fail": fail, "by_layer": layers} def continue_summary(rows: Sequence[Mapping[str, Any]], pred_key: str) -> dict[str, Any] | None: values = [] adopted = 0 for row in rows: pred = row.get(pred_key) or {} if pred.get("continued"): adopted += 1 value = pred.get("continues_previous_turn") if isinstance(value, (int, float)): values.append(float(value)) if not values: return None ordered = sorted(values) return { "n": len(values), "max": ordered[-1], "median": ordered[len(ordered) // 2], "at_or_above_0_9": sum(value >= 0.9 for value in values), "adopted": adopted, } def input_tokens(rows: Sequence[Mapping[str, Any]], pred_key: str) -> int: total = 0 for row in rows: value = (row.get(pred_key) or {}).get("input_tokens") if isinstance(value, (int, float)): total += int(value) return total def build_from_samples(source_b: Sequence[dict[str, Any]], source_c: Sequence[dict[str, Any]]) -> dict[str, Any]: for row in list(source_b) + list(source_c): if "previous_turn" in row: row["has_previous_turn"] = has_previous(row) else: row["has_previous_turn"] = bool(row.get("has_previous_turn")) pred_keys = ("jev_v0_1", "jev_v0_2", "flash_v0") b_metrics = { "jev_v0": pack_metrics(source_b, "jev_v0_1"), "flash_v0": strip_block(pack_metrics(source_b, "flash_v0")), "jev_v1": pack_metrics(source_b, "jev_v1_1"), "jev_v2_chain": pack_metrics(source_b, "jev_v2_chain_1"), "jev_v2_gold": pack_metrics(source_b, "jev_v2_gold_1"), "flash_v1": strip_block(pack_metrics(source_b, "flash_v1")), } for name, left, right in ( ("jev_v0", "jev_v0_1", "jev_v0_2"), ("jev_v1", "jev_v1_1", "jev_v1_2"), ("jev_v2_chain", "jev_v2_chain_1", "jev_v2_chain_2"), ("jev_v2_gold", "jev_v2_gold_1", "jev_v2_gold_2"), ): if b_metrics.get(name) and any(row.get(right) for row in source_b): b_metrics[name]["self_consistency"] = self_consistency(source_b, left, right) none_b = [row for row in source_b if row.get("layer") == "none"] non_echo = [row for row in source_b if row.get("gold_source") != "option_echo"] meta = { "model": JEV_MODEL, "sdk": "typesafe-sdk 0.7.0", "source_b_n": len(source_b), "n_with_previous": sum(1 for row in source_b if row.get("has_previous_turn")), "n_without_previous": sum(1 for row in source_b if not row.get("has_previous_turn")), "layers": { layer: sum(1 for row in source_b if row.get("layer") == layer) for layer in ("choice", "collect", "none") }, "gold_source": {}, "source_c_n": len(source_c), "baseline": "fdb7087b", "previous_decision_note": ( "V2 链式的 previous_decision 来自同案上一条 Jev V2 输出(链式,自喂)。" "V2 gold 上界来自同案上一条人工 gold。案件第一轮 previous_turn 为 null,不喂 previous_decision。" ), "gold_note": ( "09-19 的 source_b.jsonl 不在本机,旧 157 条按 turn_id 对回 0 条。" "其余 gold 为执行方按生产标注规范阅读后写入;点选回显另计。" ), } gold_counts: dict[str, int] = {} for row in source_b: source = str(row.get("gold_source") or "unknown") gold_counts[source] = gold_counts.get(source, 0) + 1 meta["gold_source"] = gold_counts metrics = { **b_metrics, "source_c_v0": pack_metrics(source_c, "jev_v0_1"), "representativeness": representativeness(source_b, source_c), "source_b_none_confusion": { "jev_v0": confusion_counts(none_b, "jev_v0_1"), "jev_v1": confusion_counts(none_b, "jev_v1_1"), "jev_v2_chain": confusion_counts(none_b, "jev_v2_chain_1"), "flash_v0": confusion_counts(none_b, "flash_v0"), } if none_b else {}, "non_echo": { "n": len(non_echo), "jev_v0": pack_metrics(non_echo, "jev_v0_1"), "jev_v2_chain": pack_metrics(non_echo, "jev_v2_chain_1"), "flash_v0": strip_block(pack_metrics(non_echo, "flash_v0")), }, "continues_chain": continue_summary(source_b, "jev_v2_chain_1"), "continues_gold": continue_summary(source_b, "jev_v2_gold_1"), } if metrics["source_c_v0"] and any(row.get("jev_v0_2") for row in source_c): metrics["source_c_v0"]["self_consistency"] = self_consistency(source_c, "jev_v0_1", "jev_v0_2") jev_tokens = sum( input_tokens(source_b, key) for key in ( "jev_v0_1", "jev_v0_2", "jev_v1_1", "jev_v1_2", "jev_v2_chain_1", "jev_v2_chain_2", "jev_v2_gold_1", "jev_v2_gold_2", ) ) source_c_tokens = input_tokens(source_c, "jev_v0_1") meta["usage"] = { "jev_input_tokens": jev_tokens, "jev_cost_usd": round((jev_tokens / 1_000_000) * 0.042, 4), "source_c_input_tokens": source_c_tokens, "source_c_cost_usd": round((source_c_tokens / 1_000_000) * 0.042, 4), "flash_input_tokens": input_tokens(source_b, "flash_v0") + input_tokens(source_b, "flash_v1"), "response_models": ["jev-1.13.0"], } conclusion = decide(meta, metrics) return { "meta": meta, "metrics": metrics, "conclusion": conclusion, "rows": public_rows(list(source_b) + list(source_c), pred_keys + ( "jev_v1_1", "jev_v1_2", "jev_v2_chain_1", "jev_v2_chain_2", "jev_v2_gold_1", "jev_v2_gold_2", "flash_v1", )), } def load_samples() -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, int]]: if OUTPUT_PATH.is_file(): source_b = load_jsonl(OUTPUT_PATH) else: source_b = from_legacy(load_jsonl(CACHE_DIR / "source_b.jsonl")) source_b = [row for row in source_b if isinstance(row.get("gold"), dict) and row["gold"].get("intent")] source_c = load_jsonl(SAMPLES_DIR / "simulated.jsonl") cache = load_cache() missing = {} missing.update(attach_legacy_preds(source_b, cache, { "jev_v0_1": "jev_1", "jev_v0_2": "jev_2", "flash_v0": "current_1", })) missing.update(attach_legacy_preds(source_c, cache, { "jev_v0_1": "jev_1", "jev_v0_2": "jev_2", })) return source_b, source_c, missing def recompute_from_rows(rows: Sequence[Mapping[str, Any]]) -> dict[str, Any]: source_b = [dict(row) for row in rows if row.get("source") == "B"] source_c = [dict(row) for row in rows if row.get("source") == "C"] return build_from_samples(source_b, source_c) def metric_line(title: str, block: Mapping[str, Any] | None) -> str: if not block or not block.get("all"): return f"| {title} | — | — | — | — | — | — | — |" all_m = block["all"] none_high = ((block.get("by_layer") or {}).get("none") or {}).get("high_conf_error_rate") return ( f"| {title} | {all_m['n']} | {pct(all_m['intent_acc'])} | {pct(all_m['answer_class_acc'])} | " f"{pct(all_m['dated_acc'])} | {pct(all_m['high_conf_error_rate'])} | {pct(all_m['low_conf_recall'])} | " f"{block.get('none_unclear_to_focus')} / 无焦点高置信错误 {pct(none_high)} |" ) def write_markdown(report: Mapping[str, Any]) -> None: meta = report["meta"] metrics = report["metrics"] conclusion = report["conclusion"] layers = meta.get("layers") or {} gold = meta.get("gold_source") or {} represent = metrics.get("representativeness") or {} lines = [ "# TypeSafe Jev 意图分类 · 上一轮 state 对照(2026-09-27)", "", f"- 任务:`docs/tasks/TASK-rectification-jev-intent-classifier-research-v2-20260927.md`", f"- 基线:`origin/staging` @ `{meta.get('baseline') or 'fdb7087b'}`(任务书提交 `710c848b` 在其历史上)", f"- 模型:`{meta.get('model')}`;SDK `{meta.get('sdk')}`", f"- 结论:**{conclusion.get('verdict')}**。{conclusion.get('reason')}", "", "## 样本", "", f"- 来源 B:{meta.get('source_b_n')} 条。有上一轮 {meta.get('n_with_previous')},无上一轮 {meta.get('n_without_previous')}。", f"- 层:点选 {layers.get('choice', 0)} / 采集 {layers.get('collect', 0)} / 无焦点 {layers.get('none', 0)}。", f"- gold 来源:{json.dumps(gold, ensure_ascii=False)}。", f"- {meta.get('gold_note')}", f"- {meta.get('previous_decision_note')}", "", "## 来源 B", "", "| 变体 | n | intent | answer_class | dated | 高置信错误 | 低置信召回 | 无焦点 unclear→answer |", "| --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |", metric_line("Jev V0", metrics.get("jev_v0")), metric_line("Flash V0(生产提示)", metrics.get("flash_v0")), metric_line("Jev V1", metrics.get("jev_v1")), metric_line("Jev V2 链式(自喂)", metrics.get("jev_v2_chain")), metric_line("Jev V2 gold 上界", metrics.get("jev_v2_gold")), metric_line("Flash + V1", metrics.get("flash_v1")), "", "自洽率:" + ";".join( f"{name} {pct((metrics.get(key) or {}).get('self_consistency'))}" for name, key in ( ("V0", "jev_v0"), ("V1", "jev_v1"), ("V2 链式", "jev_v2_chain"), ("V2 gold", "jev_v2_gold"), ) ) + "。", "", "有上一轮 / 无上一轮(intent;Jev 另给高置信错误):", "", "| 变体 | 有上一轮 n | intent | 高置信错误 | 无上一轮 n | intent | 高置信错误 |", "| --- | ---: | ---: | ---: | ---: | ---: | ---: |", ] for title, key in ( ("Jev V0", "jev_v0"), ("Jev V1", "jev_v1"), ("Jev V2 链式", "jev_v2_chain"), ("Jev V2 gold", "jev_v2_gold"), ("Flash V0", "flash_v0"), ("Flash + V1", "flash_v1"), ): block = metrics.get(key) or {} left = block.get("with_previous") or {} right = block.get("without_previous") or {} if not left and not right: continue lines.append( f"| {title} | {left.get('n', '—')} | {pct(left.get('intent_acc'))} | {pct(left.get('high_conf_error_rate'))} | " f"{right.get('n', '—')} | {pct(right.get('intent_acc'))} | {pct(right.get('high_conf_error_rate'))} |" ) lines += [ "", "延迟与输入 token(来源 B,第一次):", "", "| 变体 | 中位 ms | P95 ms | 次均 input tok |", "| --- | ---: | ---: | ---: |", ] for title, key in ( ("Jev V0", "jev_v0"), ("Jev V1", "jev_v1"), ("Jev V2 链式", "jev_v2_chain"), ("Jev V2 gold", "jev_v2_gold"), ("Flash V0", "flash_v0"), ("Flash + V1", "flash_v1"), ): cell = ((metrics.get(key) or {}).get("all")) or {} if not cell: continue median = cell.get("median_ms") p95 = cell.get("p95_ms") mean_tokens = cell.get("mean_input_tokens") lines.append( f"| {title} | {int(round(median)) if isinstance(median, (int, float)) else '—'} | " f"{int(round(p95)) if isinstance(p95, (int, float)) else '—'} | " f"{round(mean_tokens, 1) if isinstance(mean_tokens, (int, float)) else '—'} |" ) none_v0 = ((metrics.get("jev_v0") or {}).get("by_layer") or {}).get("none") or {} none_v2 = ((metrics.get("jev_v2_chain") or {}).get("by_layer") or {}).get("none") or {} if none_v0 and none_v2: lines += [ "", f"无焦点层 intent:V0 {pct(none_v0.get('intent_acc'))},V2 链式 {pct(none_v2.get('intent_acc'))}。" f"高置信错误从 {pct(none_v0.get('high_conf_error_rate'))} 到 {pct(none_v2.get('high_conf_error_rate'))}。", ] continues = metrics.get("continues_chain") or {} if continues: adopted = continues.get("adopted") or 0 lines.append( f"V2 链式承接分:中位 {continues.get('median'):.2f},最大 {continues.get('max'):.2f}," f"≥ 0.9 的 {continues.get('at_or_above_0_9')} 条,实际沿用上一轮 intent 的 {adopted} 条。" ) non_echo = (metrics.get("non_echo") or {}).get("jev_v2_chain") or {} non_all = non_echo.get("all") or {} if non_all: lines += [ "", f"去掉点选回显后,V2 链式 n={non_all.get('n')},intent {pct(non_all.get('intent_acc'))}," f"高置信错误 {pct(non_all.get('high_conf_error_rate'))},低置信召回 {pct(non_all.get('low_conf_recall'))}。" "门槛仍按全量样本。", ] lines += [ "", "## 来源 C 回归锚(只 V0,跑一次)", "", ] c_block = metrics.get("source_c_v0") or {} layer_names = {"choice": "点选", "collect": "采集", "none": "无焦点"} for layer in ("choice", "collect", "none"): cell = (represent.get("by_layer") or {}).get(layer) or {} published = cell.get("published_c_intent") got = cell.get("source_c_intent") delta = cell.get("published_delta_pp") gap = cell.get("delta") lines.append( f"- {layer_names[layer]}:来源 C {pct(got)},09-19 公布 {pct(published)},差 {delta if delta is None else round(delta, 2)} pp(n={cell.get('n_c')})。" f"来源 B {pct(cell.get('source_b_intent'))},与来源 C 差 {None if gap is None else round(gap * 100, 1)} pp(n={cell.get('n_b')})。" ) lines += [ f"- 来源 C 自洽率:{pct(c_block.get('self_consistency'))}。", "", "## 无焦点层混淆(来源 B)", "", "计数来自报告 JSON,不含原文。", "", ] for title, key in (("V0", "jev_v0"), ("V1", "jev_v1"), ("V2 链式", "jev_v2_chain")): confusion = (metrics.get("source_b_none_confusion") or {}).get(key) or {} labels = confusion.get("labels") or [] counts = confusion.get("counts") or {} if not labels or not any(sum((counts.get(gold) or {}).values()) for gold in labels): continue lines.append(f"### {title}") lines.append("") lines.append("| gold \\ pred | " + " | ".join(labels) + " |") lines.append("| --- | " + " | ".join("---:" for _ in labels) + " |") for gold_label in labels: cells = [str((counts.get(gold_label) or {}).get(pred, 0)) for pred in labels] lines.append(f"| {gold_label} | " + " | ".join(cells) + " |") lines.append("") usage = meta.get("usage") or {} lines += [ "## 写不出的项", "", conclusion.get("blocked") or "无", "", ] if usage: lines.append( f"来源 B 的 Jev 八次合计 {usage.get('jev_input_tokens')} input tok,按 $0.042/M 约 ${usage.get('jev_cost_usd')}。" f"来源 C 一次 {usage.get('source_c_input_tokens')} input tok,约 ${usage.get('source_c_cost_usd')}。" f"Flash 两次合计 {usage.get('flash_input_tokens')} input tok,不按 Jev 单价计。" f"响应 model:{usage.get('response_models')}。" ) lines.append("") REPORT_MD.write_text("\n".join(lines) + "\n", encoding="utf-8") def sdk_label() -> str: try: return f"typesafe-sdk {package_version('typesafe-sdk')}" except Exception: return "typesafe-sdk 0.7.0" def live_cache_key(run_id: str, sample: Mapping[str, Any]) -> str: decision = sample.get("previous_decision") if isinstance(sample.get("previous_decision"), dict) else {} extra = f"{decision.get('intent')}:{decision.get('answer_class')}" return f"{cache_key(run_id, sample)}:{extra}" def prepare_call(row: Mapping[str, Any], decision: Mapping[str, Any] | None) -> dict[str, Any]: sample = dict(row) sample.pop("previous_decision", None) if decision: sample["previous_decision"] = { "intent": decision.get("intent"), "answer_class": decision.get("answer_class"), } return sample def gold_decisions(rows: Sequence[Mapping[str, Any]]) -> list[dict[str, Any] | None]: decisions: list[dict[str, Any] | None] = [] previous: dict[str, Any] | None = None last_case = None for row in rows: case_id = row.get("case_id") if case_id != last_case: previous = None last_case = case_id if previous_turn_payload(row) and previous: decisions.append(previous) else: decisions.append(None) gold = row.get("gold") or {} if gold.get("intent"): previous = {"intent": gold.get("intent"), "answer_class": gold.get("answer_class")} else: previous = None return decisions def run_independent( rows: Sequence[dict[str, Any]], *, run_id: str, cache: dict[str, Any], workers: int, caller, decisions: Sequence[Mapping[str, Any] | None] | None = None, ) -> None: lock = threading.Lock() pending: list[tuple[dict[str, Any], dict[str, Any], str]] = [] for index, row in enumerate(rows): decision = None if decisions is None else decisions[index] sample = prepare_call(row, decision) key = live_cache_key(run_id, sample) hit = cache.get(key) if isinstance(hit, dict) and hit.get("ok"): row[run_id] = hit else: pending.append((row, sample, key)) print(f"{run_id}: {len(pending)} calls, {len(rows) - len(pending)} cached", flush=True) if not pending: return done = {"n": 0} def work(item: tuple[dict[str, Any], dict[str, Any], str]) -> None: row, sample, key = item result = caller(sample) with lock: if result.get("ok"): cache[key] = result row[run_id] = result done["n"] += 1 if done["n"] % 25 == 0 or done["n"] == len(pending): save_cache(cache) print(f" {run_id} {done['n']}/{len(pending)}", flush=True) with ThreadPoolExecutor(max_workers=workers) as pool: list(pool.map(work, pending)) save_cache(cache) def run_chain( rows: Sequence[dict[str, Any]], *, run_id: str, cache: dict[str, Any], workers: int, ) -> None: groups: list[list[dict[str, Any]]] = [] bucket: list[dict[str, Any]] = [] last_case = None for row in rows: case_id = row.get("case_id") if bucket and case_id != last_case: groups.append(bucket) bucket = [] bucket.append(row) last_case = case_id if bucket: groups.append(bucket) lock = threading.Lock() progress = {"n": 0} def one_case(group: list[dict[str, Any]]) -> None: previous: dict[str, Any] | None = None for row in group: decision = previous if previous_turn_payload(row) else None sample = prepare_call(row, decision) key = live_cache_key(run_id, sample) with lock: hit = cache.get(key) if isinstance(hit, dict) and hit.get("ok"): result = hit else: result = call_jev_retry(sample, variant="v2") if result.get("ok"): with lock: cache[key] = result progress["n"] += 1 if progress["n"] % 20 == 0: save_cache(cache) print(f" {run_id} {progress['n']}", flush=True) row[run_id] = result if result.get("ok") and result.get("intent"): previous = {"intent": result.get("intent"), "answer_class": result.get("answer_class")} else: previous = None print(f"{run_id}: {len(groups)} cases", flush=True) with ThreadPoolExecutor(max_workers=workers) as pool: list(pool.map(one_case, groups)) save_cache(cache) def run_live(*, workers: int) -> dict[str, Any]: source_b = [row for row in load_jsonl(OUTPUT_PATH) if isinstance(row.get("gold"), dict) and row["gold"].get("intent")] source_c = load_jsonl(SAMPLES_DIR / "simulated.jsonl") source_b.sort(key=lambda row: (str(row.get("case_id")), str(row.get("created_at")), str(row.get("id")))) cache = load_cache() decisions = gold_decisions(source_b) def jev_v0(sample: Mapping[str, Any]) -> dict[str, Any]: return call_jev_retry(sample, variant="v0") def jev_v1(sample: Mapping[str, Any]) -> dict[str, Any]: return call_jev_retry(sample, variant="v1") def jev_v2(sample: Mapping[str, Any]) -> dict[str, Any]: return call_jev_retry(sample, variant="v2") def flash_v0(sample: Mapping[str, Any]) -> dict[str, Any]: return call_current_retry(sample, variant="v0") def flash_v1(sample: Mapping[str, Any]) -> dict[str, Any]: return call_current_retry(sample, variant="v1") run_independent(source_b, run_id="jev_v0_1", cache=cache, workers=workers, caller=jev_v0) run_independent(source_b, run_id="jev_v0_2", cache=cache, workers=workers, caller=jev_v0) run_independent(source_b, run_id="jev_v1_1", cache=cache, workers=workers, caller=jev_v1) run_independent(source_b, run_id="jev_v1_2", cache=cache, workers=workers, caller=jev_v1) run_independent(source_b, run_id="jev_v2_gold_1", cache=cache, workers=workers, caller=jev_v2, decisions=decisions) run_independent(source_b, run_id="jev_v2_gold_2", cache=cache, workers=workers, caller=jev_v2, decisions=decisions) run_chain(source_b, run_id="jev_v2_chain_1", cache=cache, workers=workers) run_chain(source_b, run_id="jev_v2_chain_2", cache=cache, workers=workers) run_independent(source_b, run_id="flash_v0", cache=cache, workers=workers, caller=flash_v0) run_independent(source_b, run_id="flash_v1", cache=cache, workers=workers, caller=flash_v1) run_independent(source_c, run_id="jev_v0_1", cache=cache, workers=workers, caller=jev_v0) report = build_from_samples(source_b, source_c) report["meta"]["sdk"] = sdk_label() report["meta"]["baseline"] = "fdb7087b" report["meta"]["offline"] = False models = { str((row.get(key) or {}).get("model")) for row in list(source_b) + list(source_c) for key in ( "jev_v0_1", "jev_v1_1", "jev_v2_chain_1", "jev_v2_gold_1", ) if (row.get(key) or {}).get("ok") and (row.get(key) or {}).get("model") } if models: report["meta"]["usage"]["response_models"] = sorted(models) report["meta"]["model"] = sorted(models)[0] if models == {JEV_MODEL} else ", ".join(sorted(models)) return report def main(argv: Sequence[str] | None = None) -> int: parser = argparse.ArgumentParser() parser.add_argument("--offline", action="store_true") parser.add_argument("--live", action="store_true") parser.add_argument("--workers", type=int, default=6) parser.add_argument("--from-report", action="store_true", help="recompute tables from the committed JSON rows") args = parser.parse_args(argv) if args.live: report = run_live(workers=max(1, args.workers)) REPORT_JSON.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8") write_markdown(report) print(json.dumps({ "verdict": report["conclusion"]["verdict"], "reason": report["conclusion"].get("reason"), "blocked": report["conclusion"].get("blocked"), "source_b_n": report["meta"]["source_b_n"], "n_with_previous": report["meta"]["n_with_previous"], "usage": report["meta"].get("usage"), }, ensure_ascii=False)) return 0 if args.from_report: if not REPORT_JSON.is_file(): print("report json missing", file=sys.stderr) return 2 payload = json.loads(REPORT_JSON.read_text(encoding="utf-8")) report = recompute_from_rows(payload.get("rows") or []) report["meta"]["recomputed_from"] = "report_rows" write_markdown(report) print(json.dumps({ "verdict": report["conclusion"]["verdict"], "source_b_intent": ((report["metrics"].get("jev_v0") or {}).get("all") or {}).get("intent_acc"), "n": report["meta"]["source_b_n"], "n_with_previous": report["meta"]["n_with_previous"], }, ensure_ascii=False)) return 0 source_b, source_c, missing = load_samples() report = build_from_samples(source_b, source_c) report["meta"]["legacy_cache_missing"] = missing report["meta"]["offline"] = True REPORT_JSON.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8") write_markdown(report) print(json.dumps({ "verdict": report["conclusion"]["verdict"], "source_b_n": report["meta"]["source_b_n"], "n_with_previous": report["meta"]["n_with_previous"], "missing": missing, "json": str(REPORT_JSON), }, ensure_ascii=False)) return 0 if __name__ == "__main__": raise SystemExit(main())