// REPORT.md — `report.json`'s key tables, GENERATED from the schema // (./schema.ts) and the verdict vocabulary (./verdicts.mjs). The citation keys // are CITATIONS.md's (lib/citations/docs.ts), linked rather than repeated. // // A pure renderer: common/bin/file-schemas-docs.ts writes the file and // report/docs.test.ts asserts the committed bytes are what this returns. import { cell } from "../settingsDocs"; import { GENERATED_SCHEMA_DOC, REGENERATE_SCHEMA_DOC, renderKeyTable } from "../citations/docs"; import { CLAIM_FIELD_DOCS, ENTRY_FIELD_DOCS, REPORT_FIELD_DOCS, REPORT_FORMAT, REPORT_SLIDES_FIELD_DOCS, REPORT_VERSION, SECTION_FIELD_DOCS, SLIDE_FIELD_DOCS, claimSchema, entrySchema, reportSchema, reportSlidesSchema, sectionSchema, slideSchema, } from "./schema"; import { VERDICT_DEFAULTS, VERDICT_LABEL_MAX, VERDICTS } from "./verdicts"; export function renderReportMarkdown(): string { const out: string[] = []; out.push("# report.json keys"); out.push(""); out.push(GENERATED_SCHEMA_DOC); out.push(""); out.push( `One cited report, format \`"${REPORT_FORMAT}"\`, version ${REPORT_VERSION}, ` + "persisted to `transcripts/sites//reports//report.json` beside " + "its `stills/` and `sources//`; a relative path in it is relative to " + "that directory. A site's `reports` list in `site.json` is the published, " + "ordered list — see [SITE.md](SITE.md); a report directory it does not name is a " + "draft. The schema is `common/lib/report/schema.ts`; its citations and sources " + "are the citation model's — see [CITATIONS.md](CITATIONS.md). A `notes.json` " + "beside it holds the operator's notes on the article (umtool, `umtool notes`; " + "[umtool/docs/notes.md](umtool/docs/notes.md)) and is never published.", ); out.push(""); out.push( 'A **fact-check** (`"kind": "factcheck"`) is sections (chapters) of claims, each ' + 'with a verdict and its findings. A **sweep** (`"kind": "sweep"`) is sections ' + "with no verdicts, or bodies that cite inline. Either may keep a **timeline** " + "(`entries`): dated entries appended over time. Markdown fields (`summary`, an " + "entry's `body`, a section's `body`, a claim's `findings`) cite with " + "`[label](cite:)`.", ); out.push(""); out.push( "`common/lib/report/validate.ts` reports every problem with its JSON path: an " + "unknown key, a reference that names nothing (a listed citation, a claim's " + "source sentence, a `cite:` link, the subject), a section, claim or entry id used " + "twice (they share one namespace: the report page's anchors) or named for one of " + "the page's own anchors, a sweep's claim with a verdict, `updated` before " + "`published` (or an entry's before its `date`), a date that is not one, a slide " + "field out of bounds (below), and every citation problem " + "CITATIONS.md lists. Whether a still or the report's video exists, whether the " + "video fits the publish limit, and whether a quote matches its cues are checked " + "when the site is composed.", ); out.push(""); out.push(REGENERATE_SCHEMA_DOC); out.push(""); renderKeyTable(out, "## The document", reportSchema.shape, REPORT_FIELD_DOCS); renderKeyTable(out, "#### `sections[]`", sectionSchema.shape, SECTION_FIELD_DOCS); renderKeyTable(out, "#### `sections[].claims[]`", claimSchema.shape, CLAIM_FIELD_DOCS); out.push("## The timeline"); out.push(""); out.push( "A report may keep a timeline: `entries`, dated blocks a reader sees newest first under " + "**Timeline**, after the summary and before what the check found and the claims — on its " + "page, in its exports and as one slide each. Entries of the same instant keep their order " + "in the file. Each entry's id is its anchor (`#`). Its citations are numbered with " + "the rest, in the page's order. A report with a newer entry than its own `updated` is " + "shown as updated then (the index, the header, the feeds). On a site with a public " + "`siteUrl`, compose publishes the timeline as feeds beside the page — " + "`/reports//feed.xml` (RSS 2.0) and `/reports//feed.json` (JSON Feed 1.1), one " + "item per entry, its permalink the page at its anchor — and the page links them " + "(\"Subscribe\", and ``). A site with no `siteUrl` publishes no feed.", ); out.push(""); renderKeyTable(out, "#### `entries[]`", entrySchema.shape, ENTRY_FIELD_DOCS); out.push("## Slides"); out.push(""); out.push( "A report is also read as slides — its page's **Slides** view (`?rv=slides`), the **Overview** " + "that pairs each part of the article with its slide (`?rv=iso`), `slides.html` and `slides.pdf` " + "— built by `common/lib/report/slides.ts` from the same view as the article: a title slide, " + "\"In brief\", one per timeline entry (newest first), \"What the check found\" (a fact-check), " + "one slide per section and per claim, " + "then a sources slide. With no slide fields a report still has slides, derived from its text: " + "a section shows the first two sentences of its body (else its claims), a claim its verdict, " + "gist and the evidence of its first listed citation. The fields below make them good. A " + "slide's points cite only what the article cites elsewhere, and its `cite` names a citation " + "of its own section or claim. Every slide links back to its place in the article (the " + "section's or the claim's anchor).", ); out.push(""); renderKeyTable(out, "#### `slides`", reportSlidesSchema.shape, REPORT_SLIDES_FIELD_DOCS); renderKeyTable(out, "#### `sections[].slide` and `sections[].claims[].slide`", slideSchema.shape, SLIDE_FIELD_DOCS); out.push("## The verdicts"); out.push(""); out.push( "The shared vocabulary (`common/lib/report/verdicts.mjs` — the one copy; " + "report-to-video's fact-check stamps read it too), in the order a tally lists " + `them. A report's \`verdicts\` overrides a label (one line, at most ${VERDICT_LABEL_MAX} ` + "characters) or a colour (`#rgb` or `#rrggbb`), each on its own.", ); out.push(""); out.push("| Verdict | Default label | Default colour |"); out.push("|---|---|---|"); for (const v of VERDICTS) { const d = VERDICT_DEFAULTS[v]; out.push(`| \`${v}\` | ${cell(d.label)} | \`${d.color}\` |`); } out.push(""); out.push("## Making a report from what exists"); out.push(""); out.push( "`archilyzer reports convert --out ` makes a " + "report of a /sweep report (markdown: its `#` heading the title, each `##` section a " + "section, each list item or paragraph that cites a claim), an /ask answer (its " + "`[n @ mm:ss]` markers resolved against its sources) or a report-to-video manifest " + "(chapter cards the sections, `claim` entries the claims, clips, stills and posts the " + "citations), and writes it only when it validates. A /sweep or /ask citation carries " + "one second: `--channels-dir ` widens it through the record's " + "cues to whole sentences (else it is the second plus 10 s) and finds the channel that " + "keeps a post cited by its platform link. `archilyzer reports to-manifest " + "--out ` goes the other way: a starter manifest — a title card, a " + "chapter card per section, each claim's still and clips stamped with its verdict, its " + "posts. The converters are `common/lib/report/convert*.ts`; the warnings say what a " + "conversion left out.", ); out.push(""); return out.join("\n"); }