// Renders the print-ready auditable report from the agent's last result, // passed via sessionStorage. Includes original query, planner decision, // full specialist trail, map snapshot, briefing prose with citations, // and a Sources section listing every doc_id with its vintage + URL. (function () { const raw = sessionStorage.getItem("riprap_report"); if (!raw) return; let pkg; try { pkg = JSON.parse(raw); } catch (e) { document.getElementById("paper").innerHTML = `

Could not parse stored report payload: ${e.message}

`; return; } render(pkg); })(); function escapeHtml(s) { return String(s ?? "").replace(/&/g, "&").replace(//g, ">"); } function render(pkg) { const r = pkg.result || {}; const plan = pkg.plan || r.plan || {}; const trace = pkg.trace || []; const labels = pkg.sourceLabels || {}; const urls = pkg.sourceUrls || {}; const vintages = pkg.sourceVintages || {}; const stepLabels = pkg.stepLabels || {}; const intent = r.intent || plan.intent || "—"; const intentTitleMap = { single_address: "Flood-exposure briefing — address", neighborhood: "Flood-exposure briefing — neighborhood", development_check: "Active development × flood exposure", live_now: "Current conditions — NYC", }; const place = (r.target && r.target.nta_name) || (r.geocode && r.geocode.address) || r.place || "—"; // Build the citation index from the briefing prose so we render a // numbered Sources section in the SAME order the chips appear in the // text — same idiom as the agent UI. const citeIndex = {}; const para = r.paragraph || ""; const para2 = para.replace(/\[([a-z0-9_]+)\]/gi, (_, id) => { const norm = id.toLowerCase(); if (citeIndex[norm] == null) citeIndex[norm] = Object.keys(citeIndex).length + 1; return `${citeIndex[norm]}`; }); const html = `
Riprap
Citation-grounded flood-exposure briefing
Subject
${escapeHtml(intentTitleMap[intent] || "Briefing")} · ${escapeHtml(place)}
${r.geocode && r.geocode.borough ? `
Borough
${escapeHtml(r.geocode.borough)}
` : ""} ${r.target && r.target.borough ? `
Borough
${escapeHtml(r.target.borough)}
` : ""} ${r.geocode && r.geocode.bbl ? `
BBL
${escapeHtml(r.geocode.bbl)}
` : ""} ${r.target && r.target.nta_code ? `
NTA
${escapeHtml(r.target.nta_code)}
` : ""}
Generated
${escapeHtml(pkg.finishedAt || new Date().toISOString())}
Total runtime
${pkg.wallSeconds ?? r.total_s ?? "—"} s

1 · Original query

"${escapeHtml(pkg.query)}"

2 · Agent routing decision

Intent
${escapeHtml(plan.intent || intent)}
Targets
${escapeHtml((plan.targets || []).map(t => `${t.type}:${t.text}`).join(", ") || "—")}
Specialists requested
${escapeHtml((plan.specialists || []).join(", ") || "—")}
${plan.rationale ? `
"${escapeHtml(plan.rationale)}"
` : ""}

3 · Specialist trail

${trace.length} specialists invoked. Each row shows the step name, status, elapsed time, and the structured result the step produced. Sources of any data referenced in the briefing appear in Section 6.
${trace.map((s, i) => { const ok = s.ok === true; const fail = s.ok === false; const cls = ok ? "ok" : fail ? "err" : ""; const mark = ok ? "✓" : fail ? "✗" : "○"; const [label] = stepLabels[s.step] || [s.step, ""]; const detail = s.err ? `${escapeHtml(s.err)}` : `${escapeHtml(JSON.stringify(s.result ?? {}))}`; return ``; }).join("")}
#StepStatusElapsedResult / error
${i + 1} ${escapeHtml(label)}
${escapeHtml(s.step)}
${mark} ${s.elapsed_s != null ? s.elapsed_s + "s" : "—"} ${detail}
${pkg.mapPng ? `

4 · Map (snapshot)

Map snapshot at report-generation time
Snapshot of the live MapLibre map captured at report-generation time. Layers: per-intent (Sandy 2012 / DEP scenarios / NTA boundary / DOB permit pins / address pin).
` : `

4 · Map

No map snapshot was captured (the map may have been hidden or empty for this query type).
`}

5 · Cited briefing

${renderBriefingMarkdown(para2)}

6 · Sources

    ${Object.entries(citeIndex).sort((a, b) => a[1] - b[1]).map(([id, n]) => { const url = urls[id]; return `
  1. [${n}]
    ${escapeHtml(labels[id] || id)} ${vintages[id] ? `Vintage: ${escapeHtml(vintages[id])}` : ""} ${url ? `${escapeHtml(url)}` : ""} doc_id: ${escapeHtml(id)}
  2. `; }).join("")}

7 · Methodology & honest scope

This is an exposure briefing, not a damage probability or insurance rating. Tier and headline statistics are computed from a deterministic, peer-reviewed-grounded rubric (see METHODOLOGY.md in the source repository). The synthesis prose is generated by IBM Granite 4.1 in document-grounded mode; every numeric claim is verified to appear verbatim in a source document before render, and unsupported sentences are dropped.

Stack: Granite 4.1 (3b planner / 8b reconciler) via Ollama, Granite Embedding 278M for RAG over agency reports, Granite TimeSeries TTM r2 for live surge nowcast, Prithvi-EO 2.0 for satellite-derived flood polygons (offline pre-computed). Apache-2.0 across the stack. Inference runs locally on the deploying machine; no vendor LLM is contacted at runtime.

Out of scope: engineering vulnerability (foundation/structural fragility), social capacity, financial absorption, sub-surface flooding (basement apartments, subway entrances). Datasets are vintage-bounded as noted per source above.

`; document.getElementById("paper").innerHTML = html; // Update tab title to reflect the subject document.title = `Riprap — ${place}`; } // Subset markdown for the briefing: `**Header.**` lines →

; `- ` lines // →