Initial commit
This commit is contained in:
commit
bcd7c35c5c
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
insurance_report.json
|
||||||
21
data/appointments.csv
Normal file
21
data/appointments.csv
Normal file
@ -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
|
||||||
|
72
data/patients.json
Normal file
72
data/patients.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
0
requirements.txt
Normal file
0
requirements.txt
Normal file
223
src/main.py
Normal file
223
src/main.py
Normal file
@ -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()
|
||||||
Loading…
Reference in New Issue
Block a user