100 lines
4.7 KiB
Bash
Executable File
100 lines
4.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Give the personalization surface something to work on, offline.
|
|
#
|
|
# important_date_recommendation rows are what the member and MSS home pages count and what
|
|
# /member/important-date-recommendations lists. In production the Phoenix NewAccountWorker
|
|
# produces them from a member's Cronofy calendar plus an LLM call — neither reachable offline,
|
|
# so the table stays empty however long the app runs and the feature looks broken when it is
|
|
# only unfed. This synthesizes the same rows from the seed's own contacts and their birthdays.
|
|
# cronofy_event_id is left null: the column is nullable with no foreign key, only the Ecto
|
|
# changeset requires it, and the read path preloads it to nil.
|
|
#
|
|
# Runs inside the Explore container, from run-app's strongsuit-app companion block:
|
|
#
|
|
# bash /workspace/scripts/seed/seed-important-date-recommendations.sh [database]
|
|
set -euo pipefail
|
|
DB="${1:-${PGDATABASE:-strongsuit}}"
|
|
|
|
# The app's own setup creates and seeds this database; without it there is nothing to read.
|
|
if ! PGPASSWORD="${PGPASSWORD:-postgres}" psql -h "${PGHOST:-localhost}" -U "${PGUSER:-postgres}" \
|
|
-d "$DB" -tAc "select 1 from important_date_recommendation limit 1" >/dev/null 2>&1; then
|
|
echo " database \"$DB\" has no app schema yet — nothing to seed."
|
|
exit 0
|
|
fi
|
|
|
|
PGPASSWORD="${PGPASSWORD:-postgres}" psql -h "${PGHOST:-localhost}" -U "${PGUSER:-postgres}" \
|
|
-d "$DB" -v ON_ERROR_STOP=1 <<'SQL'
|
|
with member_family as (
|
|
select u.id as user_id, u.person_id, fu.family_id
|
|
from "user" u
|
|
join family_user fu on fu.user_id = u.id
|
|
where u.role = 'MEMBER' and u.deleted_at is null
|
|
),
|
|
-- A family's contacts hang off its principal person, who is often NOT a user: the seeded member
|
|
-- user is a spouse, and the birthdays sit on the principal's relationships. So expand one hop
|
|
-- (either direction) from the member's own person before reading contacts off person1, which is
|
|
-- the direction app/models/person.server.ts queries.
|
|
member_people as (
|
|
select user_id, family_id, person_id from member_family
|
|
union
|
|
select mf.user_id, mf.family_id,
|
|
case when r.person1_id = mf.person_id then r.person2_id else r.person1_id end
|
|
from member_family mf
|
|
join relationship r
|
|
on (r.person1_id = mf.person_id or r.person2_id = mf.person_id)
|
|
and r.deleted_at is null
|
|
)
|
|
insert into important_date_recommendation
|
|
(id, important_date_type, date, member_user_id, cronofy_event_id,
|
|
created_at, updated_at, status, first_name, last_name, family_id)
|
|
select
|
|
-- family_id is part of the key: a member in two families gets one row per family. The guard
|
|
-- below keys on names rather than d.id, so it is the coarser of the two.
|
|
'seed_idr_' || substr(md5(mf.user_id || p2.id || d.id || coalesce(mf.family_id, '')), 1, 20),
|
|
lower(d.type::text),
|
|
occ.occurs_on + time '09:00',
|
|
mf.user_id,
|
|
null,
|
|
now(), now(), 'pending',
|
|
p2.first_name, p2.last_name,
|
|
mf.family_id
|
|
from member_family mf
|
|
join member_people mp on mp.user_id = mf.user_id and mp.family_id = mf.family_id
|
|
join relationship r on r.person1_id = mp.person_id and r.deleted_at is null
|
|
join person p2 on p2.id = r.person2_id
|
|
join important_date d on d.person_id = p2.id and d.type in ('BIRTHDAY', 'ANNIVERSARY')
|
|
and d.deleted_at is null
|
|
-- The next occurrence, so the list reads as something upcoming rather than a set of
|
|
-- anniversaries that all fell in 1970. The day is clamped to the last of its month because a
|
|
-- Feb-29 date has no counterpart in a common year and make_date() raises rather than rounding,
|
|
-- which under ON_ERROR_STOP would abort the whole seed rather than skip one row.
|
|
cross join lateral (
|
|
select occurs_on from (
|
|
select make_date(gs.yr, d.month,
|
|
least(d.day,
|
|
extract(day from (make_date(gs.yr, d.month, 1)
|
|
+ interval '1 month' - interval '1 day'))::int)) as occurs_on
|
|
from generate_series(extract(year from current_date)::int,
|
|
extract(year from current_date)::int + 1) as gs(yr)
|
|
) c
|
|
where c.occurs_on >= current_date
|
|
order by c.occurs_on
|
|
limit 1
|
|
) occ
|
|
where p2.id <> mf.person_id
|
|
-- Skips any member/family/person/date-type that already has a recommendation, including ones
|
|
-- the member has since accepted or declined; `on conflict` covers rows an earlier run wrote.
|
|
and not exists (
|
|
select 1 from important_date_recommendation x
|
|
where x.member_user_id = mf.user_id
|
|
and x.family_id is not distinct from mf.family_id
|
|
and x.important_date_type = lower(d.type::text)
|
|
and x.first_name is not distinct from p2.first_name
|
|
and x.last_name is not distinct from p2.last_name
|
|
)
|
|
on conflict (id) do nothing;
|
|
|
|
select count(*) as pending_recommendations
|
|
from important_date_recommendation where status = 'pending';
|
|
SQL
|