Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 23 additions & 23 deletions spp_metric_service/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,27 +45,27 @@ Key Capabilities
Key Models
~~~~~~~~~~

+---------------------------------+------------------------------------+
| Model | Description |
+=================================+====================================+
| ``spp.demographic.dimension`` | Configurable dimension for |
| | breakdowns (field or CEL) |
+---------------------------------+------------------------------------+
| ``spp.metrics.fairness`` | Abstract service: equity/parity |
| | analysis |
+---------------------------------+------------------------------------+
| ``spp.metrics.distribution`` | Abstract service: distribution |
| | statistics |
+---------------------------------+------------------------------------+
| ``spp.metrics.breakdown`` | Abstract service: |
| | multi-dimensional grouping |
+---------------------------------+------------------------------------+
| ``spp.metrics.privacy`` | Abstract service: k-anonymity |
| | enforcement |
+---------------------------------+------------------------------------+
| ``spp.metrics.dimension.cache`` | Abstract service: dimension |
| | evaluation cache |
+---------------------------------+------------------------------------+
+--------------------------------+-------------------------------------+
| Model | Description |
+================================+=====================================+
| ``spp.demographic.dimension`` | Configurable dimension for |
| | breakdowns (field or CEL) |
+--------------------------------+-------------------------------------+
| ``spp.metric.fairness`` | Abstract service: equity/parity |
| | analysis |
+--------------------------------+-------------------------------------+
| ``spp.metric.distribution`` | Abstract service: distribution |
| | statistics |
+--------------------------------+-------------------------------------+
| ``spp.metric.breakdown`` | Abstract service: multi-dimensional |
| | grouping |
+--------------------------------+-------------------------------------+
| ``spp.metric.privacy`` | Abstract service: k-anonymity |
| | enforcement |
+--------------------------------+-------------------------------------+
| ``spp.metric.dimension.cache`` | Abstract service: dimension |
| | evaluation cache |
+--------------------------------+-------------------------------------+

Configuration
~~~~~~~~~~~~~
Expand Down Expand Up @@ -96,10 +96,10 @@ Group Access
Extension Points
~~~~~~~~~~~~~~~~

- Override ``_analyze_dimension()`` in ``spp.metrics.fairness`` for
- Override ``_analyze_dimension()`` in ``spp.metric.fairness`` for
custom analysis logic
- Add new dimension types by extending ``spp.demographic.dimension``
- Override ``enforce()`` in ``spp.metrics.privacy`` for custom
- Override ``enforce()`` in ``spp.metric.privacy`` for custom
suppression strategies

Dependencies
Expand Down
2 changes: 1 addition & 1 deletion spp_metric_service/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "OpenSPP Metric Service",
"summary": "Computation services for fairness, distribution, breakdown, and privacy",
"category": "OpenSPP",
"version": "19.0.2.0.0",
"version": "19.0.2.1.0",
"sequence": 1,
"author": "OpenSPP.org",
"website": "https://github.com/OpenSPP/OpenSPP2",
Expand Down
16 changes: 10 additions & 6 deletions spp_metric_service/data/demographic_dimensions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<field name="description">Gender identity from ISO 5218 vocabulary</field>
<field name="sequence">10</field>
<field name="dimension_type">field</field>
<field name="field_path">gender_id</field>
<field name="applies_to">all</field>
<field name="field_path">gender_id.code</field>
<field name="applies_to">individuals</field>
<field
name="value_labels_json"
>{"0": "Not Known", "1": "Male", "2": "Female", "9": "Not Applicable"}</field>
Expand Down Expand Up @@ -56,23 +56,27 @@
>{"true": "Group/Household", "false": "Individual"}</field>
</record>

<!-- Age Group Dimension (example using CEL expression) -->
<!-- Age Group Dimension (UNICEF/WHO-aligned buckets) -->
<record id="dimension_age_group" model="spp.demographic.dimension">
<field name="name">age_group</field>
<field name="label">Age Group</field>
<field name="description">Age group based on birth date</field>
<field
name="description"
>Age group based on birth date (UNICEF/WHO-aligned)</field>
<field name="sequence">50</field>
<field name="dimension_type">expression</field>
<field
name="cel_expression"
><![CDATA[
r.birthdate == null ? "unknown" :
age_years(r.birthdate) < 18 ? "child" :
age_years(r.birthdate) < 5 ? "under_5" :
age_years(r.birthdate) < 15 ? "child" :
age_years(r.birthdate) < 18 ? "adolescent" :
age_years(r.birthdate) < 60 ? "adult" : "elderly"
]]></field>
<field name="applies_to">individuals</field>
<field
name="value_labels_json"
>{"child": "Child (0-17)", "adult": "Adult (18-59)", "elderly": "Elderly (60+)", "unknown": "Unknown"}</field>
>{"under_5": "Under 5", "child": "Child (5-14)", "adolescent": "Adolescent (15-17)", "adult": "Adult (18-59)", "elderly": "Elderly (60+)", "unknown": "Unknown"}</field>
</record>
</odoo>
46 changes: 46 additions & 0 deletions spp_metric_service/models/breakdown_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ def compute_breakdown(self, registrant_ids, group_by, statistics=None, context=N
if not dimensions:
return {}

# Auto-expand groups to members when any dimension applies to individuals only
needs_expansion = any(d.applies_to == "individuals" for d in dimensions)
if needs_expansion:
registrant_ids = self._expand_groups_to_members(registrant_ids)

if not registrant_ids:
return {}

# Get cache service
cache_service = self.env["spp.metric.dimension.cache"]

Expand Down Expand Up @@ -95,3 +103,41 @@ def compute_breakdown(self, registrant_ids, group_by, statistics=None, context=N
# TODO: Add per-cell statistics if needed

return breakdown

@api.model
def _expand_groups_to_members(self, registrant_ids):
"""
Expand group IDs to their individual member IDs.

Groups are replaced by their active members. Individual IDs pass through.
The result is deduplicated.

:param registrant_ids: List of partner IDs (groups and/or individuals)
:returns: Deduplicated list of individual partner IDs
:rtype: list
"""
# sudo: aggregate breakdown metrics must expand groups to their members
# across all registrants regardless of the caller's record rules.
# Read-only (no writes); callers are authorized at the service entry point.
Partner = self.env["res.partner"].sudo() # nosemgrep: odoo-sudo-without-context,odoo-sudo-on-sensitive-models
records = Partner.browse(registrant_ids).exists()

groups = records.filtered("is_group")
group_ids = groups.ids
individual_ids = set((records - groups).ids)

if not group_ids:
return list(individual_ids)

# Expand groups via active memberships
Membership = self.env["spp.group.membership"].sudo() # nosemgrep: odoo-sudo-without-context
memberships = Membership.search(
[
("group", "in", group_ids),
("is_ended", "=", False),
]
)

individual_ids.update(memberships.individual.ids)

return list(individual_ids)
Loading