Civics / Governance surface: elections, votes, laws, demographics, settlements (DiscordLink parity, no restart) #61
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Net-new Civics / Governance surface, the largest unconsumed vein in the pull-everything survey (#7) and the biggest remaining DiscordLink-parity gap under the epic (#37, which explicitly wants elections/laws/voting displays). All from data the server already exports - no game restart.
No-reset spine
Consume the civics action exporters (
/api/v1/exporter/actions) and the 10 civics/people daily series (/datasets/get) already live on the server. Reuse the shared CSV plumbing the trade wave stood up: streamed aggregation (crafting.py), the#5column-corrector and id-to-name join (/api/v1/citizens), and Time-to-day conversion. No new C# mod.Exporters
Vote,DidntVote,StartElection,JoinOrLeaveElection,WonElection,LostElection,DemographicChange,ResidencyChanged,BecomeCitizen,LeaveCitizenship,SettlementFounded,PlaceNewSettlementFoundation,StartHomestead, plus the civics/people series.Scope
get_eco_civics(text + structured JSON +_meta.uicard): active/recent elections and outcomes, turnout (Vote vs DidntVote), laws-in-effect where derivable, demographic trend (citizens gained/lost, residency moves), settlements founded. Names via the citizens join, falling back toCitizen #<id>./preview/civics.jsondata plane./civicsroute - elections + outcomes, turnout, demographics, settlements; cross-linked with/economyand/server, live badge on the homepage directory. Read thedatavizskill before charts.DiscordLink parity
Directly serves the epic's "elections and votes, laws, demographics" display set on the website instead of Discord embeds. Meets and exceeds by adding turnout and demographic trend over time.
Acceptance
ward exec smokepasses; thin/zero-data paths degrade gracefully.respx-mocked unit tests for the aggregation + citizen join./preview/civics.json, vitest component test,ward exec frontend-test/lintgreen.ward exec test/lintgreen;docs/FEATURES.mdupdated. Parent: #37, survey #7.🔒 Reserved by
ward agent --driver claude— containerengineer-claude-eco-app-61on hostKAI-DESKTOP-TOWERis carrying this issue (reserved 2026-07-05T04:39:30Z). Concurrentward agentruns are blocked until it finishes or the reservation goes stale (2h0m0s TTL);--forceoverrides.Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).
— Claude (she/her), via
ward agentWARD-OUTCOME: done - get_eco_civics MCP tool, /preview/civics.json plane, and the SPA /civics route landed on main (
a6848b9), closing #61.This one went smoothly where I expected friction and fought back where I didn't. The aggregation itself was easy - the trade wave left such a clean spine (streamed CSV,
_corrected_index, the citizens join, Time/86400) that civics.py was mostly a matter of picking the right columns and folding. The genuinely honest caveat: the exact civic CSV headers and the 10 civics/people series names were never captured live (the #7 survey is inventory-only for those families), so I parse a generous candidate-column list and treat the daily series as the same names doubling as datasets. On a live server the turnout/demographic counts and the citizen actors are solid; the subject labels (election/settlement names) are best-effort until someone probes the real headers. I filed that as a follow-up in docs/civics.md rather than pretending it's nailed down.The thing that actually ate the most time was respx:
url__regexandpath__regexsilently refused to match in this env, and - the real trap -@respx.mock(assert_all_called=False)creates a local router whilerespx.get(...)registers on the global one, so every route read as "not mocked". Threading the injectedrespx_mockrouter through fixed it. Worth remembering for the next exporter test.The other surprise was traffic: three sibling surfaces (social, world, progression) landed on main while I worked, so I resolved the same additive conflicts three times - render helpers, dispatch blocks, preview routes, and a
.chart-legend/.legend-swatchCSS collision I namespaced tociv-. All additive, nothing lost, full suite green after each (453 py + frontend). Confident in the result; the only soft spot is the header guesswork above.