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
7 changes: 0 additions & 7 deletions spp_aggregation/security/ir.model.access.csv

This file was deleted.

1 change: 0 additions & 1 deletion spp_aggregation/static/description/index.html

This file was deleted.

File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
{
"name": "OpenSPP Aggregation Engine",
"summary": "Unified aggregation service for statistics, simulations, and GIS queries",
"name": "OpenSPP Analytics",
"summary": "Query engine for indicators, simulations, and GIS analytics",
"category": "OpenSPP",
"version": "19.0.2.0.0",
"sequence": 1,
Expand All @@ -16,7 +16,7 @@
"spp_area",
"spp_registry",
"spp_security",
"spp_metrics_services",
"spp_metric_service",
],
"data": [
# Security
Expand All @@ -25,8 +25,8 @@
# Data
"data/cron_cache_cleanup.xml",
# Views
"views/aggregation_scope_views.xml",
"views/aggregation_access_views.xml",
"views/analytics_scope_views.xml",
"views/analytics_access_views.xml",
"views/menu.xml",
],
"assets": {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<odoo noupdate="1">
<!-- Cron job to clean up expired cache entries -->
<record id="ir_cron_cache_cleanup" model="ir.cron">
<field name="name">Aggregation: Cache Cleanup</field>
<field name="model_id" ref="model_spp_aggregation_cache_entry" />
<field name="name">Analytics: Cache Cleanup</field>
<field name="model_id" ref="model_spp_analytics_cache_entry" />
<field name="state">code</field>
<field name="code">model.cron_cleanup_expired()</field>
<field name="interval_number">1</field>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.

from . import aggregation_scope
from . import aggregation_access
from . import analytics_scope
from . import analytics_access
from . import service_scope_resolver
from . import service_cache
from . import statistic_registry
from . import indicator_registry
from . import service_aggregation
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
_logger = logging.getLogger(__name__)


class AggregationAccessRule(models.Model):
class AnalyticsAccessRule(models.Model):
"""
Access control rules for aggregation queries.

Expand All @@ -18,7 +18,7 @@ class AggregationAccessRule(models.Model):
Also controls k-anonymity thresholds and scope restrictions.
"""

_name = "spp.aggregation.access.rule"
_name = "spp.analytics.access.rule"
_description = "Aggregation Access Rule"
_order = "sequence, name"

Expand Down Expand Up @@ -101,7 +101,7 @@ class AggregationAccessRule(models.Model):
),
)
allowed_scope_ids = fields.Many2many(
comodel_name="spp.aggregation.scope",
comodel_name="spp.analytics.scope",
relation="spp_aggregation_access_rule_scope_rel",
column1="rule_id",
column2="scope_id",
Expand Down Expand Up @@ -184,7 +184,7 @@ def get_effective_rule_for_user(self, user=None):

:param user: res.users record (defaults to current user)
:returns: Access rule record or None if no rule matches
:rtype: spp.aggregation.access.rule or None
:rtype: spp.analytics.access.rule or None
"""
user = user or self.env.user

Expand All @@ -209,7 +209,7 @@ def check_scope_allowed(self, scope):
"""
Check if a scope is allowed under this rule.

:param scope: spp.aggregation.scope record or dict for inline scope
:param scope: spp.analytics.scope record or dict for inline scope
:returns: True if allowed
:raises: ValidationError if not allowed
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
_logger = logging.getLogger(__name__)


class AggregationScope(models.Model):
class AnalyticsScope(models.Model):
"""
Unified targeting scope for aggregation queries.

Expand All @@ -21,7 +21,7 @@ class AggregationScope(models.Model):
- Explicit ID lists
"""

_name = "spp.aggregation.scope"
_name = "spp.analytics.scope"
_description = "Aggregation Scope"
_order = "name"

Expand Down Expand Up @@ -170,7 +170,7 @@ def _compute_registrant_count(self):
else:
# For other types, resolve and count
try:
ids = self.env["spp.aggregation.scope.resolver"].resolve(scope)
ids = self.env["spp.analytics.scope.resolver"].resolve(scope)
scope.registrant_count = len(ids)
except (ValidationError, UserError) as e:
_logger.debug("Could not compute registrant count for scope %s: %s", scope.id, e)
Expand Down Expand Up @@ -253,7 +253,7 @@ def resolve_registrant_ids(self):
:rtype: list[int]
"""
self.ensure_one()
return self.env["spp.aggregation.scope.resolver"].resolve(self)
return self.env["spp.analytics.scope.resolver"].resolve(self)

def action_preview_registrants(self):
"""Action to preview registrants in this scope."""
Expand All @@ -276,7 +276,7 @@ def action_refresh_cache(self):
updates the last_cache_refresh timestamp.
"""
self.ensure_one()
cache_service = self.env["spp.aggregation.cache"]
cache_service = self.env["spp.analytics.cache"]
count = cache_service.invalidate_scope(self)
if count:
scope_name = self.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
_logger = logging.getLogger(__name__)


class StatisticRegistry(models.AbstractModel):
class IndicatorRegistry(models.AbstractModel):
"""Registry that maps statistic names to computation strategies.

Replaces the fallback chain in compute_single_statistic with
a clean lookup-based approach. Each statistic type registers
how it should be computed.
"""

_name = "spp.aggregation.statistic.registry"
_name = "spp.analytics.indicator.registry"
_description = "Statistic Computation Registry"

@api.model
Expand All @@ -23,7 +23,7 @@ def compute(self, stat_name, registrant_ids, context=None):

Lookup order:
1. Built-in statistics (count, gini)
2. spp.statistic records (via CEL variable)
2. spp.indicator records (via CEL variable)
3. spp.cel.variable records (direct)

:param stat_name: Statistic name
Expand All @@ -36,7 +36,7 @@ def compute(self, stat_name, registrant_ids, context=None):
if builtin_method is not None:
return builtin_method(registrant_ids)

# Try spp.statistic (if module installed)
# Try spp.indicator (if module installed)
value = self._try_statistic_model(stat_name, registrant_ids)
if value is not None:
return value
Expand All @@ -49,18 +49,18 @@ def compute(self, stat_name, registrant_ids, context=None):
# Provide diagnostic information if debug logging is enabled
if _logger.isEnabledFor(logging.DEBUG):
# Check if models exist
has_stat_model = self.env.get("spp.statistic") is not None
has_stat_model = self.env.get("spp.indicator") is not None
has_var_model = self.env.get("spp.cel.variable") is not None

stat_count = 0
var_count = 0
if has_stat_model:
stat_count = self.env["spp.statistic"].sudo().search_count([]) # nosemgrep: odoo-sudo-without-context
stat_count = self.env["spp.indicator"].sudo().search_count([]) # nosemgrep: odoo-sudo-without-context
if has_var_model:
var_count = self.env["spp.cel.variable"].sudo().search_count([]) # nosemgrep: odoo-sudo-without-context

_logger.debug(
"Statistic lookup failed for '%s'. Available: %d spp.statistic, %d spp.cel.variable",
"Statistic lookup failed for '%s'. Available: %d spp.indicator, %d spp.cel.variable",
stat_name,
stat_count,
var_count,
Expand All @@ -82,8 +82,8 @@ def list_available(self):
for name, info in self._BUILTINS.items():
available.append({"name": name, "label": info["label"], "source": "builtin"})

# From spp.statistic
stat_model = self.env.get("spp.statistic")
# From spp.indicator
stat_model = self.env.get("spp.indicator")
if stat_model:
for stat in stat_model.sudo().search([("active", "=", True)]): # nosemgrep: odoo-sudo-without-context
available.append({"name": stat.name, "label": stat.label, "source": "statistic"})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The source is hardcoded as 'statistic'. To align with the refactoring of spp.statistic to spp.indicator, this should be updated to 'indicator' for consistency.

Suggested change
available.append({"name": stat.name, "label": stat.label, "source": "statistic"})
available.append({"name": stat.name, "label": stat.label, "source": "indicator"})

Expand Down Expand Up @@ -145,13 +145,13 @@ def _compute_gini(self, registrant_ids):

@api.model
def _try_statistic_model(self, stat_name, registrant_ids):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The method name _try_statistic_model is misleading after the refactoring, as it now works with the spp.indicator model. Renaming it to _try_indicator_model would improve clarity. You will also need to update the call to this method inside the compute method.

Suggested change
def _try_statistic_model(self, stat_name, registrant_ids):
def _try_indicator_model(self, stat_name, registrant_ids):

"""Try computing via spp.statistic record.
"""Try computing via spp.indicator record.

:param stat_name: Statistic name
:param registrant_ids: List of partner IDs
:returns: Computed value or None
"""
stat_model = self.env.get("spp.statistic")
stat_model = self.env.get("spp.indicator")
if stat_model is None:
return None
stat = stat_model.sudo().search([("name", "=", stat_name)], limit=1) # nosemgrep: odoo-sudo-without-context
Expand Down
Loading
Loading