[EPAC-917]: Daily Parliament digest notification (iOS)#805
Merged
riddim-developer-bot[bot] merged 1 commit intoJun 14, 2026
Conversation
Adds an opt-in once-per-sitting-day local notification that summarizes
the day's parliamentary activity for users who want a general "what
happened today" feed instead of topic-specific alerts. Content is
composed from authoritative Hansard subjects and recorded-division
results only — no generated or paraphrased text.
Behavior
--------
- Sends within iOS background refresh after today's Hansard ingests, and
opportunistically on app foreground.
- Fires only on confirmed sitting days (Hansard published, not just
scheduled), between 17:00 and 23:00 local, once per calendar day.
- Tapping opens the cabinetdoor://sitting/<date> deep link, routing the
user to today's sitting in the Debates calendar.
- Default OFF. Opt in from onboarding screen 4 ("Daily Parliament
summary") or from Settings → Notifications → Daily Parliament summary.
Implementation
--------------
Use case: `SendDailyParliamentDigest` composes a `DailyDigest` value
object from four read ports and hands it to `DigestNotificationPort`.
The use case owns the timing policy (window + sitting-day + dedupe);
adapters are pure plumbing.
Ports (all `outbound`, all in `ios/epac/Domain/Ports/`):
- `HansardReadPort` — sitting-day, subject count, top subjects
- `VoteReadPort` — attendance estimate, daily vote summary
- `DigestNotificationPort` — UNUserNotificationCenter delivery
- `UserPreferenceReadPort` — daily-digest opt-in flag
- `DigestDeliveryRecordPort` — last delivered day, for dedupe
Adapters (`ios/epac/Data/Adapters/`):
- `SwiftDataHansardReadAdapter` — reads Hansard + OrderOfBusiness +
SubjectOfBusiness from the local SwiftData store
- `SwiftDataVoteReadAdapter` — averages (yea+nay+paired)/338 across
the day's RecordedVote rows for attendance; first vote of the day
for the summary
- `LiveDigestNotificationAdapter` — schedules a local notification via
UNUserNotificationCenter with `userInfo` containing the date string
for the tap handler
- `UserDefaultsDigestDeliveryRecordAdapter` — once-per-day dedupe key
- `UserPreferenceAdapter` — opt-in flag in UserDefaults
Composition: `BackgroundRefreshManager.sendDailyDigestIfEligible(container:)`
wires the use case after `Fetch.backgroundRefresh()` (which now downloads
today's Hansard) and is also invoked on scenePhase .active.
Tap deep link: `AppDelegate` becomes the
`UNUserNotificationCenterDelegate`; `didReceive` parses
`userInfo[date]` and calls `UIApplication.shared.open` on
`cabinetdoor://sitting/<yyyy-MM-dd>`, which the existing `ContentView`
deep-link router routes to the Parliament tab.
Entities (`ios/epac/Domain/Entities/`):
- `DailyDigest` — date, subjectCount, attendanceEstimate?, topSubjects,
vote? (VoteSummary: billName, passed, yeas, nays)
- `NotificationPreferences` — local opt-in flags (renamed from `User`
to avoid a collision with `ExyteChat.User` in the same module)
Presentation:
- `OnboardingView` gains a `dailyDigestOptInRow` toggle on the Topics
screen; opt-in persists to UserDefaults on completion.
- New `NotificationsSettingsView` exposes the same toggle and is
reachable from Settings → Notifications.
Tests:
- `SendDailyParliamentDigestTests` — 9 cases covering opt-in,
start/end-hour boundaries (minute precision), non-sitting day,
dedupe, top-subjects limit propagation.
- `DailyDigestFormatterTests` — 6 cases for the title/body composition.
- `SnapshotTests.testNotificationsSettings_dailyDigest` — light, dark,
a11y for the new settings screen.
- `Settings_root` snapshots regenerated (new Notifications row).
Catalog: `SendDailyParliamentDigest`, the five ports, and the
`DailyDigest`/`NotificationPreferences` entities added to
`docs/architecture/use-case-catalog.md`.
Verification
------------
- xcodebuild build: BUILD SUCCEEDED (Xcode 26.5 / iPhone 17 Pro sim)
- xcodebuild test (use case + formatter): 15 tests, all passed
- Settings snapshot tests: passed after baseline regen
- swiftlint --strict: zero new violations in changed files
Release-Note: Daily Parliament summary notification (opt-in) on sitting days
riddim-developer-bot
Bot
deleted the
symphony/epac-917-daily-parliament-digest-notification-what-happen
branch
June 14, 2026 09:32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an opt-in once-per-sitting-day local notification that summarizes the day's parliamentary activity (subject count, top three subjects, attendance estimate, and one optional vote summary). Content is composed from authoritative Hansard subjects and recorded-division results only — no generated or paraphrased text. Linear: EPAC-917.
Behavior
.active).cabinetdoor://sitting/<yyyy-MM-dd>, routing the user to today's sitting in the Debates calendar via the existing deep-link router.Architecture
Clean Architecture shape exactly as named in the issue.
SendDailyParliamentDigestcomposes aDailyDigestvalue object from four read ports and hands it toDigestNotificationPort. The use case owns timing policy (window + sitting-day + dedupe); adapters are pure plumbing.ios/epac/Domain/Entities/):DailyDigest;NotificationPreferences(renamed fromUserto avoid a collision withExyteChat.Userin the same module).ios/epac/Domain/Ports/):HansardReadPort— sitting-day, subject count, top subjectsVoteReadPort— attendance estimate, daily vote summaryDigestNotificationPort— UNUserNotificationCenter deliveryUserPreferenceReadPort— daily-digest opt-in flagDigestDeliveryRecordPort— last-delivered day (dedupe)ios/epac/Data/Adapters/):SwiftDataHansardReadAdapter— readsHansard/OrderOfBusiness/SubjectOfBusinessSwiftDataVoteReadAdapter— averages(yea+nay+paired)/338across the day'sRecordedVoterows for attendance; first vote of the day for the summaryLiveDigestNotificationAdapter— schedules a local notification withuserInfo[date]for the tap handlerUserDefaultsDigestDeliveryRecordAdapter— once-per-day dedupe keyUserPreferenceAdapter— opt-in flag (UserDefaults)BackgroundRefreshManager.sendDailyDigestIfEligible(container:)wires the use case afterFetch.backgroundRefresh()(which now also downloads today's Hansard) and is invoked onscenePhase .active.AppDelegateadoptsUNUserNotificationCenterDelegate;didReceiveparsesuserInfo[date]and callsUIApplication.shared.openoncabinetdoor://sitting/<yyyy-MM-dd>, which the existingContentViewdeep-link router routes to the Parliament tab.docs/architecture/use-case-catalog.mdis updated with the use case, entities, and ports.Acceptance criteria
Fetch.downloadHansard(Date())succeeds, with the use case re-checking eligibility.HansardReadPort.isSittingDayreturns true only when Hansard exists for that date.cabinetdoor://sitting/<yyyy-MM-dd>.Test plan
xcodebuild buildon iPhone 17 Pro (iOS 26.5) simulator — BUILD SUCCEEDEDSendDailyParliamentDigestTests— 9 cases: opt-in, start/end-hour boundaries (minute precision), non-sitting day, dedupe, top-subjects limit propagation. All pass.DailyDigestFormatterTests— 6 cases for title/body composition. All pass.SnapshotTests.testNotificationsSettings_dailyDigest(light/dark/a11y) — passes.SnapshotTests.testSettings_rootsnapshots regenerated for the new Notifications row — passes.swiftlint --stricton changed files — zero violations.none).Boundary notes
UserNotifications,SwiftData, or any UI framework.DailyDigestFormatter(framework-free, verbatim authoritative content).Localizable.strings.🤖 Generated with Claude Code