devorah-ai-2016 commited on
Commit
8dea8bc
Β·
verified Β·
1 Parent(s): 593b888

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +432 -0
app.py ADDED
@@ -0,0 +1,432 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from openpyxl import Workbook
3
+ from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
4
+ from openpyxl.utils import get_column_letter
5
+ from io import BytesIO
6
+ from datetime import date, time
7
+ import sqlite3
8
+
9
+ st.set_page_config(page_title="λ°©λ¬Έλͺ©μš• μˆ˜κΈ‰μž 상담일지 μžλ™μƒμ„±κΈ°", layout="wide")
10
+
11
+ st.title("πŸ› λ°©λ¬Έλͺ©μš• μˆ˜κΈ‰μž 상담일지 μžλ™μƒμ„±κΈ° v1")
12
+ st.caption("μƒλ‹΄μ˜μ—­ β†’ λ¬Έμ œμƒν™© β†’ 원인 β†’ 쑰치 β†’ κΈ‰μ—¬λ°˜μ˜ β†’ μ—‘μ…€ λ‹€μš΄λ‘œλ“œ")
13
+ st.warning("⚠️ **β€» λ³Έ μƒμ„±κΈ°λŠ” ν•˜λ£¨ 2회 이용 ν•œλ„ μ œν•œμ΄ μžˆμŠ΅λ‹ˆλ‹€.**")
14
+
15
+ DB_FILE = "usage.db"
16
+
17
+ def init_db():
18
+ conn = sqlite3.connect(DB_FILE)
19
+ cursor = conn.cursor()
20
+ cursor.execute("""
21
+ CREATE TABLE IF NOT EXISTS usage_limit (
22
+ institution TEXT,
23
+ user_id TEXT,
24
+ use_date TEXT,
25
+ count INTEGER,
26
+ PRIMARY KEY (institution, user_id, use_date)
27
+ )
28
+ """)
29
+ conn.commit()
30
+ conn.close()
31
+
32
+ def get_today_usage(institution, user_id):
33
+ if not institution or not user_id:
34
+ return 0
35
+ init_db()
36
+ today_str = date.today().isoformat()
37
+ conn = sqlite3.connect(DB_FILE)
38
+ cursor = conn.cursor()
39
+ cursor.execute(
40
+ "SELECT count FROM usage_limit WHERE institution = ? AND user_id = ? AND use_date = ?",
41
+ (institution.strip(), user_id.strip(), today_str)
42
+ )
43
+ row = cursor.fetchone()
44
+ conn.close()
45
+ return row[0] if row else 0
46
+
47
+ def increase_today_usage(institution, user_id):
48
+ if not institution or not user_id:
49
+ return
50
+ init_db()
51
+ today_str = date.today().isoformat()
52
+ conn = sqlite3.connect(DB_FILE)
53
+ cursor = conn.cursor()
54
+ cursor.execute(
55
+ "SELECT count FROM usage_limit WHERE institution = ? AND user_id = ? AND use_date = ?",
56
+ (institution.strip(), user_id.strip(), today_str)
57
+ )
58
+ row = cursor.fetchone()
59
+ if row:
60
+ cursor.execute(
61
+ "UPDATE usage_limit SET count = count + 1 WHERE institution = ? AND user_id = ? AND use_date = ?",
62
+ (institution.strip(), user_id.strip(), today_str)
63
+ )
64
+ else:
65
+ cursor.execute(
66
+ "INSERT INTO usage_limit (institution, user_id, use_date, count) VALUES (?, ?, ?, 1)",
67
+ (institution.strip(), user_id.strip(), today_str)
68
+ )
69
+ conn.commit()
70
+ conn.close()
71
+
72
+ if "generated_data" not in st.session_state:
73
+ st.session_state.generated_data = None
74
+
75
+ inst_val = st.session_state.get("institution", "").strip()
76
+ user_val = st.session_state.get("user_id", "").strip()
77
+ today_count = get_today_usage(inst_val, user_val)
78
+
79
+ # Sidebar Usage Panel
80
+ st.sidebar.markdown("### πŸ“Š μ‚¬μš© ν•œλ„ 관리")
81
+ if not inst_val or not user_val:
82
+ st.sidebar.warning("⚠️ κΈ°κ΄€λͺ…κ³Ό μ‚¬μš©μž 식별값을 μž…λ ₯ν•΄μ£Όμ„Έμš”.")
83
+ else:
84
+ st.sidebar.write(f"였늘 생성 횟수: **{today_count} / 2회**")
85
+ if today_count >= 2:
86
+ st.sidebar.error("❌ 생성 ν•œλ„ 초과 (2/2)")
87
+ else:
88
+ st.sidebar.success(f"βœ… 생성 κ°€λŠ₯ (남은 횟수: {2 - today_count}회)")
89
+
90
+ REQUIRED_ITEMS = [
91
+ "μˆ˜κΈ‰μž μ„±λͺ…", "생년월일", "성별", "μž₯κΈ°μš”μ–‘ λ“±κΈ‰", "계약일/μ‹œμž‘μΌ", "본인뢀담λ₯ ",
92
+ "상담 μΌμ‹œ", "상담 ꡬ뢄", "상담 μž₯μ†Œ", "상담 방법", "상담 λŒ€μƒμž μ„±λͺ…", "μƒλ‹΄μžμ™€μ˜ 관계",
93
+ "κΈ°κ΄€ λ‹΄λ‹Ήμž 직책 및 μ„±λͺ…", "λ™μ„μž", "상담 λͺ©μ  및 μ‚¬μœ ",
94
+ "신체 κΈ°λŠ₯ μƒνƒœ λ³€ν™” 및 낙상 μœ„ν—˜", "인지 κΈ°λŠ₯ 및 심리 μƒνƒœ", "ν”ΌλΆ€ μƒνƒœ 및 μœ„μƒ μƒνƒœ",
95
+ "λͺ©μš• μ°¨λŸ‰/μž₯λΉ„ μ•ˆμ „ μƒνƒœ", "보호자의 μš”μ²­ 사항",
96
+ "κΈ°κ΄€μ˜ μ„€λͺ… 및 μ•ˆλ‚΄ 사항", "문제 ν•΄κ²° λ˜λŠ” 제곡된 정보 λ‚΄μš©", "ν–₯ν›„ κΈ‰μ—¬ 제곡 κ³„νš",
97
+ "κΈ‰μ—¬ 반영 μ—¬λΆ€", "사후 쑰치 및 λͺ¨λ‹ˆν„°λ§ κ³„νš", "보호자 전달/곡지사항",
98
+ "μƒλ‹΄μž μ„œλͺ…", "λŒ€μƒμž λ˜λŠ” 보호자 μ„œλͺ…"
99
+ ]
100
+
101
+ AREA_DATA = {
102
+ "λͺ©μš• κ±°λΆ€/ν˜‘μ‘°λ„": {
103
+ "problems": ["λͺ©μš• κ±°λΆ€", "νƒˆμ˜ κ±°λΆ€", "물에 λŒ€ν•œ 곡포심", "ν”ΌλΆ€ 접촉 예민", "ν”Όλ‘œκ° ν˜Έμ†Œ", "μƒ€μ›ŒκΈ° μ†Œλ¦¬μ— λΆˆμ•ˆ"],
104
+ "causes": ["치맀 인지 μ €ν•˜", "μΆ”μœ„μ— 민감함", "κ³Όκ±° 뢀정적 κ²½ν—˜", "신체 λ…ΈμΆœ μˆ˜μΉ˜μ‹¬", "ν”ΌλΆ€ 건쑰 및 가렀움", "μ»¨λ””μ…˜ μ €ν•˜"],
105
+ "actions": ["λ”°λœ»ν•œ μ‹€λ‚΄ μ˜¨λ„ μœ μ§€", "μΆ©λΆ„ν•œ 지지적 λŒ€ν™”", "νƒ€μ›”λ‘œ λͺΈμ„ 감싼 채 μ§„ν–‰", "μ„ ν˜Έν•˜λŠ” μŒμ•… ν‹€κΈ°", "κ°„ν˜Έμ‚¬ 곡유", "보호자 μ•ˆλ‚΄"],
106
+ },
107
+ "ν”ΌλΆ€ μƒνƒœ/발적/μƒμ²˜": {
108
+ "problems": ["ν”ΌλΆ€ 건쑰", "엉덩이 발적", "가렀움증 ν˜Έμ†Œ", "ν”ΌλΆ€ 짓무름", "λ―Έμ„Έ 찰과상", "각질 심화"],
109
+ "causes": ["ν˜ˆμ•‘ μˆœν™˜ μ €ν•˜", "μ˜€λž«λ™μ•ˆ μ•‰μ•„μžˆμŒ", "κΈ°μ €κ·€ μž₯κΈ° 착용", "μˆ˜λΆ„ μ„­μ·¨ λΆ€μ‘±", "ν”ΌλΆ€ μž₯λ²½ μ•½ν™”"],
110
+ "actions": ["λͺ©μš• ν›„ 보슡제 도포", "λ§ˆμ‚¬μ§€ 도움", "ν”ΌλΆ€ μœ μ‹¬νžˆ κ΄€μ°° 및 기둝", "κ°„ν˜Έμ‚¬/보호자 보고", "κΈ°μ €κ·€ ꡐ체 μ£ΌκΈ° 단좕 κ²€ν† ", "건쑰 κΈ°λŠ₯ κ°•ν™”"],
111
+ },
112
+ "체온/λ°”μ΄νƒˆ λ³€ν™”": {
113
+ "problems": ["λͺ©μš• μ „ν›„ ν˜ˆμ•• μƒμŠΉ", "λͺ©μš• ν›„ ν•œκΈ° ν˜Έμ†Œ", "λ―Έμ—΄ λ°œμƒ", "μ–΄μ§€λŸ¬μ›€ ν˜Έμ†Œ", "κΈ‰κ²©ν•œ ν”Όλ‘œκ°"],
114
+ "causes": ["μ‹€λ‚΄μ™Έ μ˜¨λ„μ°¨", "μˆ˜λΆ„ 손싀", "체λ ₯ μ†Œλͺ¨", "온수 자극", "κΈ°μ € 건강 μƒνƒœ"],
115
+ "actions": ["λͺ©μš• μ‹œκ°„ 단좕(20~30λΆ„ 이내)", "λͺ©μš• ν›„ λ”°λœ»ν•œ 음료 제곡", "체온 μƒμ‹œ μΈ‘μ •", "κ°„ν˜Έμ‚¬ 보고 및 μ‘κΈ‰μ‘°μΉ˜ μ€€λΉ„", "보호자 상담"],
116
+ },
117
+ "낙상/이동 λΆˆμ•ˆμ •": {
118
+ "problems": ["λ―Έλ„λŸ¬μš΄ λ°”λ‹₯ μœ„ν—˜", "μš•μ‘° μΆœμž… μ‹œ κ· ν˜• 상싀", "λΆ€μΆ• μ‹œ 체쀑 μ‹€λ¦Ό", "νœ μ²΄μ–΄ 이동 μ‹œ λΆˆμ•ˆμ •", "기립 μ‹œ μ–΄μ§€λŸ¬μ›€"],
119
+ "causes": ["ν•˜μ§€ κ·Όλ ₯ μ•½ν™”", "λ°”λ‹₯ λ¬ΌκΈ° 및 λΉ„λˆ—λ¬Ό", "인지 κΈ°λŠ₯ μ €ν•˜", "이동 보쑰기ꡬ λ―ΈλΉ„"],
120
+ "actions": ["λ―Έλ„λŸΌ λ°©μ§€ 맀트 μΆ”κ°€ μ„€μΉ˜", "μ•ˆμ „ μ†μž‘μ΄ ν™œμš© 보쑰", "λͺ©μš• 의자 μƒμ‹œ μ‚¬μš©", "이동 보쑰 인λ ₯ 2인 ν˜‘μ‘°", "μ•ˆμ „ μˆ˜μΉ™ ꡐ윑"],
121
+ },
122
+ "μœ„μƒ/일반 κ±΄κ°•μƒνƒœ": {
123
+ "problems": ["두피 각질/비듬", "μ†λ°œν†± 관리 ν•„μš”", "λ°°μ„€λ¬Ό μ˜€μ—Ό", "ꡬ강 μœ„μƒ λΆˆλŸ‰", "체취 심화"],
124
+ "causes": ["κ°œμΈμœ„μƒ 관리 λΆ€μ‘±", "인지 μ €ν•˜λ‘œ μœ„μƒ κ±°λΆ€", "신체 λ™μž‘ λ²”μœ„ μ œν•œ"],
125
+ "actions": ["두피 집쀑 μ„Έμ •", "λͺ©μš• ν›„ μ†λ°œν†± 정리", "μœ„μƒ κ°€μ΄λ“œ 적용", "ꡬ강 μΌ€μ–΄ 병행", "μ²­κ²° μš©ν’ˆ μ„ ν˜Έλ„ 쑰사"],
126
+ },
127
+ }
128
+
129
+ def make_sentence(area, problems, causes, actions, age, grade, mobility, cognition):
130
+ problem_text = ", ".join(problems)
131
+ cause_text = ", ".join(causes)
132
+ action_text = ", ".join(actions)
133
+
134
+ consult = (
135
+ f"μˆ˜κΈ‰μžλŠ” 만 {age}μ„Έ, μž₯κΈ°μš”μ–‘ {grade}이며 ν˜„μž¬ μ΄λ™μƒνƒœλŠ” '{mobility}', μΈμ§€μƒνƒœλŠ” '{cognition}'으둜 ν™•μΈλœλ‹€.\n\n"
136
+ f"λ°©λ¬Έλͺ©μš• κΈ‰μ—¬μ œκ³΅ κ³Όμ • 및 {area} κ΄€λ ¨ 상담을 μ‹€μ‹œν•œ κ²°κ³Ό, μ£Όμš” λ¬Έμ œμƒν™©μœΌλ‘œ {problem_text}이/κ°€ ν™•μΈλ˜μ—ˆλ‹€. "
137
+ f"λͺ©μš• μ„œλΉ„μŠ€ μ§„ν–‰ μ‹œ μˆ˜κΈ‰μžμ˜ ν˜„μž¬ 신체적 가동 λ²”μœ„, 인지 λŠ₯λ ₯, 심리 μƒνƒœμ— λ”°λ₯Έ μ„ΈλΆ€ 관찰이 μš”λ§λœλ‹€.\n\n"
138
+ f"상담 κ³Όμ •μ—μ„œ {cause_text} 등이 μ›μΈμœΌλ‘œ μž‘μš©ν•  κ°€λŠ₯성을 ν™•μΈν•˜μ˜€λ‹€. "
139
+ f"κΈ°κ΄€μ—μ„œλŠ” λͺ©μš• 쀑 κΈ‰κ²©ν•œ 체λ ₯ μ €ν•˜λ‚˜ ν˜ˆμ•• λ³€ν™”κ°€ λ°œμƒν•˜λŠ”μ§€ 예의 μ£Όμ‹œν•˜κΈ°λ‘œ ν•˜μ˜€λ‹€."
140
+ )
141
+
142
+ action = (
143
+ f"λ°©λ¬Έλͺ©μš• μ§„ν–‰ μ‹œ μˆ˜κΈ‰μžμ˜ μš”κ΅¬μ™€ μƒνƒœμ— λ§žμΆ”μ–΄ {action_text} λ“±μ˜ 쑰치λ₯Ό μ‹€μ‹œν•˜κ±°λ‚˜ κ³„νšν•˜κΈ°λ‘œ ν•˜μ˜€λ‹€. "
144
+ f"λͺ©μš• μš”μ–‘λ³΄ν˜Έμ‚¬ 및 동행 μ§μ›μ—κ²Œ μ„ΈλΆ€ 쑰치 사항을 μ „νŒŒν•˜κ³  μ•ˆμ „ν•œ κΈ‰μ—¬ 제곡이 이루어지도둝 μ‘°μΉ˜ν•˜μ˜€λ‹€.\n\n"
145
+ f"ν–₯ν›„ λͺ©μš• κ±°λΆ€λ‚˜ 낙상 μœ„ν—˜ λ“± νŠΉμ΄μ‚¬ν•­ 지속 μ‹œ μš•κ΅¬μ‚¬μ •μ— λ°˜μ˜ν•˜κ³ , ν•„μš” μ‹œ λ°©λ¬Έμš”μ–‘/κ°„ν˜Έ μ„œλΉ„μŠ€ 연계λ₯Ό κ²€ν† ν•˜κΈ°λ‘œ ν•˜μ˜€λ‹€."
146
+ )
147
+
148
+ reflection = (
149
+ "상담을 톡해 ν™•μΈλœ μš”κ΅¬μ‚¬ν•­ 및 쑰치 κ³„νšμ€ μž₯κΈ°μš”μ–‘ κΈ‰μ—¬μ œκ³΅ 과정에 적극 λ°˜μ˜ν•œλ‹€. "
150
+ "반영 κ°€λŠ₯ν•œ μ„ΈλΆ€ 사항은 λ‹€μŒ λ°©λ¬Έλͺ©μš• 일정 μ‹œ μ μš©ν•˜κ³ , "
151
+ "μž₯κΈ°μš”μ–‘κΈ°κ΄€ 평가 κ³ μ‹œ 기쀀에 λ§žμΆ”μ–΄ κΈ‰μ—¬μ œκ³΅κ³„νšμ„œ 보완을 κ²€ν† ν•˜κΈ°λ‘œ ν•œλ‹€."
152
+ )
153
+
154
+ monitoring = (
155
+ "λͺ©μš• λ‹΄λ‹Ή μš”μ–‘λ³΄ν˜Έμ‚¬μ˜ μ„œλΉ„μŠ€ ν›„ ν”Όλ“œλ°± 및 ν”ΌλΆ€/λ°”μ΄νƒˆ λͺ¨λ‹ˆν„°λ§ 기둝을 μ£Ό 1회 이상 κ²€ν† ν•œλ‹€. "
156
+ "λ‹€μŒ 상담 μ‹œ λ¬Έμ œμƒν™©μ˜ 경감 여뢀와 쑰치의 νš¨κ³Όμ„±μ„ λͺ¨λ‹ˆν„°λ§ν•˜μ—¬ 지속적인 μœ„μƒ 관리λ₯Ό μ‹€μ‹œν•˜κΈ°λ‘œ ν•œλ‹€."
157
+ )
158
+
159
+ return consult, action, reflection, monitoring
160
+
161
+ def make_excel(data, consult, action, reflection, monitoring, checked_items, missing_items):
162
+ wb = Workbook()
163
+ ws = wb.active
164
+ ws.title = "μˆ˜κΈ‰μž 상담일지"
165
+
166
+ thin = Side(style="thin", color="000000")
167
+ border = Border(left=thin, right=thin, top=thin, bottom=thin)
168
+ header_fill = PatternFill("solid", fgColor="EAF2F8") # Soft blue
169
+
170
+ for col in range(1, 10):
171
+ ws.column_dimensions[get_column_letter(col)].width = 15
172
+
173
+ current_row = 1
174
+
175
+ ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=9)
176
+ ws.cell(current_row, 1).value = f"( {data['κΈ°κ΄€λͺ…']} ) λ°©λ¬Έλͺ©μš• 상담일지"
177
+ ws.cell(current_row, 1).font = Font(bold=True, size=20)
178
+ ws.cell(current_row, 1).alignment = Alignment(horizontal="center", vertical="center")
179
+ ws.row_dimensions[current_row].height = 36
180
+ current_row += 1
181
+
182
+ info_rows = [
183
+ ("μˆ˜κΈ‰μž μ„±λͺ…", data["μˆ˜κΈ‰μž μ„±λͺ…"], "생년월일", data["생년월일"], "성별", data["성별"]),
184
+ ("λ‚˜μ΄", data["λ‚˜μ΄"], "μž₯κΈ°μš”μ–‘ λ“±κΈ‰", data["μž₯κΈ°μš”μ–‘ λ“±κΈ‰"], "μ‹œμž‘μΌ", str(data["μ‹œμž‘μΌ"])),
185
+ ("본인뢀담λ₯ ", data["본인뢀담λ₯ "], "μƒλ‹΄μΌμž", str(data["μƒλ‹΄μΌμž"]), "μƒλ‹΄μ‹œκ°„", str(data["μƒλ‹΄μ‹œκ°„"])),
186
+ ("상담ꡬ뢄", data["상담ꡬ뢄"], "상담μž₯μ†Œ", data["상��μž₯μ†Œ"], "상담방법", data["상담방법"]),
187
+ ("μƒλ‹΄λŒ€μƒμž", data["μƒλ‹΄λŒ€μƒμž"], "관계", data["관계"], "λ™μ„μž", data["λ™μ„μž"]),
188
+ ("λ‹΄λ‹Ήμž", data["λ‹΄λ‹Ήμž"], "직책", data["직책"], "μƒλ‹΄μ˜μ—­", data["μƒλ‹΄μ˜μ—­"]),
189
+ ("μ΄λ™μƒνƒœ", data["μ΄λ™μƒνƒœ"], "μΈμ§€μƒνƒœ", data["μΈμ§€μƒνƒœ"], "λͺ©μš•μœ ν˜•", data["λͺ©μš•μœ ν˜•"]),
190
+ ]
191
+
192
+ for row_data in info_rows:
193
+ ws.cell(current_row, 1).value = row_data[0]
194
+ ws.merge_cells(start_row=current_row, start_column=2, end_row=current_row, end_column=3)
195
+ ws.cell(current_row, 2).value = row_data[1]
196
+ ws.cell(current_row, 4).value = row_data[2]
197
+ ws.merge_cells(start_row=current_row, start_column=5, end_row=current_row, end_column=6)
198
+ ws.cell(current_row, 5).value = row_data[3]
199
+ ws.cell(current_row, 7).value = row_data[4]
200
+ ws.merge_cells(start_row=current_row, start_column=8, end_row=current_row, end_column=9)
201
+ ws.cell(current_row, 8).value = row_data[5]
202
+
203
+ for col in [1, 4, 7]:
204
+ ws.cell(current_row, col).fill = header_fill
205
+ ws.cell(current_row, col).font = Font(bold=True)
206
+ ws.cell(current_row, col).alignment = Alignment(horizontal="center", vertical="center")
207
+ current_row += 1
208
+
209
+ sections = [
210
+ ("상담 λ‚΄μš© (μˆ˜κΈ‰μž μƒνƒœ λ³€ν™” 및 μš”κ΅¬μ‚¬ν•­)", consult, 3),
211
+ ("쑰치 및 ν•΄κ²° κ³„νš", action, 3),
212
+ ("κΈ‰μ—¬μ œκ³΅κ³„νš 반영 λ‚΄μš©", reflection, 3),
213
+ ("사후 쑰치 및 λͺ¨λ‹ˆν„°λ§ κ³„νš", monitoring, 3),
214
+ ]
215
+
216
+ for title, text, height in sections:
217
+ ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=9)
218
+ ws.cell(current_row, 1).value = title
219
+ ws.cell(current_row, 1).fill = header_fill
220
+ ws.cell(current_row, 1).font = Font(bold=True)
221
+ ws.cell(current_row, 1).alignment = Alignment(horizontal="center")
222
+ current_row += 1
223
+
224
+ ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row + height - 1, end_column=9)
225
+ ws.cell(current_row, 1).value = text
226
+ ws.cell(current_row, 1).alignment = Alignment(horizontal="left", vertical="top", wrap_text=True)
227
+
228
+ # ν…μŠ€νŠΈ 길이에 따라 높이λ₯Ό λ™μ μœΌλ‘œ μžλ™ κ³„μ‚°ν•˜μ—¬ 3개 행에 λ‚˜λˆ„μ–΄ λ°°μ •
229
+ lines = len(text) // 65 + 1 + text.count('\n') if text else 1
230
+ total_height = max(72, lines * 20 + 10)
231
+ single_row_height = total_height / height
232
+ for r_offset in range(height):
233
+ ws.row_dimensions[current_row + r_offset].height = single_row_height
234
+
235
+ current_row += height
236
+
237
+ ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=3)
238
+ ws.merge_cells(start_row=current_row, start_column=4, end_row=current_row, end_column=6)
239
+ ws.merge_cells(start_row=current_row, start_column=7, end_row=current_row, end_column=9)
240
+
241
+ ws.cell(current_row, 1).value = "μƒλ‹΄μž μ„œλͺ…"
242
+ ws.cell(current_row, 4).value = f"{data['λ‹΄λ‹Ήμž']} (μ„œλͺ…)"
243
+ ws.cell(current_row, 7).value = "λŒ€μƒμž/보호자 (μ„œλͺ…)"
244
+ ws.cell(current_row, 1).fill = header_fill
245
+ ws.cell(current_row, 7).fill = header_fill
246
+
247
+ max_row = current_row
248
+
249
+ for row in ws.iter_rows(min_row=1, max_row=max_row, min_col=1, max_col=9):
250
+ for cell in row:
251
+ cell.border = border
252
+ cell.alignment = Alignment(
253
+ horizontal=cell.alignment.horizontal or "left",
254
+ vertical=cell.alignment.vertical or "center",
255
+ wrap_text=True
256
+ )
257
+
258
+ for r in range(1, max_row + 1):
259
+ if ws.row_dimensions[r].height is None:
260
+ ws.row_dimensions[r].height = 24
261
+
262
+ ws.page_setup.paperSize = ws.PAPERSIZE_A4
263
+ ws.page_setup.orientation = "portrait"
264
+ ws.page_setup.fitToWidth = 1
265
+ ws.page_setup.fitToHeight = 0
266
+ ws.sheet_properties.pageSetUpPr.fitToPage = True
267
+
268
+ ws.page_margins.left = 0.3
269
+ ws.page_margins.right = 0.3
270
+ ws.page_margins.top = 0.4
271
+ ws.page_margins.bottom = 0.4
272
+ ws.print_options.horizontalCentered = True
273
+
274
+ output = BytesIO()
275
+ wb.save(output)
276
+ output.seek(0)
277
+ return output
278
+
279
+ col1, col2, col3 = st.columns(3)
280
+
281
+ with col1:
282
+ facility = st.text_input("κΈ°κ΄€λͺ…", value="BeeCare μž¬κ°€λ³΅μ§€μ„Όν„°", key="institution")
283
+ user_id = st.text_input("μ‚¬μš©μž 식별값 (예: νœ΄λŒ€ν°λ²ˆν˜Έ λ’·μžλ¦¬ λ“±)", value="", key="user_id", help="ν•˜λ£¨ 2회 μ œν•œ 관리λ₯Ό μœ„ν•΄ μ‚¬μš©λ©λ‹ˆλ‹€.")
284
+ name = st.text_input("μˆ˜κΈ‰μž μ„±λͺ…", "이길순")
285
+ birth = st.text_input("생년월일", "1945-05-15")
286
+ gender = st.selectbox("성별", ["μ—¬", "남"])
287
+ age = st.number_input("λ‚˜μ΄", min_value=60, max_value=110, value=81)
288
+
289
+ with col2:
290
+ grade = st.selectbox("μž₯κΈ°μš”μ–‘ λ“±κΈ‰", ["1λ“±κΈ‰", "2λ“±κΈ‰", "3λ“±κΈ‰", "4λ“±κΈ‰", "5λ“±κΈ‰"])
291
+ admission_date = st.date_input("계약일/μ‹œμž‘μΌ", date.today())
292
+ copay = st.selectbox("본인뢀담���", ["15%", "0%", "6%", "9%", "12%", "20%"])
293
+ consult_date = st.date_input("μƒλ‹΄μΌμž", date.today())
294
+
295
+ with col3:
296
+ consult_time = st.time_input("μƒλ‹΄μ‹œκ°„", time(14, 0))
297
+ consult_type = st.selectbox("상담 ꡬ뢄", ["μ •κΈ° 상담", "μˆ˜μ‹œ 상담", "μ‹ κ·œ 계약 상담", "보호자 μš”μ²­ 상담"])
298
+ place = st.selectbox("상담 μž₯μ†Œ", ["μˆ˜κΈ‰μž μžνƒ", "μ„Όν„° 사무싀", "μœ μ„ μƒλ‹΄", "기타"])
299
+ method = st.selectbox("상담 방법", ["λŒ€λ©΄ λ©΄λ‹΄", "μœ μ„ /μ „ν™” 상담", "기타"])
300
+ target = st.text_input("상담 λŒ€μƒμž μ„±λͺ…", "μžλ…€")
301
+
302
+ col4, col5, col6 = st.columns(3)
303
+
304
+ with col4:
305
+ relation = st.selectbox("μƒλ‹΄μžμ™€μ˜ 관계", ["λ”Έ", "μ•„λ“€", "본인", "배우자", "며느리", "μ‚¬μœ„", "기타"])
306
+ staff_position = st.selectbox("κΈ°κ΄€ λ‹΄λ‹Ήμž 직책", ["μ‚¬νšŒλ³΅μ§€μ‚¬", "μš”μ–‘λ³΄ν˜Έμ‚¬", "κ°„ν˜Έμ‚¬", "μ‹œμ„€μž₯"])
307
+ staff_name = st.text_input("κΈ°κ΄€ λ‹΄λ‹Ήμž μ„±λͺ…", "μ‚¬νšŒλ³΅μ§€μ‚¬")
308
+
309
+ with col5:
310
+ companion = st.text_input("λ™μ„μž", "μ—†μŒ")
311
+ mobility = st.selectbox("μ΄λ™μƒνƒœ", ["λΆ€μΆ• ν•„μš”", "독립보행", "μ§€νŒ‘μ΄ 보행", "μ›Œμ»€ 보행", "νœ μ²΄μ–΄ 이동", "와상 μƒνƒœ"])
312
+ cognition = st.selectbox("μΈμ§€μƒνƒœ", ["경도 치맀", "인지 μ–‘ν˜Έ", "쀑등도 치맀", "쀑증 치맀", "μ˜μ‚¬μ†Œν†΅ 어렀움"])
313
+
314
+ with col6:
315
+ bath_type = st.selectbox("λͺ©μš•μœ ν˜•", ["λ°©λ¬Έλͺ©μš•(μ°¨λŸ‰λ‚΄)", "λ°©λ¬Έλͺ©μš•(κ°€μ •λ‚΄μš•μ‘°)", "κ°€μ •λ‚΄μƒ€μ›Œλ„μ›€", "기타"])
316
+ area = st.selectbox("μƒλ‹΄μ˜μ—­ 선택", list(AREA_DATA.keys()))
317
+
318
+ # --- GENERATION FLOW WITH EXPLICIT LIMIT LIMITATION ---
319
+ st.markdown("---")
320
+
321
+ is_inputs_filled = bool(facility.strip() and user_id.strip())
322
+ today_count = get_today_usage(facility, user_id) if is_inputs_filled else 0
323
+ can_generate = is_inputs_filled and (today_count < 2)
324
+
325
+ if not is_inputs_filled:
326
+ st.warning("⚠️ 생성 κΈ°λŠ₯을 μ΄μš©ν•˜μ‹œλ €λ©΄ 상단 μž…λ ₯μ°½μ—μ„œ [κΈ°κ΄€λͺ…]κ³Ό [μ‚¬μš©μž 식별값]을 μ •ν™•νžˆ μž…λ ₯ν•΄ μ£Όμ„Έμš”.")
327
+ elif today_count >= 2:
328
+ st.error("🚨 였늘 μ‚¬μš© κ°€λŠ₯ 횟수(2회)λ₯Ό λͺ¨λ‘ μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€. μƒˆλ‘œμš΄ λ¬Έμ„œ 생성은 λΆˆκ°€ν•˜λ©°, μ•„λž˜ κΈ°μ‘΄ μƒμ„±λœ λ¬Έμ„œμ˜ μˆ˜μ • 및 λ‹€μš΄λ‘œλ“œλ§Œ κ°€λŠ₯ν•©λ‹ˆλ‹€.")
329
+
330
+ checked_items = []
331
+ st.subheader("ν•„μˆ˜ κΈ°μž¬ν•­λͺ© 체크리슀트")
332
+ for item in REQUIRED_ITEMS:
333
+ if st.checkbox(item, value=True, key=f"check_{item}"):
334
+ checked_items.append(item)
335
+
336
+ missing_items = [item for item in REQUIRED_ITEMS if item not in checked_items]
337
+ score = int((len(checked_items) / len(REQUIRED_ITEMS)) * 100)
338
+
339
+ if score == 100:
340
+ st.success(f"상담일지 완성도: {score}점 / λˆ„λ½ ν•­λͺ© μ—†μŒ")
341
+ elif score >= 85:
342
+ st.warning(f"상담일지 완성도: {score}점 / 일뢀 ν•­λͺ© 확인 ν•„μš”")
343
+ else:
344
+ st.error(f"상담일지 완성도: {score}점 / ν•„μˆ˜ν•­λͺ© 보완 ν•„μš”")
345
+
346
+ data = {
347
+ "κΈ°κ΄€λͺ…": facility,
348
+ "μˆ˜κΈ‰μž μ„±λͺ…": name,
349
+ "생년월일": birth,
350
+ "성별": gender,
351
+ "λ‚˜μ΄": age,
352
+ "μž₯κΈ°μš”μ–‘ λ“±κΈ‰": grade,
353
+ "μ‹œμž‘μΌ": admission_date,
354
+ "본인뢀담λ₯ ": copay,
355
+ "μƒλ‹΄μΌμž": consult_date,
356
+ "μƒλ‹΄μ‹œκ°„": consult_time,
357
+ "상담ꡬ뢄": consult_type,
358
+ "상담μž₯μ†Œ": place,
359
+ "상담방법": method,
360
+ "μƒλ‹΄λŒ€μƒμž": target,
361
+ "관계": relation,
362
+ "λ‹΄λ‹Ήμž": staff_name,
363
+ "직책": staff_position,
364
+ "λ™μ„μž": companion,
365
+ "μƒλ‹΄μ˜μ—­": area,
366
+ "μ΄λ™μƒνƒœ": mobility,
367
+ "μΈμ§€μƒνƒœ": cognition,
368
+ "λͺ©μš•μœ ν˜•": bath_type,
369
+ }
370
+
371
+ if st.button("πŸ“ 상담일지 & μ—‘μ…€ μƒμ„±ν•˜κΈ°", disabled=not can_generate):
372
+ if not is_inputs_filled:
373
+ st.error("κΈ°κ΄€λͺ…κ³Ό μ‚¬μš©μž 식별값을 μž…λ ₯ν•΄μ•Ό 생성할 수 μžˆμŠ΅λ‹ˆλ‹€.")
374
+ st.stop()
375
+
376
+ if today_count >= 2:
377
+ st.error("였늘 μ‚¬μš© κ°€λŠ₯ 횟수λ₯Ό λͺ¨λ‘ μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€.")
378
+ st.stop()
379
+
380
+ # 생성 성곡 μ‹œ DB에 횟수 μΆ”κ°€
381
+ increase_today_usage(facility, user_id)
382
+
383
+ # ν…μŠ€νŠΈ μžλ™ 생성
384
+ gen_consult, gen_action, gen_reflection, gen_monitoring = make_sentence(
385
+ area, problems, causes, actions, age, grade, mobility, cognition
386
+ )
387
+
388
+ st.session_state.generated_data = {
389
+ "consult": gen_consult,
390
+ "action": gen_action,
391
+ "reflection": gen_reflection,
392
+ "monitoring": gen_monitoring,
393
+ "data": data,
394
+ "checked_items": checked_items,
395
+ "missing_items": missing_items
396
+ }
397
+ st.success("πŸŽ‰ 상담일지가 μ„±κ³΅μ μœΌλ‘œ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€! μ•„λž˜μ—μ„œ λ‚΄μš©μ„ 확인 및 μˆ˜μ •ν•œ ν›„ μ—‘μ…€ νŒŒμΌμ„ λ‹€μš΄λ‘œλ“œν•˜μ„Έμš”.")
398
+ st.rerun()
399
+
400
+ # μƒμ„±λœ 데이터가 μžˆμ„ κ²½μš°μ—λ§Œ ν•„λ“œ 및 λ‹€μš΄λ‘œλ“œ 제곡
401
+ if st.session_state.generated_data is not None:
402
+ g_data = st.session_state.generated_data
403
+
404
+ st.subheader("상담 λ‚΄μš©")
405
+ ui_consult = st.text_area("μƒλ‹΄λ‚΄μš© μˆ˜μ • κ°€λŠ₯", g_data["consult"], height=220)
406
+
407
+ st.subheader("쑰치 λ‚΄μš©")
408
+ ui_action = st.text_area("μ‘°μΉ˜λ‚΄μš© μˆ˜μ • κ°€λŠ₯", g_data["action"], height=180)
409
+
410
+ st.subheader("κΈ‰μ—¬μ œκ³΅ 반영 μ—¬λΆ€")
411
+ ui_reflection = st.text_area("κΈ‰μ—¬μ œκ³΅ 반영 λ‚΄μš© μˆ˜μ • κ°€λŠ₯", g_data["reflection"], height=130)
412
+
413
+ st.subheader("사후 쑰치 및 λͺ¨λ‹ˆν„°λ§ κ³„νš")
414
+ ui_monitoring = st.text_area("λͺ¨λ‹ˆν„°λ§ κ³„νš μˆ˜μ • κ°€λŠ₯", g_data["monitoring"], height=130)
415
+
416
+ # μˆ˜μ •λœ λ‚΄μš©μœΌλ‘œ μ—‘μ…€ λΉŒλ“œ
417
+ excel_file = make_excel(
418
+ g_data["data"],
419
+ ui_consult,
420
+ ui_action,
421
+ ui_reflection,
422
+ ui_monitoring,
423
+ g_data["checked_items"],
424
+ g_data["missing_items"]
425
+ )
426
+
427
+ st.download_button(
428
+ label="πŸ“₯ μˆ˜κΈ‰μž 상담일지 μ—‘μ…€ λ‹€μš΄λ‘œλ“œ",
429
+ data=excel_file,
430
+ file_name=f"{g_data['data']['μˆ˜κΈ‰μž μ„±λͺ…']}_{g_data['data']['μƒλ‹΄μ˜μ—­']}_μˆ˜κΈ‰μžμƒλ‹΄μΌμ§€.xlsx",
431
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
432
+ )