feat: extraction coverage summary in report
This commit is contained in:
+45
-11
@@ -1,5 +1,4 @@
|
||||
"""
|
||||
report.py - Stage 4: assemble the final report.
|
||||
"""report.py - Stage 4: assemble the final report.
|
||||
|
||||
Produces a single JSON object (also the web API payload) and a human-readable
|
||||
Markdown summary grouped by severity.
|
||||
@@ -8,6 +7,37 @@ Markdown summary grouped by severity.
|
||||
from typing import List, Dict
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from backend import config
|
||||
|
||||
|
||||
def _extraction_coverage(sheets: List[Dict]) -> Dict | None:
|
||||
"""Summarize per-sheet extraction coverage for the report summary.
|
||||
|
||||
Sheets may carry a ``coverage`` dict (``total_lines``/``covered_lines``/
|
||||
``ratio``) attached during wave-1 extraction. Older paths and scanned
|
||||
pages have none; when no sheet is measured, return None so callers can
|
||||
omit the key entirely.
|
||||
"""
|
||||
measured = [s for s in sheets if isinstance(s.get("coverage"), dict)]
|
||||
if not measured:
|
||||
return None
|
||||
floor = getattr(config, "EXTRACT_COVERAGE_FLOOR", 0.6)
|
||||
return {
|
||||
"pages_measured": len(measured),
|
||||
"pages_below_floor": [
|
||||
s.get("page_number") for s in measured
|
||||
if s["coverage"].get("ratio", 0.0) < floor
|
||||
],
|
||||
"fallback_pages": [
|
||||
s.get("page_number") for s in measured
|
||||
if any(a.get("grounding") == "text_layer_fallback"
|
||||
for a in s.get("assertions", []))
|
||||
],
|
||||
"mean_ratio": round(
|
||||
sum(s["coverage"].get("ratio", 0.0) for s in measured)
|
||||
/ len(measured), 3),
|
||||
}
|
||||
|
||||
|
||||
def build_report(conflicts: List[Dict], sheets: List[Dict], clusters: List[Dict],
|
||||
source: str = "") -> Dict:
|
||||
@@ -19,18 +49,22 @@ def build_report(conflicts: List[Dict], sheets: List[Dict], clusters: List[Dict]
|
||||
by_cat[c["category"]] = by_cat.get(c["category"], 0) + 1
|
||||
|
||||
disciplines = sorted({s["discipline"] for s in sheets if s.get("discipline")})
|
||||
summary = {
|
||||
"sheets_analyzed": len(sheets),
|
||||
"disciplines": disciplines,
|
||||
"assertions_extracted": sum(len(s.get("assertions", [])) for s in sheets),
|
||||
"clusters_checked": len(clusters),
|
||||
"conflicts_found": len(conflicts),
|
||||
"by_severity": by_sev,
|
||||
"by_category": by_cat,
|
||||
}
|
||||
coverage = _extraction_coverage(sheets)
|
||||
if coverage is not None:
|
||||
summary["extraction_coverage"] = coverage
|
||||
return {
|
||||
"source": source,
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"summary": {
|
||||
"sheets_analyzed": len(sheets),
|
||||
"disciplines": disciplines,
|
||||
"assertions_extracted": sum(len(s.get("assertions", [])) for s in sheets),
|
||||
"clusters_checked": len(clusters),
|
||||
"conflicts_found": len(conflicts),
|
||||
"by_severity": by_sev,
|
||||
"by_category": by_cat,
|
||||
},
|
||||
"summary": summary,
|
||||
"conflicts": conflicts,
|
||||
"sheets": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user