test_sample_storage_cert_qc.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. """储存证质检信息:类型键、payload 与自动补空。"""
  2. import unittest
  3. from datetime import datetime
  4. from types import SimpleNamespace
  5. from app.crud.sample_storage_cert_qc import _row_to_dict
  6. from app.services.sample_storage_cert_qc import (
  7. SAMPLE_TYPE_KEY_PERINATAL_HSC,
  8. SAMPLE_TYPE_KEY_UC_MSC,
  9. empty_detail,
  10. normalize_payload,
  11. resolve_sample_type_key,
  12. supports_storage_cert_qc,
  13. )
  14. from app.services.sample_storage_cert_qc_prefill import (
  15. batch_kind_from_name,
  16. build_suggested,
  17. match_cd34_param,
  18. merge_prefill,
  19. qual_yin_yang,
  20. sum_cryo_volume,
  21. )
  22. class TestResolveSampleTypeKey(unittest.TestCase):
  23. def test_hsc(self):
  24. self.assertEqual(resolve_sample_type_key("围产期造血干细胞"), SAMPLE_TYPE_KEY_PERINATAL_HSC)
  25. def test_msc(self):
  26. self.assertEqual(resolve_sample_type_key("脐带间充质干细胞"), SAMPLE_TYPE_KEY_UC_MSC)
  27. def test_other(self):
  28. self.assertIsNone(resolve_sample_type_key("胎盘亚全能干细胞"))
  29. self.assertIsNone(resolve_sample_type_key(""))
  30. def test_supports(self):
  31. self.assertTrue(supports_storage_cert_qc("围产期造血干细胞"))
  32. self.assertFalse(supports_storage_cert_qc("其他"))
  33. class TestNormalizePayload(unittest.TestCase):
  34. def test_rejects_non_object(self):
  35. with self.assertRaises(ValueError):
  36. normalize_payload([])
  37. with self.assertRaises(ValueError):
  38. normalize_payload("x")
  39. def test_keeps_dict_stringifies_keys(self):
  40. out = normalize_payload({"viability_pct": 90.4, 1: "bad"})
  41. self.assertEqual(out["viability_pct"], 90.4)
  42. self.assertNotIn(1, out)
  43. class TestEmptyDetail(unittest.TestCase):
  44. def test_shape(self):
  45. d = empty_detail(
  46. sample_code="B1",
  47. lab_flow_code="LAB-001",
  48. protocol_code="P1",
  49. mother_name="张三",
  50. sample_type_name="围产期造血干细胞",
  51. sample_type_key="perinatal_hsc",
  52. )
  53. self.assertFalse(d["filledFlag"])
  54. self.assertEqual(d["payload"], {})
  55. self.assertEqual(d["sampleCode"], "B1")
  56. self.assertEqual(d["labFlowCode"], "LAB-001")
  57. def test_lab_flow_defaults_empty(self):
  58. d = empty_detail(sample_code="B1")
  59. self.assertEqual(d["labFlowCode"], "")
  60. class TestRowToDict(unittest.TestCase):
  61. def test_filled(self):
  62. row = SimpleNamespace(
  63. id="1",
  64. sample_code="B1",
  65. sample_type_key="perinatal_hsc",
  66. collection_protocol_id="P",
  67. qc_work_order_id="W",
  68. filled_flag="1",
  69. payload='{"viability_pct":"90"}',
  70. create_time=None,
  71. update_time=None,
  72. )
  73. d = _row_to_dict(
  74. row,
  75. lab_flow_code="LAB-001",
  76. protocol_code="C1",
  77. mother_name="李",
  78. sample_type_name="围产期造血干细胞",
  79. )
  80. self.assertTrue(d["filledFlag"])
  81. self.assertEqual(d["payload"]["viability_pct"], "90")
  82. self.assertEqual(d["sampleCode"], "B1")
  83. self.assertEqual(d["labFlowCode"], "LAB-001")
  84. self.assertEqual(d["protocolCode"], "C1")
  85. def _task(item_name, stamp, params, batch_kind=""):
  86. values = [value for _name, value in params if str(value).strip()]
  87. return {
  88. "item_name": item_name,
  89. "stamp": stamp,
  90. "params": params,
  91. "values": values,
  92. "batch_kind": batch_kind,
  93. }
  94. class TestStorageCertPrefill(unittest.TestCase):
  95. def test_cd34_param_names(self):
  96. self.assertEqual(match_cd34_param("有核细胞数TNC/*108(个)"), "tnc_1e8")
  97. self.assertEqual(match_cd34_param("CD34+细胞数/*106(个)"), "cd34_count_1e6")
  98. self.assertEqual(match_cd34_param('CD34+ CD45dim 细胞数/*105(个)'), "cd34_cd45dim_1e5")
  99. def test_volume_needs_both_numbers(self):
  100. self.assertEqual(sum_cryo_volume(10.5, 2), "12.5")
  101. self.assertEqual(sum_cryo_volume(10, None), "")
  102. self.assertEqual(sum_cryo_volume(None, 2), "")
  103. def test_qual_only_explicit_yin_yang(self):
  104. self.assertEqual(qual_yin_yang(["阴性"]), "阴")
  105. self.assertEqual(qual_yin_yang(["通过"]), "")
  106. self.assertEqual(qual_yin_yang(["阴", "阳"]), "")
  107. self.assertEqual(qual_yin_yang(["未检出"]), "")
  108. def test_hsc_fills_known_values_and_skips_unconfirmed(self):
  109. older = datetime(2026, 1, 1, 8, 0)
  110. newer = datetime(2026, 2, 2, 9, 0)
  111. suggested = build_suggested(
  112. sample_type_key="perinatal_hsc",
  113. collection_time=datetime(2026, 1, 1, 13, 45),
  114. reception_time=datetime(2026, 1, 2, 9, 0),
  115. enriched_blood_weight_g=20,
  116. cryopreservation_volume_ml=9.1,
  117. gate_tasks=[
  118. _task(
  119. "CD34+细胞流式",
  120. older,
  121. [("有核细胞数TNC/*108(个)", "1.1"), ("CD34+细胞数/*106(个)", "2.2")],
  122. ),
  123. _task(
  124. "CD34+细胞流式",
  125. newer,
  126. [
  127. ("有核细胞数TNC/*10E8", "8.27"),
  128. ("CD34+细胞数/*106(个)", "55.5"),
  129. ("CD34+ CD45dim 细胞数/*105(个)", "1.14"),
  130. ],
  131. ),
  132. ],
  133. latest_freeze_date=datetime(2026, 3, 3, 16, 0),
  134. )
  135. self.assertEqual(suggested["collection_time"], "2026-01-01 13:45")
  136. self.assertEqual(suggested["receive_time"], "2026-01-02 09:00")
  137. self.assertEqual(suggested["cryo_volume_ml"], "29.1")
  138. self.assertEqual(suggested["tnc_1e8"], "8.27")
  139. self.assertEqual(suggested["cd34_count_1e6"], "55.5")
  140. self.assertEqual(suggested["cd34_cd45dim_1e5"], "1.14")
  141. self.assertEqual(suggested["storage_date"], "2026-03-03")
  142. self.assertEqual(suggested["storage_years"], "20年")
  143. self.assertEqual(suggested["bacteria_test"], "阴性")
  144. self.assertEqual(suggested["result_comment"], "合格")
  145. self.assertNotIn("hbsag", suggested)
  146. self.assertNotIn("mother_hbsag", suggested)
  147. self.assertNotIn("viability_pct", suggested)
  148. self.assertNotIn("hla_typing", suggested)
  149. self.assertNotIn("report_issue_date", suggested)
  150. def test_hsc_serology_reads_batch_item_param(self):
  151. self.assertEqual(batch_kind_from_name("B0000075-脐血"), "脐血")
  152. self.assertEqual(batch_kind_from_name("B0000075-母血"), "母血")
  153. stamp = datetime(2026, 7, 4, 9, 0)
  154. suggested = build_suggested(
  155. sample_type_key="perinatal_hsc",
  156. gate_tasks=[
  157. _task(
  158. "乙肝五项定性",
  159. stamp,
  160. [
  161. ("乙型肝炎病毒表面抗原", "阴性"),
  162. ("抗乙型肝炎病毒表面抗体", "阳性"),
  163. ],
  164. "脐血",
  165. ),
  166. _task("抗丙型肝炎病毒抗体", stamp, [("抗丙型肝炎病毒抗体", "阳性")], "脐血"),
  167. _task("抗梅毒螺旋体特异抗体", stamp, [("抗梅毒螺旋体特异抗体", "阴性")], "脐血"),
  168. _task("抗人类免疫缺陷病毒抗体", stamp, [("抗人类免疫缺陷病毒抗体", "阴性")], "脐血"),
  169. _task("乙肝五项定性", stamp, [("乙型肝炎病毒表面抗原", "阳性")], "母血"),
  170. _task("乙型肝炎病毒DNA", stamp, [("乙型肝炎病毒DNA", "阴性")], "母血"),
  171. _task(
  172. "人类免疫缺陷病毒I型核酸(HIV-1 RNA)定量,实时荧光定量PCR",
  173. stamp,
  174. [],
  175. "母血",
  176. ),
  177. _task("丙型肝炎病毒RNA", stamp, [("丙型肝炎病毒RNA", "通过")], "母血"),
  178. ],
  179. )
  180. self.assertEqual(suggested["hbsag"], "阴性")
  181. self.assertEqual(suggested["anti_hcv"], "阳性")
  182. self.assertEqual(suggested["anti_tp"], "阴性")
  183. self.assertEqual(suggested["anti_hiv"], "阴性")
  184. self.assertNotIn("cmv_igm", suggested)
  185. self.assertEqual(suggested["mother_hbsag"], "阳性")
  186. self.assertEqual(suggested["mother_hbv_dna"], "阴性")
  187. self.assertNotIn("mother_hiv1_rna", suggested)
  188. self.assertNotIn("mother_hcv_rna", suggested)
  189. self.assertEqual(suggested["bacteria_test"], "阴性")
  190. self.assertNotIn("viability_pct", suggested)
  191. def test_uc_msc_fixed_values_and_process_qual(self):
  192. suggested = build_suggested(
  193. sample_type_key="uc_msc",
  194. receive_temperature="常温",
  195. storage_total_tubes=17,
  196. available_cell_count_1e7=8.5,
  197. process_tasks=[
  198. _task("内毒素(透析液)检测", datetime(2026, 1, 1), [("结果", "阴性")]),
  199. _task("真菌培养及鉴定", datetime(2026, 1, 2), [("结论", "通过")]),
  200. _task("支原体培养+药敏", datetime(2026, 1, 3), [("结果", "阳")]),
  201. ],
  202. latest_freeze_date=datetime(2026, 4, 4),
  203. )
  204. self.assertEqual(suggested["transport_temp_c"], "常温")
  205. self.assertEqual(suggested["storage_tube_count"], "17")
  206. self.assertEqual(suggested["available_cell_count_1e7"], "8.5")
  207. self.assertEqual(suggested["endotoxin_test"], "阴性")
  208. self.assertNotIn("fungus_test", suggested)
  209. self.assertEqual(suggested["mycoplasma_test"], "阳性")
  210. self.assertEqual(suggested["bacteria_test"], "阴性")
  211. self.assertEqual(suggested["result_judgment"], "合格")
  212. self.assertNotIn("mother_hbsag", suggested)
  213. self.assertEqual(suggested["available_cell_type"], "间充质干细胞")
  214. self.assertEqual(suggested["available_cell_morphology"], "短梭/贴壁型")
  215. self.assertEqual(suggested["available_immunophenotype"], "符合间充质干细胞表型特性")
  216. self.assertEqual(suggested["freeze_method"], "程序降温盒降温")
  217. self.assertEqual(suggested["storage_temp"], "-196℃")
  218. self.assertEqual(suggested["storage_years"], "20年")
  219. self.assertEqual(suggested["storage_date_p2"], "2026-04-04")
  220. self.assertNotIn("available_viability_pct", suggested)
  221. self.assertNotIn("report_issue_date", suggested)
  222. def test_uc_msc_mother_blood_and_ignores_bacteria_culture(self):
  223. stamp = datetime(2026, 5, 5, 8, 0)
  224. suggested = build_suggested(
  225. sample_type_key="uc_msc",
  226. gate_tasks=[
  227. _task(
  228. "乙肝五项定性",
  229. stamp,
  230. [
  231. ("乙型肝炎病毒表面抗原", "阳性"),
  232. ("抗乙型肝炎病毒表面抗体", "阴性"),
  233. ],
  234. "母血",
  235. ),
  236. _task("乙型肝炎病毒DNA", stamp, [], "母血"),
  237. ],
  238. process_tasks=[
  239. _task("需氧菌培养及鉴定(相关体液)", stamp, [("需氧菌培养及鉴定(相关体液)", "阳性")]),
  240. _task(
  241. "支原体培养+药敏",
  242. stamp,
  243. [("解脲(Uu)支原体培养", "阴性"), ("人型(Mh)支原体培养", "阴性")],
  244. ),
  245. ],
  246. )
  247. self.assertEqual(suggested["mother_hbsag"], "阳性")
  248. self.assertNotIn("mother_hbv_dna", suggested)
  249. self.assertEqual(suggested["mycoplasma_test"], "阴性")
  250. self.assertEqual(suggested["bacteria_test"], "阴性")
  251. self.assertNotIn("hbsag", suggested)
  252. def test_shared_mother_blood_fills_sample_without_its_own(self):
  253. stamp = datetime(2026, 7, 4, 9, 0)
  254. shared = [
  255. _task(
  256. "乙肝五项定性",
  257. stamp,
  258. [
  259. ("乙型肝炎病毒表面抗原", "阴性"),
  260. ("抗乙型肝炎病毒表面抗体", "阳性"),
  261. ],
  262. "母血",
  263. ),
  264. _task("乙型肝炎病毒DNA", stamp, [("乙型肝炎病毒DNA", "阴性")], "母血"),
  265. _task("抗丙型肝炎病毒抗体", stamp, [("抗丙型肝炎病毒抗体", "阴性")], "母血"),
  266. _task("丙型肝炎病毒RNA", stamp, [("丙型肝炎病毒RNA", "阴性")], "母血"),
  267. _task("抗梅毒螺旋体特异抗体", stamp, [("抗梅毒螺旋体特异抗体", "阴性")], "母血"),
  268. _task("抗巨细胞病毒抗体IgM定量", stamp, [("抗巨细胞病毒抗体IgM定量", "阴性")], "母血"),
  269. _task("抗人类免疫缺陷病毒抗体", stamp, [("抗人类免疫缺陷病毒抗体", "阴性")], "母血"),
  270. _task(
  271. "人类免疫缺陷病毒I型核酸(HIV-1 RNA)定量,实时荧光定量PCR",
  272. stamp,
  273. [("人类免疫缺陷病毒核酸(1型) (HIV-1RNA)", "阴性")],
  274. "母血",
  275. ),
  276. _task("乙肝五项定性", stamp, [("乙型肝炎病毒表面抗原", "阳性")], "脐血"),
  277. ]
  278. suggested = build_suggested(sample_type_key="uc_msc", shared_mother_tasks=shared)
  279. self.assertEqual(suggested["mother_hbsag"], "阴性")
  280. self.assertEqual(suggested["mother_hbv_dna"], "阴性")
  281. self.assertEqual(suggested["mother_anti_hcv"], "阴性")
  282. self.assertEqual(suggested["mother_hcv_rna"], "阴性")
  283. self.assertEqual(suggested["mother_anti_tp"], "阴性")
  284. self.assertEqual(suggested["mother_cmv_igm"], "阴性")
  285. self.assertEqual(suggested["mother_anti_hiv"], "阴性")
  286. self.assertEqual(suggested["mother_hiv1_rna"], "阴性")
  287. self.assertNotIn("hbsag", suggested)
  288. def test_own_mother_blood_wins_and_shared_only_fills_gaps(self):
  289. stamp = datetime(2026, 7, 4, 9, 0)
  290. suggested = build_suggested(
  291. sample_type_key="perinatal_hsc",
  292. gate_tasks=[
  293. _task("乙肝五项定性", stamp, [("乙型肝炎病毒表面抗原", "阳性")], "脐血"),
  294. _task("乙肝五项定性", stamp, [("乙型肝炎病毒表面抗原", "阳性")], "母血"),
  295. ],
  296. shared_mother_tasks=[
  297. _task("乙肝五项定性", stamp, [("乙型肝炎病毒表面抗原", "阴性")], "母血"),
  298. _task("乙型肝炎病毒DNA", stamp, [("乙型肝炎病毒DNA", "阴性")], "母血"),
  299. _task("乙肝五项定性", stamp, [("乙型肝炎病毒表面抗原", "阴性")], "脐血"),
  300. ],
  301. )
  302. self.assertEqual(suggested["hbsag"], "阳性")
  303. self.assertEqual(suggested["mother_hbsag"], "阳性")
  304. self.assertEqual(suggested["mother_hbv_dna"], "阴性")
  305. def test_merge_keeps_saved_values(self):
  306. merged = merge_prefill(
  307. {"storage_years": "30年", "collection_time": ""},
  308. {"storage_years": "20年", "collection_time": "2026-01-01 13:45", "hbsag": ""},
  309. )
  310. self.assertEqual(merged["storage_years"], "30年")
  311. self.assertEqual(merged["collection_time"], "2026-01-01 13:45")
  312. self.assertNotIn("hbsag", merged)
  313. if __name__ == "__main__":
  314. unittest.main()