Skip to main content

disaster-fund-plan

Describe a disaster reconstruction plan in one JSON file, and get it checked before any money is calculated.

disaster-fund-plan reads a plan file, validates it, and gives you one trusted ImplementationPlan object. It does no money calculation: it only answers "what is the plan?" The companion package disaster-fund-calculation-engine takes this plan and works out the money.

  • Python 3.10 or newer
  • Only dependency: pydantic>=2
  • Import name: fm_plan (the PyPI name is disaster-fund-plan)
pip install disaster-fund-plan

What is a "plan"?

A plan is a single JSON file that says:

Section What it says Example
plan Which disaster, which plan, when it applies, and which areas are affected Gorkha Earthquake, Housing Reconstruction v1, districts 36 (Gorkha) and 23 (Sindhupalchok)
government The administrative tree: Federal → Provinces → Districts → Local levels, each with a required official code District 23 (Sindhupalchok) belongs to province P3, and is a Pahad district
regions, disaster_types, solution_types The allowed categories Himal / Pahad / Terai, Earthquake, Reconstruction / Retrofitting
grant_matrix How much a household receives, tranche by tranche, for every region + disaster + solution combination Pahad + Earthquake + Reconstruction: 15,000 + 15,000 (shelter), 100,000 + 100,000 + 70,000 (main)
government_liability Who pays what share: Federal %, Province %, Local % (must total 100) 50 / 30 / 20

A complete working example (example_plan.json) ships in the project repository; a minimal one is shown at the end of this README.


Use cases

1. Check a plan file before you trust it (command line)

You have just received or edited a plan file and want to know whether it is consistent.

python -m fm_plan example_plan.json
OK: plan_id = gorkha-earthquake-2015-housing-reconstruction-v1
  2 affected area(s), 2 district(s), 6 matrix row(s)

Exit code is 0 when the file is valid and 1 when it is not, so it also works as a check in a script or CI job. A bad file prints every problem it can find, with the path to the field:

bad_plan.json has 1 problem(s):
  - government_liability: government liability percentages must sum to 100%, got 110.0%

The same command is installed as fm-plan example_plan.json.

2. Load a plan inside your application

Your backend needs a validated plan at start-up, and a clear error if the file is wrong.

from fm_plan import load_plan, PlanLoadError

try:
    plan = load_plan("example_plan.json")
except PlanLoadError as error:
    print(error)          # human-readable list of problems
    raise SystemExit(1)

print(plan.plan_id)                      # gorkha-earthquake-2015-housing-reconstruction-v1
print(plan.government_liability.federal) # 50.0

load_plan accepts a path, understands files saved by Windows editors with a BOM, and turns JSON syntax errors into messages that include the line and column.

3. Find out whether an address is covered by the plan

A caseworker enters a household address and you must decide whether it falls inside the disaster-affected area. Districts and local levels can be given by name or official code, in any capitalisation.

plan.covers("Sindhupalchok")                              # True  (district is listed)
plan.covers("23")                                         # True  (same district, by code)
plan.covers("Sindhupalchok", "Melamchi Municipality", 2)  # True  (ward 2 is listed)
plan.covers("Sindhupalchok", "Melamchi Municipality", 9)  # False (only wards 1, 2, 3 are affected)
plan.covers("Kathmandu")                                  # False (not in the plan)
  • covers(district, local_level=None, ward=None) is exact, including wards.
  • is_affected(district, local_level=None) ignores wards: use it when your data has no ward (for example, counts grouped by municipality).

In the plan file, leaving out local_levels means "the whole district"; leaving out wards means "the whole local level".

4. Look up what a household is entitled to

Read the grant for one combination straight from the matrix.

row = next(
    r for r in plan.grant_matrix
    if (r.region, r.disaster_type, r.solution_type) == ("Pahad", "Earthquake", "Reconstruction")
)
print(row.total)                                  # 300000
print([(t.stage, t.amount) for t in row.tranches])
# [('shelter', 15000), ('shelter', 15000), ('main', 100000), ('main', 100000), ('main', 70000)]

The package guarantees the matrix has no gaps (every region × disaster × solution has exactly one row) and that shelter tranches always come before main tranches, so this lookup always succeeds for a valid combination. For a ready-made per-household schedule, use the engine package (its receivable_amounts function).

5. Resolve administrative units by name or code

Master data often arrives with codes in one system and names in another. The query API accepts either.

gov = plan.government
gov.find_district("23").name        # 'Sindhupalchok'
gov.find_district("gorkha").code    # '36'
gov.find_province("P3").name        # 'Bagmati Province'
gov.find_local_level("23001")       # [LocalLevel(name='Melamchi Municipality', code='23001', ...)]

find_local_level returns a list because two districts can legitimately have a local level with the same name; pass the district as the second argument to narrow it down.

Note: The query API (Python) accepts both names and codes. Only the plan file format requires codes for the "belongs-to" links — see the Minimal plan file section.

6. Build a plan in code, save it, and load it again

You are generating plans from a form or database instead of writing JSON by hand.

import json
from fm_plan import ImplementationPlan

plan = ImplementationPlan.model_validate(my_dict)   # raises pydantic.ValidationError if wrong
open("saved_plan.json", "w").write(plan.model_dump_json(indent=2))

same_plan = ImplementationPlan.model_validate_json(open("saved_plan.json").read())
assert same_plan.plan_id == plan.plan_id

A saved plan contains its plan_id, and it is always re-derived on load, so a hand-edited ID can never disagree with the plan's contents.

7. Set (and protect) the government liability split

The Federal/Province/Local percentages change every rupee the engine reports, so they are checked strictly: each between 0 and 100, together exactly 100.

If government_liability is missing from the file, the package uses 50 / 30 / 20 but emits a DefaultLiabilityWarning so the default is never silent:

import warnings
from fm_plan import DefaultLiabilityWarning

warnings.simplefilter("error", DefaultLiabilityWarning)   # make a missing split a hard failure

What gets validated

Area Rule
Dates Disaster has a start and end date; end is not before start. The plan's effective date must start after the disaster ends.
Hierarchy & Boundary Links Every district's province field must be a valid province code; every local level's district field must be a valid district code. Affected areas reference districts and local levels by code. No duplicate names or codes.
Codes Codes are mandatory on all units (Federal, Province, District, LocalLevel). A code identifies exactly one unit across all levels (the Federal code FED, a province P3, a district 23 and a local level 23001 can never overlap). A province or district name may not equal another unit's code. Codes are compared as text, so 036 and 36 are different.
Affected areas Every listed district code and local level code exists in the government section; no district or local level is listed twice.
Regions Every district's region is in regions (Himal, Pahad, Terai).
Grant matrix Every row uses known region / disaster / solution names; no repeated row; no missing combination.
Tranches Amounts are whole rupees above zero; stage is shelter or main; shelter tranches come first.
Liability Federal + Province + Local = 100%.
Whole file Unknown keys are rejected (a typo such as "distrcit" is an error, not silently ignored). Names are compared ignoring capital letters.

Plan ID

The ID is computed, never typed:

<disaster name>-<disaster start year>-<plan name>-v<plan version>
Gorkha Earthquake, 2015, Housing Reconstruction, version 1  →  gorkha-earthquake-2015-housing-reconstruction-v1

Changing the plan version creates a new ID, so results from different plan versions can never be mixed up.

Minimal plan file

All code fields are required. The province on each district and the district on each local level must be codes, not names. Affected areas reference districts and local levels by code.

{
  "plan": {
    "plan_name": "Housing Reconstruction",
    "plan_version": 1,
    "disaster_name": "Gorkha Earthquake",
    "disaster_date_range": { "start": "2015-04-25", "end": "2015-05-12" },
    "effective_date_range": { "start": "2015-09-01", "end": null },
    "affected_areas": [
      { "district": "36" },
      {
        "district": "23",
        "local_levels": [
          { "code": "23001", "wards": [1, 2, 3] },
          { "code": "23002" }
        ]
      }
    ]
  },
  "government": {
    "federal": { "name": "Government of Nepal", "code": "FED" },
    "provinces": [
      { "name": "Gandaki Province", "code": "P4" },
      { "name": "Bagmati Province", "code": "P3" }
    ],
    "districts": [
      { "name": "Gorkha",        "code": "36", "province": "P4", "region": "Pahad" },
      { "name": "Sindhupalchok", "code": "23", "province": "P3", "region": "Pahad" }
    ],
    "local_levels": [
      { "name": "Gorkha Municipality",         "code": "36001", "district": "36" },
      { "name": "Melamchi Municipality",        "code": "23001", "district": "23" },
      { "name": "Helambu Rural Municipality",   "code": "23002", "district": "23" }
    ]
  },
  "government_liability": { "federal": 50, "province": 30, "local": 20 },
  "regions": ["Pahad"],
  "disaster_types": ["Earthquake"],
  "solution_types": ["Reconstruction", "Retrofitting"],
  "grant_matrix": [
    {
      "region": "Pahad", "disaster_type": "Earthquake", "solution_type": "Reconstruction",
      "tranches": [
        { "stage": "shelter", "amount": 15000 },
        { "stage": "main",    "amount": 100000 }
      ]
    },
    {
      "region": "Pahad", "disaster_type": "Earthquake", "solution_type": "Retrofitting",
      "tranches": [
        { "stage": "shelter", "amount": 50000 },
        { "stage": "main",    "amount": 150000 }
      ]
    }
  ]
}

Plan file key rules at a glance

Field Required Value format
government.federal.code Yes Non-empty text (e.g. "FED")
government.provinces[].code Yes Non-empty text (e.g. "P3", "P4")
government.districts[].code Yes Non-empty text (e.g. "23", "36")
government.districts[].province Yes Province code (e.g. "P4") — not the name
government.local_levels[].code Yes Non-empty text (e.g. "23001")
government.local_levels[].district Yes District code (e.g. "23") — not the name
plan.affected_areas[].district Yes District code (e.g. "36") — not the name
plan.affected_areas[].local_levels[].code Yes Local level code (e.g. "23001")

API summary

Name Purpose
load_plan(path) Read, validate and return an ImplementationPlan. Raises PlanLoadError.
PlanLoadError The file is missing, not valid JSON, or fails validation. The message lists each problem.
ImplementationPlan The validated plan. Has .plan_id, .plan, .government, .regions, .disaster_types, .solution_types, .grant_matrix, .government_liability, .covers(), .is_affected().
DefaultLiabilityWarning Emitted when government_liability is missing and the 50/30/20 default is used.

Limits

  • Amounts are whole rupees (no paisa).
  • The plan describes rates and structure. Counts of actual beneficiaries, donations and all money calculations belong to disaster-fund-calculation-engine.

License

MIT

Metadata

Release files for disaster-fund-plan 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for disaster-fund-plan 0.1.1
File Size Uploaded
disaster_fund_plan-0.1.1.tar.gz 13.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for disaster-fund-plan 0.1.1
File Interpreter ABI Platform
disaster_fund_plan-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 27.2 kB

Release files / disaster_fund_plan-0.1.1.tar.gz

Download URL disaster_fund_plan-0.1.1.tar.gz
Size 13.9 kB
Tags Source
SHA-256 checksum
How to use checksums
0e44a521c786e217ac0c2d6f23e38057e7bb9b86a52f22953b2873443b67d439
BLAKE2b-256 checksum
How to use checksums
d280616156e457983b3b55f2df757855a5b512eea6f9c72609ff120e048b3580
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.6

Release files / disaster_fund_plan-0.1.1-py3-none-any.whl

Download URL disaster_fund_plan-0.1.1-py3-none-any.whl
Size 13.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ad9e2340598fc39bc18c5b236af171144659184841659cb707015cdb196750c1
BLAKE2b-256 checksum
How to use checksums
dbe4a36d224ac2aa1e3efd67da5325e92c89a0de08901beaf816ce93f8ac519b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.6

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page