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
30 changes: 11 additions & 19 deletions spp_cel_event/IMPLEMENTATION_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Overview

This document describes the implementation of the CEL executor extension for event data
queries in `/home/user/openspp-modules-v2/spp_cel_event/models/cel_event_executor.py`.
queries in `models/cel_event_executor.py`.

## Architecture

Expand Down Expand Up @@ -134,7 +134,8 @@ JSON field extraction handles multiple data types:
The Python path is used when:

- Default value specified (requires post-processing)
- Complex where predicates in aggregations
- Complex where predicates in aggregations (NOTE: `where_predicate` is not yet
implemented — registrants with this parameter are silently skipped with a warning log)
- SQL execution fails
- Non-standard comparison operators

Expand All @@ -160,19 +161,22 @@ All execution paths log performance metrics:

```python
_logger.info(
"[CEL EVENT] EventValueCompare SQL: event_type=%s field=%s op=%s rhs=%s matches=%d",
"[CEL EVENT] EventValueCompare SQL: event_type=%s field=%s matches=%d",
plan.event_type,
plan.field_name,
len(partner_ids),
)
_logger.debug(
"[CEL EVENT] EventValueCompare SQL details: op=%s rhs=%r",
plan.op,
plan.rhs,
len(partner_ids),
)
```

Log tags:

- `[CEL EVENT]` - Event executor operations
- Includes: event_type, field, operator, RHS value, match count
- Includes: event_type, field, match count (op/rhs at DEBUG level only)
- Separate log entries for SQL vs Python paths

## Integration Points
Expand All @@ -199,14 +203,11 @@ Depends on `spp.event.data` model with:

### Period Parsing

Currently supports:
All period formats are supported via `cel_event_functions.parse_period()`:

- `YYYY`: Full year (e.g., '2024')
- `YYYY-QN`: Quarter (e.g., '2024-Q1')
- `YYYY-MM`: Month (e.g., '2024-03')

**TODO**: Add support for:

- `YYYY-HN`: Half year (e.g., '2024-H1')
- `YYYY-WNN`: ISO week (e.g., '2024-W01')

Expand Down Expand Up @@ -249,11 +250,7 @@ Currently supports:
- Cache results for identical queries within request
- Invalidate on event data changes

4. **Extended Period Support**
- Add half-year and ISO week parsing
- Support dynamic period functions (this_quarter(), last_year())

5. **Batch Optimization**
4. **Batch Optimization**
- When multiple event conditions in same expression
- Combine into single query with JOINs

Expand All @@ -263,8 +260,3 @@ Currently supports:
- `odoo.tools.sql.SQL`: SQL query builder
- `spp_cel_domain`: Base CEL executor
- `spp_event_data`: Event data model

## Files Modified

1. `/home/user/openspp-modules-v2/spp_cel_event/models/cel_event_executor.py` (created)
2. `/home/user/openspp-modules-v2/spp_cel_event/models/__init__.py` (updated import)
350 changes: 348 additions & 2 deletions spp_cel_event/README.rst

Large diffs are not rendered by default.

36 changes: 13 additions & 23 deletions spp_cel_event/SPEC_COMPLIANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,21 +142,21 @@ HAVING [agg_expr] [op] %s

### Temporal Filters

| Filter | Spec | Implementation | Status |
| ------------- | --------------- | ------------------------------ | ------ |
| after | Date >= | ✓ SQL & Python | ✓ |
| before | Date <= | ✓ SQL & Python | ✓ |
| within_days | Relative days | ✓ INTERVAL '%s days' | ✓ |
| within_months | Relative months | ✓ INTERVAL '%s months' | ✓ |
| period | Named periods | ✓ Parse YYYY, YYYY-QN, YYYY-MM | ✓ |
| Filter | Spec | Implementation | Status |
| ------------- | --------------- | ------------------------------------------------- | ------ |
| after | Date >= | ✓ SQL & Python | ✓ |
| before | Date <= | ✓ SQL & Python | ✓ |
| within_days | Relative days | ✓ INTERVAL '%s days' | ✓ |
| within_months | Relative months | ✓ INTERVAL '%s months' | ✓ |
| period | Named periods | ✓ Parse YYYY, YYYY-QN, YYYY-HN, YYYY-MM, YYYY-WNN | ✓ |

**Period Formats Implemented:**

- ✓ YYYY (full year)
- ✓ YYYY-QN (quarter)
- ✓ YYYY-HN (half year)
- ✓ YYYY-MM (month)
- ✗ YYYY-HN (half year) - TODO
- ✗ YYYY-WNN (ISO week) - TODO
- ✓ YYYY-WNN (ISO week)

### State Filtering

Expand Down Expand Up @@ -249,17 +249,12 @@ Implementation assumes they exist.
- Impact: EventsAggregate with where_predicate uses Python fallback
- Plan: Future enhancement to parse simple predicates to SQL

2. **Half-year and ISO week periods**
- Status: ✗ Not implemented
- Impact: These period formats not recognized
- Plan: Add to \_parse_period() in future update

3. **Default value handling in SQL**
2. **Default value handling in SQL**
- Status: ✗ Not implemented
- Impact: EventValueCompare with default uses Python fallback
- Plan: Could use COALESCE in future, complex due to type handling

4. **Event type auto-resolution caching**
3. **Event type auto-resolution caching**
- Status: ✗ No caching
- Impact: Repeated lookups of is_one_active_per_registrant
- Plan: Add request-level cache in future
Expand All @@ -271,23 +266,18 @@ Implementation assumes they exist.
| Query Plan Nodes | 3 | 3 | 0 | 0 |
| Selection Modes | 6 | 6 | 0 | 0 |
| Temporal Filters | 5 | 5 | 0 | 0 |
| Period Formats | 5 | 3 | 0 | 2 |
| Period Formats | 5 | 5 | 0 | 0 |
| Field Types | 4 | 4 | 0 | 0 |
| Aggregations | 5 | 5 | 0 | 0 |
| Error Handling | 5 | 5 | 0 | 0 |
| Performance | 5 | 4 | 1 | 0 |

**Overall Compliance:** 93% (35/37 items fully implemented)
**Overall Compliance:** 97% (37/38 items fully implemented)

**Partial Items:**

- EventsAggregate with where_predicate (SQL path deferred)

**Not Implemented (Planned):**

- YYYY-HN period format
- YYYY-WNN period format

## Recommendation

The implementation is **PRODUCTION READY** with the following notes:
Expand Down
4 changes: 2 additions & 2 deletions spp_cel_event/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"author": "OpenSPP.org",
"website": "https://github.com/OpenSPP/OpenSPP2",
"license": "LGPL-3",
"development_status": "Beta",
"development_status": "Production/Stable",
"maintainers": ["jeremi", "gonzalesedwin1123", "emjay0921"],
"depends": [
"spp_cel_domain",
Expand All @@ -29,7 +29,7 @@
"images": [],
"application": False,
"installable": True,
"auto_install": True,
"auto_install": ["spp_cel_domain", "spp_event_data", "spp_studio"],
"post_init_hook": "post_init_hook",
"summary": "Integrate event data with CEL expressions for eligibility and entitlement rules",
}
18 changes: 0 additions & 18 deletions spp_cel_event/data/cel_event_profiles.yaml

This file was deleted.

47 changes: 15 additions & 32 deletions spp_cel_event/data/cel_profiles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ presets:
- name: events_count
signature:
"events_count(type, after?, before?, within_days?, within_months?, period?,
states?, where?)"
states?)"
description: "Count events matching the criteria"
returns: integer
parameters:
Expand Down Expand Up @@ -174,17 +174,12 @@ presets:
type: list[str]
required: false
description: "Event states to include (default: ['active'])"
- name: where
type: str
required: false
description: "CEL predicate to filter events by field values"
# TODO: 'where' parameter planned but not yet implemented
examples:
- expression: "events_count('attendance') >= 180"
description: "Count all active attendance events"
- expression: "events_count('attendance', period='2024') >= 180"
description: "Count attendance in 2024"
- expression: "events_count('attendance', where='attended == true') >= 150"
description: "Count events where attended is true"
- expression: "events_count('visit', within_days=365) >= 4"
description: "At least 4 visits in last year"

Expand All @@ -195,7 +190,7 @@ presets:
- name: events_sum
signature:
"events_sum(type, field, after?, before?, within_days?, within_months?,
period?, states?, where?)"
period?, states?)"
description: "Sum numeric field values across matching events"
returns: number
parameters:
Expand Down Expand Up @@ -231,10 +226,7 @@ presets:
type: list[str]
required: false
description: "Event states to include (default: ['active'])"
- name: where
type: str
required: false
description: "CEL predicate to filter events"
# TODO: 'where' parameter planned but not yet implemented
examples:
- expression: "events_sum('attendance', 'days_present', period='2024') >= 180"
description: "Sum attendance days in 2024"
Expand All @@ -244,7 +236,7 @@ presets:
- name: events_avg
signature:
"events_avg(type, field, after?, before?, within_days?, within_months?,
period?, states?, where?)"
period?, states?)"
description: "Calculate average of numeric field values across matching events"
returns: number
parameters:
Expand Down Expand Up @@ -280,10 +272,7 @@ presets:
type: list[str]
required: false
description: "Event states to include (default: ['active'])"
- name: where
type: str
required: false
description: "CEL predicate to filter events"
# TODO: 'where' parameter planned but not yet implemented
examples:
- expression: "events_avg('survey', 'income', within_days=365) < 500"
description: "Average income from surveys in last year"
Expand All @@ -293,7 +282,7 @@ presets:
- name: events_min
signature:
"events_min(type, field, after?, before?, within_days?, within_months?,
period?, states?, where?)"
period?, states?)"
description: "Find minimum value of numeric field across matching events"
returns: number
parameters:
Expand Down Expand Up @@ -329,10 +318,7 @@ presets:
type: list[str]
required: false
description: "Event states to include (default: ['active'])"
- name: where
type: str
required: false
description: "CEL predicate to filter events"
# TODO: 'where' parameter planned but not yet implemented
examples:
- expression: "events_min('assessment', 'score', period='2024') >= 60"
description: "Minimum assessment score in 2024"
Expand All @@ -342,7 +328,7 @@ presets:
- name: events_max
signature:
"events_max(type, field, after?, before?, within_days?, within_months?,
period?, states?, where?)"
period?, states?)"
description: "Find maximum value of numeric field across matching events"
returns: number
parameters:
Expand Down Expand Up @@ -378,10 +364,7 @@ presets:
type: list[str]
required: false
description: "Event states to include (default: ['active'])"
- name: where
type: str
required: false
description: "CEL predicate to filter events"
# TODO: 'where' parameter planned but not yet implemented
examples:
- expression: "events_max('assessment', 'score', period='2024') >= 70"
description: "Maximum assessment score in 2024"
Expand Down Expand Up @@ -541,35 +524,35 @@ presets:
- name: events_count
signature:
"events_count(type, after?, before?, within_days?, within_months?, period?,
states?, where?)"
states?)"
description: "Count events matching criteria for group registrant"
returns: integer

- name: events_sum
signature:
"events_sum(type, field, after?, before?, within_days?, within_months?,
period?, states?, where?)"
period?, states?)"
description: "Sum numeric field across group registrant's events"
returns: number

- name: events_avg
signature:
"events_avg(type, field, after?, before?, within_days?, within_months?,
period?, states?, where?)"
period?, states?)"
description: "Average numeric field across group registrant's events"
returns: number

- name: events_min
signature:
"events_min(type, field, after?, before?, within_days?, within_months?,
period?, states?, where?)"
period?, states?)"
description: "Minimum value across group registrant's events"
returns: number

- name: events_max
signature:
"events_max(type, field, after?, before?, within_days?, within_months?,
period?, states?, where?)"
period?, states?)"
description: "Maximum value across group registrant's events"
returns: number

Expand Down
2 changes: 1 addition & 1 deletion spp_cel_event/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def post_init_hook(env):
sql_file = os.path.join(module_path, "data", "cel_event_indexes.sql")

if os.path.exists(sql_file):
with open(sql_file) as f:
with open(sql_file, encoding="utf-8") as f:
sql = f.read()

# Execute SQL
Expand Down
Loading
Loading