Re-scoping the BPML sheet, and the columns that follow¶
Level: reference · deep dive
A proposal, written after running the reconciliation. It answers one question — what is the BPML sheet's job? — because the answer determines what columns it needs, and getting that order backwards would mean adding 162 rows to a sheet that shouldn't have them.
The finding that forces the question¶
The sheet references one of the library's 163 BV-backed elections. The instinct is that this is neglect and the fix is to add the other 162. It isn't, and it isn't.
Split the sheet's own rows by what they are actually about:
| rows | share | |
|---|---|---|
| BPML rows about the application | 102 | 91% |
| BPML rows about counting votes | 9 | 8% |
| Library elections, every one about counting votes | 163 | 100% |
The sheet is an application inventory: create an election, change a race, upload voters, download a CSV, log in, archive, preserve ballot secrecy. The library is a tabulation inventory: given these ballots under this method, is this the right winner.
Those are two different testing activities. The join returned 1 not because anyone neglected it, but because the two documents are about almost disjoint subject matter. No single test-id column can be the coverage map for both.
The clearest illustration is the row "Ranked Robin Voting - Single Winner." It cites BV1550, which exists in neither inventory, so the sheet reads as a coverage gap. Meanwhile the library holds 41 Ranked Robin elections, each cross-verified three ways. The row isn't uncovered — it's mis-pointed. It's pointing at one id when the honest answer is a whole family.
The re-scope¶
Two inventories, each owning one question, with a named seam between them.
| Owns the question | Verified by | Size | |
|---|---|---|---|
| BPML sheet | Does the application do the thing? | the Drive QA log (BVxxx functional tests) |
102 rows |
| The library | Given these ballots, is this the right winner? | runnable YAML cases with published answer keys | 163 elections |
The seam is those nine counting rows. A counting row's verification is a method family, not a test id — so it links to the library's by-method index, and the library's own count is the coverage number.
The sheet does not gain 162 rows. It gains nine links.
That also fixes the thing that made the sheet feel wrong to sort: today its Test Case column silently means two different things depending on which kind of row you're on. Naming the scope per row makes every other column unambiguous.
The nine seam rows, and what each should point at¶
| BPML row | Currently cites | Should point at | Library has |
|---|---|---|---|
| Voting Methods → Ranked Robin, single winner | BV1550 (nonexistent) |
RankedRobin cases | 41 |
| Voting Methods → Single-winner Approval | — | Approval cases | 15 |
| Voting Methods → STAR scoring/runoff divergence | BV90 (QA log only) |
STAR cases | 122 |
| Voting Methods → Plurality, single-winner | — | Plurality cases | 22 |
| Voting Methods → Multi-winner Bloc STAR | — | Bloc STAR cases | 23 |
| Voting Methods → Multi-winner Plurality | (process name repeated) | Plurality multi-winner | subset of 22 |
| Establish Election Procedures → Handling ties | — | the tie-behaviour cases | tie cases across methods |
| Tabulation → Distribution of Equal Support | — | Equal Support cases | STAR subset |
| Election → Verify Preference Matrix | BV705 (nonexistent) |
matrix cases | STAR subset |
Every "nonexistent" id above becomes a live link under the re-scope. Three rows that read as gaps today are in fact the best-covered things in the project.
Six of the nine resolve onto a method family. Three don't, and are curated instead. Ranked Robin, Approval, STAR, Plurality, Bloc STAR and multi-winner Plurality each map onto one method, so their coverage is that method's case count. The remaining three — Verify Preference Matrix, Handling ties, Distribution of Equal Support — are cross-cutting concerns rather than method families: their evidence is scattered across several methods, so a by-method index would be a link that doesn't answer the question.
Those three now carry hand-picked lists — 23 cases in total, printed with links in the reconciliation:
| Row | Cases | Spans |
|---|---|---|
| Handling ties | 10 | STAR, Bloc STAR, Ranked Robin, Plurality — every rung from a score tiebreak to a nine-way dead heat |
| Distribution of Equal Support | 5 | STAR — from all-equal ballots to the equal/opposite cancelling pair |
| Verify Preference Matrix | 8 | Ranked Robin, STAR, Bloc STAR — including a Condorcet cycle, the hardest grid to render |
The lists live in CURATED in reconcile_bpml.py and every entry is re-validated against the registry on each run — a case that is renamed, re-numbered or deleted is reported rather than silently dropped. The validator was checked against both failure modes (a stem that doesn't exist, and a real stem filed under the wrong test id) so it isn't vacuous. The page links are computed from the registry's own MD column, so they can't drift from where the cases actually live.
The column spec¶
Only now does this make sense. Seven columns, replacing six.
| # | Column | Values | Notes |
|---|---|---|---|
| 1–3 | L1 / L2 / L3 |
unchanged | the process hierarchy |
| 4 | Scope |
Application · Counting |
new, and the load-bearing one — it decides how columns 6 and 7 are read |
| 5 | Help URL |
a docs.bettervoting.com URL, or blank | blank = not published. This is the coverage dashboard the sheet was missing |
| 6 | Spec |
Drive design doc / functional spec link | split out of today's combined column |
| 7 | Verified by |
Application → a BVxxx from the QA log · Counting → a link to the library's by-method index |
one column, two readings, disambiguated by Scope |
| 8 | Status |
covered · gap · n/a · unchecked |
kills the ambiguous blank |
| 9 | Additional info |
unchanged | keeps EB-phase0 and similar cohort tags |
Why Status matters more than it looks. Today an empty cell could mean not needed, not written, or nobody has checked — three very different things that sort identically. That is why the sheet can't produce a percentage. With an explicit value it can, and gap becomes the filter that drives the work.
Why splitting column 4 matters. Today one column is headed "Functional Specification or BPML details or Training Document." Three artifact types in one cell means you cannot ask "which processes have user-facing help?" separately from "which have a spec" — and the first of those is the actual docs backlog.
Six defects to fix in the same pass¶
Found while reading the sheet; all cheap, none contentious.
VoterandElectors (Voters)are the same L1, split into two branches, both carrying Create / Change / Delete rows. Merge them.- Two rows have
co toas the L3 process name — placeholder text that shipped. - A row about the sheet, inside the sheet:
BPML - Overview → BPML - Overview → BPML - Overview. Election State / Status - Test, annotated "Same as Open?" — no.validElectionStatesis['draft','finalized','open','closed','archived']; there is no test state, and draft is the test mode. Verified from source in bv_draft_state_test_votes.md. Delete the row.- A stray row —
follow up on thissitting in the L1 column with a starvoting.org link in L2. Multi-winner Pluralitycites its own process name as a test case. It looks like coverage and isn't; under the re-scope it becomes a library link.
What this does not change¶
The sheet stays a backlog, not navigation. That verdict is unchanged and is argued in the docs information architecture: the L3 names are system functions, not the questions users type, and one user question scatters across several rows. Re-scoping makes the backlog sortable; it does not make it a site map.
Related: the reconciliation · docs information architecture · BV registry · all cases by method