myreport-space / scripts /test_sp_d2_d4_generate_eval.py
Chaudhry Waleed
Deploy RICS v2 (senior baseline, CPU Spaces Dockerfile)
c1708ae
Raw
History Blame Contribute Delete
7.34 kB
"""Run SP generate + coverage evaluation for D2 and D4 messy notes."""
from __future__ import annotations
import asyncio
import json
import sys
from datetime import datetime, timezone
from pathlib import Path
_ROOT = Path(__file__).resolve().parents[1]
if str(_ROOT) not in sys.path:
sys.path.insert(0, str(_ROOT))
from backend.evaluation import evaluate_report, evaluation_manifest_path
from backend.evaluation.models import SectionEvalInput
from backend.models.report import GeneratedSection, ReportResult
from scripts.test_sp_decompose_generate import (
CASES,
DEFAULT_TENANT,
_case_for,
)
from backend.domain import template_discoverer
from backend.standard_paragraphs.generate import generate_from_standard_paragraphs
from backend.standard_paragraphs.note_issues_manifest import record_note_issues
from backend.storage import retrieval_manifest
DRAFT_ID = (
"sp-e2e-d2-d4-"
+ datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
)
SECTIONS = ("D2", "D4")
_STYLE_SAMPLES = "--style-samples" in sys.argv
def _run_section(sid: str) -> dict:
case = _case_for(sid)
title = case["section_title"]
observations = list(case["observations"])
schema = template_discoverer.ensure_canonical_schema(DEFAULT_TENANT)
text, hits, messages, guidance, issues, llm_usage, style_sample_count = (
generate_from_standard_paragraphs(
tenant_id=DEFAULT_TENANT,
schema=schema,
section_id=sid,
section_title=title,
observations=observations,
candidate_ids=[sid],
force_decompose=True,
force_decompose_llm=True,
use_all_section_sps=False,
style_samples_enabled=True if _STYLE_SAMPLES else None,
)
)
record_note_issues(
DEFAULT_TENANT,
DRAFT_ID,
section_id=sid,
section_title=title,
observations=observations,
issues=list(issues),
source="e2e_d2_d4_eval",
used_llm=True,
)
retrieval_manifest.record_section_retrieval(
DEFAULT_TENANT,
DRAFT_ID,
section_id=sid,
section_title=title,
observations=observations,
baseline_text=guidance,
hits=hits,
status="MAPPED" if text.strip() else "NO_RAG_MATCH",
prompt_messages=messages,
retrieved_count=len(hits),
prompt_chunk_count=len(hits),
knowledge_source="standard_paragraph",
generated_text=text,
retrieval_issues=list(issues),
llm_usage=llm_usage,
style_sample_count=style_sample_count,
)
return {
"section_id": sid,
"title": title,
"observations": observations,
"findings": list(issues),
"hit_count": len(hits),
"generated_text": text,
"llm_usage": llm_usage,
"style_sample_count": style_sample_count,
}
async def _evaluate(results: list[dict]):
# Prefer decomposed findings for coverage (atomic notes); fall back to blob.
section_inputs = []
sections = []
for r in results:
obs = r["findings"] or r["observations"]
sections.append(
GeneratedSection(
section_id=r["section_id"],
title=r["title"],
text=r["generated_text"] or "",
)
)
section_inputs.append(
SectionEvalInput(
section_id=r["section_id"],
title=r["title"],
observations=obs,
generated_text=r["generated_text"] or "",
baseline_text="",
)
)
report = ReportResult(
tenant_id=DEFAULT_TENANT,
schema_version=2,
sections=sections,
)
return await evaluate_report(
report,
report_id=DRAFT_ID,
section_inputs=section_inputs,
)
def _pct(v: float | None) -> str:
return "n/a" if v is None else f"{v * 100:.1f}%"
async def main() -> int:
print("draft=", DRAFT_ID)
print("tenant=", DEFAULT_TENANT)
print("style_samples=", _STYLE_SAMPLES)
results = []
for sid in SECTIONS:
print(f"\n========== GENERATE {sid} ==========")
r = _run_section(sid)
results.append(r)
print("findings=", len(r["findings"]))
for i, f in enumerate(r["findings"], 1):
print(f" {i}. {f}")
print("retrieved_hits=", r["hit_count"])
print("style_sample_count=", r.get("style_sample_count"))
print("llm_usage=", r.get("llm_usage"))
print("generated_text=\n", r["generated_text"])
print("\n========== EVALUATION ==========")
evaluation = await _evaluate(results)
if evaluation is None:
print("evaluation disabled")
return 1
print("status=", evaluation.status)
print("coverage_rate=", _pct(evaluation.coverage_rate))
print("covered/total=", evaluation.covered_note_atoms, "/", evaluation.total_note_atoms)
print("faithfulness=", _pct(evaluation.faithfulness_score))
print("model=", evaluation.model)
print("error=", evaluation.error)
for sec in evaluation.sections:
print(f"\n--- {sec.section_id} coverage={_pct(sec.coverage_rate)} ---")
print("observations judged=", len(sec.observations))
for j in sec.note_judgments or []:
print(f" [{j.status}] {j.note}")
if j.evidence:
print(f" evidence: {j.evidence[:120]}")
if sec.missing_facts:
print(" missing_facts=", sec.missing_facts)
if sec.error:
print(" error=", sec.error)
path = evaluation_manifest_path(DEFAULT_TENANT, DRAFT_ID)
print("\nevaluation_manifest=", path)
print("retrieval_manifest=", retrieval_manifest.retrieval_manifest_path(DEFAULT_TENANT, DRAFT_ID))
summary = {
"draft_id": DRAFT_ID,
"sections": {
r["section_id"]: {
"findings": r["findings"],
"hit_count": r["hit_count"],
"generated_text": r["generated_text"],
}
for r in results
},
"evaluation": {
"status": evaluation.status,
"coverage_rate": evaluation.coverage_rate,
"covered_note_atoms": evaluation.covered_note_atoms,
"total_note_atoms": evaluation.total_note_atoms,
"faithfulness_score": evaluation.faithfulness_score,
"per_section": {
s.section_id: {
"coverage_rate": s.coverage_rate,
"judgments": [
{"note": j.note, "status": j.status, "evidence": j.evidence}
for j in (s.note_judgments or [])
],
"missing_facts": s.missing_facts,
}
for s in evaluation.sections
},
},
}
out = (
_ROOT
/ "backend"
/ "standard_paragraphs"
/ "samples"
/ f"{DRAFT_ID}.e2e_eval.json"
)
out.write_text(json.dumps(summary, indent=2, ensure_ascii=False), encoding="utf-8")
print("sample copy=", out)
return 0
if __name__ == "__main__":
# Ensure CASES module is loaded (D2/D4 notes updated in test_sp_decompose_notes).
_ = CASES
raise SystemExit(asyncio.run(main()))