Skip to content

Commit 3ac615d

Browse files
committed
feat: add metrics core and statistics system
New modules: - spp_metrics_core: unified foundation for all metrics (categories, base model) - spp_statistic: publishable statistics with k-anonymity privacy protection - spp_statistic_studio: Studio UI for statistics configuration (auto-installs when both spp_statistic and spp_studio are present)
1 parent 3258a93 commit 3ac615d

31 files changed

Lines changed: 2040 additions & 0 deletions

spp_metrics_core/README.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# OpenSPP Metrics Core
2+
3+
Shared foundation for all metrics (statistics, simulation metrics, etc.) in OpenSPP.
4+
5+
## Overview
6+
7+
`spp_metrics_core` provides the base model and categorization system that eliminates duplication of genuinely shared
8+
fields across different metric types. All domain modules (statistics, simulations, dashboards) inherit from the base
9+
model and add their own computation-specific fields.
10+
11+
## Architecture
12+
13+
```
14+
spp.metric.base (AbstractModel)
15+
16+
├── spp.statistic (extends with publication flags)
17+
├── spp.simulation.metric (extends with scenario-specific fields)
18+
└── [Your custom metric models]
19+
```
20+
21+
## Models
22+
23+
### spp.metric.base
24+
25+
Abstract model providing genuinely shared fields for all metric types.
26+
27+
Concrete models define their own computation-specific fields (metric_type, format, expressions, etc.) since these vary
28+
incompatibly between metric types.
29+
30+
**Identity**
31+
32+
- `name` - Technical identifier (e.g., 'children_under_5')
33+
- `label` - Human-readable display label (translated)
34+
- `description` - Detailed description (translated)
35+
36+
**Presentation**
37+
38+
- `unit` - Unit of measurement (e.g., 'people', 'USD', '%')
39+
- `decimal_places` - Decimal precision for display
40+
41+
**Categorization**
42+
43+
- `category_id` - Many2one to `spp.metric.category`
44+
45+
**Metadata**
46+
47+
- `sequence` - Display order within category
48+
- `active` - Inactive metrics are hidden
49+
50+
**What's NOT in the base** (defined by concrete models):
51+
52+
- `metric_type` / `format` - Each concrete model defines its own selections
53+
- `cel_expression` / `variable_id` - Computation approaches vary by type
54+
- `aggregation` - Only relevant for certain metric types
55+
56+
### spp.metric.category
57+
58+
Shared categorization for all metric types:
59+
60+
- `name` - Category name (e.g., "Population")
61+
- `code` - Technical code (e.g., "population")
62+
- `description` - Category description
63+
- `sequence` - Display order
64+
- `parent_id` - Optional parent category for hierarchical organization
65+
66+
## Default Categories
67+
68+
- **Population** - Population counts and demographics
69+
- **Coverage** - Program coverage and reach metrics
70+
- **Targeting** - Targeting accuracy and fairness metrics
71+
- **Distribution** - Distribution and entitlement metrics
72+
73+
## Defining Metrics
74+
75+
Since `spp.metric.base` is an **AbstractModel**, it does not store data directly. Domain modules define concrete metrics
76+
by inheriting from the base:
77+
78+
- `spp_statistic` - Defines published statistics
79+
- `spp_simulation` - Defines simulation metrics
80+
- Custom modules - Define domain-specific metrics
81+
82+
See the [Usage](#usage) section below for examples.
83+
84+
## Usage
85+
86+
### Creating Custom Metrics
87+
88+
Inherit from `spp.metric.base` to create domain-specific metrics:
89+
90+
```python
91+
class CustomMetric(models.Model):
92+
_name = "custom.metric"
93+
_inherit = ["spp.metric.base"]
94+
_description = "Custom Metric Type"
95+
96+
# Shared fields inherited from base:
97+
# - name, label, description
98+
# - unit, decimal_places
99+
# - category_id, sequence, active
100+
101+
# Define your computation-specific fields
102+
metric_type = fields.Selection([...]) # Your type selections
103+
computation_field = fields.Text() # Your computation approach
104+
105+
# Add domain-specific fields
106+
custom_field = fields.Boolean()
107+
custom_config = fields.Text()
108+
```
109+
110+
### Using Categories
111+
112+
Reference categories in your metrics:
113+
114+
```xml
115+
<record id="my_custom_metric" model="custom.metric">
116+
<field name="name">my_metric</field>
117+
<field name="label">My Custom Metric</field>
118+
<field name="category_id" ref="spp_metrics_core.category_population"/>
119+
</record>
120+
```
121+
122+
### Creating Custom Categories
123+
124+
Add domain-specific categories:
125+
126+
```xml
127+
<record id="category_health" model="spp.metric.category">
128+
<field name="name">Health</field>
129+
<field name="code">health</field>
130+
<field name="description">Health-related metrics</field>
131+
<field name="sequence">50</field>
132+
</record>
133+
```
134+
135+
## Migration
136+
137+
### From spp_statistic.category
138+
139+
The migration automatically renames `spp.statistic.category` to `spp.metric.category` while preserving all data and
140+
external references.
141+
142+
**Before**:
143+
144+
```python
145+
category = env['spp.statistic.category'].search([...])
146+
```
147+
148+
**After**:
149+
150+
```python
151+
category = env['spp.metric.category'].search([...])
152+
```
153+
154+
See [Migration Guide](../../docs/migration/statistics-refactoring.md) for details.
155+
156+
## Benefits
157+
158+
1. **No Duplication**: Genuinely shared fields defined once, reused everywhere
159+
2. **Model-Specific Freedom**: Each concrete model defines its own computation fields without conflicts
160+
3. **Consistent UI**: Common fields (name, label, category) display the same way
161+
4. **Shared Categories**: One categorization system for all metrics
162+
5. **Future-Proof**: New metric types easily add their own computation approaches
163+
164+
## Dependencies
165+
166+
- `base` - Odoo core
167+
168+
## Used By
169+
170+
- `spp_metrics_services` - Aggregation and computation services
171+
- `spp_statistic` - Published statistics
172+
- `spp_simulation` - Simulation metrics
173+
- Domain modules with custom metrics
174+
175+
## Architecture Documentation
176+
177+
See [Statistics System Architecture](../../docs/architecture/statistics-systems.md) for the complete system design.
178+
179+
## License
180+
181+
LGPL-3

spp_metrics_core/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
3+
from . import models

spp_metrics_core/__manifest__.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
{
3+
"name": "OpenSPP Metrics Core",
4+
"summary": "Unified metric foundation for statistics and simulations",
5+
"category": "OpenSPP",
6+
"version": "19.0.2.0.0",
7+
"sequence": 1,
8+
"author": "OpenSPP.org",
9+
"website": "https://github.com/OpenSPP/OpenSPP2",
10+
"license": "LGPL-3",
11+
"development_status": "Alpha",
12+
"maintainers": ["jeremi", "gonzalesedwin1123"],
13+
"depends": [
14+
"base",
15+
],
16+
"data": [
17+
"security/ir.model.access.csv",
18+
"data/metric_categories.xml",
19+
],
20+
"assets": {},
21+
"demo": [],
22+
"images": [],
23+
"application": False,
24+
"installable": True,
25+
"auto_install": False,
26+
"description": """
27+
OpenSPP Metrics Core
28+
====================
29+
30+
Unified foundation for all metrics (statistics, simulation metrics, etc.)
31+
32+
Architecture
33+
------------
34+
Provides a base abstract model that eliminates field duplication across
35+
different metric types (statistics, simulation metrics, etc.).
36+
37+
::
38+
39+
spp.metric.base (AbstractModel)
40+
41+
├── spp.statistic (extends with publication flags)
42+
└── spp.simulation.metric (extends with scenario-specific fields)
43+
44+
Models
45+
------
46+
- ``spp.metric.base``: Abstract model with shared metric fields
47+
- ``spp.metric.category``: Shared categorization for all metric types
48+
49+
Fields Provided
50+
---------------
51+
- **Identity**: name, label, description
52+
- **Computation**: metric_type, cel_expression, aggregation
53+
- **Presentation**: format, unit, decimal_places
54+
- **Categorization**: category_id
55+
- **Metadata**: sequence, active
56+
57+
Migration
58+
---------
59+
Automatically migrates ``spp.statistic.category`` to ``spp.metric.category``
60+
to maintain backward compatibility while providing a unified category model.
61+
""",
62+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<!--
3+
Part of OpenSPP. See LICENSE file for full copyright and licensing details.
4+
-->
5+
<odoo>
6+
7+
<!-- Default Metric Categories -->
8+
9+
<record id="category_population" model="spp.metric.category">
10+
<field name="name">Population</field>
11+
<field name="code">population</field>
12+
<field name="description">Population counts and demographics</field>
13+
<field name="sequence">10</field>
14+
</record>
15+
16+
<record id="category_coverage" model="spp.metric.category">
17+
<field name="name">Coverage</field>
18+
<field name="code">coverage</field>
19+
<field name="description">Program coverage and reach metrics</field>
20+
<field name="sequence">20</field>
21+
</record>
22+
23+
<record id="category_targeting" model="spp.metric.category">
24+
<field name="name">Targeting</field>
25+
<field name="code">targeting</field>
26+
<field name="description">Targeting accuracy and fairness metrics</field>
27+
<field name="sequence">30</field>
28+
</record>
29+
30+
<record id="category_distribution" model="spp.metric.category">
31+
<field name="name">Distribution</field>
32+
<field name="code">distribution</field>
33+
<field name="description">Distribution and entitlement metrics</field>
34+
<field name="sequence">40</field>
35+
</record>
36+
37+
</odoo>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
"""Migrate spp.statistic.category to spp.metric.category."""
3+
4+
import logging
5+
6+
_logger = logging.getLogger(__name__)
7+
8+
9+
def migrate(cr, version):
10+
"""Migrate spp.statistic.category to spp.metric.category.
11+
12+
Renames the table and sequence if they exist. This allows existing
13+
statistic categories to be used as metric categories without data loss.
14+
"""
15+
_logger.info("Starting migration: spp.statistic.category -> spp.metric.category")
16+
17+
# Check if old table exists
18+
cr.execute(
19+
"""
20+
SELECT EXISTS (
21+
SELECT FROM information_schema.tables
22+
WHERE table_schema = 'public'
23+
AND table_name = 'spp_statistic_category'
24+
)
25+
"""
26+
)
27+
table_exists = cr.fetchone()[0]
28+
29+
if table_exists:
30+
_logger.info("Found spp_statistic_category table, renaming to spp_metric_category")
31+
32+
# Rename table
33+
cr.execute(
34+
"""
35+
ALTER TABLE spp_statistic_category
36+
RENAME TO spp_metric_category
37+
"""
38+
)
39+
40+
# Check if old sequence exists
41+
cr.execute(
42+
"""
43+
SELECT EXISTS (
44+
SELECT FROM pg_class
45+
WHERE relname = 'spp_statistic_category_id_seq'
46+
AND relkind = 'S'
47+
)
48+
"""
49+
)
50+
seq_exists = cr.fetchone()[0]
51+
52+
if seq_exists:
53+
_logger.info(
54+
"Found spp_statistic_category_id_seq sequence, "
55+
"renaming to spp_metric_category_id_seq"
56+
)
57+
58+
# Rename sequence
59+
cr.execute(
60+
"""
61+
ALTER SEQUENCE spp_statistic_category_id_seq
62+
RENAME TO spp_metric_category_id_seq
63+
"""
64+
)
65+
66+
_logger.info("Successfully migrated spp.statistic.category to spp.metric.category")
67+
else:
68+
_logger.info("No spp_statistic_category table found, skipping migration")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
3+
from . import metric_base
4+
from . import metric_category

0 commit comments

Comments
 (0)