Fixes CI formatting failure introduced by the manual Sonar cognitive-
complexity fix (extracting the ignore-list/dedup check into its own
helper).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes CI formatting failure introduced by the manual Sonar cognitive-
complexity fix (extracting the levels/ignore_models_id check into its
own helper).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extends filter_notification_alerts (slot_manager.py) and
filter_notification_reports (formatters.py) to check
receiver_group.get('levels') and receiver_group.get('ignore_models_id'),
mirroring the existing contents/ignore pattern exactly. Closes the gap
SIENTIAPDE-2011 deliberately left open: the query-time union/intersection
filter widens what gets fetched for the union of all active groups, but until
now nothing narrowed each group back down to its own configured slice.
Both checks short-circuit via `not levels_list or ...` / `not
ignore_models_id_list or ...` rather than accessing notification['level']/
['model_id'] directly, so groups that don't configure either field (today's
common case, and every existing unit test fixture) never evaluate the
right-hand side and can't raise KeyError on notifications missing those keys.
ignore_models_id reads via `.get(...) or []`, defensively handling an
explicit `null` in a receiver_groups document from the start (same fix
SIENTIAPDE-2011 needed a follow-up commit for).
Tests:
- 8 new unit tests (4 per file): levels restriction, ignore_models_id
restriction, neither-configured passthrough, explicit-None safety.
- New e2e test (test_scenario_a_2_1_per_group_levels_and_ignore_models_id)
against real Mongo/Redis/Temporal: two active groups configured
differently: SIENTIAPDE-2011's query-time filter widens the fetch to the
union of both, and this subtask's per-group filter narrows each group back
to its own levels/ignore_models_id, including the case where a model is
ignored by only one of the active groups.
- Deduplicated the two e2e suites' identical _extract_html_body helper into
e2e/helpers.py (extract_html_body), now shared by both.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Manual code-review pass (code-review-python checklist) flagged that no test
exercised default_filter already restricting level (e.g. Alerts' ERROR-only
default) combined with every active group specifying its own levels. The
replace-not-AND behavior is deliberate per SIENTIAPDE-2011's design doc and
ticket text, but was previously only covered by a docstring comment. Adds
test_compute_query_time_filter_union_replaces_default_level_restriction so a
future change reintroducing AND semantics fails a test instead of passing
silently.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Code review findings on the SIENTIAPDE-2011 PR:
- [Major] _compute_query_time_filter used group.get('ignore_models_id', [])
for the intersection, whose default only applies when the key is missing.
receiver_groups has no schema validation, so a document with
ignore_models_id explicitly set to null (key present, value None) made
.get() return None, and set(None) raised TypeError, crashing the whole
activity for every mail type. Fixed with `group.get(...) or []`. Added a
regression test.
- [Minor] CLAUDE.md:48 described Alerts' base data filter as always
`level: ERROR`, which this ticket's own query-time union can now widen.
Updated the line to reflect that.
- [Blocker, not applied] A finding argued the levels union should AND with
default_filter's own level restriction instead of replacing it, to avoid
widening Alerts (ERROR-only real-time channel) when a receiver_group sets
broader levels. Both SIENTIAPDE-2011's ticket text and the parent design
doc explicitly call for the union to replace the hard-coded ERROR-only
default ("whatever levels the active groups actually care about"), so
applying that fix would revert the subtask's actual purpose. The
interim gap the finding correctly identifies (no per-group level filter
exists yet until SIENTIAPDE-2012 lands) is now documented in
_compute_query_time_filter's docstring instead of "fixed" by narrowing
query-time behavior the ticket asked for.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
notification['timestamp'] crosses at least one Temporal activity
boundary (load_latest_data -> filter_notification_reports/alerts)
before reaching format_log_report. Temporal's default data converter
serializes datetime values to plain ISO-8601 strings without
restoring them on decode, and pymongo returns naive datetimes for
BSON Date fields, so the string can carry no UTC offset at all. That
broke export_data_to_postgres's strict to_datetime(..., format=
DATETIME_FORMAT_WITH_TZ) parsing downstream with:
ValueError: time data "2024-06-01T12:00:00" doesn't match format
"%Y-%m-%d %H:%M:%S%z"
Same underlying issue as SIENTIAPDE-2007/2009, fixed here using the
same pattern as SlotManager.put_last_data_timestamp: normalize with
pandas.to_datetime(..., utc=True) and re-format to
DATETIME_FORMAT_WITH_TZ before the value crosses the next boundary.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI mypy caught what my local environment couldn't (Python 3.14 has no
compatible numpy stub for the project's python_version=3.11 mypy target,
so mypy couldn't run locally at all):
slot_manager.py:353: error: Incompatible types in assignment
(expression has type "str", variable has type "Timestamp")
slot_manager.py:368: error: Incompatible return value type
(got "Timestamp", expected "str | None")
Reassigning `last_data_timestamp` from a Timestamp to its strftime() string
trips mypy's inference (it fixes an unannotated local's type from its first
assignment). Split into a separate `last_data_timestamp_str` for the string
form instead of reusing the name.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
e2e fixtures (default_notification, seed_last_timestamp, default_receiver_group)
still assumed the pre-SIENTIAPDE-2007 string-timestamp representation and the
old ignore_models field name, so the load_notification_package e2e suite was
failing independently of this ticket's changes:
- default_notification now seeds `timestamp` as a native datetime (matches
production since SIENTIAPDE-2007's Notification.timestamp = now()).
- seed_last_timestamp now uses DATETIME_FORMAT_MS_WITH_TZ (matches what
put_last_data_timestamp actually writes to Redis today).
- default_receiver_group's ignore_models renamed to ignore_models_id, per
this ticket's decision.
- test_reports_main_workflow.py's explicit timestamp= call sites updated to
match.
Also fixes a real production bug in put_last_data_timestamp (slot_manager.py)
surfaced by running the e2e suite against a real Temporal environment: Temporal's
default data converter serializes datetime values to ISO-8601 strings across
the activity boundary, and pymongo returns naive datetimes for BSON Date
fields, so `data['timestamp'].max().strftime(...)` crashed on a plain str.
Unit tests never caught this because they mock the activity boundary and
never cross real (de)serialization. Fixed by normalizing through
`pandas.to_datetime(..., utc=True)`, which handles native datetime/Timestamp
and offset-aware/naive ISO strings uniformly.
Adds test_load_package_query_time_filter_union_and_intersection, a new e2e
test exercising the actual SIENTIAPDE-2011 behavior end-to-end: union of
active groups' levels, intersection of their ignore_models_id, and the
requirement that a model ignored by only one of several active groups must
not be excluded from the fetch.
Note: test_reports_main_workflow.py / test_alerts_main_workflow.py still have
a separate, pre-existing failure in process_notifications.py (same family of
bug: a notification's timestamp field parsed with a fixed strptime format
after crossing the same Temporal serialization boundary) — confirmed present
on the unmodified baseline too, unrelated to this ticket, tracked separately.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Push level and model-exclusion filtering down to the Mongo query instead of
fetching the full notification_queue unfiltered on every run.
- Fix await ordering in load_notification_package so sending_configs (active
receiver_groups) resolves before load_latest_data is called, letting the
query filter be built from group data.
- Add _compute_query_time_filter: unions active groups' `levels` (falls back
to the workflow's default filter if any group has no restriction, since
that group wants every level) and intersects their `ignore_models_id`
(a model is only safe to drop from the fetch if every active group
ignores it).
- Add unit tests covering mixed groups, a model ignored by only one group
(must not be excluded from the fetch), and the empty active-groups case.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses review feedback: prefer sientia_do's explicit datetime format
constant over Python's isoformat()/fromisoformat() when round-tripping
last_data_timestamp through Redis, matching the convention already used
elsewhere in mongo_db.py and slot_manager.py.
Notification.timestamp is now stored as a native BSON Date instead of a
string, so load_latest_data must parse the ISO string coming from Redis
back to datetime before building the $gt filter, or the comparison
silently breaks (BSON String vs Date always evaluates False). Also fixes
put_last_data_timestamp, which would otherwise raise TypeError trying to
json.dumps a pandas Timestamp when persisting the new watermark to Redis.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SIENTIAPDE-1646 Update RUNTIME_WORKFLOWS in TemporalManager to include 'drift' and 'simple_metrics', expanding workflow capabilities and enhancing orchestration functionality.
SIENTIAPDE-1646 Add support for runtime workflows in TemporalManager by introducing RUNTIME_WORKFLOWS constant and updating schedule creation logic to handle runtime names appropriately.
Update GITHUB_BRANCH in values.yaml to feature/SIENTIAPDE-1646, refactor mongo_db.py for cleaner code, and enhance worker.py by adding missing commas for improved syntax consistency.
Enhance orchestration configuration and documentation. Added `RUNTIME` variable to `.env.example`, updated `.gitignore` to exclude `openspec/` and `.cursor/`, and modified `README.md` to clarify queue naming conventions and runtime handling. Refactored activities to use synchronous database and email handling, improving performance and consistency. Updated test cases to reflect these changes and ensure compatibility with new activity definitions.
Fix error model retrieval in notification_generator.ipynb and add on_conflict parameter to common_config in orchestrator_functions.py for improved error handling.