| name | create-or-verify-country |
| description | Create or externally verify YAML tax configurations for any country/year in the universal salary calculator.
Use when:
- Creating a new country configuration
- Adding a new tax year for existing country
- Creating variant configs (expat regimes, special tax rules)
- Externally verifying existing configs (recommended for all configs)
- Fixing bugs in existing configs
Triggers: "add country", "create config", "verify country", "fix [country]", "new tax year",
"add variant", "30% ruling", "verify test vectors", etc.
CRITICAL GUARDRAILS:
1. Test vectors MUST come from official government calculators - NEVER from the engine itself
2. All configs MUST pass `npm run test:configs` before completion
3. Progressive tax systems MUST be verified with marginal rate tests
4. Multi-level taxation (federal+state) requires extra scrutiny
5. Social security systems often have hidden complexity (multiple caps, progressive rates)
|
Create or Verify Country/Year Tax Configuration
🚦 Entry Decision Tree
Are you creating or verifying?
Creating New Config:
→ Follow full workflow: Research → Plan → Implement → Write Tests → Validate → Document
Verifying Existing Config:
→ Skip to Verification Protocol (Step 7)
Workflow (Creating New Config)
- Research - Gather official sources (MANDATORY: official government calculator)
- Plan - Identify complexity level and tax system changes
- Implement - Create base.yaml with calculations
- Write Tests - Create test vectors from EXTERNAL sources only
- Validate - Run tests, debug failures
- Document - Add sources and notices
- External Verification - 3-layer verification protocol (MANDATORY)
- Add to Guides - Document in guides/countries/.md
Step 1: Research (MANDATORY CHECKLIST)
🔴 Critical Rules - Research Phase
✅ REQUIRED:
- Find the official government tax calculator URL
- Examples: IRS Tax Estimator, HMRC PAYE Calculator, Belastingdienst, etc.
- If no official calculator exists, document this explicitly
- Find official statutory tax rates (government tax authority website)
- Document ALL sources with URLs and retrieval dates
- Check if tax year has legislative changes from previous year
- For multi-level taxation (US, CA, CH, ES, IT), research each level separately
❌ FORBIDDEN:
- Third-party comparison sites (moneyland.ch, talent.com, nerdwallet.com, etc.)
- Generic tax advice blogs
- Using previous year's config without verifying changes
- Mental math or LLM calculations for test vectors
Research Checklist
Gather from official government sources ONLY:
Income Tax System
Social Security / National Insurance
🚨 SOCIAL SECURITY COMPLEXITY WARNING:
Social security is often MORE complex than it appears:
- Multiple caps: Different contributions may have different income caps
- Progressive rates: Some have base rate + solidarity/additional rate above threshold
- Regional variations: Rates may vary by state/province/canton
- Special regimes: Quebec has QPP+QPIP instead of CPP/EI
Examples of hidden complexity caught in verification:
- Switzerland: ALV 1.1% up to CHF 148,200, then 0.5% above (was missing 0.5%)
- Canada Quebec: QPP 6.40% (not CPP 5.95%) + QPIP 0.494% (was missing entirely)
- Germany Saxony: Care insurance 2.9% (not 2.4% standard)
Tax Credits and Deductions
Regional/Multi-Level Taxation
For countries with federal + state/provincial/cantonal taxation:
Expat/Special Regimes
Official Calculator Validation
Step 2: Assess Complexity
| Level | Characteristics | Approach | Examples |
|---|
| Simple | No income tax or flat tax | Pure YAML, minimal nodes | UAE, SG, HK |
| Moderate | Progressive brackets + contributions | Pure YAML | NL, AU, IE, UK |
| High | Multi-level regions or special calculations | YAML + lookups | CH, US, CA, ES |
| Complex | Income splitting, family quotient, complex formulas | Use function node | DE, FR |
🚨 Multi-Level Taxation Warning
Countries with federal + state/provincial/cantonal taxation are HIGH RISK for errors:
- US (50 states), CA (13 provinces), CH (26 cantons), ES (17 autonomous communities), IT (20 regions)
- Each level needs separate verification
- Test vectors must explicitly state which region/state
- Extra scrutiny on how levels combine
Common errors in multi-level configs:
- Using flat effective rate instead of progressive brackets (CH 2026)
- Missing regional variations (DE Saxony care insurance)
- Wrong system for regions (CA Quebec QPP vs CPP)
Step 3: Create base.yaml
Year-Over-Year Change Detection
🚨 COPYING FROM PREVIOUS YEAR? STOP AND VERIFY:
If creating year N from year N-1:
- Research what changed - Don't assume identical
- Check official sources for year N specifically
- Document changes in meta.notes
- Update ALL affected parameters
Common year-over-year changes:
- Tax bracket inflation adjustments (thresholds increase ~1-3%)
- Rate changes (rare but critical - IT 2024→2025 was complete system change)
- Social security caps (usually increase annually)
- Credit/deduction amounts (may increase or phase out)
- New legislation (e.g., US TCJA expiration, IT cuneo fiscale switch)
Example - Italy 2024→2025 disaster:
Config blindly assumed 2024 INPS cut system carried forward to 2025.
Reality: Italy completely replaced it with cuneo fiscale tax-based system.
Result: 674% marginal rate bug, massive welfare cliffs.
Template Structure
meta:
country: "xx"
year: 2025
currency: "XXX"
version: "1.0.0"
sources:
- url: "https://official-gov-site/tax-calculator"
description: "Official [Country] tax calculator"
retrieved_at: "2025-01-15"
- url: "https://official-gov-site/tax-rates"
description: "Official statutory rates for 2025"
retrieved_at: "2025-01-15"
updated_at: "2025-01-15"
notes: |
[Country] tax system for [year].
Key changes from [previous year]:
- [List specific changes or state "No changes from YYYY"]
Multi-level taxation: [Describe if applicable]
notices:
- id: "salary_input"
title: "Annual Gross Salary"
body: "Enter total annual salary before deductions."
severity: "info"
inputs:
gross_annual:
type: number
required: true
min: 0
label: "Annual Gross Salary"
filing_status:
type: enum
required: true
default: "single"
options:
single:
label: "Single"
description: "Unmarried individual"
parameters:
calculations:
outputs:
gross: "@gross_annual"
net: "$net_annual"
effective_rate: [...]
breakdown:
taxes: [...]
contributions: [...]
Step 4: Write Test Vectors
🔴 CRITICAL - Test Vector Independence Rule
Test vectors MUST NEVER be derived from the engine itself.
✅ ACCEPTABLE SOURCES:
- Official government tax calculator (best)
- Deterministic Python script implementing official formula + verified against official calculator
- Official tax tables (for simple bracket calculations)
- Authoritative references (PwC Tax Summaries, Big 4 firm guides) + verified
❌ FORBIDDEN SOURCES:
- The engine's calculation (creates circular dependency)
- Mental math or LLM arithmetic
- Third-party comparison calculators (talent.com, moneyland.ch, etc.)
- "Verified against previous year test vector" (unless previous year was externally verified)
Why this matters:
- If test vectors come from engine, fixing an engine bug breaks all tests
- Tests become useless as independent validation
- Example: CA Quebec tests passed despite wrong config because tests were derived from engine
Test Vector Creation Process
-
Use official government calculator
- Input gross salary, filing status, region
- Record EXACT net result
- Screenshot or document URL + date
-
Create Python verification script (optional but recommended)
- Implement official formula independently
- Cross-verify against government calculator
- Use script to generate expected values
-
Document source in test vector
"sources": [{
"description": "Verified via [Official Calculator Name]",
"url": "https://official-calculator-url",
"date": "2025-01-15",
"notes": "Input: €60,000 gross, single → Net: €45,234"
}]
Test Vector Coverage (Minimum Required)
Progressive Tax Bracket Validation
For progressive tax systems, include marginal rate validation:
- Engine automatically tests that marginal rates don't exceed 100% (impossible)
- Helps catch flat-rate-instead-of-progressive bugs
- Example: CH 2026 had 220% marginal rate → caught flat rate error
Step 5: Run Test Suite & Debug
npm run test:configs
npm run test:configs -- -t "xx/2025"
Common Failures & Root Causes
"Impossible marginal rate of 674%" → Progressive vs Flat Rate Bug
- Test vector assumes flat effective rate
- Engine correctly uses progressive brackets
- Fix: Recalculate test vector with progressive brackets
"Net expected 45,000 got 43,000" → Missing Component
- Often missing social security contribution type
- Example: CA Quebec missing QPIP (€346/year error)
- Fix: Research all contribution types thoroughly
"Reference not found: $node_id" → Typo or Missing Node
- Check spelling of node IDs (case-sensitive)
- Verify node is defined before it's referenced
Step 6: Document
Final checklist:
Step 7: External Verification Protocol (MANDATORY)
🚨 ALL CONFIGS MUST BE EXTERNALLY VERIFIED
Even if tests pass, configs MUST undergo 3-layer verification to ensure test vectors weren't self-derived.
Layer 1: Arithmetic Verification
Create Python verification script:
def verify_social_security(gross, rates, caps):
"""Implement official formula independently"""
pass
What to verify:
- Social security contributions (simple rate × min(gross, cap))
- Tax credits with phaseouts
- Any arithmetic that can be independently calculated
Pass criteria: All values within rounding tolerance (±€1 or 0.01%)
Layer 2: Official Government Calculator
Use official calculator to verify:
- Navigate to official government tax calculator
- Input test vector parameters (gross, filing status, region)
- Record exact net result
- Compare against test vector expected values
Tools:
- Playwright automation (preferred for bulk verification)
- Manual verification (acceptable for <5 vectors)
- Screenshot documentation (recommended)
Pass criteria: Net income within tolerance (usually ±€50 or 1%)
Layer 3: Cross-Verification
For multi-level taxation:
- Verify each level separately
- Federal calculator + state/provincial calculator
- Ensure levels combine correctly
For complex cases:
- Use multiple sources (2-3 different calculators)
- Check marginal rate discontinuities
- Validate edge cases (cap thresholds, phaseout zones)
Pass criteria: Consistent results across multiple sources
Verification Report
Create reports/[COUNTRY]-[YEAR]-VERIFICATION.md:
# [Country] [Year] External Verification Report
**Date:** YYYY-MM-DD
**Verifier:** [Agent/Human]
**Status:** ✅ VERIFIED / ⚠️ ISSUES FOUND / ❌ FAILED
## Verification Summary
- Test vectors: X/X verified ✅
- Layer 1 (Arithmetic): PASS/FAIL
- Layer 2 (Official Calculator): PASS/FAIL
- Layer 3 (Cross-Verification): PASS/FAIL
## Methodology
### Layer 1: Arithmetic Verification
[Python script details]
### Layer 2: Official Calculator
[Calculator URL, test results]
### Layer 3: Cross-Verification
[Additional sources used]
## Findings
### Issues Found
[Document any bugs, missing components, wrong rates]
### Corrections Made
[Document fixes applied]
## Sources
- Official calculator: [URL]
- Statutory rates: [URL]
- Verification script: scripts/verify-[country]-[year].py
## Conclusion
[Summary statement on verification status]
Creating Variants
For expat regimes, special tax treatments:
meta:
variant: "regime-name"
label: "Human Readable Name"
description: "Who qualifies and what it provides"
base: "../base.yaml"
sources:
- url: "https://official-source-for-regime"
description: "Official regime documentation"
retrieved_at: "2025-01-15"
Variant test vectors:
- Must verify eligibility criteria
- Must verify regime-specific benefits
- Must have own external verification
Completion Checklist
Before marking config as complete:
Research Phase
Implementation Phase
Test Vector Phase
Validation Phase
External Verification Phase (MANDATORY)
Documentation Phase
Final Validation
Red Flags & Common Mistakes
🚨 STOP if you see these patterns:
-
"Calculated using official rates..." in test vector source
→ Self-derived, not externally verified
-
Third-party calculator URLs (moneyland.ch, talent.com, nerdwallet.com)
→ Not authoritative, use official government only
-
"Verified against previous year" without documenting changes
→ Blind copy may miss legislative changes
-
Marginal rate validation failures (>100% or <0%)
→ Likely flat rate assumption or wrong tax system
-
Test vectors with no sources field
→ Cannot verify independence
-
Multi-level taxation with single flat rate
→ Likely oversimplification, verify each level separately
-
Social security with single cap for all contributions
→ Often wrong, different contributions have different caps
-
Copying config from year N-1 to year N without research
→ May miss significant legislative changes (IT 2024→2025)
Examples of Past Errors (Learn From These)
❌ Switzerland 2026 - Flat Rate Assumption
- Error: Test vectors assumed 8% flat cantonal rate
- Reality: Progressive cantonal brackets (8% then 12%)
- Impact: 3 test failures, 220% marginal rate bug
- Lesson: Always verify progressive vs flat
❌ Italy 2024→2025 - Wrong System
- Error: Implemented 2024 INPS cut system in 2025 config
- Reality: 2025 switched to cuneo fiscale tax-based system
- Impact: 674% marginal rate, €2,071 welfare cliff
- Lesson: Research year-over-year changes, don't assume continuity
❌ Canada 2025 Quebec - Test Vectors from Engine
- Error: Test vectors derived from engine (which used CPP not QPP)
- Reality: Quebec uses QPP 6.40% + QPIP, not CPP 5.95%
- Impact: Tests passed but config was €428/year wrong
- Lesson: Test vectors MUST be externally verified
❌ Switzerland 2026 - Missing ALV Solidarity
- Error: Only implemented ALV 1.1% up to cap
- Reality: ALV is 1.1% up to cap, then 0.5% above
- Impact: High earners underpaying unemployment insurance
- Lesson: Social security often has progressive rates/additional contributions
References
- Formal Schema:
packages/schema/src/config-types.ts (source of truth)
- Example Configs:
configs/nl/2026/, configs/de/2025/, configs/gb/2026/
- Verification Examples:
reports/NL-2026-VERIFICATION.md, reports/DE-2025-VERIFICATION-SUMMARY.md
- Research Guide:
references/country-tax-research.md