From bcd7c35c5c028f1a5943b2b61753705075c5bfe0 Mon Sep 17 00:00:00 2001 From: stud203999 Date: Fri, 3 Apr 2026 23:37:38 +0300 Subject: [PATCH] Initial commit --- .gitignore | 4 + data/appointments.csv | 21 ++++ data/patients.json | 72 ++++++++++++++ requirements.txt | 0 src/main.py | 223 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 320 insertions(+) create mode 100644 .gitignore create mode 100644 data/appointments.csv create mode 100644 data/patients.json create mode 100644 requirements.txt create mode 100644 src/main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53f171e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.venv/ +__pycache__/ +*.pyc +insurance_report.json \ No newline at end of file diff --git a/data/appointments.csv b/data/appointments.csv new file mode 100644 index 0000000..98f428c --- /dev/null +++ b/data/appointments.csv @@ -0,0 +1,21 @@ +appointment_id,patient_id,doctor,date,time,status +A001,P001,Смирнова,20.04.2026,10:00,timetable +A002,P001,Кузнецов,15.03.2026,14:30,completed +A003,P002,Смирнова,18.03.2026,09:00,missed +A004,P003,Кузнецов,22.04.2026,11:15,timetable +A005,P002,Волкова,19.04.2026,15:45,timetable +A006,P004,Смирнова,17.03.2026,13:20,completed +A007,P005,Кузнецов,21.04.2026,08:30,timetable +A008,P003,Волкова,23.03.2026,12:00,cancelled +A009,P005,Смирнова,16.03.2026,16:10,completed +A010,P004,Кузнецов,24.03.2026,09:30,timetable +A011,P006,Волкова,23.04.2026,14:00,timetable +A012,P006,Смирнова,25.04.2026,11:30,completed +A013,P007,Кузнецов,24.04.2026,09:15,timetable +A014,P007,Волкова,26.04.2026,16:45,scheduled +A015,P008,Смирнова,22.04.2026,13:20,missed +A016,P008,Кузнецов,27.04.2026,10:00,timetable +A017,P009,Волкова,23.04.2026,12:10,missed +A018,P009,Смирнова,28.04.2026,15:30,timetable +A019,P010,Кузнецов,24.04.2026,08:45,missed +A020,P010,Волкова,29.04.2026,14:50,timetable \ No newline at end of file diff --git a/data/patients.json b/data/patients.json new file mode 100644 index 0000000..0dded9a --- /dev/null +++ b/data/patients.json @@ -0,0 +1,72 @@ +[ + { + "patient_id": "P001", + "full_name": "Пересчетов Иван Иванович", + "birth_date": "15.06.1985", + "insurance": "РосГосСтрах", + "phone": "+7-999-123-4567" + }, + { + "patient_id": "P002", + "full_name": "Выпивалова Анна Сергеевна", + "birth_date": "23.11.1990", + "insurance": "Согаз-МЕД", + "phone": "89161234567" + }, + { + "patient_id": "P003", + "full_name": "Перекатиполе Павел Николаевич", + "birth_date": "10.03.1978", + "insurance": "ЗастрахуйБратуху", + "phone": "+7-903-111-2233" + }, + { + "patient_id": "P004", + "full_name": "Пивоварова Елена Иннокентьевна", + "birth_date": "01.07.2000", + "insurance": "Макс-М", + "phone": "+7-985-777-8899" + }, + { + "patient_id": "P005", + "full_name": "Красноносов Дмитрий Евграфович", + "birth_date": "30.12.1965", + "insurance": "Согаз-МЕД", + "phone": "8-800-555-3535" + }, + { + "patient_id": "P006", + "full_name": "Зайцева-Кроликова Ольга Владимировна", + "birth_date": "12.09.1995", + "insurance": "РосГосСтрах", + "phone": "+7-912-345-6789" + }, + { + "patient_id": "P007", + "full_name": "Соколовский Артём Дмитриевич", + "birth_date": "25.04.1988", + "insurance": "Макс-М", + "phone": "+7-922-111-2233" + }, + { + "patient_id": "P008", + "full_name": "Лебедева-Гусева Мария Петровна", + "birth_date": "03.11.2002", + "insurance": "Согаз-МЕД", + "phone": "89205556677" + }, + { + "patient_id": "P009", + "full_name": "Грошкин Андрей Сергеевич", + "birth_date": "19.07.1975", + "insurance": "ЗастрахуйБратуху", + "phone": "+7-951-444-5566" + }, + { + "patient_id": "P010", + "full_name": "Тимофеева-Акакьева Екатерина Алексеевна", + "birth_date": "28.02.1999", + "insurance": "РосГосСтрах", + "phone": "8-953-777-8899" + } +] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..aedfbc9 --- /dev/null +++ b/src/main.py @@ -0,0 +1,223 @@ +import json +import csv +from datetime import datetime, date, timedelta + + +#загрузка данных пациента из json +def Load_Patients(filepath: str) -> list[dict]: + try: + with open(filepath, 'r', encoding='utf-8') as f: + return json.load(f) + except FileNotFoundError: + print(f"Ошибка: файл {filepath} не найден.") + return [] + except json.JSONDecodeError: + print(f"Ошибка: файл {filepath} содержит некорректный JSON.") + return [] + +#загрузка записей из csv +def Load_Appointments(filepath: str) -> list[dict]: + try: + with open(filepath, 'r', encoding='utf-8') as f: + reader = csv.DictReader(f) + return list(reader) + except FileNotFoundError: + print(f"Ошибка: файл {filepath} не найден.") + return [] + except Exception as e: + print(f"Ошибка при чтении CSV: {e}") + return [] + +#вычисление возраста пацинта +def Calculate_Age(birth_date_str: str) -> int: + birth = datetime.strptime(birth_date_str, "%d.%m.%Y").date() + today = date.today() + age = today.year - birth.year - ((today.month, today.day) < (birth.month, birth.day)) + return age + +def Validate_Patient(patient: dict) -> tuple[bool, str]: + required_fields = {"patient_id", "full_name", "birth_date", "insurance", "phone"} + missing = required_fields - patient.keys() + if missing: + return False, f"отсутствуют поля: {missing}" + try: + age = Calculate_Age(patient["birth_date"]) + if not (0 < age < 120): + return False, f"возраст не корректен. Возраст: {age}" + except Exception: + return False, "некорректная дата рождения" + + + phone = patient["phone"] + allowed_chars = set("0123456789+- ") + if not all(ch in allowed_chars for ch in phone) or len(phone)<6: + return False, f"некорректный телефон: {phone}" + + return True, "" + +def Calculate_Average_Age(patients: list[dict]) -> float: + if not patients: + return 0.0 + total_age = sum(Calculate_Age(p["birth_date"]) for p in patients) + return total_age / len(patients) + +def Get_Patients_by_Insurance(patients: list[dict], insurer: str) -> list[dict]: + insurer_lower = insurer.lower() + return [p for p in patients if p["insurance"].lower() == insurer_lower] + +def Count_Appointments_per_Patient(appointments: list[dict]) -> dict[str, int]: + counts = {} + for app in appointments: + pid = app["patient_id"] + counts[pid] = counts.get(pid, 0) + 1 + return counts + +def Get_Missed_Appointments(appointments: list[dict]) -> list[dict]: + return [app for app in appointments if app.get("status") == "missed"] + +def Calculate_Doctor_Workload(appointments: list[dict], start_date: str, end_date: str) -> dict[str, int]: + workload = {} + start = datetime.strptime(start_date, "%d.%m.%Y").date() + end = datetime.strptime(end_date, "%d.%m.%Y").date() + + for app in appointments: + try: + app_date = datetime.strptime(app["date"], "%d.%m.%Y").date() + if start <= app_date <= end: + doctor = app["doctor"] + workload[doctor] = workload.get(doctor, 0) + 1 + except (KeyError, ValueError): + continue + return workload + +def Generate_Weekly_Timetable(appointments: list[dict], start_date: str) -> list[dict]: + start = datetime.strptime(start_date, "%d.%m.%Y").date() + end = start + timedelta(days=6) + + filtered = [] + for app in appointments: + try: + app_date = datetime.strptime(app["date"], "%d.%m.%Y").date() + if start <= app_date <= end: + filtered.append(app) + except (KeyError, ValueError): + continue + filtered.sort(key=lambda x: datetime.strptime(x["date"], "%d.%m.%Y")) + return filtered + +def Export_Insurance_Report(patients: list[dict], appointments: list[dict], filename: str) -> str: + #словарь страховая -> количество пациентов + insurance_patients = {} + for p in patients: + ins = p["insurance"] + insurance_patients[ins] = insurance_patients.get(ins, 0) + 1 + + #словарь patient_id -> страховая + patient_insurance = {p["patient_id"]: p["insurance"] for p in patients} + + #число приёмов со статусом timetable или completed + insurance_appointments = {} + for app in appointments: + status = app.get("status", "") + if status in ("timetable", "completed"): + pid = app["patient_id"] + ins = patient_insurance.get(pid) + if ins: + insurance_appointments[ins] = insurance_appointments.get(ins, 0) + 1 + + #итоговый отчёт + report = {} + all_insurances = set(insurance_patients.keys()) | set(insurance_appointments.keys()) + for ins in all_insurances: + report[ins] = { + "patient_count": insurance_patients.get(ins, 0), + "timetable_completed_appointments": insurance_appointments.get(ins, 0) + } + + with open(filename, 'w', encoding='utf-8') as f: + json.dump(report, f, ensure_ascii=False, indent=4) + + return f"Report saved to {filename}" + + + +#Демонстрация +def main(): + #1. Загрузка данных + patients = Load_Patients('data/patients.json') + if not patients: + print("Нет данных о пациентах. Завершение.") + return + appointments = Load_Appointments('data/appointments.csv') + if not appointments: + print("Нет данных о записях. Завершение.") + return + + # 2.Валидация пациентов + print("\n=== Проверка пациентов ===") + for patient in patients: + valid, msg = Validate_Patient(patient) + if not valid: + print(f"Ошибка у пациента {patient['patient_id']}: {msg}") + + #3. Средний возраст + avg_age = Calculate_Average_Age(patients) + print(f"\n=== Средний возраст пациентов ===") + print(f" {avg_age:.1f} лет") + + #4.Пациенты по страховке + insurer = "Согаз-МЕД" + filtered_patients = Get_Patients_by_Insurance(patients, insurer) + print(f"\n=== Пациенты со страховкой {insurer} ===") + for p in filtered_patients: + print(f" {p['full_name']} ({p['patient_id']})") + + # 5. Количество приёмов на пациента + appointment_counts = Count_Appointments_per_Patient(appointments) + print("\n=== Количество приёмов по пациентам ===") + for pid, count in appointment_counts.items(): + print(f" {pid}: {count}") + + # 6.Пропущенные приёмы + missed = Get_Missed_Appointments(appointments) + print("\n=== Пропущенные приёмы ===") + for app in missed: + print(f" {app['appointment_id']} — пациент {app['patient_id']}, врач {app['doctor']}") + + #7. Нагрузка врачей за текущий месяц + today = datetime.today() + start_month = date(today.year, today.month, 1).strftime("%d.%m.%Y") + if today.month == 12: + end_month = date(today.year + 1, 1, 1) - timedelta(days=1) + else: + end_month = date(today.year, today.month + 1, 1) - timedelta(days=1) + end_month_str = end_month.strftime("%d.%m.%Y") + + workload = Calculate_Doctor_Workload(appointments, start_month, end_month_str) + months_ru = { + 1: "Январь", 2: "Февраль", 3: "Март", 4: "Апрель", + 5: "Май", 6: "Июнь", 7: "Июль", 8: "Август", + 9: "Сентябрь", 10: "Октябрь", 11: "Ноябрь", 12: "Декабрь" + } + day, month, year = map(int, start_month.split('.')) + print(f"\n=== Нагрузка врачей за {months_ru[month]} {year} ===") + for doctor, count in workload.items(): + print(f" {doctor}: {count} приёмов") + + # 8.Расписание на неделю с понедельника + days_from_monday = today.weekday() # понедельник = 0 + start_week = (today.date() - timedelta(days=days_from_monday)).strftime("%d.%m.%Y") + weekly = Generate_Weekly_Timetable(appointments, start_week) + print(f"\n=== Расписание на неделю с {start_week} ===") + for app in weekly: + print(f" {app['date']} {app['time']} — {app['doctor']}, пациент {app['patient_id']}") + + # 9. Экспорт отчёта по страховкам + result = Export_Insurance_Report(patients, appointments, 'insurance_report.json') + print(f"\n=== {result} ===") + + print("\n=== Демонстрация завершена ===") + + +if __name__ == "__main__": + main() \ No newline at end of file