disaster-fund-plan 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- disaster_fund_plan-0.1.0/PKG-INFO +252 -0
- disaster_fund_plan-0.1.0/README.md +230 -0
- disaster_fund_plan-0.1.0/pyproject.toml +39 -0
- disaster_fund_plan-0.1.0/setup.cfg +4 -0
- disaster_fund_plan-0.1.0/src/disaster_fund_plan.egg-info/PKG-INFO +252 -0
- disaster_fund_plan-0.1.0/src/disaster_fund_plan.egg-info/SOURCES.txt +13 -0
- disaster_fund_plan-0.1.0/src/disaster_fund_plan.egg-info/dependency_links.txt +1 -0
- disaster_fund_plan-0.1.0/src/disaster_fund_plan.egg-info/entry_points.txt +2 -0
- disaster_fund_plan-0.1.0/src/disaster_fund_plan.egg-info/requires.txt +1 -0
- disaster_fund_plan-0.1.0/src/disaster_fund_plan.egg-info/top_level.txt +1 -0
- disaster_fund_plan-0.1.0/src/fm_plan/__init__.py +18 -0
- disaster_fund_plan-0.1.0/src/fm_plan/__main__.py +25 -0
- disaster_fund_plan-0.1.0/src/fm_plan/loader.py +41 -0
- disaster_fund_plan-0.1.0/src/fm_plan/models.py +515 -0
- disaster_fund_plan-0.1.0/src/fm_plan/py.typed +0 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: disaster-fund-plan
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Disaster Reconstruction & Relief Fund Implementation Plan Builder and Validator
|
|
5
|
+
Author-email: Pujan Pandey <arghakhanchipujan@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/arghakhanchipujan/Fund-Management
|
|
8
|
+
Keywords: disaster-relief,fund-management,reconstruction,nepal,grants,planning
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
18
|
+
Classifier: Development Status :: 3 - Alpha
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: pydantic>=2
|
|
22
|
+
|
|
23
|
+
# disaster-fund-plan
|
|
24
|
+
|
|
25
|
+
**Describe a disaster reconstruction plan in one JSON file, and get it checked before any money is calculated.**
|
|
26
|
+
|
|
27
|
+
`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`](https://pypi.org/project/disaster-fund-calculation-engine/) takes this plan and works out the money.
|
|
28
|
+
|
|
29
|
+
- Python 3.10 or newer
|
|
30
|
+
- Only dependency: `pydantic>=2`
|
|
31
|
+
- Import name: `fm_plan` (the PyPI name is `disaster-fund-plan`)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install disaster-fund-plan
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## What is a "plan"?
|
|
40
|
+
|
|
41
|
+
A plan is a single JSON file that says:
|
|
42
|
+
|
|
43
|
+
| Section | What it says | Example |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `plan` | Which disaster, which plan, when it applies, and which areas are affected | Gorkha Earthquake, Housing Reconstruction v1, districts Gorkha and Sindhupalchok |
|
|
46
|
+
| `government` | The administrative tree: Federal → Provinces → Districts → Local levels, each optionally with an official code | Sindhupalchok (`23`) is in Bagmati Province (`P3`) and is a `Pahad` district |
|
|
47
|
+
| `regions`, `disaster_types`, `solution_types` | The allowed categories | `Himal / Pahad / Terai`, `Earthquake`, `Reconstruction / Retrofitting` |
|
|
48
|
+
| `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) |
|
|
49
|
+
| `government_liability` | Who pays what share: Federal %, Province %, Local % (must total 100) | 50 / 30 / 20 |
|
|
50
|
+
|
|
51
|
+
A complete working example (`example_plan.json`) ships in the project repository; a minimal one is shown at the end of this README.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Use cases
|
|
56
|
+
|
|
57
|
+
### 1. Check a plan file before you trust it (command line)
|
|
58
|
+
|
|
59
|
+
You have just received or edited a plan file and want to know whether it is consistent.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python -m fm_plan example_plan.json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
OK: plan_id = gorkha-earthquake-2015-housing-reconstruction-v1
|
|
67
|
+
2 affected area(s), 2 district(s), 6 matrix row(s)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
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:
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
bad_plan.json has 1 problem(s):
|
|
74
|
+
- government_liability: government liability percentages must sum to 100%, got 110.0%
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The same command is installed as `fm-plan example_plan.json`.
|
|
78
|
+
|
|
79
|
+
### 2. Load a plan inside your application
|
|
80
|
+
|
|
81
|
+
Your backend needs a validated plan at start-up, and a clear error if the file is wrong.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from fm_plan import load_plan, PlanLoadError
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
plan = load_plan("example_plan.json")
|
|
88
|
+
except PlanLoadError as error:
|
|
89
|
+
print(error) # human-readable list of problems
|
|
90
|
+
raise SystemExit(1)
|
|
91
|
+
|
|
92
|
+
print(plan.plan_id) # gorkha-earthquake-2015-housing-reconstruction-v1
|
|
93
|
+
print(plan.government_liability.federal) # 50.0
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`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.
|
|
97
|
+
|
|
98
|
+
### 3. Find out whether an address is covered by the plan
|
|
99
|
+
|
|
100
|
+
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.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
plan.covers("Sindhupalchok") # True (district is listed)
|
|
104
|
+
plan.covers("23") # True (same district, by code)
|
|
105
|
+
plan.covers("Sindhupalchok", "Melamchi Municipality", 2) # True (ward 2 is listed)
|
|
106
|
+
plan.covers("Sindhupalchok", "Melamchi Municipality", 9) # False (only wards 1, 2, 3 are affected)
|
|
107
|
+
plan.covers("Kathmandu") # False (not in the plan)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- `covers(district, local_level=None, ward=None)` is exact, **including wards**.
|
|
111
|
+
- `is_affected(district, local_level=None)` ignores wards: use it when your data has no ward (for example, counts grouped by municipality).
|
|
112
|
+
|
|
113
|
+
In the plan file, leaving out `local_levels` means "the whole district"; leaving out `wards` means "the whole local level".
|
|
114
|
+
|
|
115
|
+
### 4. Look up what a household is entitled to
|
|
116
|
+
|
|
117
|
+
Read the grant for one combination straight from the matrix.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
row = next(
|
|
121
|
+
r for r in plan.grant_matrix
|
|
122
|
+
if (r.region, r.disaster_type, r.solution_type) == ("Pahad", "Earthquake", "Reconstruction")
|
|
123
|
+
)
|
|
124
|
+
print(row.total) # 300000
|
|
125
|
+
print([(t.stage, t.amount) for t in row.tranches])
|
|
126
|
+
# [('shelter', 15000), ('shelter', 15000), ('main', 100000), ('main', 100000), ('main', 70000)]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
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).
|
|
130
|
+
|
|
131
|
+
### 5. Resolve administrative units by name or code
|
|
132
|
+
|
|
133
|
+
Master data often arrives with codes in one system and names in another.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
gov = plan.government
|
|
137
|
+
gov.find_district("23").name # 'Sindhupalchok'
|
|
138
|
+
gov.find_district("gorkha").code # '36'
|
|
139
|
+
gov.find_province("P3").name # 'Bagmati Province'
|
|
140
|
+
gov.find_local_level("23001") # [LocalLevel(name='Melamchi Municipality', ...)]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`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.
|
|
144
|
+
|
|
145
|
+
### 6. Build a plan in code, save it, and load it again
|
|
146
|
+
|
|
147
|
+
You are generating plans from a form or database instead of writing JSON by hand.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
import json
|
|
151
|
+
from fm_plan import ImplementationPlan
|
|
152
|
+
|
|
153
|
+
plan = ImplementationPlan.model_validate(my_dict) # raises pydantic.ValidationError if wrong
|
|
154
|
+
open("saved_plan.json", "w").write(plan.model_dump_json(indent=2))
|
|
155
|
+
|
|
156
|
+
same_plan = ImplementationPlan.model_validate_json(open("saved_plan.json").read())
|
|
157
|
+
assert same_plan.plan_id == plan.plan_id
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
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.
|
|
161
|
+
|
|
162
|
+
### 7. Set (and protect) the government liability split
|
|
163
|
+
|
|
164
|
+
The Federal/Province/Local percentages change every rupee the engine reports, so they are checked strictly: each between 0 and 100, together exactly 100.
|
|
165
|
+
|
|
166
|
+
If `government_liability` is **missing** from the file, the package uses 50 / 30 / 20 but emits a `DefaultLiabilityWarning` so the default is never silent:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
import warnings
|
|
170
|
+
from fm_plan import DefaultLiabilityWarning
|
|
171
|
+
|
|
172
|
+
warnings.simplefilter("error", DefaultLiabilityWarning) # make a missing split a hard failure
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## What gets validated
|
|
178
|
+
|
|
179
|
+
| Area | Rule |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| Dates | Disaster has a start **and** end date; end is not before start. The plan's effective date must start **after** the disaster ends. |
|
|
182
|
+
| Hierarchy & Boundary Links | Every district links to an existing province code; every local level links to an existing district code. Affected areas reference district and local level codes. No duplicate names or codes. |
|
|
183
|
+
| 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. |
|
|
184
|
+
| Affected areas | Every listed district and local level exists in the `government` section; no district or local level is listed twice. |
|
|
185
|
+
| Regions | Every district's region is in `regions` (`Himal`, `Pahad`, `Terai`). |
|
|
186
|
+
| Grant matrix | Every row uses known region / disaster / solution names; no repeated row; **no missing combination**. |
|
|
187
|
+
| Tranches | Amounts are whole rupees above zero; stage is `shelter` or `main`; shelter tranches come first. |
|
|
188
|
+
| Liability | Federal + Province + Local = 100%. |
|
|
189
|
+
| Whole file | Unknown keys are rejected (a typo such as `"distrcit"` is an error, not silently ignored). Names are compared ignoring capital letters. |
|
|
190
|
+
|
|
191
|
+
## Plan ID
|
|
192
|
+
|
|
193
|
+
The ID is computed, never typed:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
<disaster name>-<disaster start year>-<plan name>-v<plan version>
|
|
197
|
+
Gorkha Earthquake, 2015, Housing Reconstruction, version 1 → gorkha-earthquake-2015-housing-reconstruction-v1
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Changing the plan version creates a new ID, so results from different plan versions can never be mixed up.
|
|
201
|
+
|
|
202
|
+
## Minimal plan file
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"plan": {
|
|
207
|
+
"plan_name": "Housing Reconstruction",
|
|
208
|
+
"plan_version": 1,
|
|
209
|
+
"disaster_name": "Gorkha Earthquake",
|
|
210
|
+
"disaster_date_range": { "start": "2015-04-25", "end": "2015-05-12" },
|
|
211
|
+
"effective_date_range": { "start": "2015-09-01", "end": null },
|
|
212
|
+
"affected_areas": [ { "district": "36" } ]
|
|
213
|
+
},
|
|
214
|
+
"government": {
|
|
215
|
+
"federal": { "name": "Government of Nepal", "code": "FED" },
|
|
216
|
+
"provinces": [ { "name": "Gandaki Province", "code": "P4" } ],
|
|
217
|
+
"districts": [ { "name": "Gorkha", "code": "36", "province": "P4", "region": "Pahad" } ],
|
|
218
|
+
"local_levels": [ { "name": "Gorkha Municipality", "code": "36001", "district": "36" } ]
|
|
219
|
+
},
|
|
220
|
+
"government_liability": { "federal": 50, "province": 30, "local": 20 },
|
|
221
|
+
"regions": ["Pahad"],
|
|
222
|
+
"disaster_types": ["Earthquake"],
|
|
223
|
+
"solution_types": ["Reconstruction"],
|
|
224
|
+
"grant_matrix": [
|
|
225
|
+
{
|
|
226
|
+
"region": "Pahad", "disaster_type": "Earthquake", "solution_type": "Reconstruction",
|
|
227
|
+
"tranches": [
|
|
228
|
+
{ "stage": "shelter", "amount": 15000 },
|
|
229
|
+
{ "stage": "main", "amount": 100000 }
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## API summary
|
|
237
|
+
|
|
238
|
+
| Name | Purpose |
|
|
239
|
+
| --- | --- |
|
|
240
|
+
| `load_plan(path)` | Read, validate and return an `ImplementationPlan`. Raises `PlanLoadError`. |
|
|
241
|
+
| `PlanLoadError` | The file is missing, not valid JSON, or fails validation. The message lists each problem. |
|
|
242
|
+
| `ImplementationPlan` | The validated plan. Has `.plan_id`, `.plan`, `.government`, `.regions`, `.disaster_types`, `.solution_types`, `.grant_matrix`, `.government_liability`, `.covers()`, `.is_affected()`. |
|
|
243
|
+
| `DefaultLiabilityWarning` | Emitted when `government_liability` is missing and the 50/30/20 default is used. |
|
|
244
|
+
|
|
245
|
+
## Limits
|
|
246
|
+
|
|
247
|
+
- Amounts are whole rupees (no paisa).
|
|
248
|
+
- The plan describes *rates and structure*. Counts of actual beneficiaries, donations and all money calculations belong to `disaster-fund-calculation-engine`.
|
|
249
|
+
|
|
250
|
+
## License
|
|
251
|
+
|
|
252
|
+
MIT
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# disaster-fund-plan
|
|
2
|
+
|
|
3
|
+
**Describe a disaster reconstruction plan in one JSON file, and get it checked before any money is calculated.**
|
|
4
|
+
|
|
5
|
+
`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`](https://pypi.org/project/disaster-fund-calculation-engine/) takes this plan and works out the money.
|
|
6
|
+
|
|
7
|
+
- Python 3.10 or newer
|
|
8
|
+
- Only dependency: `pydantic>=2`
|
|
9
|
+
- Import name: `fm_plan` (the PyPI name is `disaster-fund-plan`)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install disaster-fund-plan
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What is a "plan"?
|
|
18
|
+
|
|
19
|
+
A plan is a single JSON file that says:
|
|
20
|
+
|
|
21
|
+
| Section | What it says | Example |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `plan` | Which disaster, which plan, when it applies, and which areas are affected | Gorkha Earthquake, Housing Reconstruction v1, districts Gorkha and Sindhupalchok |
|
|
24
|
+
| `government` | The administrative tree: Federal → Provinces → Districts → Local levels, each optionally with an official code | Sindhupalchok (`23`) is in Bagmati Province (`P3`) and is a `Pahad` district |
|
|
25
|
+
| `regions`, `disaster_types`, `solution_types` | The allowed categories | `Himal / Pahad / Terai`, `Earthquake`, `Reconstruction / Retrofitting` |
|
|
26
|
+
| `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) |
|
|
27
|
+
| `government_liability` | Who pays what share: Federal %, Province %, Local % (must total 100) | 50 / 30 / 20 |
|
|
28
|
+
|
|
29
|
+
A complete working example (`example_plan.json`) ships in the project repository; a minimal one is shown at the end of this README.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Use cases
|
|
34
|
+
|
|
35
|
+
### 1. Check a plan file before you trust it (command line)
|
|
36
|
+
|
|
37
|
+
You have just received or edited a plan file and want to know whether it is consistent.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python -m fm_plan example_plan.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
OK: plan_id = gorkha-earthquake-2015-housing-reconstruction-v1
|
|
45
|
+
2 affected area(s), 2 district(s), 6 matrix row(s)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
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:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
bad_plan.json has 1 problem(s):
|
|
52
|
+
- government_liability: government liability percentages must sum to 100%, got 110.0%
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The same command is installed as `fm-plan example_plan.json`.
|
|
56
|
+
|
|
57
|
+
### 2. Load a plan inside your application
|
|
58
|
+
|
|
59
|
+
Your backend needs a validated plan at start-up, and a clear error if the file is wrong.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from fm_plan import load_plan, PlanLoadError
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
plan = load_plan("example_plan.json")
|
|
66
|
+
except PlanLoadError as error:
|
|
67
|
+
print(error) # human-readable list of problems
|
|
68
|
+
raise SystemExit(1)
|
|
69
|
+
|
|
70
|
+
print(plan.plan_id) # gorkha-earthquake-2015-housing-reconstruction-v1
|
|
71
|
+
print(plan.government_liability.federal) # 50.0
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`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.
|
|
75
|
+
|
|
76
|
+
### 3. Find out whether an address is covered by the plan
|
|
77
|
+
|
|
78
|
+
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.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
plan.covers("Sindhupalchok") # True (district is listed)
|
|
82
|
+
plan.covers("23") # True (same district, by code)
|
|
83
|
+
plan.covers("Sindhupalchok", "Melamchi Municipality", 2) # True (ward 2 is listed)
|
|
84
|
+
plan.covers("Sindhupalchok", "Melamchi Municipality", 9) # False (only wards 1, 2, 3 are affected)
|
|
85
|
+
plan.covers("Kathmandu") # False (not in the plan)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- `covers(district, local_level=None, ward=None)` is exact, **including wards**.
|
|
89
|
+
- `is_affected(district, local_level=None)` ignores wards: use it when your data has no ward (for example, counts grouped by municipality).
|
|
90
|
+
|
|
91
|
+
In the plan file, leaving out `local_levels` means "the whole district"; leaving out `wards` means "the whole local level".
|
|
92
|
+
|
|
93
|
+
### 4. Look up what a household is entitled to
|
|
94
|
+
|
|
95
|
+
Read the grant for one combination straight from the matrix.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
row = next(
|
|
99
|
+
r for r in plan.grant_matrix
|
|
100
|
+
if (r.region, r.disaster_type, r.solution_type) == ("Pahad", "Earthquake", "Reconstruction")
|
|
101
|
+
)
|
|
102
|
+
print(row.total) # 300000
|
|
103
|
+
print([(t.stage, t.amount) for t in row.tranches])
|
|
104
|
+
# [('shelter', 15000), ('shelter', 15000), ('main', 100000), ('main', 100000), ('main', 70000)]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
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).
|
|
108
|
+
|
|
109
|
+
### 5. Resolve administrative units by name or code
|
|
110
|
+
|
|
111
|
+
Master data often arrives with codes in one system and names in another.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
gov = plan.government
|
|
115
|
+
gov.find_district("23").name # 'Sindhupalchok'
|
|
116
|
+
gov.find_district("gorkha").code # '36'
|
|
117
|
+
gov.find_province("P3").name # 'Bagmati Province'
|
|
118
|
+
gov.find_local_level("23001") # [LocalLevel(name='Melamchi Municipality', ...)]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`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.
|
|
122
|
+
|
|
123
|
+
### 6. Build a plan in code, save it, and load it again
|
|
124
|
+
|
|
125
|
+
You are generating plans from a form or database instead of writing JSON by hand.
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
import json
|
|
129
|
+
from fm_plan import ImplementationPlan
|
|
130
|
+
|
|
131
|
+
plan = ImplementationPlan.model_validate(my_dict) # raises pydantic.ValidationError if wrong
|
|
132
|
+
open("saved_plan.json", "w").write(plan.model_dump_json(indent=2))
|
|
133
|
+
|
|
134
|
+
same_plan = ImplementationPlan.model_validate_json(open("saved_plan.json").read())
|
|
135
|
+
assert same_plan.plan_id == plan.plan_id
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
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.
|
|
139
|
+
|
|
140
|
+
### 7. Set (and protect) the government liability split
|
|
141
|
+
|
|
142
|
+
The Federal/Province/Local percentages change every rupee the engine reports, so they are checked strictly: each between 0 and 100, together exactly 100.
|
|
143
|
+
|
|
144
|
+
If `government_liability` is **missing** from the file, the package uses 50 / 30 / 20 but emits a `DefaultLiabilityWarning` so the default is never silent:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import warnings
|
|
148
|
+
from fm_plan import DefaultLiabilityWarning
|
|
149
|
+
|
|
150
|
+
warnings.simplefilter("error", DefaultLiabilityWarning) # make a missing split a hard failure
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## What gets validated
|
|
156
|
+
|
|
157
|
+
| Area | Rule |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| Dates | Disaster has a start **and** end date; end is not before start. The plan's effective date must start **after** the disaster ends. |
|
|
160
|
+
| Hierarchy & Boundary Links | Every district links to an existing province code; every local level links to an existing district code. Affected areas reference district and local level codes. No duplicate names or codes. |
|
|
161
|
+
| 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. |
|
|
162
|
+
| Affected areas | Every listed district and local level exists in the `government` section; no district or local level is listed twice. |
|
|
163
|
+
| Regions | Every district's region is in `regions` (`Himal`, `Pahad`, `Terai`). |
|
|
164
|
+
| Grant matrix | Every row uses known region / disaster / solution names; no repeated row; **no missing combination**. |
|
|
165
|
+
| Tranches | Amounts are whole rupees above zero; stage is `shelter` or `main`; shelter tranches come first. |
|
|
166
|
+
| Liability | Federal + Province + Local = 100%. |
|
|
167
|
+
| Whole file | Unknown keys are rejected (a typo such as `"distrcit"` is an error, not silently ignored). Names are compared ignoring capital letters. |
|
|
168
|
+
|
|
169
|
+
## Plan ID
|
|
170
|
+
|
|
171
|
+
The ID is computed, never typed:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
<disaster name>-<disaster start year>-<plan name>-v<plan version>
|
|
175
|
+
Gorkha Earthquake, 2015, Housing Reconstruction, version 1 → gorkha-earthquake-2015-housing-reconstruction-v1
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Changing the plan version creates a new ID, so results from different plan versions can never be mixed up.
|
|
179
|
+
|
|
180
|
+
## Minimal plan file
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"plan": {
|
|
185
|
+
"plan_name": "Housing Reconstruction",
|
|
186
|
+
"plan_version": 1,
|
|
187
|
+
"disaster_name": "Gorkha Earthquake",
|
|
188
|
+
"disaster_date_range": { "start": "2015-04-25", "end": "2015-05-12" },
|
|
189
|
+
"effective_date_range": { "start": "2015-09-01", "end": null },
|
|
190
|
+
"affected_areas": [ { "district": "36" } ]
|
|
191
|
+
},
|
|
192
|
+
"government": {
|
|
193
|
+
"federal": { "name": "Government of Nepal", "code": "FED" },
|
|
194
|
+
"provinces": [ { "name": "Gandaki Province", "code": "P4" } ],
|
|
195
|
+
"districts": [ { "name": "Gorkha", "code": "36", "province": "P4", "region": "Pahad" } ],
|
|
196
|
+
"local_levels": [ { "name": "Gorkha Municipality", "code": "36001", "district": "36" } ]
|
|
197
|
+
},
|
|
198
|
+
"government_liability": { "federal": 50, "province": 30, "local": 20 },
|
|
199
|
+
"regions": ["Pahad"],
|
|
200
|
+
"disaster_types": ["Earthquake"],
|
|
201
|
+
"solution_types": ["Reconstruction"],
|
|
202
|
+
"grant_matrix": [
|
|
203
|
+
{
|
|
204
|
+
"region": "Pahad", "disaster_type": "Earthquake", "solution_type": "Reconstruction",
|
|
205
|
+
"tranches": [
|
|
206
|
+
{ "stage": "shelter", "amount": 15000 },
|
|
207
|
+
{ "stage": "main", "amount": 100000 }
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## API summary
|
|
215
|
+
|
|
216
|
+
| Name | Purpose |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| `load_plan(path)` | Read, validate and return an `ImplementationPlan`. Raises `PlanLoadError`. |
|
|
219
|
+
| `PlanLoadError` | The file is missing, not valid JSON, or fails validation. The message lists each problem. |
|
|
220
|
+
| `ImplementationPlan` | The validated plan. Has `.plan_id`, `.plan`, `.government`, `.regions`, `.disaster_types`, `.solution_types`, `.grant_matrix`, `.government_liability`, `.covers()`, `.is_affected()`. |
|
|
221
|
+
| `DefaultLiabilityWarning` | Emitted when `government_liability` is missing and the 50/30/20 default is used. |
|
|
222
|
+
|
|
223
|
+
## Limits
|
|
224
|
+
|
|
225
|
+
- Amounts are whole rupees (no paisa).
|
|
226
|
+
- The plan describes *rates and structure*. Counts of actual beneficiaries, donations and all money calculations belong to `disaster-fund-calculation-engine`.
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
MIT
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "disaster-fund-plan"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Disaster Reconstruction & Relief Fund Implementation Plan Builder and Validator"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Pujan Pandey", email = "arghakhanchipujan@gmail.com" }]
|
|
13
|
+
keywords = ["disaster-relief", "fund-management", "reconstruction", "nepal", "grants", "planning"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Topic :: Office/Business :: Financial :: Accounting",
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
dependencies = [
|
|
28
|
+
"pydantic>=2",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
fm-plan = "fm_plan.__main__:main"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Repository = "https://github.com/arghakhanchipujan/Fund-Management"
|