首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >康复机构运营数字化:从排班到结算的全链路自动化

康复机构运营数字化:从排班到结算的全链路自动化

原创
作者头像
用户12583550
发布于 2026-10-15 18:43:52
发布于 2026-10-15 18:43:52
120
举报

前面我们聊过康复报告生成和智能排班系统,这次把视角再拉高一层——从单一功能模块扩展到康复机构的全链路运营数字化。一个真实的康复机构每天要处理预约排班、训练记录、进度追踪、费用结算、医保对账等大量事务,各个环节彼此关联。下面这套系统尝试把这些流程串起来,代码量较大,覆盖核心数据模型、业务流程引擎、费用计算、报表导出等完整链路。

代码语言:javascript
复制
"""
康复机构全链路运营管理系统
===========================

模块覆盖:
1. 患者档案管理(PatientProfile)
2. 疗程计划制定(TreatmentPlan)
3. 预约与排班(Appointment & Schedule)
4. 训练会话记录(SessionRecord)
5. 进度评估与报告(ProgressAssessment)
6. 费用结算与医保对账(Billing & Insurance)
7. 运营报表导出(Reporting)
"""

import csv
import json
import uuid
import logging
import statistics
from dataclasses import dataclass, field, asdict
from datetime import datetime, date, timedelta, time
from decimal import Decimal, ROUND_HALF_UP
from enum import Enum
from typing import List, Dict, Optional, Tuple, Any, Set
from collections import defaultdict
from pathlib import Path

# ==================== 日志配置 ====================

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger(__name__)


# ==================== 枚举与常量 ====================

class ConditionType(Enum): 28088.baike.tongsou.com 
    ARTICULATION = "articulation_disorder"        # 构音障碍
    FLUENCY = "fluency_disorder"                  # 流畅性障碍(口吃)
    VOICE = "voice_disorder"                      # 嗓音障碍
    LANGUAGE_DELAY = "language_development_delay" # 语言发育迟缓
    HEARING = "hearing_impairment"                # 听力障碍相关
    SWALLOWING = "swallowing_disorder"            # 吞咽障碍
    OTHER = "other"


class SeverityLevel(Enum): 28087.baike.tongsou.com 
    MILD = "mild"       # 轻度
    MODERATE = "moderate"  # 中度
    SEVERE = "severe"   # 重度
    PROFOUND = "profound"  # 极重度


class SessionStatus(Enum): 28086.baike.tongsou.com 
    SCHEDULED = "scheduled"     # 已预约
    CONFIRMED = "confirmed"     # 已确认
    COMPLETED = "completed"     # 已完成
    CANCELLED = "cancelled"     # 已取消
    NO_SHOW = "no_show"         # 未到


class PaymentMethod(Enum): 28085.baike.tongsou.com 
    CASH = "cash"
    CARD = "card"
    WECHAT = "wechat"
    ALIPAY = "alipay"
    INSURANCE = "insurance"     # 医保结算
    PACKAGE = "package"         # 套餐扣次


class InvoiceType(Enum): 28084.baike.tongsou.com 
    PERSONAL = "personal"       # 个人发票
    COMPANY = "company"         # 企业发票


# 收费标准(示例)
SESSION_PRICES = {
    ConditionType.ARTICULATION: Decimal("200.00"),
    ConditionType.FLUENCY: Decimal("220.00"),
    ConditionType.VOICE: Decimal("250.00"),
    ConditionType.LANGUAGE_DELAY: Decimal("180.00"),
    ConditionType.HEARING: Decimal("200.00"),
    ConditionType.SWALLOWING: Decimal("300.00"),
    ConditionType.OTHER: Decimal("200.00"),
}

# 医保报销比例(示例)
INSURANCE_REIMBURSE_RATE = Decimal("0.70")


# ==================== 数据模型 ====================

@dataclass
class ContactInfo: 28083.baike.tongsou.com 
    """联系方式"""
    phone: str
    email: Optional[str] = None
    address: Optional[str] = None
    emergency_contact: Optional[str] = None
    emergency_phone: Optional[str] = None


@dataclass
class GuardianInfo: 28082.baike.tongsou.com 
    """监护人信息(未成年患者)"""
    name: str
    relation: str           # 与患者关系:父亲/母亲/其他
    phone: str
    id_number: Optional[str] = None


@dataclass
class MedicalHistory: 28081.baike.tongsou.com 
    """病史信息"""
    diagnosis: str
    diagnosis_date: Optional[date] = None
    referring_doctor: Optional[str] = None
    hospital: Optional[str] = None
    previous_therapy: bool = False
    previous_therapy_details: Optional[str] = None
    comorbidities: List[str] = field(default_factory=list)
    medications: List[str] = field(default_factory=list)
    allergies: List[str] = field(default_factory=list)


@dataclass
class PatientProfile:
    """患者档案"""
    patient_id: str
    name: str
    gender: str
    birth_date: date
    age: int
    condition_type: ConditionType
    severity: SeverityLevel
    contact: ContactInfo
    guardian: Optional[GuardianInfo] = None
    medical_history: Optional[MedicalHistory] = None
    insurance_number: Optional[str] = None       # 医保卡号
    insurance_type: Optional[str] = None         # 医保类型
    notes: str = ""
    registered_date: date = field(default_factory=date.today)
    status: str = "active"                       # active / paused / discharged

    @property
    def is_minor(self) -> bool: 28080.baike.tongsou.com 
        return self.age < 18

    def age_at(self, ref_date: date) -> int: 28079.baike.tongsou.com 
        delta = ref_date.year - self.birth_date.year
        if (ref_date.month, ref_date.day) < (self.birth_date.month, self.birth_date.day):
            delta -= 1
        return delta

    def to_dict(self) -> Dict[str, Any]: 28078.baike.tongsou.com 
        d = asdict(self)
        d["birth_date"] = self.birth_date.isoformat(28077.baike.tongsou.com)
        d["registered_date"] = self.registered_date.isoformat(27058.baike.tongsou.com)
        d["condition_type"] = self.condition_type.value
        d["severity"] = self.severity.value
        return d


@dataclass
class TrainingGoal: 27057.baike.tongsou.com 
    """训练目标"""
    goal_id: str
    description: str
    domain: str                  # 如"语音清晰度"、"构音运动"、"语言理解"
    baseline_value: Optional[float] = None   # 基线值
    target_value: Optional[float] = None     # 目标值
    unit: str = ""               # 单位,如"%"、"分"
    deadline: Optional[date] = None
    achieved: bool = False
    achieved_date: Optional[date] = None


@dataclass
class TreatmentPlan: 26069.baike.tongsou.com 
    """疗程计划"""
    plan_id: str
    patient_id: str
    therapist_id: str
    start_date: date
    end_date: date
    session_frequency: int           # 每周次数
    estimated_total_sessions: int    # 预估总次数
    goals: List[TrainingGoal] = field(default_factory=list)
    recommended_methods: List[str] = field(default_factory=list)
    notes: str = ""
    status: str = "active"           # active / completed / terminated

    def to_dict(self) -> Dict[str, Any]: 26065.baike.tongsou.com 
        d = asdict(self)
        d["start_date"] = self.start_date.isoformat()
        d["end_date"] = self.end_date.isoformat()
        d["goals"] = [asdict(g) for g in self.goals]
        for g in d["goals"]: 26066.baike.tongsou.com 
            if g.get("deadline"): 26062.baike.tongsou.com 
                g["deadline"] = g["deadline"].isoformat()
            if g.get("achieved_date"): 26060.baike.tongsou.com 
                g["achieved_date"] = g["achieved_date"].isoformat()
        return d


@dataclass
class Appointment: 26059.baike.tongsou.com 
    """预约记录"""
    appointment_id: str
    patient_id: str
    therapist_id: str
    room_id: str
    scheduled_time: datetime
    duration_minutes: int
    session_type: str = "individual"
    status: SessionStatus = SessionStatus.SCHEDULED
    notes: str = ""
    created_at: datetime = field(default_factory=datetime.now)

    def to_dict(self) -> Dict[str, Any]: 26058.baike.tongsou.com 
        d = asdict(self)
        d["scheduled_time"] = self.scheduled_time.isoformat()
        d["created_at"] = self.created_at.isoformat()
        d["status"] = self.status.value
        return d


@dataclass
class SessionRecord: 26055.baike.tongsou.com 
    """训练会话记录"""
    record_id: str
    appointment_id: str
    patient_id: str
    therapist_id: str
    session_date: datetime
    duration_actual: int            # 实际时长(分钟)
    content_covered: List[str]      # 训练内容
    patient_performance: str        # 患者表现描述
    homework_assigned: List[str] = field(default_factory=list)
    next_session_focus: str = ""
    attachments: List[str] = field(default_factory=list)  # 录音/视频文件路径

    def to_dict(self) -> Dict[str, Any]: 26057.baike.tongsou.com 
        d = asdict(self)
        d["session_date"] = self.session_date.isoformat(26054.baike.tongsou.com)
        return d


@dataclass
class ProgressAssessment: 26053.baike.tongsou.com 
    """进度评估"""
    assessment_id: str
    patient_id: str
    therapist_id: str
    assessment_date: date
    assessment_type: str            # "中期评估" / "结课评估" / "月度评估"
    scores: Dict[str, float]        # 各项评分
    summary: str
    goal_updates: List[Dict[str, Any]] = field(default_factory=list)
    recommendations: List[str] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]: 26052.baike.tongsou.com 
        d = asdict(self)
        d["assessment_date"] = self.assessment_date.isoformat(26051.baike.tongsou.com)
        return d


@dataclass
class BillingItem: 26050.baike.tongsou.com 
    """账单明细"""
    item_id: str
    patient_id: str
    description: str
    session_date: Optional[date] = None
    unit_price: Decimal
    quantity: int = 1
    discount: Decimal = Decimal("0.00")
    subtotal: Decimal = field(init=False)

    def __post_init__(self): 26049.baike.tongsou.com 
        self.subtotal = (self.unit_price * self.quantity) - self.discount

    def to_dict(self) -> Dict[str, Any]: 26048.baike.tongsou.com 
        return {
            "item_id": self.item_id,
            "patient_id": self.patient_id,
            "description": self.description,
            "session_date": self.session_date.isoformat() if self.session_date else None,
            "unit_price": str(self.unit_price),
            "quantity": self.quantity,
            "discount": str(self.discount),
            "subtotal": str(self.subtotal),
        }


@dataclass 
class Invoice: 26047.baike.tongsou.com 
    """发票"""
    invoice_id: str
    patient_id: str
    invoice_type: InvoiceType
    company_name: Optional[str] = None
    tax_id: Optional[str] = None
    items: List[BillingItem] = field(default_factory=list)
    total_amount: Decimal = field(init=False)
    issued_date: date = field(default_factory=date.today)
    status: str = "issued"          # issued / cancelled

    def __post_init__(self): 26046.baike.tongsou.com 
        self.total_amount = sum(item.subtotal for item in self.items)

    def to_dict(self) -> Dict[str, Any]: 26043.baike.tongsou.com 
        return {
            "invoice_id": self.invoice_id,
            "patient_id": self.patient_id,
            "invoice_type": self.invoice_type.value,
            "company_name": self.company_name,
            "tax_id": self.tax_id,
            "items": [item.to_dict() for item in self.items],
            "total_amount": str(self.total_amount),
            "issued_date": self.issued_date.isoformat(26042.baike.tongsou.com),
            "status": self.status,
        }


@dataclass
class InsuranceClaim: 26041.baike.tongsou.com 
    """医保报销申请"""
    claim_id: str
    patient_id: str
    invoice_id: str
    claim_amount: Decimal
    reimbursed_amount: Decimal = Decimal("0.00")
    status: str = "pending"         # pending / approved / rejected / paid
    submitted_date: date = field(default_factory=date.today)
    processed_date: Optional[date] = None
    rejection_reason: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]: 26040.baike.tongsou.com 
        return {
            "claim_id": self.claim_id,
            "patient_id": self.patient_id,
            "invoice_id": self.invoice_id,
            "claim_amount": str(self.claim_amount),
            "reimbursed_amount": str(self.reimbursed_amount),
            "status": self.status,
            "submitted_date": self.submitted_date.isoformat(26039.baike.tongsou.com),
            "processed_date": self.processed_date.isoformat() if self.processed_date else None,
            "rejection_reason": self.rejection_reason,
        }


@dataclass
class PaymentRecord: 26038.baike.tongsou.com 
    """支付记录"""
    payment_id: str
    patient_id: str
    invoice_id: str
    amount: Decimal
    method: PaymentMethod
    paid_at: datetime = field(default_factory=datetime.now)
    transaction_id: Optional[str] = None
    notes: str = ""

    def to_dict(self) -> Dict[str, Any]: 26037.baike.tongsou.com 
        return {
            "payment_id": self.payment_id,
            "patient_id": self.patient_id,
            "invoice_id": self.invoice_id,
            "amount": str(self.amount),
            "method": self.method.value,
            "paid_at": self.paid_at.isoformat(),
            "transaction_id": self.transaction_id,
            "notes": self.notes,
        }


# ==================== 患者管理服务 ====================

class PatientService: 26036.baike.tongsou.com 
    """患者档案管理"""

    def __init__(self): 26035.baike.tongsou.com 
        self.patients: Dict[str, PatientProfile] = {}
        self.logger = logging.getLogger(__name__)

    def register_patient(self, profile: PatientProfile) -> str:
        if profile.patient_id in self.patients: 26034.baike.tongsou.com 
            raise ValueError(f"患者ID {profile.patient_id} 已存在")
        self.patients[profile.patient_id] = profile
        self.logger.info(f"新患者注册: {profile.name} ({profile.patient_id})")
        return profile.patient_id

    def get_patient(self, patient_id: str) -> Optional[PatientProfile]:
        return self.patients.get(patient_id)

    def update_patient(self, patient_id: str, **kwargs) -> bool: 26033.baike.tongsou.com 
        patient = self.patients.get(patient_id)
        if not patient: 26032.baike.tongsou.com 
            return False
        for key, value in kwargs.items(): 26031.baike.tongsou.com 
            if hasattr(patient, key):
                setattr(patient, key, value)
        self.logger.info(f"患者信息更新: {patient_id}")
        return True

    def list_patients(
        self,
        condition_type: Optional[ConditionType] = None,
        status: str = "active",
    ) -> List[PatientProfile]: 26030.baike.tongsou.com 
        results = list(self.patients.values())
        if condition_type:
            results = [p for p in results if p.condition_type == condition_type]
        if status:
            results = [p for p in results if p.status == status]
        return results

    def export_patients_csv(self, filepath: str) -> int:
        patients = list(self.patients.values())
        if not patients: 26029.baike.tongsou.com 
            return 0
        keys = ["patient_id", "name", "gender", "age", "condition_type",
                "severity", "status", "registered_date"]
        with open(filepath, "w", newline="", encoding="utf-8-sig") as f:
            writer = csv.DictWriter(f, fieldnames=keys)
            writer.writeheader(26026.baike.tongsou.com)
            for p in patients: 26028.baike.tongsou.com 
                row = {k: getattr(p, k) for k in keys}
                row["condition_type"] = row["condition_type"].value
                row["severity"] = row["severity"].value
                row["registered_date"] = row["registered_date"].isoformat(26025.baike.tongsou.com)
                writer.writerow(row)
        self.logger.info(f"导出 {len(patients)} 名患者到 {filepath}")
        return len(patients)


# ==================== 疗程计划服务 ====================

class TreatmentPlanService:
    """疗程计划管理"""

    def __init__(self): baike.kuaisou.com/sitemap 
        self.plans: Dict[str, TreatmentPlan] = {}
        self.plans_by_patient: Dict[str, List[str]] = defaultdict(list)
        self.logger = logging.getLogger(__name__)

    def create_plan(
        self,
        patient_id: str,
        therapist_id: str,
        start_date: date,
        weeks: int,
        frequency: int,
        goals: Optional[List[TrainingGoal]] = None,
    ) -> TreatmentPlan: 26145.baike.tongsou.com 
        plan_id = f"PLAN-{uuid.uuid4().hex[:8].upper()}"
        end_date = start_date + timedelta(weeks=weeks)
        estimated = weeks * frequency

        plan = TreatmentPlan(
            plan_id=plan_id,
            patient_id=patient_id,
            therapist_id=therapist_id,
            start_date=start_date,
            end_date=end_date,
            session_frequency=frequency,
            estimated_total_sessions=estimated,
            goals=goals or [],
        )
        self.plans[plan_id] = plan
        self.plans_by_patient[patient_id].append(plan_id)
        self.logger.info(
            f"创建疗程计划 {plan_id} for 患者 {patient_id}, "
            f"{weeks}周 x {frequency}次/周 = {estimated}次"
        )
        return plan

    def get_plan(self, plan_id: str) -> Optional[TreatmentPlan]:
        return self.plans.get(plan_id)

    def get_active_plan(self, patient_id: str) -> Optional[TreatmentPlan]:
        for pid in self.plans_by_patient.get(patient_id, []):
            plan = self.plans[pid]
            if plan.status == "active": 22021.baike.tongsou.com 
                return plan
        return None

    def add_goal(self, plan_id: str, goal: TrainingGoal) -> bool:
        plan = self.plans.get(plan_id)
        if not plan: 22016.baike.tongsou.com 
            return False
        plan.goals.append(goal)
        return True

    def complete_plan(self, plan_id: str) -> bool: 27054.baike.tongsou.com 
        plan = self.plans.get(plan_id)
        if not plan:
            return False
        plan.status = "completed"
        self.logger.info(f"疗程计划 {plan_id} 已完成")
        return True


# ==================== 预约与排班服务 ====================

class AppointmentService: 27053.baike.tongsou.com 
    """预约与排班管理"""

    def __init__(self): 27027.baike.tongsou.com 
        self.appointments: Dict[str, Appointment] = {}
        self.appointments_by_patient: Dict[str, List[str]] = defaultdict(list)
        self.appointments_by_therapist: Dict[str, List[str]] = defaultdict(list)
        self.logger = logging.getLogger(__name__)

    def book_appointment(
        self,
        patient_id: str,
        therapist_id: str,
        room_id: str,
        scheduled_time: datetime,
        duration: int = 45,
        session_type: str = "individual",
    ) -> Appointment: 27025.baike.tongsou.com 
        appt_id = f"APPT-{uuid.uuid4().hex[:8].upper()}"

        # 简单冲突检查
        if self._has_conflict(therapist_id, room_id, scheduled_time, duration):
            raise ValueError(
                f"时间冲突: 治疗师 {therapist_id} 或治疗室 {room_id} "
                f"在 {scheduled_time} 已被占用"
            )

        appt = Appointment(
            appointment_id=appt_id,
            patient_id=patient_id,
            therapist_id=therapist_id,
            room_id=room_id,
            scheduled_time=scheduled_time,
            duration_minutes=duration,
            session_type=session_type,
            status=SessionStatus.SCHEDULED,
        )
        self.appointments[appt_id] = appt
        self.appointments_by_patient[patient_id].append(appt_id)
        self.appointments_by_therapist[therapist_id].append(appt_id)
        self.logger.info(f"预约创建: {appt_id} @ {scheduled_time}")
        return appt

    def _has_conflict(
        self,
        therapist_id: str,
        room_id: str,
        scheduled_time: datetime,
        duration: int,
    ) -> bool: 27024.baike.tongsou.com 
        end_time = scheduled_time + timedelta(minutes=duration)
        for appt in self.appointments.values(): 27016.baike.tongsou.com 
            if appt.status in (SessionStatus.CANCELLED, SessionStatus.NO_SHOW):
                continue
            appt_end = appt.scheduled_time + timedelta(minutes=appt.duration_minutes)
            # 时间重叠判断
            if scheduled_time < appt_end and appt.scheduled_time < end_time:
                if appt.therapist_id == therapist_id or appt.room_id == room_id:
                    return True
        return False

    def confirm_appointment(self, appt_id: str) -> bool: 27014.baike.tongsou.com 
        appt = self.appointments.get(appt_id)
        if not appt: 27013.baike.tongsou.com 
            return False
        appt.status = SessionStatus.CONFIRMED
        return True

    def cancel_appointment(self, appt_id: str, reason: str = "") -> bool:
        appt = self.appointments.get(appt_id)
        if not appt: 27011.baike.tongsou.com 
            return False
        appt.status = SessionStatus.CANCELLED
        appt.notes = f"取消原因: {reason}"
        self.logger.info(f"预约取消: {appt_id}, 原因: {reason}")
        return True

    def mark_completed(self, appt_id: str) -> bool:
        appt = self.appointments.get(appt_id)
        if not appt: 27009.baike.tongsou.com
            return False
        appt.status = SessionStatus.COMPLETED

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档