Indicators
Contents
Indicators#
This article guides you through understanding and customizing indicators in OpenSPP, using a practical scenario and a working example. Indicators are computed fields that provide dynamic, calculated values based on registrant data, essential for social protection programs to track demographic information, eligibility criteria, and program outcomes.
Field Naming Conventions
OpenSPP uses systematic field naming prefixes to categorize different types of indicators:
z_ind_: Prefix for all indicator fieldsgrp: Group-level indicators (households, families)indv: Individual-level indicatorscst: Custom/arbitrary indicators
Examples:
z_ind_grp_num_children: Number of children in a groupz_ind_indv_age_years: Age in years for an individualz_ind_grp_is_single_head_hh: Boolean indicating single-headed household
Indicator Types
Count Indicators: Count records matching specific criteria
Boolean Indicators: True/false flags based on conditions
Computed Indicators: Calculated values from other fields
Prerequisites#
Solid understanding of Odoo 17 module development, including Python, XML, and XPath.
OpenSPP modules "g2p_registry_group", "g2p_registry_individual", and "spp_custom_field" must be installed.
To set up OpenSPP for development, please refer to the Development Setup Guide.
Module Structure#
A typical custom indicators module follows the standard Odoo module structure. Here’s an example for spp_custom_indicators:
spp_custom_indicators/
├── __init__.py
├── __manifest__.py
├── models/
│ ├── __init__.py
│ └── res_partner.py
└── security/
└── ir.model.access.csv
Step-by-Step Guide#
Create the Module Scaffold#
Create a new directory for your module (e.g., spp_custom_indicators) and populate it with the basic Odoo module files and structure shown above.
Define Module Manifest#
Create a manifest file that includes the proper dependencies:
{
"name": "OpenSPP Custom Indicators",
"summary": "Adds custom indicators to registrants (res.partner)",
"category": "OpenSPP",
"version": "17.0.1.0.0",
"author": "Your Organization",
"website": "https://your-website.com",
"license": "LGPL-3",
"depends": [
"g2p_registry_group",
"g2p_registry_individual",
"spp_custom_field",
],
"data": [
# No need for view XML if using spp_custom_field
],
"application": False,
"installable": True,
"auto_install": False,
}
Extend the res.partner Model#
Create models/res_partner.py to add your indicators and import it in models/__init__.py:
from odoo import fields, models
import datetime
from dateutil.relativedelta import relativedelta
class G2PRegistrant(models.Model):
_inherit = "res.partner"
# Count indicator example
z_ind_grp_num_children = fields.Integer(
string="Number of children",
compute="_compute_ind_grp_num_children",
help="Number of children in the group",
store=True,
)
# Boolean indicator example
z_ind_grp_is_single_head_hh = fields.Boolean(
string="Is single-headed household",
compute="_compute_ind_grp_is_single_head_hh",
help="Single-headed HH - extracted from demographic data of HH adult members",
store=True,
)
# Individual indicator example
z_ind_indv_age_years = fields.Integer(
string="Age (years)",
compute="_compute_ind_indv_age_years",
store=True,
help="Computed age in years for individuals",
)
def _compute_ind_grp_num_children(self):
"""Compute the number of children in the group"""
now = datetime.datetime.now()
children = now - relativedelta(years=18)
domain = [("birthdate", ">=", children)]
self.compute_count_and_set_indicator("z_ind_grp_num_children", None, domain)
def _compute_ind_grp_is_single_head_hh(self):
"""Compute if this is a single-headed household"""
now = datetime.datetime.now()
domain = [("birthdate", "<", now - relativedelta(years=18))]
self.compute_count_and_set_indicator("z_ind_grp_is_single_head_hh", None, domain, presence_only=True)
def _compute_ind_indv_age_years(self):
"""Compute age in years for individuals"""
today = fields.Date.context_today(self)
for partner in self:
if partner.is_group or not partner.birthdate:
partner.z_ind_indv_age_years = 0
continue
age = today.year - partner.birthdate.year - (
(today.month, today.day) < (partner.birthdate.month, partner.birthdate.day)
)
partner.z_ind_indv_age_years = max(age, 0)
Add Security Access (Optional)#
If you introduce new models, add access rights. For simple field additions, this is not required. Example:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_spp_custom_indicator_admin,spp.custom.indicator.admin,spp_custom_indicators.model_g2pregistrant,g2p_registry_base.group_g2p_admin,1,1,1,1
Add More Indicators (Optional)#
You can add more complex indicators, such as those based on gender, disability, or custom fields. Use the provided helper method for consistency and performance:
def compute_count_and_set_indicator(self, field_name, membership_kinds, domain, presence_only=False):
"""
Helper method to compute and set indicator values
"""
# ...implementation provided by OpenSPP base modules...
Example: Elderly and Gender-Based Indicators#
def _compute_ind_grp_num_elderly(self):
now = datetime.datetime.now()
domain = [("birthdate", "<", now - relativedelta(years=65))]
self.compute_count_and_set_indicator("z_ind_grp_num_elderly", None, domain)
def _compute_ind_grp_num_adults_female_not_elderly(self):
now = datetime.datetime.now()
domain = [
("birthdate", ">=", now - relativedelta(years=65)),
("birthdate", "<", now - relativedelta(years=18)),
("gender", "=", "Female"),
]
self.compute_count_and_set_indicator("z_ind_grp_num_adults_female_not_elderly", None, domain)
Example: Disability Indicators#
def _compute_ind_grp_num_disability(self):
domain = [("z_cst_indv_disability_level", ">", 0)]
self.compute_count_and_set_indicator("z_ind_grp_num_disability", None, domain)
def _compute_ind_grp_is_hh_with_disabled(self):
domain = [("z_cst_indv_disability_level", ">", 0)]
self.compute_count_and_set_indicator("z_ind_grp_is_hh_with_disabled", None, domain, presence_only=True)
Add Constraints, and Validations (Optional)#
You can add additional constraints for indicator logic:
from odoo import api, ValidationError
@api.constrains("z_ind_grp_num_children")
def _check_num_children_positive(self):
for record in self:
if record.z_ind_grp_num_children is not None and record.z_ind_grp_num_children < 0:
raise ValidationError("Number of children cannot be negative.")
Install and Test#
Install or upgrade the module through the Apps menu.
Open the Individual and Group registries and verify the new indicators display in form views (handled automatically by
spp_custom_field).Create or update records and ensure the indicators compute correctly.
Test filtering and searching by indicator values.
Best Practices#
Use
store=Truefor indicators that need to be queried.Provide clear help text explaining the indicator's purpose.
References#
For more information on extending OpenSPP modules, refer to:
openspp.org