QA Rubric
Field-by-field checks to apply against real scraper output.
city-scrapers-meetings-viewerthe Next.js app and docs you are readingQA rubric
Apply these checks field by field against the scraper's JSON output. Each check has a pass/fail criteria and links to the relevant schema section.
Rubric report
Paste a spider's JSON output below to run the mechanical half of the rubric - required fields, datetime format, vocabularies, link order, duplicates. The checks reuse the same generated schema and duplicate detection the app uses.
title
- Check: Official meeting name including the body name; no embedded date or time.
- Pass:
"City Council Regular Meeting". - Fail:
"Regular Meeting"or"Board Meeting - January 15, 2026 at 6:00 PM".
description
- Check: Present; usually empty unless the source provides one.
- Pass:
""or a real source-provided description. - Fail: Missing field, or a synthesized copy of the title.
classification
- Check: One of the city-scrapers-core classification constants - Advisory Committee, Board, City Council, Commission, Committee, Forum, Police Beat, Not classified.
- Pass:
"City Council"for a council meeting. - Fail: An invented type outside the vocabulary.
start
- Check: Naive datetime string
"YYYY-MM-DD HH:mm:ss"- no timezone suffix. - Pass:
"2026-01-15 10:00:00". - Fail:
"2026-01-15T10:00:00-08:00"(timezone-aware) or a missing field.
end
- Check: Naive datetime, and after
startwhen present. - Pass:
"2026-01-15 12:00:00"(two hours after start). - Fail: Before
start, or carrying a timezone suffix.
If end is not explicitly set by the spider, it defaults to start + 2 hours. Usually correct - but check it against the source, some meetings have
different durations.
status
- Check: One of
passed,tentative,confirmed,cancelled, and consistent withstart. - Pass:
passedfor past meetings,tentativeorconfirmedfor upcoming ones,cancelledonly when the source confirms cancellation. - Fail:
passedfor a future meeting, or a value outside the vocabulary.
location
- Check: Not empty unless
time_notesexplains why. - Pass:
{ "name": "City Hall", "address": "202 C St, San Diego, CA" }. - Fail: Empty
nameandaddresswith an emptytime_notes.
links
- Check: In priority order - agenda, minutes, video, other.
- Pass: Agenda first, then minutes, then a recording link.
- Fail: Video before minutes, or minutes before agenda.
all_day
- Check: Correct for the meeting type.
- Pass:
truefor all-day events with midnight start/end times. - Fail:
truefor a meeting with a specific start time.
duplicates
- Check: No records with the same start time and a similar title.
- Pass: All records are distinct.
- Fail: Two records with the same start and titles like "City Council Meeting" and "City Council Regular Meeting" - the viewer's duplicate detection uses significant-word overlap, not exact match.
The viewer's duplicate detection lives in lib/duplicate-detection.ts. It
groups records by start time and then checks significant-word overlap in
titles - generic words like "the", "meeting", "regular" are ignored. The
rubric report above runs the same algorithm.
Last updated on