Commit f4f7a212 by luoqi

perf(verify-recall): 扫描器改集合式(unnest 进索引临时表)— 全量秒级(原逐行相关子查询 13万患者卡死)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 98575181
-- ============================================================
-- 召回正确性扫描器(treatment_initiation_recall · 只读回归)
-- 召回正确性扫描器(treatment_initiation_recall · 只读回归)· 集合式(全量可跑)
--
-- 用途:独立于 scenario 代码,用 plan_reasons.signals 反查 patient_facts 交叉验证
-- "该召没召 / 不该召却召"。每次摄入/改算法后跑,看 FP/FN 体量。
-- 独立性:下方 codes VALUES 重述一份 code→{resolver/cooldown/⑤d科目/全口/乳牙},
-- 独立性:下方 vt_codes 重述一份 code→{resolver/cooldown/⑤d科目/全口/乳牙},
-- 跟 scenario 代码各写一份 → 不一致即暴露(差分)。
-- 性能:把 治疗/召回/诊断/预约 牙位各 unnest 一次进【索引临时表】+ ANALYZE,
-- 再用索引查找做 EXISTS/JOIN。13 万患者秒级(旧逐行相关子查询版会 O(n²) 卡死)。
-- 运行:
-- 本地 docker exec -i pac-postgres psql -U pac -d pac -f - < apps/pac-service/sql/verify-recall.sql
-- 服务器 docker exec -i pac-postgres-1 psql -U pac -d pac -f - < apps/pac-service/sql/verify-recall.sql
-- 章节:§A 规模 / §B FP 硬闸(应0) / §C 牙位交叉表+FP / §C3-C4 FN 逐层解释 /
-- §D 全口码治疗后复发被压 / §E K08 正畸语境 FP
-- 注:§C/§D 的"残留"需再排 cooldown / 已指派锁 / ⑤d 才是真问题(见 docs 讨论)。
-- 只读;tx_final 用 LATEST 诊断做时间基准(修复发误判),并标全部合法排除层。
-- ============================================================
\pset pager off
\set ON_ERROR_STOP on
SET work_mem = '256MB';
CREATE TEMP TABLE tx_final AS
WITH
-- code 配置:resolver(已治家族,宽)+ cooldown + ⑤d 相关预约科目文本 + 是否全口/乳牙剔除
codes(code,sub,cooldown,resolver,complaints,whole,drop_decid) AS (VALUES
('K08','missing_tooth',30, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric'], ARRAY['种植','修复'], false, true),
('K02','caries_no_filling',14, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric'], ARRAY[]::text[], false, false),
('K03','hard_tissue_damage',14, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric'], ARRAY['修复','拔牙'], false, false),
('K04','endo_no_rct',14, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric'], ARRAY[]::text[], false, false),
('K01','impacted_tooth',14, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric'], ARRAY['拔牙'], false, false),
('K09','jaw_cyst',14, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric'], ARRAY['拔牙'], false, false),
('K00','development_eruption',30, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric','orthodontic'], ARRAY['拔牙','修复','种植','正畸','早矫'], false, false),
('K06','gum_alveolar_lesion',14, ARRAY['periodontic','surgical'], ARRAY['牙周','拔牙'], false, false)),
diag AS (
SELECT d.patient_id,c.code,c.sub,c.resolver,c.complaints,c.drop_decid,c.cooldown,tk AS tooth,d.occurred_at AS dx_at,d.content->>'name_zh' AS name_zh
FROM patient_facts d JOIN codes c ON c.code=d.content->>'code'
JOIN patients p ON p.id=d.patient_id JOIN patient_profiles pp ON pp.patient_id=d.patient_id
CROSS JOIN LATERAL unnest(string_to_array(regexp_replace(coalesce(d.content->>'tooth_position',''),'[^0-9;]+',';','g'),';')) tk
WHERE d.type='diagnosis_record' AND d.status='active' AND p.active AND NOT pp.do_not_contact AND NOT pp.deceased AND tk ~ '^[0-9]{2}$'),
g AS ( -- 每(患者×code×牙):取 LATEST 诊断(修复发),过 cooldown
SELECT patient_id,code,sub,tooth,min(resolver) resolver,min(complaints) complaints,bool_or(drop_decid) drop_decid,
max(dx_at) dx_at, string_agg(DISTINCT name_zh,'/') name_zh, min(cooldown) cooldown
FROM diag GROUP BY patient_id,code,sub,tooth
HAVING max(dx_at) <= now()-(min(cooldown)||' days')::interval)
SELECT g.patient_id,g.code,g.sub,g.tooth,g.dx_at,g.name_zh,
EXISTS(SELECT 1 FROM patient_facts t CROSS JOIN LATERAL unnest(string_to_array(regexp_replace(coalesce(t.content->>'tooth_position',''),'[^0-9;]+',';','g'),';')) tt
WHERE t.patient_id=g.patient_id AND t.type='treatment_record' AND t.kind='actual' AND t.status IN('active','fulfilled')
AND t.content->>'category'=ANY(g.resolver) AND tt=g.tooth AND t.occurred_at>=g.dx_at) AS resolved,
EXISTS(SELECT 1 FROM plan_reasons pr JOIN followup_plans fp ON fp.id=pr.plan_id
CROSS JOIN LATERAL unnest(string_to_array(regexp_replace(coalesce(pr.signals->>'toothPosition',''),'[^0-9;]+',';','g'),';')) rt
WHERE fp.patient_id=g.patient_id AND fp.status IN('active','assigned') AND pr.signals->>'subKey'=g.sub AND rt=g.tooth) AS recalled,
-- 合法排除层
EXISTS(SELECT 1 FROM patient_facts a WHERE a.patient_id=g.patient_id AND a.type='appointment_record' AND a.status='active' AND COALESCE(a.planned_for,a.occurred_at)>now()) AS x_future_appt,
EXISTS(SELECT 1 FROM patient_facts v WHERE v.patient_id=g.patient_id AND v.type IN('encounter_record','emr_record') AND v.occurred_at>now()-interval '14 days') AS x_recent_visit,
(cardinality(g.complaints)>0 AND EXISTS(SELECT 1 FROM patient_facts a WHERE a.patient_id=g.patient_id AND a.type='appointment_record'
AND a.status IN('active','fulfilled') AND COALESCE(a.planned_for,a.occurred_at)>=g.dx_at
AND EXISTS(SELECT 1 FROM unnest(string_to_array(coalesce(a.content->>'complaint_category',''),',')) c WHERE trim(c)=ANY(g.complaints)))) AS x_appt_complaint,
(g.name_zh IN ('废用牙','无功能牙')) AS x_ineligible_name,
(g.drop_decid AND g.tooth ~ '^[5-8][1-5]$') AS x_deciduous
FROM g;
-- 牙位级 code 配置(K05/K07 全口码不在牙位交叉表,单列 §D)
CREATE TEMP TABLE vt_codes(code text, sub text, cooldown int, resolver text[], complaints text[], drop_decid bool);
INSERT INTO vt_codes VALUES
('K08','missing_tooth',30, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric'], ARRAY['种植','修复'], true),
('K02','caries_no_filling',14, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric'], ARRAY[]::text[], false),
('K03','hard_tissue_damage',14, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric'], ARRAY['修复','拔牙'], false),
('K04','endo_no_rct',14, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric'], ARRAY[]::text[], false),
('K01','impacted_tooth',14, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric'], ARRAY['拔牙'], false),
('K09','jaw_cyst',14, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric'], ARRAY['拔牙'], false),
('K00','development_eruption',30, ARRAY['implant','prosthodontic','restorative','endodontic','surgical','cosmetic','pediatric','orthodontic'], ARRAY['拔牙','修复','种植','正畸','早矫'], false),
('K06','gum_alveolar_lesion',14, ARRAY['periodontic','surgical'], ARRAY['牙周','拔牙'], false);
\echo '════════ §A 规模 ════════'
-- ── 牙位 unnest 一次 → 索引临时表 ──
CREATE TEMP TABLE vt_treat AS
SELECT t.patient_id, t.content->>'category' AS cat, tk AS tooth, t.occurred_at AS tx_at
FROM patient_facts t
CROSS JOIN LATERAL unnest(string_to_array(regexp_replace(coalesce(t.content->>'tooth_position',''),'[^0-9;]+',';','g'),';')) tk
WHERE t.type='treatment_record' AND t.kind='actual' AND t.status IN('active','fulfilled') AND tk ~ '^[0-9]{2}$';
CREATE INDEX ON vt_treat(patient_id, tooth); ANALYZE vt_treat;
CREATE TEMP TABLE vt_recall AS
SELECT fp.patient_id, pr.signals->>'subKey' AS sub, tk AS tooth
FROM plan_reasons pr JOIN followup_plans fp ON fp.id=pr.plan_id
CROSS JOIN LATERAL unnest(string_to_array(regexp_replace(coalesce(pr.signals->>'toothPosition',''),'[^0-9;]+',';','g'),';')) tk
WHERE fp.status IN('active','assigned') AND pr.scenario='treatment_initiation_recall' AND tk ~ '^[0-9]{2}$';
CREATE INDEX ON vt_recall(patient_id, sub, tooth); ANALYZE vt_recall;
-- 预约科目(⑤d):patient × 中文 complaint × 时间
CREATE TEMP TABLE vt_appt AS
SELECT a.patient_id, trim(c) AS compl, COALESCE(a.planned_for,a.occurred_at) AS appt_at
FROM patient_facts a
CROSS JOIN LATERAL unnest(string_to_array(coalesce(a.content->>'complaint_category',''),',')) c
WHERE a.type='appointment_record' AND a.status IN('active','fulfilled') AND trim(c)<>'';
CREATE INDEX ON vt_appt(patient_id, compl); ANALYZE vt_appt;
-- 患者级标记:未来预约(⑤b)/ 近期到诊(⑤f)
CREATE TEMP TABLE vt_future AS SELECT DISTINCT patient_id FROM patient_facts
WHERE type='appointment_record' AND status='active' AND COALESCE(planned_for,occurred_at)>now();
CREATE INDEX ON vt_future(patient_id); ANALYZE vt_future;
CREATE TEMP TABLE vt_recent AS SELECT DISTINCT patient_id FROM patient_facts
WHERE type IN('encounter_record','emr_record') AND occurred_at>now()-interval '14 days';
CREATE INDEX ON vt_recent(patient_id); ANALYZE vt_recent;
-- 结构码 dx/rec 牙位(§C4 取代规则:同牙更晚不同结构码 → 取代)
CREATE TEMP TABLE vt_struct AS
SELECT ld.patient_id, ld.content->>'code' AS code, tk AS tooth, COALESCE(ld.occurred_at,ld.planned_for) AS at
FROM patient_facts ld
CROSS JOIN LATERAL unnest(string_to_array(regexp_replace(coalesce(ld.content->>'tooth_position',''),'[^0-9;]+',';','g'),';')) tk
WHERE ld.status='active' AND ld.type IN('diagnosis_record','recommendation_record') AND tk ~ '^[0-9]{2}$'
AND ld.content->>'code' IN ('K00','K01','K02','K03','K04','K08','K09','IMPLANT_RECOMMENDED','CROWN_RECOMMENDED','FILLING_RECOMMENDED','EXTRACTION_RECOMMENDED','RCT_RECOMMENDED','HARD_TISSUE_REPAIR_RECOMMENDED','ERUPTION_INTERVENTION_RECOMMENDED','JAW_CYST_REMOVAL_RECOMMENDED');
CREATE INDEX ON vt_struct(patient_id, tooth); ANALYZE vt_struct;
-- ── 诊断牙(宇宙):合规 + 过 cooldown,取 LATEST 诊断 ──
CREATE TEMP TABLE vt_diag AS
SELECT d.patient_id, c.code, c.sub, c.resolver, c.complaints, c.drop_decid,
tk AS tooth, max(d.occurred_at) AS dx_at, string_agg(DISTINCT d.content->>'name_zh','/') AS name_zh, min(c.cooldown) AS cooldown
FROM patient_facts d
JOIN vt_codes c ON c.code = d.content->>'code'
JOIN patients p ON p.id=d.patient_id JOIN patient_profiles pp ON pp.patient_id=d.patient_id
CROSS JOIN LATERAL unnest(string_to_array(regexp_replace(coalesce(d.content->>'tooth_position',''),'[^0-9;]+',';','g'),';')) tk
WHERE d.type='diagnosis_record' AND d.status='active' AND p.active AND NOT pp.do_not_contact AND NOT pp.deceased AND tk ~ '^[0-9]{2}$'
GROUP BY d.patient_id, c.code, c.sub, c.resolver, c.complaints, c.drop_decid, tk;
DELETE FROM vt_diag WHERE dx_at > now() - (cooldown||' days')::interval;
CREATE INDEX ON vt_diag(patient_id, tooth); ANALYZE vt_diag;
-- ── 分类(EXISTS 全打索引临时表,秒级)──
CREATE TEMP TABLE vt_final AS
SELECT d.patient_id, d.code, d.sub, d.tooth, d.dx_at, d.name_zh,
EXISTS(SELECT 1 FROM vt_treat t WHERE t.patient_id=d.patient_id AND t.tooth=d.tooth AND t.cat=ANY(d.resolver) AND t.tx_at>=d.dx_at) AS resolved,
EXISTS(SELECT 1 FROM vt_recall r WHERE r.patient_id=d.patient_id AND r.sub=d.sub AND r.tooth=d.tooth) AS recalled,
(d.patient_id IN (SELECT patient_id FROM vt_future)) AS x_future,
(d.patient_id IN (SELECT patient_id FROM vt_recent)) AS x_recent,
(cardinality(d.complaints)>0 AND EXISTS(SELECT 1 FROM vt_appt a WHERE a.patient_id=d.patient_id AND a.compl=ANY(d.complaints) AND a.appt_at>=d.dx_at)) AS x_appt,
(d.name_zh IN('废用牙','无功能牙')) AS x_inelig,
(d.drop_decid AND d.tooth ~ '^[5-8][1-5]$') AS x_decid,
EXISTS(SELECT 1 FROM vt_struct s WHERE s.patient_id=d.patient_id AND s.tooth=d.tooth AND s.code<>d.code AND s.at>=d.dx_at) AS x_superseded
FROM vt_diag d;
ANALYZE vt_final;
\echo '════════ §A 规模(诊断牙:合规+过cooldown)════════'
SELECT count(*) AS teeth, count(DISTINCT patient_id) AS patients,
count(*) FILTER (WHERE recalled) AS recalled_teeth,
count(*) FILTER (WHERE resolved) AS resolved_teeth FROM tx_final;
count(*) FILTER (WHERE recalled) AS recalled, count(*) FILTER (WHERE resolved) AS resolved FROM vt_final;
\echo '════════ §B FP 硬闸(应=0)════════'
SELECT
(SELECT count(*) FROM plan_reasons pr JOIN followup_plans fp ON fp.id=pr.plan_id JOIN patients p ON p.id=fp.patient_id JOIN patient_profiles pp ON pp.patient_id=fp.patient_id
WHERE fp.status IN('active','assigned') AND (p.active=false OR pp.do_not_contact OR pp.deceased)) AS fp_compliance,
(SELECT count(*) FROM plan_reasons pr JOIN followup_plans fp ON fp.id=pr.plan_id
WHERE fp.status IN('active','assigned') AND pr.scenario='treatment_initiation_recall'
AND EXISTS(SELECT 1 FROM patient_facts a WHERE a.patient_id=fp.patient_id AND a.type='appointment_record' AND a.status='active' AND COALESCE(a.planned_for,a.occurred_at)>now())) AS fp_future_appt;
(SELECT count(*) FROM plan_reasons pr JOIN followup_plans fp ON fp.id=pr.plan_id JOIN patients p ON p.id=fp.patient_id JOIN patient_profiles pp ON pp.patient_id=fp.patient_id
WHERE fp.status IN('active','assigned') AND pr.scenario='treatment_initiation_recall' AND (p.active=false OR pp.do_not_contact OR pp.deceased)) AS fp_compliance,
(SELECT count(*) FROM plan_reasons pr JOIN followup_plans fp ON fp.id=pr.plan_id
WHERE fp.status IN('active','assigned') AND pr.scenario='treatment_initiation_recall'
AND EXISTS(SELECT 1 FROM vt_future f WHERE f.patient_id=fp.patient_id)) AS fp_future_appt;
\echo '════════ §C 牙位交叉表(LATEST 诊断基准)════════'
\echo '════════ §C 牙位交叉表(resolved × recalled)════════'
SELECT resolved, recalled, count(*) AS teeth,
CASE WHEN NOT resolved AND recalled THEN '✓ 未治→召'
WHEN resolved AND NOT recalled THEN '✓ 已治→不召'
WHEN resolved AND recalled THEN '✗ FP 已治却召'
ELSE '? FN 未治却不召' END AS verdict
FROM tx_final GROUP BY 1,2 ORDER BY 1,2;
CASE WHEN NOT resolved AND recalled THEN '✓ 未治→召' WHEN resolved AND NOT recalled THEN '✓ 已治→不召'
WHEN resolved AND recalled THEN '✗ FP 已治却召' ELSE '? FN 未治却不召' END AS verdict
FROM vt_final GROUP BY 1,2 ORDER BY 1,2;
\echo '════════ §C2 FP(已治却召)真量 + 样例 ════════'
SELECT count(*) AS fp_teeth FROM tx_final WHERE resolved AND recalled;
SELECT substr(patient_id::text,1,8) pid, code, tooth, dx_at::date, name_zh FROM tx_final WHERE resolved AND recalled LIMIT 12;
\echo '════════ §C2 FP(已治却召)样例 ════════'
SELECT substr(patient_id::text,1,8) pid, code, tooth, dx_at::date, name_zh FROM vt_final WHERE resolved AND recalled LIMIT 15;
\echo '════════ §C3 FN 逐层解释 → 真·无法解释 ════════'
SELECT count(*) AS fn_total,
count(*) FILTER (WHERE x_future_appt) AS e_future_appt,
count(*) FILTER (WHERE NOT x_future_appt AND x_recent_visit) AS e_recent_visit,
count(*) FILTER (WHERE NOT x_future_appt AND NOT x_recent_visit AND x_appt_complaint) AS e_appt_complaint,
count(*) FILTER (WHERE NOT x_future_appt AND NOT x_recent_visit AND NOT x_appt_complaint AND (x_ineligible_name OR x_deciduous)) AS e_ineligible_decid,
count(*) FILTER (WHERE NOT x_future_appt AND NOT x_recent_visit AND NOT x_appt_complaint AND NOT x_ineligible_name AND NOT x_deciduous) AS truly_unexplained
FROM tx_final WHERE NOT resolved AND NOT recalled;
\echo '---- 真·无法解释 FN 样例(按 code)----'
SELECT code, substr(patient_id::text,1,8) pid, tooth, dx_at::date, name_zh FROM tx_final
WHERE NOT resolved AND NOT recalled AND NOT x_future_appt AND NOT x_recent_visit AND NOT x_appt_complaint AND NOT x_ineligible_name AND NOT x_deciduous
count(*) FILTER (WHERE x_future) AS e_future_appt,
count(*) FILTER (WHERE NOT x_future AND x_recent) AS e_recent_visit,
count(*) FILTER (WHERE NOT x_future AND NOT x_recent AND x_appt) AS e_appt_complaint,
count(*) FILTER (WHERE NOT x_future AND NOT x_recent AND NOT x_appt AND (x_inelig OR x_decid)) AS e_inelig_decid,
count(*) FILTER (WHERE NOT x_future AND NOT x_recent AND NOT x_appt AND NOT x_inelig AND NOT x_decid AND x_superseded) AS e_superseded,
count(*) FILTER (WHERE NOT x_future AND NOT x_recent AND NOT x_appt AND NOT x_inelig AND NOT x_decid AND NOT x_superseded) AS truly_unexplained
FROM vt_final WHERE NOT resolved AND NOT recalled;
\echo '---- 真·无法解释 FN 样例 ----'
SELECT code, substr(patient_id::text,1,8) pid, tooth, dx_at::date, name_zh FROM vt_final
WHERE NOT resolved AND NOT recalled AND NOT x_future AND NOT x_recent AND NOT x_appt AND NOT x_inelig AND NOT x_decid AND NOT x_superseded
ORDER BY code LIMIT 30;
\echo '════════ §C4 真·无法解释 FN 里被"同牙更晚结构诊断/建议取代"(branch b/c,我未建模)解释的 ════════'
SELECT count(*) AS superseded
FROM tx_final f
WHERE NOT f.resolved AND NOT f.recalled AND NOT f.x_future_appt AND NOT f.x_recent_visit AND NOT f.x_appt_complaint AND NOT f.x_ineligible_name AND NOT f.x_deciduous
AND EXISTS (SELECT 1 FROM patient_facts ld
CROSS JOIN LATERAL unnest(string_to_array(regexp_replace(coalesce(ld.content->>'tooth_position',''),'[^0-9;]+',';','g'),';')) lt
WHERE ld.patient_id=f.patient_id AND ld.status='active' AND ld.type IN('diagnosis_record','recommendation_record')
AND ld.content->>'code' IN ('K00','K01','K02','K03','K04','K08','K09','IMPLANT_RECOMMENDED','CROWN_RECOMMENDED','FILLING_RECOMMENDED','EXTRACTION_RECOMMENDED','RCT_RECOMMENDED','HARD_TISSUE_REPAIR_RECOMMENDED','ERUPTION_INTERVENTION_RECOMMENDED','JAW_CYST_REMOVAL_RECOMMENDED')
AND ld.content->>'code' <> f.code AND lt=f.tooth AND COALESCE(ld.occurred_at,ld.planned_for) >= f.dx_at);
\echo '════════ §D ⭐ 全口码 治疗后又被诊断 却被压(excludeIfEverTreated 缺陷)════════'
WITH wm(code,sub,tx_cat) AS (VALUES ('K05','perio_no_srp','periodontic'),('K07','ortho_no_consult','orthodontic')),
pt AS (
SELECT wm.code,wm.sub,d.patient_id, max(d.occurred_at) AS latest_dx,
(SELECT max(t.occurred_at) FROM patient_facts t WHERE t.patient_id=d.patient_id AND t.type='treatment_record'
AND t.kind='actual' AND t.status IN('active','fulfilled') AND t.content->>'category'=wm.tx_cat) AS latest_tx
FROM patient_facts d JOIN wm ON wm.code=d.content->>'code'
JOIN patients p ON p.id=d.patient_id JOIN patient_profiles pp ON pp.patient_id=d.patient_id
WHERE d.type='diagnosis_record' AND d.status='active' AND p.active AND NOT pp.do_not_contact AND NOT pp.deceased
GROUP BY wm.code,wm.sub,wm.tx_cat,d.patient_id)
SELECT code,
count(*) FILTER (WHERE latest_tx IS NOT NULL) AS treated_pts,
count(*) FILTER (WHERE latest_tx IS NOT NULL AND latest_dx>latest_tx) AS new_dx_after_tx,
count(*) FILTER (WHERE latest_tx IS NOT NULL AND latest_dx>latest_tx
AND NOT EXISTS(SELECT 1 FROM plan_reasons pr JOIN followup_plans fp ON fp.id=pr.plan_id
WHERE fp.patient_id=pt.patient_id AND fp.status IN('active','assigned') AND pr.signals->>'subKey'=pt.sub)) AS suppressed_not_recalled,
count(*) FILTER (WHERE latest_tx IS NOT NULL AND latest_dx>latest_tx
AND NOT EXISTS(SELECT 1 FROM plan_reasons pr JOIN followup_plans fp ON fp.id=pr.plan_id
WHERE fp.patient_id=pt.patient_id AND fp.status IN('active','assigned') AND pr.signals->>'subKey'=pt.sub)
AND NOT EXISTS(SELECT 1 FROM patient_facts a WHERE a.patient_id=pt.patient_id AND a.type='appointment_record' AND a.status='active' AND COALESCE(a.planned_for,a.occurred_at)>now())
AND NOT EXISTS(SELECT 1 FROM patient_facts v WHERE v.patient_id=pt.patient_id AND v.type IN('encounter_record','emr_record') AND v.occurred_at>now()-interval '14 days')) AS suppressed_clean
FROM pt GROUP BY code;
\echo '════════ §D 全口码 治疗后复发被压(excludeIfEverTreated)════════'
WITH tx AS (SELECT patient_id, content->>'category' cat, max(occurred_at) last_tx FROM patient_facts
WHERE type='treatment_record' AND kind='actual' AND status IN('active','fulfilled') AND content->>'category' IN('periodontic','orthodontic') GROUP BY 1,2),
dx AS (SELECT patient_id, content->>'code' code, max(occurred_at) last_dx FROM patient_facts
WHERE type='diagnosis_record' AND status='active' AND content->>'code' IN('K05','K07') GROUP BY 1,2),
rec AS (SELECT fp.patient_id, pr.signals->>'subKey' sub FROM plan_reasons pr JOIN followup_plans fp ON fp.id=pr.plan_id
WHERE fp.status IN('active','assigned') AND pr.signals->>'subKey' IN('perio_no_srp','ortho_no_consult') GROUP BY 1,2)
SELECT d.code,
count(*) FILTER (WHERE t.last_tx IS NOT NULL) AS treated_pts,
count(*) FILTER (WHERE t.last_tx IS NOT NULL AND d.last_dx>t.last_tx) AS recurrence,
count(*) FILTER (WHERE t.last_tx IS NOT NULL AND d.last_dx>t.last_tx AND r.patient_id IS NOT NULL) AS recurrence_recalled
FROM dx d JOIN patients p ON p.id=d.patient_id JOIN patient_profiles pp ON pp.patient_id=d.patient_id
LEFT JOIN tx t ON t.patient_id=d.patient_id AND t.cat=CASE d.code WHEN 'K05' THEN 'periodontic' ELSE 'orthodontic' END
LEFT JOIN rec r ON r.patient_id=d.patient_id AND r.sub=CASE d.code WHEN 'K05' THEN 'perio_no_srp' ELSE 'ortho_no_consult' END
WHERE p.active AND NOT pp.do_not_contact AND NOT pp.deceased
GROUP BY d.code;
\echo '════════ §E K08 正畸语境 FP(缺牙被召种植,但患者在做/做过正畸)════════'
SELECT count(DISTINCT t.patient_id) AS k08_ortho_ctx_patients FROM tx_final t
WHERE t.code='K08' AND t.recalled
AND (EXISTS(SELECT 1 FROM patient_facts d WHERE d.patient_id=t.patient_id AND d.type='diagnosis_record' AND d.status='active' AND d.content->>'code'='K07')
OR EXISTS(SELECT 1 FROM patient_facts x WHERE x.patient_id=t.patient_id AND x.type='treatment_record' AND x.content->>'category'='orthodontic'));
\echo '════════ §E K08 正畸语境 FP 上界(召缺牙→种植 且 有K07诊断/正畸治疗)════════'
CREATE TEMP TABLE vt_ortho_pt AS
SELECT DISTINCT patient_id FROM patient_facts WHERE status='active' AND type='diagnosis_record' AND content->>'code'='K07'
UNION SELECT DISTINCT patient_id FROM patient_facts WHERE type='treatment_record' AND content->>'category'='orthodontic';
CREATE INDEX ON vt_ortho_pt(patient_id); ANALYZE vt_ortho_pt;
SELECT count(DISTINCT f.patient_id) AS k08_ortho_ctx_patients
FROM vt_final f WHERE f.code='K08' AND f.recalled AND f.patient_id IN (SELECT patient_id FROM vt_ortho_pt);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment