import sys import os import random import re import pandas as pd import streamlit as st from datetime import date, datetime, timedelta from io import BytesIO from openpyxl import Workbook, load_workbook from openpyxl.styles import Font, Alignment, Border, Side, PatternFill from openpyxl.utils import get_column_letter from openai import OpenAI # 로컬 db_sync 모듈 sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import db_sync CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) # ----------------------------- # Streamlit 기본 설정 & 테마 CSS # ----------------------------- st.set_page_config(page_title="BeeCare Core Integration", page_icon="🐝", layout="wide") st.markdown(""" """, unsafe_allow_html=True) # ----------------------------- # 데이터 동기화 기동 시 자동 다운로드 # ----------------------------- if "db_downloaded" not in st.session_state: with st.spinner("허깅페이스 DB 데이터 로딩 중..."): db_sync.download_all() st.session_state["db_downloaded"] = True # ----------------------------- # 엑셀 데이터 파일 입출력 헬퍼 # ----------------------------- def load_excel_data(relative_path, default_cols): local_path = os.path.join(CURRENT_DIR, relative_path) os.makedirs(os.path.dirname(local_path), exist_ok=True) if os.path.exists(local_path): try: df = pd.read_excel(local_path) # 결락 컬럼 보완 for col in default_cols: if col not in df.columns: df[col] = "" return df except Exception: pass df = pd.DataFrame(columns=default_cols) df.to_excel(local_path, index=False) return df def save_excel_data(df, relative_path): local_path = os.path.join(CURRENT_DIR, relative_path) os.makedirs(os.path.dirname(local_path), exist_ok=True) df.to_excel(local_path, index=False) # ----------------------------- # 기능별 컬럼 및 옵션 상수 선언 # ----------------------------- CLIENT_COLS = [ "NO.", "수급자이름", "가명표기", "성별", "생년월일", "실질생일날짜", "장기요양등급", "인정번호", "인정기간시작일", "인정기간종료일", "입소일", "이용상태", "주요질환", "인지상태", "이동상태", "식사상태", "배변/배뇨상태", "보호자이름", "보호자연락처", "특이사항", "비고", "기관유형", "기관명" ] COUNSELING_COLS = [ "NO.", "수급자이름", "가명표기", "상담일자", "상담자", "상담분류", "상담사유", "상담내용", "조치계획", "추가욕구요청사항", "사례관리필요", "욕구사정필요", "급여제공계획변경필요", "등록일자" ] CASE_MANAGEMENT_COLS = [ "NO.", "수급자이름", "가명표기", "회의일자", "참석자", "회의분류", "사례선정이유", "해결방안(1차계획)", "시행과정", "종합결과(2차평가)", "등록일자" ] VALID_GENDERS = ["여", "남", "기타"] VALID_GRADES = ["1등급", "2등급", "3등급", "4등급", "5등급", "인지지원등급", "등급외", "미입력"] VALID_INST_TYPES = ["방문요양", "방문목욕", "방문간호", "주간보호", "요양원", "공동생활가정"] VALID_STATUS = ["이용중", "입소중", "퇴소", "중지", "사망"] def load_clients(): df = load_excel_data("data/clients.xlsx", CLIENT_COLS) # 등급 숫자 정규화 if not df.empty and "장기요양등급" in df.columns: df["장기요양등급"] = df["장기요양등급"].apply(lambda x: str(x).strip() + "등급" if str(x).strip() in ["1", "2", "3", "4", "5"] else str(x).strip()) return df def load_counseling(): return load_excel_data("data/counseling_logs.xlsx", COUNSELING_COLS) def load_case_management(): return load_excel_data("data/case_management.xlsx", CASE_MANAGEMENT_COLS) def safe(val, default=""): if pd.isna(val) or val is None: return default return str(val).strip() def mask_name(name): name = safe(name) if len(name) >= 3: return name[0] + "○" + name[2:] if len(name) == 2: return name[0] + "○" return name # 🎂 생신 알림 기초 도구 def get_this_year_birthday(birth_date_str, real_birth_date_str): md = "" r_val = safe(real_birth_date_str) if r_val and re.match(r"^\d{2}-\d{2}$", r_val): md = r_val if not md: b_val = safe(birth_date_str) if re.match(r"^\d{4}-\d{2}-\d{2}$", b_val): md = b_val[5:10] if not md: return None try: m, d = map(int, md.split("-")) return date(datetime.now().year, m, d) except Exception: return None def display_birthday_alerts(df): if df.empty: return today = date.today() this_month = today.month today_birthdays = [] month_birthdays = [] for _, row in df.iterrows(): name_masked = mask_name(row.get("수급자이름")) bday = get_this_year_birthday(row.get("생년월일"), row.get("실질생일날짜")) if bday: if bday.month == today.month and bday.day == today.day: today_birthdays.append(f"**{name_masked} 어르신**" + (f" (실질생일: {row.get('실질생일날짜')})" if row.get('실질생일날짜') else "")) elif bday.month == this_month: month_birthdays.append(f"**{name_masked} 어르신** ({bday.day}일 - " + (f"실질: {row.get('실질생일날짜')}" if row.get('실질생일날짜') else f"생년월일: {row.get('생년월일')[5:10] if row.get('생년월일') else ''}") + ")") if today_birthdays or month_birthdays: st.markdown(f"### 🎂 {this_month}월 생신 알림") if today_birthdays: st.markdown(f"""
🎉 오늘 생신! 오늘은 {', '.join(today_birthdays)}의 생신입니다. 축하드립니다! 🎂
""", unsafe_allow_html=True) if month_birthdays: items = "".join([f"
  • {item}
  • " for item in month_birthdays]) st.markdown(f"""
    🎁 {this_month}월 생신 어르신 목록:
    """, unsafe_allow_html=True) # 🤖 AI 전문 소견 API 연동부 def generate_ai_opinion(system_prompt, user_prompt, client_name, reason, content): # API 키를 사이드바 혹은 환경 변수에서 가져옵니다 api_key = st.sidebar.text_input("OpenAI API Key (선택)", value=os.environ.get("OPENAI_API_KEY", ""), type="password") use_lm_studio = st.sidebar.toggle("LM Studio 사용 (Qwen)", value=False) if use_lm_studio: try: ai_client = OpenAI(base_url="http://127.0.0.1:1234/v1", api_key="lm-studio") completion = ai_client.chat.completions.create( model="qwen2.5-7b-instruct", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], temperature=0.7 ) return completion.choices[0].message.content.strip() except Exception as e: st.sidebar.error(f"LM Studio 연결 실패: {e}") if api_key.strip(): try: ai_client = OpenAI(api_key=api_key) completion = ai_client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], temperature=0.5 ) return completion.choices[0].message.content.strip() except Exception as e: st.sidebar.error(f"OpenAI API 호출 실패: {e}") # Fallback Rule-based (룰 기반 대체 텍스트) return ( f"1. {client_name} 어르신의 금번 상담 사유인 '{reason}'에 따라 수급자의 건강 및 생활 패턴 변화를 세밀하게 관찰하였습니다.\n" f"2. {content[:30]}...의 입력을 바탕으로 관찰한 결과, 일상 생활 지원 및 신체 활동 관리에 적극적인 개입이 요망됩니다.\n" f"3. 작성된 조치계획의 충실한 이행 여부를 정기 검토하고 상태 이상 징후 확인 시 즉각 보호자 및 유관 직종과 공유하겠습니다." ) # ----------------------------- # 공통 레이아웃 구성 # ----------------------------- st.markdown("""
    BeeCare Core v1.0

    🐝 BeeCare Core 통합 연동 문서시스템

    수급자등록, 상담일지, 사례관리를 하나의 앱과 데이터베이스 환경으로 완전 연동하여 실시간 공유를 보장합니다.

    """, unsafe_allow_html=True) # 🔄 사이드바 동기화 제어판 st.sidebar.subheader("🔄 실시간 동기화 제어") if st.sidebar.button("허깅페이스에서 DB 다운로드"): with st.spinner("다운로드 중..."): if db_sync.download_all(): st.sidebar.success("성공적으로 다운로드되었습니다!") st.rerun() else: st.sidebar.error("다운로드 실패!") if st.sidebar.button("허깅페이스에 DB 업로드"): with st.spinner("업로드 중..."): if db_sync.upload_all(): st.sidebar.success("성공적으로 업로드되었습니다!") else: st.sidebar.error("업로드 실패!") # ----------------------------- # 탭 정의 (1차 통합) # ----------------------------- tab1, tab2, tab3 = st.tabs(["👥 수급자 등록 · 관리", "📝 상담일지 작성", "📋 사례관리 회의록"]) # ========================================== # 탭 1: 수급자 등록 # ========================================== with tab1: st.subheader("👥 수급자 등록 및 기초 데이터 관리") df_clients = load_clients() display_birthday_alerts(df_clients) sub_menu = st.radio("수급자 관리 메뉴", ["수급자 직접 등록", "엑셀 일괄 업로드", "등록된 수급자 목록"], horizontal=True) if sub_menu == "수급자 직접 등록": with st.form("client_register_form"): st.markdown("##### 👤 기본 정보 입력") c1, c2, c3 = st.columns(3) name = c1.text_input("수급자 이름") gender = c2.selectbox("성별", VALID_GENDERS) birth_date = c3.text_input("생년월일 (YYYY-MM-DD)", placeholder="예: 1942-09-20") c4, c5, c6 = st.columns(3) real_birth_date = c4.text_input("실질생일날짜 (MM-DD)", placeholder="예: 09-20 (음력 등)") care_grade = c5.selectbox("장기요양등급", VALID_GRADES) rec_num = c6.text_input("인정번호", placeholder="숫자만 입력해도 L 자동 결합") c7, c8, c9 = st.columns(3) rec_start = c7.text_input("인정기간 시작일 (YYYY-MM-DD)", placeholder="예: 2025-03-03") rec_end = c8.text_input("인정기간 종료일 (YYYY-MM-DD)", placeholder="예: 2027-03-02") admission_date = c9.text_input("입소일/이용시작일 (YYYY-MM-DD)", placeholder="예: 2025-03-05") c10, c11, c12 = st.columns(3) inst_type = c10.selectbox("기관 유형", VALID_INST_TYPES) inst_name = c11.text_input("기관명") status = c12.selectbox("이용 상태", VALID_STATUS) st.markdown("##### 🏥 어르신 상태 및 케어 조건") cc1, cc2, cc3 = st.columns(3) main_cond = cc1.text_area("주요 질환 / 상태", height=80) cog_stat = cc2.text_area("인지 상태", height=80) mob_stat = cc3.text_area("이동 상태", height=80) cc4, cc5, cc6 = st.columns(3) diet_stat = cc4.text_area("식사 상태", height=80) toil_stat = cc5.text_area("배변/배뇨 상태", height=80) notes = cc6.text_area("특이사항 / 메모", height=80) st.markdown("##### 👥 보호자 정보") ccc1, ccc2, ccc3 = st.columns(3) guard_name = ccc1.text_input("보호자 성함") guard_phone = ccc2.text_input("보호자 연락처") remarks = ccc3.text_input("비고") submit = st.form_submit_button("수급자 정보 저장 및 동기화") if submit: if not name.strip(): st.error("수급자 이름은 필수 입력 항목입니다.") else: # 인정번호 L 포맷 정리 rec_num_clean = rec_num.replace(" ", "").upper() if rec_num_clean and not rec_num_clean.startswith("L"): rec_num_clean = "L" + re.sub(r"[^0-9]", "", rec_num_clean) new_no = len(df_clients) + 1 new_row = { "NO.": new_no, "수급자이름": name.strip(), "가명표기": mask_name(name), "성별": gender, "생년월일": birth_date.strip(), "실질생일날짜": real_birth_date.strip(), "장기요양등급": care_grade, "인정번호": rec_num_clean, "인정기간시작일": rec_start.strip(), "인정기간종료일": rec_end.strip(), "입소일": admission_date.strip(), "이용상태": status, "주요질환": main_cond.strip(), "인지상태": cog_stat.strip(), "이동상태": mob_stat.strip(), "식사상태": diet_stat.strip(), "배변/배뇨상태": toil_stat.strip(), "보호자이름": guard_name.strip(), "보호자연락처": guard_phone.strip(), "특이사항": notes.strip(), "비고": remarks.strip(), "기관유형": inst_type, "기관명": inst_name.strip() } df_clients = pd.concat([df_clients, pd.DataFrame([new_row])], ignore_index=True) save_excel_data(df_clients, "data/clients.xlsx") db_sync.upload_all() st.success(f"{name} 어르신의 정보가 등록 및 동기화 완료되었습니다!") st.rerun() elif sub_menu == "엑셀 일괄 업로드": st.info("기존에 제공된 수급자 등록 양식과 동일한 구조의 엑셀 파일을 업로드해 주세요.") uploaded_file = st.file_uploader("수급자 엑셀 파일 선택", type=["xlsx"]) overwrite = st.checkbox("중복 이름이 있을 경우 기존 데이터 덮어쓰기", value=True) if uploaded_file: try: uploaded_df = pd.read_excel(uploaded_file) # 정규화 및 바인딩 st.dataframe(uploaded_df.head(3), use_container_width=True) if st.button("데이터 업로드 실행"): # 기존 데이터에 결합 로직 구현 for _, row in uploaded_df.iterrows(): u_name = safe(row.get("수급자이름")) if not u_name: continue # 등급 정규화 grade_val = safe(row.get("장기요양등급")) if grade_val in ["1", "2", "3", "4", "5"]: grade_val += "등급" if grade_val not in VALID_GRADES: grade_val = "미입력" # 인정번호 L 정규화 rec_raw = safe(row.get("인정번호")).upper() if rec_raw and not rec_raw.startswith("L"): rec_raw = "L" + re.sub(r"[^0-9]", "", rec_raw) # 중복 여부 확인 duplicate_mask = df_clients["수급자이름"] == u_name row_data = { "NO.": len(df_clients) + 1 if not duplicate_mask.any() else df_clients.loc[duplicate_mask, "NO."].values[0], "수급자이름": u_name, "가명표기": mask_name(u_name), "성별": safe(row.get("성별")) if safe(row.get("성별")) in VALID_GENDERS else "여", "생년월일": safe(row.get("생년월일")), "실질생일날짜": safe(row.get("실질생일날짜")), "장기요양등급": grade_val, "인정번호": rec_raw, "인정기간시작일": safe(row.get("인정기간시작일")), "인정기간종료일": safe(row.get("인정기간종료일")), "입소일": safe(row.get("입소일")), "이용상태": safe(row.get("이용상태")) if safe(row.get("이용상태")) in VALID_STATUS else "이용중", "주요질환": safe(row.get("주요질환")), "인지상태": safe(row.get("인지상태")), "이동상태": safe(row.get("이동상태")), "식사상태": safe(row.get("식사상태")), "배변/배뇨상태": safe(row.get("배변/배뇨상태")), "보호자이름": safe(row.get("보호자이름")), "보호자연락처": safe(row.get("보호자연락처")), "특이사항": safe(row.get("특이사항")), "비고": safe(row.get("비고")), "기관유형": safe(row.get("기관유형")) if safe(row.get("기관유형")) in VALID_INST_TYPES else "방문요양", "기관명": safe(row.get("기관명")) } if duplicate_mask.any() and overwrite: df_clients.loc[duplicate_mask] = list(row_data.values()) else: df_clients = pd.concat([df_clients, pd.DataFrame([row_data])], ignore_index=True) # 번호 재정렬 df_clients["NO."] = range(1, len(df_clients) + 1) save_excel_data(df_clients, "data/clients.xlsx") db_sync.upload_all() st.success("엑셀 데이터 업로드 및 실시간 동기화가 완료되었습니다!") st.rerun() except Exception as e: st.error(f"엑셀 처리 중 오류 발생: {e}") elif sub_menu == "등록된 수급자 목록": if df_clients.empty: st.info("등록된 수급자 정보가 없습니다.") else: st.dataframe(df_clients, use_container_width=True, hide_index=True) # ========================================== # 탭 2: 상담일지 # ========================================== with tab2: st.subheader("📝 연동 상담일지 작성") df_clients = load_clients() df_counseling = load_counseling() if df_clients.empty: st.warning("먼저 '수급자 등록' 탭에서 수급자 기본 정보를 1명 이상 등록해 주세요.") else: st.markdown("##### 1단계: 상담 대상 어르신 선택") client_options = df_clients.apply( lambda x: f"{x['NO.']}번 - {x['가명표기']} / {x['장기요양등급']} / {x['기관유형']} / {x['이용상태']}", axis=1 ).tolist() selected_client_opt = st.selectbox("수급자 선택", client_options, key="counsel_client_select") selected_no = int(selected_client_opt.split("번")[0]) client_row = df_clients[df_clients["NO."] == selected_no].iloc[0] st.markdown("---") st.markdown("##### 2단계: 상담 상세 내용 기록") with st.form("counseling_form"): col_c1, col_c2, col_c3 = st.columns(3) c_name = col_c1.text_input("수급자 성명", value=safe(client_row["수급자이름"]), disabled=True) c_masked = col_c2.text_input("가명 표기", value=safe(client_row["가명표기"]), disabled=True) c_date = col_c3.date_input("상담일자", value=date.today()) col_c4, col_c5 = st.columns(2) c_writer = col_c4.text_input("상담자", value="사회복지사") c_type = col_c5.selectbox("상담분류", ["수급자상담", "보호자상담", "종사자상담", "기타상담"]) c_reason = st.selectbox("상담사유", [ "정기상담", "상태변화에 따른 상담", "수급자 요청", "보호자 요청", "요양보호사 건의", "응급조치 피드백" ]) c_content = st.text_area("상담내용", placeholder="상담한 핵심 내용을 육하원칙에 의거해 기록해 주세요.", height=120) c_plan = st.text_area("조치계획", placeholder="상담 이후 어르신에게 제공될 조치나 조율사항을 적어주세요.", height=80) c_needs = st.text_area("추가욕구요청사항", placeholder="추가로 전달받은 서비스 요구나 보호자의 개선 요청 등을 상세히 입력해 주세요.", height=80) st.markdown("##### ⚠️ 후속 업무 연동 지정") st.info("상담 결과에 따라 연동 처리가 필요한 업무를 체크해 주세요. 탭 3(사례관리)은 '사례관리필요'가 활성화된 어르신만 연동됩니다.") col_cb1, col_cb2, col_cb3 = st.columns(3) need_case = col_cb1.checkbox("사례관리 회의 회부 필요", value=False) need_needs = col_cb2.checkbox("욕구사정 재평가 필요", value=False) need_careplan = col_cb3.checkbox("급여제공계획 변경/재작성 필요", value=False) st.markdown("##### 🤖 AI 소견 생성") st.caption("작성된 내용을 바탕으로 AI가 전문적인 요약을 생성합니다. (임의의 증상/상태는 지어내지 않습니다)") ai_opinion = "" if st.form_submit_button("🤖 AI 총평 임시 생성"): if not c_content.strip(): st.error("상담내용을 입력해야 AI 총평을 구성할 수 있습니다.") else: sys_prompt = ( "당신은 장기요양기관 평가와 상담기록 작성에 숙련된 20년 경력의 사회복지사입니다. " "평가 기준에 부합하게 핵심이 일목요연하고 명료한 종합소견으로 정리해 주십시오." ) user_prompt = f""" [수급자 정보] 이름: {c_name} (가명: {c_masked}) 장기요양등급: {safe(client_row["장기요양등급"])} [상담 기록] 상담사유: {c_reason} 상담내용: {c_content} 조치계획: {c_plan} 추가욕구요청사항: {c_needs} [주의사항/제약] - 상담 내용과 조치 계획에 기재되지 않은 사실(예: 낙상 흔적, 욕창 발생, 침해 행동, 기저귀 젖음 등)을 AI가 독단적으로 지어내거나 추정하여 기술하지 마십시오. - 오직 입력된 정보만을 바탕으로 사회복지사 종합 소견 형식의 문장들로 1., 2., 3. 번호 형태로 정리해주십시오. """ ai_opinion = generate_ai_opinion(sys_prompt, user_prompt, c_name, c_reason, c_content) st.session_state["temp_ai_counsel"] = ai_opinion final_opinion = st.text_area("종합소견 (직접 보완 가능)", value=st.session_state.get("temp_ai_counsel", ""), height=120) save_counsel = st.form_submit_button("📝 상담일지 저장 및 허깅페이스 연동") if save_counsel: if not c_content.strip(): st.error("상담내용을 입력해 주세요.") else: new_no = len(df_counseling) + 1 new_row = { "NO.": new_no, "수급자이름": c_name, "가명표기": c_masked, "상담일자": str(c_date), "상담자": c_writer.strip(), "상담분류": c_type, "상담사유": c_reason, "상담내용": c_content.strip(), "조치계획": c_plan.strip() if c_plan.strip() else final_opinion.strip(), "추가욕구요청사항": c_needs.strip(), "사례관리필요": "True" if need_case else "False", "욕구사정필요": "True" if need_needs else "False", "급여제공계획변경필요": "True" if need_careplan else "False", "등록일자": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } df_counseling = pd.concat([df_counseling, pd.DataFrame([new_row])], ignore_index=True) save_excel_data(df_counseling, "data/counseling_logs.xlsx") db_sync.upload_all() # 세션 템플릿 비우기 if "temp_ai_counsel" in st.session_state: del st.session_state["temp_ai_counsel"] st.success("상담일지 저장이 완료되었으며 실시간 업로드 동기화가 끝났습니다!") st.rerun() st.markdown("---") st.markdown("##### 📂 등록된 상담일지 목록") if df_counseling.empty: st.info("작성된 상담일지 내역이 없습니다.") else: st.dataframe(df_counseling, use_container_width=True, hide_index=True) # ========================================== # 탭 3: 사례관리 # ========================================== with tab3: st.subheader("📋 사례관리 회의록 작성 및 연동 관리") df_counseling = load_counseling() df_case = load_case_management() # ⚠️ 연동 규칙 필터링 # 상담일지에서 사례관리필요 == True 이거나 추가욕구요청사항에 "사례관리" 단어가 포함된 것만 추출 qualified_clients = [] if not df_counseling.empty: # 문자열로 변환하여 안전하게 필터링 df_counseling["사례관리필요"] = df_counseling["사례관리필요"].astype(str) df_counseling["추가욕구요청사항"] = df_counseling["추가욕구요청사항"].fillna("") filtered_df = df_counseling[ (df_counseling["사례관리필요"] == "True") | (df_counseling["추가욕구요청사항"].str.contains("사례관리", case=False)) ] if not filtered_df.empty: qualified_clients = filtered_df["수급자이름"].unique().tolist() if not qualified_clients: st.warning("현재 상담일지에서 '사례관리 필요'로 선별되거나 '추가욕구요청사항'에 '사례관리'가 지정된 연동 대상 어르신이 없습니다.") st.info("상담일지 작성 시 후속 업무 연동에서 '사례관리 회의 회부 필요'를 체크하시면 이곳에 자동으로 등록됩니다.") else: st.markdown("##### 🔗 상담 연동 사례관리 대상자 선택") selected_case_name = st.selectbox("사례관리 대상 어르신 선택", qualified_clients, key="case_client_select") # 해당 어르신의 최근 연동 상담 내역 요약 노출 ref_counsel_logs = df_counseling[df_counseling["수급자이름"] == selected_case_name].sort_values(by="상담일자", ascending=False) latest_counsel = ref_counsel_logs.iloc[0] st.markdown(f"""
    📝 연동된 최근 상담 정보 ({latest_counsel['상담일자']}):
    • 상담사유: {latest_counsel['상담사유']}
    • 상담내용: {latest_counsel['상담내용']}
    • 추가욕구/요청: {latest_counsel['추가욕구요청사항']}
    """, unsafe_allow_html=True) with st.form("case_management_form"): col_m1, col_m2 = st.columns(2) m_name = col_m1.text_input("수급자 성명", value=selected_case_name, disabled=True) m_masked = col_m2.text_input("가명 표기", value=safe(latest_counsel["가명표기"]), disabled=True) col_m3, col_m4, col_m5 = st.columns(3) m_date = col_m3.date_input("회의 일자", value=date.today()) m_participants = col_m4.text_input("참석자", value="시설장, 사회복지사, 간호조무사, 요양보호사") m_type = col_m5.selectbox("회의 구분", ["정기 사례회의", "응급 사례회의", "수시 사례회의"]) m_reason = st.text_area("사례 선정 이유", value=f"상담기록 연동에 의거, 상담 사유 '{latest_counsel['상담사유']}' 및 추가 요청 '{latest_counsel['추가욕구요청사항']}' 해결을 위한 조율이 시급하여 사례회의 대상자로 선정함.", height=70) st.markdown("##### 📝 회의 핵심 기록") m_first_plan = st.text_area("해결 방안 및 1차 계획", placeholder="회의를 통해 결정된 구체적인 실행 계획을 작성해 주세요.", height=90) m_process = st.text_area("시행 과정 기록", placeholder="해결 방안 적용 후 실행 과정상의 피드백이나 특이사항을 입력해 주세요.", height=90) m_result = st.text_area("종합 결과 및 2차 평가", placeholder="회의 결과 평가 및 향후 반영할 급여 내용을 작성해 주세요.", height=90) st.markdown("##### 🤖 AI 전문 소견 생성") st.caption("입력된 구체적인 내용에만 한정하여 AI가 사례회의 종합 의견을 생성합니다.") ai_case_opinion = "" if st.form_submit_button("🤖 AI 종합 의견 임시 생성"): if not m_first_plan.strip(): st.error("1차 계획 및 해결방안을 입력해야 종합 소견 작성이 가능합니다.") else: sys_prompt = ( "당신은 장기요양기관 평가에 정통한 노인복지 전문가이자 사회복지사입니다. " "사례관리 회의록의 결과를 평가 기준에 부합하게 명료한 문장들로 정리해 주십시오." ) user_prompt = f""" [수급자] 이름: {m_name} (가명: {m_masked}) [사례관리 기록] 선정이유: {m_reason} 해결방안 및 1차계획: {m_first_plan} 시행과정: {m_process} 종합결과(2차평가): {m_result} [제약조건] - 절대로 입력된 내용 이외에 다른 병증 상태나 응급 낙상 사건 등을 거짓으로 지어내지 마십시오. - 오직 입력된 문맥에 의거하여 종합적인 사례회의 소견을 1., 2., 3. 번호 형태로 정리해주십시오. """ ai_case_opinion = generate_ai_opinion(sys_prompt, user_prompt, m_name, "사례회의", m_first_plan) st.session_state["temp_ai_case"] = ai_case_opinion final_case_opinion = st.text_area("사례회의 종합의견 보완", value=st.session_state.get("temp_ai_case", ""), height=100) save_case = st.form_submit_button("📋 사례관리 회의록 저장 및 허깅페이스 연동") if save_case: if not m_first_plan.strip(): st.error("해결방안(1차계획)을 입력해 주세요.") else: new_no = len(df_case) + 1 new_row = { "NO.": new_no, "수급자이름": m_name, "가명표기": m_masked, "회의일자": str(m_date), "참석자": m_participants.strip(), "회의분류": m_type, "사례선정이유": m_reason.strip(), "해결방안(1차계획)": m_first_plan.strip(), "시행과정": m_process.strip(), "종합결과(2차평가)": m_result.strip() if m_result.strip() else final_case_opinion.strip(), "등록일자": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } df_case = pd.concat([df_case, pd.DataFrame([new_row])], ignore_index=True) save_excel_data(df_case, "data/case_management.xlsx") db_sync.upload_all() if "temp_ai_case" in st.session_state: del st.session_state["temp_ai_case"] st.success("사례관리 회의록 저장이 완료되었으며 실시간 업로드 동기화가 끝났습니다!") st.rerun() st.markdown("---") st.markdown("##### 📂 등록된 사례관리 회의록 목록") if df_case.empty: st.info("작성된 사례관리 회의록 내역이 없습니다.") else: st.dataframe(df_case, use_container_width=True, hide_index=True)