8000 Keep chapter data in sync: one owner per fact, checked on PR and reconciled weekly · Issue #161 · rladies/jinx · GitHub
[go: up one dir, main page]

Skip to content

Keep chapter data in sync: one owner per fact, checked on PR and reconciled weekly #161

Open

Description

@drmowinckels

Chapter data lives in five places today: the website's data/chapters/*.json, the Airtable organiser base, the Airtable chapters intake table, GitHub teams and repos, and Meetup. Nothing keeps them honest, and they have already drifted.

The rule: one owner per fact

Bidirectional sync between five systems is 20 edges. One owner per fact is five.

Fact Owner Everyone else
A chapter exists, its status, its organisers website data/chapters reads
Organiser PII (email, GitHub, Meetup handle) Airtable organiser base reads
Intake: applications up to "Onboarded" Airtable chapters table stops there; the website takes over
Team and repo existence GitHub reads
Events Meetup reads

Two corollaries:

  • Derive, don't store. The filename, the team slug, the repo name and the mailbox address are all functions of city/region/country. Storing them invites a second spelling. jinx already has chapter_filename(); everything else should route through one function too.
  • No bidirectional sync. Nothing writes back to an owner it does not own. Airtable intake hands off to the website once, at "Onboarded", and stops.

Two mechanisms, not one

Prevention — a PR check on the website. Catches drift at the only moment it is cheap to fix. Scope: schema, filename matches the city/region/country it is derived from, urlname agrees with social_media.meetup, and no escaped byte placeholders. Only files the PR touches, so existing drift does not block unrelated work.

Detection — one weekly chapter_reconcile(). Everything the PR check cannot see because it is cross-system: a chapter with no Meetup group, a team with no chapter, a chapter whose organisers do not match Airtable. Reports to a single issue, updated in place, never a new issue per run.

Baseline drift, measured today

Running the checks over all 384 chapter files:

Check Files
Schema 0
Escaped byte placeholders 57
Filename mismatch 32
urlname vs social_media.meetup 5

The 57 are the urgent one and were not in the original proposal — I found them building the check. Accented characters have been written into the data as R's undecodable-byte placeholder: Ceará is stored as the literal seven characters Cear<e1>, Bogotá as Bogot<e1>. It affects city names, region names, and organiser names — Ana Betty Villaseñor, Isabella Velásquez, Bea Hernández and ~50 others are misspelled on the live site. The fix is mechanical (<xx> is the latin1 codepoint) but should be a reviewed PR, not a blind sweep.

The 32 filename mismatches are a mix of the same encoding damage and genuine data drift (chile-santiago.json holds city "Santiago de Chile" and region "Metropolitana"; two files spell the state "Missisippi").

Deduplication this exposes

scripts/json_shema/chapter.json in the website repo and inst/schemas/chapter.json in jinx are byte-identical copies. They have not drifted yet, which is the argument for consolidating now rather than after they do. jinx should own it; the website's validate_jsons.R drops its chapter block.

Order of work

  1. Website PR check — jinx side in progress
  2. Repair the 57 mojibake files
  3. Repair the 32 filenames and 5 urlname disagreements
  4. chapter_reconcile() weekly report
  5. Route team slug, repo name and mailbox address through one derivation

Deferred

Checking country against the canonical Countries list in the Airtable World base would need Airtable credentials in the website repo's CI. Better placed in chapter_reconcile(), which already runs inside jinx with those credentials.

🤖 Generated with Claude Code

Activity

  1. drmowinckels commented on Sep 27, 2026

    @drmowinckels
    MemberAuthor

    Correction to the drift table above: those counts were measured against my local checkout of the website, which sits on feat/cloudflare-web-analytics, not main. The filename and urlname figures were wrong.

    Measured properly against origin/main, all 384 files:

    Check Files
    Schema 0
    Escaped byte placeholders 57
    Filename mismatch 32 12
    urlname vs social_media.meetup 5 6

    The 57 was right, and is fixed in rladies.github.io#610 — that PR takes encoding damage to zero.

    Afterwards the filename count reads 14 rather than 12, which is not a regression: jinx skips the filename check on a file whose text it knows is corrupt, so argentina-cordoba-cordoba.json and brazil-esperito-santo-vitoria.json become visible once the encoding is repaired. Both have real, pre-existing problems.

    Step 2 of the order of work is done. Steps 3 (filenames and urlname) and 4 (chapter_reconcile()) remain.

    The sixth urlname case needs a human: switzerland-geneva.json has social_media.meetup of rladies-Genève, which can't be a real Meetup slug.

  2. drmowinckels commented on Sep 27, 2026

    @drmowinckels
    MemberAuthor

    Two more findings from repairing the data, both pre-existing and neither addressed yet.

    121 chapter files never become pages. content/chapters/_content.gotmpl keys each page path on urlname, and only 263 of the 384 files have one, so a local hugo build produces 263 chapter pages. Whether those 121 should be rendered, or are deliberately hidden (many are prospective), is a question for the chapters team.

    Form boilerplate is reaching the data. mexico-chiapas-tuxtla-gutierrez.json listed the literal string "comma separated" as an organiser — leftover placeholder text from whatever form created it. That file also had a lowercase country, a slug in the city field, and a zero-width space in its filename, so it looks like one bad intake path rather than a typo. Worth finding which form produced it before the chapters Airtable table starts feeding this directory.

    Both are fixed for that one file in rladies.github.io#611; neither is fixed at the source.

  3. drmowinckels commented on Sep 27, 2026

    @drmowinckels
    MemberAuthor

    Retracting the "121 chapter files never become pages" point from my previous comment — that is the expected state, not a defect. A chapter that has not finished setup has no Meetup group yet, so it has no urlname, so it has no page. Working as intended.

    Cross-tabulating confirms it:

    Status Has urlname No urlname
    prospective 5 115
    active 231 7
    retired 26 0

    115 of the 121 are prospective, exactly as expected.

    The residue is the part that might be worth a look: 7 chapters are active but have no urlname, so they render no page on the site.

    croatia-zagreb · germany-marburg · kyrgyzstan-bishkek · norway-trondheim · united-kingdom-sheffield · usa-mississippi-oxford · venezuela-caracas

    I do not know whether that is wrong. A chapter could legitimately be active and organise somewhere other than Meetup — usa-mississippi-oxford has an empty social_media entirely. If active-without-Meetup is not supposed to happen, this is a good cross-field rule for chapter_reconcile(): warn when status is active and urlname is missing. If it is legitimate, there is nothing to do and the 5 prospective-with-urlname are just chapters whose status has not been flipped after setup finished.

    Leaving it as a question rather than adding the check.

  4. drmowinckels commented on Sep 27, 2026

    @drmowinckels
    MemberAuthor

    Checked the 7 active-without-urlname chapters against meetup_archive/data/chapters.json (generated from the Meetup API, and the same file the site merges at build time). Six of the seven have a live Meetup group we simply never recorded.

    Chapter Meetup group Members
    kyrgyzstan-bishkek rladies-bishkek 206
    united-kingdom-sheffield rladies-sheffield 90
    usa-mississippi-oxford rladies-oxford-ms 109
    venezuela-caracas rladies-caracas 48
    croatia-zagreb rladies-zagreb 9
    norway-trondheim rladies-trondheim 137

    Only germany-marburg has no group.

    Widening it to the whole archive: 17 of the 251 Meetup groups are not referenced by any chapter file. Eleven belong to a chapter we already have, which is just a missing link:

    RLadiesJeddah (412) · rladies-bishkek (206) · rladies-oxford-ms (109) · rladies-sheffield (90) · rladies-aarhus (73) · rladies-kuala-lumpur (69) · rladies-caracas (48) · rladies-waltham (27) · rladies-cincinnati-oh (16) · rladies-albany-ny (5) · rladies-geneve (7)

    Six have no chapter file at all — roughly 850 members invisible to the site:

    rladies-muscat Oman (420) · rladies-kano Nigeria (169) · rladies-comitan Mexico (161) · rladies-tlalnepantla Mexico (55) · rladies-la-paz-mexico Mexico (35) · rladies-wake-forest USA (8)

    Two traps for whoever does this: rladies-la-paz-mexico is not bolivia-la-paz.json, and rladies-oxford-ms is Mississippi, not united-kingdom-oxford.json. Matching on city alone pairs both wrongly — I did exactly that on my first pass.

    The archive also settles four of the six urlname disagreements, since only one side of each exists as a real group:

    Chapter Real slug Wrong field
    iran-orumiyeh rladies-urmia (332) social_media.meetup
    mexico-san-cristobal-de-las-casas rladies-sancristobal (129) social_media.meetup
    morocco-rabat rladies-rabat-ma (700) social_media.meetup
    singapore-singapore rladies-singapore-sg (54) urlname
    switzerland-geneva rladies-geneve (7) both — see rladies.github.io#607
    mexico-chiapas-tuxtla-gutierrez neither exists prospective, no group yet

    The three social_media.meetup-only fixes are safe. The urlname ones move the chapter's page URL, since the page path derives from urlname — a maintainer call.

    This is a good argument for chapter_reconcile() doing exactly this join on a schedule.

  5. drmowinckels commented on Sep 27, 2026

    @drmowinckels
    MemberAuthor

    Tallied urlname against social_media.meetup across all 384 files on main:

    Files
    Both present, identical 256
    Neither (chapter not set up yet) 120
    Both present, differ 6
    social_media.meetup only, no urlname 2
    urlname only, no meetup 0

    So they agree in 256 of the 262 cases where both exist, and urlname is never the field that is missing.

    social_media.meetup has no consumers. Nothing in the theme or the site config reads it. urlname does all the work: the page path in _content.gotmpl, the archive join in merge_chapters, the event filter and the "Join chapter" button in chapters/single.html (which builds meetup.com/{{ .Params.urlname }} directly). social_media.meetup is a second copy of the same fact that nothing looks at — exactly the "derive, don't store" case from the top of this issue, and the reason all 6 disagreements went unnoticed.

    Proposal: delete social_media.meetup once the two values it uniquely holds are moved into urlname, and drop the urlname check from the validator, because there is nothing left to disagree with.

    Correcting myself from an earlier comment: I said six of the seven active-without-urlname chapters had a group "we simply never recorded". Not true for two of them. croatia-zagreb and norway-trondheim had their group recorded all along, in social_media.meetup — just not in the field the site reads, which is why their pages do not exist. The accurate split is:

    • Recorded in the wrong field (2): croatia-zagreb -> rladies-zagreb, norway-trondheim -> rladies-trondheim
    • Not recorded anywhere (4): kyrgyzstan-bishkek, united-kingdom-sheffield, usa-mississippi-oxford, venezuela-caracas
    • No group exists (1): germany-marburg
  6. drmowinckels commented on Sep 27, 2026

    @drmowinckels
    MemberAuthor

    Correcting myself again, and this one matters: I said social_media.meetup has no consumers. That is wrong. I grepped the layouts for social_media and for .meetup and concluded nothing read it, but the consumer reaches it generically — chapters/single.html and chapters/list.html both pass the whole social_media map to partials/social_media.html, which dispatches on the key name into partials/funcs/social_media.html, where meetup builds https://www.meetup.com/%s.

    So it renders a Meetup social icon, and the duplication is visible on every chapter page. A correct chapter shows the same Meetup URL twice:

    meetup.com/rladies-oslo          <- social icon, from social_media.meetup
    meetup.com/rladies-oslo          <- "Join chapter" button, from urlname
    

    And a wrong one shows two different broken links. Geneva, live today:

    meetup.com/rladies-Gen%c3%a8ve   <- social icon
    meetup.com/rladies-geneva        <- "Join chapter" button
    

    Neither is the real group. That is the cost of storing the same fact twice: one copy went stale and nothing compared them.

    The conclusion is unchanged, but for a better reason. urlname stays canonical — it drives the page path, the archive join, the event filter and the join button. social_media.meetup should go, and the icon disappears with it, leaving exactly one Meetup link per chapter instead of two.

    Separately, 31 chapters link to a group that is not in the archive: 23 retired (their groups are gone), 6 active, 2 prospective. Gating the link on meetup_id — which merge_chapters sets only on a successful archive match — removes all 31.

  7. drmowinckels commented on Sep 27, 2026

    @drmowinckels
    MemberAuthor

    Pinged all 272 chapter urlnames directly against meetup.com to see which groups actually exist. This works despite the bot wall: a real group's HTML carries <meta property="og:title" content="<Group Name> | Meetup">, a dead slug carries none. Two gotchas worth recording — mixed-case slugs answer 308 and must be followed (urllib does not by default), and a request failure must never be counted as dead.

    Status Group live Group dead
    active 235 1
    prospective 7 2
    retired 4 22

    The single dead active is switzerland-geneva (rladies-geneva), fixed in rladies.github.io#612. With that merged, every active chapter has a live Meetup group.

    The ping is a better signal than the archive, which is missing real groups: rladies-tucson-az ("R-Ladies Tucson AZ") and the Warsaw collaboration are both live but absent from it. It also settles oman-masqat, which 308-redirects to R-Ladies Muscat, so that value was non-canonical rather than broken.

    The important find is that status is stale. Seven chapters marked prospective have live groups — they finished setup and nobody flipped the field:

    algeria-algiers · denmark-aarhus · malaysia-kuala-lumpur · usa-colorado-aurora · usa-colorado-fort-collins · usa-massachusetts-waltham · usa-new-york-albany

    And four marked retired still have live groups: benin-cotonou, brazil-parana-curitiba, switzerland-lausanne, usa-ohio-cincinnati. Either those chapters are still running or the groups were never closed.

    That matters for rladies.github.io#613, which gates the Meetup link on status: it is right for the 24 genuinely dead, but wrong for those 11.

    Recommendation, in the spirit of one owner per fact: do not add a stored "group is live" flag and do not ping at build time — that is ~270 requests per build against a host that rate-limits, and a transient failure would either hide a good link or fail the build. Put the ping in chapter_reconcile() on a schedule and have it correct status. Then status is trustworthy, and the simple template gate in #613 is correct by construction.

  8. drmowinckels commented on Sep 27, 2026

    @drmowinckels
    MemberAuthor

    Profiled all 120 prospective chapters against the Meetup archive's events.json (5,240 events) and the liveness ping.

    Chapters
    No urlname, no group, no event ever 111
    Live group, never ran an event 4
    Ran events, then stalled 1
    Group existed and is now gone 2
    Running events right now 2

    The two at the bottom are not prospective by any reading: usa-colorado-aurora has run 21 events, most recently 2026-01-14, and usa-colorado-fort-collins 5 events, most recently 2026-04-10. algeria-algiers ran 3 but nothing since 2020-11. Four more — denmark-aarhus, malaysia-kuala-lumpur, usa-massachusetts-waltham, usa-new-york-albany — have a live group but have never run an event.

    No new status needed

    The guide already defines the categories, in global-team/monitoring:

    • Unbegun (created in the past 6 months, no events yet)
    • Active (have run an event in the past 6 months or have an upcoming event in the near future)
    • Inactive (no events in the past 6 months, no upcoming events)

    So "never started properly" is Unbegun, and it expires after six months — past that a chapter with no events is simply Inactive. The website's status field (active / prospective / retired on DATE) does not use these words at all, which is its own mismatch worth resolving.

    Mapped onto that vocabulary, the rule is mechanical:

    Condition Guide category Action
    Event in the last 6 months Active flip to active (2 chapters)
    No events, created < 6 months ago Unbegun leave alone
    No events, created > 6 months ago Inactive flag for the chapters team

    The blocker

    That rule needs to know when a chapter applied, and we cannot currently tell. 118 of the 120 prospective files were added to git in a single bulk import on 2023-01-04, so the git date is the import date, not the application date. Only mexico-chiapas-tuxtla (2023-05-31) and usa-colorado-aurora (2023-09-19) have a real one.

    The intake date belongs to the Airtable chapters table, which this issue already assigns as the owner of intake. Until that is populated, the six-month clock can only run for chapters that come through it — which is fine going forward, and means the existing 111 need a one-off human pass rather than an automated flip.

    Happy to build chapter_prospective_audit() as a read-only report on the two signals we do have (events and group liveness), so the 9 interesting cases surface without touching the 111.

  9. drmowinckels commented on Sep 27, 2026

    @drmowinckels
    MemberAuthor

    Correcting my last two comments on this. I claimed the Meetup signal was unreliable because chapters had moved off the platform, and cited RLadies+ Oslo as a chapter that was quiet on Meetup but still running.

    Both wrong. Chapters are asked to create their events on Meetup even when they advertise them elsewhere, precisely so activity can be tracked — so an absent event is a real gap, not an artefact. And Oslo is genuinely inactive; I inferred otherwise from the fact that its organiser was in front of me, which was not evidence.

    So the numbers stand as measured: 2 chapters to promote, 20 quiet for 6-12 months, 310 with nothing for a year or more. 193 of those 310 are currently stored active.

    One thing to fix in the guide while this is fresh: global-team/monitoring still tells a reactivating chapter "There is no need to use the platform meetup.com, please let us know what platforms you plan to use" (Email template D, point 4c). That is the opposite of the current expectation, and it is aimed at exactly the organisers we most need posting events. It is also what led me astray.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    3AA1 No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0