chore: 문서를 작성할 때 한국어의 표현 작성 스킬 추가 및 1인칭 관점의 글 작성 검증 테스트 추가
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from claridoc.models import Brief, Outline, Source, SourcePack
|
||||
|
||||
|
||||
def build_evidence_map(brief: Brief, outline: Outline, sources: SourcePack) -> dict[str, Any]:
|
||||
source_by_id = {source.id: source for source in sources.sources}
|
||||
sections: list[dict[str, Any]] = []
|
||||
for section in outline.sections:
|
||||
evidence = []
|
||||
for source_id in section.evidence_ids:
|
||||
source = source_by_id.get(source_id)
|
||||
if source is None:
|
||||
continue
|
||||
evidence.append(_source_record(source))
|
||||
sections.append(
|
||||
{
|
||||
"section_id": section.id,
|
||||
"intent": section.intent,
|
||||
"title": section.title,
|
||||
"reader_question": section.reader_question,
|
||||
"decision_requirements": section.decision_requirements,
|
||||
"evidence": evidence,
|
||||
"evidence_gap": bool(section.decision_requirements and not evidence),
|
||||
}
|
||||
)
|
||||
return {
|
||||
"schema_version": 2,
|
||||
"document": brief.title,
|
||||
"citation_style": brief.constraints.citation_style,
|
||||
"reader_document_contains_internal_source_ids": brief.constraints.citation_style == "source_id",
|
||||
"sections": sections,
|
||||
"sources": [_source_record(source) for source in sources.sources],
|
||||
}
|
||||
|
||||
|
||||
def render_provenance(brief: Brief, outline: Outline, sources: SourcePack) -> str:
|
||||
source_by_id = {source.id: source for source in sources.sources}
|
||||
lines = [
|
||||
"# Evidence and decision provenance",
|
||||
"",
|
||||
"> This is an internal sidecar. It is not reader-facing article content.",
|
||||
"> Source IDs, repository paths, line ranges, status labels, and access dates belong here—not in `document.md`.",
|
||||
"",
|
||||
f"- Document: **{brief.title}**",
|
||||
f"- Citation rendering: `{brief.constraints.citation_style}`",
|
||||
f"- Evidence sources: **{len(sources.sources)}**",
|
||||
"",
|
||||
"## Section evidence map",
|
||||
"",
|
||||
"| Section | Decision contract | Evidence | Status / location |",
|
||||
"|---|---|---|---|",
|
||||
]
|
||||
for section in outline.sections:
|
||||
decision = ", ".join(section.decision_requirements) if section.decision_requirements else "—"
|
||||
if not section.evidence_ids:
|
||||
lines.append(f"| {escape(section.title)} | {escape(decision)} | **GAP** | No allocated evidence |")
|
||||
continue
|
||||
for position, source_id in enumerate(section.evidence_ids):
|
||||
source = source_by_id.get(source_id)
|
||||
if source is None:
|
||||
lines.append(f"| {escape(section.title)} | {escape(decision)} | `{source_id}` | Unknown source |")
|
||||
continue
|
||||
section_name = section.title if position == 0 else "↳"
|
||||
location = _location(source)
|
||||
status = source.status or "unspecified"
|
||||
lines.append(
|
||||
f"| {escape(section_name)} | {escape(decision if position == 0 else '—')} | "
|
||||
f"`{source.id}` {escape(source.title)} | `{escape(status)}` · {escape(location)} |"
|
||||
)
|
||||
lines.extend(["", "## Source details", ""])
|
||||
for source in sources.sources:
|
||||
lines.extend(
|
||||
[
|
||||
f"### `{source.id}` {source.title}",
|
||||
"",
|
||||
f"- Type: `{source.source_type}`",
|
||||
f"- Status: `{source.status or 'unspecified'}`",
|
||||
f"- Location: `{_location(source)}`",
|
||||
f"- Public/reference URL: `{source.url}`",
|
||||
f"- Claim IDs: {', '.join(f'`{item}`' for item in source.claim_ids) or '—'}",
|
||||
f"- Decision IDs: {', '.join(f'`{item}`' for item in source.decision_ids) or '—'}",
|
||||
f"- Retrieval priority: `{source.priority:.4f}`",
|
||||
"",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def _source_record(source: Source) -> dict[str, Any]:
|
||||
return {
|
||||
"id": source.id,
|
||||
"title": source.title,
|
||||
"source_type": source.source_type,
|
||||
"status": source.status,
|
||||
"path": source.path,
|
||||
"heading": source.heading,
|
||||
"line_start": source.line_start,
|
||||
"line_end": source.line_end,
|
||||
"url": source.url,
|
||||
"accessed": source.accessed,
|
||||
"claim_ids": list(source.claim_ids),
|
||||
"decision_ids": list(source.decision_ids),
|
||||
"priority": source.priority,
|
||||
}
|
||||
|
||||
|
||||
def _location(source: Source) -> str:
|
||||
location = source.path or source.url
|
||||
if source.heading:
|
||||
location += f" — {source.heading}"
|
||||
if source.line_start is not None:
|
||||
location += f" (lines {source.line_start}-{source.line_end or source.line_start})"
|
||||
return location
|
||||
|
||||
|
||||
def escape(value: str) -> str:
|
||||
return value.replace("|", "\\|").replace("\n", " ")
|
||||
Reference in New Issue
Block a user