from __future__ import annotations from pathlib import Path import pandas as pd from jinja2 import Template from .utils import ensure_dir REPORT_TEMPLATE = """ BGtopoVJ Blue Box PoC Report

BGtopoVJ Blue Rectangle/Square PoC Report

This is a weak-label mining report. Treat candidates as review targets, not truth.

Candidate summary

MetricValue
Total candidates{{ total }}
Average score{{ avg_score }}
Median score{{ med_score }}

By inferred fill style

{{ style_table }}

QA overlays

{% for overlay in overlays %}

{{ overlay.name }}

{% endfor %}
""" def build_report(candidate_csvs: list[str | Path], overlays: list[str | Path], out_html: str | Path) -> Path: frames = [pd.read_csv(p) for p in candidate_csvs if Path(p).exists()] df = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame() out_html = Path(out_html) ensure_dir(out_html.parent) style_table = "

No candidates.

" if not df.empty: style_table = df.groupby("fill_style").agg(count=("fill_style", "size"), avg_score=("score", "mean")).reset_index().to_html(index=False) overlay_items = [] for ov in overlays: ov = Path(ov) try: rel = ov.relative_to(out_html.parent) except ValueError: rel = ov overlay_items.append({"name": ov.name, "rel": str(rel).replace("\\", "/")}) html = Template(REPORT_TEMPLATE).render( total=0 if df.empty else len(df), avg_score="—" if df.empty else f"{df['score'].mean():.3f}", med_score="—" if df.empty else f"{df['score'].median():.3f}", style_table=style_table, overlays=overlay_items, ) out_html.write_text(html, encoding="utf-8") return out_html