chore: 문서를 작성할 때 한국어의 표현 작성 스킬 추가 및 1인칭 관점의 글 작성 검증 테스트 추가
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
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>")
|
||||
Reference in New Issue
Block a user