devorah-ai-2016 commited on
Commit
888c3b1
·
verified ·
1 Parent(s): 8dea8bc

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -432
app.py DELETED
@@ -1,432 +0,0 @@
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
- )