mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
A snooze that had already run out before the send is history, not a choice about the reminded slot, so it no longer keeps a row from being repaired. The "no due date" arm matched nothing, because a reminder cannot be created without a cadence; it now covers a rule whose last occurrence was the one sent, which is open unless it was done or skipped since, while a course whose window closed before the send stays closed through the end-date guard. The start-page step reads a stored item list only when it is a list, so a malformed layout can no longer fail the migration.
119 lines
6.0 KiB
SQL
119 lines
6.0 KiB
SQL
-- An open check-up stays due after its reminder.
|
|
--
|
|
-- Until now the reminder tick rolled every delivered reminder on to its next
|
|
-- slot. For a reminder on a cycle longer than a week (a fortnightly
|
|
-- questionnaire, a yearly check-up) that filed an open task under a slot
|
|
-- weeks or a year away the moment it was reminded, without it having been
|
|
-- done: the start page and the check-up card then showed it as scheduled far
|
|
-- ahead, and a one-shot check-up disappeared altogether.
|
|
--
|
|
-- From this release such a reminder keeps its due date until it is satisfied
|
|
-- (a matching reading or lab result, or marked done), skipped or snoozed, and
|
|
-- is reminded again at most once a week while it stays open. A reminder on a
|
|
-- weekly or shorter cycle still rolls on. Three steps.
|
|
|
|
-- 1. The cursor that keeps the repeat to once a week: when a reminder for
|
|
-- this row last reached a channel. Nullable, no default; a row that was
|
|
-- never reminded simply has none.
|
|
ALTER TABLE "measurement_reminders" ADD COLUMN "last_notified_at" TIMESTAMP(3);
|
|
|
|
-- 2. Put back the due date of reminders the old behaviour rolled on.
|
|
--
|
|
-- The evidence is the reminder claim ledger: every reminder send first
|
|
-- claims `measurement:<reminder id>:<local date>` in `notification_events`
|
|
-- (event type MEASUREMENT_REMINDER). The claim is written whether or not a
|
|
-- channel delivered, but the roll-on only happened after a delivery, so a
|
|
-- row counts as rolled on by a send only when ALL of these hold:
|
|
--
|
|
-- - it is live and enabled, origin VORSORGE or COACH (never an
|
|
-- appointment), and on a cycle the new behaviour holds: an interval
|
|
-- longer than seven days, a recurrence rule, or no cadence at all.
|
|
-- Measurement reminders are included; whether it names a measurement
|
|
-- type does not matter;
|
|
-- - it was not done or skipped after that send, and no snooze reaches
|
|
-- past it (a snooze that had already run out before the send is
|
|
-- history, not a choice about this slot);
|
|
-- - the send was its most recent claim, and the row's last write
|
|
-- (`updated_at`) lands within a minute before to ten minutes after
|
|
-- that claim. The roll-on is the write that follows a delivery by
|
|
-- seconds; any later write (an edit, a snooze, a skip, a Telegram
|
|
-- "later") moves `updated_at` past the window and the row is left
|
|
-- alone;
|
|
-- - the due date it carries now is what the roll-on would have written
|
|
-- and what the new behaviour no longer writes: more than a week past
|
|
-- the send (a weekly or shorter cycle, including a rule that recurs
|
|
-- within the week, still rolls on, so it is not repaired), or no due
|
|
-- date at all: a rule whose last occurrence was the one sent, which
|
|
-- is still open unless it was done or skipped since. A course whose
|
|
-- window ended before the send is excluded by the end-date guard.
|
|
--
|
|
-- The due date goes back to the instant of that send, which is the day
|
|
-- the reminder went out and at its notify hour. `last_notified_at`
|
|
-- takes the same instant, so the repeat waits a week from that send
|
|
-- instead of firing again at the next notify hour. A claim older than the
|
|
-- ledger's 90-day retention is gone, and a row rolled on by such a send
|
|
-- stays as it is: without the claim there is no evidence of which slot
|
|
-- was reminded. A reading or lab result that satisfied the reminder
|
|
-- after the send moved `last_satisfied_at` past it, so such a row is
|
|
-- excluded above. Nothing is deleted.
|
|
WITH latest_claim AS (
|
|
SELECT DISTINCT ON (r."id")
|
|
r."id" AS "reminder_id",
|
|
ne."created_at" AS "sent_at"
|
|
FROM "measurement_reminders" r
|
|
JOIN "notification_events" ne
|
|
ON ne."record_user_id" = r."user_id"
|
|
AND ne."event_type" = 'MEASUREMENT_REMINDER'
|
|
AND ne."dedup_key" LIKE 'measurement:' || r."id" || ':%'
|
|
ORDER BY r."id", ne."created_at" DESC
|
|
)
|
|
UPDATE "measurement_reminders" r
|
|
SET "next_due_at" = lc."sent_at",
|
|
"last_notified_at" = lc."sent_at"
|
|
FROM latest_claim lc
|
|
WHERE r."id" = lc."reminder_id"
|
|
AND r."deleted_at" IS NULL
|
|
AND r."enabled" = TRUE
|
|
AND r."origin" IN ('VORSORGE', 'COACH')
|
|
AND (
|
|
r."interval_days" > 7
|
|
OR r."rrule" IS NOT NULL
|
|
OR (r."interval_days" IS NULL AND r."rrule" IS NULL)
|
|
)
|
|
AND (r."snoozed_until" IS NULL OR r."snoozed_until" <= lc."sent_at")
|
|
AND r."last_notified_at" IS NULL
|
|
AND (r."last_satisfied_at" IS NULL OR r."last_satisfied_at" < lc."sent_at")
|
|
AND (r."last_skipped_at" IS NULL OR r."last_skipped_at" < lc."sent_at")
|
|
AND (r."ends_on" IS NULL OR r."ends_on" > lc."sent_at")
|
|
AND r."updated_at" >= lc."sent_at" - INTERVAL '1 minute'
|
|
AND r."updated_at" <= lc."sent_at" + INTERVAL '10 minutes'
|
|
AND (
|
|
r."next_due_at" > lc."sent_at" + INTERVAL '7 days 12 hours'
|
|
OR r."next_due_at" IS NULL
|
|
);
|
|
|
|
-- 3. Show booked visits on the start page for people who chose its items
|
|
-- before visits were one of them.
|
|
--
|
|
-- The start page's item choice is stored as the list of ENABLED kinds, and
|
|
-- only when it is not "all of them". A list saved before `upcoming_visit`
|
|
-- existed (v1.34.0 to v1.37.1) therefore reads as "visits switched off",
|
|
-- although nobody switched them off. Append the kind to every stored,
|
|
-- non-empty list that lacks it. An empty list is the whole rail turned off
|
|
-- and stays empty. A person who has deliberately switched visits off since
|
|
-- v1.37.2 cannot be told apart from one who never had the choice; they see
|
|
-- visits again and can switch them off once more, which now sticks. A
|
|
-- stored value that is not a list is left alone rather than read.
|
|
UPDATE "users"
|
|
SET "dashboard_widgets_json" = jsonb_set(
|
|
"dashboard_widgets_json",
|
|
'{enabledHeroItemKinds}',
|
|
("dashboard_widgets_json" -> 'enabledHeroItemKinds') || '["upcoming_visit"]'::jsonb
|
|
)
|
|
WHERE CASE
|
|
WHEN jsonb_typeof("dashboard_widgets_json" -> 'enabledHeroItemKinds') = 'array'
|
|
THEN jsonb_array_length("dashboard_widgets_json" -> 'enabledHeroItemKinds') > 0
|
|
AND NOT ("dashboard_widgets_json" -> 'enabledHeroItemKinds') ? 'upcoming_visit'
|
|
ELSE FALSE
|
|
END;
|