Files
document-haness/build/lib/claridoc/report.py
T

115 lines
4.6 KiB
Python

from __future__ import annotations
from collections import Counter
from claridoc.models import Brief, PipelineConfig, RoundResult
def render_run_report(
brief: Brief,
config: PipelineConfig,
rounds: list[RoundResult],
warnings: list[str],
) -> str:
final = rounds[-1]
lines = [
"# ClariDoc quality report",
"",
f"- Document: **{brief.title}**",
f"- Type: `{brief.document_type.value}`",
f"- Language: `{brief.language}`",
f"- Gate: **{'PASS' if final.passed else 'FAIL'}**",
f"- Final composite score: **{final.composite_score:.1f}/100**",
f"- Rounds: **{len(rounds)}**",
"",
"## Provider topology",
"",
f"- Planner: `{config.planner.provider}`{_model_suffix(config.planner.model)}",
f"- Writer: `{config.writer.provider}`{_model_suffix(config.writer.model)}",
f"- Reviser: `{config.reviser.provider}`{_model_suffix(config.reviser.model)}",
"- Reviewers: " + ", ".join(
f"`{reviewer.role}` → `{reviewer.provider.provider}`{_model_suffix(reviewer.provider.model)}"
for reviewer in config.reviewers
),
"",
"## Quality-gate configuration",
"",
f"- Minimum score: {config.quality_gate.minimum_score:.1f}",
f"- Maximum blockers: {config.quality_gate.max_blockers}",
f"- Maximum errors: {config.quality_gate.max_errors}",
f"- Maximum revisions: {config.quality_gate.max_revisions}",
f"- Weights: deterministic {config.quality_gate.deterministic_weight:.0%}, model reviews {config.quality_gate.model_weight:.0%}",
"",
"## Round history",
"",
"| Round | Deterministic | Model mean | Composite | Blockers | Errors | Gate |",
"|---:|---:|---:|---:|---:|---:|---|",
]
for item in rounds:
model_mean = sum(review.score for review in item.reviews) / len(item.reviews) if item.reviews else item.lint_report.score
lines.append(
f"| {item.round_number} | {item.lint_report.score:.1f} | {model_mean:.1f} | "
f"{item.composite_score:.1f} | {item.blocker_count} | {item.error_count} | "
f"{'PASS' if item.passed else 'FAIL'} |"
)
lines.extend(["", "## Final deterministic findings", ""])
if not final.lint_report.issues:
lines.append("No deterministic findings.\n")
else:
counts = Counter(issue.severity.value for issue in final.lint_report.issues)
lines.append(
", ".join(f"{name}: {counts.get(name, 0)}" for name in ("blocker", "error", "warning", "info"))
)
lines.extend(["", "| Severity | Code | Location | Finding |", "|---|---|---|---|"])
for issue in final.lint_report.issues:
location = f"line {issue.line}" if issue.line else (issue.section or "—")
message = _escape_table_cell(issue.message)
lines.append(
f"| {issue.severity.value} | `{issue.code}` | {location} | {message} |"
)
lines.extend(["", "## Final independent reviews", ""])
for review in final.reviews:
lines.extend([
f"### {review.role}{review.provider}",
"",
f"Score: **{review.score:.1f}/100**",
"",
])
if review.strengths:
lines.append("Strengths: " + "; ".join(review.strengths))
lines.append("")
if review.issues:
lines.extend(["| Severity | Section | Problem | Correction |", "|---|---|---|---|"])
for issue in review.issues:
problem = _escape_table_cell(issue.problem)
fix = _escape_table_cell(issue.fix)
lines.append(
f"| {issue.severity} | {issue.section or '—'} | {problem} | {fix} |"
)
lines.append("")
else:
lines.append("No material issues reported.\n")
if warnings:
lines.extend(["## Harness warnings", ""])
lines.extend(f"- {warning}" for warning in warnings)
lines.append("")
lines.extend([
"## Interpretation",
"",
"A PASS means this run met the configured structural, lint, and model-review gate. It does not replace domain-owner verification, executable code testing, legal review, security review, or independent validation of source truth.",
"",
])
return "\n".join(lines)
def _model_suffix(model: str) -> str:
return f" (`{model}`)" if model else ""
def _escape_table_cell(value: str) -> str:
return value.replace("|", "\\|").replace("\n", "<br>")