From 1632433b6af573dc0f72eb26d3e3e6b32431281f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Feb 2026 21:05:32 +0000 Subject: [PATCH] add specs and tasks for registration-confirmation-email specs/registration-notifications/spec.md: - MODIFIED: parent confirmation email sent on completion (alongside admin) - Bilingual body (de/en), QR-bill embedded inline, plain-text fallback - Language persisted as metadata.language in registration record tasks.md: - 6 sections: qrbill dep, language persistence, notify_parent() impl, wire into both completion sites (email agent + chat), tests, smoke test https://claude.ai/code/session_01LjjK7RjKVnC8bETtccfgna --- .../specs/registration-notifications/spec.md | 81 +++++++++++++++++++ .../registration-confirmation-email/tasks.md | 52 ++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 openspec/changes/registration-confirmation-email/specs/registration-notifications/spec.md create mode 100644 openspec/changes/registration-confirmation-email/tasks.md diff --git a/openspec/changes/registration-confirmation-email/specs/registration-notifications/spec.md b/openspec/changes/registration-confirmation-email/specs/registration-notifications/spec.md new file mode 100644 index 0000000..ff9e3b9 --- /dev/null +++ b/openspec/changes/registration-confirmation-email/specs/registration-notifications/spec.md @@ -0,0 +1,81 @@ +## MODIFIED Requirements + +### Requirement: Notify parent on completed registration +The system SHALL send an HTML confirmation email to the parent immediately after a registration is completed and stored. + +#### Scenario: Confirmation sent to parent email +- **WHEN** a registration is completed +- **THEN** the system SHALL send a confirmation email to the address in `parentGuardian.email` + +#### Scenario: Confirmation sent before or alongside admin notification +- **WHEN** a registration is completed +- **THEN** both the admin notification and the parent confirmation SHALL be dispatched in the same completion event; failure of either SHALL be logged but SHALL NOT block the other or fail the registration + +#### Scenario: No confirmation for incomplete registration +- **WHEN** a registration is not yet complete (any required field missing) +- **THEN** no confirmation email SHALL be sent to the parent + +--- + +### Requirement: Confirmation email contains full registration summary +The confirmation email SHALL include a summary of all registration data the parent submitted. + +#### Scenario: All required fields present in confirmation +- **WHEN** the confirmation email is sent +- **THEN** it SHALL include child name, date of birth, special needs, selected playgroup type(s), selected days, parent/guardian contact details, and emergency contact + +#### Scenario: Monthly fee shown as informational text +- **WHEN** the confirmation email is sent +- **THEN** it SHALL display the calculated monthly subscription fee as informational text (not a payment request) + +--- + +### Requirement: Confirmation email contains payment instructions for registration fee +The confirmation email SHALL include instructions for paying the one-time CHF 80 registration fee. + +#### Scenario: IBAN and payee shown as text +- **WHEN** the confirmation email is sent +- **THEN** it SHALL display the payee name, IBAN, and amount in plain text so the parent can pay manually if the QR code is not rendered + +#### Scenario: Swiss QR-bill embedded inline +- **WHEN** the confirmation email is sent +- **THEN** it SHALL include a Swiss QR-bill image (per SIX Group standard) embedded inline as a `Content-ID` referenced image within the HTML part +- **AND** the QR-bill SHALL encode: IBAN `CH14 0900 0000 4930 8018 8`, payee Familienverein Fällanden Spielgruppen (Huebwisstrase 5, 8117 Fällanden), amount CHF 80.00, currency CHF, reference type NON + +#### Scenario: QR-bill fallback for non-HTML clients +- **WHEN** a parent's email client does not render HTML +- **THEN** the plain-text part SHALL include the IBAN and payee details in full so payment is still possible without the QR code + +--- + +### Requirement: Confirmation email language matches parent's detected language +The confirmation email body SHALL be rendered in the language detected during the conversation. + +#### Scenario: German parent receives German confirmation +- **WHEN** the conversation language is `"de"` +- **THEN** the confirmation email body SHALL be in German + +#### Scenario: English-speaking parent receives English confirmation +- **WHEN** the conversation language is `"en"` +- **THEN** the confirmation email body SHALL be in English + +#### Scenario: Unknown language falls back to German +- **WHEN** the stored language value is not `"de"` or `"en"` +- **THEN** the confirmation email SHALL be sent in German + +#### Scenario: QR-bill slip labels are not translated +- **WHEN** the confirmation email is rendered in any language +- **THEN** the Swiss QR-bill payment slip labels SHALL remain in German (per SIX Group standard; the slip is internationally recognisable without translation) + +--- + +### Requirement: Parent's conversation language is persisted in the registration record +The language detected during the parent's conversation SHALL be stored in the completed registration record. + +#### Scenario: Language written to registration record +- **WHEN** a registration is stored +- **THEN** the JSON record SHALL include a `metadata.language` field containing the detected language code (`"de"` or `"en"`) + +#### Scenario: Language defaults to German when not detected +- **WHEN** no language was explicitly detected during the conversation +- **THEN** `metadata.language` SHALL be `"de"` diff --git a/openspec/changes/registration-confirmation-email/tasks.md b/openspec/changes/registration-confirmation-email/tasks.md new file mode 100644 index 0000000..73e6b86 --- /dev/null +++ b/openspec/changes/registration-confirmation-email/tasks.md @@ -0,0 +1,52 @@ +## 1. Add `qrbill` Dependency + +- [ ] 1.1 Add `qrbill` to `[project.dependencies]` in `pyproject.toml` +- [ ] 1.2 Run `uv lock` to update the lockfile +- [ ] 1.3 Verify `qrbill` imports successfully in a smoke test or REPL + +## 2. Persist Language in Registration Record + +- [ ] 2.1 Update `ConversationStore._build_record()` in `src/storage/json_store.py` to include `language` from `state.language` in the `metadata` dict +- [ ] 2.2 Update `ConversationStore.save_registration()` and `save_registration_version()` signatures to accept/forward `state` (already does — confirm `_build_record` receives the full state) +- [ ] 2.3 Add a test in `tests/test_storage.py` asserting that the saved record's `metadata.language` matches `state.language` + +## 3. Add `notify_parent()` to `AdminNotifier` + +- [ ] 3.1 Add a `_generate_qr_bill_png()` static/class method to `AdminNotifier` using `qrbill` with fixed payment data: + - IBAN: `CH14 0900 0000 4930 8018 8` + - Payee: Familienverein Fällanden Spielgruppen, Huebwisstrase 5, 8117 Fällanden + - Amount: `80.00`, Currency: `CHF`, Reference type: NON + - Returns raw PNG `bytes` +- [ ] 3.2 Add bilingual string template dicts `_STRINGS_DE` and `_STRINGS_EN` (module-level constants) covering all user-visible strings in the confirmation email (subject, section headers, fee labels, payment instructions text, closing) +- [ ] 3.3 Add `_build_parent_html()` method: renders full HTML confirmation email body using the appropriate string dict, embedding the QR image via `cid:qrbill`; includes registration summary and both monthly fee (informational) and CHF 80 registration fee (with IBAN text + QR reference) +- [ ] 3.4 Add `_build_parent_text()` method: renders the plain-text fallback, including all summary fields and IBAN/payee details in plain text (no image) +- [ ] 3.5 Add `notify_parent()` public method: + - Parameters: `registration: RegistrationData`, `language: str = "de"` + - Select string dict based on `language`; fall back to `"de"` for unknown values + - Call `_generate_qr_bill_png()` to get PNG bytes + - Build MIME structure: `multipart/mixed` > `multipart/alternative` > plain text part + `multipart/related` > HTML part + inline PNG (`Content-Disposition: inline`, `Content-ID: `) + - Call `_send()` with `to=[registration.parent_guardian.email]`, empty `cc`, localised subject, the assembled MIME message + - If `_smtp_host` is empty (dev mode), log and skip as with `notify_admin` + +## 4. Wire `notify_parent()` into Completion Events + +- [ ] 4.1 In `src/agent/core.py` `_handle_registration()`: after the existing `notify_admin()` try/except block, add a parallel try/except block calling `self._notifier.notify_parent(registration=state.registration, language=state.language)` +- [ ] 4.2 In `chat_app.py` `on_message()`: after the existing `notify_admin()` call inside the completion block, add a parallel try/except block calling `_notifier.notify_parent(registration=state.registration, language=state.language)` +- [ ] 4.3 Verify both call sites log a warning (not an exception) on failure, and the registration completion path continues normally + +## 5. Tests + +- [ ] 5.1 Add `tests/test_notifier.py` tests for `notify_parent()`: + - `test_notify_parent_calls_send`: mock `_send` and assert it is called with `to=[parent_email]` + - `test_notify_parent_german_subject`: assert subject contains German text when `language="de"` + - `test_notify_parent_english_subject`: assert subject contains English text when `language="en"` + - `test_notify_parent_unknown_language_falls_back_to_de`: assert `language="fr"` produces German subject + - `test_notify_parent_no_smtp_skips_send`: when `smtp_host=""`, `_send` is NOT called +- [ ] 5.2 Add a test asserting that the plain-text body contains the IBAN string `CH14` when `smtp_host` is empty (inspecting log or body build directly) +- [ ] 5.3 Add a test for `_generate_qr_bill_png()` asserting it returns `bytes` with non-zero length (requires `qrbill` installed) + +## 6. Manual Smoke Test + +- [ ] 6.1 Run `chainlit run chat_app.py` locally (or the email poller), complete a registration end-to-end, and verify the parent confirmation email arrives with the inline QR image rendered correctly +- [ ] 6.2 Verify the admin notification still arrives unchanged alongside the parent confirmation +- [ ] 6.3 Verify the saved `current.json` for the registration includes `metadata.language`