Spaces:
No application file
No application file
Upload app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,935 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sqlite3
|
| 3 |
+
import random
|
| 4 |
+
from io import BytesIO
|
| 5 |
+
from datetime import date, datetime, timedelta
|
| 6 |
+
import tempfile
|
| 7 |
+
|
| 8 |
+
import pandas as pd
|
| 9 |
+
import streamlit as st
|
| 10 |
+
from openpyxl import Workbook
|
| 11 |
+
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
|
| 12 |
+
from openpyxl.utils import get_column_letter
|
| 13 |
+
from openpyxl.drawing.image import Image as XLImage
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# =========================================================
|
| 17 |
+
# BeeCare ํตํฉ์ฐ๋ ์ํ๋ณํ๊ธฐ๋ก์ง ์์ฑ๊ธฐ
|
| 18 |
+
# - Client Center ์๊ธ์ DB ์ฐ๋
|
| 19 |
+
# - ๋ชจ๋ ๊ธฐ๊ด์ ํ ๊ธฐ๋ณธ ์ฃผ 1ํ ์์ฑ
|
| 20 |
+
# - ํ์ ์ ์ถ๊ฐ์์ฑ ๊ฐ๋ฅ
|
| 21 |
+
# - ์ํ๋ณํ ๋ฐ์ ์ ๊ธ์ฌ์ ๊ณต๊ณํ ์ฌ์์ฑ ํ์ ์ฌ๋ถ ์๋ ํ๋จ
|
| 22 |
+
# - ์๋ด์ผ์ง/์๊ตฌ์ฌ์ /์ฌ๋ก๊ด๋ฆฌ/๊ธ์ฌ๊ณํ๊ณผ ์ฐ๊ฒฐ ๊ฐ๋ฅํ DB ์ ์ฅ
|
| 23 |
+
# - ์์
๋ค์ด๋ก๋, ์ฌ์ง์ฒจ๋ถ, ํ๊ฐ ์ ํฉ๋ ์ ๊ฒ, ๊ฒฐ์ฌ๋ผ์ธ 2์นธ
|
| 24 |
+
# =========================================================
|
| 25 |
+
|
| 26 |
+
st.set_page_config(
|
| 27 |
+
page_title="์ํ๋ณํ๊ธฐ๋ก์ง",
|
| 28 |
+
page_icon="๐ฉบ",
|
| 29 |
+
layout="wide"
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 33 |
+
|
| 34 |
+
DB_CANDIDATES = [
|
| 35 |
+
os.path.join(CURRENT_DIR, "data", "clients.db"),
|
| 36 |
+
os.path.join(CURRENT_DIR, "..", "Client Center", "data", "clients.db"),
|
| 37 |
+
os.path.join(CURRENT_DIR, "..", "ClientCenter", "data", "clients.db"),
|
| 38 |
+
os.path.join(CURRENT_DIR, "..", "Client_Center", "data", "clients.db"),
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
DB_PATH = None
|
| 42 |
+
for p in DB_CANDIDATES:
|
| 43 |
+
if os.path.exists(p):
|
| 44 |
+
DB_PATH = p
|
| 45 |
+
break
|
| 46 |
+
|
| 47 |
+
if DB_PATH is None:
|
| 48 |
+
os.makedirs(os.path.join(CURRENT_DIR, "data"), exist_ok=True)
|
| 49 |
+
DB_PATH = os.path.join(CURRENT_DIR, "data", "clients.db")
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
# -----------------------------
|
| 53 |
+
# UI ๋์์ธ
|
| 54 |
+
# -----------------------------
|
| 55 |
+
st.markdown("""
|
| 56 |
+
<style>
|
| 57 |
+
.block-container { padding-top: 2rem; }
|
| 58 |
+
.hero {
|
| 59 |
+
background: linear-gradient(135deg, #1D4ED8, #60A5FA);
|
| 60 |
+
padding: 30px;
|
| 61 |
+
border-radius: 22px;
|
| 62 |
+
text-align: center;
|
| 63 |
+
margin-bottom: 22px;
|
| 64 |
+
}
|
| 65 |
+
.hero h1 { color: white; margin: 0; font-size: 36px; }
|
| 66 |
+
.hero p { color: white; margin-top: 10px; font-size: 18px; }
|
| 67 |
+
.notice {
|
| 68 |
+
background: #fff8e1;
|
| 69 |
+
border-left: 7px solid #f9a825;
|
| 70 |
+
padding: 15px;
|
| 71 |
+
border-radius: 12px;
|
| 72 |
+
margin-bottom: 18px;
|
| 73 |
+
}
|
| 74 |
+
.okbox {
|
| 75 |
+
background: #e8f5e9;
|
| 76 |
+
border-left: 7px solid #43a047;
|
| 77 |
+
padding: 14px;
|
| 78 |
+
border-radius: 12px;
|
| 79 |
+
margin-bottom: 14px;
|
| 80 |
+
}
|
| 81 |
+
.warnbox {
|
| 82 |
+
background: #fff3e0;
|
| 83 |
+
border-left: 7px solid #fb8c00;
|
| 84 |
+
padding: 14px;
|
| 85 |
+
border-radius: 12px;
|
| 86 |
+
margin-bottom: 14px;
|
| 87 |
+
}
|
| 88 |
+
.dangerbox {
|
| 89 |
+
background: #ffebee;
|
| 90 |
+
border-left: 7px solid #e53935;
|
| 91 |
+
padding: 14px;
|
| 92 |
+
border-radius: 12px;
|
| 93 |
+
margin-bottom: 14px;
|
| 94 |
+
}
|
| 95 |
+
</style>
|
| 96 |
+
""", unsafe_allow_html=True)
|
| 97 |
+
|
| 98 |
+
st.markdown("""
|
| 99 |
+
<div class="hero">
|
| 100 |
+
<h1>๐ฉบ ์ํ๋ณํ๊ธฐ๋ก์ง ์์ฑ๊ธฐ</h1>
|
| 101 |
+
<p>Client Center ์๊ธ์ ์ ๋ณด์ ์ฐ๋ํ์ฌ ์ฃผ 1ํ ์ํ๋ณํ๊ธฐ๋ก ๋ฐ ์ถ๊ฐ๊ธฐ๋ก์ ์์ฑํฉ๋๋ค.</p>
|
| 102 |
+
</div>
|
| 103 |
+
""", unsafe_allow_html=True)
|
| 104 |
+
|
| 105 |
+
st.markdown("""
|
| 106 |
+
<div class="notice">
|
| 107 |
+
<strong>์์ฑ ์์น:</strong> ๋ชจ๋ ๊ธฐ๊ด์ ํ์ ๊ธฐ๋ณธ ์ฃผ 1ํ ์์ฑ์ผ๋ก ์ค๊ณํ๊ณ , ๋์ยท์
์ยทํด์ยท์์ฐฝยท์์ฌ๋ ๊ฐ์ยท๋ณดํธ์ ์์ฒญ ๋ฑ ํน์ด์ฌํญ ๋ฐ์ ์ ์ถ๊ฐ์์ฑํ ์ ์์ต๋๋ค.
|
| 108 |
+
</div>
|
| 109 |
+
""", unsafe_allow_html=True)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
# -----------------------------
|
| 113 |
+
# DB
|
| 114 |
+
# -----------------------------
|
| 115 |
+
def get_conn():
|
| 116 |
+
return sqlite3.connect(DB_PATH, check_same_thread=False)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def init_db():
|
| 120 |
+
conn = get_conn()
|
| 121 |
+
cur = conn.cursor()
|
| 122 |
+
|
| 123 |
+
cur.execute("""
|
| 124 |
+
CREATE TABLE IF NOT EXISTS clients (
|
| 125 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 126 |
+
name TEXT NOT NULL,
|
| 127 |
+
masked_name TEXT,
|
| 128 |
+
gender TEXT,
|
| 129 |
+
birth_year TEXT,
|
| 130 |
+
care_grade TEXT,
|
| 131 |
+
institution_type TEXT,
|
| 132 |
+
institution_name TEXT,
|
| 133 |
+
admission_date TEXT,
|
| 134 |
+
main_condition TEXT,
|
| 135 |
+
cognitive_status TEXT,
|
| 136 |
+
mobility_status TEXT,
|
| 137 |
+
diet_status TEXT,
|
| 138 |
+
toileting_status TEXT,
|
| 139 |
+
guardian_name TEXT,
|
| 140 |
+
guardian_phone TEXT,
|
| 141 |
+
notes TEXT,
|
| 142 |
+
status TEXT DEFAULT '์ด์ฉ์ค',
|
| 143 |
+
created_at TEXT,
|
| 144 |
+
updated_at TEXT
|
| 145 |
+
)
|
| 146 |
+
""")
|
| 147 |
+
|
| 148 |
+
# Client Center v2 ํ๋๊ฐ ์์ ๊ฒฝ์ฐ ์ถ๊ฐ
|
| 149 |
+
new_columns = {
|
| 150 |
+
"previous_care_grade": "TEXT",
|
| 151 |
+
"plan_change_required": "TEXT DEFAULT '์๋์ค'",
|
| 152 |
+
"plan_change_type": "TEXT",
|
| 153 |
+
"plan_change_reason": "TEXT",
|
| 154 |
+
"plan_change_date": "TEXT",
|
| 155 |
+
"plan_rewrite_status": "TEXT DEFAULT 'ํด๋น์์'",
|
| 156 |
+
"service_end_reason": "TEXT",
|
| 157 |
+
"service_end_date": "TEXT",
|
| 158 |
+
"linked_document": "TEXT",
|
| 159 |
+
}
|
| 160 |
+
cur.execute("PRAGMA table_info(clients)")
|
| 161 |
+
existing_cols = [row[1] for row in cur.fetchall()]
|
| 162 |
+
for col, col_type in new_columns.items():
|
| 163 |
+
if col not in existing_cols:
|
| 164 |
+
cur.execute(f"ALTER TABLE clients ADD COLUMN {col} {col_type}")
|
| 165 |
+
|
| 166 |
+
cur.execute("""
|
| 167 |
+
CREATE TABLE IF NOT EXISTS status_change_records (
|
| 168 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 169 |
+
client_id INTEGER,
|
| 170 |
+
client_masked_name TEXT,
|
| 171 |
+
institution_type TEXT,
|
| 172 |
+
record_type TEXT,
|
| 173 |
+
record_date TEXT,
|
| 174 |
+
week_period TEXT,
|
| 175 |
+
writer TEXT,
|
| 176 |
+
change_category TEXT,
|
| 177 |
+
body_status TEXT,
|
| 178 |
+
cognitive_status TEXT,
|
| 179 |
+
emotion_status TEXT,
|
| 180 |
+
diet_status TEXT,
|
| 181 |
+
toileting_status TEXT,
|
| 182 |
+
mobility_status TEXT,
|
| 183 |
+
skin_status TEXT,
|
| 184 |
+
sleep_status TEXT,
|
| 185 |
+
program_status TEXT,
|
| 186 |
+
guardian_opinion TEXT,
|
| 187 |
+
staff_observation TEXT,
|
| 188 |
+
action_taken TEXT,
|
| 189 |
+
followup_plan TEXT,
|
| 190 |
+
plan_change_required TEXT,
|
| 191 |
+
plan_change_reason TEXT,
|
| 192 |
+
evaluation_score INTEGER,
|
| 193 |
+
missing_items TEXT,
|
| 194 |
+
compliance_result TEXT,
|
| 195 |
+
photo_count INTEGER,
|
| 196 |
+
created_at TEXT
|
| 197 |
+
)
|
| 198 |
+
""")
|
| 199 |
+
|
| 200 |
+
cur.execute("""
|
| 201 |
+
CREATE TABLE IF NOT EXISTS plan_change_logs (
|
| 202 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 203 |
+
client_id INTEGER,
|
| 204 |
+
client_masked_name TEXT,
|
| 205 |
+
institution_type TEXT,
|
| 206 |
+
change_type TEXT,
|
| 207 |
+
change_reason TEXT,
|
| 208 |
+
change_date TEXT,
|
| 209 |
+
linked_document TEXT,
|
| 210 |
+
plan_change_required TEXT,
|
| 211 |
+
plan_rewrite_status TEXT,
|
| 212 |
+
memo TEXT,
|
| 213 |
+
created_at TEXT
|
| 214 |
+
)
|
| 215 |
+
""")
|
| 216 |
+
|
| 217 |
+
conn.commit()
|
| 218 |
+
conn.close()
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def load_clients():
|
| 222 |
+
conn = get_conn()
|
| 223 |
+
df = pd.read_sql_query("SELECT * FROM clients ORDER BY id DESC", conn)
|
| 224 |
+
conn.close()
|
| 225 |
+
return df
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def load_status_records(client_id=None):
|
| 229 |
+
conn = get_conn()
|
| 230 |
+
try:
|
| 231 |
+
if client_id:
|
| 232 |
+
df = pd.read_sql_query(
|
| 233 |
+
"SELECT * FROM status_change_records WHERE client_id=? ORDER BY record_date DESC, id DESC",
|
| 234 |
+
conn,
|
| 235 |
+
params=(int(client_id),)
|
| 236 |
+
)
|
| 237 |
+
else:
|
| 238 |
+
df = pd.read_sql_query(
|
| 239 |
+
"SELECT * FROM status_change_records ORDER BY record_date DESC, id DESC",
|
| 240 |
+
conn
|
| 241 |
+
)
|
| 242 |
+
except Exception:
|
| 243 |
+
df = pd.DataFrame()
|
| 244 |
+
conn.close()
|
| 245 |
+
return df
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def safe(row, key, default=""):
|
| 249 |
+
try:
|
| 250 |
+
v = row.get(key, default)
|
| 251 |
+
if pd.isna(v):
|
| 252 |
+
return default
|
| 253 |
+
return str(v)
|
| 254 |
+
except Exception:
|
| 255 |
+
return default
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def mask_name(name):
|
| 259 |
+
name = str(name or "").strip()
|
| 260 |
+
if len(name) >= 3:
|
| 261 |
+
return name[0] + "โ" + name[2:]
|
| 262 |
+
if len(name) == 2:
|
| 263 |
+
return name[0] + "โ"
|
| 264 |
+
return name
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
def save_uploaded_photos(uploaded_files):
|
| 268 |
+
paths = []
|
| 269 |
+
for file in (uploaded_files or [])[:3]:
|
| 270 |
+
suffix = os.path.splitext(file.name)[1].lower() or ".png"
|
| 271 |
+
temp_path = os.path.join(tempfile.gettempdir(), f"status_photo_{datetime.now().strftime('%Y%m%d%H%M%S%f')}{suffix}")
|
| 272 |
+
with open(temp_path, "wb") as f:
|
| 273 |
+
f.write(file.getbuffer())
|
| 274 |
+
paths.append(temp_path)
|
| 275 |
+
return paths
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
def save_plan_change_log(client_id, client_masked_name, institution_type, change_reason, record_date, required, memo):
|
| 279 |
+
conn = get_conn()
|
| 280 |
+
cur = conn.cursor()
|
| 281 |
+
|
| 282 |
+
rewrite_status = "์ฌ์์ฑ ํ์" if required == "์" else "ํ์ฌ๊ณํ ์ ์ง"
|
| 283 |
+
|
| 284 |
+
cur.execute("""
|
| 285 |
+
INSERT INTO plan_change_logs (
|
| 286 |
+
client_id, client_masked_name, institution_type, change_type,
|
| 287 |
+
change_reason, change_date, linked_document, plan_change_required,
|
| 288 |
+
plan_rewrite_status, memo, created_at
|
| 289 |
+
)
|
| 290 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 291 |
+
""", (
|
| 292 |
+
client_id,
|
| 293 |
+
client_masked_name,
|
| 294 |
+
institution_type,
|
| 295 |
+
"์ํ๋ณํ",
|
| 296 |
+
change_reason,
|
| 297 |
+
record_date,
|
| 298 |
+
"์ํ๋ณํ๊ธฐ๋ก์ง",
|
| 299 |
+
required,
|
| 300 |
+
rewrite_status,
|
| 301 |
+
memo,
|
| 302 |
+
datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 303 |
+
))
|
| 304 |
+
|
| 305 |
+
cur.execute("""
|
| 306 |
+
UPDATE clients
|
| 307 |
+
SET plan_change_required=?,
|
| 308 |
+
plan_change_type=?,
|
| 309 |
+
plan_change_reason=?,
|
| 310 |
+
plan_change_date=?,
|
| 311 |
+
plan_rewrite_status=?,
|
| 312 |
+
linked_document=?,
|
| 313 |
+
updated_at=?
|
| 314 |
+
WHERE id=?
|
| 315 |
+
""", (
|
| 316 |
+
required,
|
| 317 |
+
"์ํ๋ณํ",
|
| 318 |
+
change_reason,
|
| 319 |
+
record_date,
|
| 320 |
+
rewrite_status,
|
| 321 |
+
"์ํ๋ณํ๊ธฐ๋ก์ง",
|
| 322 |
+
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 323 |
+
client_id
|
| 324 |
+
))
|
| 325 |
+
|
| 326 |
+
conn.commit()
|
| 327 |
+
conn.close()
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def save_status_record(data):
|
| 331 |
+
conn = get_conn()
|
| 332 |
+
cur = conn.cursor()
|
| 333 |
+
cur.execute("""
|
| 334 |
+
INSERT INTO status_change_records (
|
| 335 |
+
client_id, client_masked_name, institution_type, record_type,
|
| 336 |
+
record_date, week_period, writer, change_category,
|
| 337 |
+
body_status, cognitive_status, emotion_status, diet_status,
|
| 338 |
+
toileting_status, mobility_status, skin_status, sleep_status,
|
| 339 |
+
program_status, guardian_opinion, staff_observation,
|
| 340 |
+
action_taken, followup_plan, plan_change_required,
|
| 341 |
+
plan_change_reason, evaluation_score, missing_items,
|
| 342 |
+
compliance_result, photo_count, created_at
|
| 343 |
+
)
|
| 344 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 345 |
+
""", (
|
| 346 |
+
data["client_id"],
|
| 347 |
+
data["client_masked_name"],
|
| 348 |
+
data["institution_type"],
|
| 349 |
+
data["record_type"],
|
| 350 |
+
data["record_date"],
|
| 351 |
+
data["week_period"],
|
| 352 |
+
data["writer"],
|
| 353 |
+
data["change_category"],
|
| 354 |
+
data["body_status"],
|
| 355 |
+
data["cognitive_status"],
|
| 356 |
+
data["emotion_status"],
|
| 357 |
+
data["diet_status"],
|
| 358 |
+
data["toileting_status"],
|
| 359 |
+
data["mobility_status"],
|
| 360 |
+
data["skin_status"],
|
| 361 |
+
data["sleep_status"],
|
| 362 |
+
data["program_status"],
|
| 363 |
+
data["guardian_opinion"],
|
| 364 |
+
data["staff_observation"],
|
| 365 |
+
data["action_taken"],
|
| 366 |
+
data["followup_plan"],
|
| 367 |
+
data["plan_change_required"],
|
| 368 |
+
data["plan_change_reason"],
|
| 369 |
+
data["evaluation_score"],
|
| 370 |
+
data["missing_items"],
|
| 371 |
+
data["compliance_result"],
|
| 372 |
+
data["photo_count"],
|
| 373 |
+
datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 374 |
+
))
|
| 375 |
+
conn.commit()
|
| 376 |
+
conn.close()
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
init_db()
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
# -----------------------------
|
| 383 |
+
# ๊ธฐ๊ด์ ํ๋ณ ์ถ๊ฐ ์ ๊ฒ ํญ๋ชฉ
|
| 384 |
+
# -----------------------------
|
| 385 |
+
INSTITUTION_GUIDES = {
|
| 386 |
+
"๋ฐฉ๋ฌธ์์": {
|
| 387 |
+
"focus": ["์ฌ๊ฐ์ํ ๋ณํ", "๋ณดํ/์ด๋", "์์ฌยท๋ณต์ฝ", "์ธ์งํ๋", "๊ฐ์ฌ์ง์ ๋ฒ์", "๋ณดํธ์ ์์ฒญ"],
|
| 388 |
+
"note": "๋ฐฉ๋ฌธ์๋น์ค ์ ๊ณต ์ค ํ์ธ๋ ์ํ๋ณํ์ ๋ณดํธ์ ์๊ฒฌ, ์์๋ณดํธ์ฌ ๊ด์ฐฐ๋ด์ฉ์ ์ค์ฌ์ผ๋ก ๊ธฐ๋กํฉ๋๋ค.",
|
| 389 |
+
},
|
| 390 |
+
"๋ฐฉ๋ฌธ๋ชฉ์": {
|
| 391 |
+
"focus": ["ํผ๋ถ์ํ", "๋ชฉ์ํ์กฐ๋", "๋ชฉ์ ์ ํ ํ๋ ฅ", "์ด๋์์ ", "์ฒญ๊ฒฐ์ํ", "๋ณดํธ์ ํ์กฐ"],
|
| 392 |
+
"note": "๋ชฉ์ ์ ํ ๊ฑด๊ฐ์ํ, ํผ๋ถ ๋ฐ์ ยท์์ฒ, ์ด๋์์ ๊ณผ ๋ชฉ์ ๊ฑฐ๋ถ ์ฌ๋ถ๋ฅผ ์ค์ฌ์ผ๋ก ๊ธฐ๋กํฉ๋๋ค.",
|
| 393 |
+
},
|
| 394 |
+
"๋ฐฉ๋ฌธ๊ฐํธ": {
|
| 395 |
+
"focus": ["ํ์/ํ๋น", "์์ฒ๊ด๋ฆฌ", "ํฌ์ฝ๋ณ๊ฒฝ", "๊ฑด๊ฐ์๋ด", "์๋ฃ์ฐ๊ณ", "๋ณดํธ์ ๊ต์ก"],
|
| 396 |
+
"note": "๊ฑด๊ฐ์งํ ๋ณํ, ์ฒ์น ๋ด์ฉ, ํฌ์ฝยท์์ฒยท์๋ฃ์ฐ๊ณ ํ์์ฑ์ ์ค์ฌ์ผ๋ก ๊ธฐ๋กํฉ๋๋ค.",
|
| 397 |
+
},
|
| 398 |
+
"์ฃผ๊ฐ๋ณดํธ": {
|
| 399 |
+
"focus": ["ํ๋ก๊ทธ๋จ ์ฐธ์ฌ", "์์ฌ", "๋ฐฐ๋ณ", "์ ์", "์ฌํ์ฑ", "์ก์์์ ", "๋์์ํ"],
|
| 400 |
+
"note": "์ผํฐ ์ด์ฉ ์ค ํ ์ฃผ๊ฐ์ ์ ์ฒดยท์ธ์งยท์ ์ยท์์ฌยท๋ฐฐ๋ณยทํ๋ก๊ทธ๋จ ์ฐธ์ฌ ๋ณํ๋ฅผ ์์ฝํฉ๋๋ค.",
|
| 401 |
+
},
|
| 402 |
+
"์์์": {
|
| 403 |
+
"focus": ["์์ฌ", "์๋ฉด", "์์ฐฝ", "๋์", "์ธ์ง", "๋ฐฐ๋ณ", "ํ๋ก๊ทธ๋จ", "์ ์์ง์"],
|
| 404 |
+
"note": "๋งค์ผ ๊ด์ฐฐ๊ธฐ๋ก๊ณผ ๋ณ๋๋ก ์ฃผ 1ํ ์ด์ ์ํ๋ณํ๋ฅผ ์์ฝํ๊ณ , ๊ธ๊ฒฉํ ๋ณํ๋ ์ถ๊ฐ์์ฑํฉ๋๋ค.",
|
| 405 |
+
},
|
| 406 |
+
"๊ณต๋์ํ๊ฐ์ ": {
|
| 407 |
+
"focus": ["์ํ์ ์", "์ ์์์ ", "์ฌํ๊ด๊ณ", "ํ๋ก๊ทธ๋จ ์ฐธ์ฌ", "์์ฌ", "๊ฐ๋ณ ์๊ตฌ", "๊ฐ์กฑ์ํต"],
|
| 408 |
+
"note": "์๊ท๋ชจ ๊ณต๋์ํ ์ ์, ์ ์ยท์ฌํ๊ด๊ณ ๋ณํ, ๊ฐ๋ณ์๊ตฌ์ ๊ฐ์กฑ์ํต ๋ณํ๋ฅผ ์ค์ฌ์ผ๋ก ๊ธฐ๋กํฉ๋๋ค.",
|
| 409 |
+
},
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
CHANGE_CATEGORIES = [
|
| 413 |
+
"ํน์ด๋ณํ ์์",
|
| 414 |
+
"๋์ ๋ฐ์/์ํ ์ฆ๊ฐ",
|
| 415 |
+
"์์ฐฝ ๋ฐ์/์ํ ์ฆ๊ฐ",
|
| 416 |
+
"์์ฌ๋ ๊ฐ์",
|
| 417 |
+
"์๋ถ์ญ์ทจ ์ ํ",
|
| 418 |
+
"์ฒด์ค๊ฐ์ ์ฐ๋ ค",
|
| 419 |
+
"๋ณดํ๋ฅ๋ ฅ ์ ํ",
|
| 420 |
+
"์ธ์ง๊ธฐ๋ฅ ์ ํ",
|
| 421 |
+
"์ ์๋ถ์/์ฐ์ธ๊ฐ",
|
| 422 |
+
"๋ฐฐ๋ณ/๋ฐฐ๋จ ๋ณํ",
|
| 423 |
+
"๊ธฐ์ ๊ท ์ฐฉ์ฉ ์์/์ฆ๊ฐ",
|
| 424 |
+
"ํผ๋ถ์ํ ๋ณํ",
|
| 425 |
+
"์๋ฉด์ํ ๋ณํ",
|
| 426 |
+
"๋ณต์ฝ/๊ฑด๊ฐ์ํ ๋ณํ",
|
| 427 |
+
"๋ณ์์ง๋ฃ/์
์",
|
| 428 |
+
"ํด์ ํ ์ฌ์ด์ฉ",
|
| 429 |
+
"๋ฑ๊ธ๋ณ๊ฒฝ",
|
| 430 |
+
"๋ณดํธ์ ์์ฒญ",
|
| 431 |
+
"์๋น์ค ๋ณ๊ฒฝ ํ์",
|
| 432 |
+
"์ฌ๋ง/์ ์/์๋น์ค ์ข
๋ฃ",
|
| 433 |
+
"๊ธฐํ",
|
| 434 |
+
]
|
| 435 |
+
|
| 436 |
+
PLAN_CHANGE_TRIGGER_WORDS = [
|
| 437 |
+
"๋์", "์์ฐฝ", "์์ฌ๋ ๊ฐ์", "์๋ถ์ญ์ทจ", "์ฒด์ค", "๋ณดํ๋ฅ๋ ฅ ์ ํ",
|
| 438 |
+
"์ธ์ง๊ธฐ๋ฅ ์ ํ", "์ ์๋ถ์", "๋ฐฐ๋ณ", "๊ธฐ์ ๊ท", "ํผ๋ถ์ํ ๋ณํ",
|
| 439 |
+
"์๋ฉด์ํ ๋ณํ", "๋ณต์ฝ", "์
์", "ํด์", "๋ฑ๊ธ๋ณ๊ฒฝ", "๋ณดํธ์ ์์ฒญ", "์๋น์ค ๋ณ๊ฒฝ"
|
| 440 |
+
]
|
| 441 |
+
|
| 442 |
+
TERMINATION_WORDS = ["์ฌ๋ง", "์ ์", "์๋น์ค ์ข
๋ฃ"]
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
def determine_plan_change(change_category, body_status, cognitive_status, diet_status, mobility_status, skin_status, guardian_opinion, action_taken):
|
| 446 |
+
combined = " ".join([
|
| 447 |
+
change_category, body_status, cognitive_status, diet_status,
|
| 448 |
+
mobility_status, skin_status, guardian_opinion, action_taken
|
| 449 |
+
])
|
| 450 |
+
|
| 451 |
+
if any(w in combined for w in TERMINATION_WORDS):
|
| 452 |
+
return "์๋์ค", "์ฌ๋งยท์ ์ยท์๋น์ค ์ข
๋ฃ ์ฌ์ ๋ ๊ธ์ฌ์ ๊ณต๊ณํ ์ฌ์์ฑ๋ณด๋ค ์ข
๊ฒฐ/์ค์ง ๊ธฐ๋ก ์ ๋ฆฌ๊ฐ ์ฐ์ ์
๋๋ค."
|
| 453 |
+
|
| 454 |
+
if change_category == "ํน์ด๋ณํ ์์":
|
| 455 |
+
return "์๋์ค", "ํน์ด ์ํ๋ณํ๊ฐ ์์ด ํ์ฌ ๊ธ์ฌ์ ๊ณต๊ณํ์ ์ ์งํฉ๋๋ค."
|
| 456 |
+
|
| 457 |
+
if any(w in combined for w in PLAN_CHANGE_TRIGGER_WORDS):
|
| 458 |
+
return "์", "์ํ๋ณํ๊ฐ ํ์ธ๋์ด ์๋ด์ผ์งยท์๊ตฌ์ฌ์ ยท์ฌ๋ก๊ด๋ฆฌ์ ์ฐ๋ํ์ฌ ๊ธ์ฌ์ ๊ณต๊ณํ ์ฌ์์ฑ ์ฌ๋ถ๋ฅผ ๊ฒํ ํด์ผ ํฉ๋๋ค."
|
| 459 |
+
|
| 460 |
+
return "๊ฒํ ", "์ํ๋ณํ ๋ด์ฉ์ด ์์ผ๋ฏ๋ก ๋ด๋น์ ํ์ธ ํ ๊ธ์ฌ์ ๊ณต๊ณํ ๋ฐ์ ์ฌ๋ถ๋ฅผ ํ๋จํฉ๋๋ค."
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
def build_status_text(client, institution_type, record_type, change_category, body_status, cognitive_status, emotion_status,
|
| 464 |
+
diet_status, toileting_status, mobility_status, skin_status, sleep_status, program_status,
|
| 465 |
+
guardian_opinion, staff_observation, action_taken, followup_plan):
|
| 466 |
+
focus = ", ".join(INSTITUTION_GUIDES[institution_type]["focus"])
|
| 467 |
+
opener = random.choice([
|
| 468 |
+
"์ด๋ฒ ์ฃผ ์๊ธ์์ ์ ๋ฐ์ ์ธ ์ํ๋ฅผ ๊ด์ฐฐํ ๊ฒฐ๊ณผ",
|
| 469 |
+
"์๋น์ค ์ ๊ณต ๊ณผ์ ์์ ํ์ธ๋ ์ํ๋ณํ๋ฅผ ์ข
ํฉํ๋ฉด",
|
| 470 |
+
"์ฃผ๊ฐ ๊ด์ฐฐ ๋ฐ ์ข
์ฌ์ ์๊ฒฌ์ ์ข
ํฉํ ๊ฒฐ๊ณผ",
|
| 471 |
+
"์๊ธ์์ ์ ์ฒดยท์ธ์งยท์ ์ ์ํ๋ฅผ ํจ๊ป ํ์ธํ ๊ฒฐ๊ณผ",
|
| 472 |
+
])
|
| 473 |
+
|
| 474 |
+
if record_type == "์ถ๊ฐ์์ฑ":
|
| 475 |
+
opener = random.choice([
|
| 476 |
+
"ํน์ด์ฌํญ ๋ฐ์์ผ๋ก ์ถ๊ฐ ์ํ๋ณํ๊ธฐ๋ก์ ์์ฑํจ.",
|
| 477 |
+
"์ ๊ธฐ ์ฃผ๊ฐ๊ธฐ๋ก ์ธ์ ์ฆ์ ํ์ธ์ด ํ์ํ ๋ณํ๊ฐ ์์ด ์ถ๊ฐ ๊ธฐ๋กํจ.",
|
| 478 |
+
"์ํ๋ณํ ๋ฐ์์ ๋ฐ๋ผ ๊ธ์ฌ์ ๊ณต ๋ฐฉํฅ ๊ฒํ ๋ฅผ ์ํด ์ถ๊ฐ ๊ธฐ๋กํจ.",
|
| 479 |
+
])
|
| 480 |
+
|
| 481 |
+
text = f"""
|
| 482 |
+
{opener}
|
| 483 |
+
|
| 484 |
+
[๊ธฐ๋ก๊ตฌ๋ถ]
|
| 485 |
+
{record_type}
|
| 486 |
+
|
| 487 |
+
[๊ธฐ๊ด์ ํ๋ณ ์ค์ ํ์ธ]
|
| 488 |
+
{institution_type}์์๋ {focus} ํญ๋ชฉ์ ์ค์ฌ์ผ๋ก ์ํ๋ณํ๋ฅผ ํ์ธํฉ๋๋ค.
|
| 489 |
+
{INSTITUTION_GUIDES[institution_type]["note"]}
|
| 490 |
+
|
| 491 |
+
[์ํ๋ณํ ๋ถ๋ฅ]
|
| 492 |
+
{change_category}
|
| 493 |
+
|
| 494 |
+
[์ธ๋ถ ๊ด์ฐฐ๋ด์ฉ]
|
| 495 |
+
- ์ ์ฒด์ํ: {body_status or 'ํน์ด์ฌํญ ์์'}
|
| 496 |
+
- ์ธ์ง์ํ: {cognitive_status or 'ํน์ด์ฌํญ ์์'}
|
| 497 |
+
- ์ ์์ํ: {emotion_status or 'ํน์ด์ฌํญ ์์'}
|
| 498 |
+
- ์์ฌ์ํ: {diet_status or 'ํน์ด์ฌํญ ์์'}
|
| 499 |
+
- ๋ฐฐ๋ณ/๋ฐฐ๋จ์ํ: {toileting_status or 'ํน์ด์ฌํญ ์์'}
|
| 500 |
+
- ์ด๋์ํ: {mobility_status or 'ํน์ด์ฌํญ ์์'}
|
| 501 |
+
- ํผ๋ถ์ํ: {skin_status or 'ํน์ด์ฌํญ ์์'}
|
| 502 |
+
- ์๋ฉด์ํ: {sleep_status or 'ํน์ด์ฌํญ ์์'}
|
| 503 |
+
- ํ๋ก๊ทธ๋จ/ํ๋์ํ: {program_status or 'ํด๋น ์์'}
|
| 504 |
+
|
| 505 |
+
[๋ณดํธ์ ์๊ฒฌ]
|
| 506 |
+
{guardian_opinion or '๋ณดํธ์ ์๊ฒฌ์ ํน์ด์ฌํญ ์์ผ๋ฉฐ, ํ์ ์ ์๋ด์ผ์ง๋ก ์ถ๊ฐ ํ์ธ ์์ '}
|
| 507 |
+
|
| 508 |
+
[์ข
์ฌ์ ๊ด์ฐฐ์ฌํญ]
|
| 509 |
+
{staff_observation or '๊ธ์ฌ์ ๊ณต ์ค ํน์ด์ฌํญ์ ์์ผ๋ ์ง์ ๊ด์ฐฐ ํ์'}
|
| 510 |
+
|
| 511 |
+
[์กฐ์น๋ด์ฉ]
|
| 512 |
+
{action_taken or '์ํ๋ฅผ ์ง์ ๊ด์ฐฐํ๊ณ ํ์ ์ ๋ด๋น์ ๋ฐ ๋ณดํธ์์๊ฒ ๊ณต์ '}
|
| 513 |
+
|
| 514 |
+
[ํ์๊ณํ]
|
| 515 |
+
{followup_plan or '๋ค์ ์ฃผ ์ํ๋ณํ๋ฅผ ์ฌํ์ธํ๊ณ , ๋ณํ๊ฐ ์ง์๋ ๊ฒฝ์ฐ ์๋ด์ผ์งยท์๊ตฌ์ฌ์ ยท๊ธ์ฌ์ ๊ณต๊ณํ์ ๋ฐ์ ์ฌ๋ถ๋ฅผ ๊ฒํ '}
|
| 516 |
+
""".strip()
|
| 517 |
+
|
| 518 |
+
return text
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
def evaluate_record(record_type, change_category, status_text, guardian_opinion, staff_observation, action_taken, followup_plan, plan_change_required):
|
| 522 |
+
required = {
|
| 523 |
+
"๊ธฐ๋ก๊ตฌ๋ถ": bool(record_type),
|
| 524 |
+
"์ํ๋ณํ ๋ถ๋ฅ": bool(change_category),
|
| 525 |
+
"์ ์ฒด/์ธ์ง/์ ์ ๋ฑ ๊ด์ฐฐ": "์ธ๋ถ ๊ด์ฐฐ๋ด์ฉ" in status_text,
|
| 526 |
+
"๋ณดํธ์ ์๊ฒฌ ํ์ธ": bool(guardian_opinion.strip()) or "๋ณดํธ์ ์๊ฒฌ" in status_text,
|
| 527 |
+
"์ข
์ฌ์ ๊ด์ฐฐ ๋ฐ์": bool(staff_observation.strip()) or "์ข
์ฌ์ ๊ด์ฐฐ" in status_text,
|
| 528 |
+
"์กฐ์น๋ด์ฉ": bool(action_taken.strip()) or "์กฐ์น๋ด์ฉ" in status_text,
|
| 529 |
+
"ํ์๊ณํ": bool(followup_plan.strip()) or "ํ์๊ณํ" in status_text,
|
| 530 |
+
"๊ธ์ฌ๊ณํ ์ฐ๊ณ ํ๋จ": plan_change_required in ["์", "์๋์ค", "๊ฒํ "],
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
score = int(sum(required.values()) / len(required) * 100)
|
| 534 |
+
missing = [k for k, v in required.items() if not v]
|
| 535 |
+
|
| 536 |
+
if score >= 90:
|
| 537 |
+
result = "ํ๊ฐ ์ ํฉ๋๊ฐ ๋์ต๋๋ค. ์ํ๋ณํ, ์กฐ์น, ํ์๊ณํ, ๊ธ์ฌ๊ณํ ์ฐ๊ณ ํ๋จ์ด ์ ๋ฐ์๋์์ต๋๋ค."
|
| 538 |
+
elif score >= 75:
|
| 539 |
+
result = "๋์ฒด๋ก ์ ํฉํ๋ ์ผ๋ถ ๋ณด์์ด ํ์ํฉ๋๋ค. ๋ถ์กฑ ํญ๋ชฉ์ ์ถ๊ฐํ๋ฉด ๊ธฐ๋ก ์์ฑ๋๊ฐ ๋์์ง๋๋ค."
|
| 540 |
+
else:
|
| 541 |
+
result = "๋ณด์์ด ํ์ํฉ๋๋ค. ์ํ๋ณํ, ๋ณดํธ์ ์๊ฒฌ, ์กฐ์น๋ด์ฉ, ํ์๊ณํ์ด ๊ตฌ์ฒด์ ์ผ๋ก ๋ค์ด๊ฐ์ผ ํฉ๋๋ค."
|
| 542 |
+
|
| 543 |
+
return score, missing, result
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
# -----------------------------
|
| 547 |
+
# ์์
|
| 548 |
+
# -----------------------------
|
| 549 |
+
def style_ws(ws, max_row):
|
| 550 |
+
"""A4 ์ถ๋ ฅ ์ ์ ์ด ์๋ฆฌ์ง ์๋๋ก ์ ์ฒด ์ถ๋ ฅ๋ฒ์์ ํ
๋๋ฆฌ/์ ๋ ฌ์ ์ ์ฉํฉ๋๋ค."""
|
| 551 |
+
thin = Side(style="thin", color="000000")
|
| 552 |
+
border = Border(left=thin, right=thin, top=thin, bottom=thin)
|
| 553 |
+
|
| 554 |
+
for row in ws.iter_rows(min_row=1, max_row=max_row, min_col=1, max_col=9):
|
| 555 |
+
for cell in row:
|
| 556 |
+
cell.border = border
|
| 557 |
+
if cell.value is not None:
|
| 558 |
+
current_size = cell.font.sz or 10
|
| 559 |
+
cell.font = Font(
|
| 560 |
+
name="Malgun Gothic",
|
| 561 |
+
size=current_size,
|
| 562 |
+
bold=cell.font.bold
|
| 563 |
+
)
|
| 564 |
+
else:
|
| 565 |
+
cell.font = Font(name="Malgun Gothic", size=10)
|
| 566 |
+
cell.alignment = Alignment(
|
| 567 |
+
horizontal=cell.alignment.horizontal or "left",
|
| 568 |
+
vertical=cell.alignment.vertical or "center",
|
| 569 |
+
wrap_text=True
|
| 570 |
+
)
|
| 571 |
+
|
| 572 |
+
# ๋ณด๊ธฐ ์ข๊ฒ ์ถ๋ ฅ๋๋๋ก ์ด ๋๋น ์กฐ์
|
| 573 |
+
widths = {1: 11, 2: 13, 3: 13, 4: 11, 5: 13, 6: 13, 7: 11, 8: 13, 9: 13}
|
| 574 |
+
for col, width in widths.items():
|
| 575 |
+
ws.column_dimensions[get_column_letter(col)].width = width
|
| 576 |
+
|
| 577 |
+
ws.page_setup.paperSize = ws.PAPERSIZE_A4
|
| 578 |
+
ws.page_setup.orientation = "portrait"
|
| 579 |
+
ws.page_setup.fitToWidth = 1
|
| 580 |
+
ws.page_setup.fitToHeight = 0
|
| 581 |
+
ws.sheet_properties.pageSetUpPr.fitToPage = True
|
| 582 |
+
ws.page_margins.left = 0.25
|
| 583 |
+
ws.page_margins.right = 0.25
|
| 584 |
+
ws.page_margins.top = 0.30
|
| 585 |
+
ws.page_margins.bottom = 0.30
|
| 586 |
+
ws.sheet_view.showGridLines = False
|
| 587 |
+
ws.print_area = f"A1:I{max_row}"
|
| 588 |
+
ws.print_options.horizontalCentered = True
|
| 589 |
+
|
| 590 |
+
|
| 591 |
+
def write_title_with_approval(ws, title, title_fill, header_fill):
|
| 592 |
+
"""ํฐ ์ ๋ชฉ์ ์ผ์ชฝ A:G, ๊ฒฐ์ฌ๋ผ์ธ์ ์ค๋ฅธ์ชฝ ๋งจ ์ H:I์ ๊ณ ์ ํฉ๋๋ค."""
|
| 593 |
+
for col in range(1, 10):
|
| 594 |
+
ws.column_dimensions[get_column_letter(col)].width = 12
|
| 595 |
+
|
| 596 |
+
thin = Side(style="thin", color="000000")
|
| 597 |
+
border = Border(left=thin, right=thin, top=thin, bottom=thin)
|
| 598 |
+
|
| 599 |
+
ws.merge_cells(start_row=1, start_column=1, end_row=2, end_column=7)
|
| 600 |
+
title_cell = ws.cell(1, 1)
|
| 601 |
+
title_cell.value = title
|
| 602 |
+
title_cell.font = Font(name="Malgun Gothic", size=24, bold=True)
|
| 603 |
+
title_cell.alignment = Alignment(horizontal="center", vertical="center")
|
| 604 |
+
title_cell.fill = title_fill
|
| 605 |
+
|
| 606 |
+
# ์ ๋ชฉ ๋ณํฉ ์์ญ ์ ์ฒด ํ
๋๋ฆฌ ์ ์ฉ
|
| 607 |
+
for rr in [1, 2]:
|
| 608 |
+
for cc in range(1, 8):
|
| 609 |
+
cell = ws.cell(rr, cc)
|
| 610 |
+
cell.border = border
|
| 611 |
+
cell.fill = title_fill
|
| 612 |
+
|
| 613 |
+
# ๊ฒฐ์ฌ๋ผ์ธ ์ค๋ฅธ์ชฝ ๋งจ ์ ๊ณ ์ : H:I๋ง ์ฌ์ฉ
|
| 614 |
+
approval = {
|
| 615 |
+
(1, 8): "๋ด๋น์",
|
| 616 |
+
(1, 9): "์์ค์ฅ",
|
| 617 |
+
(2, 8): "",
|
| 618 |
+
(2, 9): "",
|
| 619 |
+
}
|
| 620 |
+
for (rr, cc), value in approval.items():
|
| 621 |
+
cell = ws.cell(rr, cc)
|
| 622 |
+
cell.value = value
|
| 623 |
+
cell.border = border
|
| 624 |
+
cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
| 625 |
+
cell.font = Font(name="Malgun Gothic", size=11, bold=(rr == 1))
|
| 626 |
+
if rr == 1:
|
| 627 |
+
cell.fill = header_fill
|
| 628 |
+
|
| 629 |
+
ws.row_dimensions[1].height = 28
|
| 630 |
+
ws.row_dimensions[2].height = 42
|
| 631 |
+
|
| 632 |
+
|
| 633 |
+
def write_info_row(ws, row, a, b, c, d, e, f, header_fill):
|
| 634 |
+
ws.cell(row, 1).value = a
|
| 635 |
+
ws.merge_cells(start_row=row, start_column=2, end_row=row, end_column=3)
|
| 636 |
+
ws.cell(row, 2).value = b
|
| 637 |
+
ws.cell(row, 4).value = c
|
| 638 |
+
ws.merge_cells(start_row=row, start_column=5, end_row=row, end_column=6)
|
| 639 |
+
ws.cell(row, 5).value = d
|
| 640 |
+
ws.cell(row, 7).value = e
|
| 641 |
+
ws.merge_cells(start_row=row, start_column=8, end_row=row, end_column=9)
|
| 642 |
+
ws.cell(row, 8).value = f
|
| 643 |
+
|
| 644 |
+
for col in [1, 4, 7]:
|
| 645 |
+
ws.cell(row, col).fill = header_fill
|
| 646 |
+
ws.cell(row, col).font = Font(name="Malgun Gothic", size=11, bold=True)
|
| 647 |
+
ws.cell(row, col).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
| 648 |
+
|
| 649 |
+
for col in [2, 5, 8]:
|
| 650 |
+
ws.cell(row, col).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
| 651 |
+
|
| 652 |
+
ws.row_dimensions[row].height = 30
|
| 653 |
+
|
| 654 |
+
|
| 655 |
+
def write_section(ws, row, title, content, header_fill, height=100):
|
| 656 |
+
ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=9)
|
| 657 |
+
ws.cell(row, 1).value = title
|
| 658 |
+
ws.cell(row, 1).font = Font(name="Malgun Gothic", size=15, bold=True)
|
| 659 |
+
ws.cell(row, 1).alignment = Alignment(horizontal="center", vertical="center")
|
| 660 |
+
ws.cell(row, 1).fill = header_fill
|
| 661 |
+
ws.row_dimensions[row].height = 30
|
| 662 |
+
row += 1
|
| 663 |
+
|
| 664 |
+
ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=9)
|
| 665 |
+
ws.cell(row, 1).value = content
|
| 666 |
+
ws.cell(row, 1).font = Font(name="Malgun Gothic", size=11)
|
| 667 |
+
ws.cell(row, 1).alignment = Alignment(horizontal="left", vertical="top", wrap_text=True)
|
| 668 |
+
lines = len(str(content)) // 60 + 1 + str(content).count("\n")
|
| 669 |
+
ws.row_dimensions[row].height = max(height, lines * 18)
|
| 670 |
+
return row + 1
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def generate_excel(data):
|
| 674 |
+
wb = Workbook()
|
| 675 |
+
title_fill = PatternFill("solid", fgColor="DBEAFE")
|
| 676 |
+
header_fill = PatternFill("solid", fgColor="F3F4F6")
|
| 677 |
+
|
| 678 |
+
ws = wb.active
|
| 679 |
+
ws.title = "์ํ๋ณํ๊ธฐ๋ก์ง"
|
| 680 |
+
write_title_with_approval(ws, "์ํ๋ณํ๊ธฐ๋ก์ง", title_fill, header_fill)
|
| 681 |
+
|
| 682 |
+
r = 3
|
| 683 |
+
write_info_row(ws, r, "๊ธฐ๊ด๋ช
", data["institution_name"], "๊ธฐ๊ด์ ํ", data["institution_type"], "๊ธฐ๋ก์ผ์", data["record_date"], header_fill); r += 1
|
| 684 |
+
write_info_row(ws, r, "์๊ธ์", data["client_masked_name"], "์ฅ๊ธฐ์์๋ฑ๊ธ", data["grade"], "๊ธฐ๋ก๊ตฌ๋ถ", data["record_type"], header_fill); r += 1
|
| 685 |
+
write_info_row(ws, r, "์์ฑ์", data["writer"], "์ฃผ๊ฐ๊ธฐ๊ฐ", data["week_period"], "์ฌ์์ฑํ์", data["plan_change_required"], header_fill); r += 1
|
| 686 |
+
|
| 687 |
+
r = write_section(ws, r, "์ํ๋ณํ ๊ธฐ๋ก๋ด์ฉ", data["status_text"], header_fill, 240)
|
| 688 |
+
r = write_section(ws, r, "๊ธ์ฌ์ ๊ณต๊ณํ ์ฐ๊ณ ํ๋จ", data["plan_change_reason"], header_fill, 70)
|
| 689 |
+
|
| 690 |
+
# ํ๊ฐ ์ ํฉ๋๋ ์ถ๋ ฅ๋ฌผ์๋ ๋ณด์ด์ง ์๋๋ก ๋ด๋ถ์ ๊ฒ ์ํธ๋ก๋ง ๊ด๋ฆฌ
|
| 691 |
+
internal_ws = wb.create_sheet("๋ด๋ถ์ ๊ฒ์๋ฃ_์ถ๋ ฅ์ ์ธ")
|
| 692 |
+
internal_ws.sheet_state = "hidden"
|
| 693 |
+
internal_ws.cell(1, 1).value = "ํ๊ฐ ์ ํฉ๋"
|
| 694 |
+
internal_ws.cell(1, 2).value = f"{data['evaluation_score']}์ "
|
| 695 |
+
internal_ws.cell(2, 1).value = "๋ถ์กฑ ํญ๋ชฉ"
|
| 696 |
+
internal_ws.cell(2, 2).value = data["missing_items"]
|
| 697 |
+
internal_ws.cell(3, 1).value = "์ ๊ฒ ๊ฒฐ๊ณผ"
|
| 698 |
+
internal_ws.cell(3, 2).value = data["compliance_result"]
|
| 699 |
+
for row in internal_ws.iter_rows(min_row=1, max_row=3, min_col=1, max_col=2):
|
| 700 |
+
for cell in row:
|
| 701 |
+
cell.font = Font(name="Malgun Gothic", size=10)
|
| 702 |
+
cell.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
|
| 703 |
+
|
| 704 |
+
# ์ฌ์ง์ฒจ๋ถ
|
| 705 |
+
if data.get("photo_paths"):
|
| 706 |
+
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=9)
|
| 707 |
+
ws.cell(r, 1).value = "์ฌ์ง์ฒจ๋ถ"
|
| 708 |
+
ws.cell(r, 1).font = Font(name="Malgun Gothic", size=15, bold=True)
|
| 709 |
+
ws.cell(r, 1).alignment = Alignment(horizontal="center", vertical="center")
|
| 710 |
+
ws.cell(r, 1).fill = header_fill
|
| 711 |
+
r += 1
|
| 712 |
+
|
| 713 |
+
anchors = ["A", "D", "G"]
|
| 714 |
+
img_row = r
|
| 715 |
+
for idx, photo_path in enumerate(data.get("photo_paths", [])[:3]):
|
| 716 |
+
try:
|
| 717 |
+
img = XLImage(photo_path)
|
| 718 |
+
img.width = 210
|
| 719 |
+
img.height = 150
|
| 720 |
+
ws.add_image(img, f"{anchors[idx]}{img_row}")
|
| 721 |
+
except Exception:
|
| 722 |
+
pass
|
| 723 |
+
|
| 724 |
+
for rr in range(img_row, img_row + 8):
|
| 725 |
+
ws.row_dimensions[rr].height = 22
|
| 726 |
+
r = img_row + 8
|
| 727 |
+
|
| 728 |
+
style_ws(ws, r)
|
| 729 |
+
|
| 730 |
+
output = BytesIO()
|
| 731 |
+
wb.save(output)
|
| 732 |
+
output.seek(0)
|
| 733 |
+
return output
|
| 734 |
+
|
| 735 |
+
|
| 736 |
+
# -----------------------------
|
| 737 |
+
# ํ๋ฉด
|
| 738 |
+
# -----------------------------
|
| 739 |
+
menu = st.sidebar.radio("๋ฉ๋ด", ["์ํ๋ณํ๊ธฐ๋ก ์์ฑ", "์ ์ฅ๊ธฐ๋ก ๋ณด๊ธฐ"])
|
| 740 |
+
|
| 741 |
+
st.sidebar.success("DB ์ฐ๊ฒฐ")
|
| 742 |
+
st.sidebar.caption(DB_PATH)
|
| 743 |
+
|
| 744 |
+
clients_df = load_clients()
|
| 745 |
+
|
| 746 |
+
if menu == "์ํ๋ณํ๊ธฐ๋ก ์์ฑ":
|
| 747 |
+
if clients_df.empty:
|
| 748 |
+
st.warning("Client Center ์๊ธ์ DB๊ฐ ๋น์ด ์์ต๋๋ค. ๋จผ์ ์๊ธ์๋ฅผ ๋ฑ๋กํ์ธ์.")
|
| 749 |
+
st.stop()
|
| 750 |
+
|
| 751 |
+
options = clients_df.apply(
|
| 752 |
+
lambda x: f"{x['id']}๋ฒ - {x['masked_name']} / {x['care_grade']} / {x['institution_type']} / {x.get('status','')}",
|
| 753 |
+
axis=1
|
| 754 |
+
).tolist()
|
| 755 |
+
|
| 756 |
+
selected_option = st.selectbox("์๊ธ์ ์ ํ", options)
|
| 757 |
+
selected_id = int(selected_option.split("๋ฒ")[0])
|
| 758 |
+
client = clients_df[clients_df["id"] == selected_id].iloc[0]
|
| 759 |
+
|
| 760 |
+
client_id = int(safe(client, "id", 0) or 0)
|
| 761 |
+
institution_type = safe(client, "institution_type", "๋ฐฉ๋ฌธ์์")
|
| 762 |
+
if institution_type not in INSTITUTION_GUIDES:
|
| 763 |
+
institution_type = "๋ฐฉ๋ฌธ์์"
|
| 764 |
+
|
| 765 |
+
m1, m2, m3, m4 = st.columns(4)
|
| 766 |
+
m1.metric("์๊ธ์", safe(client, "masked_name"))
|
| 767 |
+
m2.metric("๋ฑ๊ธ", safe(client, "care_grade"))
|
| 768 |
+
m3.metric("๊ธฐ๊ด์ ํ", institution_type)
|
| 769 |
+
m4.metric("์์ฑ์ฃผ๊ธฐ", "์ฃผ 1ํ ๊ธฐ๋ณธ")
|
| 770 |
+
|
| 771 |
+
st.info(INSTITUTION_GUIDES[institution_type]["note"])
|
| 772 |
+
|
| 773 |
+
st.markdown("### ๊ธฐ๋ก ๊ธฐ๋ณธ์ ๋ณด")
|
| 774 |
+
col1, col2, col3 = st.columns(3)
|
| 775 |
+
|
| 776 |
+
record_type = col1.radio("๊ธฐ๋ก๊ตฌ๋ถ", ["์ฃผ๊ฐ ์ ๊ธฐ๊ธฐ๋ก", "์ถ๊ฐ์์ฑ"], horizontal=True)
|
| 777 |
+
record_date = col2.date_input("๊ธฐ๋ก์ผ์", value=date.today())
|
| 778 |
+
writer = col3.text_input("์์ฑ์", "์ฌํ๋ณต์ง์ฌ")
|
| 779 |
+
|
| 780 |
+
week_start = record_date - timedelta(days=record_date.weekday())
|
| 781 |
+
week_end = week_start + timedelta(days=6)
|
| 782 |
+
week_period = st.text_input("์ฃผ๊ฐ๊ธฐ๊ฐ", value=f"{week_start} ~ {week_end}")
|
| 783 |
+
|
| 784 |
+
st.markdown("### ์ํ๋ณํ ๋ถ๋ฅ")
|
| 785 |
+
change_category = st.selectbox("์ํ๋ณํ ๋ถ๋ฅ", CHANGE_CATEGORIES)
|
| 786 |
+
|
| 787 |
+
st.markdown("### ์ธ๋ถ ์ํ ์
๋ ฅ")
|
| 788 |
+
c1, c2 = st.columns(2)
|
| 789 |
+
|
| 790 |
+
with c1:
|
| 791 |
+
body_status = st.text_area("์ ์ฒด์ํ ๋ณํ", placeholder="์: ๋ณดํ ์ ํ์ฒญ๊ฑฐ๋ฆผ ์ฆ๊ฐ, ํต์ฆ ํธ์ ๋ฑ", height=90)
|
| 792 |
+
cognitive_status = st.text_area("์ธ์ง์ํ ๋ณํ", placeholder="์: ๋ฐ๋ณต์ง๋ฌธ ์ฆ๊ฐ, ์๊ฐ/์ฅ์ ํผ๋ ๋ฑ", height=90)
|
| 793 |
+
emotion_status = st.text_area("์ ์์ํ ๋ณํ", placeholder="์: ๋ถ์, ์ฐ์ธ๊ฐ, ์ง์ฆ ์ฆ๊ฐ ๋ฑ", height=90)
|
| 794 |
+
diet_status = st.text_area("์์ฌ์ํ ๋ณํ", placeholder="์: ์์ฌ๋ ๊ฐ์, ์ฌ๋ , ์ฐํ๊ณค๋ ๋ฑ", height=90)
|
| 795 |
+
toileting_status = st.text_area("๋ฐฐ๋ณ/๋ฐฐ๋จ ๋ณํ", placeholder="์: ๋ณ๋น, ์ค์ฌ, ์ค๊ธ, ๊ธฐ์ ๊ท ์ฌ์ฉ ์ฆ๊ฐ ๋ฑ", height=90)
|
| 796 |
+
|
| 797 |
+
with c2:
|
| 798 |
+
mobility_status = st.text_area("์ด๋์ํ ๋ณํ", placeholder="์: ๋ถ์ถ ํ์ ์ฆ๊ฐ, ํ ์ฒด์ด ์ด๋์ผ๋ก ๋ณ๊ฒฝ ๋ฑ", height=90)
|
| 799 |
+
skin_status = st.text_area("ํผ๋ถ์ํ ๋ณํ", placeholder="์: ๋ฐ์ , ๏ฟฝ๏ฟฝ์ฒ, ์์ฐฝ ์ํ ๋ฑ", height=90)
|
| 800 |
+
sleep_status = st.text_area("์๋ฉด์ํ ๋ณํ", placeholder="์: ์ผ๊ฐ๊ฐ์ฑ, ๋ฎ์ ์ฆ๊ฐ ๋ฑ", height=90)
|
| 801 |
+
program_status = st.text_area("ํ๋ก๊ทธ๋จ/ํ๋ ๋ณํ", placeholder="์: ์ฐธ์ฌ ์ ์กฐ, ํฅ๋ฏธ ์ ํ ๋ฑ", height=90)
|
| 802 |
+
guardian_opinion = st.text_area("๋ณดํธ์ ์๊ฒฌ", placeholder="์: ๋ณดํธ์๊ฐ ์์ฌ๋ ๊ฐ์๋ฅผ ๊ฑฑ์ ํจ ๋ฑ", height=90)
|
| 803 |
+
|
| 804 |
+
staff_observation = st.text_area("์ข
์ฌ์ ๊ด์ฐฐ์ฌํญ", placeholder="์: ์์๋ณดํธ์ฌ/๊ฐํธ์กฐ๋ฌด์ฌ๊ฐ ๊ด์ฐฐํ ๋ณํ", height=90)
|
| 805 |
+
action_taken = st.text_area("์กฐ์น๋ด์ฉ", placeholder="์: ๋ณดํธ์์๊ฒ ๊ณต์ , ๋ณ์์ง๋ฃ ๊ถ์ , ํ๊ฒฝ์ ๋ฆฌ, ์์ฌ๋ณด์กฐ ๊ฐํ ๋ฑ", height=90)
|
| 806 |
+
followup_plan = st.text_area("ํ์๊ณํ", placeholder="์: ๋ค์ ์ฃผ ์ฌํ์ธ, ์๋ด์ผ์ง ์์ฑ, ์๊ตฌ์ฌ์ ์ฌ์ ๊ฒ, ๊ธ์ฌ๊ณํ ๋ณ๊ฒฝ ๊ฒํ ๋ฑ", height=90)
|
| 807 |
+
|
| 808 |
+
photos = st.file_uploader("์ฌ์ง์ฒจ๋ถ (์ต๋ 3์ฅ, ์ ํ์ฌํญ)", type=["png", "jpg", "jpeg"], accept_multiple_files=True)
|
| 809 |
+
|
| 810 |
+
required, plan_reason = determine_plan_change(
|
| 811 |
+
change_category, body_status, cognitive_status, diet_status,
|
| 812 |
+
mobility_status, skin_status, guardian_opinion, action_taken
|
| 813 |
+
)
|
| 814 |
+
|
| 815 |
+
st.markdown("### ๊ธ์ฌ์ ๊ณต๊ณํ ์ฐ๊ณ ํ๋จ")
|
| 816 |
+
p1, p2 = st.columns(2)
|
| 817 |
+
p1.metric("๊ธ์ฌ๊ณํ ์ฌ์์ฑ ํ์", required)
|
| 818 |
+
p2.metric("์ฐ๋๋ฌธ์", "์ํ๋ณํ๊ธฐ๋ก์ง")
|
| 819 |
+
|
| 820 |
+
if required == "์":
|
| 821 |
+
st.markdown(f'<div class="dangerbox">{plan_reason}</div>', unsafe_allow_html=True)
|
| 822 |
+
elif required == "๊ฒํ ":
|
| 823 |
+
st.markdown(f'<div class="warnbox">{plan_reason}</div>', unsafe_allow_html=True)
|
| 824 |
+
else:
|
| 825 |
+
st.markdown(f'<div class="okbox">{plan_reason}</div>', unsafe_allow_html=True)
|
| 826 |
+
|
| 827 |
+
if st.button("๐ฉบ ์ํ๋ณํ๊ธฐ๋ก ์์ฑ", use_container_width=True):
|
| 828 |
+
status_text = build_status_text(
|
| 829 |
+
client, institution_type, record_type, change_category,
|
| 830 |
+
body_status, cognitive_status, emotion_status, diet_status,
|
| 831 |
+
toileting_status, mobility_status, skin_status, sleep_status,
|
| 832 |
+
program_status, guardian_opinion, staff_observation, action_taken, followup_plan
|
| 833 |
+
)
|
| 834 |
+
|
| 835 |
+
score, missing, compliance = evaluate_record(
|
| 836 |
+
record_type, change_category, status_text, guardian_opinion,
|
| 837 |
+
staff_observation, action_taken, followup_plan, required
|
| 838 |
+
)
|
| 839 |
+
|
| 840 |
+
photo_paths = save_uploaded_photos(photos)
|
| 841 |
+
|
| 842 |
+
st.session_state["status_data"] = {
|
| 843 |
+
"client_id": client_id,
|
| 844 |
+
"client_masked_name": safe(client, "masked_name"),
|
| 845 |
+
"institution_name": safe(client, "institution_name"),
|
| 846 |
+
"institution_type": institution_type,
|
| 847 |
+
"grade": safe(client, "care_grade"),
|
| 848 |
+
"record_type": record_type,
|
| 849 |
+
"record_date": str(record_date),
|
| 850 |
+
"week_period": week_period,
|
| 851 |
+
"writer": writer,
|
| 852 |
+
"change_category": change_category,
|
| 853 |
+
"body_status": body_status,
|
| 854 |
+
"cognitive_status": cognitive_status,
|
| 855 |
+
"emotion_status": emotion_status,
|
| 856 |
+
"diet_status": diet_status,
|
| 857 |
+
"toileting_status": toileting_status,
|
| 858 |
+
"mobility_status": mobility_status,
|
| 859 |
+
"skin_status": skin_status,
|
| 860 |
+
"sleep_status": sleep_status,
|
| 861 |
+
"program_status": program_status,
|
| 862 |
+
"guardian_opinion": guardian_opinion,
|
| 863 |
+
"staff_observation": staff_observation,
|
| 864 |
+
"action_taken": action_taken,
|
| 865 |
+
"followup_plan": followup_plan,
|
| 866 |
+
"status_text": status_text,
|
| 867 |
+
"plan_change_required": required,
|
| 868 |
+
"plan_change_reason": plan_reason,
|
| 869 |
+
"evaluation_score": score,
|
| 870 |
+
"missing_items": ", ".join(missing) if missing else "๋ถ์กฑ ํญ๋ชฉ ์์",
|
| 871 |
+
"compliance_result": compliance,
|
| 872 |
+
"photo_paths": photo_paths,
|
| 873 |
+
"photo_count": len(photo_paths),
|
| 874 |
+
}
|
| 875 |
+
|
| 876 |
+
if "status_data" in st.session_state:
|
| 877 |
+
data = st.session_state["status_data"]
|
| 878 |
+
|
| 879 |
+
st.markdown("---")
|
| 880 |
+
st.subheader("์์ฑ ๊ฒฐ๊ณผ ์์ ")
|
| 881 |
+
data["status_text"] = st.text_area("์ํ๋ณํ ๊ธฐ๋ก๋ด์ฉ", value=data["status_text"], height=320)
|
| 882 |
+
data["plan_change_reason"] = st.text_area("๊ธ์ฌ๊ณํ ์ฐ๊ณ ํ๋จ ๋ฌธ๊ตฌ", value=data["plan_change_reason"], height=90)
|
| 883 |
+
|
| 884 |
+
score, missing, compliance = evaluate_record(
|
| 885 |
+
data["record_type"], data["change_category"], data["status_text"],
|
| 886 |
+
data["guardian_opinion"], data["staff_observation"], data["action_taken"],
|
| 887 |
+
data["followup_plan"], data["plan_change_required"]
|
| 888 |
+
)
|
| 889 |
+
|
| 890 |
+
data["evaluation_score"] = score
|
| 891 |
+
data["missing_items"] = ", ".join(missing) if missing else "๋ถ์กฑ ํญ๋ชฉ ์์"
|
| 892 |
+
data["compliance_result"] = compliance
|
| 893 |
+
|
| 894 |
+
st.subheader("ํ๊ฐ ์ ํฉ๋ ์ ๊ฒ")
|
| 895 |
+
e1, e2, e3 = st.columns(3)
|
| 896 |
+
e1.metric("ํ๊ฐ ์ ํฉ๋", f"{score}์ ")
|
| 897 |
+
e2.metric("๋ถ์กฑ ํญ๋ชฉ", data["missing_items"])
|
| 898 |
+
e3.metric("์ฌ์์ฑ ํ์", data["plan_change_required"])
|
| 899 |
+
|
| 900 |
+
if score >= 90:
|
| 901 |
+
st.markdown(f'<div class="okbox">{compliance}</div>', unsafe_allow_html=True)
|
| 902 |
+
else:
|
| 903 |
+
st.markdown(f'<div class="warnbox">{compliance}</div>', unsafe_allow_html=True)
|
| 904 |
+
|
| 905 |
+
if st.button("๐พ ์ํ๋ณํ๊ธฐ๋ก DB ์ ์ฅ ๋ฐ Client Center ๋ฐ์", use_container_width=True):
|
| 906 |
+
save_status_record(data)
|
| 907 |
+
save_plan_change_log(
|
| 908 |
+
data["client_id"],
|
| 909 |
+
data["client_masked_name"],
|
| 910 |
+
data["institution_type"],
|
| 911 |
+
data["plan_change_reason"],
|
| 912 |
+
data["record_date"],
|
| 913 |
+
data["plan_change_required"],
|
| 914 |
+
data["plan_change_reason"]
|
| 915 |
+
)
|
| 916 |
+
st.success("์ํ๋ณํ๊ธฐ๋ก์ด ์ ์ฅ๋์๊ณ Client Center ๊ธ์ฌ๊ณํ ๋ณ๊ฒฝ๊ด๋ฆฌ์๋ ๋ฐ์๋์์ต๋๋ค.")
|
| 917 |
+
|
| 918 |
+
filename = f"์ํ๋ณํ๊ธฐ๋ก์ง_{data['client_masked_name']}_{data['record_date']}.xlsx"
|
| 919 |
+
st.download_button(
|
| 920 |
+
"๐ฅ ์ํ๋ณํ๊ธฐ๋ก์ง ์์
๋ค์ด๋ก๋",
|
| 921 |
+
data=generate_excel(data),
|
| 922 |
+
file_name=filename,
|
| 923 |
+
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
| 924 |
+
use_container_width=True,
|
| 925 |
+
)
|
| 926 |
+
|
| 927 |
+
elif menu == "์ ์ฅ๊ธฐ๋ก ๋ณด๊ธฐ":
|
| 928 |
+
st.subheader("๐ ์ ์ฅ๋ ์ํ๋ณํ๊ธฐ๋ก")
|
| 929 |
+
df = load_status_records()
|
| 930 |
+
if df.empty:
|
| 931 |
+
st.info("์ ์ฅ๋ ์ํ๋ณํ๊ธฐ๋ก์ด ์์ต๋๋ค.")
|
| 932 |
+
else:
|
| 933 |
+
st.dataframe(df, use_container_width=True)
|
| 934 |
+
|
| 935 |
+
st.caption("Client Center ์ฐ๋ํ ์ํ๋ณํ๊ธฐ๋ก์ง ์์ฑ๊ธฐ")
|