kcsc-mcp / src /practical_answer_renderer.py
nicefree19's picture
Upload 105 files
0ee3c92 verified
Raw
History Blame Contribute Delete
6.27 kB
"""Render practical answer drafts from source-backed retrieval contracts."""
from __future__ import annotations
from typing import Any
def _as_list(value: Any) -> list[Any]:
return value if isinstance(value, list) else []
def _first_text(*values: Any) -> str:
for value in values:
text = str(value or "").strip()
if text:
return text
return ""
def _status_label(status: str) -> str:
if status == "covered":
return "κ·Όκ±° 확인"
if status == "missing":
return "μΆ”κ°€ 확인 ν•„μš”"
return "μƒνƒœ 미확인"
def _markdown_link(label: str, url: str | None) -> str:
if url and str(url).startswith("https://www.kcsc.re.kr/"):
return f"[{label}]({url})"
return label
def _numbered(items: list[str], *, empty: str) -> list[str]:
if not items:
return [f"- {empty}"]
return [f"{index}. {item}" for index, item in enumerate(items, start=1)]
def _bullets(items: list[str], *, empty: str) -> list[str]:
if not items:
return [f"- {empty}"]
return [f"- {item}" for item in items]
def build_practical_answer_sections(query: str, draft: dict[str, Any]) -> dict[str, Any]:
"""Build stable user-facing sections from a response draft.
This intentionally does not infer new technical conclusions. It only
restructures the concept contract and retrieved evidence into a readable
answer shape.
"""
outline = draft.get("answer_outline") or {}
abstention = draft.get("abstention") or {}
should_abstain = bool(abstention.get("should_abstain"))
standards = []
for item in _as_list(draft.get("basis_evidence"))[:8]:
code = _first_text(item.get("code"))
role = _first_text(item.get("role"), item.get("title"))
status = _first_text(item.get("evidence_status"), "unknown")
revision_note = f", {item.get('revision_date')}" if item.get("revision_date") else ""
standards.append({
"code": code,
"title": _first_text(item.get("title")),
"role": role,
"evidence_status": status,
"status_label": _status_label(status),
"official_url": item.get("official_url"),
"revision_date": item.get("revision_date"),
"link_status": item.get("link_status"),
"line": (
f"{_markdown_link(code, item.get('official_url'))} - {role} ({_status_label(status)}{revision_note})"
if role
else f"{_markdown_link(code, item.get('official_url'))} ({_status_label(status)}{revision_note})"
),
})
evidence = []
for item in _as_list(draft.get("evidence_summary"))[:5]:
code = _first_text(item.get("code"))
title = _first_text(item.get("title"))
section = _first_text(item.get("section"))
preview = _first_text(item.get("evidence_preview"))
label = " / ".join(part for part in (code, section) if part)
evidence.append({
"label": label or code or title or "검색 κ·Όκ±°",
"title": title,
"preview": preview,
"official_url": item.get("official_url"),
"revision_date": item.get("revision_date"),
"link_status": item.get("link_status"),
})
required_inputs = [str(item) for item in _as_list(outline.get("required_inputs"))[:8]]
compliance_workflow = [str(item) for item in _as_list(outline.get("compliance_workflow"))[:8]]
cautions = [str(item) for item in _as_list(outline.get("evidence_cautions"))[:4]]
if should_abstain:
summary = "κ²€μƒ‰λœ μ‘°ν•­ κ·Όκ±°κ°€ λΆ€μ‘±ν•΄ μžλ™ 닡변을 보λ₯˜ν•©λ‹ˆλ‹€. κΈ°μ€€λͺ…, 수치, 적합성 νŒλ‹¨μ€ μΆ”κ°€ 검색 λ˜λŠ” μˆ˜λ™ κ²€ν†  ν›„ ν™•μ •ν•΄μ•Ό ν•©λ‹ˆλ‹€."
else:
summary = "검색 근거와 싀무 concept 맀핑을 λ°”νƒ•μœΌλ‘œ μž‘μ„±ν•œ κΈ°μ€€ κ²€ν†  μ΄ˆμ•ˆμž…λ‹ˆλ‹€. ꡬ쑰 μ•ˆμ „, ν’ˆμ§ˆ νŒμ •, 보수 곡법 확정은 μ±…μž„κΈ°μˆ μž κ²€ν† κ°€ ν•„μš”ν•©λ‹ˆλ‹€."
return {
"query": query,
"summary": summary,
"standard_priority": standards,
"required_inputs": required_inputs,
"compliance_workflow": compliance_workflow,
"evidence_summary": evidence,
"cautions": cautions,
"should_abstain": should_abstain,
"review_required": bool(draft.get("review_required")),
"confidence": draft.get("confidence"),
}
def render_practical_answer_markdown(query: str, draft: dict[str, Any]) -> str:
"""Render a concise Markdown answer from the structured draft."""
sections = build_practical_answer_sections(query, draft)
heading = "κ²€ν†  보λ₯˜" if sections["should_abstain"] else "κ²€ν†  μ΄ˆμ•ˆ"
lines: list[str] = [
f"**{heading}**",
sections["summary"],
"",
"**κΈ°μ€€ μš°μ„ μˆœμœ„**",
]
standard_lines = [item["line"] for item in sections["standard_priority"] if item.get("line")]
lines.extend(_numbered(standard_lines, empty="검색 κ·Όκ±°κ°€ μ—†μ–΄ μš°μ„  적용 기쀀을 ν™•μ •ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."))
lines.extend(["", "**ν•„μˆ˜ 확인 쑰건**"])
lines.extend(_bullets(sections["required_inputs"], empty="ν˜„μž₯ 쑰건과 섀계 μž…λ ₯값을 μΆ”κ°€ 확인해야 ν•©λ‹ˆλ‹€."))
lines.extend(["", "**μ€€μˆ˜ 체크리슀트**"])
lines.extend(_bullets(sections["compliance_workflow"], empty="κ²€μƒ‰λœ μ‘°ν•­ κ·Όκ±° 확보 ν›„ 체크리슀트λ₯Ό ν™•μ •ν•©λ‹ˆλ‹€."))
lines.extend(["", "**κ·Όκ±° μš”μ•½**"])
if sections["evidence_summary"]:
for item in sections["evidence_summary"]:
label = _markdown_link(item["label"], item.get("official_url"))
preview = f": {item['preview']}" if item.get("preview") else ""
lines.append(f"- {label}{preview}")
else:
lines.append("- κ²€μƒ‰λœ μ‘°ν•­ κ·Όκ±°κ°€ μ—†μŠ΅λ‹ˆλ‹€.")
lines.extend(["", "**주의**"])
caution_lines = sections["cautions"] or [
"검색 traceκ°€ μ—†λŠ” 기쀀은 μ΅œμ’… μˆ˜μΉ˜λ‚˜ 적합성 νŒλ‹¨μ— μ‚¬μš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.",
]
lines.extend(_bullets(caution_lines, empty="검색 traceκ°€ μ—†λŠ” 기쀀은 μ΅œμ’… μˆ˜μΉ˜λ‚˜ 적합성 νŒλ‹¨μ— μ‚¬μš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."))
return "\n".join(lines)