///|
/// Offline review document: no scripts, fonts, images or network dependencies.
/// All input-derived text is escaped; navigation IDs are generated by position.
pub fn render_audit_html(trajectory : @evidence.Trajectory) -> String {
render_audit_html_in(trajectory, English)
}
///|
/// Localized presentation; input-derived text and machine codes are preserved.
pub fn render_audit_html_in(
trajectory : @evidence.Trajectory,
language : ReportLanguage,
) -> String {
let out = StringBuilder()
let style =
#|:root{color-scheme:light;--ink:#263348;--muted:#6e7984;--paper:#f6f3ed;--line:#e2e4df}*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font-family:system-ui,-apple-system,"Segoe UI",sans-serif;font-size:14px;line-height:1.6}.wrap{max-width:1200px;margin:auto;padding:40px 36px 70px}.brand{font-size:11px;letter-spacing:2px;font-weight:750;color:#247b68}.mast{display:flex;gap:30px;justify-content:space-between;align-items:start;padding:20px 0 26px;border-bottom:1px solid var(--line)}h1{font-size:34px;letter-spacing:-1.2px;line-height:1.18;margin:0 0 12px}h2{font-size:19px;margin:0 0 6px}h3{font-size:15px;margin:0}p{margin:6px 0}.muted{color:var(--muted)}.tag,.badge{display:inline-block;border:1px solid var(--line);border-radius:5px;padding:4px 10px;font-size:11px;white-space:nowrap}.badge{font-weight:750;border:0;color:white;font-size:12px}.cards{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin:24px 0}.card{display:block;background:#fffdf9;border:1px solid var(--line);border-radius:8px;padding:14px 18px;color:inherit;text-decoration:none}.card strong{display:block;font-size:29px;line-height:1.3}.card small{font-size:11px;letter-spacing:1px}.panel{background:#fffdf9;border:1px solid var(--line);border-radius:9px;padding:23px;margin-top:20px}.panel-head{display:flex;justify-content:space-between;gap:12px;align-items:center}.chart svg{width:100%;height:auto;margin-top:12px}.table-scroll{overflow:auto}table{border-collapse:collapse;width:100%;font-size:12px;margin-top:14px}th{text-align:left;color:var(--muted);font-size:10px;letter-spacing:.7px;text-transform:uppercase;font-weight:650}td,th{padding:11px 12px;border-bottom:1px solid var(--line);vertical-align:top}td:first-child,th:first-child{padding-left:0}td.num,th.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}a{color:#246e70}details{border-top:1px solid var(--line);padding:17px 0}details:first-of-type{border-top:0}summary{cursor:pointer;display:flex;align-items:center;gap:13px}summary .grow{flex:1}.details-inner{margin:15px 0 0 47px}.finding{border-left:3px solid #d8dfdb;padding:5px 12px;margin:9px 0}.finding code{font-size:11px;color:#247b68}.notes{font-size:12px;color:var(--muted);border-top:1px solid var(--line);padding-top:22px;margin-top:28px}.meta{font-size:12px;text-align:right;max-width:290px;overflow-wrap:anywhere}.legend{display:flex;flex-wrap:wrap;gap:16px;font-size:11px;color:var(--muted)}.legend span:before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--c);margin-right:6px}.study-id{overflow-wrap:anywhere}.notice{font-size:12px;background:#edf3f0;border-radius:6px;padding:11px 14px;margin-top:17px}tr:target,details:target{background:#edf3f0}code{overflow-wrap:anywhere}@media(max-width:700px){.wrap{padding:24px 16px}.mast{display:block}h1{font-size:27px}.meta{text-align:left;margin-top:16px}.cards{gap:6px}.card{padding:10px}.card strong{font-size:23px}.panel{padding:15px}.details-inner{margin-left:0}.panel-head{display:block}}@media print{body{background:white}.wrap{padding:0;max-width:none}.panel{break-inside:avoid}.cards{margin:15px 0}details{break-inside:avoid}details::details-content{content-visibility:visible}summary{list-style:none}a{color:inherit;text-decoration:none}}
out.write_string(
tr(
language, "Target-lesion review · ",
"靶病灶复核 · ",
) +
html_escape(trajectory.study.id) +
" LESION FOLLOWUP / OFFLINE REVIEW
Every change. Its reference. Its evidence. An ordered review of manually measured CT target lesions.
本报告仅提供靶病灶分类;非靶病灶、新病灶、确认评价和整体疗效需要额外评价。
",
),
)
for code in ["CR", "PR", "SD", "PD", "NE"] {
let mut count = 0
let mut first = "#visits"
for i, a in trajectory.assessments {
if a.response.code() == code {
if count == 0 {
first = "#visit-\{i}"
}
count += 1
}
}
out.write_string(
tr(
language,
"\{code} / TARGET \{count} ",
"\{code} / 靶病灶 \{count} ",
),
)
}
out.write_string(
tr(
language, " Target burden over time Complete comparable visits define the trend; NE visits leave a gap.
DIAMETER SUM / mm ",
"靶病灶总和趋势 完整且可比的随访进入总和趋势;NE 随访断线。
直径总和 / mm ",
) +
render_trajectory_svg_in(trajectory, language) +
tr(
language, "CR PR SD PD NE / line break
Visit ledger Baseline is the PR reference. The prior minimum is the PD reference.
测量依据 展开随访查看各靶病灶观察与规则分支。
",
),
)
for i, a in trajectory.assessments {
let color = response_color(a.response)
out.write_string(
"" +
a.response.code() +
" " +
html_escape(a.visit_id) +
" " +
a.date +
tr(
language, "