import random
import os
import sqlite3
from io import BytesIO
from datetime import date, datetime
import pandas as pd
import streamlit as st
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.pagebreak import Break
from openai import OpenAI
st.set_page_config(page_title="μλ΄μΌμ§", page_icon="π", layout="wide")
# -----------------------------
# DB κ²½λ‘ μ€μ
# -----------------------------
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
DB_CANDIDATES = [
"/app/data/clients.db",
os.path.join(CURRENT_DIR, "..", "μκΈμλ±λ‘", "data", "clients.db"),
os.path.join(CURRENT_DIR, "..", "Client Center", "data", "clients.db"),
os.path.join(CURRENT_DIR, "..", "ClientCenter", "data", "clients.db"),
os.path.join(CURRENT_DIR, "..", "Client_Center", "data", "clients.db"),
os.path.join(CURRENT_DIR, "data", "clients.db"),
]
DB_PATH = None
for path in DB_CANDIDATES:
if os.path.exists(path):
DB_PATH = path
break
if DB_PATH is None:
os.makedirs(os.path.join(CURRENT_DIR, "data"), exist_ok=True)
DB_PATH = os.path.join(CURRENT_DIR, "data", "clients.db")
# -----------------------------
# DB μ΄κΈ°ν ν¨μ
# -----------------------------
def init_db():
try:
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
# clients ν
μ΄λΈ (κΈ°μ‘΄ ν
μ΄λΈ μ‘΄μ¬ μ 무μ)
cur.execute("""
CREATE TABLE IF NOT EXISTS clients (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
masked_name TEXT,
gender TEXT,
birth_year TEXT,
care_grade TEXT,
institution_type TEXT,
institution_name TEXT,
recognition_number TEXT,
recognition_start_date TEXT,
recognition_end_date TEXT,
status TEXT
)
""")
# counseling_records ν
μ΄λΈ
cur.execute("""
CREATE TABLE IF NOT EXISTS counseling_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_id INTEGER,
client_masked_name TEXT,
institution_type TEXT,
counseling_type TEXT,
counseling_date TEXT,
counselor TEXT,
counseling_reason TEXT,
client_status_change TEXT,
guardian_opinion TEXT,
counseling_content TEXT,
action_plan TEXT,
evaluation_score INTEGER,
missing_items TEXT,
compliance_result TEXT,
next_due_note TEXT,
created_at TEXT
)
""")
# λμ μ»¬λΌ μΆκ° (ALTER TABLE)
cur.execute("PRAGMA table_info(counseling_records)")
existing_cols = [r[1] for r in cur.fetchall()]
new_cols = {
"counseling_purpose": "TEXT",
"selected_items": "TEXT",
"keywords": "TEXT",
"additional_needs": "TEXT",
"case_needed": "INTEGER DEFAULT 0",
"needs_assessment_needed": "INTEGER DEFAULT 0",
"careplan_change_needed": "INTEGER DEFAULT 0",
"care_grade_review_needed": "INTEGER DEFAULT 0",
"followup_counseling_needed": "INTEGER DEFAULT 0",
}
for col, col_type in new_cols.items():
if col not in existing_cols:
cur.execute(f"ALTER TABLE counseling_records ADD COLUMN {col} {col_type}")
conn.commit()
conn.close()
except Exception as e:
st.sidebar.error(f"DB μ΄κΈ°ν μ€ν¨: {e}")
init_db()
# -----------------------------
# μκΈμ μ 보 λ‘λ ν¨μ
# -----------------------------
def load_clients() -> pd.DataFrame:
# 1. DBμμ λ‘λ μλ
if DB_PATH and os.path.exists(DB_PATH):
try:
conn = sqlite3.connect(DB_PATH)
available_cols = pd.read_sql_query("PRAGMA table_info(clients)", conn)["name"].tolist()
wanted_cols = [
"id", "name", "masked_name", "gender", "birth_year", "care_grade",
"recognition_number", "recognition_start_date", "recognition_end_date",
"institution_type", "institution_name", "admission_date", "status"
]
select_cols = [c for c in wanted_cols if c in available_cols]
query = "SELECT " + ", ".join(select_cols) + " FROM clients ORDER BY id DESC"
df = pd.read_sql_query(query, conn)
conn.close()
# DB μ»¬λΌ νκΈ λ°μΈλ©
db_to_excel_map = {
"name": "μκΈμμ΄λ¦",
"gender": "μ±λ³",
"birth_year": "μλ
μμΌ",
"care_grade": "μ₯κΈ°μμλ±κΈ",
"recognition_number": "μΈμ λ²νΈ",
"recognition_start_date": "μΈμ κΈ°κ° μμμΌ",
"recognition_end_date": "μΈμ κΈ°κ° μ’
λ£μΌ",
"institution_type": "κΈ°κ΄μ ν",
"institution_name": "κΈ°κ΄λͺ
"
}
for db_col, kor_col in db_to_excel_map.items():
if db_col in df.columns:
df[kor_col] = df[db_col]
if "μκΈμμ΄λ¦" not in df.columns and "name" in df.columns:
df["μκΈμμ΄λ¦"] = df["name"]
return df
except Exception as e:
st.sidebar.warning(f"DB λ‘λ μ€ν¨: {e}. Excel μλ μ€...")
# 2. Excelμμ λ‘λ μλ
excel_path = os.path.join(CURRENT_DIR, "data", "clients.xlsx")
if os.path.exists(excel_path):
try:
df = pd.read_excel(excel_path)
if "μκΈμλͺ
" in df.columns and "μκΈμμ΄λ¦" not in df.columns:
df["μκΈμμ΄λ¦"] = df["μκΈμλͺ
"]
expected = ["μκΈμμ΄λ¦", "μ±λ³", "μλ
μμΌ", "μ₯κΈ°μμλ±κΈ", "μΈμ λ²νΈ", "μΈμ κΈ°κ° μμμΌ", "μΈμ κΈ°κ° μ’
λ£μΌ", "κΈ°κ΄μ ν", "κΈ°κ΄λͺ
"]
for col in expected:
if col not in df.columns:
df[col] = ""
return df
except Exception as e:
st.sidebar.error(f"Excel λ‘λ μ€ν¨: {e}")
return pd.DataFrame()
# -----------------------------
# AI ν둬ννΈ κ·μΉ μ μ
# -----------------------------
COUNSELING_GENERATION_RULE = """
λλ μ₯κΈ°μμκΈ°κ΄ μλ΄μΌμ§ μμ± λ³΄μ‘° AIμ΄λ€.
λ°λμ μ¬μ©μκ° μ
λ ₯ν λ΄μ©λ§ μ¬μ©νλ€.
μ νλ νλͺ©, ν΅μ¬λ¨μ΄, μΆκ°μꡬ/μμ²μ¬νμ μλ λ΄μ©μ μμ±νμ§ μλλ€.
μμ°½, λ°°ν, 곡격μ±, κ·κ°μꡬ, λμ, μΉλ§€νλ λ±μ μ
λ ₯λ κ²½μ°μλ§ μμ±νλ€.
AIλ μλ‘μ΄ μ¬μ€μ λ§λ€μ§ μκ³ λ¬Έμ₯μ μμ°μ€λ½κ² μ λ¦¬λ§ νλ€.
μλ΄ λͺ©μ μ΄ μ¬λ‘κ΄λ¦¬ κ²ν μλ΄μ΄λ©΄ μ¬λ‘κ΄λ¦¬ νμ μ¬μ μ€μ¬μΌλ‘ μμ±νλ€.
μλ΄ λͺ©μ μ΄ μꡬμ¬μ μ¬κ²ν μλ΄μ΄λ©΄ μꡬμ¬μ μ¬κ²ν μ¬μ μ€μ¬μΌλ‘ μμ±νλ€.
μλ΄ λͺ©μ μ΄ κΈμ¬μ 곡κ³ν λ³κ²½ μλ΄μ΄λ©΄ κΈμ¬μ 곡κ³ν λ³κ²½ μ¬μ μ€μ¬μΌλ‘ μμ±νλ€.
λ³΅ν© μλ΄μ΄λ©΄ 체ν¬λ νλͺ©λ³λ‘ ꡬλΆνμ¬ μμ±νλ€.
μμ± νμ:
1. μλ΄ μ¬μ
2. μλ΄ λ΄μ©
3. μ λ¬Έμ μ¬μ
4. μ‘°μΉκ³ν
5. μ°λ νμ νλͺ©
"""
# -----------------------------
# μλ΄μΌμ§ λ¬Έμ₯ νμ± ν¨μ
# -----------------------------
def parse_sections(text):
sections = {
"μλ΄ μ¬μ ": "",
"μλ΄ λ΄μ©": "",
"μ λ¬Έμ μ¬μ ": "",
"μ‘°μΉκ³ν": "",
"μ°λ νμ νλͺ©": ""
}
import re
headers = [
("μλ΄ μ¬μ ", ["1. μλ΄ μ¬μ ", "μλ΄ μ¬μ :", "μλ΄ μ¬μ "]),
("μλ΄ λ΄μ©", ["2. μλ΄ λ΄μ©", "μλ΄ λ΄μ©:", "μλ΄ λ΄μ©"]),
("μ λ¬Έμ μ¬μ ", ["3. μ λ¬Έμ μ¬μ ", "μ λ¬Έμ μ¬μ :", "μ λ¬Έμ μ¬μ "]),
("μ‘°μΉκ³ν", ["4. μ‘°μΉκ³ν", "μ‘°μΉκ³ν:", "μ‘°μΉκ³ν"]),
("μ°λ νμ νλͺ©", ["5. μ°λ νμ νλͺ©", "μ°λ νμ νλͺ©:", "μ°λ νμ νλͺ©"])
]
current_text = text
for i in range(len(headers)):
header_name, header_aliases = headers[i]
next_header_aliases = headers[i+1][1] if i+1 < len(headers) else []
start_idx = -1
matched_alias = ""
for alias in header_aliases:
idx = current_text.find(alias)
if idx != -1:
start_idx = idx
matched_alias = alias
break
if start_idx != -1:
end_idx = len(current_text)
for next_alias in next_header_aliases:
n_idx = current_text.find(next_alias, start_idx + len(matched_alias))
if n_idx != -1:
end_idx = n_idx
break
section_content = current_text[start_idx + len(matched_alias):end_idx].strip()
section_content = re.sub(r'^[\s\:\-\=]*', '', section_content).strip()
sections[header_name] = section_content
# νμ± μ€ν¨ μ λ체 λΆν
if not sections["μλ΄ μ¬μ "] and not sections["μλ΄ λ΄μ©"]:
lines = text.split("\n\n")
if len(lines) >= 5:
sections["μλ΄ μ¬μ "] = lines[0].strip()
sections["μλ΄ λ΄μ©"] = lines[1].strip()
sections["μ λ¬Έμ μ¬μ "] = lines[2].strip()
sections["μ‘°μΉκ³ν"] = lines[3].strip()
sections["μ°λ νμ νλͺ©"] = lines[4].strip()
return sections
# -----------------------------
# κ·μΉ κΈ°λ° λ¬Έμ₯ μ»΄νμΌλ¬ (AI μ€νλΌμΈ λλΉ)
# -----------------------------
def generate_rule_based_counseling_text(data):
name = data["masked_name"]
grade = data["care_grade"]
inst_type = data["institution_type"]
reason = data["counseling_reason"]
purpose = data["counseling_purpose"]
selected_items_str = ", ".join(data.get("selected_items_list", []))
keywords = data.get("keywords", "").strip()
additional_needs = data.get("additional_needs", "").strip()
# μ
λ ₯ μ 보 κΈ°λ° μν ν€μλ νμ§
combined_inputs = keywords + " " + additional_needs
has_fall = any(k in combined_inputs for k in ["λμ", "λμ΄", "λ―Έλ", "μ΄μ§", "보ν", "λΆμΆ", "μ§ν‘", "μ컀", "ν 체μ΄"])
has_bedsore = any(k in combined_inputs for k in ["μμ°½", "λ°μ ", "μμ²", "νΌλΆ", "μ§λ¬΄", "체μ", "μΉ¨μ"])
has_dementia = any(k in combined_inputs for k in ["μΉλ§€", "μΈμ§", "κΈ°μ΅", "λ°°ν", "λ§μ", "λΆμ", "κ·κ°", "μ§μ", "νΌλ"])
has_guardian = any(k in combined_inputs for k in ["νλ€", "μμ§", "λΆλ΄", "μμ²", "κ°μ‘±", "보νΈμ"])
# 1. μλ΄ μ¬μ
reason_lines = []
reason_lines.append(f"{name} μκΈμλ μ₯κΈ°μμ {grade} λ±κΈμΌλ‘μ {inst_type} μλΉμ€ μ΄μ© μ€, [{reason}] μ¬μ λ‘ μλ΄μ μ€μνμμ΅λλ€.")
if purpose != "μΌλ° μλ΄":
reason_lines.append(f"μ΄λ² μλ΄μ μκΈμμ μν© λ³νμ λ°λ₯Έ [{purpose}] μ§νμ λͺ©μ μΌλ‘ ν©λλ€.")
counseling_reason_text = " ".join(reason_lines)
# 2. μλ΄ λ΄μ©
content_lines = []
if selected_items_str:
content_lines.append(f"μλ΄ λͺ©μ μ λ°λΌ μ νν κ²ν νλͺ©({selected_items_str})μ μ€μ¬μΌλ‘ νμ¬ μνμ μ£Όμ μ λ‘μ¬νμ μ²μ·¨νκ³ νμΈνμμ΅λλ€.")
if keywords:
content_lines.append(f"μκΈμ λ° κ΄λ ¨μΈμ ν΅μ¬ μμ² μ¬νμΌλ‘ '{keywords}' κ΄λ ¨ λ΄μ©μ΄ μ§μ μ μΌλ‘ μ μλμμΌλ©° μ΄μ λν΄ λ©΄λ°ν μ΄μΌκΈ°λ₯Ό λλμμ΅λλ€.")
if additional_needs:
content_lines.append(f"μΆκ°μ μΌλ‘ μ μλ μꡬ λ° κ΅¬μ²΄μ μν©μΌλ‘ '{additional_needs}'κ° νμ
λμμ΅λλ€.")
risk_mentions = []
if has_fall:
risk_mentions.append("보ν λΆμμ μ± λ° λμ μν μλ°© νμμ±")
if has_bedsore:
risk_mentions.append("μμ μν λ° μ₯μκ° μΉ¨μ μνλ‘ μΈν νΌλΆ λ°μ /μμ°½ μλ°© κ΄λ¦¬")
if has_dementia:
risk_mentions.append("μΈμ§ κΈ°λ₯ λ³νμ λ°λ₯Έ νλ λ³νμ μ μ μμ μ μλ νμμ±")
if has_guardian:
risk_mentions.append("μ£Όμ λλ΄ λ³΄νΈμμ λΆμ λΆλ΄ λ° μλΉμ€ μ‘°μ μ견")
if risk_mentions:
content_lines.append("μλ΄ μ€ μ
λ ₯λ ꡬ체μ μν λ° κ΄μ°° μ¬νμΌλ‘ " + ", ".join(risk_mentions) + "μ κ΄ν μΈλΆ μ ν©μ νμΈνμμ΅λλ€.")
if not content_lines:
content_lines.append("μκΈμ λ° λ³΄νΈμμ μΌμμ μΈ μν μνμ μλΉμ€ λ§μ‘±λλ₯Ό νμΈνκ³ κ²©λ €μ λ§μμ μ νμμ΅λλ€.")
counseling_content_text = " ".join(content_lines)
# 3. μ λ¬Έμ μ¬μ
assessment_lines = []
if selected_items_str:
assessment_lines.append(f"μ νλ κ²ν νλͺ©({selected_items_str})κ³Ό κ΄λ ¨νμ¬ κΈ°μ‘΄ κΈμ¬μ 곡κ³νμ μ μ μ± μ¬κ²ν λ° μ€μ§μ μΈ λλ΄ λ°©λ² μ‘°μ μ΄ μꡬλλ μνλ‘ νλ¨λ©λλ€.")
if keywords:
assessment_lines.append(f"μ
λ ₯λ ν€μλ '{keywords}'λ₯Ό λ°νμΌλ‘ μ¬μ ν κ²°κ³Ό, κΈ°λ₯ μ νλ μ¬λ¦¬μ λΆνΈκ°μ μννκΈ° μν μμ보νΈμ¬μ μ κ·Ήμ μΈ μ§μκ³Ό κ΄μ°°μ΄ μ€μν©λλ€.")
if additional_needs:
assessment_lines.append(f"μΆκ° μμ²μΈ '{additional_needs}'μ κ·Όκ±°ν λ, κ°μ λ΄ νκ²½ κ°μ μ΄λ μ κ΄ μλΉμ€ λ³κ²½ κ²ν κ° μ¦κ° λ°μλ νμκ° μλ μ¬μ κ²°κ³Όλ₯Ό μ»μμ΅λλ€.")
risk_assess = []
if has_fall:
risk_assess.append("λμ λ°©μ§λ₯Ό μν μ΄λ μ μ μ μΈ λΆμΆ λ° νμ₯μ€ λν μμ ν보")
if has_bedsore:
risk_assess.append("체μ λ³κ²½ λΉλ μ€μ λ° κΌ¬λ¦¬λΌ λΆμ λ± λ°μ μ§μ€ κ΄μ°°")
if has_dementia:
risk_assess.append("λ°°ν λ° μΉλ§€ μΈμ§ νΌλ μλ°©μ μν μΌκ΄μ νκ²½ μ‘°μ± λ° κ³΅κ°μ μμ¬μν΅ μ μ©")
if risk_assess:
assessment_lines.append("μ λ¬Έ μ¬μ μ " + ", ".join(risk_assess) + " κ΄λ ¨ κ°λ³ μΌμ΄ μ λ΅ μλ¦½μ΄ ν΅μ¬μ μμ΄ λμΆλμμ΅λλ€.")
if not assessment_lines:
assessment_lines.append("μ λ°μ μΈ μκΈμμ κΈ°λ₯ μνλ μμ μ μΌλ‘ μ μ§λκ³ μμΌλ©°, κΈκ²©ν μν μ ν μ§νλ μν μμΈμ μ¬μ λμ§ μμ νν κ΄λ¦¬λ₯Ό μ μ§ν©λλ€.")
professional_assessment_text = " ".join(assessment_lines)
# 4. μ‘°μΉκ³ν
plan_lines = []
plan_lines.append("μλ΄ κ³Όμ μμ μμ§λ λ΄μ©κ³Ό μ¬μ μ견μ μλΉμ€ μ 곡 μΈλ ₯(μμ보νΈμ¬ λ±)κ³Ό 곡μ νκ³ , νμν μ‘°μΉλ₯Ό μ΄ννκΈ°λ‘ νμμ΅λλ€.")
interlock_actions = []
if data["case_needed"]:
interlock_actions.append("μ¬λ‘κ΄λ¦¬ νμ μ건μΌλ‘ μμ νμ¬ λ€κ°μ μ§μ λ°©μ(보쑰기ꡬ κ΅μ²΄, μΈλΆμμ λ±)μ λ
Όμν¨")
if data["needs_assessment_needed"]:
interlock_actions.append("μꡬμ¬μ μ¬νκ°λ₯Ό μ μν μννμ¬ μκΈμ ADL λ° νκ²½ λ³νλ₯Ό 곡μμ μΌλ‘ κΈ°λ‘ν¨")
if data["careplan_change_needed"]:
interlock_actions.append("κΈμ¬μ 곡κ³νμ λͺ©νλ₯Ό μμ νκ³ μ 곡λλ μλΉμ€ μκ°/λ΄μ© λ³κ²½ κ³μ½μ μ€λΉν¨")
if data["care_grade_review_needed"]:
interlock_actions.append("κ΅λ―Όκ±΄κ°λ³΄νκ³΅λ¨ λ±κΈ μ μ² μ μ°¨ λ° μλ₯ κ΅¬λΉ μλ Ήμ λν΄ λ³΄νΈμμκ² μλ΄ν¨")
if data["followup_counseling_needed"]:
interlock_actions.append("μΆν 보νΈμμμ μΆκ° λλ©΄ λ° μ ν μλ΄ μΌμ μ μ‘°μ¨νμ¬ μ‘°μΉ κ²°κ³Όμ λ°μμ νμΈν¨")
if interlock_actions:
plan_lines.append("μΈλΆ μ‘°μΉ κ³νμΌλ‘λ: " + ", ".join(interlock_actions) + "λ₯Ό μνν μμ μ
λλ€.")
else:
plan_lines.append("μκΈμμ μꡬμ κΈ°λ°νμ¬ ν μλΉμ€ μνλ₯Ό λ©΄λ°ν λͺ¨λν°λ§νκ³ μ κΈ°μ μΈ μ μ νμΈμ μ§μνκΈ°λ‘ νμμ΅λλ€.")
action_plan_text = " ".join(plan_lines)
# 5. μ°λ νμ νλͺ©
interlock_names = []
if data["case_needed"]: interlock_names.append("μ¬λ‘κ΄λ¦¬ νμ")
if data["needs_assessment_needed"]: interlock_names.append("μꡬμ¬μ μ¬κ²ν νμ")
if data["careplan_change_needed"]: interlock_names.append("κΈμ¬μ 곡κ³ν λ³κ²½ νμ")
if data["care_grade_review_needed"]: interlock_names.append("λ±κΈλ³κ²½ μ μ² κ²ν νμ")
if data["followup_counseling_needed"]: interlock_names.append("보νΈμ μΆκ° μλ΄ νμ")
interlock_text = ", ".join(interlock_names) if interlock_names else "ν΄λΉ μ¬ν μμ"
full_text = f"""
1. μλ΄ μ¬μ
{counseling_reason_text}
2. μλ΄ λ΄μ©
{counseling_content_text}
3. μ λ¬Έμ μ¬μ
{professional_assessment_text}
4. μ‘°μΉκ³ν
{action_plan_text}
5. μ°λ νμ νλͺ©
{interlock_text}
""".strip()
return full_text
# -----------------------------
# DB μ μ₯ ν¨μ
# -----------------------------
def save_counseling_record(data) -> bool:
try:
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
insert_query = """
INSERT INTO counseling_records (
client_id, client_masked_name, institution_type, counseling_type,
counseling_date, counselor, counseling_reason, client_status_change,
guardian_opinion, counseling_content, action_plan, evaluation_score,
missing_items, compliance_result, next_due_note, created_at,
counseling_purpose, selected_items, keywords, additional_needs,
case_needed, needs_assessment_needed, careplan_change_needed,
care_grade_review_needed, followup_counseling_needed
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
cur.execute(
insert_query,
(
data.get("client_id", 0),
data["masked_name"],
data["institution_type"],
data["counseling_type"],
data["counseling_date"],
data["counselor"],
data["counseling_reason"],
data.get("client_status_change", ""),
data.get("guardian_opinion", ""),
data["counseling_content"],
data["action_plan"],
data["evaluation_score"],
data["missing_items"],
data["compliance_result"],
data.get("next_due_note", ""),
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
data["counseling_purpose"],
", ".join(data.get("selected_items_list", [])),
data["keywords"],
data["additional_needs"],
1 if data["case_needed"] else 0,
1 if data["needs_assessment_needed"] else 0,
1 if data["careplan_change_needed"] else 0,
1 if data["care_grade_review_needed"] else 0,
1 if data["followup_counseling_needed"] else 0,
)
)
conn.commit()
conn.close()
return True
except Exception as e:
st.error(f"DB μ μ₯ μ€ μ€λ₯ λ°μ: {e}")
return False
st.markdown("""
""", unsafe_allow_html=True)
st.markdown("""
π μλ΄μΌμ§ μμ±κΈ°
λΉ λ₯Έ μ§μ μ
λ ₯ λ²μ μ
λλ€. μλ΄ κ³Όμ κ³Ό νμΈ μ μ°¨κ° ν¬ν¨λλλ‘ λ¬Έμλ₯Ό μμ±ν©λλ€.
""", unsafe_allow_html=True)
st.markdown("""
μΆμ ν
μ€νΈμ©: μλμ λ¬Έμ νμ§μ λ¨Όμ νμΈνκΈ° μν΄ μκΈμ DB μλμ‘°ν κΈ°λ₯μ μ μΈνμ΅λλ€. μ§μ μ
λ ₯ ν μλ΄ κ³Όμ Β·νμΈ λ΄μ©Β·μ‘°μΉκ³νμ΄ ν¬ν¨λ μλ΄μΌμ§λ₯Ό μμ±ν©λλ€.
""", unsafe_allow_html=True)
st.markdown("""
β» μμ±λ λ¬Έμλ κ·Έλλ‘ μ¬μ©νμ
λ λλ©°, κΈ°κ΄μ μ΄μλ°©μΉ¨, μκΈμ μν, 보νΈμ μ견 λ° νκ°κΈ°μ€μ λ°λΌ μΆκ° 보μμ¬νμ μΌλ§λ μ§ μμ κ°λ₯ν©λλ€. κΈ°κ΄μ μν©μ λ§κ² νμ©νμκΈ° λ°λλλ€.
""", unsafe_allow_html=True)
INSTITUTION_GUIDES = {
"λ°©λ¬Έμμ": ["μλΉμ€ μκ°", "μμ보νΈμ¬ λ³κ²½", "κ°μ¬μ§μ λ²μ", "μΈμ§νλ", "볡μ½νμΈ", "λμμλ°©"],
"λ°©λ¬Έλͺ©μ": ["λͺ©μ μ ν 건κ°μν", "νΌλΆμν", "λμμλ°©", "μ΄λ 보쑰", "λͺ©μ κ±°λΆκ°", "보νΈμ νμ‘°"],
"λ°©λ¬Έκ°νΈ": ["νμ/νλΉ κ΄μ°°", "μμ²κ΄λ¦¬", "ν¬μ½κ΄λ¦¬", "건κ°μλ΄", "μλ£κΈ°κ΄ μ°κ³", "κ°νΈμ²μΉ"],
"μ£Όκ°λ³΄νΈ": ["μ‘μ", "νλ‘κ·Έλ¨ μ°Έμ¬", "μμ¬", "λ°°λ³", "λμμλ°©", "μ μμ§μ", "κ°μ‘±μν΅"],
"μμμ": ["μ
μμν μ μ", "μμ¬/μλ©΄/λ°°λ³", "μμ°½μλ°©", "λμμλ°©", "νλ‘κ·Έλ¨ μ°Έμ¬", "κ°μ‘±λ©΄ν"],
"곡λμνκ°μ ": ["곡λμν μ μ", "μ μμ§μ", "κ°λ³ μꡬ", "κ°μ‘±μν΅", "νλ‘κ·Έλ¨ μ°Έμ¬", "μμ κ΄λ¦¬"],
}
TYPE_GUIDES = {
"μκΈμμλ΄": {
"focus": "μκΈμμ 건κ°μν, μ μμν, μΌμμν λΆνΈκ°, μλΉμ€ λ§μ‘±λ, μνλ³νλ₯Ό μ€μ¬μΌλ‘ μλ΄",
"action": "μκΈμ μꡬλ₯Ό κΈμ¬μ 곡κ³νκ³Ό μΌμΌ μλΉμ€ μ 곡 λ΄μ©μ λ°μ",
},
"보νΈμμλ΄": {
"focus": "보νΈμμ μ견, κ°μ‘± μμ²μ¬ν, μνλ³ν μ λ¬, μλΉμ€ μ‘°μ νμμ±μ μ€μ¬μΌλ‘ μλ΄",
"action": "보νΈμ μ견μ κΈ°λ‘νκ³ νμν κ²½μ° κΈμ¬μ 곡κ³ν λ³κ²½ λλ μ§μ 곡μ ",
},
"μ’
μ¬μμλ΄": {
"focus": "μλΉμ€ μ 곡 μ€ κ΄μ°°μ¬ν, μ
무 κ³ μΆ©, μκΈμ μν λ³ν, μμ κ΄λ¦¬ νμμ±μ μ€μ¬μΌλ‘ μλ΄",
"action": "μ’
μ¬μ κ΄μ°° λ΄μ©μ λ°μνμ¬ μλΉμ€ λ°©λ²μ μ‘°μ νκ³ νμ μ κ΅μ‘Β·νμ μ건μΌλ‘ μ°κ²°",
},
"κΈ°νμλ΄": {
"focus": "κΈ°κ΄ μ΄μμ νμν μλ΄, μ§μμμ μ°κ³, λ―Όμ μλ°©, νΉμ΄μ¬ν νμΈμ μ€μ¬μΌλ‘ μλ΄",
"action": "μλ΄ λ΄μ©μ κΈ°κ΄ κΈ°λ‘μΌλ‘ λ¨κΈ°κ³ νμμ‘°μΉ λ΄λΉμμ κΈ°νμ μ€μ ",
},
}
QA_RULES = {
"μκΈμμλ΄": [
"νμ¬ κ°μ₯ λΆνΈνκ±°λ νλ μ μ 무μμ
λκΉ?",
"μ΅κ·Ό 건κ°μν, ν΅μ¦, μ΄μ§λΌ, μμ¬λ, μλ©΄, λ°°λ³Β·λ°°λ¨μ λ³νκ° μμμ΅λκΉ?",
"μλΉμ€ μ 곡 κ³Όμ μμ λ§μ‘±νλ μ κ³Ό λΆνΈν μ μ 무μμ
λκΉ?",
"μμ보νΈμ¬ λλ κΈ°κ΄μ μΆκ°λ‘ μμ²νκ³ μΆμ λμμ΄ μμ΅λκΉ?",
"λμ, μμ°½, μΈμ§νΌλ, μ μλΆμ λ± μμ μ κ±±μ λλ μΌμ΄ μμμ΅λκΉ?",
],
"보νΈμμλ΄": [
"보νΈμκ° λ³΄κΈ°μ μκΈμμ μ΅κ·Ό 건κ°μνλ μνμν λ³νλ 무μμ
λκΉ?",
"κ°μ‘±μ΄ κ±±μ νλ μνμμΈμ΄λ λ³μμ§λ£ νμμ±μ΄ μμ΅λκΉ?",
"νμ¬ μλΉμ€ μκ°, μ 곡λ΄μ©, μ’
μ¬μ λ°°μΉμ μ‘°μ μ΄ νμν©λκΉ?",
"보νΈμκ° κΈ°κ΄μ μμ²νκ±°λ 곡μ νκ³ μΆμ μ¬νμ 무μμ
λκΉ?",
"κΈμ¬μ 곡κ³νμ λ°μν΄μΌ ν κ°μ‘± μκ²¬μ΄ μμ΅λκΉ?",
],
"μ’
μ¬μμλ΄": [
"μλΉμ€ μ 곡 μ€ κ΄μ°°ν μκΈμμ μ£Όμ λ³νλ 무μμ
λκΉ?",
"λμ, μμ°½, μμ¬λ κ°μ, μΈμ§νΌλ, νμΈΒ·κ±°λΆ λ± μν μ§νκ° μμμ΅λκΉ?",
"νμ¬ μ 곡 λ°©λ² μ€ μ‘°μ μ΄ νμν λΆλΆμ 무μμ
λκΉ?",
"μ
무 μν μ€ μ΄λ €μμ΄λ μΆκ° κ΅μ‘Β·νμκ° νμν λ΄μ©μ 무μμ
λκΉ?",
"보νΈμ λλ λ΄λΉμμκ² μ¦μ 곡μ ν΄μΌ ν μ¬νμ΄ μμ΅λκΉ?",
],
"κΈ°νμλ΄": [
"μλ΄μ΄ νμν μ£Όμ μν©μ 무μμ
λκΉ?",
"μκΈμ, 보νΈμ, μ’
μ¬μ μ€ λꡬμ κ΄λ ¨λ λ΄μ©μ
λκΉ?",
"κΈ°κ΄ μ°¨μμ μ‘°μΉλ μΈλΆ μ°κ³κ° νμν λΆλΆμ 무μμ
λκΉ?",
"νμ νμΈμ΄ νμν μμ κ³Ό λ΄λΉμλ λꡬμ
λκΉ?",
],
}
STATUS_CHANGE_KEYWORDS = ["λμ", "μμ°½", "λ°μ ", "μμ²", "μ΄μ§", "ν΅μ¦", "μμ¬λ", "μ¬λ ", "μ°ν", "μλ©΄", "λ°°λ³", "λ°°λ¨", "μ€κΈ", "κΈ°μ κ·", "보ν", "νμ²", "μΈμ§", "νΌλ", "λΆμ", "μ°μΈ", "μ
μ", "ν΄μ"]
CASE_RISK_KEYWORDS = ["λ°λ³΅", "μ§μ", "μ
ν", "곡격", "νμΈ", "κ±°λΆ", "λ§μ", "λ°°ν", "κ°λ±", "λ€νΌ", "νλ", "λ°©μ", "μν΄", "μν", "μ
μ", "μκΈ", "보νΈμ λΆμ¬"]
CARE_PLAN_KEYWORDS = ["μλΉμ€", "μκ°", "λ³κ²½", "μ‘°μ ", "μΆκ°", "μμ²", "λ³μ", "λν", "μμ¬", "μ΄λ", "λͺ©μ", "볡μ½", "νλ‘κ·Έλ¨", "κΈμ¬", "κ³ν"]
FALL_KEYWORDS = ["λμ", "λμ΄", "λ―Έλ", "μ΄μ§", "νκΈ°", "νμ²", "보ν", "λΆμΆ", "μ§ν‘", "μ컀", "ν 체μ΄", "λ¬Έν±", "μ‘°λͺ
", "μΌκ°", "νμ₯μ€", "μ‘μ"]
PRESSURE_INJURY_KEYWORDS = ["μμ°½", "λ°μ ", "μμ²", "νΌλΆ", "μ§λ¬΄", "μΌμΆ", "λλ μ±", "체μ", "μμ", "μΉ¨μ", "ν 체μ΄", "ν΅μ¦", "μ΄κ°", "μμ", "νμ"]
DEMENTIA_KEYWORDS = ["μΉλ§€", "μΈμ§", "κΈ°μ΅", "λ°λ³΅", "λ°°ν", "λ§μ", "νκ°", "λΆμ", "μ΄μ‘°", "곡격", "κ±°λΆ", "κ·κ°", "μ§μ", "κ³ μ§", "νΌλ", "μ¬λ§"]
GUARDIAN_BURDEN_KEYWORDS = ["νλ€", "μμ§", "λΆλ΄", "μ§μΉ¨", "νΌμ", "κ±±μ ", "λΆμ", "μμ²", "νλ", "λ³κ²½", "λν", "μλ΄"]
GENERATION_RULE = """
μλ΄μΌμ§ μμ± κ·μΉ:
1. μ¬μ©μκ° μ
λ ₯ν μλ΄λ΄μ©μ μλ λ¬Έμ λ μ λ μμ±νμ§ μλλ€.
2. μμ°½, λ°°ν, 곡격μ±, κ·κ°μꡬ, μΉλ§€νλ, λμ λ± μνμμΈμ μ
λ ₯λ κ²½μ°μλ§ μμ±νλ€.
3. μ
λ ₯λμ§ μμ μνμμΈμ 'νμΈ νμ', 'κ²ν νμ'λΌλ ννμΌλ‘λ λ£μ§ μλλ€.
4. μ λ¬Έμ μ¬μ (A)μ μ€μ μ
λ ₯λ μλ΄λ΄μ©μ λ°νμΌλ‘λ§ μμ±νλ€.
5. μ‘°μΉκ³ν(P)μ νμΈλ λ¬Έμ μ λν κ³νλ§ μμ±νλ€.
6. μλ΄λ΄μ©μ΄ λ¨μ μΌλ°μλ΄μ΄λ©΄ μΌλ°μλ΄ λ΄μ©μΌλ‘λ§ μμ±νλ€.
""".strip()
RESTRICTED_REPLACEMENTS = {
"μ΄μ κ³Ό λμΌ": "μλ΄ μμ μ μνλ μ΄μ κΈ°λ‘κ³Ό ν° μ°¨μ΄ μμ΄ μ μ§λλ κ²μΌλ‘ μ
λ ₯λ¨",
"μν μνΈ": "μμ¬, μ΄λ, μ μ νν λ± μΌμ κΈ°λ₯μ΄ λΉκ΅μ μμ μ μΌλ‘ μ μ§λλ κ²μΌλ‘ νμΈν¨",
"νΉμ΄μ¬ν μμ": "μλ΄ μμ μ μΆκ°λ‘ μ
λ ₯λ νΉμ΄μ¬νμ μμ",
"λ³μΌ μμ": "μλ΄ μμ μ μΆκ°λ‘ μ
λ ₯λ λ³λ μ¬νμ μμ",
"λ¬Έμ μμ": "μλ΄ μμ μ λ³λ λ¬Έμ λ‘ μ
λ ₯λ λ΄μ©μ μμ",
}
INSTITUTION_SOAP_GUIDES = {
"λ°©λ¬Έμμ": {
"focus": "κ°μ λ΄ μμ‘΄ κΈ°λ₯ μ μ§, μ£Όκ±°νκ²½ λμ μν, 보νΈμ λΆμ¬μκ°, λΉκΈμ¬μ± μꡬμ κΈμ¬μ 곡 λ²μ μ‘°μ¨",
"plan": "κ°μ λ΄ μ΄λ λμ κ³Ό νμ₯μ€ μ΄μ© νκ²½μ μ κ²νκ³ κΈμ¬μ 곡κ³ν λ²μ μμμ μ 체νλ λ° μ μμ§μ λ°©λ²μ μ‘°μ ν¨",
},
"λ°©λ¬Έλͺ©μ": {
"focus": "λͺ©μ μ ν 건κ°μν, νΌλΆ λ°μ , κ°μΌμ± νΌλΆμ§ν, μ΄λΒ·λ―ΈλλΌΒ·νμ μλ°©, λͺ©μ κ±°λΆκ°",
"plan": "λͺ©μ μ ν 컨λμ
κ³Ό νΌλΆμνλ₯Ό νμΈνκ³ λ―ΈλλΌ λ°©μ§, μ²΄μ¨ λ³ν, νΌλΆ λ§μ°° μ΅μν μ‘°μΉλ₯Ό μ μ©ν¨",
},
"λ°©λ¬Έκ°νΈ": {
"focus": "λ§μ±μ§ν κ΄λ¦¬, λ³΅μ½ μ΄ν, κ°νΈμ²μΉ μν, λ°©λ¬Έκ°νΈμ§μμ μ ν¨μ±, 보νΈμμ μ²μΉ μ΄ν΄λ",
"plan": "νλ ₯μ§νμ μ²μΉ μνλ₯Ό νμΈνκ³ λ³΅μ½Β·μμ²Β·κ΄ μ½μ
κ΄λ¦¬ νμ μ μλ£κΈ°κ΄ λλ 보νΈμμ μ°κ³ν¨",
},
"μ£Όκ°λ³΄νΈ": {
"focus": "μ‘μ μμ , λ±μ κ±°λΆ, νλ‘κ·Έλ¨ μ°Έμ¬λ, μμ¬Β·μμ μ°Έμ¬, 보νΈμ λΆμ λΆλ΄ κ²½κ°",
"plan": "μ‘μ λ° μΌν° λ΄ μ΄λ λμ μ μ κ²νκ³ νλ‘κ·Έλ¨ μ°Έμ¬ μμ€κ³Ό 보νΈμ λΆλ΄ λ³νλ₯Ό ν¨κ» νμΈν¨",
},
"μμμ": {
"focus": "μ
μμν μ μ, 곡λμν κ°λ±, μμ¬Β·μλ©΄Β·λ°°λ³, μμ°½Β·λμ μλ°©, 보νΈμ μλ΄ λ°μ",
"plan": "곡λμν μ μκ³Ό μ 체·μ μ μνλ₯Ό κ΄μ°°νκ³ λ³΄νΈμ λ©΄νΒ·μλ΄ λ΄μ©μ μμ€ μΌμ΄κ³νμ λ°μν¨",
},
"곡λμνκ°μ ": {
"focus": "μκ·λͺ¨ 곡λ체 κ΄κ³, μκΈ°κ²°μ κΆ, κ°λ³ μνμ΅κ΄, κ°μ ν νκ²½ μ μ§, μ€λ΄ μ΄λ λμ μμ ",
"plan": "κ°λ³ μνμ΅κ΄κ³Ό 곡λμν κ°λ±μμΈμ μ‘°μ¨νκ³ μκ·λͺ¨ νκ²½μμμ μ μ μμ λ° μμ κ΄λ¦¬ κ³νμ μ립ν¨",
},
}
SUPPORT_METHOD_RULES = {
"μ΄λμ§μ": {
"keywords": ["μ΄λ", "보ν", "λΆμΆ", "ν 체μ΄", "μ§ν‘", "μ컀", "νμ₯μ€", "λμ", "νμ²", "μ΄μ§"],
"items": [
"μΉ¨μκ³Ό ν μ²΄μ΄ μ΄λ μ λΆμΆμ μ 곡ν¨",
"보νκΈ° λ° μ§ν‘μ΄ μ¬μ©μ μ§μν¨",
"μ€λ΄ μ΄λ μ μμ μ νμΈν¨",
"μ΄λ λμ μ μ 리νμ¬ λμμ μλ°©ν¨",
"νμ₯μ€ μ΄λ μ λν λ° μμ κ΄μ°°μ μ€μν¨",
],
},
"μΌμμνμ§μ": {
"keywords": ["μμ", "μΈλ©΄", "ꡬκ°", "μ볡", "μ°©ν", "μλ°ν±", "λͺ©μ", "νΌλΆ"],
"items": [
"μΈλ©΄ λ° κ΅¬κ°κ΄λ¦¬λ₯Ό μ§μν¨",
"μ볡 μ°©νμλ₯Ό μ§μν¨",
"κ°μΈμμ μνλ₯Ό μ κ²ν¨",
"μλ°ν± μνλ₯Ό νμΈνκ³ μμκ΄λ¦¬λ₯Ό μ§μν¨",
"λͺ©μ μ ν 건κ°μνμ νΌλΆμνλ₯Ό νμΈν¨",
],
},
"λμμλ°© μ§μ": {
"keywords": FALL_KEYWORDS,
"items": [
"보ν μ λΆμΆμ μ 곡ν¨",
"λ―ΈλλΌ λ°©μ§ νκ²½μ μ κ²ν¨",
"μΉ¨μ λκ° μνλ₯Ό νμΈν¨",
"μΌκ° μ΄λ μ μμ κ΄μ°°μ μ€μν¨",
"μ΄λ μ μ΄μ§λΌ μ¬λΆλ₯Ό νμΈν¨",
"보ν보쑰기 μ¬μ©μ μλ΄ν¨",
],
},
"μμ°½μλ°© μ§μ": {
"keywords": PRESSURE_INJURY_KEYWORDS,
"items": [
"μ κΈ°μ 체μλ³κ²½μ μ€μν¨",
"νΌλΆμνλ₯Ό νμΈν¨",
"μμ°½ λ°μ μ¬λΆλ₯Ό κ΄μ°°ν¨",
"μμμνλ₯Ό νμΈν¨",
"μ₯μκ° λμΌ μμΈλ₯Ό μλ°©ν¨",
"νΌλΆ μ²κ²°μ μ μ§ν¨",
],
},
"μΈμ§μ§μ": {
"keywords": DEMENTIA_KEYWORDS + ["μΈμ§μ ν", "κΈ°μ΅μ ν", "μΌμ ν", "μμ μμΉ"],
"items": [
"λ μ§μ μ₯μλ₯Ό λ°λ³΅ μλ΄ν¨",
"νμλνλ₯Ό μ€μν¨",
"μ΅μν νκ²½μ μ μ§νλλ‘ μ§μν¨",
"μΈμ§μκ·Ή νλμ μ 곡ν¨",
"μΌμ νλ₯Ό νμ©νμ¬ μλ΄ν¨",
"μμ μμΉμ λ°λ³΅ κ΅μ‘ν¨",
],
},
"BPSD μ§μ": {
"keywords": ["λ°λ³΅μ§λ¬Έ", "λ§μ", "λ°°ν", "곡격", "νμΈ", "κ·κ°", "νκ°", "κ±°λΆ", "λΆμ", "μ΄μ‘°"],
"items": [
"λμΌ μ§λ¬Έμ μμ μ μΌλ‘ μλν¨",
"μκ°μλ£λ₯Ό νμ©νμ¬ μ€λͺ
ν¨",
"μ λ©΄ λ°λ°μ μ§μν¨",
"κ°μ μ 곡κ°ν¨",
"νμ λ₯Ό μ νν¨",
"μμ ν μ΄λμ μ§μν¨",
"μκ·ΉμμΈμ μ κ±°νκ³ μ°¨λΆν νκ²½μ μ 곡ν¨",
],
},
"μ μμ§μ": {
"keywords": ["μΈλ‘μ", "μ°μΈ", "λΆμ", "무기λ ₯", "μμΈ", "λλ¬Ό", "μ§μ¦", "μ μ", "κ°μ‘±", "κ±±μ "],
"items": [
"λ§λ² μλΉμ€λ₯Ό μ 곡ν¨",
"곡κ°μ κ²½μ²μ μ€μν¨",
"κΈμ μ νΌλλ°±μ μ 곡ν¨",
"μ·¨λ―Ένλ μ°Έμ¬λ₯Ό μ§μν¨",
"μ±μ·¨κ°μ κ²½ννλλ‘ μ§μν¨",
"μμ μ μΈ νκ²½μ μ 곡ν¨",
"κ°μ‘± μ°λ½μ μ§μν¨",
],
},
"μ¬νμ°Έμ¬μ§μ": {
"keywords": ["κ³ λ¦½", "νλ‘κ·Έλ¨", "κ΄κ³", "λμΈ", "μ°Έμ¬", "νλ", "λ§λ€νΌ", "κ°λ±"],
"items": [
"μ§λ¨ νλ‘κ·Έλ¨ μ°Έμ¬λ₯Ό μ§μν¨",
"λλ κ΄κ³ νμ±μ μ§μν¨",
"μ§μμ¬ν νλ μ°Έμ¬λ₯Ό μ°κ³ν¨",
"λν κΈ°νλ₯Ό μ 곡ν¨",
"곡λνλ μ°Έμ¬λ₯Ό μ λν¨",
],
},
"μμμ§μ": {
"keywords": ["μμ¬", "μμ", "체μ€", "μμ", "μλΆ", "νμ", "κΈ°νΈμ", "μ νΈ μμ"],
"items": [
"μ νΈ μμμ νμΈν¨",
"κΈ°νΈμμ λ°μν¨",
"μλΆμμ·¨λ₯Ό λ
λ €ν¨",
"μμμνλ₯Ό κ΄μ°°ν¨",
"체μ€λ³νλ₯Ό νμΈν¨",
"μμ보좩μ μ§μν¨",
],
},
"λ°°λ³λ°°λ¨μ§μ": {
"keywords": ["λ°°λ³", "λ°°λ¨", "λ³λΉ", "μμ€κΈ", "μ€κΈ", "κΈ°μ κ·", "νμ₯μ€"],
"items": [
"μλΆμμ·¨λ₯Ό λ
λ €ν¨",
"κ·μΉμ λ°°λ³μ΅κ΄μ μ§μν¨",
"νλλ μ¦κ°λ₯Ό μ λν¨",
"λ°°λ³λ°°λ¨ ν¨ν΄μ νμΈν¨",
"μ κΈ° λ³κΈ°μ λλ₯Ό μ€μν¨",
"μμκ΄λ¦¬λ₯Ό μ§μν¨",
],
},
"볡μ½μ§μ": {
"keywords": ["볡μ½", "μ½", "ν¬μ½", "μ½λ¬Ό", "볡μ©", "νμ", "νλΉ"],
"items": [
"λ³΅μ½ μ¬λΆλ₯Ό νμΈν¨",
"μ½λ¬Ό 보κ΄μνλ₯Ό μ κ²ν¨",
"보νΈμμ λ³΅μ½ μ 보λ₯Ό 곡μ ν¨",
"볡μ½μΌμ§λ₯Ό νμΈν¨",
"ν¬μ½μκ°μ μλ΄ν¨",
],
},
"보νΈμμ§μ": {
"keywords": GUARDIAN_BURDEN_KEYWORDS + ["보νΈμ", "κ°μ‘±", "κ°μ‘±κ°λ±"],
"items": [
"보νΈμ μλ΄μ μ€μν¨",
"λλ΄ λΆλ΄μ κ²½μ²ν¨",
"μ§μμμ μ°κ³λ₯Ό μλ΄ν¨",
"κ°μ‘± μ견μ μ‘°μ ν¨",
"μμ¬μν΅μ μ§μν¨",
],
},
"μ§μμμ μ°κ³": {
"keywords": ["μΉλ§€", "건κ°κ΄λ¦¬", "κ²½μ ", "κ³ λ¦½", "λ³μ", "보건μ", "볡μ§κ΄", "νμ 볡μ§μΌν°", "μ§μμμ", "λ°©λ¬Έκ°νΈ"],
"items": [
"μ
λ ₯λ λ΄μ©μ λ°λΌ κ΄λ ¨ μ§μμμμ μλ΄ν¨",
"λ°©λ¬Έκ°νΈ μλΉμ€λ₯Ό μ°κ³ν¨",
"보건μ νλ‘κ·Έλ¨μ μλ΄ν¨",
"νμ 볡μ§μΌν° μ΄μ© μ 보λ₯Ό μλ΄ν¨",
"볡μ§κ΄ νλ‘κ·Έλ¨ μ°Έμ¬λ₯Ό μ°κ³ν¨",
],
},
}
def _join_qna_answer_values(qna_answers):
if not qna_answers:
return ""
answers = []
for answer in qna_answers.values():
answer = _clean_text(answer)
if answer:
answers.append(answer)
return " ".join(answers)
def _contains_keywords(text, keywords):
source = str(text or "")
return any(keyword in source for keyword in keywords)
def _replace_restricted_phrases(text):
result = str(text or "")
for bad, good in RESTRICTED_REPLACEMENTS.items():
result = result.replace(bad, good)
return result
def _formalize_sentence(text):
cleaned = _replace_restricted_phrases(_clean_text(text))
if not cleaned:
return ""
if cleaned.endswith(("ν¨", "μ", "λ¨", "νμΈλ¨", "νλ¨λ¨", "μ¬μ λ¨", "μμ", "μμ", "νμ¬", "λ§μνμ¬", ".")):
return cleaned.rstrip(".")
return f"{cleaned}λ‘ νμΈλ¨"
def _risk_flag(text, keywords):
return _contains_keywords(text, keywords)
def build_individual_support_methods(data, specialized=None):
qna_text = _join_qna_answer_values(data.get("qna_answers", {}))
combined = " ".join([
data.get("institution_type", ""),
data.get("care_grade", ""),
data.get("main_condition", ""),
data.get("cognitive_status", ""),
data.get("mobility_status", ""),
data.get("diet_status", ""),
data.get("toileting_status", ""),
data.get("client_status_change", ""),
data.get("guardian_opinion", ""),
data.get("staff_observation", ""),
data.get("custom_need", ""),
qna_text,
])
selected = {}
for category, rule in SUPPORT_METHOD_RULES.items():
if _contains_keywords(combined, rule["keywords"]):
selected[category] = rule["items"][:3]
if specialized:
if specialized.get("fall"):
selected.setdefault("λμμλ°© μ§μ", SUPPORT_METHOD_RULES["λμμλ°© μ§μ"]["items"][:3])
if specialized.get("pressure"):
selected.setdefault("μμ°½μλ°© μ§μ", SUPPORT_METHOD_RULES["μμ°½μλ°© μ§μ"]["items"][:3])
if specialized.get("dementia"):
selected.setdefault("μΈμ§μ§μ", SUPPORT_METHOD_RULES["μΈμ§μ§μ"]["items"][:3])
selected.setdefault("BPSD μ§μ", SUPPORT_METHOD_RULES["BPSD μ§μ"]["items"][2:5])
if specialized.get("guardian_burden"):
selected.setdefault("보νΈμμ§μ", SUPPORT_METHOD_RULES["보νΈμμ§μ"]["items"][:3])
if not selected:
return "[κ°μΈλ³ μ΄μ©κ³ν μ§μλ°©λ²]\n- μ
λ ₯λ μλ΄λ΄μ©μ λ³λ μ§μλ°©λ² λ³κ²½ μ¬νμ΄ μμ΄ νμ¬ μλΉμ€ μ 곡 λ΄μ©μ μ μ§ν¨"
limited = list(selected.items())[:5]
lines = ["[κ°μΈλ³ μ΄μ©κ³ν μ§μλ°©λ²]"]
for category, items in limited:
lines.append(f"{category}")
for item in items[:3]:
lines.append(f"- {item}")
return "\n".join(lines)
def analyze_specialized_risks(data):
qna_text = _join_qna_answer_values(data.get("qna_answers", {}))
combined = " ".join([
data.get("main_condition", ""),
data.get("cognitive_status", ""),
data.get("mobility_status", ""),
data.get("diet_status", ""),
data.get("toileting_status", ""),
data.get("client_status_change", ""),
data.get("guardian_opinion", ""),
data.get("staff_observation", ""),
data.get("custom_need", ""),
qna_text,
])
fall = _risk_flag(combined, FALL_KEYWORDS)
pressure = _risk_flag(combined, PRESSURE_INJURY_KEYWORDS)
dementia = _risk_flag(combined, DEMENTIA_KEYWORDS)
guardian_burden = _risk_flag(data.get("guardian_opinion", "") + " " + qna_text, GUARDIAN_BURDEN_KEYWORDS)
count = sum([fall, pressure, dementia])
if count >= 2 or _contains_keywords(combined, ["μκΈ", "μ
μ", "μ
ν", "곡격", "μμ²", "μΌμΆ", "κ°μΌ", "λ°λ³΅ λμ"]):
level = "μν"
elif count == 1 or guardian_burden:
level = "μ£Όμ"
else:
level = "μμ "
details = []
plans = []
if fall:
details.append("μ
λ ₯λ μλ΄λ΄μ©μ λμ κ΄λ ¨ λ¨μκ° ν¬ν¨λ¨")
plans.append("λμμλ°©μ μν΄ μ΄λ λμ , νμ₯μ€ μ΄μ©, λ―ΈλλΌ λ°©μ§, 보ν보쑰기 μ¬μ© μ¬λΆλ₯Ό μ κ²νκ³ μ΄λ μ λΆμΆ λ° μ§μ λνμ κ°νν¨")
if pressure:
details.append("μ
λ ₯λ μλ΄λ΄μ©μ μμ°½ λλ νΌλΆμμ κ΄λ ¨ λ¨μκ° ν¬ν¨λ¨")
plans.append("μ
λ ₯λ νΌλΆ κ΄λ ¨ λ΄μ©μ λ°λΌ 체μλ³κ²½, μλ°λΆμ κ΄λ¦¬, μμΒ·μλΆ μμ·¨ μ§μμ μλΉμ€ μ 곡 κ³Όμ μ λ°μν¨")
if dementia:
details.append("μ
λ ₯λ μλ΄λ΄μ©μ μΉλ§€ λλ μΈμ§Β·νλ λ³ν κ΄λ ¨ λ¨μκ° ν¬ν¨λ¨")
plans.append("μ λ©΄ λ°λ°μ νΌνκ³ κ°μ 곡κ°, μ§§μ μ€λͺ
, μ΅μν νμ μ ν, κ°λ³ νλ‘κ·Έλ¨ λλ μμ νκ²½ μ 곡μ μ μ©ν¨")
if guardian_burden:
details.append("μ
λ ₯λ μλ΄λ΄μ©μ 보νΈμμ λλ΄ λΆλ΄ λλ μΆκ° μμ²μ΄ ν¬ν¨λ¨")
plans.append("보νΈμ μ견μ μλ΄κΈ°λ‘μ λ°μνκ³ μλΉμ€ μ‘°μ κ°λ₯ λ²μ, λ³μμ§λ£, μλ΄, μ§μμμ μ°κ³ νμμ±μ μλ΄ν¨")
if not details:
details.append("μ
λ ₯λ μλ΄λ΄μ©μμ λ³λ μνμμΈμ μμ±λμ§ μμ")
if not plans:
plans.append("μ
λ ₯λ μλ΄λ΄μ©μ μλ΄κΈ°λ‘μΌλ‘ 보κ΄νκ³ νμ¬ μλΉμ€ μ 곡 λ΄μ©μ μ μ§ν¨")
return {
"combined": combined,
"fall": fall,
"pressure": pressure,
"dementia": dementia,
"guardian_burden": guardian_burden,
"level": level,
"details": details,
"plans": plans,
}
def build_soap_sections(data, analysis, specialized):
subject_sources = [
data.get("guardian_opinion", ""),
data.get("custom_need", ""),
_join_qna_answer_values(data.get("qna_answers", {})),
data.get("client_status_change", ""),
]
subjective = _clean_text(" / ".join([_formalize_sentence(x) for x in subject_sources if _clean_text(x)]))
if not subjective:
subjective = f"{data['counseling_type']} κ³Όμ μμ {data['counseling_reason']} λ΄μ©μΌλ‘ μΌλ°μλ΄μ μ§νν¨"
objective_items = []
objective_fields = [
("μ£Όμ μ§ν λ° μν", "main_condition"),
("μΈμ§μν", "cognitive_status"),
("μ΄λμν", "mobility_status"),
("μμ¬μν", "diet_status"),
("λ°°λ³Β·λ°°λ¨μν", "toileting_status"),
("μνλ³ν", "client_status_change"),
("μ’
μ¬μ κ΄μ°°μ¬ν", "staff_observation"),
]
for label, key in objective_fields:
value = _formalize_sentence(data.get(key))
if value:
objective_items.append(f"{label}: {value}")
if not objective_items:
objective_items.append("μΆκ° μνμμΈ μ
λ ₯ μμ΄ μΌλ° μλ΄μΌλ‘ κΈ°λ‘ν¨")
objective = "\n".join([f"- {item}" for item in objective_items])
risk_summary = " / ".join(specialized["details"])
assessment_parts = [
f"μλ΄λμμ {data['counseling_type']}μ΄λ©°, μ
λ ₯λ μλ΄λ΄μ©μ κΈ°μ€μΌλ‘ μ¬μ ν¨.",
risk_summary,
]
if analysis["status_needed"]:
assessment_parts.append("μ
λ ₯ λ΄μ©μ μνλ³ν λ¨μκ° μμ΄ μνλ³νκΈ°λ‘μ§ μμ± λμμΌλ‘ νλ¨ν¨.")
if analysis["case_needed"] or specialized["level"] == "μν":
assessment_parts.append("μ
λ ₯ λ΄μ©μ μ¬λ‘κ΄λ¦¬ μ°κ³κ° νμν μν λ¨μκ° ν¬ν¨λ¨.")
assessment = " ".join([part for part in assessment_parts if _clean_text(part)])
plan_items = list(specialized["plans"])
if analysis["care_plan_needed"]:
plan_items.append(analysis["care_plan_reason"])
if analysis["status_needed"]:
plan_items.append(analysis["status_reason"])
if analysis["case_needed"] or specialized["level"] == "μν":
plan_items.append(analysis["case_reason"])
unique_plans = []
for item in plan_items:
item = _formalize_sentence(item)
if item and item not in unique_plans:
unique_plans.append(item)
plan = "\n".join([f"- {item}" for item in unique_plans[:8]])
return f"""
[SOAP μλ΄κΈ°λ‘]
S(μ£Όκ΄μ μ§μ ): {subjective}
O(κ°κ΄μ κ΄μ°°):
{objective}
A(μ λ¬Έμ μ¬μ ): {assessment}
P(μ‘°μΉκ³ν):
{plan}
""".strip()
def analyze_counseling_answers(data):
qna_text = _join_qna_answer_values(data.get("qna_answers", {}))
combined = " ".join([
data.get("main_condition", ""),
data.get("cognitive_status", ""),
data.get("mobility_status", ""),
data.get("diet_status", ""),
data.get("toileting_status", ""),
data.get("client_status_change", ""),
data.get("guardian_opinion", ""),
data.get("staff_observation", ""),
data.get("custom_need", ""),
qna_text,
])
status_needed = _contains_keywords(combined, STATUS_CHANGE_KEYWORDS)
case_needed = _contains_keywords(combined, CASE_RISK_KEYWORDS)
care_plan_needed = _contains_keywords(combined, CARE_PLAN_KEYWORDS) or status_needed or case_needed
if case_needed:
risk_level = "μν"
elif status_needed:
risk_level = "μ£Όμ"
else:
risk_level = "μμ "
return {
"qna_text": qna_text,
"combined": combined,
"risk_level": risk_level,
"status_needed": status_needed,
"case_needed": case_needed,
"care_plan_needed": care_plan_needed,
"status_reason": "μλ΄ λ΅λ³μμ μνλ³ν λ¨μκ° νμΈλμ΄ μνλ³νκΈ°λ‘μ§ μμ± λμμΌλ‘ κΈ°λ‘ν¨" if status_needed else "ν΄λΉ μμ",
"case_reason": "μλ΄ λ΅λ³μμ λ°λ³΅Β·μ
νΒ·μν μμ€μ λ¬Έμ κ° νμΈλμ΄ μ¬λ‘κ΄λ¦¬ μ°κ³ λμμΌλ‘ κΈ°λ‘ν¨" if case_needed else "ν΄λΉ μμ",
"care_plan_reason": "μλ΄ κ²°κ³Όλ₯Ό κΈμ¬μ 곡κ³ν λλ μλΉμ€ μ 곡 λ°©λ²μ λ°μν νμκ° μμ" if care_plan_needed else "μλ΄λ΄μ©λ§ κΈ°λ‘ν¨",
}
def build_qna_generated_sections(data):
analysis = analyze_counseling_answers(data)
qna_text = analysis["qna_text"] or "Q&A μ
λ ₯ λ΄μ© μμ"
status_text = _clean_text(data.get("client_status_change")) or "μνλ³ν μ
λ ₯ λ΄μ© μμ"
guardian_text = _clean_text(data.get("guardian_opinion")) or "보νΈμ μ견 μ
λ ₯ λ΄μ© μμ"
staff_text = _clean_text(data.get("staff_observation")) or "μ’
μ¬μ κ΄μ°°μ¬ν μ
λ ₯ λ΄μ© μμ"
need_text = _clean_text(data.get("custom_need")) or "μΆκ° μꡬ λ° μμ²μ¬ν μ
λ ₯ λ΄μ© μμ"
follow_items = []
if analysis["status_needed"]:
follow_items.append(analysis["status_reason"])
if analysis["case_needed"]:
follow_items.append(analysis["case_reason"])
if analysis["care_plan_needed"]:
follow_items.append(analysis["care_plan_reason"])
follow_text = "\n".join([f"- {item}" for item in follow_items]) or "- λ¨μ μΌλ°μλ΄μΌλ‘ κΈ°λ‘ν¨"
if analysis["status_needed"] or analysis["case_needed"]:
status_analysis_text = f"μ
λ ₯λ μλ΄λ΄μ©μ κΈ°μ€μΌλ‘ μνμμ€μ {analysis['risk_level']} λ¨κ³λ‘ κΈ°λ‘ν¨. μ
λ ₯λμ§ μμ μνμμΈμ μμ±νμ§ μμ."
else:
status_analysis_text = "μ
λ ₯λ μλ΄λ΄μ©μ κΈ°μ€μΌλ‘ μΌλ°μλ΄μΌλ‘ κΈ°λ‘ν¨."
optional_sections = []
if analysis["care_plan_needed"]:
optional_sections.append(f"[κΈμ¬μ 곡κ³ν λ°μ μ¬λΆ]\n{analysis['care_plan_reason']}")
if analysis["status_needed"]:
optional_sections.append(f"[μνλ³νκΈ°λ‘μ§ μμ± νμ μ¬λΆ]\n{analysis['status_reason']}")
if analysis["case_needed"]:
optional_sections.append(f"[μ¬λ‘κ΄λ¦¬ μ°κ³ νμ μ¬λΆ]\n{analysis['case_reason']}")
optional_text = "\n\n".join(optional_sections)
return f"""
[Q&A νμΈ λ΄μ©]
μ
λ ₯ λ΅λ³: {qna_text}
[μλ΄λ΄μ©]
μλ΄λΆλ₯λ {data['counseling_type']}μ΄λ©°, μλ΄μ¬μ λ {data['counseling_reason']}μ. Q&A λ° κΈ°λ³Έ μ
λ ₯μ μ’
ν©ν κ²°κ³Ό, μκΈμ μνλ³νλ "{status_text}"λ‘ νμΈλ¨. 보νΈμ μ견μ "{guardian_text}"μ΄λ©°, μ’
μ¬μ κ΄μ°°μ¬νμ "{staff_text}"λ‘ μ 리λ¨.
[μνλΆμ]
{status_analysis_text}
[μ‘°μΉκ³ν]
νμΈλ μꡬ λ° μμ²μ¬νμ "{need_text}"μ. μ
λ ₯λ μλ΄λ΄μ©μ λ΄λΉμμ 곡μ νκ³ νμν λ²μμμλ§ μ‘°μΉν¨.
[νμκ΄λ¦¬]
{follow_text}
{chr(10) + chr(10) + optional_text if optional_text else ""}
""".strip(), analysis
OPENERS = [
"μλ΄ κ³Όμ μμ νμΈν κ²°κ³Ό",
"κΈμΌ μλ΄μ ν΅ν΄ νμ
ν λ΄μ©μ",
"μ΅κ·Ό μλΉμ€ μ 곡 μν©μ μ’
ν©ν΄ 보면",
"μκΈμ λ° κ΄λ ¨μμ μ견μ νμΈν κ²°κ³Ό",
]
TONES = [
"νμ¬ μνλ₯Ό μ μ§νλ©΄μλ μΈμ¬ν κ΄μ°°μ΄ μꡬλ©λλ€.",
"μΌμμνμ μμ μ±μ λμ΄κΈ° μν μ§μμ μΈ μ§μμ΄ νμν©λλ€.",
"κ°λ³ μκ΅¬κ° κΈμ¬μ 곡 κ³Όμ μ μΆ©λΆν λ°μλλλ‘ μ‘°μ μ΄ νμν©λλ€.",
"μν λ³ν κ°λ₯μ±μ κ³ λ €νμ¬ μ κΈ°μ μΈ νμΈκ³Ό κΈ°λ‘μ΄ νμν©λλ€.",
]
def mask_name(name: str) -> str:
name = str(name or "").strip()
if len(name) >= 3:
return name[0] + "β" + name[2:]
if len(name) == 2:
return name[0] + "β"
return name
def _clean_text(value: str) -> str:
text = str(value or "").strip()
while ",," in text:
text = text.replace(",,", ",")
return text.strip(" ,")
def _symptom_process(data):
"""μ
λ ₯λ μνλ³νμ λ°λΌ μλ΄ κ³Όμ μ΄ λ¨μ 볡λΆμ΄ λμ§ μλλ‘ νμΈ μ μ°¨λ₯Ό μλ ꡬμ±ν©λλ€."""
combined = " ".join([
data.get("main_condition", ""),
data.get("client_status_change", ""),
data.get("guardian_opinion", ""),
data.get("staff_observation", ""),
data.get("custom_need", ""),
])
steps = []
if any(k in combined for k in ["μ΄μ§", "νκΈ°", "νμ²", "기립", "λ©λ―Έ"]):
steps.extend([
"μ
λ ₯λ μ΄μ§λΌ κ΄λ ¨ λ΄μ©μ μλ΄κΈ°λ‘μ λ°μνμλ€.",
"μ΄μ§λΌκ³Ό ν¨κ» μ
λ ₯λ μνμ λΆνΈ μ¬νμ λ΄λΉμμ 곡μ νκΈ°λ‘ νμλ€.",
])
if any(k in combined for k in ["μμ¬", "μμ", "μμ", "체μ€", "μ¬λ ", "μ°ν"]):
steps.extend([
"μ
λ ₯λ μμ¬ κ΄λ ¨ λ΄μ©μ μλ΄κΈ°λ‘μ λ°μνμλ€.",
"μμ¬μ κ΄λ ¨νμ¬ μ
λ ₯λ μμ²μ¬νμ μλΉμ€ μ 곡 κ³Όμ μ μ°Έκ³ νκΈ°λ‘ νμλ€.",
])
if any(k in combined for k in ["λμ", "보ν", "μ§ν‘", "ν 체μ΄", "λΆμΆ", "μ΄λ"]):
steps.extend([
"보ν 보쑰λꡬ μ¬μ©μν, μ΄λ λμ , νμ₯μ€ μ΄λ μ μνμμΈ, λ°λ₯ λ―ΈλλΌ μ¬λΆλ₯Ό νμΈνμλ€.",
"λμμλ°©μ μν΄ μ΄λ μ ν μ§μ νμΈ, μ리 μ΄λ μ λΆμΆ, νλ‘κ·Έλ¨ μ°Έμ¬ μ μμ ν μ’μ λ°°μΉλ₯Ό μ κ²νκΈ°λ‘ νμλ€.",
])
if any(k in combined for k in ["μΉλ§€", "μΈμ§", "κΈ°μ΅", "λΆμ", "μ΄μ‘°", "λ°°ν"]):
steps.extend([
"μ
λ ₯λ μΈμ§ λ° μ μ κ΄λ ¨ λ΄μ©μ μλ΄κΈ°λ‘μ λ°μνμλ€.",
"μ€λͺ
μ μ§§κ³ λ°λ³΅μ μΌλ‘ μ 곡νκ³ , μ΅μν νκ²½μμ μμ κ°μ λλΌλλ‘ μ μμ§μ§λ₯Ό λ³ννκΈ°λ‘ νμλ€.",
])
if any(k in combined for k in ["λ°°λ³", "λ°°λ¨", "κΈ°μ κ·", "μ€κΈ", "λ³λΉ"]):
steps.extend([
"λ°°λ³Β·λ°°λ¨ μμ, νμ₯μ€ μ΄λ κ°λ₯ μ¬λΆ, μ€κΈ λλ λ³λΉ μ¬λΆλ₯Ό νμΈνκ³ μμκ΄λ¦¬ νμμ±μ μ κ²νμλ€.",
])
if not steps:
steps = [
"μ
λ ₯λ μλ΄λ΄μ©μ μ€μ¬μΌλ‘ μλ΄ μ¬μ μ μμ²μ¬νμ κΈ°λ‘νμλ€.",
"μλ΄ μ€ λ³λλ‘ μ
λ ₯λ μνμμΈμ΄ μμ΄ μΌλ° μλ΄ λ΄μ©μΌλ‘ μ 리νμλ€.",
"μ
λ ₯λ λ΄μ©μ λ΄λΉμμ 곡μ νκ³ νμ¬ μλΉμ€ μ 곡 κ³Όμ μ μ°Έκ³ νκΈ°λ‘ νμλ€.",
]
# μ€λ³΅ μ κ±°
unique = []
for step in steps:
if step not in unique:
unique.append(step)
return "\n".join([f"{i}. {step}" for i, step in enumerate(unique[:7], 1)])
def build_counseling_text(data):
# Prepare inputs
selected_items_str = ", ".join(data.get("selected_items_list", []))
keywords = data.get("keywords", "").strip()
additional_needs = data.get("additional_needs", "").strip()
# Check if AI should be used
use_ai = st.session_state.get("use_lm_studio", False)
ai_generated = False
ai_content = ""
if use_ai:
system_prompt = COUNSELING_GENERATION_RULE
user_prompt = f"""
μκΈμλͺ
: {data['masked_name']}
μ₯κΈ°μμλ±κΈ: {data['care_grade']}
κΈ°κ΄μ ν: {data['institution_type']}
μλ΄ μ¬μ : {data['counseling_reason']}
μλ΄ λͺ©μ : {data['counseling_purpose']}
μ νλ κ²ν νλͺ©: {selected_items_str}
ν΅μ¬ λ¨μ΄: {keywords}
μΆκ° μꡬ/μμ² μ¬ν: {additional_needs}
μ°λ μ¬λΆ: μ¬λ‘κ΄λ¦¬ νμ({data['case_needed']}), μꡬμ¬μ μ¬κ²ν νμ({data['needs_assessment_needed']}), κΈμ¬μ 곡κ³ν λ³κ²½ νμ({data['careplan_change_needed']}), λ±κΈλ³κ²½ μ μ² κ²ν νμ({data['care_grade_review_needed']}), 보νΈμ μΆκ° μλ΄ νμ({data['followup_counseling_needed']})
"""
basic_status = []
if data.get("cognitive_status", "").strip(): basic_status.append(f"- μΈμ§μν: {data['cognitive_status']}")
if data.get("mobility_status", "").strip(): basic_status.append(f"- μ΄λμν: {data['mobility_status']}")
if data.get("diet_status", "").strip(): basic_status.append(f"- μμ¬μν: {data['diet_status']}")
if data.get("toileting_status", "").strip(): basic_status.append(f"- λ°°λ³/λ°°λ¨μν: {data['toileting_status']}")
if data.get("client_status_change", "").strip(): basic_status.append(f"- μνλ³ν: {data['client_status_change']}")
if data.get("guardian_opinion", "").strip(): basic_status.append(f"- 보νΈμ μ견: {data['guardian_opinion']}")
if data.get("staff_observation", "").strip(): basic_status.append(f"- μ’
μ¬μ κ΄μ°°μ¬ν: {data['staff_observation']}")
basic_status_str = "\n".join(basic_status)
if basic_status_str:
user_prompt += f"\nμκΈμ μν μ°Έκ³ μ 보:\n{basic_status_str}"
try:
client = OpenAI(base_url="http://127.0.0.1:1234/v1", api_key="lm-studio")
completion = client.chat.completions.create(
model="qwen2.5-7b-instruct",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
temperature=0.7,
)
ai_content = completion.choices[0].message.content
ai_generated = True
except Exception as e:
st.error(f"LM Studio AI μμ± μ€ν¨ ({e}). κ·μΉ κΈ°λ° μμ±κΈ°λ‘ λ체ν©λλ€.")
if not ai_generated:
ai_content = generate_rule_based_counseling_text(data)
# Parse sections
parsed = parse_sections(ai_content)
# Build content by combining sections 1, 2, 3, 5
content_parts = []
if parsed["μλ΄ μ¬μ "]:
content_parts.append(f"[μλ΄ μ¬μ ]\n{parsed['μλ΄ μ¬μ ']}")
if parsed["μλ΄ λ΄μ©"]:
content_parts.append(f"[μλ΄ λ΄μ©]\n{parsed['μλ΄ λ΄μ©']}")
if parsed["μ λ¬Έμ μ¬μ "]:
content_parts.append(f"[μ λ¬Έμ μ¬μ ]\n{parsed['μ λ¬Έμ μ¬μ ']}")
if parsed["μ°λ νμ νλͺ©"]:
content_parts.append(f"[μ°λ νμ νλͺ©]\n{parsed['μ°λ νμ νλͺ©']}")
content = "\n\n".join(content_parts)
if not content:
content = ai_content # fallback
action = parsed["μ‘°μΉκ³ν"] or "μλ΄ κ²°κ³Όλ₯Ό λ΄λΉ μ§μλ€κ³Ό 곡μ νκ³ μλΉμ€ μ 곡μ λ°μν¨."
# Generate support methods
support_methods = build_individual_support_methods(data)
return content, action, support_methods
def evaluate_record(data):
# μ νλ(ν΅μ¬λ¨μ΄, μΆκ°μꡬ) λ°μνμ¬ νκ° κΈ°μ€ μν λ° κ²μ¦
has_keywords_or_needs = bool(data.get("keywords", "").strip()) or bool(data.get("additional_needs", "").strip())
required = {
"μλ΄μΌμ": bool(data["counseling_date"]),
"μλ΄λΆλ₯": bool(data["counseling_type"]),
"μλ΄μ¬μ ": bool(data["counseling_reason"]),
"μνλ³ν λ°μ": bool(data.get("client_status_change", "").strip()) or has_keywords_or_needs or "μνλ³ν" in data.get("counseling_content", ""),
"보νΈμ μ견 λ°μ": bool(data.get("guardian_opinion", "").strip()) or has_keywords_or_needs or data["counseling_type"] == "보νΈμμλ΄",
"μ’
μ¬μ κ΄μ°° λ°μ": bool(data.get("staff_observation", "").strip()) or has_keywords_or_needs or data["counseling_type"] == "μ’
μ¬μμλ΄",
"νμμ‘°μΉ": bool(data.get("action_plan", "").strip()),
"κΈμ¬μ 곡 λ°μ": "κΈμ¬" in data.get("counseling_content", "") or "κ³ν" in data.get("action_plan", ""),
"μ§μλ°©λ² κ΅¬μ²΄ν": bool(data.get("support_methods", "").strip()) or "κ°μΈλ³ μ΄μ©κ³ν μ§μλ°©λ²" in data.get("counseling_content", ""),
}
score = int(sum(required.values()) / len(required) * 100)
missing = [k for k, v in required.items() if not v]
if score >= 90:
result = "νκ° μ ν©λκ° λμ΅λλ€. μλ΄μ¬μ , μνλ³ν, 보νΈμ μ견, νμμ‘°μΉκ° μ λ°μλμμ΅λλ€."
elif score >= 75:
result = "λμ²΄λ‘ μ ν©νλ μΌλΆ 보μμ΄ νμν©λλ€. λΆμ‘± νλͺ©μ μΆκ°νλ©΄ νκ° λμλ ₯μ΄ λμμ§λλ€."
else:
result = "보μμ΄ νμν©λλ€. μλ΄μ¬μ , μνλ³ν, 보νΈμ μ견, νμμ‘°μΉλ₯Ό λ ꡬ체μ μΌλ‘ μ
λ ₯ν΄ μ£ΌμΈμ."
return score, missing, result
def style_range(ws, cell_range, fill=None, font=None, alignment=None):
thin = Side(style="thin", color="000000")
border = Border(left=thin, right=thin, top=thin, bottom=thin)
cells = ws[cell_range]
if not isinstance(cells, tuple):
cells = ((cells,),)
for row in cells:
for cell in row:
cell.border = border
if fill:
cell.fill = fill
if font:
cell.font = font
if alignment:
cell.alignment = alignment
def split_text_by_length(text, max_len=650):
text = str(text or "").strip()
if not text:
return [""]
chunks = []
while len(text) > max_len:
cut = text.rfind("\n", 0, max_len)
if cut == -1:
cut = text.rfind(". ", 0, max_len)
if cut == -1:
cut = text.rfind("λ€.", 0, max_len)
if cut != -1:
cut += 2
if cut == -1 or cut < int(max_len * 0.45):
cut = max_len
chunks.append(text[:cut].strip())
text = text[cut:].strip()
if text:
chunks.append(text)
return chunks or [""]
def generate_excel(data):
wb = Workbook()
ws = wb.active
ws.title = "μλ΄μΌμ§"
ws.page_setup.paperSize = ws.PAPERSIZE_A4
ws.page_setup.orientation = "portrait"
ws.page_setup.fitToWidth = 1
ws.page_setup.fitToHeight = 0
ws.sheet_properties.pageSetUpPr.fitToPage = True
ws.page_margins.left = 0.3
ws.page_margins.right = 0.3
ws.page_margins.top = 0.4
ws.page_margins.bottom = 0.4
ws.sheet_view.showGridLines = False
title_fill = PatternFill("solid", fgColor="E2F0D9")
header_fill = PatternFill("solid", fgColor="F2F2F2")
for col in range(1, 10):
ws.column_dimensions[get_column_letter(col)].width = 11.5
ws.merge_cells("A1:G2")
ws.cell(1, 1).value = "μ λ΄ μΌ μ§"
ws.cell(1, 1).font = Font(name="Malgun Gothic", size=24, bold=True)
ws.cell(1, 1).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
style_range(ws, "A1:G2", fill=title_fill)
ws.cell(1, 8).value = "λ΄λΉμ"
ws.cell(1, 9).value = "μμ€μ₯"
ws.cell(2, 8).value = ""
ws.cell(2, 9).value = ""
for r in [1, 2]:
for c in [8, 9]:
cell = ws.cell(r, c)
cell.alignment = Alignment(horizontal="center", vertical="center")
cell.font = Font(name="Malgun Gothic", size=11, bold=(r == 1))
if r == 1:
cell.fill = header_fill
style_range(ws, f"{get_column_letter(c)}{r}:{get_column_letter(c)}{r}")
ws.row_dimensions[1].height = 28
ws.row_dimensions[2].height = 45
def write_info_row(row, a, b, c_val, d, e, f):
ws.cell(row, 1).value = a
ws.merge_cells(start_row=row, start_column=2, end_row=row, end_column=3)
ws.cell(row, 2).value = b
ws.cell(row, 4).value = c_val
ws.merge_cells(start_row=row, start_column=5, end_row=row, end_column=6)
ws.cell(row, 5).value = d
ws.cell(row, 7).value = e
ws.merge_cells(start_row=row, start_column=8, end_row=row, end_column=9)
ws.cell(row, 8).value = f
for col in [1, 4, 7]:
ws.cell(row, col).fill = header_fill
ws.cell(row, col).font = Font(name="Malgun Gothic", size=11, bold=True)
ws.cell(row, col).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
for col in [2, 5, 8]:
ws.cell(row, col).font = Font(name="Malgun Gothic", size=11)
ws.cell(row, col).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
style_range(ws, f"A{row}:I{row}")
ws.row_dimensions[row].height = 30
r = 3
write_info_row(r, "κΈ°κ΄λͺ
", data["institution_name"], "κΈ°κ΄μ ν", data["institution_type"], "μλ΄λΆλ₯", data["counseling_type"]); r += 1
write_info_row(r, "μκΈμ", data["masked_name"], "μ₯κΈ°μμλ±κΈ", data["care_grade"], "μλ΄μΌμ", data["counseling_date"]); r += 1
write_info_row(r, "μλ΄μ", data["counselor"], "μλ΄μ¬μ ", data["counseling_reason"], "νκ°μ ν©λ", f"{data['evaluation_score']}μ "); r += 1
def estimate_body_rows(text, min_rows=4):
text = str(text or "")
visual_lines = 0
for line in text.split("\n") or [""]:
visual_lines += max(1, (len(line) // 62) + 1)
return max(min_rows, min(24, visual_lines + 2))
def write_text_section(row, label, content_val, min_rows=4):
pages = split_text_by_length(content_val, 650)
for page_idx, page_text in enumerate(pages, start=1):
display_label = f"{label} ({page_idx}/{len(pages)}νμ΄μ§)" if len(pages) > 1 else label
ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=9)
ws.cell(row, 1).value = display_label
ws.cell(row, 1).font = Font(name="Malgun Gothic", size=11, bold=True)
ws.cell(row, 1).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
ws.cell(row, 1).fill = header_fill
style_range(ws, f"A{row}:I{row}", fill=header_fill)
ws.row_dimensions[row].height = 24
body_start = row + 1
body_rows = estimate_body_rows(page_text, min_rows)
body_end = body_start + body_rows - 1
ws.merge_cells(start_row=body_start, start_column=1, end_row=body_end, end_column=9)
ws.cell(body_start, 1).value = page_text
ws.cell(body_start, 1).font = Font(name="Malgun Gothic", size=10.5)
ws.cell(body_start, 1).alignment = Alignment(horizontal="left", vertical="top", wrap_text=True)
for body_row in range(body_start, body_end + 1):
ws.row_dimensions[body_row].height = 19
style_range(ws, f"A{body_start}:I{body_end}")
row = body_end + 1
if page_idx < len(pages):
ws.row_breaks.append(Break(id=row))
return row
detail_rows = [
("μλ΄λ΄μ©", data["counseling_content"], 8),
("κ°μΈλ³ μ΄μ©κ³ν μ§μλ°©λ²", data.get("support_methods", ""), 6),
("μ‘°μΉκ³ν", data["action_plan"], 5),
("νκ° μ ν©λ μ κ²", data["compliance_result"], 3),
("λΆμ‘±ν νλͺ©", data["missing_items"], 3),
]
for label, content_val, min_rows in detail_rows:
r = write_text_section(r, label, content_val, min_rows)
# κ³΅ν΅ μλ΄λ¬Έκ΅¬ μΆκ°
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=9)
ws.cell(r, 1).value = "β» μμ±λ λ¬Έμλ κ·Έλλ‘ μ¬μ©νμ
λ λλ©°, κΈ°κ΄μ μ΄μλ°©μΉ¨, μκΈμ μν, 보νΈμ μ견 λ° νκ°κΈ°μ€μ λ°λΌ μΆκ° 보μμ¬νμ μΌλ§λ μ§ μμ κ°λ₯ν©λλ€. κΈ°κ΄μ μν©μ λ§κ² νμ©νμκΈ° λ°λλλ€."
ws.cell(r, 1).font = Font(name="Malgun Gothic", size=8.5, color="595959", italic=True)
ws.cell(r, 1).alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
style_range(ws, f"A{r}:I{r}")
ws.row_dimensions[r].height = 36
r += 1
ws.print_area = f"A1:I{r - 1}"
output = BytesIO()
wb.save(output)
output.seek(0)
return output
clients_df = load_clients()
# Sidebar config
st.sidebar.subheader("AI μ€μ ")
use_lm_studio = st.sidebar.toggle("LM Studio / Qwen μ¬μ©", value=False)
st.session_state["use_lm_studio"] = use_lm_studio
if DB_PATH and os.path.exists(DB_PATH):
st.sidebar.success("Client Center DB μ°κ²°λ¨")
st.sidebar.caption(DB_PATH)
else:
st.sidebar.warning("Client Center DBλ₯Ό μ°Ύμ§ λͺ»νμ΅λλ€. μ§μ μ
λ ₯ λͺ¨λλ‘ μλν©λλ€.")
st.subheader("1λ¨κ³. μκΈμ μ ν λ° κΈ°λ³Έμ 보")
mode = st.radio(
"μ
λ ₯ λ°©μ μ ν",
["Client Centerμμ μκΈμ μ ν", "μ§μ μ
λ ₯"],
horizontal=True,
)
selected_client = None
if mode == "Client Centerμμ μκΈμ μ ν" and not clients_df.empty:
options = clients_df.apply(
lambda x: f"{x['id']}λ² - {x['masked_name']} / {x['μ₯κΈ°μμλ±κΈ']} / {x['κΈ°κ΄μ ν']} / {x['status']}",
axis=1,
).tolist()
selected_option = st.selectbox("μκΈμ μ ν", options)
selected_id = int(selected_option.split("λ²")[0])
selected_client = clients_df[clients_df["id"] == selected_id].iloc[0]
elif mode == "Client Centerμμ μκΈμ μ ν" and clients_df.empty:
st.warning("λ±λ‘λ μκΈμ μ λ³΄κ° μκ±°λ DBλ₯Ό μ°Ύμ§ λͺ»νμ΅λλ€. μ§μ μ
λ ₯ λͺ¨λλ‘ μ§ννμΈμ.")
# Prefill variables
if selected_client is not None:
default_name = selected_client.get("name", "νκΈΈλ")
default_masked_name = selected_client.get("masked_name", "νβλ")
default_grade = str(selected_client.get("μ₯κΈ°μμλ±κΈ", "3λ±κΈ")).strip()
if default_grade in ["1", "2", "3", "4", "5"]:
default_grade = default_grade + "λ±κΈ"
default_inst_type = selected_client.get("κΈ°κ΄μ ν", "λ°©λ¬Έμμ")
default_inst_name = selected_client.get("κΈ°κ΄λͺ
", "")
default_recognition_number = selected_client.get("μΈμ λ²νΈ", "")
default_recognition_start = selected_client.get("μΈμ κΈ°κ° μμμΌ", "")
default_recognition_end = selected_client.get("μΈμ κΈ°κ° μ’
λ£μΌ", "")
default_main_condition = selected_client.get("main_condition", "")
# Load status inputs if available
default_cognitive = selected_client.get("cognitive_status", "")
default_mobility = selected_client.get("mobility_status", "")
default_diet = selected_client.get("diet_status", "")
default_toileting = selected_client.get("toileting_status", "")
default_notes = selected_client.get("notes", "")
else:
default_name = "νκΈΈλ"
default_masked_name = "νβλ"
default_grade = "3λ±κΈ"
default_inst_type = "λ°©λ¬Έμμ"
default_inst_name = "μ₯κΈ°μμκΈ°κ΄"
default_recognition_number = ""
default_recognition_start = ""
default_recognition_end = ""
default_main_condition = ""
default_cognitive = ""
default_mobility = ""
default_diet = ""
default_toileting = ""
default_notes = ""
# Show input columns
col1, col2, col3 = st.columns(3)
institution_name = col1.text_input("κΈ°κ΄λͺ
", default_inst_name)
institution_type = col2.selectbox(
"κΈ°κ΄μ ν",
list(INSTITUTION_GUIDES.keys()),
index=list(INSTITUTION_GUIDES.keys()).index(default_inst_type) if default_inst_type in INSTITUTION_GUIDES else 0
)
counseling_type = col3.radio("μλ΄λΆλ₯", ["μκΈμμλ΄", "보νΈμμλ΄", "μ’
μ¬μμλ΄", "κΈ°νμλ΄"], horizontal=True)
col4, col5, col6, col7 = st.columns(4)
name = col4.text_input("μκΈμ μ΄λ¦", default_name)
masked_name = col5.text_input("κ°λͺ
νκΈ°", default_masked_name)
care_grade = col6.selectbox(
"μ₯κΈ°μμλ±κΈ",
["1λ±κΈ", "2λ±κΈ", "3λ±κΈ", "4λ±κΈ", "5λ±κΈ", "μΈμ§μ§μλ±κΈ", "λ―Έμ
λ ₯"],
index=["1λ±κΈ", "2λ±κΈ", "3λ±κΈ", "4λ±κΈ", "5λ±κΈ", "μΈμ§μ§μλ±κΈ", "λ―Έμ
λ ₯"].index(default_grade) if default_grade in ["1λ±κΈ", "2λ±κΈ", "3λ±κΈ", "4λ±κΈ", "5λ±κΈ", "μΈμ§μ§μλ±κΈ", "λ―Έμ
λ ₯"] else 2
)
counseling_date = col7.date_input("μλ΄μΌμ", value=date.today())
col8, col9, col10 = st.columns(3)
counselor = col8.text_input("μλ΄μ", "μ¬ν볡μ§μ¬")
counseling_reason = col9.selectbox(
"μλ΄ μ¬μ (trigger)",
[
"μνλ³νκΈ°λ‘μ§ μλ΄νμ κΈ°λ‘",
"μκΈμ μμ²",
"보νΈμ μμ²",
"μμ보νΈμ¬/μ’
μ¬μ μμ²",
"μ¬ν볡μ§μ¬ νλ¨",
"μμ€μ₯/κ΄λ¦¬μ±
μμ νλ¨",
"μ¬λ‘κ΄λ¦¬ κ²ν νμ",
"μꡬμ¬μ μ¬κ²ν νμ",
"κΈμ¬μ 곡κ³ν λ³κ²½ νμ",
"κΈ°ν μ§μ μ
λ ₯",
]
)
main_condition = col10.text_input("μ£Όμ μ§ν/μν", default_main_condition)
# Recognition numbers and dates (Section 3-1)
col_rec1, col_rec2, col_rec3 = st.columns(3)
recognition_number = col_rec1.text_input("μΈμ λ²νΈ", default_recognition_number)
recognition_start_date = col_rec2.text_input("μΈμ κΈ°κ° μμμΌ", default_recognition_start, placeholder="μ: 2025-03-03")
recognition_end_date = col_rec3.text_input("μΈμ κΈ°κ° μ’
λ£μΌ", default_recognition_end, placeholder="μ: 2027-03-02")
st.subheader("2λ¨κ³. μλ΄ λͺ©μ λ° κ΄λ ¨ νλͺ© μ ν")
counseling_purpose = st.radio(
"μ΄λ² μλ΄μ λͺ©μ ",
[
"μΌλ° μλ΄",
"μ¬λ‘κ΄λ¦¬ κ²ν μλ΄",
"μꡬμ¬μ μ¬κ²ν μλ΄",
"κΈμ¬μ 곡κ³ν λ³κ²½ μλ΄",
"λ³΅ν© μλ΄",
],
horizontal=True
)
selected_items_list = []
# Dynamic multiselect elements based on purpose
if counseling_purpose in ["μ¬λ‘κ΄λ¦¬ κ²ν μλ΄", "λ³΅ν© μλ΄"]:
case_items = st.multiselect(
"μ¬λ‘κ΄λ¦¬ κ²ν νλͺ©",
[
"μ΄λλ₯λ ₯ μ ν",
"μ컀 μ¬μ©μμ ν μ²΄μ΄ μ¬μ©μΌλ‘ λ³κ²½",
"λ°λ³΅ λμ λλ λμμν μ¦κ°",
"νμ§κ·Όλ ₯ μ½ν",
"μ
μ λλ ν΄μ ν κ΄λ¦¬ νμ",
"μ₯κΈ°μμλ±κΈ λ³κ²½ μ μ² κ²ν ",
"보νΈμ λλ΄ λΆλ΄ μ¦κ°",
"μλΉμ€ μκ° λλ νμ μ‘°μ νμ",
"보쑰기ꡬ λ³κ²½ νμ",
"μΉλ§€νλ λλ μΈμ§κΈ°λ₯ λ³ν",
"μμ¬λ κ°μ λλ μμλΆκ· ν",
"λ°°λ³Β·λ°°λ¨ λ¬Έμ μ¦κ°",
"μ μλΆμ, μ°μΈ, μλ©΄λ¬Έμ ",
"κ°μ‘±Β·μ£Όκ±°Β·κ²½μ μ λ¬Έμ ",
"μ§μμ¬ν μμ μ°κ³ νμ",
"κΈ°ν μ§μ μ
λ ₯",
]
)
selected_items_list.extend(case_items)
if counseling_purpose in ["μꡬμ¬μ μ¬κ²ν μλ΄", "λ³΅ν© μλ΄"]:
needs_items = st.multiselect(
"μꡬμ¬μ μ¬κ²ν νλͺ©",
[
"ADL λ³ν",
"IADL λ³ν",
"보ν λ° μ΄λλ₯λ ₯ λ³ν",
"보쑰기ꡬ μ¬μ© λ³ν",
"μΈμ§κΈ°λ₯ λ³ν",
"μμ¬μν΅ λ³ν",
"μμ¬ λ° μμμν λ³ν",
"λ°°λ³Β·λ°°λ¨ μν λ³ν",
"λμμν μ¦κ°",
"μμ°½μν μ¦κ°",
"ν΅μ¦ μ¦κ°",
"μλ©΄μν λ³ν",
"μ μμν λ³ν",
"보νΈμ λλ κ°μ‘±νκ²½ λ³ν",
"μ£Όκ±°νκ²½ λ³ν",
"μ
μΒ·ν΄μΒ·μ§λ³μν λ³ν",
"κΈ°ν μ§μ μ
λ ₯",
]
)
selected_items_list.extend(needs_items)
if counseling_purpose in ["κΈμ¬μ 곡κ³ν λ³κ²½ μλ΄", "λ³΅ν© μλ΄"]:
careplan_items = st.multiselect(
"κΈμ¬μ 곡κ³ν λ³κ²½ νλͺ©",
[
"μλΉμ€ μκ° λ³κ²½ νμ",
"μλΉμ€ νμ λ³κ²½ νμ",
"μλΉμ€ λ΄μ© λ³κ²½ νμ",
"μ 체νλμ§μ λ³κ²½",
"μΈμ§νλμ§μ λ³κ²½",
"μ μμ§μ λ³κ²½",
"μΌμμνμ§μ λ³κ²½",
"κ°μ‘±μλ΄ λλ 보νΈμ μ§μ νμ",
"λμμλ°© κ³ν λ°μ",
"μμ°½μλ°© κ³ν λ°μ",
"μμ¬Β·μμκ΄λ¦¬ κ³ν λ°μ",
"λ°°λ³Β·λ°°λ¨κ΄λ¦¬ κ³ν λ°μ",
"보쑰기ꡬ λ³κ²½ λ°μ",
"λ±κΈλ³κ²½ μ μ² κ²ν ",
"λͺ©ν μμ νμ",
"κ³΅λ¨ μ
λ ₯ μ°Έκ³ μλ£ νμ",
"κΈ°ν μ§μ μ
λ ₯",
]
)
selected_items_list.extend(careplan_items)
st.subheader("3λ¨κ³. μμΈ μ 보 μ
λ ₯")
col_text1, col_text2 = st.columns(2)
with col_text1:
keywords = st.text_area(
"ν΅μ¬ λ¨μ΄ λλ μ§§μ λ¬Έμ₯ μ
λ ₯",
placeholder="μ: μ컀βν 체μ΄, λ±κΈλ³κ²½, 보νΈμ λΆλ΄",
help="μ
λ ₯ν λ¨μ΄μ μ νν νλͺ©λ§ λ¬Έμμ λ°μλ©λλ€. μ
λ ₯νμ§ μμ λ΄μ©μ μμ μμ±λμ§ μμ΅λλ€."
)
st.caption("β» μ¬λ‘κ΄λ¦¬, μꡬμ¬μ μ¬κ²ν , κΈμ¬μ 곡κ³ν λ³κ²½μ΄ νμν κ²½μ°μλ κ΄λ ¨ νλͺ©μ μ ννκ³ μμ ν΅μ¬ λ¨μ΄λ₯Ό 2~3κ° μ
λ ₯ν΄ μ£ΌμΈμ.")
with col_text2:
additional_needs = st.text_area(
"μΆκ°μꡬ / μμ²μ¬ν",
placeholder="μ: μ¬λ‘κ΄λ¦¬ νμ - μ컀 μ¬μ©μμ ν μ²΄μ΄ μ¬μ©μΌλ‘ λ³κ²½λμ΄ μ΄λμ§μ μ¬κ²ν νμ",
help="μ
λ ₯λ λ΄μ©μ ν΄λΉ λ¬Έμ μμ± μ μ°Έκ³ μλ£λ‘ μ°λλ©λλ€."
)
st.caption("β» μ¬λ‘κ΄λ¦¬, μꡬμ¬μ μ¬κ²ν , κΈμ¬μ 곡κ³ν λ³κ²½μ΄ νμν κ²½μ°μλ μ΄κ³³μ ꡬ체μ μΈ μ¬μ λ₯Ό μ
λ ₯ν΄ μ£ΌμΈμ.")
# Optional/Basic input fields just in case they want to add them
with st.expander("π μΆκ° μν κ΄μ°° μ 보 μ
λ ₯ (κΈ°λ³Έ μν/Q&A μ
λ ₯ νΌμΉκΈ°)"):
input_mode = st.radio("μ
λ ₯ λ°©μ", ["κΈ°λ³Έ μ
λ ₯", "Q&A μ
λ ₯", "κΈ°λ³Έ+Q&A μ
λ ₯"], horizontal=True)
use_basic_input = input_mode in ["κΈ°λ³Έ μ
λ ₯", "κΈ°λ³Έ+Q&A μ
λ ₯"]
use_qna_input = input_mode in ["Q&A μ
λ ₯", "κΈ°λ³Έ+Q&A μ
λ ₯"]
cognitive_status = ""
mobility_status = ""
diet_status = ""
toileting_status = ""
client_status_change = ""
guardian_opinion = ""
staff_observation = ""
custom_need = ""
qna_answers = {}
if use_basic_input:
c1, c2 = st.columns(2)
cognitive_status = c1.text_area("μΈμ§μν", value=default_cognitive, placeholder="μ: λ°λ³΅μ§λ¬Έ μ¦κ°, μκ°/μ₯μ νΌλ λ±", height=80)
mobility_status = c2.text_area("μ΄λμν", value=default_mobility, placeholder="μ: 보ν λΆμμ , λΆμΆ νμ λ±", height=80)
c3, c4 = st.columns(2)
diet_status = c3.text_area("μμ¬μν", value=default_diet, placeholder="μ: μμ¬λ κ°μ, μ¬λ , μ°νκ³€λ λ±", height=80)
toileting_status = c4.text_area("λ°°λ³/λ°°λ¨μν", value=default_toileting, placeholder="μ: λ³λΉ, μ€κΈ, κΈ°μ κ· μ¬μ© μ¦κ° λ±", height=80)
c5, c6 = st.columns(2)
client_status_change = c5.text_area("μκΈμ μνλ³ν", placeholder="μ: μ΅κ·Ό μ΄μ§λΌμ¦μ΄ μμ΄ λ³΄νμ κΊΌλ¦Ό, μμ¬λ κ°μ, μλ©΄ λ³ν λ±", height=100)
guardian_opinion = c6.text_area("보νΈμ μ견", placeholder="μ: μ΄μ§λΌμ΄ μ¬ν΄μ§λ©΄ λ³μμ§λ£λ₯Ό μν΄ λ³μλν μμ²", height=100)
c7, c8 = st.columns(2)
staff_observation = c7.text_area("μ’
μ¬μ κ΄μ°°μ¬ν", placeholder="μ: μ΄λ μ λΆμΆ μ 곡, μμ/보νμν κ΄μ°°, μμ¬λ νμΈ λ±", height=100)
custom_need = c8.text_area("μΆκ° μꡬ / μμ²μ¬ν", value=default_notes, placeholder="μ: μ΄λ λμ κ°ν, νμ μ¬νμΈ, 보νΈμμκ² μ¦μλ³ν μλ΄ λ±", height=100)
if use_qna_input:
st.info(f"{counseling_type}μ λ§λ μ§λ¬Έμ΄ μλ νμλ©λλ€. λ΅λ³ λ΄μ©μ μλ΄λ΄μ© μ λμ νμ©λ©λλ€.")
questions = QA_RULES.get(counseling_type, QA_RULES["κΈ°νμλ΄"])
for idx, question in enumerate(questions, 1):
qna_answers[question] = st.text_area(f"Q{idx}. {question}", key=f"qna_{counseling_type}_{idx}", height=80)
st.subheader("4λ¨κ³. μ°λ μ€μ λ° μμ±")
col_chk1, col_chk2, col_chk3, col_chk4, col_chk5 = st.columns(5)
case_needed = col_chk1.checkbox("μ¬λ‘κ΄λ¦¬ νμ", value=(counseling_purpose == "μ¬λ‘κ΄λ¦¬ κ²ν μλ΄"))
needs_assessment_needed = col_chk2.checkbox("μꡬμ¬μ μ¬κ²ν νμ", value=(counseling_purpose == "μꡬμ¬μ μ¬κ²ν μλ΄"))
careplan_change_needed = col_chk3.checkbox("κΈμ¬μ 곡κ³ν λ³κ²½ νμ", value=(counseling_purpose == "κΈμ¬μ 곡κ³ν λ³κ²½ μλ΄"))
care_grade_review_needed = col_chk4.checkbox("λ±κΈλ³κ²½ μ μ² κ²ν νμ")
followup_counseling_needed = col_chk5.checkbox("보νΈμ μΆκ° μλ΄ νμ")
if st.button("π μλ΄μΌμ§ λΉ λ₯΄κ² μμ±", use_container_width=True):
data = {
"client_id": int(selected_client["id"]) if selected_client is not None and "id" in selected_client else 0,
"institution_name": institution_name,
"institution_type": institution_type,
"counseling_type": counseling_type,
"name": name,
"masked_name": masked_name,
"care_grade": care_grade,
"counseling_date": str(counseling_date),
"counselor": counselor,
"counseling_reason": counseling_reason, # trigger
"counseling_purpose": counseling_purpose,
"selected_items_list": selected_items_list,
"keywords": keywords,
"additional_needs": additional_needs,
"case_needed": case_needed,
"needs_assessment_needed": needs_assessment_needed,
"careplan_change_needed": careplan_change_needed,
"care_grade_review_needed": care_grade_review_needed,
"followup_counseling_needed": followup_counseling_needed,
"main_condition": main_condition,
# Optional basic status fields
"cognitive_status": cognitive_status,
"mobility_status": mobility_status,
"diet_status": diet_status,
"toileting_status": toileting_status,
"client_status_change": client_status_change,
"guardian_opinion": guardian_opinion,
"staff_observation": staff_observation,
"custom_need": custom_need,
"qna_answers": qna_answers,
}
content, action, support_methods = build_counseling_text(data)
data["counseling_content"] = content
data["action_plan"] = action
data["support_methods"] = support_methods
score, missing, result = evaluate_record(data)
data["evaluation_score"] = score
data["missing_items"] = ", ".join(missing) if missing else "λΆμ‘± νλͺ© μμ"
data["compliance_result"] = result
st.session_state["counseling_data"] = data
st.session_state.pop("counseling_excel", None)
if "counseling_data" in st.session_state:
data = st.session_state["counseling_data"]
st.markdown("---")
st.subheader("5λ¨κ³. μμ± κ²°κ³Ό νμΈ/μμ ")
data["counseling_content"] = st.text_area("μλ΄ μ¬μ λ° λ΄μ©", value=data["counseling_content"], height=280)
data["action_plan"] = st.text_area("μ‘°μΉκ³ν", value=data["action_plan"], height=130)
data["support_methods"] = st.text_area("κ°μΈλ³ μ΄μ©κ³ν μ§μλ°©λ²", value=data.get("support_methods", ""), height=150)
# Recalculate score on modification
score, missing, result = evaluate_record(data)
data["evaluation_score"] = score
data["missing_items"] = ", ".join(missing) if missing else "λΆμ‘± νλͺ© μμ"
data["compliance_result"] = result
analysis = analyze_counseling_answers(data)
m1, m2, m3 = st.columns(3)
m1.metric("νκ° μ ν©λ", f"{score}μ ")
m2.metric("λΆμ‘± νλͺ©", data["missing_items"])
m3.metric("μΆλ ₯", "μμ
μ ν μμ±")
link1, link2, link3 = st.columns(3)
link1.metric("μ¬λ‘κ΄λ¦¬ μ°κ³", "νμ" if data["case_needed"] else "ν΄λΉ μμ")
link2.metric("μꡬμ¬μ μ¬κ²ν ", "νμ" if data["needs_assessment_needed"] else "ν΄λΉ μμ")
link3.metric("κΈμ¬κ³ν λ°μ", "νμ" if data["careplan_change_needed"] else "μ μ§")
if score >= 90:
st.markdown(f'{result}
', unsafe_allow_html=True)
else:
st.markdown(f'{result}
', unsafe_allow_html=True)
col_save1, col_save2 = st.columns(2)
with col_save1:
if st.button("πΎ DBμ μλ΄κΈ°λ‘ μ μ₯νκΈ°", use_container_width=True):
success = save_counseling_record(data)
if success:
st.success("λνλ! μλ΄μΌμ§ κΈ°λ‘μ΄ μ±κ³΅μ μΌλ‘ DBμ μ μ₯λμμ΅λλ€! π")
else:
st.error("DB μ μ₯ μ€ν¨. λ‘κ·Έλ₯Ό νμΈνμΈμ.")
with col_save2:
if st.button("π¦ μμ
νμΌ μ€λΉνκΈ°", use_container_width=True):
st.session_state["counseling_excel"] = generate_excel(data)
st.success("μμ
νμΌμ΄ μ€λΉλμμ΅λλ€.")
if "counseling_excel" in st.session_state:
filename = f"μλ΄μΌμ§_{data['masked_name']}_{data['counseling_type']}_{datetime.now().strftime('%Y%m%d')}.xlsx"
st.download_button("π₯ μλ΄μΌμ§ μμ
λ€μ΄λ‘λ", data=st.session_state["counseling_excel"], file_name=filename, mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", use_container_width=True)
st.caption("ν΅ν© μ°λ μ€λ§νΈ λ²μ : Client Center DB λ° Excel λ°μ΄ν°μ μνΈμμ©νμ¬ μλ΄μΌμ§λ₯Ό μμ±ν©λλ€.")