Every claim links to the exact public page or video moment. Evidence rules
Set by Justin Smith on 2026-10-11. Applies to every dossier in dossiers/ and to anything the site says about a named person.
Every claim about a person carries a public link to the exact evidence for it. "Exact" means a reader who clicks lands on the statement, vote, or page itself: the right page of the PDF, the right second of the video, the right agenda item. A link to a site's home page or a meeting list is not evidence.
raw/…, video/granicus-3165/…, transcript.json or speaker_evidence.json are working files. They can sit in an HTML comment for maintainers, never as the citation.K-01, …) so it can be audited, and every claim line must contain at least one https:// link. tools/check_dossier_links.py enforces this and re-checks every link (HTTP 200 and right document type; for #page=N, the PDF has at least N pages).| Claim type | Required evidence link | Label |
|---|---|---|
| Vote / motion / outcome (minutes exist) | Approved minutes PDF at the page: MinutesViewer…#page=N |
none needed (official record) |
| Vote / outcome (minutes not out yet) | Granicus video at the moment of the vote: MediaPlayer…&entrytime=S |
"machine transcript, unofficial; replace with minutes when approved" |
| Sponsorship / authorship | Docket/agenda PDF at the page with the BY: line or the signed letter: AgendaViewer…#page=N |
none |
| Quote from a document | The PDF page (#page=N) or web page (optionally with a #:~:text= fragment) where the words appear |
none; must be verbatim, [sic] for errors |
| Paraphrase from minutes | The minutes page (#page=N) |
[paraphrase from minutes] |
| Quote from a meeting video | Video at the timestamp (&entrytime=S or YouTube &t=Ss) |
"machine transcript; check against the video" (wording is ASR) |
| Machine speaker attribution (who said it) | Video at the timestamp of the remark plus a link to the moment that identifies the voice (for example, the chair being addressed by name) | "Speaker identified automatically by voice matching, not by official record" |
| School board vote | BoardDocs item link (shows "Motion & Voting" with mover, seconder, Yea/Nay by name) | none |
| Self-description (bio, questionnaire, campaign site) | The page, ideally with a #:~:text= fragment |
"her/his own description, not independently verified" |
| Secondary source (news) | The article URL | "secondary source" |
| Election result | Cuyahoga BOE certified results PDF at the page | "unofficial" if it is not the certified canvass |
| Source | Format | How it was verified | Status |
|---|---|---|---|
| Granicus video at a timestamp | https://onelakewood.granicus.com/MediaPlayer.php?view_id=2&clip_id=<clip>&entrytime=<seconds> |
Clip 3165: HTTP 200 (redirects to /player/clip/3165?view_id=2&entrytime=720). The server writes the value into the player config (start_time: 720; start_time: 0 without it), and Granicus's own share/embed code builds links with &entrytime=. Player chapter cuepoints (13:52 item 3, 14:08 item 4) line up with our ASR times (13:52, 14:10), so ASR seconds = player seconds for Granicus clips. |
Works. Caveat: actual playback couldn't be watched from the box (the video CDN refuses the box's headless browser), so the seek itself was confirmed from the player config, not on screen. |
| Granicus minutes PDF at a page | https://onelakewood.granicus.com/MinutesViewer.php?view_id=2&clip_id=<clip>&doc_id=<uuid>#page=<N> |
200, 302 → DocumentViewer.php?file=…pdf, application/pdf, served inline (no attachment header). Browsers keep the #page fragment across the redirect. N is the PDF page index; for Lakewood minutes it equals the printed page number. |
Works in Chrome, Edge, Firefox, desktop Safari. iOS/Android viewers may ignore #page, so always also write "p. N" in the text. |
| Granicus agenda/docket PDF at a page | https://onelakewood.granicus.com/AgendaViewer.php?view_id=2&clip_id=<clip>#page=<N> (or &event_id=<id> before a video exists) |
Same as minutes. N ≠ the docket's printed "pg.": Oct 5 and Sept 8 dockets have 3 cover pages, so PDF page = printed page + 3 (printed pg. 161 = #page=164). Always compute N from the PDF itself. |
Works (same caveat) |
| BoardDocs agenda item | https://go.boarddocs.com/oh/lwcsoh/Board.nsf/goto?open&id=<ITEM_UNIQUE> (item ids from BD-GetAgenda); meeting: same with the meeting id |
goto?open&id=DXWL925545A1 loaded in a real browser shows "Agenda Item Details … Motion & Voting … Motion by Lisa Dopman, second by Nora Katzenberger … Yea: …". 200 with a browser User-Agent. |
Works |
| YouTube at a timestamp | https://www.youtube.com/watch?v=<id>&t=<seconds>s |
Video id w4HUbUbaYdE confirmed public via YouTube oEmbed (200, title "09-28-2026 Lakewood City Schools Board of Education Meeting"). &t= is YouTube's standard start-time parameter. |
Use it, but the box can't load the watch page itself (YouTube returns 429/"sorry"), so the link checker verifies the id via oEmbed, not the seek. YouTube auto-caption timing matches the video timeline. |
| City / other web page | Plain URL; for a quote, optionally add a text fragment #:~:text=<exact words> |
lakewoodoh.gov/city-council/sarah-kepple/ etc. 200; quoted text confirmed in the fetched HTML. |
Works. Text fragments highlight in Chrome/Edge/Safari and Firefox 131+; older browsers just open the page, so the quote must also be in the dossier text. |
| Cuyahoga BOE results PDF | https://boe.cuyahogacounty.gov/elections/GetDocumentById/<uuid>/#page=<N> |
Certified "NOV25GEN – Official Results by Contest" (2025-11-19): 200 application/pdf; Lakewood Council at Large is on PDF page 17. |
Works |
https://onelakewood.granicus.com/videos/<clip>/captions.vtt: 200 but empty for clip 3165. Not usable as evidence.DocumentViewer.php?file=… direct file URLs: they work today, but they're the redirect target, not the canonical link. Cite MinutesViewer/AgendaViewer instead.archive-stream.granicus.com/…m3u8) needs a Referer header and has no timestamp UI. Never cite it.raw/, video/, data/), our ASR JSON, and our speaker-label files are never evidence.#page=N from the PDF itself (pdftotext + search), or the second from the ASR segment start (link about 2 s early).python3 tools/check_dossier_links.py dossiers/<name>.md. Every link must return 200, and every claim line needs a link.