Align DICOM UPP registration uniqueness
This commit is contained in:
@@ -198,9 +198,95 @@ def ensure_registration_table() -> None:
|
||||
updated_by text NOT NULL DEFAULT 'admin',
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (ct_number, series_instance_uid, algorithm_model, stl_family)
|
||||
UNIQUE (ct_number, algorithm_model)
|
||||
);
|
||||
ALTER TABLE public.{REGISTRATION_TABLE_SQL}
|
||||
ADD COLUMN IF NOT EXISTS series_instance_uid text NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS series_description text NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS algorithm_model text NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS stl_family text NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS view_mode text NOT NULL DEFAULT 'family',
|
||||
ADD COLUMN IF NOT EXISTS selected_stl_files jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS transform jsonb NOT NULL DEFAULT '{{}}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS dicom_reference jsonb NOT NULL DEFAULT '{{}}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS model_reference jsonb NOT NULL DEFAULT '{{}}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS segmentation jsonb NOT NULL DEFAULT '{{}}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS notes text NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS locked boolean NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS locked_by text,
|
||||
ADD COLUMN IF NOT EXISTS locked_at timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS updated_by text NOT NULL DEFAULT 'admin',
|
||||
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(),
|
||||
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now();
|
||||
UPDATE public.{REGISTRATION_TABLE_SQL}
|
||||
SET
|
||||
ct_number = upper(regexp_replace(COALESCE(ct_number, ''), '\\s+', '', 'g')),
|
||||
algorithm_model = COALESCE(NULLIF(btrim(algorithm_model), ''), '未指定模型'),
|
||||
stl_family = COALESCE(NULLIF(btrim(stl_family), ''), '全部STL')
|
||||
WHERE ct_number <> upper(regexp_replace(COALESCE(ct_number, ''), '\\s+', '', 'g'))
|
||||
OR algorithm_model = ''
|
||||
OR stl_family = '';
|
||||
CREATE TABLE IF NOT EXISTS public.{REGISTRATION_TABLE_SQL}_duplicate_archive (
|
||||
archived_at timestamptz NOT NULL DEFAULT now(),
|
||||
reason text NOT NULL,
|
||||
row_data jsonb NOT NULL
|
||||
);
|
||||
WITH ranked AS (
|
||||
SELECT
|
||||
id,
|
||||
row_number() OVER (
|
||||
PARTITION BY ct_number, algorithm_model
|
||||
ORDER BY locked DESC, updated_at DESC NULLS LAST, id DESC
|
||||
) AS rn
|
||||
FROM public.{REGISTRATION_TABLE_SQL}
|
||||
),
|
||||
duplicates AS (
|
||||
SELECT t.*
|
||||
FROM public.{REGISTRATION_TABLE_SQL} t
|
||||
JOIN ranked r ON r.id = t.id
|
||||
WHERE r.rn > 1
|
||||
)
|
||||
INSERT INTO public.{REGISTRATION_TABLE_SQL}_duplicate_archive(reason, row_data)
|
||||
SELECT 'ct_algorithm_unique_migration', row_to_json(duplicates)::jsonb
|
||||
FROM duplicates;
|
||||
WITH ranked AS (
|
||||
SELECT
|
||||
id,
|
||||
row_number() OVER (
|
||||
PARTITION BY ct_number, algorithm_model
|
||||
ORDER BY locked DESC, updated_at DESC NULLS LAST, id DESC
|
||||
) AS rn
|
||||
FROM public.{REGISTRATION_TABLE_SQL}
|
||||
)
|
||||
DELETE FROM public.{REGISTRATION_TABLE_SQL} t
|
||||
USING ranked r
|
||||
WHERE t.id = r.id AND r.rn > 1;
|
||||
DO $$
|
||||
DECLARE
|
||||
old_constraint record;
|
||||
BEGIN
|
||||
FOR old_constraint IN
|
||||
SELECT conname
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'public.{REGISTRATION_TABLE_SQL}'::regclass
|
||||
AND contype = 'u'
|
||||
AND pg_get_constraintdef(oid) LIKE '%(ct_number, series_instance_uid, algorithm_model, stl_family)%'
|
||||
LOOP
|
||||
EXECUTE format('ALTER TABLE public.{REGISTRATION_TABLE_SQL} DROP CONSTRAINT %I', old_constraint.conname);
|
||||
END LOOP;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'public.{REGISTRATION_TABLE_SQL}'::regclass
|
||||
AND contype = 'u'
|
||||
AND conname = '{REGISTRATION_TABLE_SQL}_ct_algorithm_key'
|
||||
) THEN
|
||||
ALTER TABLE public.{REGISTRATION_TABLE_SQL}
|
||||
ADD CONSTRAINT {REGISTRATION_TABLE_SQL}_ct_algorithm_key UNIQUE (ct_number, algorithm_model);
|
||||
END IF;
|
||||
END $$;
|
||||
CREATE INDEX IF NOT EXISTS idx_{REGISTRATION_TABLE_SQL}_ct ON public.{REGISTRATION_TABLE_SQL}(ct_number);
|
||||
CREATE INDEX IF NOT EXISTS idx_{REGISTRATION_TABLE_SQL}_algorithm ON public.{REGISTRATION_TABLE_SQL}(algorithm_model);
|
||||
CREATE INDEX IF NOT EXISTS idx_{REGISTRATION_TABLE_SQL}_locked ON public.{REGISTRATION_TABLE_SQL}(locked);
|
||||
""",
|
||||
timeout=12,
|
||||
@@ -852,11 +938,14 @@ def stl_rows_for_ct(ct_number: str) -> list[dict[str, Any]]:
|
||||
SELECT
|
||||
COALESCE(u.algorithm_model, '未指定模型') AS algorithm_model,
|
||||
u.processed_stl_dir,
|
||||
COALESCE(s.files, u.stl_files, '[]'::jsonb) AS files
|
||||
CASE
|
||||
WHEN jsonb_typeof(u.stl_files) = 'array' AND jsonb_array_length(u.stl_files) > 0 THEN u.stl_files
|
||||
ELSE COALESCE(s.files, '[]'::jsonb)
|
||||
END AS files
|
||||
FROM public.{UPP_ASSET_TABLE_SQL} u
|
||||
LEFT JOIN public.{UPP_STL_TABLE_SQL} s ON upper(s.ct_number) = upper(u.ct_number)
|
||||
WHERE upper(u.ct_number) = {sql_literal(normalize_ct(ct_number))}
|
||||
ORDER BY u.updated_at DESC NULLS LAST
|
||||
ORDER BY COALESCE(u.algorithm_model, '未指定模型'), u.updated_at DESC NULLS LAST
|
||||
""",
|
||||
timeout=14,
|
||||
)
|
||||
@@ -950,9 +1039,7 @@ def save_registration(payload: RegistrationPayload, user: str = Depends(require_
|
||||
SELECT locked
|
||||
FROM public.{REGISTRATION_TABLE_SQL}
|
||||
WHERE ct_number = {sql_literal(ct_number)}
|
||||
AND series_instance_uid = {sql_literal(series_uid)}
|
||||
AND algorithm_model = {sql_literal(algorithm_model)}
|
||||
AND stl_family = {sql_literal(stl_family)}
|
||||
LIMIT 1
|
||||
""",
|
||||
timeout=8,
|
||||
@@ -989,8 +1076,10 @@ def save_registration(payload: RegistrationPayload, user: str = Depends(require_
|
||||
{sql_literal(user)},
|
||||
now()
|
||||
)
|
||||
ON CONFLICT (ct_number, series_instance_uid, algorithm_model, stl_family) DO UPDATE SET
|
||||
ON CONFLICT (ct_number, algorithm_model) DO UPDATE SET
|
||||
series_instance_uid = EXCLUDED.series_instance_uid,
|
||||
series_description = EXCLUDED.series_description,
|
||||
stl_family = EXCLUDED.stl_family,
|
||||
view_mode = EXCLUDED.view_mode,
|
||||
selected_stl_files = EXCLUDED.selected_stl_files,
|
||||
transform = EXCLUDED.transform,
|
||||
|
||||
Reference in New Issue
Block a user