Spaces:
Build error
Build error
| import streamlit as st | |
| import sqlite3 | |
| import pandas as pd | |
| from datetime import datetime | |
| import os | |
| import shutil | |
| import altair as alt # برای چارتها | |
| # تنظیمات اولیه | |
| st.set_page_config(layout="wide", page_title="سیستم مدیریت بیماران بهداری") | |
| st.markdown(""" | |
| <style> | |
| body { direction: rtl; text-align: right; font-family: 'Tahoma'; } | |
| .stSidebar { background-color: #f0f0f0; } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # توابع تبدیل تاریخ بدون لایبرری | |
| def gregorian_to_jalali(gy, gm, gd): | |
| g_d_m = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] | |
| gy -= 1600 | |
| gm -= 1 | |
| gd -= 1 | |
| g_day_no = 365 * gy + (gy + 3) // 4 - (gy + 99) // 100 + (gy + 399) // 400 | |
| for i in range(gm): | |
| g_day_no += g_d_m[i] | |
| if gm > 1 and ((gy % 4 == 0 and gy % 100 != 0) or (gy % 400 == 0)): | |
| g_day_no += 1 | |
| g_day_no += gd | |
| j_day_no = g_day_no - 79 | |
| j_np = j_day_no // 12053 | |
| j_day_no %= 12053 | |
| jy = 979 + 33 * j_np + 4 * (j_day_no // 1461) | |
| j_day_no %= 1461 | |
| if j_day_no >= 366: | |
| jy += (j_day_no - 1) // 365 | |
| j_day_no = (j_day_no - 1) % 365 | |
| j_days_in_month = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29] | |
| i = 0 | |
| while i < 11 and j_day_no >= j_days_in_month[i]: | |
| j_day_no -= j_days_in_month[i] | |
| i += 1 | |
| jm = i + 1 | |
| jd = j_day_no + 1 | |
| return jy, jm, jd | |
| def jalali_to_gregorian(jy, jm, jd): | |
| jy += 1595 | |
| days = -355668 + (365 * jy) + (jy // 33 * 8) + ((jy % 33) // 4) + jd | |
| if jm < 7: | |
| days += (jm - 1) * 31 | |
| else: | |
| days += ((jm - 7) * 30) + 186 | |
| gy = 400 * (days // 146097) | |
| days %= 146097 | |
| if days > 36524: | |
| days -= 36525 | |
| gy += 100 | |
| gy += 4 * (days // 1461) | |
| days %= 1461 | |
| if days > 365: | |
| gy += (days - 1) // 365 | |
| days = (days - 1) % 365 | |
| gd = days + 1 | |
| if (gy % 4 == 0 and gy % 100 != 0) or (gy % 400 == 0): | |
| sal_a = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335] | |
| else: | |
| sal_a = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] | |
| gm = 0 | |
| while gm < 13 and gd > sal_a[gm]: | |
| gm += 1 | |
| gd -= sal_a[gm - 1] | |
| return gy, gm, gd | |
| # اتصال به DB | |
| DB_FILE = "health_unit.db" | |
| BACKUP_DIR = "backups" | |
| if not os.path.exists(BACKUP_DIR): | |
| os.makedirs(BACKUP_DIR) | |
| def init_db(): | |
| conn = sqlite3.connect(DB_FILE) | |
| c = conn.cursor() | |
| # جدول کاربران | |
| c.execute('''CREATE TABLE IF NOT EXISTS users | |
| (id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| username TEXT, | |
| password TEXT, | |
| role TEXT)''') | |
| # هش رمز نمونه | |
| def hash_pw(pw): return hashlib.sha256(pw.encode()).hexdigest() | |
| # دادههای اولیه کاربران | |
| users = [ | |
| ('admin', hash_pw('admin'), 'admin'), | |
| ('doctor_zakizadeh', hash_pw('password'), 'doctor_zakizadeh'), | |
| ('receptionist', hash_pw('password'), 'receptionist'), | |
| ('hsee_zali', hash_pw('password'), 'hsee_zali') | |
| ] | |
| for u in users: | |
| c.execute("INSERT OR IGNORE INTO users (username, password, role) VALUES (?, ?, ?)", u) | |
| # جدول بیماران | |
| c.execute('''CREATE TABLE IF NOT EXISTS patients | |
| (id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| first_name TEXT, | |
| last_name TEXT, | |
| national_id TEXT UNIQUE, | |
| personnel_id TEXT UNIQUE, | |
| gender TEXT, | |
| age INTEGER, | |
| job TEXT, | |
| phone TEXT, | |
| department TEXT, | |
| medical_history TEXT, | |
| blood_type TEXT, | |
| vaccination_status TEXT)''') | |
| # جدول ویزیتها | |
| c.execute('''CREATE TABLE IF NOT EXISTS visits | |
| (id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| patient_id INTEGER, | |
| visit_date TEXT, -- جلالی string YYYY/MM/DD | |
| symptoms TEXT, | |
| history TEXT, | |
| physical_exam TEXT, | |
| diagnosis TEXT, | |
| prescribed_medications TEXT, | |
| recommendations TEXT, | |
| needs_lab BOOLEAN, | |
| needs_referral BOOLEAN, | |
| needs_hospitalization BOOLEAN)''') | |
| # جدول معاینات ادواری | |
| c.execute('''CREATE TABLE IF NOT EXISTS periodic_examinations | |
| (id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| department TEXT, | |
| scheduled_date TEXT, | |
| notified BOOLEAN DEFAULT 0)''') | |
| # جدول داروها | |
| c.execute('''CREATE TABLE IF NOT EXISTS medications | |
| (id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| name TEXT, | |
| stock_quantity INTEGER DEFAULT 100)''') | |
| # دادههای اولیه داروها | |
| meds = ['آسپیرین', 'ایبوپروفن', 'پاراستامول', 'آموکسیسیلین', 'لوراتادین', 'امپرازول', 'آتورواستاتین', 'متفورمین', 'لوزارتان', 'کلوپیدوگرل', | |
| 'دیکلوفناک', 'رانیتیدین', 'سیپروفلوکساسین', 'آزیترومایسین', 'کدئین', 'ترامادول', 'دیازپام', 'آلپرازولام', 'سرترالین', 'فلوکستین'] | |
| for m in meds: | |
| c.execute("INSERT OR IGNORE INTO medications (name) VALUES (?)", (m,)) | |
| # جدول مصرف دارو | |
| c.execute('''CREATE TABLE IF NOT EXISTS medication_usage | |
| (id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| medication_id INTEGER, | |
| visit_id INTEGER, | |
| quantity INTEGER, | |
| date_used TEXT, | |
| department TEXT)''') | |
| # جدول بخشها | |
| c.execute('''CREATE TABLE IF NOT EXISTS departments | |
| (id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| name TEXT UNIQUE)''') | |
| depts = ["معاونت کشاورزی", "مطالعات کاربردی", "مهندسی زراعی", "تولید یکم", "تولید دوم", | |
| "اداری", "بازرگانی", "حراست", "انبار", "کارخانه", "یارد", "تجهیزات مکانیکی", | |
| "دفتر فنی", "غیر نیشکری"] | |
| for d in depts: | |
| c.execute("INSERT OR IGNORE INTO departments (name) VALUES (?)", (d,)) | |
| conn.commit() | |
| conn.close() | |
| init_db() | |
| # توابع کمکی | |
| def get_jalali_today(): | |
| now = datetime.now() | |
| jy, jm, jd = gregorian_to_jalali(now.year, now.month, now.day) | |
| return f"{jy:04d}/{jm:02d}/{jd:02d}" | |
| # چک نوتیفیکیشنها | |
| def check_notifications(): | |
| today_str = get_jalali_today() | |
| ty, tm, td = map(int, today_str.split('/')) | |
| conn = sqlite3.connect(DB_FILE) | |
| c = conn.cursor() | |
| c.execute("SELECT department, scheduled_date FROM periodic_examinations WHERE notified=0") | |
| exams = c.fetchall() | |
| notifications = [] | |
| for dept, sch_date in exams: | |
| sy, sm, sd = map(int, sch_date.split('/')) | |
| # تبدیل به gregorian برای محاسبه تفاوت | |
| g_sch_y, g_sch_m, g_sch_d = jalali_to_gregorian(sy, sm, sd) | |
| g_today_y, g_today_m, g_today_d = jalali_to_gregorian(ty, tm, td) | |
| sch_date_g = datetime(g_sch_y, g_sch_m, g_sch_d) | |
| today_g = datetime(g_today_y, g_today_m, g_today_d) | |
| days_diff = (sch_date_g - today_g).days | |
| if days_diff == 3: | |
| notifications.append(f"معاینه ادواری برای بخش {dept} در {sch_date}") | |
| c.execute("UPDATE periodic_examinations SET notified=1 WHERE department=? AND scheduled_date=?", (dept, sch_date)) | |
| conn.commit() | |
| conn.close() | |
| return notifications | |
| # مدیریت جلسه | |
| if 'logged_in' not in st.session_state: | |
| st.session_state.logged_in = False | |
| if 'role' not in st.session_state: | |
| st.session_state.role = None | |
| # صفحه ورود | |
| def login_page(): | |
| st.title("ورود به سیستم") | |
| username = st.text_input("نام کاربری") | |
| password = st.text_input("رمز عبور", type="password") | |
| if st.button("ورود"): | |
| import hashlib | |
| conn = sqlite3.connect(DB_FILE) | |
| c = conn.cursor() | |
| hash_pw = hashlib.sha256(password.encode()).hexdigest() | |
| c.execute("SELECT role FROM users WHERE username=? AND password=?", (username, hash_pw)) | |
| result = c.fetchone() | |
| conn.close() | |
| if result: | |
| st.session_state.logged_in = True | |
| st.session_state.role = result[0] | |
| st.rerun() | |
| else: | |
| st.error("نام کاربری یا رمز عبور اشتباه است") | |
| # داشبورد | |
| def dashboard(): | |
| st.title("داشبورد") | |
| notifications = check_notifications() | |
| if notifications: | |
| st.warning("اعلانها:") | |
| for n in notifications: | |
| st.write(n) | |
| # ثبت بیمار | |
| def register_patient(): | |
| st.title("ثبت بیمار جدید") | |
| with st.form("register_form"): | |
| first_name = st.text_input("نام") | |
| last_name = st.text_input("نام خانوادگی") | |
| national_id = st.text_input("کد ملی") | |
| personnel_id = st.text_input("شماره پرسنلی") | |
| gender = st.selectbox("جنسیت", ["مرد", "زن"]) | |
| age = st.number_input("سن", min_value=0) | |
| job = st.text_input("شغل") | |
| phone = st.text_input("شماره تماس") | |
| conn = sqlite3.connect(DB_FILE) | |
| c = conn.cursor() | |
| c.execute("SELECT name FROM departments") | |
| depts = [row[0] for row in c.fetchall()] | |
| conn.close() | |
| department = st.selectbox("بخش", depts) | |
| medical_history = st.text_area("سابقه بیماری") | |
| blood_type = st.text_input("گروه خونی") | |
| vaccination_status = st.text_input("وضعیت واکسیناسیون") | |
| submitted = st.form_submit_button("ثبت") | |
| if submitted: | |
| conn = sqlite3.connect(DB_FILE) | |
| c = conn.cursor() | |
| try: | |
| c.execute("SELECT id FROM patients WHERE national_id=? OR personnel_id=?", (national_id, personnel_id)) | |
| if c.fetchone(): | |
| st.error("کد ملی یا شماره پرسنلی تکراری است") | |
| else: | |
| c.execute("""INSERT INTO patients (first_name, last_name, national_id, personnel_id, gender, age, job, phone, department, medical_history, blood_type, vaccination_status) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", | |
| (first_name, last_name, national_id, personnel_id, gender, age, job, phone, department, medical_history, blood_type, vaccination_status)) | |
| conn.commit() | |
| st.success("بیمار ثبت شد") | |
| except Exception as e: | |
| st.error(f"خطا: {e}") | |
| conn.close() | |
| # جستجو بیمار | |
| def search_patient(query): | |
| conn = sqlite3.connect(DB_FILE) | |
| c = conn.cursor() | |
| c.execute("SELECT * FROM patients WHERE national_id LIKE ? OR last_name LIKE ?", (f"%{query}%", f"%{query}%")) | |
| results = c.fetchall() | |
| conn.close() | |
| return results | |
| # ویزیت بیمار | |
| def visit_patient(): | |
| st.title("ویزیت بیمار") | |
| query = st.text_input("جستجو با کد ملی یا نام خانوادگی", key="search_key") | |
| if query: | |
| patients = search_patient(query) | |
| if patients: | |
| patient_options = {f"{p[1]} {p[2]} - کد ملی: {p[3]}": p[0] for p in patients} | |
| selected = st.selectbox("انتخاب بیمار", list(patient_options.keys())) | |
| if selected: | |
| selected_id = patient_options[selected] | |
| with st.form("visit_form"): | |
| visit_date = st.text_input("تاریخ ویزیت (جلالی YYYY/MM/DD)", value=get_jalali_today()) | |
| symptoms = st.text_area("علائم بیماری") | |
| history = st.text_area("شرح حال") | |
| physical_exam = st.text_area("معاینه فیزیکی") | |
| diagnosis = st.text_area("تشخیص پزشک") | |
| conn = sqlite3.connect(DB_FILE) | |
| c = conn.cursor() | |
| c.execute("SELECT id, name FROM medications") | |
| meds = {row[1]: row[0] for row in c.fetchall()} | |
| selected_meds = st.multiselect("داروهای تجویز شده", list(meds.keys())) | |
| recommendations = st.text_area("توصیهها") | |
| needs_lab = st.checkbox("نیاز به آزمایش") | |
| needs_referral = st.checkbox("نیاز به ارجاع") | |
| needs_hospitalization = st.checkbox("نیاز به بستری") | |
| submitted = st.form_submit_button("ثبت ویزیت") | |
| if submitted: | |
| prescribed = ','.join(selected_meds) | |
| try: | |
| c.execute("""INSERT INTO visits (patient_id, visit_date, symptoms, history, physical_exam, diagnosis, prescribed_medications, recommendations, needs_lab, needs_referral, needs_hospitalization) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", | |
| (selected_id, visit_date, symptoms, history, physical_exam, diagnosis, prescribed, recommendations, needs_lab, needs_referral, needs_hospitalization)) | |
| visit_id = c.lastrowid | |
| for m in selected_meds: | |
| med_id = meds[m] | |
| c.execute("UPDATE medications SET stock_quantity = stock_quantity - 1 WHERE id=?", (med_id,)) | |
| c.execute("""INSERT INTO medication_usage (medication_id, visit_id, quantity, date_used, department) | |
| VALUES (?, ?, ?, ?, ?)""", (med_id, visit_id, 1, visit_date, patients[0][8])) # department | |
| conn.commit() | |
| st.success("ویزیت ثبت شد") | |
| except Exception as e: | |
| st.error(f"خطا: {e}") | |
| conn.close() | |
| else: | |
| st.info("بیماری یافت نشد") | |
| # برنامه معاینات | |
| def schedules(): | |
| st.title("برنامه معاینات ادواری") | |
| conn = sqlite3.connect(DB_FILE) | |
| c = conn.cursor() | |
| c.execute("SELECT name FROM departments") | |
| depts = [row[0] for row in c.fetchall()] | |
| department = st.selectbox("بخش", depts) | |
| scheduled_date = st.text_input("تاریخ برنامهریزی (جلالی YYYY/MM/DD)") | |
| if st.button("اضافه کردن"): | |
| try: | |
| c.execute("INSERT INTO periodic_examinations (department, scheduled_date) VALUES (?, ?)", (department, scheduled_date)) | |
| conn.commit() | |
| st.success("برنامه اضافه شد") | |
| except Exception as e: | |
| st.error(f"خطا: {e}") | |
| c.execute("SELECT * FROM periodic_examinations") | |
| df = pd.DataFrame(c.fetchall(), columns=['ID', 'بخش', 'تاریخ', 'اعلان شده']) | |
| st.dataframe(df) | |
| conn.close() | |
| # گزارشات | |
| def reports(): | |
| st.title("گزارشگیری") | |
| conn = sqlite3.connect(DB_FILE) | |
| c = conn.cursor() | |
| start_date = st.text_input("از تاریخ (جلالی YYYY/MM/DD)") | |
| end_date = st.text_input("تا تاریخ (جلالی YYYY/MM/DD)") | |
| c.execute("SELECT name FROM departments") | |
| depts = ['همه'] + [row[0] for row in c.fetchall()] | |
| department = st.selectbox("بخش", depts) | |
| where = "" | |
| params = [] | |
| if start_date: | |
| where += " AND v.visit_date >= ?" | |
| params.append(start_date) | |
| if end_date: | |
| where += " AND v.visit_date <= ?" | |
| params.append(end_date) | |
| if department != 'همه': | |
| where += " AND p.department = ?" | |
| params.append(department) | |
| # بیشترین بیماریها | |
| query = f"""SELECT diagnosis, COUNT(*) as count FROM visits v | |
| JOIN patients p ON v.patient_id = p.id | |
| WHERE 1=1 {where} | |
| GROUP BY diagnosis ORDER BY count DESC""" | |
| df_diseases = pd.read_sql_query(query, conn, params=params) | |
| st.subheader("بیشترین بیماریها") | |
| st.dataframe(df_diseases) | |
| chart = alt.Chart(df_diseases).mark_bar().encode(x='diagnosis', y='count', color='diagnosis').properties(title="نمودار بیماریها") | |
| st.altair_chart(chart, use_container_width=True) | |
| # درصد | |
| total = df_diseases['count'].sum() | |
| if total > 0: | |
| df_diseases['percent'] = df_diseases['count'] / total * 100 | |
| st.subheader("تفکیک درصدی بیماریها") | |
| st.dataframe(df_diseases) | |
| # موجودی دارو | |
| st.subheader("موجودی داروها") | |
| df_meds = pd.read_sql_query("SELECT name, stock_quantity FROM medications", conn) | |
| st.dataframe(df_meds) | |
| # مصرف دارو | |
| st.subheader("داروهای مصرف شده") | |
| query_usage = f"""SELECT m.name, u.quantity, u.date_used, u.department FROM medication_usage u | |
| JOIN medications m ON u.medication_id = m.id | |
| WHERE 1=1 {where.replace('v.', 'u.').replace('p.', 'u.')}""" # تقریبی | |
| df_usage = pd.read_sql_query(query_usage, conn, params=params) | |
| st.dataframe(df_usage) | |
| conn.close() | |
| # بکاپ | |
| def backup_db(): | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| backup_file = os.path.join(BACKUP_DIR, f"backup_{timestamp}.db") | |
| shutil.copy(DB_FILE, backup_file) | |
| st.success(f"بکاپ گرفته شد: {backup_file}") | |
| # منوی اصلی | |
| if not st.session_state.logged_in: | |
| login_page() | |
| else: | |
| with st.sidebar: | |
| st.title("منو") | |
| page = st.radio("صفحه", ["داشبورد", "ثبت بیمار", "ویزیت بیمار", "برنامه معاینات", "گزارشات", "بکاپ"]) | |
| if st.button("خروج"): | |
| st.session_state.logged_in = False | |
| st.session_state.role = None | |
| st.rerun() | |
| if page == "داشبورد": | |
| dashboard() | |
| elif page == "ثبت بیمار" and st.session_state.role in ['admin', 'receptionist']: | |
| register_patient() | |
| elif page == "ویزیت بیمار" and st.session_state.role in ['admin', 'doctor_zakizadeh']: | |
| visit_patient() | |
| elif page == "برنامه معاینات" and st.session_state.role in ['admin', 'doctor_zakizadeh']: | |
| schedules() | |
| elif page == "گزارشات" and st.session_state.role in ['admin', 'hsee_zali', 'doctor_zakizadeh']: | |
| reports() | |
| elif page == "بکاپ" and st.session_state.role == 'admin': | |
| if st.button("گرفتن بکاپ"): | |
| backup_db() | |
| else: | |
| st.error("دسترسی مجاز نیست") | |
| # برای Hugging Face Spaces: | |
| # 1. فایل app.py ایجاد کنید با این کد. | |
| # 2. requirements.txt با: streamlit pandas altair | |
| # 3. DB در runtime ایجاد میشود. | |
| # توجه: Hugging Face persistent storage دارد، پس DB حفظ میشود. | |
| # برای جستجوی بلادرنگ، از key در text_input استفاده شد، اما برای debounce، Streamlit builtin است. |