| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- """
- 患者消费统计宽表同步任务
- 功能:将MySQL中的患者消费数据同步到Doris宽表
- """
- import datetime
- from db.pool import get_mysql_conn, get_doris_conn
- from logger.handler import get_logger
- logger = get_logger("job.consumption")
- def run(start_date=None, end_date=None, group_id=1):
- """执行任务"""
- if start_date is None or end_date is None:
- yesterday = datetime.date.today() - datetime.timedelta(days=1)
- start_date = end_date = yesterday.strftime("%Y-%m-%d")
- logger.info("=" * 50)
- logger.info(f"[患者消费] 开始同步: {start_date} ~ {end_date}, group_id={group_id}")
- logger.info("=" * 50)
- conn = get_mysql_conn()
- cursor = conn.cursor()
- # 1. 查询主要的消费数据
- params = {
- "group_id": int(group_id),
- "fsdate": start_date + " 00:00:00",
- "fedate": end_date + " 23:59:59",
- }
- sql = """
- SELECT
- x.clinic_id,
- x.patientId,
- x.patient_name,
- x.inviter_name,
- x.sex,
- x.patient_mobile as mobile,
- x.patient_birthday as birthday,
- x.patient_identity as identity,
- x.come_number,
- x.apt_doctor_name,
- x.apt_opt_name as apt_optometrist_name,
- x.act_doctor_name,
- x.act_assistant_name,
- x.act_opt_name as act_optometrist_name,
- x.matter,
- x.type,
- x.come_time,
- x.org_name,
- x.source1,
- x.source2,
- x.manager_name,
- x.first_opt_name,
- x.first_accept_date,
- x.created as pay_time,
- x.type_flag,
- x.fee_class,
- x.by_user_name,
- x.coupon_name,
- x.sign_wxapp,
- x.add_manager,
- x.not_transaction_reason,
- c.org_id,
- c.customer_manager_staff_id,
- c.customer_optometrist_staff_id,
- SUM(x.pay_amt) as total_amt
- FROM fee_dealdetail x
- JOIN clinic_clinic c ON c.id = x.clinic_id
- WHERE x.group_id = %(group_id)s
- AND x.org_id != 16
- AND x.created >= %(fsdate)s
- AND x.created <= %(fedate)s
- GROUP BY x.clinic_id, x.patientId, x.patient_name, x.inviter_name, x.sex,
- x.patient_mobile, x.patient_birthday, x.patient_identity, x.come_number,
- x.apt_doctor_name, x.apt_opt_name, x.act_doctor_name, x.act_assistant_name,
- x.act_opt_name, x.matter, x.type, x.come_time, x.org_name, x.source1, x.source2,
- x.manager_name, x.first_opt_name, x.first_accept_date, x.created,
- x.type_flag, x.fee_class, x.by_user_name, x.coupon_name, x.sign_wxapp,
- x.add_manager, x.not_transaction_reason, c.org_id, c.customer_manager_staff_id, c.customer_optometrist_staff_id
- """
- logger.info("正在查询主数据...")
- cursor.execute(sql, params)
- data_list = cursor.fetchall() # DictCursor 直接返回字典列表
- logger.info(f"查询到 {len(data_list)} 条记录")
- if not data_list:
- cursor.close()
- conn.close()
- logger.info("[患者消费] 无数据")
- return 0
- # 2. 获取 clinic_id 列表
- clinic_ids = [data["clinic_id"] for data in data_list]
- # 3. 查询诊所和分组信息
- org_ids = list(set([d.get("org_id") for d in data_list if d.get("org_id")]))
- org_info_dict = {}
- if org_ids:
- sql = """
- SELECT o.id as org_id, o.name as org_name, o.group_id, g.name as group_name
- FROM org_org o
- LEFT JOIN org_group g ON o.group_id = g.id
- WHERE o.id IN %(ids)s
- """
- cursor.execute(sql, {"ids": org_ids})
- org_list = cursor.fetchall()
- org_info_dict = {o["org_id"]: o for o in org_list}
- # 4. 查询员工信息
- staff_ids = []
- for data in data_list:
- if data.get("customer_manager_staff_id"):
- staff_ids.append(data["customer_manager_staff_id"])
- if data.get("customer_optometrist_staff_id"):
- staff_ids.append(data["customer_optometrist_staff_id"])
- staff_ids = list(set(staff_ids))
- staff_dict = {}
- if staff_ids:
- sql = """
- SELECT s.id, u.name, s.qywx_userid, u.mobile
- FROM staff_staff s
- JOIN users_userprofile u ON s.user_id = u.id
- WHERE s.id IN %(ids)s
- """
- cursor.execute(sql, {"ids": staff_ids})
- staff_list = cursor.fetchall()
- staff_dict = {s["id"]: s for s in staff_list}
- # 5. 查询推广信息
- clinic_info_dict = {}
- if clinic_ids:
- sql = """
- SELECT cc.collect_date, cc.activity_location,
- u.name as extension_user_name, cc.clinic_id
- FROM returnvisit_customerconsultationrecord cc
- LEFT JOIN users_userprofile u ON cc.extension_user_id = u.id
- WHERE cc.clinic_id IN %(ids)s
- """
- cursor.execute(sql, {"ids": clinic_ids})
- clinic_info_list = cursor.fetchall()
- for c in clinic_info_list:
- clinic_info_dict[c["clinic_id"]] = c
- # 5.1 查询患者检查信息
- patient_ids = list(set([d.get("patientId") for d in data_list if d.get("patientId")]))
- patient_exam_dict = {}
- if patient_ids:
- sql = """
- SELECT patientId, r_sph, l_sph, r_nakeye, l_nakeye, al_od, al_os
- FROM patient_custome
- WHERE patientId IN %(ids)s
- """
- cursor.execute(sql, {"ids": patient_ids})
- exam_list = cursor.fetchall()
- patient_exam_dict = {e["patientId"]: e for e in exam_list}
- cursor.close()
- conn.close()
- # 6. 字段列表
- fields = [
- "clinic_id", "pay_date", "patientId", "patient_name", "mobile", "sex", "birthday",
- "identity", "come_number", "inviter_name", "come_time", "come_date", "type", "matter",
- "org_id", "org_name", "group_id", "group_name", "apt_doctor_id", "apt_doctor_name",
- "apt_optometrist_id", "apt_optometrist_name", "act_doctor_id", "act_doctor_name",
- "act_optometrist_id", "act_optometrist_name", "act_assistant_name",
- "source1", "source2", "manager_id", "manager_name", "manager_qywx_id", "manager_mobile",
- "optometrist_id", "optometrist_name", "optometrist_qywx_id", "optometrist_mobile",
- "first_opt_name", "first_accept_date",
- "r_sph", "l_sph", "r_nakeye", "l_nakeye", "al_od", "al_os", "not_transaction_reason",
- "total_amt", "pay_time", "by_user_id",
- "by_user_name", "coupon_name", "add_manager", "sign_wxapp", "collect_date",
- "activity_location", "extension_user_id", "extension_user_name", "create_time",
- ]
- # 7. 处理数据
- result_dict = {}
- for data in data_list:
- clinic_id = data["clinic_id"]
- # 处理金额(根据type_flag正负)
- total_amt = data.get("total_amt", 0)
- try:
- total_amt = float(total_amt) if total_amt is not None else 0.0
- except (TypeError, ValueError):
- total_amt = 0.0
- if str(data.get("type_flag")) != "1":
- total_amt = -total_amt if total_amt else 0
- # 处理日期
- pay_time = data.get("pay_time")
- if pay_time:
- if isinstance(pay_time, str):
- pay_date = pay_time.split(" ")[0]
- pay_time_str = pay_time if " " in pay_time else f"{pay_time} 00:00:00"
- else:
- pay_date = pay_time.strftime("%Y-%m-%d")
- pay_time_str = pay_time.strftime("%Y-%m-%d %H:%M:%S")
- else:
- pay_date = None
- pay_time_str = None
-
- if not pay_date:
- continue
-
- key = f"{clinic_id}_{pay_date}"
-
- if key in result_dict:
- result_dict[key]["total_amt"] += total_amt
- continue
- come_time = data.get("come_time")
- if come_time:
- if isinstance(come_time, str):
- come_date = come_time.split(" ")[0]
- come_time_str = come_time if " " in come_time else f"{come_time} 00:00:00"
- else:
- come_date = come_time.strftime("%Y-%m-%d")
- come_time_str = come_time.strftime("%Y-%m-%d %H:%M:%S")
- else:
- come_date = None
- come_time_str = None
- # 获取诊所信息
- org_id = data.get("org_id")
- org_info = org_info_dict.get(org_id, {})
- # 获取员工信息
- manager_staff = staff_dict.get(data.get("customer_manager_staff_id"), {})
- optometrist_staff = staff_dict.get(data.get("customer_optometrist_staff_id"), {})
- # 获取推广信息
- clinic_info = clinic_info_dict.get(clinic_id, {})
- # 获取患者检查信息
- patient_exam = patient_exam_dict.get(data.get("patientId"), {})
- # 处理性别
- sex_val = data.get("sex")
- if sex_val == "男":
- sex_val = 1
- elif sex_val == "女":
- sex_val = 2
- # 处理就诊类型
- type_val = data.get("type")
- if type_val == "初诊":
- type_val = 1
- elif type_val == "复诊":
- type_val = 2
- elif type_val == "预约":
- type_val = 3
- elif type_val == "复查":
- type_val = 4
- # 确保整数字段类型正确
- def safe_int(v):
- if v is None or v == "":
- return None
- try:
- return int(v)
- except (TypeError, ValueError):
- return None
- # 确保字符串字段去除两边空格
- def safe_str(v):
- if v is None:
- return ""
- return str(v).strip()
- record_dict = {
- "clinic_id": safe_int(clinic_id),
- "pay_date": pay_date,
- "patientId": safe_str(data.get("patientId")),
- "patient_name": safe_str(data.get("patient_name")),
- "mobile": safe_str(data.get("mobile")),
- "sex": safe_int(sex_val) if sex_val else None,
- "birthday": data.get("birthday"),
- "identity": safe_str(data.get("identity")),
- "come_number": safe_int(data.get("come_number")),
- "inviter_name": safe_str(data.get("inviter_name")),
- "come_time": come_time_str,
- "come_date": come_date,
- "type": safe_int(type_val) if type_val else None,
- "matter": safe_str(data.get("matter")),
- "org_id": safe_int(org_id),
- "org_name": safe_str(org_info.get("org_name")),
- "group_id": safe_int(org_info.get("group_id")),
- "group_name": safe_str(org_info.get("group_name")),
- "apt_doctor_id": None,
- "apt_doctor_name": safe_str(data.get("apt_doctor_name")),
- "apt_optometrist_id": None,
- "apt_optometrist_name": safe_str(data.get("apt_optometrist_name")),
- "act_doctor_id": None,
- "act_doctor_name": safe_str(data.get("act_doctor_name")),
- "act_optometrist_id": None,
- "act_optometrist_name": safe_str(data.get("act_optometrist_name")),
- "act_assistant_name": safe_str(data.get("act_assistant_name")),
- "source1": safe_str(data.get("source1")),
- "source2": safe_str(data.get("source2")),
- "manager_id": safe_int(data.get("customer_manager_staff_id")),
- "manager_name": safe_str(manager_staff.get("name")),
- "manager_qywx_id": safe_str(manager_staff.get("qywx_userid")),
- "manager_mobile": safe_str(manager_staff.get("mobile")),
- "optometrist_id": safe_int(data.get("customer_optometrist_staff_id")),
- "optometrist_name": safe_str(optometrist_staff.get("name")),
- "optometrist_qywx_id": safe_str(optometrist_staff.get("qywx_userid")),
- "optometrist_mobile": safe_str(optometrist_staff.get("mobile")),
- "first_opt_name": safe_str(data.get("first_opt_name")),
- "first_accept_date": data.get("first_accept_date"),
- "r_sph": safe_str(patient_exam.get("r_sph")),
- "l_sph": safe_str(patient_exam.get("l_sph")),
- "r_nakeye": safe_str(patient_exam.get("r_nakeye")),
- "l_nakeye": safe_str(patient_exam.get("l_nakeye")),
- "al_od": safe_str(patient_exam.get("al_od")),
- "al_os": safe_str(patient_exam.get("al_os")),
- "not_transaction_reason": safe_str(data.get("not_transaction_reason")),
- "total_amt": total_amt,
- "pay_time": pay_time_str,
- "by_user_id": None,
- "by_user_name": safe_str(data.get("by_user_name")),
- "coupon_name": safe_str(data.get("coupon_name")),
- "add_manager": "已添加" if data.get("add_manager") == 1 else "未添加",
- "sign_wxapp": "已注册" if data.get("sign_wxapp") == 1 else "未注册",
- "collect_date": clinic_info.get("collect_date"),
- "activity_location": safe_str(clinic_info.get("activity_location")),
- "extension_user_id": None,
- "extension_user_name": safe_str(clinic_info.get("extension_user_name")),
- "create_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
- }
- result_dict[key] = record_dict
- result = []
- for key, record_dict in result_dict.items():
- record = (
- record_dict["clinic_id"], record_dict["pay_date"], record_dict["patientId"],
- record_dict["patient_name"], record_dict["mobile"], record_dict["sex"],
- record_dict["birthday"], record_dict["identity"], record_dict["come_number"],
- record_dict["inviter_name"], record_dict["come_time"], record_dict["come_date"],
- record_dict["type"], record_dict["matter"], record_dict["org_id"],
- record_dict["org_name"], record_dict["group_id"], record_dict["group_name"],
- record_dict["apt_doctor_id"], record_dict["apt_doctor_name"],
- record_dict["apt_optometrist_id"], record_dict["apt_optometrist_name"],
- record_dict["act_doctor_id"], record_dict["act_doctor_name"],
- record_dict["act_optometrist_id"], record_dict["act_optometrist_name"],
- record_dict["act_assistant_name"], record_dict["source1"], record_dict["source2"],
- record_dict["manager_id"], record_dict["manager_name"],
- record_dict["manager_qywx_id"], record_dict["manager_mobile"],
- record_dict["optometrist_id"], record_dict["optometrist_name"],
- record_dict["optometrist_qywx_id"], record_dict["optometrist_mobile"],
- record_dict["first_opt_name"], record_dict["first_accept_date"],
- record_dict["r_sph"], record_dict["l_sph"], record_dict["r_nakeye"],
- record_dict["l_nakeye"], record_dict["al_od"], record_dict["al_os"],
- record_dict["not_transaction_reason"],
- float(record_dict["total_amt"]), record_dict["pay_time"],
- record_dict["by_user_id"], record_dict["by_user_name"], record_dict["coupon_name"],
- record_dict["add_manager"], record_dict["sign_wxapp"], record_dict["collect_date"],
- record_dict["activity_location"], record_dict["extension_user_id"],
- record_dict["extension_user_name"], record_dict["create_time"]
- )
- result.append(record)
- # 8. 写入Doris
- logger.info(f"数据处理完成,共 {len(result)} 条记录")
- conn = get_doris_conn()
- cursor = conn.cursor()
- # 8.1 先删除该时间段的数据(确保幂等性)
- logger.info(f"清理该时间段的历史数据: {start_date} ~ {end_date}")
- delete_sql = """
- DELETE FROM fact_patientconsumption
- WHERE pay_date >= %s AND pay_date <= %s
- AND group_id = %s
- """
- cursor.execute(delete_sql, (start_date, end_date, int(group_id)))
- deleted_count = cursor.rowcount
- logger.info(f"已删除 {deleted_count} 条历史数据")
- conn.commit()
- # 8.2 插入新数据
- sql = f"INSERT INTO fact_patientconsumption ({', '.join(fields)}) VALUES ({', '.join(['%s'] * len(fields))})"
- total = len(result)
- batch_size = 100
- success_count = 0
- for i in range(0, total, batch_size):
- batch = result[i:i + batch_size]
- try:
- cursor.executemany(sql, batch)
- conn.commit()
- success_count += len(batch)
- logger.info(f"[患者消费] 已写入 {min(i + batch_size, total)}/{total}")
- except Exception as e:
- logger.error(f"写入失败: {e}")
- conn.rollback()
- raise
- cursor.close()
- conn.close()
- logger.info("=" * 50)
- logger.info(f"[患者消费] 同步完成,共 {success_count} 条记录")
- logger.info("=" * 50)
- return success_count
|