///|
/// The complete, deterministic input used to render a case report.
pub(all) struct ForensicReport {
case : Case
summary : String
timeline : Timeline
findings : DiagnosticReport
evidence_index : EvidenceManifest
limitations : Array[String]
} derive(Debug, Eq, ToJson)
///|
fn markdown_cell(value : String) -> String {
let escaped : Array[String] = []
for character in value {
escaped.push(
match character {
'\\' => "\\\\"
'|' => "\\|"
'\n' => "
"
'\r' => ""
'<' => "<"
'>' => ">"
'&' => "&"
'`' => "\\`"
_ => "\{character}"
},
)
}
escaped.join("")
}
///|
fn report_severity_label(severity : Severity?) -> String {
match severity {
Some(Severity::Trace) => "TRACE"
Some(Severity::Debug) => "DEBUG"
Some(Severity::Info) => "INFO"
Some(Severity::Warning) => "WARN"
Some(Severity::Error) => "ERROR"
Some(Severity::Fatal) => "FATAL"
Some(Severity::Unknown(value)) => value
None => "未知"
}
}
///|
fn report_timestamp_label(timestamp : NormalizedTimestamp?) -> String {
match timestamp {
Some(value) => value.raw
None => "时间未知"
}
}
///|
fn report_finding_kind_label(kind : FindingKind) -> String {
match kind {
FindingKind::Observation => "观察事实"
FindingKind::Hypothesis => "待验证假设"
}
}
///|
/// Renders a Markdown case report with a timeline, findings, evidence index, and limitations.
/// All dynamic table values are escaped so source text cannot add table cells or HTML.
///
/// # Example
/// ```mbt check
/// test {
/// let timeline = build_timeline([])
/// let report = ForensicReport::{
/// case: Case::{
/// case_id: "case-1",
/// title: "Example incident",
/// timezone: "UTC",
/// collected_at: Timestamp::{ value: "2026-01-01T00:00:00Z", },
/// evidence: [],
/// metadata: EvidenceMetadata::{ entries: [], },
/// },
/// summary: "A reproducible sample case.",
/// timeline,
/// findings: DiagnosticReport::{ findings: [], },
/// evidence_index: EvidenceManifest::{ entries: [], },
/// limitations: ["No causal conclusion is asserted."],
/// }
/// let markdown = render_markdown_report(report)
/// assert_true(markdown.contains("## 摘要"))
/// assert_true(markdown.contains("## 限制说明"))
/// }
/// ```
pub fn render_markdown_report(report : ForensicReport) -> String {
let lines : Array[String] = [
"# \{markdown_cell(report.case.title)}",
"",
"- **案件 ID:** \{markdown_cell(report.case.case_id)}",
"- **时区:** \{markdown_cell(report.case.timezone)}",
"- **证据收集完成时间:** \{markdown_cell(report.case.collected_at.value)}",
"",
"## 摘要",
"",
markdown_cell(report.summary),
"",
"## 时间线",
"",
"| 序号 | 时间 | 来源 | 级别 | 原始内容 |",
"| ---: | --- | --- | --- | --- |",
]
if report.timeline.events.length() == 0 {
lines.push("| — | — | — | — | 无时间线事件 |")
} else {
for index, event in report.timeline.events {
lines.push(
"| \{index + 1} | \{markdown_cell(report_timestamp_label(event.timestamp))} | \{markdown_cell(event.source_id)} | \{markdown_cell(report_severity_label(event.severity))} | \{markdown_cell(event.raw_content)} |",
)
}
}
lines.push("")
lines.push("## 发现")
lines.push("")
if report.findings.findings.length() == 0 {
lines.push("没有规则发现。")
} else {
for finding in report.findings.findings {
lines.push("### \{markdown_cell(finding.title)}")
lines.push("")
lines.push("- **分类:** \{report_finding_kind_label(finding.kind)}")
lines.push("- **规则 ID:** `\{markdown_cell(finding.rule_id)}`")
lines.push(
"- **置信度说明:** \{markdown_cell(finding.confidence_note)}",
)
lines.push("")
lines.push(markdown_cell(finding.explanation))
if finding.evidence.length() > 0 {
lines.push("")
lines.push("**支持证据:**")
for reference in finding.evidence {
lines.push(
"- 时间线 #\{reference.event_index + 1} · \{markdown_cell(report_timestamp_label(reference.timestamp))} · \{markdown_cell(reference.source_id)}",
)
lines.push(" - 原文:\{markdown_cell(reference.raw_content)}")
}
}
lines.push("")
}
}
lines.push("## 证据索引")
lines.push("")
lines.push("| 证据 ID | 路径 | 来源 | 大小(字节) | SHA-256 |")
lines.push("| --- | --- | --- | ---: | --- |")
if report.evidence_index.entries.length() == 0 {
lines.push("| — | — | — | — | — |")
} else {
for entry in report.evidence_index.entries {
lines.push(
"| \{markdown_cell(entry.id)} | \{markdown_cell(entry.path)} | \{markdown_cell(entry.source_id)} | \{entry.size_bytes} | `\{markdown_cell(entry.sha256)}` |",
)
}
}
lines.push("")
lines.push("## 限制说明")
lines.push("")
if report.limitations.length() == 0 {
lines.push(
"未提供额外限制说明;时间先后和资源相同本身不构成因果结论。",
)
} else {
for limitation in report.limitations {
lines.push("- \{markdown_cell(limitation)}")
}
}
lines.join("\n") + "\n"
}
///|
/// Renders the complete report as deterministic, two-space-indented JSON.
///
/// # Example
/// ```mbt check
/// test {
/// let timeline = build_timeline([])
/// let report = ForensicReport::{
/// case: Case::{
/// case_id: "case-1",
/// title: "Example incident",
/// timezone: "UTC",
/// collected_at: Timestamp::{ value: "2026-01-01T00:00:00Z", },
/// evidence: [],
/// metadata: EvidenceMetadata::{ entries: [], },
/// },
/// summary: "A reproducible sample case.",
/// timeline,
/// findings: DiagnosticReport::{ findings: [], },
/// evidence_index: EvidenceManifest::{ entries: [], },
/// limitations: [],
/// }
/// let json = render_json_report(report)
/// assert_true(json.contains("\"evidence_index\""))
/// assert_true(json.contains("\"limitations\""))
/// }
/// ```
pub fn render_json_report(report : ForensicReport) -> String {
ToJson::to_json(report).stringify(indent=2)
}