f708edf365
SQL kinds now match the TypeScript ledger so batch ingest can confirm dated events. Confirm no longer burns the opening focus, recap uses server date labels, and the Agent sees indistinguishable width instead of a fake unique minute. Co-authored-by: Cursor <cursoragent@cursor.com>
721 lines
24 KiB
PL/PgSQL
721 lines
24 KiB
PL/PgSQL
begin;
|
|
|
|
create or replace function public.agentic_rectification_evidence_kinds()
|
|
returns text[]
|
|
language sql
|
|
immutable
|
|
as $$
|
|
select array[
|
|
'education_start', 'education_completion', 'education_interruption',
|
|
'education_change', 'education_milestone',
|
|
'career_entry', 'career_change', 'promotion', 'career_pressure',
|
|
'career_exit', 'business_start',
|
|
'relationship_start', 'relationship_commitment', 'relationship_separation',
|
|
'relationship_end', 'relationship_change',
|
|
'relocation', 'foreign_move', 'return', 'home_change',
|
|
'finance_gain', 'finance_loss', 'income_change', 'asset_change',
|
|
'finance_change',
|
|
'self_health_event', 'pressure_period', 'family_event', 'other'
|
|
]::text[]
|
|
$$;
|
|
|
|
revoke all on function public.agentic_rectification_evidence_kinds()
|
|
from public, anon, authenticated;
|
|
grant execute on function public.agentic_rectification_evidence_kinds()
|
|
to service_role;
|
|
|
|
create or replace function public.agentic_rectification_evidence_domains()
|
|
returns text[]
|
|
language sql
|
|
immutable
|
|
as $$
|
|
select array[
|
|
'education', 'career', 'relationship', 'relocation', 'finance',
|
|
'health', 'health_pressure', 'family', 'other'
|
|
]::text[]
|
|
$$;
|
|
|
|
revoke all on function public.agentic_rectification_evidence_domains()
|
|
from public, anon, authenticated;
|
|
grant execute on function public.agentic_rectification_evidence_domains()
|
|
to service_role;
|
|
|
|
create or replace function public.agentic_rectification_date_precisions()
|
|
returns text[]
|
|
language sql
|
|
immutable
|
|
as $$
|
|
select array['year', 'month', 'quarter', 'day', 'range', 'unknown']::text[]
|
|
$$;
|
|
|
|
revoke all on function public.agentic_rectification_date_precisions()
|
|
from public, anon, authenticated;
|
|
grant execute on function public.agentic_rectification_date_precisions()
|
|
to service_role;
|
|
|
|
create or replace function public.agentic_rectification_precision_rank(p_precision text)
|
|
returns integer
|
|
language sql
|
|
immutable
|
|
as $$
|
|
select case p_precision
|
|
when 'day' then 4
|
|
when 'month' then 3
|
|
when 'quarter' then 2
|
|
when 'range' then 2
|
|
when 'year' then 1
|
|
when 'unknown' then 0
|
|
else -1
|
|
end
|
|
$$;
|
|
|
|
revoke all on function public.agentic_rectification_precision_rank(text)
|
|
from public, anon, authenticated;
|
|
grant execute on function public.agentic_rectification_precision_rank(text)
|
|
to service_role;
|
|
|
|
create or replace function public.agentic_rectification_normalize_quote(p_value text)
|
|
returns text
|
|
language sql
|
|
immutable
|
|
as $$
|
|
select regexp_replace(
|
|
lower(coalesce(p_value, '')),
|
|
'[\s\u3000,。!?、;:“”‘’()《》·—…,!.;:?]',
|
|
'',
|
|
'g'
|
|
)
|
|
$$;
|
|
|
|
do $$
|
|
declare
|
|
rec record;
|
|
begin
|
|
for rec in
|
|
select con.conname
|
|
from pg_constraint con
|
|
where con.conrelid = 'public.agentic_rectification_evidence'::regclass
|
|
and con.contype = 'c'
|
|
and pg_get_constraintdef(con.oid) like '%event_kind%'
|
|
loop
|
|
execute format(
|
|
'alter table public.agentic_rectification_evidence drop constraint %I',
|
|
rec.conname
|
|
);
|
|
end loop;
|
|
|
|
for rec in
|
|
select con.conname
|
|
from pg_constraint con
|
|
where con.conrelid = 'public.agentic_rectification_evidence'::regclass
|
|
and con.contype = 'c'
|
|
and pg_get_constraintdef(con.oid) like '%domain%'
|
|
and pg_get_constraintdef(con.oid) not like '%event_kind%'
|
|
loop
|
|
execute format(
|
|
'alter table public.agentic_rectification_evidence drop constraint %I',
|
|
rec.conname
|
|
);
|
|
end loop;
|
|
|
|
for rec in
|
|
select con.conname
|
|
from pg_constraint con
|
|
where con.conrelid = 'public.agentic_rectification_evidence'::regclass
|
|
and con.contype = 'c'
|
|
and pg_get_constraintdef(con.oid) like '%date_precision%'
|
|
loop
|
|
execute format(
|
|
'alter table public.agentic_rectification_evidence drop constraint %I',
|
|
rec.conname
|
|
);
|
|
end loop;
|
|
end;
|
|
$$;
|
|
|
|
alter table public.agentic_rectification_evidence
|
|
add constraint agentic_rectification_evidence_event_kind_check
|
|
check (event_kind = any (public.agentic_rectification_evidence_kinds()));
|
|
|
|
alter table public.agentic_rectification_evidence
|
|
add constraint agentic_rectification_evidence_domain_check
|
|
check (domain = any (public.agentic_rectification_evidence_domains()));
|
|
|
|
alter table public.agentic_rectification_evidence
|
|
add constraint agentic_rectification_evidence_date_precision_check
|
|
check (date_precision = any (public.agentic_rectification_date_precisions()));
|
|
|
|
create or replace function public.propose_agentic_rectification_evidence(
|
|
p_user_id uuid,
|
|
p_case_id uuid,
|
|
p_source_turn_id uuid,
|
|
p_user_quote text,
|
|
p_subject text,
|
|
p_event_kind text,
|
|
p_domain text,
|
|
p_occurred_from date,
|
|
p_occurred_to date,
|
|
p_date_precision text,
|
|
p_summary text
|
|
)
|
|
returns jsonb
|
|
language plpgsql
|
|
security definer
|
|
set search_path = ''
|
|
as $$
|
|
declare
|
|
v_case public.agentic_rectification_cases%rowtype;
|
|
v_turn public.agentic_rectification_turns%rowtype;
|
|
v_evidence_id uuid;
|
|
v_existing_id uuid;
|
|
begin
|
|
if p_user_id is null or p_case_id is null or p_source_turn_id is null
|
|
or length(btrim(coalesce(p_user_quote, ''))) = 0
|
|
or length(btrim(coalesce(p_summary, ''))) = 0
|
|
or p_subject not in ('self', 'family', 'other') then
|
|
return jsonb_build_object(
|
|
'evidence_id', null,
|
|
'idempotent', false,
|
|
'outcome', 'rejected',
|
|
'error_code', 'invalid_item',
|
|
'status', 'rejected'
|
|
);
|
|
end if;
|
|
if p_event_kind is null
|
|
or not (p_event_kind = any (public.agentic_rectification_evidence_kinds()))
|
|
or p_domain is null
|
|
or not (p_domain = any (public.agentic_rectification_evidence_domains()))
|
|
or p_date_precision is null
|
|
or not (p_date_precision = any (public.agentic_rectification_date_precisions())) then
|
|
return jsonb_build_object(
|
|
'evidence_id', null,
|
|
'idempotent', false,
|
|
'outcome', 'rejected',
|
|
'error_code', 'invalid_item',
|
|
'status', 'rejected'
|
|
);
|
|
end if;
|
|
|
|
select * into v_case
|
|
from public.agentic_rectification_cases
|
|
where id = p_case_id and user_id = p_user_id;
|
|
if not found then
|
|
raise exception 'agentic_rectification_case_not_found' using errcode = 'P0001';
|
|
end if;
|
|
if v_case.status in ('confirmed', 'closed', 'abandoned', 'superseded') then
|
|
raise exception 'agentic_rectification_case_terminal' using errcode = 'P0001';
|
|
end if;
|
|
|
|
select * into v_turn
|
|
from public.agentic_rectification_turns
|
|
where id = p_source_turn_id and case_id = p_case_id;
|
|
if not found then
|
|
raise exception 'agentic_rectification_turn_not_found' using errcode = 'P0001';
|
|
end if;
|
|
if v_turn.user_message is null
|
|
or position(
|
|
public.agentic_rectification_normalize_quote(p_user_quote)
|
|
in public.agentic_rectification_normalize_quote(v_turn.user_message)
|
|
) = 0 then
|
|
return jsonb_build_object(
|
|
'evidence_id', null,
|
|
'idempotent', false,
|
|
'outcome', 'rejected',
|
|
'error_code', 'quote_not_grounded',
|
|
'status', 'rejected'
|
|
);
|
|
end if;
|
|
|
|
select id into v_existing_id
|
|
from public.agentic_rectification_evidence
|
|
where case_id = p_case_id
|
|
and source_turn_id = p_source_turn_id
|
|
and user_quote = p_user_quote
|
|
and event_kind = p_event_kind
|
|
and summary = p_summary
|
|
and status in ('draft', 'pending_confirmation')
|
|
limit 1;
|
|
if v_existing_id is not null then
|
|
return jsonb_build_object(
|
|
'evidence_id', v_existing_id,
|
|
'idempotent', true,
|
|
'outcome', 'accepted',
|
|
'error_code', null,
|
|
'status', 'draft'
|
|
);
|
|
end if;
|
|
|
|
insert into public.agentic_rectification_evidence (
|
|
case_id, source_turn_id, user_quote, subject, event_kind, domain,
|
|
occurred_from, occurred_to, date_precision, summary, status
|
|
) values (
|
|
p_case_id, p_source_turn_id, p_user_quote, p_subject, p_event_kind, p_domain,
|
|
p_occurred_from, p_occurred_to, p_date_precision, p_summary, 'draft'
|
|
) returning id into v_evidence_id;
|
|
|
|
update public.agentic_rectification_cases
|
|
set last_activity_at = pg_catalog.now(),
|
|
updated_at = pg_catalog.now()
|
|
where id = p_case_id;
|
|
|
|
return jsonb_build_object(
|
|
'evidence_id', v_evidence_id,
|
|
'idempotent', false,
|
|
'outcome', 'accepted',
|
|
'error_code', null,
|
|
'status', 'draft'
|
|
);
|
|
end;
|
|
$$;
|
|
|
|
create or replace function public.confirm_agentic_rectification_evidence_v10(
|
|
p_user_id uuid,
|
|
p_case_id uuid,
|
|
p_focus_id uuid,
|
|
p_evidence_id uuid
|
|
)
|
|
returns jsonb
|
|
language plpgsql
|
|
security definer
|
|
set search_path = ''
|
|
as $$
|
|
declare
|
|
v_case public.agentic_rectification_cases%rowtype;
|
|
v_focus public.agentic_rectification_conversation_focuses%rowtype;
|
|
v_result jsonb;
|
|
begin
|
|
if p_user_id is null or p_case_id is null or p_evidence_id is null then
|
|
raise exception 'agentic_rectification_invalid_input' using errcode = 'P0001';
|
|
end if;
|
|
select * into v_case
|
|
from public.agentic_rectification_cases
|
|
where id = p_case_id and user_id = p_user_id
|
|
for update;
|
|
if not found then
|
|
raise exception 'agentic_rectification_case_not_found' using errcode = 'P0001';
|
|
end if;
|
|
if v_case.status in ('confirmed', 'closed', 'abandoned', 'superseded') then
|
|
raise exception 'agentic_rectification_case_terminal' using errcode = 'P0001';
|
|
end if;
|
|
if not exists (
|
|
select 1 from public.agentic_rectification_evidence
|
|
where id = p_evidence_id and case_id = p_case_id
|
|
) then
|
|
raise exception 'agentic_rectification_evidence_not_found' using errcode = 'P0001';
|
|
end if;
|
|
|
|
if p_focus_id is null then
|
|
v_result := public.confirm_agentic_rectification_evidence(
|
|
p_user_id, p_case_id, p_evidence_id
|
|
);
|
|
return v_result || jsonb_build_object('focus_id', null);
|
|
end if;
|
|
|
|
select * into v_focus
|
|
from public.agentic_rectification_conversation_focuses
|
|
where id = p_focus_id and case_id = p_case_id
|
|
for update;
|
|
if not found then
|
|
raise exception 'agentic_rectification_focus_not_found' using errcode = 'P0001';
|
|
end if;
|
|
if v_focus.target_evidence_id is not null
|
|
and v_focus.target_evidence_id <> p_evidence_id then
|
|
raise exception 'agentic_rectification_focus_target_mismatch' using errcode = 'P0001';
|
|
end if;
|
|
if v_focus.status not in ('active', 'resolved') then
|
|
raise exception 'agentic_rectification_focus_not_active' using errcode = 'P0001';
|
|
end if;
|
|
if v_focus.status = 'resolved'
|
|
and v_focus.target_evidence_id is distinct from p_evidence_id then
|
|
raise exception 'agentic_rectification_focus_target_mismatch' using errcode = 'P0001';
|
|
end if;
|
|
|
|
v_result := public.confirm_agentic_rectification_evidence(
|
|
p_user_id, p_case_id, p_evidence_id
|
|
);
|
|
if v_focus.status = 'active' and v_focus.target_evidence_id is not null then
|
|
perform public.resolve_agentic_rectification_conversation_focus(
|
|
p_user_id, p_case_id, p_focus_id, 'resolved', p_evidence_id
|
|
);
|
|
end if;
|
|
return v_result || jsonb_build_object('focus_id', p_focus_id);
|
|
end;
|
|
$$;
|
|
|
|
create or replace function public.revise_agentic_rectification_evidence(
|
|
p_user_id uuid,
|
|
p_case_id uuid,
|
|
p_evidence_id uuid,
|
|
p_user_quote text,
|
|
p_occurred_from date,
|
|
p_occurred_to date,
|
|
p_date_precision text,
|
|
p_summary text
|
|
)
|
|
returns jsonb
|
|
language plpgsql
|
|
security definer
|
|
set search_path = ''
|
|
as $$
|
|
declare
|
|
v_case public.agentic_rectification_cases%rowtype;
|
|
v_target public.agentic_rectification_evidence%rowtype;
|
|
v_new_id uuid;
|
|
v_normalized text;
|
|
v_has_day boolean;
|
|
v_has_month boolean;
|
|
v_has_year boolean;
|
|
v_allows_coarser boolean;
|
|
begin
|
|
if p_user_id is null or p_case_id is null or p_evidence_id is null
|
|
or length(btrim(coalesce(p_user_quote, ''))) = 0
|
|
or length(btrim(coalesce(p_summary, ''))) = 0
|
|
or p_date_precision is null
|
|
or not (p_date_precision = any (public.agentic_rectification_date_precisions())) then
|
|
raise exception 'agentic_rectification_invalid_input' using errcode = 'P0001';
|
|
end if;
|
|
select * into v_case
|
|
from public.agentic_rectification_cases
|
|
where id = p_case_id and user_id = p_user_id;
|
|
if not found then
|
|
raise exception 'agentic_rectification_case_not_found' using errcode = 'P0001';
|
|
end if;
|
|
if v_case.status in ('confirmed', 'closed', 'abandoned', 'superseded') then
|
|
raise exception 'agentic_rectification_case_terminal' using errcode = 'P0001';
|
|
end if;
|
|
|
|
select * into v_target
|
|
from public.agentic_rectification_evidence
|
|
where id = p_evidence_id and case_id = p_case_id;
|
|
if not found then
|
|
raise exception 'agentic_rectification_evidence_not_found' using errcode = 'P0001';
|
|
end if;
|
|
if v_target.status not in ('confirmed', 'pending_confirmation') then
|
|
raise exception 'agentic_rectification_evidence_not_revisable' using errcode = 'P0001';
|
|
end if;
|
|
|
|
v_normalized := public.agentic_rectification_normalize_quote(p_user_quote);
|
|
if public.agentic_rectification_precision_rank(p_date_precision)
|
|
< public.agentic_rectification_precision_rank(v_target.date_precision) then
|
|
v_has_day := v_normalized ~ '[0-9]{1,2}月[0-9]{1,2}[日号]'
|
|
or coalesce(p_user_quote, '') ~ '[0-9]{4}-[0-9]{2}-[0-9]{2}';
|
|
v_has_month := v_normalized ~ '[0-9]{1,2}月'
|
|
or coalesce(p_user_quote, '') ~ '[0-9]{4}-[0-9]{2}';
|
|
v_has_year := v_normalized ~ '[0-9]{4}年'
|
|
or coalesce(p_user_quote, '') ~ '[0-9]{4}';
|
|
v_allows_coarser := case p_date_precision
|
|
when 'year' then v_has_year and not v_has_month and not v_has_day
|
|
when 'month' then v_has_month and not v_has_day
|
|
when 'quarter' then v_has_month and not v_has_day
|
|
when 'unknown' then v_normalized ~ '(不记得|记不清|不明|不知道)'
|
|
else false
|
|
end;
|
|
if not v_allows_coarser then
|
|
raise exception 'agentic_rectification_precision_downgrade' using errcode = 'P0001';
|
|
end if;
|
|
end if;
|
|
|
|
update public.agentic_rectification_evidence
|
|
set status = 'superseded',
|
|
updated_at = pg_catalog.now()
|
|
where id = v_target.id;
|
|
|
|
insert into public.agentic_rectification_evidence (
|
|
case_id, source_turn_id, user_quote, subject, event_kind, domain,
|
|
occurred_from, occurred_to, date_precision, summary, status, supersedes_evidence_id
|
|
) values (
|
|
v_target.case_id, v_target.source_turn_id, p_user_quote, v_target.subject,
|
|
v_target.event_kind, v_target.domain,
|
|
p_occurred_from, p_occurred_to, p_date_precision, p_summary,
|
|
'pending_confirmation', v_target.id
|
|
) returning id into v_new_id;
|
|
|
|
update public.agentic_rectification_cases
|
|
set last_activity_at = pg_catalog.now(),
|
|
updated_at = pg_catalog.now()
|
|
where id = p_case_id;
|
|
|
|
return jsonb_build_object(
|
|
'evidence_id', v_new_id,
|
|
'supersedes_evidence_id', v_target.id,
|
|
'idempotent', false
|
|
);
|
|
end;
|
|
$$;
|
|
|
|
create or replace function public.record_agentic_rectification_evidence_batch(
|
|
p_user_id uuid,
|
|
p_case_id uuid,
|
|
p_source_turn_id uuid,
|
|
p_focus_id uuid,
|
|
p_items jsonb
|
|
)
|
|
returns jsonb
|
|
language plpgsql
|
|
security definer
|
|
set search_path = ''
|
|
as $$
|
|
declare
|
|
v_case public.agentic_rectification_cases%rowtype;
|
|
v_turn public.agentic_rectification_turns%rowtype;
|
|
v_focus public.agentic_rectification_conversation_focuses%rowtype;
|
|
v_focus_replay boolean := false;
|
|
v_focus_match_id uuid;
|
|
v_focus_match_count integer := 0;
|
|
v_focus_resolution text := 'not_requested';
|
|
v_item jsonb;
|
|
v_index bigint;
|
|
v_item_key text;
|
|
v_quote text;
|
|
v_subject text;
|
|
v_kind text;
|
|
v_domain text;
|
|
v_precision text;
|
|
v_summary text;
|
|
v_from date;
|
|
v_to date;
|
|
v_status text;
|
|
v_outcome text;
|
|
v_error text;
|
|
v_clarification jsonb;
|
|
v_existing public.agentic_rectification_evidence%rowtype;
|
|
v_evidence_id uuid;
|
|
v_idempotent boolean;
|
|
v_results jsonb := '[]'::jsonb;
|
|
v_accepted integer := 0;
|
|
v_needs integer := 0;
|
|
v_rejected integer := 0;
|
|
begin
|
|
if p_user_id is null or p_case_id is null or p_source_turn_id is null
|
|
or p_items is null or jsonb_typeof(p_items) <> 'array'
|
|
or jsonb_array_length(p_items) = 0 or jsonb_array_length(p_items) > 12 then
|
|
raise exception 'agentic_rectification_invalid_input' using errcode = 'P0001';
|
|
end if;
|
|
|
|
select * into v_case
|
|
from public.agentic_rectification_cases
|
|
where id = p_case_id and user_id = p_user_id
|
|
for update;
|
|
if not found then
|
|
raise exception 'agentic_rectification_case_not_found' using errcode = 'P0001';
|
|
end if;
|
|
if v_case.status in ('confirmed', 'closed', 'abandoned', 'superseded') then
|
|
raise exception 'agentic_rectification_case_terminal' using errcode = 'P0001';
|
|
end if;
|
|
|
|
select * into v_turn
|
|
from public.agentic_rectification_turns
|
|
where id = p_source_turn_id and case_id = p_case_id;
|
|
if not found then
|
|
raise exception 'agentic_rectification_turn_not_found' using errcode = 'P0001';
|
|
end if;
|
|
if v_turn.user_message is null then
|
|
raise exception 'agentic_rectification_quote_not_grounded' using errcode = 'P0001';
|
|
end if;
|
|
|
|
if p_focus_id is not null then
|
|
select * into v_focus
|
|
from public.agentic_rectification_conversation_focuses
|
|
where id = p_focus_id and case_id = p_case_id
|
|
for update;
|
|
if not found or v_focus.status not in ('active', 'resolved') then
|
|
raise exception 'agentic_rectification_focus_not_active' using errcode = 'P0001';
|
|
end if;
|
|
v_focus_replay := v_focus.status = 'resolved';
|
|
if not v_focus_replay and v_focus.target_evidence_id is not null then
|
|
raise exception 'agentic_rectification_focus_target_mismatch' using errcode = 'P0001';
|
|
end if;
|
|
v_focus_resolution := case when v_focus_replay then 'idempotent' else 'active' end;
|
|
if v_focus_replay and exists (
|
|
select 1
|
|
from jsonb_array_elements(p_items) item
|
|
where length(btrim(coalesce(item->>'idempotency_key', ''))) = 0
|
|
or not exists (
|
|
select 1 from public.agentic_rectification_evidence e
|
|
where e.case_id = p_case_id
|
|
and e.idempotency_key = btrim(item->>'idempotency_key')
|
|
)
|
|
) then
|
|
raise exception 'agentic_rectification_focus_not_active' using errcode = 'P0001';
|
|
end if;
|
|
end if;
|
|
|
|
for v_item, v_index in
|
|
select value, ordinality
|
|
from jsonb_array_elements(p_items) with ordinality
|
|
loop
|
|
v_item_key := btrim(coalesce(v_item->>'idempotency_key', ''));
|
|
v_quote := btrim(coalesce(v_item->>'quote', ''));
|
|
v_subject := coalesce(v_item->>'subject', '');
|
|
v_kind := coalesce(v_item->>'event_kind', '');
|
|
v_domain := coalesce(v_item->>'domain', '');
|
|
v_precision := coalesce(v_item->>'date_precision', '');
|
|
v_summary := btrim(coalesce(v_item->>'summary', ''));
|
|
v_from := null;
|
|
v_to := null;
|
|
v_error := null;
|
|
v_clarification := '[]'::jsonb;
|
|
v_evidence_id := null;
|
|
v_idempotent := false;
|
|
|
|
begin
|
|
if nullif(v_item->>'occurred_from', '') is not null then
|
|
v_from := (v_item->>'occurred_from')::date;
|
|
end if;
|
|
if nullif(v_item->>'occurred_to', '') is not null then
|
|
v_to := (v_item->>'occurred_to')::date;
|
|
end if;
|
|
exception when others then
|
|
v_error := 'invalid_date';
|
|
end;
|
|
|
|
if v_error is null and (
|
|
length(v_item_key) = 0 or length(v_item_key) > 160
|
|
or length(v_quote) = 0 or length(v_summary) = 0
|
|
or v_subject not in ('self', 'family', 'other')
|
|
or not (v_kind = any (public.agentic_rectification_evidence_kinds()))
|
|
or not (v_domain = any (public.agentic_rectification_evidence_domains()))
|
|
or not (v_precision = any (public.agentic_rectification_date_precisions()))
|
|
) then
|
|
v_error := 'invalid_item';
|
|
end if;
|
|
|
|
if v_error is null and position(
|
|
public.agentic_rectification_normalize_quote(v_quote)
|
|
in public.agentic_rectification_normalize_quote(v_turn.user_message)
|
|
) = 0 then
|
|
v_error := 'quote_not_grounded';
|
|
end if;
|
|
|
|
if v_error is null and v_precision = 'range'
|
|
and (v_from is null or v_to is null or v_from > v_to) then
|
|
v_error := 'invalid_range';
|
|
end if;
|
|
|
|
if v_error is not null then
|
|
v_outcome := 'rejected';
|
|
v_status := 'rejected';
|
|
v_rejected := v_rejected + 1;
|
|
else
|
|
select * into v_existing
|
|
from public.agentic_rectification_evidence
|
|
where case_id = p_case_id and idempotency_key = v_item_key;
|
|
|
|
if found then
|
|
if v_existing.source_turn_id is distinct from p_source_turn_id
|
|
or v_existing.user_quote is distinct from v_quote
|
|
or v_existing.subject is distinct from v_subject
|
|
or v_existing.event_kind is distinct from v_kind
|
|
or v_existing.domain is distinct from v_domain
|
|
or v_existing.occurred_from is distinct from v_from
|
|
or v_existing.occurred_to is distinct from v_to
|
|
or v_existing.date_precision is distinct from v_precision
|
|
or v_existing.summary is distinct from v_summary then
|
|
v_outcome := 'rejected';
|
|
v_status := 'rejected';
|
|
v_error := 'idempotency_conflict';
|
|
v_rejected := v_rejected + 1;
|
|
else
|
|
v_evidence_id := v_existing.id;
|
|
v_idempotent := true;
|
|
if v_existing.status = 'confirmed' then
|
|
v_outcome := 'accepted';
|
|
v_status := 'confirmed';
|
|
v_accepted := v_accepted + 1;
|
|
else
|
|
v_outcome := 'needs_clarification';
|
|
v_status := v_existing.status;
|
|
v_clarification := case
|
|
when v_existing.date_precision = 'unknown' then '["date"]'::jsonb
|
|
else '[]'::jsonb
|
|
end;
|
|
v_needs := v_needs + 1;
|
|
end if;
|
|
end if;
|
|
else
|
|
if v_precision = 'unknown' or v_from is null then
|
|
v_outcome := 'needs_clarification';
|
|
v_status := 'draft';
|
|
v_clarification := '["date"]'::jsonb;
|
|
v_needs := v_needs + 1;
|
|
else
|
|
v_outcome := 'accepted';
|
|
v_status := 'confirmed';
|
|
v_accepted := v_accepted + 1;
|
|
end if;
|
|
|
|
insert into public.agentic_rectification_evidence (
|
|
case_id, source_turn_id, user_quote, subject, event_kind, domain,
|
|
occurred_from, occurred_to, date_precision, summary, status,
|
|
confirmed_at, idempotency_key
|
|
) values (
|
|
p_case_id, p_source_turn_id, v_quote, v_subject, v_kind, v_domain,
|
|
v_from, v_to, v_precision, v_summary, v_status,
|
|
case when v_status = 'confirmed' then pg_catalog.now() else null end,
|
|
v_item_key
|
|
) returning id into v_evidence_id;
|
|
end if;
|
|
end if;
|
|
|
|
if p_focus_id is not null
|
|
and v_outcome = 'accepted'
|
|
and v_evidence_id is not null
|
|
and (v_focus.target_domain is null or v_domain = v_focus.target_domain)
|
|
and (v_focus.target_kind is null or v_kind = v_focus.target_kind) then
|
|
v_focus_match_count := v_focus_match_count + 1;
|
|
if v_focus_match_id is null then
|
|
v_focus_match_id := v_evidence_id;
|
|
end if;
|
|
end if;
|
|
|
|
v_results := v_results || jsonb_build_array(jsonb_build_object(
|
|
'index', v_index - 1,
|
|
'idempotency_key', nullif(v_item_key, ''),
|
|
'outcome', v_outcome,
|
|
'evidence_id', v_evidence_id,
|
|
'status', v_status,
|
|
'idempotent', v_idempotent,
|
|
'clarification_fields', v_clarification,
|
|
'error_code', v_error
|
|
));
|
|
end loop;
|
|
|
|
update public.agentic_rectification_cases
|
|
set status = case when status = 'draft' and v_accepted > 0 then 'collecting_evidence' else status end,
|
|
last_activity_at = pg_catalog.now(),
|
|
updated_at = pg_catalog.now()
|
|
where id = p_case_id;
|
|
|
|
if p_focus_id is not null then
|
|
if v_focus_replay then
|
|
if v_focus_match_count <> 1
|
|
or v_focus_match_id is distinct from v_focus.target_evidence_id then
|
|
raise exception 'agentic_rectification_focus_idempotency_conflict' using errcode = 'P0001';
|
|
end if;
|
|
v_focus_resolution := 'idempotent';
|
|
elsif v_focus_match_count = 1 then
|
|
perform public.resolve_agentic_rectification_conversation_focus(
|
|
p_user_id, p_case_id, p_focus_id, 'resolved', v_focus_match_id
|
|
);
|
|
v_focus_resolution := 'resolved';
|
|
elsif v_focus_match_count > 1 then
|
|
v_focus_resolution := 'ambiguous';
|
|
else
|
|
v_focus_resolution := 'unmatched';
|
|
end if;
|
|
end if;
|
|
|
|
return jsonb_build_object(
|
|
'items', v_results,
|
|
'accepted_count', v_accepted,
|
|
'needs_clarification_count', v_needs,
|
|
'rejected_count', v_rejected,
|
|
'focus_id', p_focus_id,
|
|
'focus_evidence_id', v_focus_match_id,
|
|
'focus_resolution', v_focus_resolution
|
|
);
|
|
end;
|
|
$$;
|
|
|
|
commit;
|