Skip to content

Commit a4dd8e8

Browse files
feat(spp_hazard): re-land CAP vocabulary severity with upgrade migration (from #76)
Re-lands the spp_hazard severity->vocabulary change from reverted PR #76, together with the dependent adaptations in spp_drims, spp_drims_sl_demo and spp_hazard_programs, plus the two fixes the original PR lacked: - migrations/19.0.2.1.0/post-migration.py backfills severity_id / severity_override_id from the legacy 1-5 Selection columns for databases upgrading from v19.0.2.0.x (release Biliran ships the old schema). Mapping: 1->minor, 2->moderate, 3->severe, 4->severe, 5->extreme. - hazard_demo.xml incident-area records now use severity_override_id vocabulary refs; the stale severity_override field broke demo installs. Includes migration regression tests (mapping, area override, idempotency, unmapped values, fresh-install no-op).
1 parent 47b12f1 commit a4dd8e8

25 files changed

Lines changed: 1409 additions & 70 deletions

spp_drims/__manifest__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"and distribution tracking. Links to hazard incidents with multi-tier "
66
"approval workflows and warehouse operations.",
77
"category": "OpenSPP/Inventory",
8-
"version": "19.0.2.0.0",
8+
"version": "19.0.2.1.0",
99
"sequence": 1,
1010
"author": "OpenSPP.org",
1111
"website": "https://github.com/OpenSPP/OpenSPP2",

spp_drims/models/hazard_incident_area.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
22
from odoo import api, fields, models
33

4+
# Map CAP severity vocabulary codes to a 1-5 numeric scale (higher = more
5+
# severe) for choropleth map visualization.
6+
CAP_SEVERITY_NUMERIC = {
7+
"extreme": 5,
8+
"severe": 4,
9+
"moderate": 3,
10+
"minor": 2,
11+
"unknown": 1,
12+
}
13+
414

515
class HazardIncidentArea(models.Model):
616
"""Extend incident area with GIS polygon for map visualization."""
@@ -17,17 +27,12 @@ class HazardIncidentArea(models.Model):
1727
)
1828

1929
# Computed severity that falls back to incident severity
20-
effective_severity = fields.Selection(
21-
[
22-
("1", "Level 1 - Minor"),
23-
("2", "Level 2 - Moderate"),
24-
("3", "Level 3 - Significant"),
25-
("4", "Level 4 - Severe"),
26-
("5", "Level 5 - Catastrophic"),
27-
],
30+
effective_severity_id = fields.Many2one(
31+
"spp.vocabulary.code",
32+
string="Effective Severity",
2833
compute="_compute_effective_severity",
2934
store=True,
30-
help="Area-specific severity or inherited from incident",
35+
help="Area-specific severity override, or inherited from the incident",
3136
)
3237

3338
# Additional fields for GIS popup display and grouping
@@ -61,14 +66,14 @@ class HazardIncidentArea(models.Model):
6166
help="Numeric severity (1-5) for choropleth map visualization",
6267
)
6368

64-
@api.depends("severity_override", "incident_id.severity")
69+
@api.depends("severity_override_id", "incident_id.severity_id")
6570
def _compute_effective_severity(self):
66-
"""Compute effective severity from override or incident default."""
71+
"""Compute effective severity from area override or incident default."""
6772
for rec in self:
68-
rec.effective_severity = rec.severity_override or rec.incident_id.severity or "3"
73+
rec.effective_severity_id = rec.severity_override_id or rec.incident_id.severity_id
6974

70-
@api.depends("effective_severity")
75+
@api.depends("effective_severity_id")
7176
def _compute_severity_numeric(self):
72-
"""Compute numeric severity from selection value for choropleth."""
77+
"""Compute numeric severity from the CAP severity code for choropleth."""
7378
for rec in self:
74-
rec.severity_numeric = int(rec.effective_severity) if rec.effective_severity else 3
79+
rec.severity_numeric = CAP_SEVERITY_NUMERIC.get(rec.effective_severity_id.code, 0)

spp_drims/readme/HISTORY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 19.0.2.1.0
2+
3+
- feat: incident areas expose `effective_severity_id` (area override or incident severity) and a numeric 1-5 `severity_numeric` derived from CAP severity codes for choropleth visualization (re-land from #76, follows the spp_hazard severity vocabulary change).
4+
15
### 19.0.2.0.0
26

37
- Initial migration to OpenSPP2

spp_drims/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from . import test_coordination
88
from . import test_donation
99
from . import test_incident
10+
from . import test_incident_area_severity
1011
from . import test_personnel
1112
from . import test_request
1213
from . import test_request_from_template_wizard
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
from .common import DrimsTestCommon
3+
4+
5+
class TestIncidentAreaSeverity(DrimsTestCommon):
6+
"""Effective severity / numeric severity for the GIS choropleth.
7+
8+
Guards the spp_hazard severity migration (Selection -> CAP vocabulary
9+
Many2one): spp_drims extends spp.hazard.incident.area and must follow the
10+
new severity_id / severity_override_id fields.
11+
"""
12+
13+
@classmethod
14+
def setUpClass(cls):
15+
super().setUpClass()
16+
VocabCode = cls.env["spp.vocabulary.code"]
17+
ns = "urn:oasis:names:tc:cap:severity"
18+
cls.sev_extreme = VocabCode.get_code(ns, "extreme")
19+
cls.sev_severe = VocabCode.get_code(ns, "severe")
20+
cls.sev_moderate = VocabCode.get_code(ns, "moderate")
21+
22+
def _make_area(self, severity_override_id=False):
23+
return self.env["spp.hazard.incident.area"].create(
24+
{
25+
"incident_id": self.incident.id,
26+
"area_id": self.area.id,
27+
"severity_override_id": severity_override_id,
28+
}
29+
)
30+
31+
def test_effective_severity_uses_override(self):
32+
"""Area override takes precedence over the incident severity."""
33+
self.incident.severity_id = self.sev_moderate
34+
area = self._make_area(severity_override_id=self.sev_extreme.id)
35+
self.assertEqual(area.effective_severity_id, self.sev_extreme)
36+
self.assertEqual(area.severity_numeric, 5)
37+
38+
def test_effective_severity_falls_back_to_incident(self):
39+
"""Without an override, severity is inherited from the incident."""
40+
self.incident.severity_id = self.sev_severe
41+
area = self._make_area()
42+
self.assertEqual(area.effective_severity_id, self.sev_severe)
43+
self.assertEqual(area.severity_numeric, 4)
44+
45+
def test_severity_numeric_zero_when_unset(self):
46+
"""No override and no incident severity -> numeric 0 (no choropleth value)."""
47+
self.incident.severity_id = False
48+
area = self._make_area()
49+
self.assertFalse(area.effective_severity_id)
50+
self.assertEqual(area.severity_numeric, 0)

spp_drims_sl_demo/__manifest__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"summary": "Demo data generator for DRIMS Sri Lanka implementation. "
55
"Creates sample incidents, donations, requests, and stock for demonstrations.",
66
"category": "OpenSPP/Inventory",
7-
"version": "19.0.2.0.0",
7+
"version": "19.0.2.0.1",
88
"sequence": 1,
99
"author": "OpenSPP.org",
1010
"website": "https://github.com/OpenSPP/OpenSPP2",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 19.0.2.0.1
2+
3+
- fix: demo generator resolves scenario severity levels to CAP severity vocabulary codes (follows the spp_hazard severity vocabulary change, re-land from #76).
4+
15
### 19.0.2.0.0
26

37
- Initial migration to OpenSPP2

spp_drims_sl_demo/wizard/drims_demo_generator.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ def _simplify_geometry(geometry, tolerance=0.001):
4949
return mapping(simplified)
5050

5151

52+
# Severity is stored as a CAP vocabulary code (spp.vocabulary.code). The demo
53+
# scenarios describe severity with the legacy 1-5 scale, mapped here to the
54+
# equivalent CAP severity code.
55+
CAP_SEVERITY_NS = "urn:oasis:names:tc:cap:severity"
56+
_LEGACY_SEVERITY_TO_CAP = {
57+
"5": "extreme",
58+
"4": "severe",
59+
"3": "moderate",
60+
"2": "minor",
61+
"1": "unknown",
62+
}
63+
64+
5265
class DrimsDemoGenerator(models.TransientModel):
5366
_name = "spp.drims.demo.generator"
5467
_description = "DRIMS Demo Data Generator"
@@ -653,6 +666,13 @@ def _add_warehouse_gps_coordinates(self):
653666
)
654667
_logger.debug("Added GPS coordinates to warehouse %s", wh_name)
655668

669+
def _resolve_severity(self, legacy_value):
670+
"""Resolve a legacy 1-5 severity string to a CAP severity vocab code."""
671+
code = _LEGACY_SEVERITY_TO_CAP.get(legacy_value)
672+
if not code:
673+
return self.env["spp.vocabulary.code"]
674+
return self.env["spp.vocabulary.code"].get_code(CAP_SEVERITY_NS, code)
675+
656676
def _generate_incidents(self):
657677
"""Generate demo hazard incidents."""
658678
Incident = self.env["spp.hazard.incident"]
@@ -792,7 +812,7 @@ def _generate_incidents(self):
792812
area_details.append(
793813
{
794814
"area_id": area.id,
795-
"severity_override": area_info.get("severity"),
815+
"severity_override_id": self._resolve_severity(area_info.get("severity")).id,
796816
"affected_population_estimate": area_info.get("population", 0),
797817
"notes": f"Affected area in {scenario['name']}",
798818
}
@@ -805,7 +825,7 @@ def _generate_incidents(self):
805825
"name": scenario["name"],
806826
"code": scenario["code"],
807827
"category_id": scenario["category_id"],
808-
"severity": scenario.get("severity", "3"),
828+
"severity_id": self._resolve_severity(scenario.get("severity", "3")).id,
809829
"start_date": scenario["start_date"],
810830
"description": scenario.get("description", ""),
811831
"status": "active",

spp_hazard/__manifest__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"for emergency response. Links registrants to disaster events with geographic scope "
99
"and severity tracking to enable targeted humanitarian assistance.",
1010
"category": "OpenSPP/Targeting",
11-
"version": "19.0.2.0.2",
11+
"version": "19.0.2.1.0",
1212
"sequence": 1,
1313
"author": "OpenSPP.org",
1414
"website": "https://github.com/OpenSPP/OpenSPP2",
@@ -21,11 +21,13 @@
2121
"spp_registry",
2222
"spp_area",
2323
"spp_gis",
24+
"spp_vocabulary",
2425
],
2526
"data": [
2627
"security/privileges.xml",
2728
"security/groups.xml",
2829
"security/ir.model.access.csv",
30+
"data/vocabulary_cap.xml",
2931
"data/impact_type_data.xml",
3032
"data/user_roles.xml",
3133
"views/hazard_category_views.xml",

0 commit comments

Comments
 (0)