///|
/// Individual observations remain visible even in an NE visit. NE visits and
/// missing observations break connecting lines; only measured/default values plot.
pub fn render_lesion_trends_svg(
trajectory : @evidence.Trajectory,
language? : ReportLanguage = English,
) -> String {
let out = StringBuilder()
let height = trajectory.study.targets.length() * 260
out.write_string(
"\n")
out.to_string()
}
///|
fn lesion_trends_note(language : ReportLanguage) -> String {
tr(
language, "Each target uses its own vertical scale. V1, V2 follow the visit ledger. Squares mark the explicit 5.0 mm default; crosses have no diameter. Hollow points retain an observation from an NE visit. Connecting lines stop at NE or missing visits. These points do not make an NE visit eligible for the total trend or nadir.",
"各病灶使用独立纵轴比例,V1、V2 对应随访台账顺序。方点表示明确的 5.0 mm 默认值,叉号无直径数值,空心点保留 NE 随访中的观察。NE 或缺失处断线;保留观察不意味着该次随访可进入总和趋势或更新最低值。",
)
}
///|
fn render_lesion_section_html(
trajectory : @evidence.Trajectory,
language : ReportLanguage,
) -> String {
let out = StringBuilder()
out.write_string(
"" +
tr(language, "Individual target trends", "单病灶趋势") +
"
" +
lesion_trends_note(language) +
"
" +
render_lesion_trends_svg(trajectory, language~),
)
out.write_string(
"" +
tr(
language, "Visit / date Target / axis Observation Effective / mm Visit target category ",
"随访 / 日期 病灶 / 测量轴 观察状态 采用直径 / mm 随访靶病灶分类 ",
) +
" ",
)
for target in trajectory.study.targets {
out.write_string(
"" +
tr(language, "Baseline", "基线") +
"
" +
trajectory.study.baseline_date +
" " +
html_escape(target.id) +
" · " +
axis_label(target.kind, language) +
" " +
tr(language, "baseline measurement", "基线测量") +
" " +
format_diameter_mm(target.baseline_tenths_mm) +
" — ",
)
}
for i, a in trajectory.assessments {
for m in a.measurements {
out.write_string(
"" +
html_escape(a.visit_id) +
"
" +
a.date +
" " +
html_escape(m.lesion_id) +
" · " +
axis_label(m.kind, language) +
" " +
observation_label(m.observation, language) +
" " +
mm_or_missing(m.effective_tenths_mm) +
" " +
a.response.code() +
" ",
)
}
}
out.write_string("
")
out.to_string()
}
///|
fn render_lesion_section_markdown(
trajectory : @evidence.Trajectory,
language : ReportLanguage,
) -> String {
let out = StringBuilder()
out.write_string(
"\n## " +
tr(language, "Individual target trends", "单病灶趋势") +
"\n\n" +
lesion_trends_note(language) +
"\n\n",
)
out.write_string(
tr(
language, "| Visit / date | Target / axis | Observation | Effective / mm | Visit target category |\n",
"| 随访 / 日期 | 病灶 / 测量轴 | 观察状态 | 采用直径 / mm | 随访靶病灶分类 |\n",
) +
"|---|---|---|---:|---|\n",
)
for target in trajectory.study.targets {
out.write_string(
"| " +
tr(language, "Baseline", "基线") +
" / " +
trajectory.study.baseline_date +
" | " +
markdown_escape(target.id) +
" / " +
axis_label(target.kind, language) +
" | " +
tr(language, "baseline measurement", "基线测量") +
" | " +
format_diameter_mm(target.baseline_tenths_mm) +
" | — |\n",
)
}
for a in trajectory.assessments {
for m in a.measurements {
out.write_string(
"| " +
markdown_escape(a.visit_id) +
" / " +
a.date +
" | " +
markdown_escape(m.lesion_id) +
" / " +
axis_label(m.kind, language) +
" | " +
observation_label(m.observation, language) +
" | " +
mm_or_missing(m.effective_tenths_mm) +
" | " +
a.response.code() +
" |\n",
)
}
}
out.to_string()
}