CREATE TABLE IF NOT EXISTS upp_exam_assets ( ct_number text NOT NULL PRIMARY KEY, list_present boolean NOT NULL DEFAULT false, stl_present boolean NOT NULL DEFAULT false, patient_name text, patient_sex text, patient_age text, patient_id_masked text, exam_date timestamptz, task_created_at timestamptz, exam_description text, exam_device text, algorithm_model text, upp_status text, list_record_count integer NOT NULL DEFAULT 0, selected_list_record jsonb, list_records jsonb NOT NULL DEFAULT '[]'::jsonb, selected_source_case_dir text, selected_source_stl_dir text, processed_stl_dir text, stl_case_name text, stl_sequence_no integer, stl_file_count integer NOT NULL DEFAULT 0, stl_total_bytes bigint NOT NULL DEFAULT 0, stl_files jsonb NOT NULL DEFAULT '[]'::jsonb, stl_candidates jsonb NOT NULL DEFAULT '[]'::jsonb, updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT ck_upp_exam_assets_ct_number_present CHECK (btrim(ct_number) <> ''), CONSTRAINT ck_upp_exam_assets_ct_number_format CHECK (ct_number ~ '^D?CT[0-9]{8,}$') ); DO $$ BEGIN IF to_regclass('public.upp_stl_files') IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'upp_stl_files' AND column_name = 'file_names' ) THEN CREATE TABLE IF NOT EXISTS upp_stl_files_row_detail_backup AS TABLE upp_stl_files; DROP TABLE upp_stl_files; END IF; END $$; CREATE TABLE IF NOT EXISTS upp_stl_files ( ct_number text NOT NULL PRIMARY KEY REFERENCES upp_exam_assets(ct_number) ON DELETE CASCADE, file_count integer NOT NULL DEFAULT 0, total_bytes bigint NOT NULL DEFAULT 0, segment_names jsonb NOT NULL DEFAULT '[]'::jsonb, segment_families jsonb NOT NULL DEFAULT '[]'::jsonb, segment_categories jsonb NOT NULL DEFAULT '[]'::jsonb, file_names jsonb NOT NULL DEFAULT '[]'::jsonb, source_file_paths jsonb NOT NULL DEFAULT '[]'::jsonb, processed_file_paths jsonb NOT NULL DEFAULT '[]'::jsonb, files jsonb NOT NULL DEFAULT '[]'::jsonb, updated_at timestamptz NOT NULL DEFAULT now() ); ALTER TABLE upp_exam_assets ALTER COLUMN ct_number SET NOT NULL; ALTER TABLE upp_stl_files ALTER COLUMN ct_number SET NOT NULL; ALTER TABLE upp_stl_files ADD COLUMN IF NOT EXISTS segment_families jsonb NOT NULL DEFAULT '[]'::jsonb; ALTER TABLE upp_stl_files ADD COLUMN IF NOT EXISTS segment_categories jsonb NOT NULL DEFAULT '[]'::jsonb; CREATE INDEX IF NOT EXISTS idx_upp_exam_assets_list_present ON upp_exam_assets(list_present); CREATE INDEX IF NOT EXISTS idx_upp_exam_assets_stl_present ON upp_exam_assets(stl_present); CREATE INDEX IF NOT EXISTS idx_upp_exam_assets_patient_name ON upp_exam_assets(patient_name); CREATE INDEX IF NOT EXISTS idx_upp_exam_assets_exam_date ON upp_exam_assets(exam_date); CREATE INDEX IF NOT EXISTS idx_upp_exam_assets_list_records_gin ON upp_exam_assets USING gin (list_records); CREATE INDEX IF NOT EXISTS idx_upp_exam_assets_stl_files_gin ON upp_exam_assets USING gin (stl_files); CREATE INDEX IF NOT EXISTS idx_upp_stl_files_segment_names_gin ON upp_stl_files USING gin (segment_names); CREATE INDEX IF NOT EXISTS idx_upp_stl_files_segment_families_gin ON upp_stl_files USING gin (segment_families); CREATE INDEX IF NOT EXISTS idx_upp_stl_files_segment_categories_gin ON upp_stl_files USING gin (segment_categories); CREATE INDEX IF NOT EXISTS idx_upp_stl_files_file_names_gin ON upp_stl_files USING gin (file_names); CREATE INDEX IF NOT EXISTS idx_upp_stl_files_files_gin ON upp_stl_files USING gin (files); COMMENT ON TABLE upp_exam_assets IS 'UPP列表、STL重建结果、未来CT数据的CT号唯一资产索引'; COMMENT ON COLUMN upp_exam_assets.ct_number IS 'CT检查号,唯一索引键'; COMMENT ON COLUMN upp_exam_assets.list_records IS '来自UPP列表OCR的同CT号全部记录'; COMMENT ON COLUMN upp_exam_assets.selected_list_record IS '同CT号列表记录中按任务创建时间/检查时间选择的新记录'; COMMENT ON COLUMN upp_exam_assets.stl_candidates IS '同CT号全部候选STL目录,保留去重选择依据'; COMMENT ON COLUMN upp_exam_assets.stl_files IS '最终选中STL目录的文件清单'; COMMENT ON COLUMN upp_exam_assets.processed_stl_dir IS '规范化后的STL目录,目录名为CT号'; COMMENT ON TABLE upp_stl_files IS 'UPP最终选中STL文件聚合表,每个CT号唯一一行'; COMMENT ON COLUMN upp_stl_files.ct_number IS 'CT检查号,主键并关联upp_exam_assets.ct_number'; COMMENT ON COLUMN upp_stl_files.segment_names IS '最终选中STL文件的分割名称JSON数组'; COMMENT ON COLUMN upp_stl_files.segment_families IS '与segment_names同顺序的训练family JSON数组'; COMMENT ON COLUMN upp_stl_files.segment_categories IS '与segment_names同顺序的粗分类JSON数组'; COMMENT ON COLUMN upp_stl_files.file_names IS '最终选中STL文件名JSON数组'; COMMENT ON COLUMN upp_stl_files.files IS '最终选中STL文件完整明细JSON数组';