diff --git a/spp_cel_domain/README.rst b/spp_cel_domain/README.rst index 9a72fa2b4..5ae39f0a4 100644 --- a/spp_cel_domain/README.rst +++ b/spp_cel_domain/README.rst @@ -142,6 +142,27 @@ Dependencies Changelog ========= +19.0.2.1.1 +~~~~~~~~~~ + +- fix(executor): probe for the legacy metric evaluation service by + capability, not by model name. ``_exec_metric`` and the + aggregate-metric path took the presence of ``spp.indicator`` in the + registry to mean the retired ``spp_indicators`` service (with + ``evaluate()``) was installed; OpenSPP2's ``spp_indicator`` reuses + that model name for an unrelated configuration model, so wherever it + is installed every ``metric()`` over a variable whose cache was not + fresh raised ``AttributeError`` inside the executor and the whole + expression compiled to an error instead of the documented graceful + empty result. Both sites now return no matches, log a warning and + record a ``no_service`` entry in the metrics info when no model + exposes the service's ``evaluate()`` and + ``enqueue_refresh_from_domain()``, instead of raising. Note for + deployers: on such databases a ``metric()`` over a not-fresh cached + variable therefore yields an empty match set rather than an error, the + same degradation every database without ``spp_indicator`` already had + (#443) + 19.0.2.1.0 ~~~~~~~~~~ diff --git a/spp_cel_domain/__manifest__.py b/spp_cel_domain/__manifest__.py index 550c3d0f3..d53b3c3a2 100644 --- a/spp_cel_domain/__manifest__.py +++ b/spp_cel_domain/__manifest__.py @@ -2,7 +2,7 @@ { "name": "CEL Domain Query Builder", "summary": "Write simple CEL-like expressions to filter records (OpenSPP/OpenG2P friendly)", - "version": "19.0.2.1.0", + "version": "19.0.2.1.1", "license": "LGPL-3", "development_status": "Production/Stable", "author": "OpenSPP.org, OpenSPP Community", diff --git a/spp_cel_domain/models/cel_executor.py b/spp_cel_domain/models/cel_executor.py index b9fb36003..0be2cd845 100644 --- a/spp_cel_domain/models/cel_executor.py +++ b/spp_cel_domain/models/cel_executor.py @@ -1072,6 +1072,27 @@ def _split_child_membership(self, through_model: str, child_plan: Any) -> tuple[ # Fallback: cannot split return [], child_plan + _LEGACY_METRIC_SERVICE_METHODS = ("evaluate", "enqueue_refresh_from_domain") + + def _legacy_metric_service(self): + """The legacy metric evaluation service, or ``None`` when no module provides it. + + Two unrelated things share the ``spp.indicator`` model name: the + evaluation service of the retired ``spp_indicators`` module, which this + executor calls, and OpenSPP2's ``spp_indicator`` publishable-indicator + configuration model, which must be treated as "no service". The probe + therefore checks for the service's methods on the model class rather + than for the name in the registry. Class-level lookup keeps it free of + field access checks: a field that happened to carry one of these names + resolves to a non-callable descriptor. + """ + service = self.env.get("spp.indicator") + if service is None: + return None + if not all(callable(getattr(type(service), name, None)) for name in self._LEGACY_METRIC_SERVICE_METHODS): + return None + return service + def _exec_metric( self, model: str, @@ -1171,19 +1192,22 @@ def _exec_metric( # Compute candidate size cheaply via search_count base_count = self.env[subject_model].search_count(base_dom) - # Check for evaluation service (legacy spp.indicator for now) # TODO: Fully migrate to spp.data.cache.manager (Phase 4 of ADR-017 complete) - if "spp.indicator" not in self.env: + svc = self._legacy_metric_service() + if svc is None: # No evaluation service available - can only use SQL fast path # If we reach here, cache is not fresh and we can't compute self._logger.warning( "[CEL Metrics] No evaluation service available for metric=%s. " - "SQL fast path requires fresh cache. Consider installing spp_indicators module.", + "SQL fast path requires fresh cache; subjects without a fresh cached value are left out.", p.metric, ) + if metrics_info is not None: + mi = dict(status) + mi.update({"metric": p.metric, "period_key": period_key, "path": "no_service"}) + metrics_info.append(mi) return [] - svc = self.env["spp.indicator"] default_mode = "refresh" if (base_count < async_threshold) else "fallback" if default_mode == "fallback" and status.get("status") != "fresh" and not preview_cache_only_mode: # large + not fresh → enqueue refresh and report queued @@ -1590,16 +1614,19 @@ def _exec_agg_metric( # noqa: C901 if not all_child_ids: return [] - # Check for evaluation service (legacy spp.indicator for now) # TODO: Fully migrate to spp.data.cache.manager (Phase 4 of ADR-017 complete) - if "spp.indicator" not in self.env: + svc = self._legacy_metric_service() + if svc is None: self._logger.warning( "[CEL Metrics] No evaluation service available for aggregate metric=%s", p.metric, ) + if metrics_info is not None: + metrics_info.append( + {"metric": p.metric, "period_key": str(p.period_key or "default"), "path": "no_service"} + ) return [] - svc = self.env["spp.indicator"] values, stats = svc.evaluate( p.metric, p.child_model, diff --git a/spp_cel_domain/readme/HISTORY.md b/spp_cel_domain/readme/HISTORY.md index 1b4fd10d3..93a61d681 100644 --- a/spp_cel_domain/readme/HISTORY.md +++ b/spp_cel_domain/readme/HISTORY.md @@ -1,3 +1,7 @@ +### 19.0.2.1.1 + +- fix(executor): probe for the legacy metric evaluation service by capability, not by model name. `_exec_metric` and the aggregate-metric path took the presence of `spp.indicator` in the registry to mean the retired `spp_indicators` service (with `evaluate()`) was installed; OpenSPP2's `spp_indicator` reuses that model name for an unrelated configuration model, so wherever it is installed every `metric()` over a variable whose cache was not fresh raised `AttributeError` inside the executor and the whole expression compiled to an error instead of the documented graceful empty result. Both sites now return no matches, log a warning and record a `no_service` entry in the metrics info when no model exposes the service's `evaluate()` and `enqueue_refresh_from_domain()`, instead of raising. Note for deployers: on such databases a `metric()` over a not-fresh cached variable therefore yields an empty match set rather than an error, the same degradation every database without `spp_indicator` already had (#443) + ### 19.0.2.1.0 - feat(sql): compile CEL ternary expressions to SQL CASE via `to_sql_case`, with `case_when`/`comparison` builders and a right-associative ternary parsing fix diff --git a/spp_cel_domain/static/description/index.html b/spp_cel_domain/static/description/index.html index f280ce2a1..96a20364b 100644 --- a/spp_cel_domain/static/description/index.html +++ b/spp_cel_domain/static/description/index.html @@ -522,6 +522,28 @@

Changelog

+

19.0.2.1.1

+ +
+

19.0.2.1.0

-
+

19.0.2.0.0