# DFI Builder

A tool for scoring a country's democratic fitness. You describe a country's legal framework in a YAML file; the Builder produces Excel scorecards across all seven categories of the **Democratic Fitness Indicator** plus a cross-category summary giving the country's overall DFI percentage.

The Builder covers all seven DFI categories end to end:

| # | Category | Weight |
|---|---|---:|
| 1 | Elections | 25% |
| 2 | Holding Politicians Accountable | 15% |
| 3 | Parliamentary Legislative Initiative | 5% |
| 4 | Citizens-Initiated Referenda | 10% |
| 5 | Citizens' Assemblies | 15% |
| 6 | Safeguards | 15% |
| 7 | Ability to Change the Constitution | 15% |

## What the Democratic Fitness Indicator measures

The DFI asks one question: *to what degree can citizens actually exercise power?* It evaluates the legal architecture of a political system across seven categories — election design, accountability mechanisms, the right to initiate legislation, referenda, citizens' assemblies, institutional safeguards, and the procedure for changing the constitution itself.

The scoring is mechanical: each category's spec lists the discrete options that exist in real-world systems and assigns each a numeric score. The Builder turns the country's YAML answers into an arithmetic result with a complete audit trail. The judgement work — figuring out which spec option matches the country's actual law — is done by the YAML author. The Builder doesn't interpret; it computes.

See `Measuring Democracy with DFI - ready.docx` for the methodology paper. The authoritative scoring rules are in `New scoring guidelines/`; the framework specs (one per category) are in `Main DFI framework/`; research and verification methodology lives in `Research guidelines/`; focused investigation guides for high-stakes Cat 1 + Cat 2 questions live in `Special research requests/`.

---

## What to read, in order

Scoring a country from scratch involves three documents, in this sequence:

1. **`README - DFI Builder.md`** (this file) — covers the full workflow end-to-end and the build-system mechanics. Start here.
2. **`README - DFI Research.md`** — the research-phase guide. How to produce the per-category research reports that become the inputs for YAML authoring. Read after this file's Phase 1 below.
3. **`Per-category research agent guide.md`** — the working manual for Cat 1–7 research. Per-category reading lists, common pitfalls, and worked examples from the 20-country corpus. Read alongside file 2 when you start the research phase.

The three files are layered: this README sets the workflow; the Research README drills into Phase 1; the Per-category research agent guide is the operating manual for that drill-down. Don't skip the order — the Per-category guide assumes you've read the Research README, which assumes you've read this one.

---

## Scoring a new country from scratch

This is the main path. There are six phases. Plan on **2–4 hours of focused work** for a country you're not yet familiar with — most of that is reading the law, not running the tool.

### Phase 1 — Research

Before opening the Builder, conduct the research phase as described in **`README - DFI Research.md`** and the **`Per-category research agent guide.md`**. This phase produces seven per-category research reports for the country (one per DFI category), plus several focused special-research outputs for high-stakes questions: five for Cat 1 (foreign donation loopholes, public funding parts 1 and 2, electoral disproportionality, intentional gerrymandering) and one for Cat 2 (accountability institution).

The research phase is deliberately separated from the scoring phase: research establishes the facts; scoring applies the framework to those facts. Each per-category report documents, row by row, the country's constitutional and statutory position with direct quotes and primary-source citations, then proposes the matching framework classification. The YAML author transcribes those proposed classifications mechanically — the methodology calls happen at the research stage, not at YAML time.

In short, for each category you'll need:

- **Cat 1 (Elections)**: electoral code + campaign finance act + constituency-boundary law. Voting method per chamber, district structure, donation regime (foreign / corporate / individual), spending limits, public funding rules, disclosure threshold (with the country's hourly minimum wage as benchmark), and structural distortions (monarch's PM-selection power, unelected upper house, presidential dissolution, Gallagher Index for disproportionality, currently-in-force boundary intent).
- **Cat 2 (Accountability)**: recall mechanisms (presidential, full-parliament, partial, individual legislator), citizen-initiated abrogative referendum, Freedom of Information Act, and any independent accountability institution that investigates elected officials.
- **Cat 3 (PLI)**: constitutional or statutory provision letting citizens submit ordinary legislation to parliament. Signature threshold, signature method, collection window, statutory consideration deadline, end-of-term behavior, subject-matter restrictions.
- **Cat 4 (Referenda)**: separate provisions for legislative and policy-issue referenda. Threshold, method, time window, majority/quorum, veto / judicial-review distortions.
- **Cat 5 (Citizens' Assemblies)**: legal provisions for citizen-initiated assemblies (rare). Even when absent, list any government-initiated national-level deliberative assemblies — they qualify for a bonus.
- **Cat 6 (Safeguards)**: how each of six institutions is staffed (judges, media regulator, electoral commission, prosecutor general, constitutional court, election results validator).
- **Cat 7 (Constitutional Change)**: initiation methods, drafting process, approval mechanism, and any distortions (monarch's veto, religious / military veto, eternity clauses).

The Research README and the Per-category research agent guide are authoritative on what each report must contain, the source-documentation hierarchy, the strict-adherence rule, the "used in practice" 100-year window, and the verification protocol. Don't infer the methodology from the bullet list above — read the two research documents before starting.

Per-row notes (direct quotes from constitutional articles, statute sections, citation URLs) become the LEGAL BASIS subsections of the research reports and shortened versions later become YAML comments. The discipline pays off when scores look wrong and you need to retrace the call.

### Phase 2 — Generate the scaffold

Run the Builder's `--scaffold` command:

```bash
python3 build_template.py --scaffold "Republic of Foo" \
    --population 12345678 \
    --size Medium \
    --out countries/foo.yaml
```

`--size` is one of `Small` (<10M), `Medium` (10–50M), `Large` (>50M) — the population bucket drives signature-threshold breakpoints in Cat 2/3/4/5/7.

The scaffold writes a structurally complete YAML skeleton with every required key, every required section, and inline `<placeholder>` hints showing the valid options for each field. You replace the angle-bracket placeholders with real values.

### Phase 3 — Fill in the values

Open the new YAML and work through each category. For every field where you're not sure of the exact spec label, run:

```bash
python3 build_template.py --list-options safeguards.judges
python3 build_template.py --list-options accountability.2.C
python3 build_template.py --list-options elections.1.6
python3 build_template.py --list-options "constitutional_change.approval_mechanisms"
```

It prints every valid option with its score in human-readable form. Copy the exact label.

Add a per-row comment whenever the call isn't obvious — at minimum, the legal source. Future readers (including future you) need to be able to verify the call by following your citations. The existing country YAMLs in `countries/` are worked examples; check a similar country (parliamentary monarchy, federal republic, etc.) for shape.

### Phase 4 — Validate, then build

```bash
python3 build_template.py --validate countries/foo.yaml
python3 build_template.py countries/foo.yaml
```

Validation runs without writing xlsx files. It catches every typo, missing key, and invalid option label. When something is off, the error message tells you the exact path and includes a "Did you mean…" suggestion based on edit distance:

```
❌ foo.yaml: ... unknown option 'Independent Commission Model'.
       Did you mean for 'Independent Commission Model'? 'The Independent Commission Model'
```

When validation passes, the build step writes one xlsx per declared category plus a cross-category `Country - DFI Score.xlsx` summary:

```
results/Foo - Elections.xlsx
results/Foo - Accountability.xlsx
results/Foo - Parliamentary Initiative.xlsx
results/Foo - Referenda.xlsx
results/Foo - Citizens' Assemblies.xlsx
results/Foo - Safeguards.xlsx
results/Foo - Constitutional Change.xlsx
results/Foo - DFI Score.xlsx
```

Each per-category workbook shows the per-row classification, the arithmetic, any penalties that fired, and the final score with its DFI contribution. The summary workbook lists each category with its final score, weight, and contribution toward the overall DFI percentage.

### Phase 5 — Sanity-check

Open the workbooks and check:

- Does each category's score look sensible given the country's institutional structure? A country with no citizen-initiated mechanisms across Cat 3/4/5 should score 0% in those three.
- Did any penalty trigger that shouldn't have? Each category's Penalty Ledger sheet itemises every penalty firing with its scope and amount.
- Does the country total feel right vs. similar countries in the corpus? The 20 existing country YAMLs cover a broad range from ~13% (Norway) to ~39% (Colombia) — your country should land somewhere defensible relative to those.

If a number looks wrong, it's almost always a YAML classification call that needs revisiting. The arithmetic is mechanical; the judgement is in the spec-option mapping.

### Phase 6 — Independent verification

Once the YAML builds cleanly and the scores look sensible, the country YAML should be **reviewed by an independent agent or LLM** before being treated as final. The reviewer compares the YAML against the country's research files and flags genuine mismatches: classifications that diverge from what the research proposed, factual claims in YAML comments not supported by the research, or unsourced specific events used to justify penalty calls.

This is the verification gate analogous to the second-conversation verification stage in the research phase. It catches three patterns that single-author authoring misses:

- **Unsourced factual claims**: comments citing specific events (years, bill numbers, named cases) without research-file backing. The pattern surfaces repeatedly because external knowledge slips into comments without anchoring.
- **Hedge-language hidden misclassifications**: research files using vague "rarely" / "occasionally" combined with binary-spec classifications. The reviewer pushes back on whether "rarely" + non-zero documented uses should classify as "used in practice" rather than "not used."
- **Spec-option mismatches**: YAML using a label that's close to but not exactly what the research proposed, or that the research proposed but isn't a valid framework option (the research file invented a value to handle a gap).

The reviewer's output is a per-category audit listing genuine mismatches. The YAML author then addresses each — either fixing the YAML or adding a comment that explicitly documents why the YAML deviates from the research's literal proposal (with sourced reasoning).

A YAML that has passed independent verification can be treated as the final scoring artifact for that country.

---

## Quick reference

```bash
# Discovery (use during authoring)
python3 build_template.py --scaffold "Country Name" --out countries/foo.yaml
python3 build_template.py --list-options                       # everything
python3 build_template.py --list-options safeguards            # one category
python3 build_template.py --list-options elections.1.6         # one section
python3 build_template.py --list-options safeguards.judges     # one row

# Validate without writing xlsx
python3 build_template.py --validate countries/foo.yaml
python3 build_template.py --validate --all

# Build one country / all countries
python3 build_template.py countries/foo.yaml
python3 build_template.py --all

# Regression suite (confirm no scores drifted after framework changes)
python3 -m unittest discover tests -p "test_*.py"
```

---

## Existing corpus

20 countries currently have full seven-category YAMLs and DFI summary workbooks. Each country's eight workbook files (one per category plus a cross-category DFI Score summary) live in two places: in the flat `results/` folder for build-system convenience, and in `Countries - results/<Country>/Score/` alongside that country's research and special-research outputs. Use the existing YAMLs in `countries/` as worked examples when authoring your own country — pick the one structurally closest to yours.

| Type of system | Example countries |
|---|---|
| Federal presidential republic | USA, Mexico, Colombia, South Africa |
| Unitary presidential republic | Costa Rica, Peru, South Korea, Kenya |
| Parliamentary republic (unitary) | Latvia, Poland |
| Parliamentary republic (federal) | Germany |
| Westminster-style parliamentary monarchy | UK, Canada, Botswana, Sweden, Norway |
| Bicameral parliamentary monarchy | Japan, Spain, Netherlands |
| Federal directorial republic | Switzerland |

Plus one synthetic country (`_synthetic_dual_penalty.yaml`) used to stress-test penalty arithmetic that no real country exercises. The synthetic YAML is excluded from the published `Countries - results/` set — it lives only in `countries/` for engine debugging.

---

## Directory layout

```
DFI/
├── README - DFI Builder.md       This file — workflow + build-system mechanics
├── README - DFI Research.md      Research-phase guide (Phase 1)
├── Per-category research agent guide.md
│                                 Per-category research operating manual
├── build_template.py             CLI entry point
├── dfi/
│   ├── framework.py              Elections framework (single source of truth)
│   ├── engine.py                 Elections workbook builder
│   ├── country.py                YAML loader + cross-row consistency checks
│   ├── common.py                 Cross-category primitives
│   │                             (HourlyMinWage, SIGNATURE_METHOD_*,
│   │                              THRESHOLD_YAML_LABELS)
│   ├── summary.py                Cross-category DFI summary builder
│   └── scaffold.py               --scaffold + --list-options data
├── categories/
│   ├── accountability.py         Holding Politicians Accountable
│   ├── parliamentary_initiative.py    Parliamentary Legislative Initiative
│   ├── referenda.py              Citizens-Initiated Referenda
│   ├── citizens_assemblies.py    Citizens' Assemblies
│   ├── safeguards.py             Safeguards
│   └── constitutional_change.py  Ability to Change the Constitution
├── countries/
│   ├── botswana.yaml ... usa.yaml    20 country YAMLs
│   └── _synthetic_dual_penalty.yaml  Synthetic stress test
├── results/                      Generated workbooks, flat layout
│   └── Country - <Category>.xlsx     One per category + a Country - DFI Score.xlsx
├── Countries - results/          Per-country archival folders
│   └── <Country>/
│       ├── Research/             Seven per-category research reports
│       ├── Special research/     Focused special-research outputs (Cat 1, Cat 2)
│       └── Score/                Eight workbook files (mirror of results/)
├── tests/
│   ├── README - test files and research utilities.md
│   ├── test_expected_scores.py   Regression suite (one *RegressionTest
│   │                             class per category + DFISummaryRegressionTest)
│   ├── verify_country.py         Full Elections computation trace
│   ├── inspect_named_ranges.py   Workbook diagnostic for #NAME? errors
│   ├── hand_verify_*.py          Per-category 0pp delta hand-verifications
│   ├── build_dfi_table.py        Generates Table 1 for the methodology paper
│   ├── gallagher_templates/      Per-country Gallagher Index calculators
│   │                             (templates for Cat 1 §1.6 disproportionality)
│   └── test_*.py                 Validator + framework tests
├── Main DFI framework/           Framework specs (one .docx per category)
├── New scoring guidelines/       Scoring rules (one .md per category)
├── Research guidelines/          Research, formatting, verification methodology
├── Special research requests/    Methodology for Cat 1 + Cat 2 special research
├── notes/
│   └── spec-issues.md            Spec-level ambiguities for the next revision
└── Measuring Democracy with DFI - ready.docx     Methodology paper
```

**One file per category.** Each category lives in its own module containing the framework definitions, the strict YAML validator, the compute layer, and the workbook builder. A spec revision touches one file.

**Country YAMLs are the only place country-specific data lives.** Option text matches the framework character-for-character. Per-row comments document why each classification was chosen.

**`results/` is regenerable.** Treat it as cache; rebuild from `--all` whenever framework or YAMLs change.

---

## The country YAML at a glance

A YAML may declare any subset of the seven category blocks. Only `country:` is mandatory. The cross-category DFI summary is generated only when all seven blocks are present.

```yaml
country:
  name: Republic of Foo
  population: 12345678
  research_date: 2025-01

  hourly_min_wage:                          # Required when any category needs the wage cascade
    value: 5.81
    source: monthly_wage_divided_by_160     # Cascade rung — see scaffold for options
    currency: FOO
    monthly_source_value: 930.0
    note: |
      <one paragraph: source citation, year, any caveats>

elections: { ... }                # Cat 1 — applicability + 6 sections
accountability: { ... }           # Cat 2 — 4 sections (recall, abrogative, FOI, bonus)
parliamentary_initiative: { ... } # Cat 3 — exists + signature reqs + timeline
referenda: { ... }                # Cat 4 — legislative + policy types
citizens_assemblies: { ... }      # Cat 5 — types + system_design + bonus count
safeguards: { ... }               # Cat 6 — 6 sub-components
constitutional_change: { ... }    # Cat 7 — initiation + process + approval + distortions
```

Every existing country YAML in `countries/` is annotated with per-row research justifications. Use them as worked examples.

### Strict validation

All seven validators refuse anything that doesn't match the framework character-for-character: typos in option labels, missing required rows, unknown extra rows, contradictory cross-row settings (e.g. Public Funding §1.1.4 mismatch). Errors arrive at YAML load time with a clear path like:

```
<country> safeguards.electoral_commission: 'foo' is not a valid option.
Allowed: [...]
  Did you mean for 'foo'? 'fooo' | 'fo'
```

The `--suggest` mechanism uses edit-distance matching against the framework option lists to point you at likely typos.

---

## The generated workbooks

Each xlsx is a complete scorecard for one category. Layout:

1. **Country profile** — name, population, size bucket
2. **Sub-component breakdown** — every scoring row, the chosen option, points and percentage
3. **Calculation** — section subtotals, weights, multipliers, penalty applications, final score
4. **Penalty Ledger** (when penalties fire) — itemized list of every triggered penalty with scope and amount
5. **Build metadata** — framework version, build timestamp

Elections workbooks are named-range-driven: cells like `final_score`, `dfi_contribution`, `parl_score`, `pres_score` survive row-shifts and can be referenced from external sheets.

The cross-category `Country - DFI Score.xlsx` summary lists each category with its final score, its DFI weight, and its contribution toward the overall DFI percentage. The total at the bottom is the country's overall DFI score.

---

## Penalty mechanics

Each category implements its spec's penalty arithmetic faithfully. Two arithmetic forms a YAML author should be aware of:

**Additive percentage-point penalties** (Elections category-wide, Accountability scope penalties): penalties sum within scope, capped at −100% per scope, then subtracted in percentage points from the pre-penalty score. Example: 75% − 65% = 10%, floored at 0%.

**Multiplicative penalties** (Cat 1 scope penalties, Cat 4 scope penalties, Cat 7 distortions): penalties sum, the sum is clamped at −100%, and the final score is `base × (1 + sum/100)`, floored at 0. Example: 65% × (1 − 0.5) = 32.5%.

Each per-category module's docstring documents which arithmetic applies and cites the relevant Scoring Guidelines section.

Penalty caps:
- **Foreign donation cap** (Elections): max −25% combined across the foreign-donation family (Official / Other / Loopholes share the cap).
- **Corporate donation cap** (Elections): max −25% for Official + Other combined.
- **Public Funding §1.1.4 halving** (Elections): when a country with both presidential and parliamentary elections has public funding for only one type, the combined Coverage + Bonus contribution is halved.
- **Floor at 0**: when penalties would push a score below zero, the score is 0.

---

## Cascades and conditional structure

Several categories adjust their structure based on the country's institutional shape:

**Elections**:
- `legislature: Unicameral` → upper-house rows go N/A
- `direct_president: No` → Section 1.5 N/A; presidential-scope weight collapses
- Redistribution: `multiplier = 100 / Σ(applicable raw weights)` per Scoring Guidelines §3.1.9

**Accountability**:
- `Presidential Recall.exists: No` (no direct presidential election) → drops 50% raw weight; remaining mechanisms redistribute
- `Partial Parliament Recall.exists: No` (unicameral) → drops without redistribution
- `Individual Legislator Recall` → always 0% per spec
- FOI: when `Oversight body: No dedicated body`, the Independence and Powers rows drop and the Oversight body's 40% expands to 100% of Enforcement

**Parliamentary Legislative Initiative** / **Referenda** / **Citizens' Assemblies**:
- Existence gate (`exists: No` → category short-circuits to 0%)
- Type-conditional weights for Cat 4 (legislative-only / policy-only / both)
- Direct-DFI bonus path for Cat 5 (government-initiated assemblies, max 3pp)

**Safeguards**:
- Constitutional Court existence gate (`exists: No` → sub-component scores 0/40)
- Media dual-system averaging (single string OR `{public, private}` mapping)

**Constitutional Change**:
- Per sub-component (Initiation, Process Type, Approval), the score is the **mean** of all structurally-present pathways
- Distortions apply multiplicatively after sub-component aggregation

---

## When things go wrong

- **YAML option mismatch** — strict loader raises with the section, component, chamber, the bad option text, and a "Did you mean…" suggestion. Copy the exact label from `--list-options <scope>`.
- **Workbook formula error** — Elections uses named ranges (`final_score`, `dfi_contribution`, etc.); if a Summary cell shows `#NAME?`, the corresponding row in the Calculation sheet didn't render. Inspect with `python3 tests/inspect_named_ranges.py <path>`.
- **Regression test fails with small delta (< 0.01 pp)** — likely floating-point. Bump `TOLERANCE_PCT` with a brief justification, or investigate.
- **Regression test fails with large delta** — a framework or engine change moved a number. Don't just update the snapshot; investigate whether the change is methodologically correct, then update if it is.

---

## Adding a new category

If the framework ever adds an eighth category:

1. Create `categories/<name>.py` following the existing per-category structure: docstring, `FRAMEWORK_VERSION`, weight constants with sum-to-N assertion, option enumerations, validator, compute layer, workbook builder.
2. Reuse primitives from `dfi/common.py` (population bucket, hourly wage, signature-method multiplier, threshold labels) where applicable.
3. Register the new category in `dfi/scaffold.py::CATEGORY_REGISTRY` and add a scaffolder function — this gets the category into `--scaffold` and `--list-options` automatically.
4. Hook into `build_template.py` with `if "<name>" in yaml_data: build_<name>_workbook(country, yaml_data, out_path)`.
5. Register in `dfi/summary.py::CATEGORIES` so it joins the cross-category summary.
6. Add a regression-snapshot dict and `*RegressionTest` class to `tests/test_expected_scores.py`.
7. Hand-verify against the spec text for at least one country at 0pp delta.

See any existing `categories/<name>.py` as a structural template — the simpler categories (Parliamentary Initiative, Constitutional Change with distortions) cover the common shapes.

---

## Spec ambiguities

Where the framework specs leave a question unresolved (the "Simple majority in both chambers" Parliamentary-Initiated case in Cat 7, or the Cat 4 §5/§6 boundary on judicial-review-vs-veto), the Builder makes a defensible best-fit call and logs the issue in `notes/spec-issues.md` for the next spec revision. The Builder follows the spec; gaps are flagged, not papered over.

---

## Key source documents

- `Measuring Democracy with DFI - ready.docx` — the DFI methodology paper.
- `Main DFI framework/DFI - <category> <n> - version <X>.docx` — framework specs (one per category).
- `New scoring guidelines/DFI - Scoring Guidelines <n> - <Category>.md` — operational scoring rules.
- `Research guidelines/DFI - Research Phase Guidelines.md` — what a researcher must document for each country.
- `Research guidelines/DFI - Research formatting guidelines.md` — output format for per-category research reports.
- `Research guidelines/DFI - Verification of Research Guidelines.md` — quality check for research reports.
