155 lines
6.4 KiB
PL/PgSQL
155 lines
6.4 KiB
PL/PgSQL
begin;
|
|
|
|
-- Global birthplace intake stores provider identity and IANA timezone metadata
|
|
-- in the immutable rectification declaration. Keep the database validator in
|
|
-- lockstep with the strict TypeScript persistence contract so valid non-China
|
|
-- profiles are not surfaced as conversational_action_conflict.
|
|
create or replace function public.conversational_rectification_valid_declared_birth_input(
|
|
p_value jsonb
|
|
)
|
|
returns boolean
|
|
language plpgsql
|
|
immutable
|
|
strict
|
|
set search_path = ''
|
|
as $$
|
|
declare
|
|
v_place jsonb;
|
|
v_source text;
|
|
v_key text;
|
|
v_before integer;
|
|
v_after integer;
|
|
begin
|
|
if pg_catalog.jsonb_typeof(p_value) is distinct from 'object'
|
|
or pg_catalog.octet_length(p_value::text) > 12000
|
|
or public.conversational_rectification_numbers_are_stable(p_value) is not true
|
|
or not public.conversational_rectification_has_only_keys(
|
|
p_value,
|
|
array[
|
|
'birthDate', 'source', 'birthTimeClue', 'birthplace', 'reportedTime',
|
|
'reportedPeriod', 'uncertaintyBeforeMinutes', 'uncertaintyAfterMinutes'
|
|
]::text[]
|
|
)
|
|
or not (p_value ?& array['birthDate', 'source', 'birthTimeClue', 'birthplace']::text[])
|
|
or pg_catalog.jsonb_typeof(p_value -> 'birthDate') is distinct from 'string'
|
|
or not public.conversational_rectification_valid_date_text(p_value ->> 'birthDate')
|
|
or pg_catalog.jsonb_typeof(p_value -> 'source') is distinct from 'string'
|
|
or public.conversational_rectification_text_is_nonblank(
|
|
p_value ->> 'source'
|
|
) is not true
|
|
or p_value -> 'birthTimeClue' <> 'null'::jsonb and (
|
|
pg_catalog.jsonb_typeof(p_value -> 'birthTimeClue') is distinct from 'string'
|
|
or public.conversational_rectification_text_utf16_length(
|
|
p_value ->> 'birthTimeClue'
|
|
) not between 1 and 240
|
|
or public.conversational_rectification_text_is_nonblank(
|
|
p_value ->> 'birthTimeClue'
|
|
) is not true
|
|
) then return false; end if;
|
|
|
|
v_place := p_value -> 'birthplace';
|
|
if pg_catalog.jsonb_typeof(v_place) is distinct from 'object'
|
|
or pg_catalog.octet_length(v_place::text) > 4096
|
|
or not public.conversational_rectification_has_only_keys(
|
|
v_place,
|
|
array[
|
|
'city', 'placeId', 'placeType', 'provider', 'countryCode',
|
|
'provinceCode', 'cityCode', 'districtCode', 'latitude', 'longitude',
|
|
'timezoneId', 'timezoneSource', 'timezoneOffset'
|
|
]::text[]
|
|
)
|
|
or not (v_place ? 'timezoneOffset')
|
|
or not ((v_place ? 'city') or (v_place ? 'cityCode') or (v_place ? 'placeId'))
|
|
or pg_catalog.jsonb_typeof(v_place -> 'timezoneOffset') is distinct from 'number'
|
|
or (v_place ->> 'timezoneOffset')::numeric not between -12 and 14
|
|
or (v_place ? 'latitude') <> (v_place ? 'longitude') then return false; end if;
|
|
foreach v_key in array array[
|
|
'city', 'placeId', 'placeType', 'provider', 'provinceCode', 'cityCode',
|
|
'districtCode', 'timezoneId', 'timezoneSource'
|
|
]::text[] loop
|
|
if v_place ? v_key and (
|
|
pg_catalog.jsonb_typeof(v_place -> v_key) is distinct from 'string'
|
|
or public.conversational_rectification_text_utf16_length(
|
|
v_place ->> v_key
|
|
) not between 1 and case
|
|
when v_key = 'city' then 120
|
|
when v_key = 'placeId' then 240
|
|
when v_key = 'timezoneId' then 120
|
|
else 80
|
|
end
|
|
or public.conversational_rectification_text_is_nonblank(
|
|
v_place ->> v_key
|
|
) is not true
|
|
) then return false; end if;
|
|
end loop;
|
|
if v_place ? 'countryCode' and (
|
|
pg_catalog.jsonb_typeof(v_place -> 'countryCode') is distinct from 'string'
|
|
or v_place ->> 'countryCode' !~ '^[A-Z0-9-]{1,8}$'
|
|
) then return false; end if;
|
|
if v_place ? 'latitude' and (
|
|
pg_catalog.jsonb_typeof(v_place -> 'latitude') is distinct from 'number'
|
|
or pg_catalog.jsonb_typeof(v_place -> 'longitude') is distinct from 'number'
|
|
or (v_place ->> 'latitude')::numeric not between -90 and 90
|
|
or (v_place ->> 'longitude')::numeric not between -180 and 180
|
|
) then return false; end if;
|
|
|
|
v_source := p_value ->> 'source';
|
|
if v_source not in (
|
|
'hospital_record', 'family_exact', 'approximate', 'period_only', 'unknown', 'legacy_import'
|
|
) then return false; end if;
|
|
if p_value ? 'reportedTime' and (
|
|
pg_catalog.jsonb_typeof(p_value -> 'reportedTime') is distinct from 'string'
|
|
or not public.conversational_rectification_valid_time_text(p_value ->> 'reportedTime')
|
|
) then return false; end if;
|
|
if p_value ? 'reportedPeriod' and (
|
|
pg_catalog.jsonb_typeof(p_value -> 'reportedPeriod') is distinct from 'string'
|
|
or p_value ->> 'reportedPeriod' not in (
|
|
'early_morning', 'morning', 'afternoon', 'evening', 'late_night'
|
|
)
|
|
) then return false; end if;
|
|
if p_value ? 'uncertaintyBeforeMinutes' then
|
|
if pg_catalog.jsonb_typeof(p_value -> 'uncertaintyBeforeMinutes') is distinct from 'number'
|
|
or pg_catalog.jsonb_typeof(p_value -> 'uncertaintyAfterMinutes') is distinct from 'number'
|
|
or p_value ->> 'uncertaintyBeforeMinutes' !~ '^[0-9]+$'
|
|
or p_value ->> 'uncertaintyAfterMinutes' !~ '^[0-9]+$' then
|
|
return false;
|
|
end if;
|
|
v_before := (p_value ->> 'uncertaintyBeforeMinutes')::integer;
|
|
v_after := (p_value ->> 'uncertaintyAfterMinutes')::integer;
|
|
elsif p_value ? 'uncertaintyAfterMinutes' then
|
|
return false;
|
|
end if;
|
|
|
|
if v_source = 'hospital_record' then
|
|
return p_value ? 'reportedTime'
|
|
and not (p_value ? 'reportedPeriod')
|
|
and v_before = 2 and v_after = 2;
|
|
elsif v_source = 'family_exact' then
|
|
return p_value ? 'reportedTime'
|
|
and not (p_value ? 'reportedPeriod')
|
|
and v_before = v_after and v_before in (5, 10, 15);
|
|
elsif v_source = 'approximate' then
|
|
return p_value ? 'reportedTime'
|
|
and not (p_value ? 'reportedPeriod')
|
|
and v_before = v_after and v_before in (15, 30, 60);
|
|
elsif v_source = 'period_only' then
|
|
return p_value ? 'reportedPeriod'
|
|
and not (p_value ? 'reportedTime')
|
|
and not (p_value ? 'uncertaintyBeforeMinutes')
|
|
and not (p_value ? 'uncertaintyAfterMinutes');
|
|
elsif v_source = 'unknown' then
|
|
return not (p_value ? 'reportedPeriod')
|
|
and not (p_value ? 'reportedTime')
|
|
and not (p_value ? 'uncertaintyBeforeMinutes')
|
|
and not (p_value ? 'uncertaintyAfterMinutes');
|
|
end if;
|
|
return not ((p_value ? 'reportedTime') and (p_value ? 'reportedPeriod'))
|
|
and ((p_value ? 'reportedTime') or v_before is null)
|
|
and (v_before is null or (v_before between 0 and 720 and v_after between 0 and 720));
|
|
exception when others then
|
|
return false;
|
|
end;
|
|
$$;
|
|
|
|
commit;
|