ads_build.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. ADS 层宽表计算任务
  5. 说明:在 Doris 内部生成患者检查周期宽表(基于末次取镜时间)
  6. """
  7. import datetime
  8. from db.pool import get_doris_conn
  9. from logger.handler import get_logger
  10. logger = get_logger("job.ads_patient_exam")
  11. def run(start_date=None, end_date=None):
  12. """执行 ADS 层计算"""
  13. if start_date is None or end_date is None:
  14. yesterday = datetime.date.today() - datetime.timedelta(days=1)
  15. start_date = end_date = yesterday.strftime("%Y-%m-%d")
  16. logger.info("=" * 60)
  17. logger.info(f"[ADS计算] 开始计算: {start_date}")
  18. logger.info("=" * 60)
  19. conn = get_doris_conn()
  20. cursor = conn.cursor()
  21. try:
  22. # 1. 查询昨天有取镜记录的患者
  23. logger.info("步骤1: 查询取镜患者...")
  24. patient_sql = get_patient_sql(start_date)
  25. cursor.execute(patient_sql)
  26. patients = cursor.fetchall()
  27. logger.info(f"找到 {len(patients)} 个患者")
  28. if not patients:
  29. logger.warning("没有取镜患者,跳过")
  30. return 0
  31. # 2. 为每个患者生成宽表数据
  32. logger.info("步骤2: 生成宽表数据...")
  33. all_rows = []
  34. for patient in patients:
  35. patient_id = patient['patient_id']
  36. patient_id_str = patient['patient_id_str']
  37. # 生成单条宽表记录
  38. row = build_wide_row(cursor, patient_id, patient_id_str)
  39. if row:
  40. all_rows.append(row)
  41. # 3. 写入宽表
  42. logger.info("步骤3: 写入宽表...")
  43. insert_sql = get_insert_sql()
  44. placeholders = ', '.join(['%s'] * len(all_rows[0]))
  45. final_sql = insert_sql.replace('{placeholders}', placeholders)
  46. batch_size = 100
  47. total_inserted = 0
  48. for i in range(0, len(all_rows), batch_size):
  49. batch = all_rows[i:i+batch_size]
  50. cursor.executemany(final_sql, batch)
  51. conn.commit()
  52. total_inserted += len(batch)
  53. logger.info(f"已写入 {total_inserted}/{len(all_rows)}")
  54. logger.info("=" * 60)
  55. logger.info(f"[ADS计算] 计算完成: {total_inserted} 条")
  56. logger.info("=" * 60)
  57. return total_inserted
  58. except Exception as e:
  59. logger.error(f"[ADS计算] 计算失败: {e}")
  60. conn.rollback()
  61. raise
  62. finally:
  63. cursor.close()
  64. conn.close()
  65. def get_patient_sql(pickup_date):
  66. """查询有取镜记录的患者"""
  67. return f"""
  68. SELECT DISTINCT
  69. p.id as patient_id,
  70. p.patientId as patient_id_str
  71. FROM ods_treat_sellgoods sg
  72. JOIN ods_material_goods g ON g.id = sg.goods_id
  73. JOIN ods_treat_sellgoodslist sgl ON sgl.id = sg.sell_goods_list_id
  74. JOIN ods_clinic_clinic c ON c.id = sgl.clinic_id
  75. JOIN ods_patient_custome p ON p.id = c.patient_id
  76. WHERE sg.out_time >= '{pickup_date} 00:00:00'
  77. AND sg.out_time < '{pickup_date} 23:59:59'
  78. AND sg.out_time IS NOT NULL
  79. AND g.fee_class IN ('长期治疗类', '离焦镜片')
  80. """
  81. def build_wide_row(cursor, patient_id, patient_id_str):
  82. """为单个患者构建宽表数据"""
  83. # 查询患者基本信息
  84. info_sql = f"""
  85. SELECT
  86. p.name as patient_name,
  87. CASE WHEN p.sex = '男' THEN 1 ELSE 2 END as sex,
  88. TIMESTAMPDIFF(YEAR, p.birthday, CURDATE()) as age,
  89. p.birthday,
  90. p.mobile,
  91. p.identity,
  92. s.name as customer_optometrist_name,
  93. org_s.name as customer_optometrist_org_name,
  94. g.name as last_pickup_goods,
  95. g.id as last_pickup_goods_id,
  96. g.fee_class
  97. FROM ods_patient_custome p
  98. LEFT JOIN ods_staff_staff s ON s.id = p.customer_optometrist_staff_id
  99. LEFT JOIN ods_org_org org_s ON org_s.id = s.main_org_id
  100. JOIN (
  101. SELECT sg2.out_time, g2.name, g2.id, g2.fee_class
  102. FROM ods_treat_sellgoods sg2
  103. JOIN ods_material_goods g2 ON g2.id = sg2.goods_id
  104. JOIN ods_treat_sellgoodslist sgl2 ON sgl2.id = sg2.sell_goods_list_id
  105. JOIN ods_clinic_clinic c2 ON c2.id = sgl2.clinic_id
  106. WHERE c2.patient_id = {patient_id}
  107. ORDER BY sg2.out_time DESC
  108. LIMIT 1
  109. ) g ON 1=1
  110. WHERE p.id = {patient_id}
  111. """
  112. cursor.execute(info_sql)
  113. patient_info = cursor.fetchone()
  114. if not patient_info:
  115. return None
  116. # 查询购买统计
  117. buy_sql = f"""
  118. SELECT
  119. COUNT(CASE WHEN r.id IS NULL THEN 1 END) as buy_cnt,
  120. COUNT(CASE WHEN r.id IS NOT NULL THEN 1 END) as refund_cnt,
  121. MAX(CASE WHEN g.name LIKE '%铂视控%' THEN 1 END) as has_bs,
  122. MAX(CASE WHEN g.name LIKE '%星趣控%' THEN 1 END) as has_xq
  123. FROM ods_clinic_clinic c
  124. JOIN ods_fee_order fo ON fo.patientId IN (SELECT patientId FROM ods_patient_custome WHERE id = {patient_id})
  125. JOIN ods_fee_orderdetail fod ON fod.order_id = fo.id
  126. JOIN ods_material_goods g ON g.id = fod.goods_id
  127. LEFT JOIN ods_fee_refunddetail r ON r.orderdetail_id = fod.id
  128. WHERE c.patient_id = {patient_id}
  129. AND g.fee_class = '{patient_info['fee_class']}'
  130. """
  131. cursor.execute(buy_sql)
  132. buy_info = cursor.fetchone()
  133. # 查询末次取镜时间
  134. pickup_sql = f"""
  135. SELECT MAX(sg.out_time) as last_pickup_time
  136. FROM ods_treat_sellgoods sg
  137. JOIN ods_material_goods g ON g.id = sg.goods_id
  138. JOIN ods_treat_sellgoodslist sgl ON sgl.id = sg.sell_goods_list_id
  139. JOIN ods_clinic_clinic c ON c.id = sgl.clinic_id
  140. WHERE c.patient_id = {patient_id}
  141. AND sg.out_time IS NOT NULL
  142. AND g.fee_class IN ('长期治疗类', '离焦镜片')
  143. """
  144. cursor.execute(pickup_sql)
  145. pickup_info = cursor.fetchone()
  146. last_pickup_time = pickup_info['last_pickup_time']
  147. last_pickup_date = last_pickup_time.date() if last_pickup_time else None
  148. # 构建宽表记录
  149. row = {
  150. 'patient_id': patient_id,
  151. 'patient_id_str': patient_id_str,
  152. 'patient_name': patient_info['patient_name'],
  153. 'sex': patient_info['sex'],
  154. 'age': patient_info['age'],
  155. 'birthday': patient_info['birthday'],
  156. 'mobile': patient_info['mobile'],
  157. 'identity': patient_info['identity'],
  158. 'fee_class': patient_info['fee_class'],
  159. 'customer_optometrist_name': patient_info['customer_optometrist_name'] or '',
  160. 'customer_optometrist_org_name': patient_info['customer_optometrist_org_name'] or '',
  161. 'last_pickup_time': last_pickup_time,
  162. 'last_pickup_date': last_pickup_date,
  163. 'last_pickup_goods': patient_info['last_pickup_goods'],
  164. 'last_pickup_goods_id': patient_info['last_pickup_goods_id'],
  165. 'buy_cnt': buy_info['buy_cnt'] if buy_info else 0,
  166. 'refund_cnt': buy_info['refund_cnt'] if buy_info else 0,
  167. 'is_both_bs_xq': 1 if (buy_info['has_bs'] and buy_info['has_xq']) else 0,
  168. 'data_date': last_pickup_date,
  169. }
  170. # TODO: 这里需要实现周期数据的计算逻辑
  171. # 可以参考 export_goods.py 中的 parse_exam_data 和 fill_cycle_exam_data 函数
  172. # 由于逻辑复杂,建议用存储过程或单独的 Python 函数实现
  173. return row
  174. def get_insert_sql():
  175. """获取插入 SQL"""
  176. # 这里简化了字段,实际需要添加所有周期字段
  177. return """
  178. INSERT INTO fact_patient_exam_wide (
  179. patient_id,
  180. patient_id_str,
  181. patient_name,
  182. sex,
  183. age,
  184. birthday,
  185. mobile,
  186. identity,
  187. fee_class,
  188. customer_optometrist_name,
  189. customer_optometrist_org_name,
  190. last_pickup_time,
  191. last_pickup_date,
  192. last_pickup_goods,
  193. last_pickup_goods_id,
  194. buy_cnt,
  195. refund_cnt,
  196. is_both_bs_xq,
  197. baseline_date,
  198. baseline_right_se_val,
  199. baseline_left_se_val,
  200. baseline_al_od_val,
  201. baseline_al_os_val,
  202. m3_right_se_val,
  203. m3_left_se_val,
  204. m3_al_od_val,
  205. m3_al_os_val,
  206. p3_right_se_val,
  207. p3_left_se_val,
  208. p3_al_od_val,
  209. p3_al_os_val,
  210. p6_right_se_val,
  211. p6_left_se_val,
  212. p6_al_od_val,
  213. p6_al_os_val,
  214. data_date,
  215. create_time,
  216. update_time
  217. ) VALUES ({placeholders})
  218. ON DUPLICATE KEY UPDATE
  219. patient_name = VALUES(patient_name),
  220. update_time = NOW()
  221. """