Skip to content

Backfill open-data: office -> roles[] #2478

Closed as not planned
Closed as not planned

Description

@shelltr

Stage 4 of the temporal-roles work. Split from the schema change so the two can land separately.

What

One-time rewrite of every published person file from office: {name, division_ocdid} to roles[], across ~3,430 files in 13 states (tx 964, mi 878, nj 496, wa 196, nh 187, ma 172, co 120, tn 113, id 95, sc 92, ca 73, nd 37, nc 7).

Follows the existing pattern in open-data/scripts/one_off/ — see reformat_people_yaml.py and backfill_person_ids.py for prior art on repo-wide YAML rewrites.

Why it is separate from the schema issue

The schema can merge behind a pinned shared (see the dependency-pinning issue) while the data has not moved yet. Bundling them means a single unrevertable step that breaks open-data CI for every file at once.

The hard part is not mechanical

Splitting office.name back into roles uses the same lossy parse we are removing — office_name_to_roles (pipelines/src/utils/people_utils.py:99) keeps only parts present in the alias map and drops the rest. Running the backfill through it would bake today's data loss permanently into history.

So the backfill needs a stricter rule:

  • Every part of every office.name must map to a role term, or the file is flagged for human review — never silently dropped.
  • Records with "Unknown Office" (the person_to_official fallback, people_utils.py:148) need an explicit decision.
  • .title()-cased values must be mapped back to canonical display names, not preserved as-is.

The audit issue produces the numbers needed to size this — how many files parse cleanly, how many need judgment.

History for backfilled roles

Backfilled roles have no observation history. Decide what first_seen_at means for them:

  • the record's existing updated_at, or
  • the jurisdiction's scraped_at (civicpatch.org/src/database/jurisdictions.py:638-651), or
  • null, with a marker that history begins at the backfill

Whatever we choose, it should be distinguishable from a genuinely observed first_seen_at.

Verification

Run against test-open-data first — the dev stack already points there (civicpatch.org/docker-compose.yml:16). Confirm validate_jurisdiction.py passes on every file, then a real E2E scrape → PR → merge → od_sync before touching the production repo.

Depends on

Schema change merged, shared pinned, audit complete.


Part of the temporal-roles work — see the tracking issue for ordering.

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

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions