feat: rebuild birth time rectification workflow
This commit is contained in:
@@ -0,0 +1,987 @@
|
||||
begin;
|
||||
|
||||
create table public.birth_time_rectification_v4_cases (
|
||||
id uuid primary key,
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
protocol text not null default 'rectification-evidence-v4' check (protocol = 'rectification-evidence-v4'),
|
||||
version bigint not null default 0 check (version >= 0),
|
||||
status text not null check (status in ('awaiting_answer', 'processing', 'range_ready', 'paused', 'abandoned')),
|
||||
phase text not null check (phase in ('collecting_evidence', 'extracting_evidence', 'scoring_candidates', 'checking_robustness', 'planning_question', 'complete')),
|
||||
calculation_spec jsonb not null check (jsonb_typeof(calculation_spec) = 'object' and octet_length(calculation_spec::text) <= 16384),
|
||||
calculation_spec_hash text not null check (calculation_spec_hash ~ '^[a-f0-9]{64}$'),
|
||||
evidence_set_hash text not null check (evidence_set_hash ~ '^[a-f0-9]{64}$'),
|
||||
current_question jsonb check (current_question is null or (jsonb_typeof(current_question) = 'object' and octet_length(current_question::text) <= 4096)),
|
||||
latest_snapshot_id uuid,
|
||||
accepted_range_start text check (accepted_range_start is null or accepted_range_start ~ '^([01][0-9]|2[0-3]):[0-5][0-9]$'),
|
||||
accepted_range_end text check (accepted_range_end is null or accepted_range_end ~ '^([01][0-9]|2[0-3]):[0-5][0-9]$'),
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
check ((accepted_range_start is null) = (accepted_range_end is null)),
|
||||
check (accepted_range_start is null or accepted_range_start <> accepted_range_end)
|
||||
);
|
||||
|
||||
create unique index birth_time_rectification_v4_one_active_case
|
||||
on public.birth_time_rectification_v4_cases(user_id)
|
||||
where status <> 'abandoned' and accepted_range_start is null;
|
||||
|
||||
create table public.birth_time_rectification_v4_actions (
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
action_id uuid not null,
|
||||
case_id uuid not null references public.birth_time_rectification_v4_cases(id) on delete cascade,
|
||||
job_id uuid,
|
||||
created_at timestamptz not null default now(),
|
||||
primary key (user_id, action_id)
|
||||
);
|
||||
|
||||
create table public.birth_time_rectification_v4_turns (
|
||||
id uuid primary key,
|
||||
case_id uuid not null references public.birth_time_rectification_v4_cases(id) on delete cascade,
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
case_version bigint not null check (case_version > 0),
|
||||
question_id uuid,
|
||||
question_domain text check (question_domain is null or question_domain in ('education', 'relocation', 'relationship', 'career', 'finance', 'health_pressure', 'family', 'other')),
|
||||
question_target_event_id uuid,
|
||||
question text not null check (length(btrim(question)) between 1 and 1000),
|
||||
answer text not null check (length(answer) <= 4000),
|
||||
action_id uuid not null,
|
||||
created_at timestamptz not null default now(),
|
||||
unique (user_id, action_id)
|
||||
);
|
||||
|
||||
create table public.birth_time_rectification_v4_events (
|
||||
id uuid primary key,
|
||||
case_id uuid not null references public.birth_time_rectification_v4_cases(id) on delete cascade,
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
created_at timestamptz not null default now(),
|
||||
unique (case_id, id)
|
||||
);
|
||||
|
||||
alter table public.birth_time_rectification_v4_turns
|
||||
add constraint birth_time_rectification_v4_turn_target_event_fk
|
||||
foreign key (case_id, question_target_event_id)
|
||||
references public.birth_time_rectification_v4_events(case_id, id);
|
||||
|
||||
create table public.birth_time_rectification_v4_event_revisions (
|
||||
id uuid primary key,
|
||||
event_id uuid not null references public.birth_time_rectification_v4_events(id) on delete cascade,
|
||||
case_id uuid not null references public.birth_time_rectification_v4_cases(id) on delete cascade,
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
revision integer not null check (revision > 0),
|
||||
domain text not null check (domain in ('education', 'relocation', 'relationship', 'career', 'finance', 'health_pressure', 'family', 'other')),
|
||||
event_kind text not null check (event_kind in ('education_milestone', 'relocation', 'relationship_start', 'relationship_end', 'career_change', 'finance_change', 'health_event', 'family_event', 'other')),
|
||||
summary text not null check (length(btrim(summary)) between 1 and 1000),
|
||||
raw_text text not null check (length(btrim(raw_text)) between 1 and 4000),
|
||||
date_start date not null,
|
||||
date_end date not null,
|
||||
date_precision text not null check (date_precision in ('day', 'month', 'quarter', 'year', 'range')),
|
||||
date_label text not null check (length(btrim(date_label)) between 1 and 80),
|
||||
scoreability text not null check (scoreability in ('scoreable', 'context_only')),
|
||||
supersedes_revision_id uuid references public.birth_time_rectification_v4_event_revisions(id),
|
||||
created_at timestamptz not null default now(),
|
||||
unique (event_id, revision),
|
||||
check (date_start <= date_end),
|
||||
check (domain not in ('family', 'other') or scoreability = 'context_only'),
|
||||
check (domain <> 'relationship' or event_kind in ('relationship_start', 'relationship_end'))
|
||||
);
|
||||
|
||||
create table public.birth_time_rectification_v4_candidate_snapshots (
|
||||
id uuid primary key,
|
||||
case_id uuid not null references public.birth_time_rectification_v4_cases(id) on delete cascade,
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
case_version bigint not null check (case_version >= 0),
|
||||
evidence_set_hash text not null check (evidence_set_hash ~ '^[a-f0-9]{64}$'),
|
||||
calculation_spec_hash text not null check (calculation_spec_hash ~ '^[a-f0-9]{64}$'),
|
||||
algorithm_version text not null check (algorithm_version = 'rectification-v4-range-scoring-1'),
|
||||
candidates jsonb not null check (jsonb_typeof(candidates) = 'array' and jsonb_array_length(candidates) between 1 and 1440 and octet_length(candidates::text) <= 524288),
|
||||
clusters jsonb not null check (jsonb_typeof(clusters) = 'array' and jsonb_array_length(clusters) <= 20 and octet_length(clusters::text) <= 32768),
|
||||
robustness jsonb not null check (jsonb_typeof(robustness) = 'object' and octet_length(robustness::text) <= 16384),
|
||||
can_confirm_exact_minute boolean not null default false check (can_confirm_exact_minute = false),
|
||||
can_accept_range boolean not null,
|
||||
gate_reasons jsonb not null check (jsonb_typeof(gate_reasons) = 'array' and jsonb_array_length(gate_reasons) <= 20),
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
alter table public.birth_time_rectification_v4_cases
|
||||
add constraint birth_time_rectification_v4_latest_snapshot_fk
|
||||
foreign key (latest_snapshot_id) references public.birth_time_rectification_v4_candidate_snapshots(id);
|
||||
|
||||
create table public.birth_time_rectification_v4_jobs (
|
||||
id uuid primary key,
|
||||
case_id uuid not null references public.birth_time_rectification_v4_cases(id) on delete cascade,
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
turn_id uuid not null references public.birth_time_rectification_v4_turns(id) on delete cascade,
|
||||
status text not null check (status in ('pending', 'processing', 'completed', 'failed', 'stale')),
|
||||
phase text not null check (phase in ('collecting_evidence', 'extracting_evidence', 'scoring_candidates', 'checking_robustness', 'planning_question', 'complete')),
|
||||
expected_case_version bigint not null check (expected_case_version >= 0),
|
||||
evidence_set_hash text not null check (evidence_set_hash ~ '^[a-f0-9]{64}$'),
|
||||
calculation_spec_hash text not null check (calculation_spec_hash ~ '^[a-f0-9]{64}$'),
|
||||
worker_id uuid,
|
||||
lease_expires_at timestamptz,
|
||||
result_snapshot_id uuid references public.birth_time_rectification_v4_candidate_snapshots(id),
|
||||
error_code text check (error_code is null or length(error_code) between 1 and 120),
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
alter table public.birth_time_rectification_v4_actions
|
||||
add constraint birth_time_rectification_v4_actions_job_fk
|
||||
foreign key (job_id) references public.birth_time_rectification_v4_jobs(id);
|
||||
|
||||
create index birth_time_rectification_v4_jobs_claim_idx
|
||||
on public.birth_time_rectification_v4_jobs(status, created_at)
|
||||
where status in ('pending', 'processing');
|
||||
|
||||
create table public.birth_time_rectification_v4_handoffs (
|
||||
case_id uuid primary key references public.birth_time_rectification_v4_cases(id) on delete cascade,
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
question text not null check (length(btrim(question)) between 1 and 500),
|
||||
question_fingerprint text not null check (question_fingerprint ~ '^[a-f0-9]{64}$'),
|
||||
attached_case_version bigint not null check (attached_case_version >= 0),
|
||||
attach_action_id uuid not null,
|
||||
state text not null check (state in ('pending', 'claimed', 'executing', 'consumed')),
|
||||
attempt integer not null default 0 check (attempt between 0 and 1000),
|
||||
request_id uuid not null,
|
||||
claim_action_id uuid,
|
||||
lease_expires_at timestamptz,
|
||||
claimed_at timestamptz,
|
||||
consumed_at timestamptz,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (user_id, request_id),
|
||||
check ((state in ('claimed', 'executing')) = (claim_action_id is not null)),
|
||||
check ((state in ('claimed', 'executing')) = (lease_expires_at is not null)),
|
||||
check ((state = 'consumed') = (consumed_at is not null))
|
||||
);
|
||||
|
||||
create table public.birth_time_rectification_v4_handoff_attach_receipts (
|
||||
case_id uuid not null references public.birth_time_rectification_v4_cases(id) on delete cascade,
|
||||
action_id uuid not null,
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
expected_case_version bigint not null check (expected_case_version >= 0),
|
||||
question_fingerprint text not null check (question_fingerprint ~ '^[a-f0-9]{64}$'),
|
||||
response jsonb not null,
|
||||
created_at timestamptz not null default now(),
|
||||
primary key (case_id, action_id)
|
||||
);
|
||||
|
||||
create table public.birth_time_rectification_v4_handoff_settlements (
|
||||
case_id uuid not null references public.birth_time_rectification_v4_cases(id) on delete cascade,
|
||||
request_id uuid not null,
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
claim_action_id uuid not null,
|
||||
emitted boolean not null,
|
||||
response jsonb not null,
|
||||
created_at timestamptz not null default now(),
|
||||
primary key (case_id, request_id)
|
||||
);
|
||||
|
||||
create index birth_time_rectification_v4_handoff_owner_state_idx
|
||||
on public.birth_time_rectification_v4_handoffs(user_id, state, updated_at desc);
|
||||
|
||||
alter table public.birth_time_rectification_v4_cases enable row level security;
|
||||
alter table public.birth_time_rectification_v4_actions enable row level security;
|
||||
alter table public.birth_time_rectification_v4_turns enable row level security;
|
||||
alter table public.birth_time_rectification_v4_events enable row level security;
|
||||
alter table public.birth_time_rectification_v4_event_revisions enable row level security;
|
||||
alter table public.birth_time_rectification_v4_candidate_snapshots enable row level security;
|
||||
alter table public.birth_time_rectification_v4_jobs enable row level security;
|
||||
alter table public.birth_time_rectification_v4_handoffs enable row level security;
|
||||
alter table public.birth_time_rectification_v4_handoff_attach_receipts enable row level security;
|
||||
alter table public.birth_time_rectification_v4_handoff_settlements enable row level security;
|
||||
|
||||
revoke all on table public.birth_time_rectification_v4_cases, public.birth_time_rectification_v4_actions,
|
||||
public.birth_time_rectification_v4_turns, public.birth_time_rectification_v4_events,
|
||||
public.birth_time_rectification_v4_event_revisions, public.birth_time_rectification_v4_candidate_snapshots,
|
||||
public.birth_time_rectification_v4_jobs, public.birth_time_rectification_v4_handoffs,
|
||||
public.birth_time_rectification_v4_handoff_attach_receipts,
|
||||
public.birth_time_rectification_v4_handoff_settlements from public, anon, authenticated;
|
||||
grant all on table public.birth_time_rectification_v4_cases, public.birth_time_rectification_v4_actions,
|
||||
public.birth_time_rectification_v4_turns, public.birth_time_rectification_v4_events,
|
||||
public.birth_time_rectification_v4_event_revisions, public.birth_time_rectification_v4_candidate_snapshots,
|
||||
public.birth_time_rectification_v4_jobs, public.birth_time_rectification_v4_handoffs,
|
||||
public.birth_time_rectification_v4_handoff_attach_receipts,
|
||||
public.birth_time_rectification_v4_handoff_settlements to service_role;
|
||||
|
||||
create function public.create_birth_time_rectification_v4_case(
|
||||
p_user_id uuid, p_case_id uuid, p_action_id uuid, p_status text, p_phase text,
|
||||
p_calculation_spec jsonb, p_calculation_spec_hash text, p_evidence_set_hash text,
|
||||
p_current_question jsonb, p_now timestamptz
|
||||
) returns uuid
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
declare v_case public.birth_time_rectification_v4_cases%rowtype; v_case_id uuid;
|
||||
begin
|
||||
select action.case_id into v_case_id from public.birth_time_rectification_v4_actions action
|
||||
where action.user_id = p_user_id and action.action_id = p_action_id;
|
||||
if v_case_id is not null then return v_case_id; end if;
|
||||
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(p_user_id::text || ':rectification-v4-case', 0));
|
||||
select value.* into v_case from public.birth_time_rectification_v4_cases value
|
||||
where value.user_id = p_user_id and value.status <> 'abandoned'
|
||||
and value.accepted_range_start is null
|
||||
order by value.created_at desc limit 1 for update;
|
||||
if found and v_case.calculation_spec_hash = p_calculation_spec_hash then
|
||||
insert into public.birth_time_rectification_v4_actions(user_id, action_id, case_id, created_at)
|
||||
values (p_user_id, p_action_id, v_case.id, p_now);
|
||||
return v_case.id;
|
||||
end if;
|
||||
if found then
|
||||
update public.birth_time_rectification_v4_cases set
|
||||
status = 'abandoned', phase = 'complete', current_question = null, updated_at = p_now
|
||||
where id = v_case.id;
|
||||
update public.birth_time_rectification_v4_jobs set
|
||||
status = 'stale', lease_expires_at = null, updated_at = p_now
|
||||
where case_id = v_case.id and status in ('pending', 'processing');
|
||||
end if;
|
||||
insert into public.birth_time_rectification_v4_cases (
|
||||
id, user_id, status, phase, calculation_spec, calculation_spec_hash,
|
||||
evidence_set_hash, current_question, created_at, updated_at
|
||||
) values (
|
||||
p_case_id, p_user_id, p_status, p_phase, p_calculation_spec, p_calculation_spec_hash,
|
||||
p_evidence_set_hash, p_current_question, p_now, p_now
|
||||
);
|
||||
insert into public.birth_time_rectification_v4_actions(user_id, action_id, case_id, created_at)
|
||||
values (p_user_id, p_action_id, p_case_id, p_now);
|
||||
return p_case_id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create function public.submit_birth_time_rectification_v4_answer(
|
||||
p_user_id uuid, p_case_id uuid, p_action_id uuid, p_expected_version bigint,
|
||||
p_turn_id uuid, p_question_id uuid, p_question_domain text, p_question_target_event_id uuid, p_question text,
|
||||
p_answer text, p_job_id uuid, p_now timestamptz
|
||||
) returns uuid
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
declare v_case public.birth_time_rectification_v4_cases%rowtype; v_job_id uuid;
|
||||
begin
|
||||
select action.job_id into v_job_id from public.birth_time_rectification_v4_actions action
|
||||
where action.user_id = p_user_id and action.action_id = p_action_id;
|
||||
if v_job_id is not null then return v_job_id; end if;
|
||||
select value.* into v_case from public.birth_time_rectification_v4_cases value
|
||||
where value.id = p_case_id and value.user_id = p_user_id for update;
|
||||
if not found then raise exception 'rectification_v4_case_not_found'; end if;
|
||||
if v_case.version <> p_expected_version then raise exception 'stale_rectification_v4_case'; end if;
|
||||
if v_case.status not in ('awaiting_answer', 'range_ready') then raise exception 'rectification_v4_case_not_awaiting_answer'; end if;
|
||||
insert into public.birth_time_rectification_v4_turns(
|
||||
id, case_id, user_id, case_version, question_id, question_domain, question_target_event_id, question, answer, action_id, created_at
|
||||
) values (
|
||||
p_turn_id, p_case_id, p_user_id, p_expected_version + 1, p_question_id, p_question_domain, p_question_target_event_id,
|
||||
p_question, p_answer, p_action_id, p_now
|
||||
);
|
||||
update public.birth_time_rectification_v4_cases set
|
||||
version = p_expected_version + 1, status = 'processing', phase = 'extracting_evidence',
|
||||
current_question = null, updated_at = p_now
|
||||
where id = p_case_id;
|
||||
insert into public.birth_time_rectification_v4_jobs(
|
||||
id, case_id, user_id, turn_id, status, phase, expected_case_version,
|
||||
evidence_set_hash, calculation_spec_hash, created_at, updated_at
|
||||
) values (
|
||||
p_job_id, p_case_id, p_user_id, p_turn_id, 'pending', 'extracting_evidence', p_expected_version + 1,
|
||||
v_case.evidence_set_hash, v_case.calculation_spec_hash, p_now, p_now
|
||||
);
|
||||
insert into public.birth_time_rectification_v4_actions(user_id, action_id, case_id, job_id, created_at)
|
||||
values (p_user_id, p_action_id, p_case_id, p_job_id, p_now);
|
||||
return p_job_id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create function public.revise_birth_time_rectification_v4_event(
|
||||
p_user_id uuid, p_case_id uuid, p_action_id uuid, p_expected_version bigint,
|
||||
p_revision jsonb, p_output_evidence_set_hash text, p_turn_id uuid, p_job_id uuid, p_now timestamptz
|
||||
) returns uuid
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
declare v_case public.birth_time_rectification_v4_cases%rowtype; v_job_id uuid; v_event_id uuid;
|
||||
begin
|
||||
select action.job_id into v_job_id from public.birth_time_rectification_v4_actions action
|
||||
where action.user_id = p_user_id and action.action_id = p_action_id;
|
||||
if v_job_id is not null then return v_job_id; end if;
|
||||
select value.* into v_case from public.birth_time_rectification_v4_cases value
|
||||
where value.id = p_case_id and value.user_id = p_user_id for update;
|
||||
if not found then raise exception 'rectification_v4_case_not_found'; end if;
|
||||
if v_case.version <> p_expected_version then raise exception 'stale_rectification_v4_case'; end if;
|
||||
if v_case.status in ('processing', 'abandoned', 'paused') then raise exception 'rectification_v4_case_invalid_state'; end if;
|
||||
if jsonb_typeof(p_revision) <> 'object' then raise exception 'invalid_rectification_v4_event_revision'; end if;
|
||||
v_event_id = (p_revision->>'eventId')::uuid;
|
||||
insert into public.birth_time_rectification_v4_events(id, case_id, user_id, created_at)
|
||||
values (v_event_id, p_case_id, p_user_id, p_now) on conflict (id) do nothing;
|
||||
insert into public.birth_time_rectification_v4_event_revisions(
|
||||
id, event_id, case_id, user_id, revision, domain, event_kind, summary, raw_text,
|
||||
date_start, date_end, date_precision, date_label, scoreability, supersedes_revision_id, created_at
|
||||
) values (
|
||||
(p_revision->>'id')::uuid, v_event_id, p_case_id, p_user_id,
|
||||
(p_revision->>'revision')::integer, p_revision->>'domain', p_revision->>'eventKind',
|
||||
p_revision->>'summary', p_revision->>'rawText',
|
||||
(p_revision#>>'{dateRange,start}')::date, (p_revision#>>'{dateRange,end}')::date,
|
||||
p_revision#>>'{dateRange,precision}', p_revision#>>'{dateRange,label}', p_revision->>'scoreability',
|
||||
nullif(p_revision->>'supersedesRevisionId', '')::uuid, (p_revision->>'createdAt')::timestamptz
|
||||
);
|
||||
insert into public.birth_time_rectification_v4_turns(
|
||||
id, case_id, user_id, case_version, question, answer, action_id, created_at
|
||||
) values (p_turn_id, p_case_id, p_user_id, p_expected_version + 1, '修订事件', '', p_action_id, p_now);
|
||||
update public.birth_time_rectification_v4_cases set
|
||||
version = p_expected_version + 1, status = 'processing', phase = 'scoring_candidates',
|
||||
evidence_set_hash = p_output_evidence_set_hash, current_question = null, updated_at = p_now
|
||||
where id = p_case_id;
|
||||
insert into public.birth_time_rectification_v4_jobs(
|
||||
id, case_id, user_id, turn_id, status, phase, expected_case_version,
|
||||
evidence_set_hash, calculation_spec_hash, created_at, updated_at
|
||||
) values (
|
||||
p_job_id, p_case_id, p_user_id, p_turn_id, 'pending', 'scoring_candidates', p_expected_version + 1,
|
||||
p_output_evidence_set_hash, v_case.calculation_spec_hash, p_now, p_now
|
||||
);
|
||||
insert into public.birth_time_rectification_v4_actions(user_id, action_id, case_id, job_id, created_at)
|
||||
values (p_user_id, p_action_id, p_case_id, p_job_id, p_now);
|
||||
return p_job_id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create function public.transition_birth_time_rectification_v4_case(
|
||||
p_user_id uuid, p_case_id uuid, p_action_id uuid, p_expected_version bigint,
|
||||
p_status text, p_phase text, p_accepted_range_start text, p_accepted_range_end text, p_now timestamptz
|
||||
) returns uuid
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
declare v_case public.birth_time_rectification_v4_cases%rowtype; v_case_id uuid; v_snapshot public.birth_time_rectification_v4_candidate_snapshots%rowtype; v_primary jsonb;
|
||||
begin
|
||||
select action.case_id into v_case_id from public.birth_time_rectification_v4_actions action
|
||||
where action.user_id = p_user_id and action.action_id = p_action_id;
|
||||
if v_case_id is not null then return v_case_id; end if;
|
||||
select value.* into v_case from public.birth_time_rectification_v4_cases value
|
||||
where value.id = p_case_id and value.user_id = p_user_id for update;
|
||||
if not found then raise exception 'rectification_v4_case_not_found'; end if;
|
||||
if v_case.version <> p_expected_version then raise exception 'stale_rectification_v4_case'; end if;
|
||||
if v_case.status = 'abandoned' then raise exception 'rectification_v4_case_invalid_state'; end if;
|
||||
if p_status = 'paused' and v_case.status not in ('awaiting_answer', 'range_ready') then raise exception 'rectification_v4_case_invalid_state'; end if;
|
||||
if p_status = 'awaiting_answer' and v_case.status <> 'paused' then raise exception 'rectification_v4_case_invalid_state'; end if;
|
||||
if p_status = 'range_ready' then
|
||||
if v_case.status not in ('awaiting_answer', 'range_ready') or v_case.latest_snapshot_id is null then raise exception 'rectification_v4_case_invalid_state'; end if;
|
||||
select value.* into v_snapshot from public.birth_time_rectification_v4_candidate_snapshots value where value.id = v_case.latest_snapshot_id;
|
||||
v_primary = v_snapshot.clusters->0;
|
||||
if not v_snapshot.can_accept_range
|
||||
or p_accepted_range_start is null or p_accepted_range_end is null
|
||||
or p_accepted_range_start = p_accepted_range_end
|
||||
or v_primary->>'startTime' <> p_accepted_range_start
|
||||
or v_primary->>'endTime' <> p_accepted_range_end then
|
||||
raise exception 'rectification_v4_range_not_acceptable';
|
||||
end if;
|
||||
elsif p_accepted_range_start is not null or p_accepted_range_end is not null then
|
||||
raise exception 'rectification_v4_range_not_acceptable';
|
||||
end if;
|
||||
update public.birth_time_rectification_v4_cases set
|
||||
version = p_expected_version + 1, status = p_status, phase = p_phase,
|
||||
accepted_range_start = case when p_status = 'range_ready' then p_accepted_range_start else accepted_range_start end,
|
||||
accepted_range_end = case when p_status = 'range_ready' then p_accepted_range_end else accepted_range_end end,
|
||||
current_question = case when p_status in ('abandoned', 'range_ready') then null else current_question end,
|
||||
updated_at = p_now
|
||||
where id = p_case_id;
|
||||
insert into public.birth_time_rectification_v4_actions(user_id, action_id, case_id, created_at)
|
||||
values (p_user_id, p_action_id, p_case_id, p_now);
|
||||
return p_case_id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create function public.claim_next_birth_time_rectification_v4_job(
|
||||
p_worker_id uuid, p_now timestamptz
|
||||
) returns uuid
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
declare v_job_id uuid;
|
||||
begin
|
||||
select value.id into v_job_id from public.birth_time_rectification_v4_jobs value
|
||||
where value.status = 'pending'
|
||||
or (value.status = 'processing' and value.lease_expires_at <= p_now)
|
||||
order by value.created_at for update skip locked limit 1;
|
||||
if v_job_id is null then return null; end if;
|
||||
update public.birth_time_rectification_v4_jobs set
|
||||
status = 'processing', worker_id = p_worker_id, lease_expires_at = p_now + interval '10 minutes',
|
||||
error_code = null, updated_at = p_now
|
||||
where id = v_job_id;
|
||||
return v_job_id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create function public.update_birth_time_rectification_v4_job_phase(
|
||||
p_worker_id uuid, p_job_id uuid, p_phase text, p_now timestamptz
|
||||
) returns void
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
declare v_case_id uuid;
|
||||
begin
|
||||
update public.birth_time_rectification_v4_jobs set phase = p_phase, updated_at = p_now,
|
||||
lease_expires_at = p_now + interval '10 minutes'
|
||||
where id = p_job_id and worker_id = p_worker_id and status = 'processing'
|
||||
and lease_expires_at > p_now returning case_id into v_case_id;
|
||||
if v_case_id is null then raise exception 'rectification_v4_job_lease_lost'; end if;
|
||||
update public.birth_time_rectification_v4_cases set phase = p_phase, updated_at = p_now where id = v_case_id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create function public.complete_birth_time_rectification_v4_job(
|
||||
p_worker_id uuid, p_job_id uuid, p_expected_case_version bigint,
|
||||
p_input_evidence_set_hash text, p_output_evidence_set_hash text, p_calculation_spec_hash text,
|
||||
p_event_revisions jsonb, p_snapshot jsonb, p_next_question jsonb,
|
||||
p_status text, p_phase text, p_now timestamptz
|
||||
) returns uuid
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
declare
|
||||
v_job public.birth_time_rectification_v4_jobs%rowtype;
|
||||
v_case public.birth_time_rectification_v4_cases%rowtype;
|
||||
item jsonb;
|
||||
v_snapshot_id uuid;
|
||||
begin
|
||||
select value.* into v_job from public.birth_time_rectification_v4_jobs value
|
||||
where value.id = p_job_id for update;
|
||||
if not found or v_job.worker_id is distinct from p_worker_id or v_job.status <> 'processing'
|
||||
or v_job.lease_expires_at <= p_now then raise exception 'rectification_v4_job_lease_lost'; end if;
|
||||
select value.* into v_case from public.birth_time_rectification_v4_cases value
|
||||
where value.id = v_job.case_id for update;
|
||||
if v_case.version <> p_expected_case_version
|
||||
or v_case.evidence_set_hash <> p_input_evidence_set_hash
|
||||
or v_case.calculation_spec_hash <> p_calculation_spec_hash
|
||||
or v_job.expected_case_version <> p_expected_case_version
|
||||
or v_job.evidence_set_hash <> p_input_evidence_set_hash
|
||||
or v_job.calculation_spec_hash <> p_calculation_spec_hash then
|
||||
update public.birth_time_rectification_v4_jobs set status = 'stale', updated_at = p_now where id = p_job_id;
|
||||
raise exception 'stale_rectification_v4_job';
|
||||
end if;
|
||||
if jsonb_typeof(p_event_revisions) <> 'array' then raise exception 'invalid_rectification_v4_event_revisions'; end if;
|
||||
for item in select value from jsonb_array_elements(p_event_revisions) loop
|
||||
insert into public.birth_time_rectification_v4_events(id, case_id, user_id, created_at)
|
||||
values ((item->>'eventId')::uuid, v_case.id, v_case.user_id, (item->>'createdAt')::timestamptz)
|
||||
on conflict (id) do nothing;
|
||||
insert into public.birth_time_rectification_v4_event_revisions(
|
||||
id, event_id, case_id, user_id, revision, domain, event_kind, summary, raw_text,
|
||||
date_start, date_end, date_precision, date_label, scoreability, supersedes_revision_id, created_at
|
||||
) values (
|
||||
(item->>'id')::uuid, (item->>'eventId')::uuid, v_case.id, v_case.user_id,
|
||||
(item->>'revision')::integer, item->>'domain', item->>'eventKind', item->>'summary', item->>'rawText',
|
||||
(item#>>'{dateRange,start}')::date, (item#>>'{dateRange,end}')::date,
|
||||
item#>>'{dateRange,precision}', item#>>'{dateRange,label}', item->>'scoreability',
|
||||
nullif(item->>'supersedesRevisionId', '')::uuid, (item->>'createdAt')::timestamptz
|
||||
);
|
||||
end loop;
|
||||
if p_snapshot is not null then
|
||||
v_snapshot_id = (p_snapshot->>'id')::uuid;
|
||||
if (p_snapshot->>'canConfirmExactMinute')::boolean then raise exception 'exact_minute_confirmation_forbidden'; end if;
|
||||
insert into public.birth_time_rectification_v4_candidate_snapshots(
|
||||
id, case_id, user_id, case_version, evidence_set_hash, calculation_spec_hash,
|
||||
algorithm_version, candidates, clusters, robustness, can_confirm_exact_minute,
|
||||
can_accept_range, gate_reasons, created_at
|
||||
) values (
|
||||
v_snapshot_id, v_case.id, v_case.user_id, (p_snapshot->>'caseVersion')::bigint,
|
||||
p_snapshot->>'evidenceSetHash', p_snapshot->>'calculationSpecHash', p_snapshot->>'algorithmVersion',
|
||||
p_snapshot->'candidates', p_snapshot->'clusters', p_snapshot->'robustness', false,
|
||||
(p_snapshot->>'canAcceptRange')::boolean, p_snapshot->'gateReasons', (p_snapshot->>'createdAt')::timestamptz
|
||||
);
|
||||
end if;
|
||||
update public.birth_time_rectification_v4_cases set
|
||||
version = p_expected_case_version + 1, evidence_set_hash = p_output_evidence_set_hash,
|
||||
latest_snapshot_id = coalesce(v_snapshot_id, latest_snapshot_id), current_question = p_next_question,
|
||||
status = p_status, phase = p_phase, updated_at = p_now
|
||||
where id = v_case.id;
|
||||
update public.birth_time_rectification_v4_jobs set
|
||||
status = 'completed', phase = p_phase, result_snapshot_id = v_snapshot_id,
|
||||
lease_expires_at = null, updated_at = p_now
|
||||
where id = p_job_id;
|
||||
return v_case.id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create function public.fail_birth_time_rectification_v4_job(
|
||||
p_worker_id uuid, p_job_id uuid, p_expected_case_version bigint, p_error_code text,
|
||||
p_restore_question jsonb, p_now timestamptz
|
||||
) returns void
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
declare v_case_id uuid;
|
||||
begin
|
||||
update public.birth_time_rectification_v4_jobs set
|
||||
status = 'failed', error_code = p_error_code, lease_expires_at = null, updated_at = p_now
|
||||
where id = p_job_id and worker_id = p_worker_id and status = 'processing'
|
||||
returning case_id into v_case_id;
|
||||
if v_case_id is null then raise exception 'rectification_v4_job_lease_lost'; end if;
|
||||
update public.birth_time_rectification_v4_cases set
|
||||
status = 'awaiting_answer', phase = 'collecting_evidence', current_question = p_restore_question, updated_at = p_now
|
||||
where id = v_case_id and version = p_expected_case_version;
|
||||
end;
|
||||
$$;
|
||||
|
||||
|
||||
create function public.birth_time_rectification_v4_handoff_projection(
|
||||
p_user_id uuid,
|
||||
p_case_id uuid
|
||||
) returns jsonb
|
||||
language sql stable security definer set search_path = '' as $$
|
||||
select pg_catalog.jsonb_build_object(
|
||||
'protocol', 'rectification-evidence-v4',
|
||||
'caseId', handoff.case_id,
|
||||
'caseVersion', case_value.version,
|
||||
'question', handoff.question,
|
||||
'questionFingerprint', handoff.question_fingerprint,
|
||||
'requestId', handoff.request_id,
|
||||
'status', case
|
||||
when handoff.state = 'pending' then 'pending'
|
||||
when handoff.state in ('claimed', 'executing')
|
||||
and handoff.lease_expires_at <= pg_catalog.now() then 'pending'
|
||||
when handoff.state in ('claimed', 'executing') then 'in_progress'
|
||||
else 'consumed'
|
||||
end,
|
||||
'acceptedRange', case
|
||||
when case_value.accepted_range_start is null then null
|
||||
else pg_catalog.jsonb_build_object(
|
||||
'start', case_value.accepted_range_start,
|
||||
'end', case_value.accepted_range_end
|
||||
)
|
||||
end
|
||||
)
|
||||
from public.birth_time_rectification_v4_handoffs handoff
|
||||
join public.birth_time_rectification_v4_cases case_value
|
||||
on case_value.id = handoff.case_id and case_value.user_id = handoff.user_id
|
||||
where handoff.case_id = p_case_id and handoff.user_id = p_user_id;
|
||||
$$;
|
||||
|
||||
create function public.attach_birth_time_rectification_v4_question(
|
||||
p_user_id uuid,
|
||||
p_case_id uuid,
|
||||
p_expected_version bigint,
|
||||
p_action_id uuid,
|
||||
p_question text,
|
||||
p_question_fingerprint text
|
||||
) returns jsonb
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
declare
|
||||
v_case public.birth_time_rectification_v4_cases%rowtype;
|
||||
v_handoff public.birth_time_rectification_v4_handoffs%rowtype;
|
||||
v_receipt public.birth_time_rectification_v4_handoff_attach_receipts%rowtype;
|
||||
v_response jsonb;
|
||||
begin
|
||||
if p_user_id is null or p_case_id is null or p_action_id is null
|
||||
or p_expected_version is null or p_expected_version < 0
|
||||
or p_question is null or length(btrim(p_question)) not between 1 and 500
|
||||
or p_question_fingerprint is null or p_question_fingerprint !~ '^[a-f0-9]{64}$'
|
||||
or public.conversational_rectification_question_fingerprint(p_question)
|
||||
is distinct from p_question_fingerprint then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
|
||||
perform pg_catalog.pg_advisory_xact_lock(
|
||||
pg_catalog.hashtextextended(
|
||||
p_user_id::text || ':' || p_case_id::text || ':rectification-v4-attach', 0
|
||||
)
|
||||
);
|
||||
|
||||
select case_value.* into v_case
|
||||
from public.birth_time_rectification_v4_cases case_value
|
||||
where case_value.id = p_case_id and case_value.user_id = p_user_id
|
||||
for update;
|
||||
if not found then
|
||||
raise exception 'rectification_v4_case_not_found' using errcode = 'P0001';
|
||||
end if;
|
||||
|
||||
select receipt.* into v_receipt
|
||||
from public.birth_time_rectification_v4_handoff_attach_receipts receipt
|
||||
where receipt.case_id = p_case_id and receipt.action_id = p_action_id
|
||||
for update;
|
||||
if found then
|
||||
if v_receipt.user_id is distinct from p_user_id
|
||||
or v_receipt.expected_case_version is distinct from p_expected_version
|
||||
or v_receipt.question_fingerprint is distinct from p_question_fingerprint then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
return v_receipt.response;
|
||||
end if;
|
||||
|
||||
if v_case.version is distinct from p_expected_version then
|
||||
raise exception 'stale_rectification_v4_case' using errcode = 'P0001';
|
||||
end if;
|
||||
if v_case.status = 'abandoned' then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
|
||||
select handoff.* into v_handoff
|
||||
from public.birth_time_rectification_v4_handoffs handoff
|
||||
where handoff.case_id = p_case_id and handoff.user_id = p_user_id
|
||||
for update;
|
||||
|
||||
if found then
|
||||
if v_handoff.question_fingerprint is distinct from p_question_fingerprint
|
||||
or v_handoff.question is distinct from p_question then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
else
|
||||
insert into public.birth_time_rectification_v4_handoffs (
|
||||
case_id, user_id, question, question_fingerprint,
|
||||
attached_case_version, attach_action_id, state, attempt, request_id
|
||||
) values (
|
||||
p_case_id, p_user_id, p_question, p_question_fingerprint,
|
||||
p_expected_version, p_action_id, 'pending', 0,
|
||||
public.conversational_rectification_handoff_request_id(p_case_id, 0)
|
||||
);
|
||||
end if;
|
||||
|
||||
v_response := public.birth_time_rectification_v4_handoff_projection(
|
||||
p_user_id, p_case_id
|
||||
);
|
||||
insert into public.birth_time_rectification_v4_handoff_attach_receipts (
|
||||
case_id, action_id, user_id, expected_case_version,
|
||||
question_fingerprint, response
|
||||
) values (
|
||||
p_case_id, p_action_id, p_user_id, p_expected_version,
|
||||
p_question_fingerprint, v_response
|
||||
);
|
||||
return v_response;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create function public.load_birth_time_rectification_v4_handoff(
|
||||
p_user_id uuid,
|
||||
p_case_id uuid default null
|
||||
) returns jsonb
|
||||
language plpgsql stable security definer set search_path = '' as $$
|
||||
declare v_case_id uuid;
|
||||
begin
|
||||
if p_user_id is null then return null; end if;
|
||||
if p_case_id is not null then
|
||||
v_case_id := p_case_id;
|
||||
else
|
||||
select handoff.case_id into v_case_id
|
||||
from public.birth_time_rectification_v4_handoffs handoff
|
||||
where handoff.user_id = p_user_id and handoff.state <> 'consumed'
|
||||
order by handoff.updated_at desc, handoff.created_at desc
|
||||
limit 1;
|
||||
end if;
|
||||
return public.birth_time_rectification_v4_handoff_projection(p_user_id, v_case_id);
|
||||
end;
|
||||
$$;
|
||||
|
||||
create function public.consume_birth_time_rectification_v4_handoff(
|
||||
p_user_id uuid,
|
||||
p_case_id uuid
|
||||
) returns void
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
begin
|
||||
update public.birth_time_rectification_v4_handoffs
|
||||
set state = 'consumed', claim_action_id = null, lease_expires_at = null,
|
||||
consumed_at = coalesce(consumed_at, pg_catalog.now()), updated_at = pg_catalog.now()
|
||||
where case_id = p_case_id and user_id = p_user_id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create function public.claim_birth_time_rectification_v4_handoff(
|
||||
p_user_id uuid,
|
||||
p_case_id uuid,
|
||||
p_expected_version bigint,
|
||||
p_action_id uuid,
|
||||
p_question_fingerprint text
|
||||
) returns jsonb
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
declare
|
||||
v_case public.birth_time_rectification_v4_cases%rowtype;
|
||||
v_handoff public.birth_time_rectification_v4_handoffs%rowtype;
|
||||
v_request_status text;
|
||||
begin
|
||||
if p_user_id is null or p_case_id is null or p_action_id is null
|
||||
or p_expected_version is null or p_expected_version < 0
|
||||
or p_question_fingerprint is null or p_question_fingerprint !~ '^[a-f0-9]{64}$' then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
|
||||
perform pg_catalog.pg_advisory_xact_lock(
|
||||
pg_catalog.hashtextextended(
|
||||
p_user_id::text || ':' || p_case_id::text || ':rectification-v4-claim', 0
|
||||
)
|
||||
);
|
||||
|
||||
select case_value.* into v_case
|
||||
from public.birth_time_rectification_v4_cases case_value
|
||||
where case_value.id = p_case_id and case_value.user_id = p_user_id
|
||||
for update;
|
||||
if not found then
|
||||
raise exception 'rectification_v4_case_not_found' using errcode = 'P0001';
|
||||
end if;
|
||||
if v_case.version is distinct from p_expected_version then
|
||||
raise exception 'stale_rectification_v4_case' using errcode = 'P0001';
|
||||
end if;
|
||||
if v_case.status is distinct from 'range_ready'
|
||||
or v_case.phase is distinct from 'complete'
|
||||
or v_case.accepted_range_start is null
|
||||
or v_case.accepted_range_end is null then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
|
||||
select handoff.* into v_handoff
|
||||
from public.birth_time_rectification_v4_handoffs handoff
|
||||
where handoff.case_id = p_case_id and handoff.user_id = p_user_id
|
||||
for update;
|
||||
if not found or v_handoff.question_fingerprint is distinct from p_question_fingerprint then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
if v_handoff.state = 'consumed' then
|
||||
return public.birth_time_rectification_v4_handoff_projection(p_user_id, p_case_id);
|
||||
end if;
|
||||
|
||||
if v_handoff.state in ('claimed', 'executing')
|
||||
and v_handoff.lease_expires_at > pg_catalog.now() then
|
||||
if v_handoff.state = 'claimed' and v_handoff.claim_action_id = p_action_id then
|
||||
return public.birth_time_rectification_v4_handoff_projection(p_user_id, p_case_id)
|
||||
|| pg_catalog.jsonb_build_object('status', 'claimed');
|
||||
end if;
|
||||
return public.birth_time_rectification_v4_handoff_projection(p_user_id, p_case_id)
|
||||
|| pg_catalog.jsonb_build_object('status', 'in_progress');
|
||||
end if;
|
||||
|
||||
select request.status into v_request_status
|
||||
from public.consultation_requests request
|
||||
where request.user_id = p_user_id and request.request_id = v_handoff.request_id::text
|
||||
for update;
|
||||
if v_request_status = 'completed' then
|
||||
perform public.consume_birth_time_rectification_v4_handoff(p_user_id, p_case_id);
|
||||
return public.birth_time_rectification_v4_handoff_projection(p_user_id, p_case_id);
|
||||
end if;
|
||||
if v_request_status = 'cancelled' then
|
||||
update public.birth_time_rectification_v4_handoffs
|
||||
set attempt = attempt + 1,
|
||||
request_id = public.conversational_rectification_handoff_request_id(
|
||||
p_case_id, attempt + 1
|
||||
)
|
||||
where case_id = p_case_id and user_id = p_user_id
|
||||
returning * into v_handoff;
|
||||
end if;
|
||||
|
||||
update public.birth_time_rectification_v4_handoffs
|
||||
set state = 'claimed', claim_action_id = p_action_id,
|
||||
lease_expires_at = pg_catalog.now() + interval '2 minutes',
|
||||
claimed_at = pg_catalog.now(), consumed_at = null, updated_at = pg_catalog.now()
|
||||
where case_id = p_case_id and user_id = p_user_id;
|
||||
return public.birth_time_rectification_v4_handoff_projection(p_user_id, p_case_id)
|
||||
|| pg_catalog.jsonb_build_object('status', 'claimed');
|
||||
end;
|
||||
$$;
|
||||
|
||||
create function public.begin_birth_time_rectification_v4_handoff_execution(
|
||||
p_user_id uuid,
|
||||
p_case_id uuid,
|
||||
p_expected_version bigint,
|
||||
p_claim_action_id uuid,
|
||||
p_request_id uuid,
|
||||
p_question_fingerprint text
|
||||
) returns jsonb
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
declare
|
||||
v_case public.birth_time_rectification_v4_cases%rowtype;
|
||||
v_handoff public.birth_time_rectification_v4_handoffs%rowtype;
|
||||
v_request_status text;
|
||||
v_credits integer;
|
||||
v_accepted_range jsonb;
|
||||
begin
|
||||
if p_user_id is null or p_case_id is null or p_claim_action_id is null
|
||||
or p_request_id is null or p_expected_version is null or p_expected_version < 0
|
||||
or p_question_fingerprint is null or p_question_fingerprint !~ '^[a-f0-9]{64}$' then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
|
||||
perform pg_catalog.pg_advisory_xact_lock(
|
||||
pg_catalog.hashtextextended(
|
||||
p_user_id::text || ':' || p_case_id::text || ':rectification-v4-execute', 0
|
||||
)
|
||||
);
|
||||
|
||||
select case_value.* into v_case
|
||||
from public.birth_time_rectification_v4_cases case_value
|
||||
where case_value.id = p_case_id and case_value.user_id = p_user_id
|
||||
for update;
|
||||
select handoff.* into v_handoff
|
||||
from public.birth_time_rectification_v4_handoffs handoff
|
||||
where handoff.case_id = p_case_id and handoff.user_id = p_user_id
|
||||
for update;
|
||||
|
||||
if v_case.id is null or v_handoff.case_id is null
|
||||
or v_case.version is distinct from p_expected_version
|
||||
or v_case.status is distinct from 'range_ready'
|
||||
or v_case.phase is distinct from 'complete'
|
||||
or v_case.accepted_range_start is null
|
||||
or v_case.accepted_range_end is null
|
||||
or v_handoff.question_fingerprint is distinct from p_question_fingerprint
|
||||
or v_handoff.request_id is distinct from p_request_id then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
|
||||
v_accepted_range := pg_catalog.jsonb_build_object(
|
||||
'start', v_case.accepted_range_start,
|
||||
'end', v_case.accepted_range_end
|
||||
);
|
||||
|
||||
if v_handoff.state = 'consumed' then
|
||||
return pg_catalog.jsonb_build_object(
|
||||
'status', 'consumed', 'requestId', p_request_id, 'acceptedRange', v_accepted_range
|
||||
);
|
||||
end if;
|
||||
if v_handoff.claim_action_id is distinct from p_claim_action_id
|
||||
or v_handoff.lease_expires_at <= pg_catalog.now() then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
if v_handoff.state = 'executing' then
|
||||
return pg_catalog.jsonb_build_object(
|
||||
'status', 'in_progress', 'requestId', p_request_id, 'acceptedRange', v_accepted_range
|
||||
);
|
||||
end if;
|
||||
if v_handoff.state is distinct from 'claimed' then
|
||||
return pg_catalog.jsonb_build_object(
|
||||
'status', 'in_progress', 'requestId', p_request_id, 'acceptedRange', v_accepted_range
|
||||
);
|
||||
end if;
|
||||
|
||||
select request.status into v_request_status
|
||||
from public.consultation_requests request
|
||||
where request.user_id = p_user_id and request.request_id = p_request_id::text
|
||||
for update;
|
||||
if v_request_status = 'completed' then
|
||||
perform public.consume_birth_time_rectification_v4_handoff(p_user_id, p_case_id);
|
||||
return pg_catalog.jsonb_build_object(
|
||||
'status', 'consumed', 'requestId', p_request_id, 'acceptedRange', v_accepted_range
|
||||
);
|
||||
end if;
|
||||
if v_request_status = 'cancelled' then
|
||||
update public.birth_time_rectification_v4_handoffs
|
||||
set state = 'pending', attempt = attempt + 1,
|
||||
request_id = public.conversational_rectification_handoff_request_id(
|
||||
p_case_id, attempt + 1
|
||||
),
|
||||
claim_action_id = null, lease_expires_at = null,
|
||||
claimed_at = null, updated_at = pg_catalog.now()
|
||||
where case_id = p_case_id and user_id = p_user_id;
|
||||
return pg_catalog.jsonb_build_object(
|
||||
'status', 'released', 'requestId', p_request_id, 'acceptedRange', v_accepted_range
|
||||
);
|
||||
end if;
|
||||
|
||||
select profile.credits into v_credits
|
||||
from public.profiles profile where profile.id = p_user_id;
|
||||
update public.birth_time_rectification_v4_handoffs
|
||||
set state = 'executing', lease_expires_at = pg_catalog.now() + interval '2 minutes',
|
||||
updated_at = pg_catalog.now()
|
||||
where case_id = p_case_id and user_id = p_user_id;
|
||||
return pg_catalog.jsonb_build_object(
|
||||
'status', 'ready',
|
||||
'requestId', p_request_id,
|
||||
'billingReused', coalesce(v_request_status = 'reserved', false),
|
||||
'credits', v_credits,
|
||||
'acceptedRange', v_accepted_range
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
create function public.settle_birth_time_rectification_v4_handoff(
|
||||
p_user_id uuid,
|
||||
p_case_id uuid,
|
||||
p_claim_action_id uuid,
|
||||
p_request_id uuid,
|
||||
p_emitted boolean
|
||||
) returns jsonb
|
||||
language plpgsql security definer set search_path = '' as $$
|
||||
declare
|
||||
v_handoff public.birth_time_rectification_v4_handoffs%rowtype;
|
||||
v_receipt public.birth_time_rectification_v4_handoff_settlements%rowtype;
|
||||
v_success boolean;
|
||||
v_credits integer;
|
||||
v_error text;
|
||||
v_response jsonb;
|
||||
begin
|
||||
if p_user_id is null or p_case_id is null or p_claim_action_id is null
|
||||
or p_request_id is null or p_emitted is null then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
|
||||
perform pg_catalog.pg_advisory_xact_lock(
|
||||
pg_catalog.hashtextextended(
|
||||
p_user_id::text || ':' || p_case_id::text || ':rectification-v4-settle', 0
|
||||
)
|
||||
);
|
||||
|
||||
select settlement.* into v_receipt
|
||||
from public.birth_time_rectification_v4_handoff_settlements settlement
|
||||
where settlement.case_id = p_case_id and settlement.request_id = p_request_id
|
||||
for update;
|
||||
if found then
|
||||
if v_receipt.user_id is distinct from p_user_id
|
||||
or v_receipt.claim_action_id is distinct from p_claim_action_id
|
||||
or v_receipt.emitted is distinct from p_emitted then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
return v_receipt.response;
|
||||
end if;
|
||||
|
||||
select handoff.* into v_handoff
|
||||
from public.birth_time_rectification_v4_handoffs handoff
|
||||
where handoff.case_id = p_case_id and handoff.user_id = p_user_id
|
||||
for update;
|
||||
if not found or v_handoff.request_id is distinct from p_request_id
|
||||
or v_handoff.claim_action_id is distinct from p_claim_action_id
|
||||
or v_handoff.state not in ('claimed', 'executing') then
|
||||
raise exception 'rectification_v4_handoff_conflict' using errcode = 'P0001';
|
||||
end if;
|
||||
|
||||
if p_emitted then
|
||||
select result.success, result.credits, result.error_code
|
||||
into v_success, v_credits, v_error
|
||||
from public.complete_consultation_credit(p_user_id, p_request_id::text) result;
|
||||
if v_success is not true then
|
||||
raise exception 'rectification_v4_billing_failed' using errcode = 'P0001';
|
||||
end if;
|
||||
perform public.consume_birth_time_rectification_v4_handoff(p_user_id, p_case_id);
|
||||
v_response := pg_catalog.jsonb_build_object(
|
||||
'status', 'consumed', 'requestId', p_request_id, 'credits', v_credits
|
||||
);
|
||||
else
|
||||
select result.success, result.credits, result.error_code
|
||||
into v_success, v_credits, v_error
|
||||
from public.cancel_consultation_credit(p_user_id, p_request_id::text) result;
|
||||
if v_success is not true then
|
||||
raise exception 'rectification_v4_billing_failed' using errcode = 'P0001';
|
||||
end if;
|
||||
update public.birth_time_rectification_v4_handoffs
|
||||
set state = 'pending', attempt = attempt + 1,
|
||||
request_id = public.conversational_rectification_handoff_request_id(
|
||||
p_case_id, attempt + 1
|
||||
),
|
||||
claim_action_id = null, lease_expires_at = null,
|
||||
claimed_at = null, updated_at = pg_catalog.now()
|
||||
where case_id = p_case_id and user_id = p_user_id;
|
||||
v_response := pg_catalog.jsonb_build_object(
|
||||
'status', 'pending', 'requestId', p_request_id, 'credits', v_credits
|
||||
);
|
||||
end if;
|
||||
|
||||
insert into public.birth_time_rectification_v4_handoff_settlements (
|
||||
case_id, request_id, user_id, claim_action_id, emitted, response
|
||||
) values (
|
||||
p_case_id, p_request_id, p_user_id, p_claim_action_id, p_emitted, v_response
|
||||
);
|
||||
return v_response;
|
||||
end;
|
||||
$$;
|
||||
|
||||
revoke all on function public.create_birth_time_rectification_v4_case(uuid, uuid, uuid, text, text, jsonb, text, text, jsonb, timestamptz) from public, anon, authenticated;
|
||||
revoke all on function public.submit_birth_time_rectification_v4_answer(uuid, uuid, uuid, bigint, uuid, uuid, text, uuid, text, text, uuid, timestamptz) from public, anon, authenticated;
|
||||
revoke all on function public.revise_birth_time_rectification_v4_event(uuid, uuid, uuid, bigint, jsonb, text, uuid, uuid, timestamptz) from public, anon, authenticated;
|
||||
revoke all on function public.transition_birth_time_rectification_v4_case(uuid, uuid, uuid, bigint, text, text, text, text, timestamptz) from public, anon, authenticated;
|
||||
revoke all on function public.claim_next_birth_time_rectification_v4_job(uuid, timestamptz) from public, anon, authenticated;
|
||||
revoke all on function public.update_birth_time_rectification_v4_job_phase(uuid, uuid, text, timestamptz) from public, anon, authenticated;
|
||||
revoke all on function public.complete_birth_time_rectification_v4_job(uuid, uuid, bigint, text, text, text, jsonb, jsonb, jsonb, text, text, timestamptz) from public, anon, authenticated;
|
||||
revoke all on function public.fail_birth_time_rectification_v4_job(uuid, uuid, bigint, text, jsonb, timestamptz) from public, anon, authenticated;
|
||||
revoke all on function public.birth_time_rectification_v4_handoff_projection(uuid, uuid) from public, anon, authenticated, service_role;
|
||||
revoke all on function public.consume_birth_time_rectification_v4_handoff(uuid, uuid) from public, anon, authenticated, service_role;
|
||||
revoke all on function public.attach_birth_time_rectification_v4_question(uuid, uuid, bigint, uuid, text, text) from public, anon, authenticated;
|
||||
revoke all on function public.load_birth_time_rectification_v4_handoff(uuid, uuid) from public, anon, authenticated;
|
||||
revoke all on function public.claim_birth_time_rectification_v4_handoff(uuid, uuid, bigint, uuid, text) from public, anon, authenticated;
|
||||
revoke all on function public.begin_birth_time_rectification_v4_handoff_execution(uuid, uuid, bigint, uuid, uuid, text) from public, anon, authenticated;
|
||||
revoke all on function public.settle_birth_time_rectification_v4_handoff(uuid, uuid, uuid, uuid, boolean) from public, anon, authenticated;
|
||||
grant execute on function public.create_birth_time_rectification_v4_case(uuid, uuid, uuid, text, text, jsonb, text, text, jsonb, timestamptz) to service_role;
|
||||
grant execute on function public.submit_birth_time_rectification_v4_answer(uuid, uuid, uuid, bigint, uuid, uuid, text, uuid, text, text, uuid, timestamptz) to service_role;
|
||||
grant execute on function public.revise_birth_time_rectification_v4_event(uuid, uuid, uuid, bigint, jsonb, text, uuid, uuid, timestamptz) to service_role;
|
||||
grant execute on function public.transition_birth_time_rectification_v4_case(uuid, uuid, uuid, bigint, text, text, text, text, timestamptz) to service_role;
|
||||
grant execute on function public.claim_next_birth_time_rectification_v4_job(uuid, timestamptz) to service_role;
|
||||
grant execute on function public.update_birth_time_rectification_v4_job_phase(uuid, uuid, text, timestamptz) to service_role;
|
||||
grant execute on function public.complete_birth_time_rectification_v4_job(uuid, uuid, bigint, text, text, text, jsonb, jsonb, jsonb, text, text, timestamptz) to service_role;
|
||||
grant execute on function public.fail_birth_time_rectification_v4_job(uuid, uuid, bigint, text, jsonb, timestamptz) to service_role;
|
||||
grant execute on function public.attach_birth_time_rectification_v4_question(uuid, uuid, bigint, uuid, text, text) to service_role;
|
||||
grant execute on function public.load_birth_time_rectification_v4_handoff(uuid, uuid) to service_role;
|
||||
grant execute on function public.claim_birth_time_rectification_v4_handoff(uuid, uuid, bigint, uuid, text) to service_role;
|
||||
grant execute on function public.begin_birth_time_rectification_v4_handoff_execution(uuid, uuid, bigint, uuid, uuid, text) to service_role;
|
||||
grant execute on function public.settle_birth_time_rectification_v4_handoff(uuid, uuid, uuid, uuid, boolean) to service_role;
|
||||
|
||||
commit;
|
||||
Reference in New Issue
Block a user