refactor: extract notification decisions and split core modules - #125
Conversation
📝 WalkthroughWalkthroughThe PR separates notification decisions from briefing generation, centralizes LLM transport, splits persistence and rendering responsibilities, extracts weather parsing modules, and reorganizes CLI composition, scheduling, diagnostics, prompts, and tests. ChangesApplication and notification flow
LLM transport and delivery
Persistence and weather parsing
CLI, prompts, and documentation
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
/agentic_review |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #125 +/- ##
========================================
Coverage 99.86% 99.87%
========================================
Files 117 140 +23
Lines 12435 13241 +806
Branches 742 790 +48
========================================
+ Hits 12418 13224 +806
Misses 12 12
Partials 5 5 ☔ View full report in Codecov by Harness. |
Code Review by Qodo
Context used✅ Compliance rules (platform):
46 rules 1.
|
eddcde0 to
0943e06
Compare
|
ⓘ Your Qodo trial ends soon. Ask your workspace admin to set up billing to keep reviews running after the trial. Manage billing |
|
/agentic_review |
1 similar comment
|
/agentic_review |
PR Summary by QodoDecouple notification decisions via typed policy core and per-kind prompts
AI Description
Diagram
High-Level Assessment
Files changed (57)
|
22a3ed0 to
9565802
Compare
|
Code review by qodo was updated up to the latest commit 22a3ed0 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@weather_briefing/notification_decision/core.py`:
- Around line 66-71: Update NotificationPolicy.__post_init__ to validate that
system_prompt is a string before calling strip(), raising the same intended
ValueError for non-string or empty prompts. Preserve the existing non-empty
prompt validation for valid string values.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: bef2c793-8ba7-4910-aacb-29947815fed7
📒 Files selected for processing (59)
docs/design.mdtests/test_any_llm_provider.pytests/test_cli.pytests/test_llm.pytests/test_llm_fallback.pytests/test_notification_decision.pytests/test_prompts.pytests/test_service.pytests/test_service_status.pytests/test_summarization.pytests/test_weather_context.pyweather_briefing/application/briefing_settings.pyweather_briefing/application/briefing_validation.pyweather_briefing/application/notification.pyweather_briefing/application/summarization.pyweather_briefing/cli.pyweather_briefing/command_parser.pyweather_briefing/composition/delivery.pyweather_briefing/composition/llm.pyweather_briefing/composition/notifications.pyweather_briefing/composition/weather.pyweather_briefing/data/notification_policy.txtweather_briefing/data/prompts.pyweather_briefing/data/system_prompt.txtweather_briefing/delivery/__init__.pyweather_briefing/delivery/bark_renderer.pyweather_briefing/delivery/base.pyweather_briefing/delivery/plain_renderer.pyweather_briefing/delivery/renderers.pyweather_briefing/delivery/rendering.pyweather_briefing/delivery/telegram_renderer.pyweather_briefing/llm/any_llm.pyweather_briefing/llm/any_llm_transport.pyweather_briefing/llm/fallback.pyweather_briefing/llm/lazy.pyweather_briefing/llm/result.pyweather_briefing/llm/schema.pyweather_briefing/notification_decision/__init__.pyweather_briefing/notification_decision/core.pyweather_briefing/notification_decision/policies.pyweather_briefing/notification_decision/service_status.txtweather_briefing/notification_decision/weather.txtweather_briefing/notifications.pyweather_briefing/persistence/__init__.pyweather_briefing/persistence/content.pyweather_briefing/persistence/context.pyweather_briefing/persistence/service_status.pyweather_briefing/persistence/store.pyweather_briefing/persistence/warnings.pyweather_briefing/runtime_diagnostics.pyweather_briefing/scheduling.pyweather_briefing/service.pyweather_briefing/service_status/monitor.pyweather_briefing/service_status/notification.pyweather_briefing/state.pyweather_briefing/weather/open_meteo.pyweather_briefing/weather/open_meteo_parsing.pyweather_briefing/weather/qweather.pyweather_briefing/weather/qweather_parsing.py
💤 Files with no reviewable changes (4)
- weather_briefing/data/notification_policy.txt
- weather_briefing/notifications.py
- weather_briefing/delivery/renderers.py
- weather_briefing/llm/schema.py
9565802 to
2a68af6
Compare
|
Code review by qodo was updated up to the latest commit 2a68af6 |
2a68af6 to
4b2d299
Compare
|
Code review by qodo was updated up to the latest commit 4b2d299 |
4b2d299 to
0107886
Compare
|
Code review by qodo was updated up to the latest commit 0107886 |
|
@coderabbitai full review |
✅ Action performedFull review finished. Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 33 minutes. |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
weather_briefing/notification_decision/core.py (1)
121-124: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winValidate policy kinds at the registry boundary.
NotificationDecisionServicetrustspolicy.kind; anotherNotificationPolicyimplementation can register a non-string or unnormalized value, producing invalid configuration that later fails dispatch. Apply the same non-empty, trimmed-string validation before duplicate detection.As per coding guidelines, validate configuration at its input boundary and reject invalid types and unknown application-owned choices instead of coercing them.
Proposed fix
registered: dict[str, NotificationPolicy] = {} for policy in policies: - if policy.kind in registered: - raise ValueError(f"Duplicate notification policy: {policy.kind}") - registered[policy.kind] = policy + kind = policy.kind + if not isinstance(kind, str) or not kind or kind != kind.strip(): + raise ValueError("Notification policy kind must be a non-empty normalized string") + if kind in registered: + raise ValueError(f"Duplicate notification policy: {kind}") + registered[kind] = policy🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@weather_briefing/notification_decision/core.py` around lines 121 - 124, Update the policy registration loop in NotificationDecisionService to validate policy.kind before duplicate detection: require a non-empty string after trimming and reject unknown application-owned kinds without coercing the value. Use the normalized kind consistently for duplicate checks and registry storage, while preserving the existing duplicate-policy error behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@weather_briefing/persistence/content.py`:
- Around line 57-59: Update the shared require_aware_datetime helper to reject
ambiguous DST-overlap timestamps, including aware values whose fold/offset
choices are not uniquely resolvable, while preserving valid aware timestamps.
This validation must cover the persistence boundaries at
weather_briefing/persistence/content.py:57-59, 84-86, 116-122, 136-138, and
146-149; weather_briefing/persistence/context.py:19-21 and 49-51;
weather_briefing/persistence/health.py:93-99 and 176-182;
weather_briefing/persistence/store.py:61-66 and 85-93; and
weather_briefing/persistence/warnings.py:21-23 and 44-52, which require no
direct changes if they already call the helper. Add coverage for parser-produced
timestamps and IANA-timezone paths feeding these boundaries.
In `@weather_briefing/persistence/service_status.py`:
- Around line 29-33: Update _stored_surfaces so persisted bytes or bytearray
values are passed directly to json.loads without str coercion, preserving valid
JSON decoding; if those types are not supported, explicitly validate and reject
non-text values before parsing. Keep the existing optional None handling and
downstream surface validation unchanged.
---
Outside diff comments:
In `@weather_briefing/notification_decision/core.py`:
- Around line 121-124: Update the policy registration loop in
NotificationDecisionService to validate policy.kind before duplicate detection:
require a non-empty string after trimming and reject unknown application-owned
kinds without coercing the value. Use the normalized kind consistently for
duplicate checks and registry storage, while preserving the existing
duplicate-policy error behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9dd8f839-1c98-4b30-84f2-a5f4bcf64956
📒 Files selected for processing (28)
docs/design.mdtests/test_cli.pytests/test_notification_decision.pytests/test_prompts.pytests/test_service.pytests/test_service_status.pytests/test_state.pytests/test_weather_context.pyweather_briefing/application/notification.pyweather_briefing/data/localization.jsonweather_briefing/data/system_prompt.txtweather_briefing/localization.pyweather_briefing/models.pyweather_briefing/notification_decision/core.pyweather_briefing/persistence/content.pyweather_briefing/persistence/context.pyweather_briefing/persistence/health.pyweather_briefing/persistence/schema.pyweather_briefing/persistence/service_status.pyweather_briefing/persistence/store.pyweather_briefing/persistence/warnings.pyweather_briefing/runtime_diagnostics.pyweather_briefing/service.pyweather_briefing/service_status/models.pyweather_briefing/service_status/monitor.pyweather_briefing/service_status/notification.pyweather_briefing/weather/open_meteo_parsing.pyweather_briefing/weather/qweather_parsing.py
🚧 Files skipped from review as they are similar to previous changes (3)
- weather_briefing/data/system_prompt.txt
- docs/design.md
- tests/test_prompts.py
|
Addressed the outside-diff CodeRabbit body finding in 9a3042f: policy registry construction now validates every declared kind before duplicate detection and storage. The analogous persistence audit continued in b7d1cdf, 5d30583, and e194dd3, which enforce JSON-text boundaries, warning field shape, and row/payload identity consistency. The two inline threads have been replied to with either the fixing commits or the DST-fold rationale and marked resolved. |
|
The CodeRabbit CLI fallback for the rate-limited e194dd3 page review found one valid boundary issue. Fixed in afabe77: NotificationDecision now rejects non-boolean should_notify values, and NotificationDecisionService rejects invalid custom-policy result objects. Focused and full verification passed, followed by clean exact-SHA GPT-5.6 sol max and Zhipu GLM-5.2 max local reviews before this push. |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
weather_briefing/persistence/service_status.py (1)
158-185: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winValidate
handled_atbefore persisting it.
handled_atreachesstorage_time()on Line 185 withoutrequire_aware_datetime, so a naive timestamp can enter service-status state. Validate it at this boundary, consistent with the other persistence operations.Proposed fix
) -> None: """Mark one observed message as delivered or intentionally skipped.""" + handled_at = require_aware_datetime( + handled_at, + context="Service-status handling time", + ) cursor = self._connection.execute(Based on learnings, persistence must reject naive timestamps while retaining explicitly resolved DST-fold instants.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@weather_briefing/persistence/service_status.py` around lines 158 - 185, Update mark_service_status_message_handled to validate handled_at with require_aware_datetime before passing it to storage_time. Preserve explicitly resolved DST-fold instants while rejecting naive timestamps, consistent with the other persistence operations.Sources: Coding guidelines, Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@weather_briefing/persistence/service_status.py`:
- Around line 158-185: Update mark_service_status_message_handled to validate
handled_at with require_aware_datetime before passing it to storage_time.
Preserve explicitly resolved DST-fold instants while rejecting naive timestamps,
consistent with the other persistence operations.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4364164c-2b3a-4f75-a3f3-9ed97b8f26f8
📒 Files selected for processing (7)
tests/test_notification_decision.pytests/test_service_status.pytests/test_state.pyweather_briefing/notification_decision/core.pyweather_briefing/persistence/content.pyweather_briefing/persistence/service_status.pyweather_briefing/persistence/warnings.py
|
Not changing the latest outside-diff CodeRabbit suggestion about handled_at. mark_service_status_message_handled already passes handled_at to the shared persistence serialization.storage_time helper, and that helper calls require_aware_datetime before converting to UTC. A naive value therefore raises ValueError before the UPDATE executes; adding the same validation immediately before storage_time would duplicate the existing boundary without changing behavior. Explicitly resolved DST-fold instants remain valid, as intended. |
Summary
notification_decisionpackage with explicit message kinds, policy registration, message-specific assessment builders, and separate promptsReview guide
The first 9 dependency-ordered commits build the refactor from parser and renderer splits through the reusable decision core, message-specific adapters, persistence, composition, and design contract. Twenty-one focused follow-up commits address independently validated review findings without rewriting reviewed history, including optional weather enrichment, localization, assessment immutability, skipped rendering, prompt and transaction ownership, timestamp validation, diagnostics boundaries, forced delivery, durable notification comparison state, service-status surfaces, policy registration, strict stored-state validation, and policy result validation.
Validation
prek run --all-filesuv run --with pytest --with pytest-cov -- pytest --cov --cov-branch --cov-report=xmlAny,cast(), or type-checker suppressionSummary by CodeRabbit
New Features
Bug Fixes
Documentation