from __future__ import annotations import html import itertools from typing import Iterable from .models import EvidenceItem _counter = itertools.count(1) def reset_evidence_counter() -> None: global _counter _counter = itertools.count(1) def make_evidence( *, category: str, finding: str, source_name: str, source_url: str, source_type: str, resolution_or_scope: str, confidence: str, limitation: str, design_implication: str, verification_needed: str, output_label: str, ) -> EvidenceItem: if confidence not in {"high", "medium", "low"}: raise ValueError(f"Invalid confidence: {confidence}") if output_label not in { "computed", "public_data", "cad_derived", "user_input", "ai_interpretation", "site_visit_required", "professional_verification_required", }: raise ValueError(f"Invalid output label: {output_label}") return EvidenceItem( id=f"E{next(_counter):03d}", category=category, finding=finding, source_name=source_name, source_url=source_url, source_type=source_type, resolution_or_scope=resolution_or_scope, confidence=confidence, # type: ignore[arg-type] limitation=limitation, design_implication=design_implication, verification_needed=verification_needed, output_label=output_label, # type: ignore[arg-type] ) def evidence_to_markdown_table(items: Iterable[EvidenceItem]) -> str: rows = list(items) if not rows: return "_No evidence rows were generated._" header = ( "| ID | Category | Finding | Source | Confidence | Limitation | " "Design implication | Verify on site |\n" "|---|---|---|---|---|---|---|---|" ) body = [] for item in rows: source = _md(item.source_name) if item.source_url: source = f"[{source}]({item.source_url})" body.append( "| {id} | {category} | {finding} | {source} | {confidence} | " "{limitation} | {implication} | {verify} |".format( id=_md(item.id), category=_md(item.category), finding=_md(item.finding), source=source, confidence=_md(item.confidence), limitation=_md(item.limitation), implication=_md(item.design_implication), verify=_md(item.verification_needed), ) ) return header + "\n" + "\n".join(body) def evidence_to_dataframe_rows(items: Iterable[EvidenceItem]) -> list[dict[str, str]]: return [ { "ID": item.id, "Category": item.category, "Finding": item.finding, "Source": item.source_name, "Source URL": item.source_url, "Type": item.source_type, "Scope / resolution": item.resolution_or_scope, "Confidence": item.confidence, "Limitation": item.limitation, "Design implication": item.design_implication, "Verify": item.verification_needed, "Label": item.output_label, } for item in items ] def evidence_to_html_table(items: Iterable[EvidenceItem]) -> str: rows = list(items) if not rows: return "

No evidence rows were generated.

" body = [] for item in rows: source = html.escape(item.source_name or "Not available") if item.source_url: safe_url = html.escape(item.source_url, quote=True) source = f"{source}" body.append( "" f"{html.escape(item.id)}" f"{html.escape(item.category)}" f"{html.escape(item.finding)}" f"{source}{html.escape(item.source_type)}" f"{html.escape(item.confidence)}" f"{html.escape(item.resolution_or_scope)}" f"{html.escape(item.limitation)}" f"{html.escape(item.design_implication)}" f"{html.escape(item.verification_needed)}" "" ) return ( "
" "
" "
Evidence AuditEvery board claim must trace to a source, calculation, or verification rule.
" f"{len(rows)} rows" "
" "
" "" "" "" "" "" "" + "".join(body) + "
IDCategoryFindingSourceConfidenceScopeLimitationDesign implicationVerify
" ) def _md(text: object) -> str: escaped = html.escape("" if text is None else str(text)) return escaped.replace("|", "\\|").replace("\n", " ")