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 isdisaster-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)
| File | Size | Uploaded | |
|---|---|---|---|
| disaster_fund_plan-0.1.1.tar.gz | 13.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|