devorah-ai-2016 commited on
Commit
1023020
ยท
verified ยท
1 Parent(s): 9096301

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +774 -0
app.py ADDED
@@ -0,0 +1,774 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sqlite3
3
+ import random
4
+ import tempfile
5
+ from io import BytesIO
6
+ from datetime import date, datetime
7
+
8
+ import pandas as pd
9
+ import streamlit as st
10
+ from openpyxl import Workbook
11
+ from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
12
+ from openpyxl.utils import get_column_letter
13
+ from openpyxl.drawing.image import Image as XLImage
14
+ from openpyxl.worksheet.pagebreak import Break # ํŽ˜์ด์ง€ ๋‚˜๋ˆ„๊ธฐ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
15
+
16
+
17
+ # =========================================================
18
+ # ๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ ๊ต์œก์ผ์ง€ ํ†ตํ•ฉ์—ฐ๋™ ์ƒ์„ฑ๊ธฐ
19
+ # - Client Center DB์™€ ๊ฐ™์€ clients.db ์‚ฌ์šฉ
20
+ # - ๊ธฐ๊ด€์ •๋ณด/์ฐธ์„์ž(์ง์ข…๋ณ„ ์ž…๋ ฅ)/๊ฐ•์‚ฌ/๊ต์œก์ž๋ฃŒ ์ฒจ๋ถ€/๊ต์œก๊ฒฐ๊ณผ
21
+ # - ํ‰๊ฐ€ ์ ํ•ฉ๋„, ํ›„์†์กฐ์น˜๋Š” ์ž์ฒด ๋ณด๊ด€์šฉ (์—‘์…€ ์ถœ๋ ฅ ์ œ์™ธ)
22
+ # - ๊ฒฐ์žฌ๋ผ์ธ ๋‹ด๋‹น์ž/์‹œ์„ค์žฅ 2์นธ, ์ง€์ €๋ถ„ํ•œ ๋นˆ์นธ ์—†์ด ์ถœ๋ ฅ
23
+ # - ๊ต์œก๋‚ด์šฉ๊ณผ ๊ต์œก๊ฒฐ๊ณผ ํŽ˜์ด์ง€ ๋ถ„ํ• 
24
+ # =========================================================
25
+
26
+ st.set_page_config(
27
+ page_title="๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ ๊ต์œก์ผ์ง€",
28
+ page_icon="๐Ÿ“˜",
29
+ layout="wide"
30
+ )
31
+
32
+ CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
33
+
34
+ DB_CANDIDATES = [
35
+ os.path.join(CURRENT_DIR, "data", "clients.db"),
36
+ os.path.join(CURRENT_DIR, "..", "Client Center", "data", "clients.db"),
37
+ os.path.join(CURRENT_DIR, "..", "ClientCenter", "data", "clients.db"),
38
+ os.path.join(CURRENT_DIR, "..", "Client_Center", "data", "clients.db"),
39
+ ]
40
+
41
+ DB_PATH = None
42
+ for p in DB_CANDIDATES:
43
+ if os.path.exists(p):
44
+ DB_PATH = p
45
+ break
46
+
47
+ if DB_PATH is None:
48
+ os.makedirs(os.path.join(CURRENT_DIR, "data"), exist_ok=True)
49
+ DB_PATH = os.path.join(CURRENT_DIR, "data", "clients.db")
50
+
51
+
52
+ # -----------------------------
53
+ # ๋””์ž์ธ
54
+ # -----------------------------
55
+ st.markdown("""
56
+ <style>
57
+ .block-container { padding-top: 2rem; }
58
+ .hero {
59
+ background: linear-gradient(135deg, #7C2D12, #EA580C);
60
+ padding: 30px;
61
+ border-radius: 22px;
62
+ text-align: center;
63
+ margin-bottom: 22px;
64
+ }
65
+ .hero h1 { color: white; margin: 0; font-size: 36px; }
66
+ .hero p { color: white; margin-top: 10px; font-size: 18px; }
67
+ .notice {
68
+ background: #fff8e1;
69
+ border-left: 7px solid #f9a825;
70
+ padding: 15px;
71
+ border-radius: 12px;
72
+ margin-bottom: 18px;
73
+ }
74
+ .okbox {
75
+ background: #e8f5e9;
76
+ border-left: 7px solid #43a047;
77
+ padding: 14px;
78
+ border-radius: 12px;
79
+ margin-bottom: 14px;
80
+ }
81
+ .warnbox {
82
+ background: #fff3e0;
83
+ border-left: 7px solid #fb8c00;
84
+ padding: 14px;
85
+ border-radius: 12px;
86
+ margin-bottom: 14px;
87
+ }
88
+ </style>
89
+ """, unsafe_allow_html=True)
90
+
91
+ st.markdown("""
92
+ <div class="hero">
93
+ <h1>๐Ÿ“˜ ๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ ๊ต์œก์ผ์ง€ ์ƒ์„ฑ๊ธฐ</h1>
94
+ <p>์ข…์‚ฌ์ž ๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ ๊ต์œก์ผ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ง์ข…๋ณ„ ์ฐธ์„ ๋ช…๋‹จ๊ณผ ์ด์ˆ˜๊ธฐ๋ก์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.</p>
95
+ </div>
96
+ """, unsafe_allow_html=True)
97
+
98
+ st.markdown("""
99
+ <div class="notice">
100
+ <strong>ํ•„์ˆ˜ ๊ตฌ์กฐ:</strong> ๊ต์œก๋ช…, ๊ต์œก์ผ์‹œ, ์žฅ์†Œ, ๊ฐ•์‚ฌ๋ช…, ์ง์ข…๋ณ„ ์ฐธ์„์ž ๋ช…๋‹จ, ๊ต์œก๋ชฉ์ , ๊ต์œก๋‚ด์šฉ, ๊ต์œก์ž๋ฃŒ ์ฒจ๋ถ€, ๊ต์œก๊ฒฐ๊ณผ ๋“ฑ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
101
+ </div>
102
+ """, unsafe_allow_html=True)
103
+
104
+
105
+ # -----------------------------
106
+ # DB
107
+ # -----------------------------
108
+ def get_conn():
109
+ return sqlite3.connect(DB_PATH, check_same_thread=False)
110
+
111
+ def init_db():
112
+ conn = get_conn()
113
+ cur = conn.cursor()
114
+
115
+ cur.execute("""
116
+ CREATE TABLE IF NOT EXISTS education_records (
117
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
118
+ institution_name TEXT,
119
+ institution_type TEXT,
120
+ education_title TEXT,
121
+ guideline_items TEXT,
122
+ education_date TEXT,
123
+ education_time TEXT,
124
+ education_place TEXT,
125
+ instructor_name TEXT,
126
+ instructor_position TEXT,
127
+ attendees TEXT,
128
+ absent_staff TEXT,
129
+ education_method TEXT,
130
+ education_purpose TEXT,
131
+ education_content TEXT,
132
+ education_materials TEXT,
133
+ education_result TEXT,
134
+ followup_plan TEXT,
135
+ evaluation_score INTEGER,
136
+ missing_items TEXT,
137
+ compliance_result TEXT,
138
+ created_at TEXT
139
+ )
140
+ """)
141
+
142
+ conn.commit()
143
+ conn.close()
144
+
145
+ def save_education_record(data):
146
+ conn = get_conn()
147
+ cur = conn.cursor()
148
+ cur.execute("""
149
+ INSERT INTO education_records (
150
+ institution_name, institution_type, education_title, guideline_items,
151
+ education_date, education_time, education_place,
152
+ instructor_name, instructor_position, attendees, absent_staff,
153
+ education_method, education_purpose, education_content,
154
+ education_materials, education_result, followup_plan,
155
+ evaluation_score, missing_items, compliance_result, created_at
156
+ )
157
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
158
+ """, (
159
+ data["institution_name"], data["institution_type"], data["education_title"],
160
+ data["guideline_items"], data["education_date"], data["education_time"],
161
+ data["education_place"], data["instructor_name"], data["instructor_position"],
162
+ data["attendees"], data["absent_staff"], data["education_method"],
163
+ data["education_purpose"], data["education_content"], data["education_materials"],
164
+ data["education_result"], data["followup_plan"], data["evaluation_score"],
165
+ data["missing_items"], data["compliance_result"], datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
166
+ ))
167
+ conn.commit()
168
+ conn.close()
169
+
170
+ def load_education_records():
171
+ conn = get_conn()
172
+ try:
173
+ df = pd.read_sql_query("SELECT * FROM education_records ORDER BY id DESC", conn)
174
+ except Exception:
175
+ df = pd.DataFrame()
176
+ conn.close()
177
+ return df
178
+
179
+ init_db()
180
+
181
+
182
+ # -----------------------------
183
+ # ๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ
184
+ # -----------------------------
185
+ GUIDELINES = {
186
+ "01. ์ข…์‚ฌ์ž ์œค๋ฆฌ์ง€์นจ": {
187
+ "purpose": "์ข…์‚ฌ์ž์˜ ์ง์—…์œค๋ฆฌ์™€ ์ˆ˜๊ธ‰์ž ์กด์ค‘ ํƒœ๋„๋ฅผ ๊ฐ•ํ™”ํ•˜์—ฌ ์ „๋ฌธ์ ์ธ ๊ธ‰์—ฌ์ œ๊ณต์ด ์ด๋ฃจ์–ด์ง€๋„๋ก ํ•œ๋‹ค.",
188
+ "content": ["์ˆ˜๊ธ‰์ž ์ธ๊ฒฉ ์กด์ค‘๊ณผ ์‚ฌ์ƒํ™œ ๋ณดํ˜ธ", "๊ธ‰์—ฌ์ œ๊ณต ๋ฒ”์œ„ ์ค€์ˆ˜์™€ ๋ถ€๋‹น์š”๊ตฌ ๋Œ€์‘", "์ „๋ฌธ์ง ์ข…์‚ฌ์ž๋กœ์„œ์˜ ์ฑ…์ž„๊ณผ ๋ณด๊ณ ์ฒด๊ณ„", "์„œ๋น„์Šค ์ œ๊ณต ์ค‘ ๋น„๋ฐ€์œ ์ง€์™€ ๊ณต์ •ํ•œ ํƒœ๋„"],
189
+ },
190
+ "02. ๋…ธ์ธ์ธ๊ถŒ๋ณดํ˜ธ์ง€์นจ": {
191
+ "purpose": "์ˆ˜๊ธ‰์ž์˜ ๊ถŒ๋ฆฌ์™€ ์ž๊ธฐ๊ฒฐ์ •๊ถŒ์„ ์กด์ค‘ํ•˜๊ณ  ์ธ๊ถŒ์นจํ•ด๋ฅผ ์˜ˆ๋ฐฉํ•œ๋‹ค.",
192
+ "content": ["๋…ธ์ธ ์ธ๊ถŒ์˜ ๊ธฐ๋ณธ ์›์น™", "์‹ ์ฒด์ ยท์ •์„œ์ ยท์–ธ์–ด์  ์ธ๊ถŒ์นจํ•ด ์˜ˆ๋ฐฉ", "์ˆ˜๊ธ‰์ž์˜ ์˜์‚ฌํ‘œํ˜„๊ณผ ์„ ํƒ๊ถŒ ์กด์ค‘", "์ธ๊ถŒ์นจํ•ด ๋ฐœ์ƒ ์‹œ ๋ณด๊ณ  ๋ฐ ์กฐ์น˜ ์ ˆ์ฐจ"],
193
+ },
194
+ "03. ๋…ธ์ธํ•™๋Œ€์˜ˆ๋ฐฉ์ง€์นจ": {
195
+ "purpose": "๋…ธ์ธํ•™๋Œ€ ์œ ํ˜•์„ ์ดํ•ดํ•˜๊ณ  ์˜์‹ฌ์‚ฌ๋ก€ ๋ฐœ๊ฒฌ ์‹œ ์ฆ‰์‹œ ๋ณด๊ณ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.",
196
+ "content": ["์‹ ์ฒด์ ยท์ •์„œ์ ยท๊ฒฝ์ œ์  ํ•™๋Œ€ ๋ฐ ๋ฐฉ์ž„์˜ ์œ ํ˜•", "ํ•™๋Œ€ ์˜์‹ฌ ์ง•ํ›„ ํ™•์ธ", "์‹ ๊ณ ์˜๋ฌด์™€ ์‹ ๊ณ ์ ˆ์ฐจ", "ํ•™๋Œ€ ์˜ˆ๋ฐฉ์„ ์œ„ํ•œ ์ข…์‚ฌ์ž ํ–‰๋™์ˆ˜์น™"],
197
+ },
198
+ "04. ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ์ง€์นจ": {
199
+ "purpose": "์ˆ˜๊ธ‰์ž์™€ ๋ณดํ˜ธ์ž์˜ ๊ฐœ์ธ์ •๋ณด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•˜๊ณ  ์œ ์ถœ์„ ์˜ˆ๋ฐฉํ•œ๋‹ค.",
200
+ "content": ["๊ฐœ์ธ์ •๋ณด ์ˆ˜์ง‘ยท์ด์šฉยท๋ณด๊ด€ ์›์น™", "๋ฌธ์„œ ๋ฐ ์ „์‚ฐ์ž๋ฃŒ ์ ‘๊ทผ๊ด€๋ฆฌ", "์‚ฌ์ง„ยท์˜์ƒ ์‚ฌ์šฉ ์‹œ ๋™์˜์™€ ๋ณดํ˜ธ์กฐ์น˜", "๊ฐœ์ธ์ •๋ณด ์œ ์ถœ ์‹œ ๋ณด๊ณ  ์ ˆ์ฐจ"],
201
+ },
202
+ "05. ์„ฑํญ๋ ฅ์˜ˆ๋ฐฉ ๋ฐ ๋Œ€์‘์ง€์นจ": {
203
+ "purpose": "์ˆ˜๊ธ‰์ž์™€ ์ข…์‚ฌ์ž์˜ ์„ฑ์  ์ž๊ธฐ๊ฒฐ์ •๊ถŒ์„ ๋ณดํ˜ธํ•˜๊ณ  ์„ฑํฌ๋กฑยท์„ฑํญ๋ ฅ์„ ์˜ˆ๋ฐฉํ•œ๋‹ค.",
204
+ "content": ["์„ฑํฌ๋กฑยท์„ฑํญ๋ ฅ์˜ ๊ฐœ๋…๊ณผ ์‚ฌ๋ก€", "๋ชฉ์š•ยท๋ฐฐ๋ณ€ยท์‹ ์ฒด์ง€์› ์‹œ ์œ ์˜์‚ฌํ•ญ", "๋ถˆ์พŒ๊ฐ ํ‘œํ˜„ ์‹œ ์ฆ‰์‹œ ์ค‘๋‹จ ๋ฐ ๋ณด๊ณ ", "ํ”ผํ•ด ๋ฐœ์ƒ ์‹œ ๋ณดํ˜ธ์กฐ์น˜์™€ ๋ณด๊ณ ์ฒด๊ณ„"],
205
+ },
206
+ "06. ์‘๊ธ‰์ƒํ™ฉ๋Œ€์‘์ง€์นจ": {
207
+ "purpose": "๋‚™์ƒ, ํ˜ธํก๊ณค๋ž€, ์˜์‹์ €ํ•˜ ๋“ฑ ์‘๊ธ‰์ƒํ™ฉ ๋ฐœ์ƒ ์‹œ ์‹ ์†ํ•˜๊ณ  ์ ์ ˆํ•˜๊ฒŒ ๋Œ€์‘ํ•œ๋‹ค.",
208
+ "content": ["์‘๊ธ‰์ƒํ™ฉ ์œ ํ˜•๊ณผ ์ดˆ๊ธฐ ํ™•์ธ", "119 ์‹ ๊ณ  ๋ฐ ๋ณดํ˜ธ์ž ์—ฐ๋ฝ ์ ˆ์ฐจ", "๊ธฐ๊ด€ ๋ณด๊ณ ์ฒด๊ณ„์™€ ๊ธฐ๋ก ๋ฐฉ๋ฒ•", "์‹ฌํ์†Œ์ƒ์ˆ  ๋ฐ ๊ธฐ๋ณธ ์‘๊ธ‰์ฒ˜์น˜ ์œ ์˜์‚ฌํ•ญ"],
209
+ },
210
+ "07. ๊ฐ์—ผ์˜ˆ๋ฐฉ ๋ฐ ๊ด€๋ฆฌ์ง€์นจ": {
211
+ "purpose": "๊ฐ์—ผ๋ณ‘ ์ „ํŒŒ๋ฅผ ์˜ˆ๋ฐฉํ•˜๊ณ  ์ˆ˜๊ธ‰์ž์™€ ์ข…์‚ฌ์ž์˜ ๊ฑด๊ฐ•์„ ๋ณดํ˜ธํ•œ๋‹ค.",
212
+ "content": ["์†์œ„์ƒ๊ณผ ๊ฐœ์ธ๋ณดํ˜ธ๊ตฌ ์‚ฌ์šฉ", "๋ฐœ์—ดยทํ˜ธํก๊ธฐ ์ฆ์ƒ ํ™•์ธ๊ณผ ๊ฒฉ๋ฆฌ ์•ˆ๋‚ด", "ํ™˜๊ฒฝ์†Œ๋… ๋ฐ ํ™˜๊ธฐ๊ด€๋ฆฌ", "๊ฐ์—ผ ์˜์‹ฌ ์‹œ ๋ณด๊ณ  ๋ฐ ์กฐ์น˜"],
213
+ },
214
+ "08. ์š•์ฐฝ์˜ˆ๋ฐฉ์ง€์นจ": {
215
+ "purpose": "์š•์ฐฝ ์œ„ํ—˜์š”์ธ์„ ์กฐ๊ธฐ์— ๋ฐœ๊ฒฌํ•˜๊ณ  ์ฒด์œ„๋ณ€๊ฒฝ๊ณผ ํ”ผ๋ถ€๊ด€์ฐฐ์„ ํ†ตํ•ด ์š•์ฐฝ์„ ์˜ˆ๋ฐฉํ•œ๋‹ค.",
216
+ "content": ["์š•์ฐฝ ์œ„ํ—˜์š”์ธ๊ณผ ํ˜ธ๋ฐœ๋ถ€์œ„", "์ฒด์œ„๋ณ€๊ฒฝ๊ณผ ์••๋ฐ• ์™„ํ™”", "ํ”ผ๋ถ€ ๋ฐœ์ ยท์ƒ์ฒ˜ ๊ด€์ฐฐ ๋ฐ ๋ณด๊ณ ", "์˜์–‘ยท์ˆ˜๋ถ„์„ญ์ทจ์™€ ์ฒญ๊ฒฐ๊ด€๋ฆฌ"],
217
+ },
218
+ "09. ๋‚™์ƒ์˜ˆ๋ฐฉ์ง€์นจ": {
219
+ "purpose": "์ˆ˜๊ธ‰์ž์˜ ๋‚™์ƒ ์œ„ํ—˜์„ ์ค„์ด๊ณ  ์•ˆ์ „ํ•œ ์ด๋™๊ณผ ์ƒํ™œํ™˜๊ฒฝ์„ ์œ ์ง€ํ•œ๋‹ค.",
220
+ "content": ["๋‚™์ƒ ์œ„ํ—˜์š”์ธ ํ™•์ธ", "๋ณดํ–‰๋ณด์กฐ์™€ ํ™”์žฅ์‹ค ์ด๋™ ์ง€์›", "ํ™˜๊ฒฝ์ •๋ฆฌ์™€ ๋ฏธ๋„๋Ÿผ ์˜ˆ๋ฐฉ", "๋‚™์ƒ ๋ฐœ์ƒ ์‹œ ์กฐ์น˜์™€ ๊ธฐ๋ก"],
221
+ },
222
+ "10. ์น˜๋งค์˜ˆ๋ฐฉ ๋ฐ ๊ด€๋ฆฌ์ง€์นจ": {
223
+ "purpose": "์ธ์ง€๊ธฐ๋Šฅ ์ €ํ•˜ ์ˆ˜๊ธ‰์ž์˜ ํŠน์„ฑ์„ ์ดํ•ดํ•˜๊ณ  ์•ˆ์ •์ ์ธ ๋Œ๋ด„์„ ์ œ๊ณตํ•œ๋‹ค.",
224
+ "content": ["์น˜๋งค ์ฆ์ƒ๊ณผ ํ–‰๋™์‹ฌ๋ฆฌ์ฆ์ƒ ์ดํ•ด", "๋ฐ˜๋ณต์„ค๋ช…๊ณผ ์•ˆ์ •์  ํ™˜๊ฒฝ ์ œ๊ณต", "์ธ์ง€ํ™œ๋™๊ณผ ์ผ์ƒ์ƒํ™œ ์ง€์›", "๋ฐฐํšŒยท๋ถˆ์•ˆยท๊ณต๊ฒฉ์„ฑ ๋Œ€์‘ ๋ฐฉ๋ฒ•"],
225
+ },
226
+ "11. ๊ทผ๊ณจ๊ฒฉ๊ณ„์งˆํ™˜ ์˜ˆ๋ฐฉ์ง€์นจ": {
227
+ "purpose": "์ข…์‚ฌ์ž์˜ ํ—ˆ๋ฆฌยท์–ด๊นจยท์†๋ชฉ ์†์ƒ์„ ์˜ˆ๋ฐฉํ•˜๊ณ  ์•ˆ์ „ํ•œ ์—…๋ฌด์ˆ˜ํ–‰ ๋ฐฉ๋ฒ•์„ ์ตํžŒ๋‹ค.",
228
+ "content": ["๊ทผ๊ณจ๊ฒฉ๊ณ„์งˆํ™˜ ์œ„ํ—˜์š”์ธ", "์ด๋™ยท์ฒด์œ„๋ณ€๊ฒฝ ์‹œ ์˜ฌ๋ฐ”๋ฅธ ์ž์„ธ", "๋ณด์กฐ๋„๊ตฌ ํ™œ์šฉ๊ณผ 2์ธ ํ˜‘์กฐ", "ํ†ต์ฆ ๋ฐœ์ƒ ์‹œ ๋ณด๊ณ ์™€ ์—…๋ฌด์กฐ์ •"],
229
+ },
230
+ "12. ์ข…์‚ฌ์ž ์ธ๊ถŒ์นจํ•ด ์˜ˆ๋ฐฉ์ง€์นจ": {
231
+ "purpose": "์ข…์‚ฌ์ž์— ๋Œ€ํ•œ ํญ์–ธยทํญํ–‰ยท์„ฑํฌ๋กฑ ๋“ฑ ์ธ๊ถŒ์นจํ•ด๋ฅผ ์˜ˆ๋ฐฉํ•˜๊ณ  ๋ณดํ˜ธ์ฒด๊ณ„๋ฅผ ์ดํ•ดํ•œ๋‹ค.",
232
+ "content": ["์ข…์‚ฌ์ž ์ธ๊ถŒ์นจํ•ด ์œ ํ˜•", "๋ถ€๋‹น์š”๊ตฌ์™€ ํญ์–ธยทํญํ–‰ ๋Œ€์‘", "๊ณ ์ถฉ ์ ‘์ˆ˜์™€ ๊ธฐ๊ด€ ๋ณดํ˜ธ์กฐ์น˜", "์žฌ๋ฐœ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ๊ธฐ๋ก๊ณผ ๋ณด๊ณ "],
233
+ },
234
+ "13. ๊ณ ์ถฉ์ฒ˜๋ฆฌ ๋ฐ ์•ผ๊ฐ„๊ทผ๋ฌด์ง€์นจ": {
235
+ "purpose": "์ˆ˜๊ธ‰์žยท๋ณดํ˜ธ์žยท์ข…์‚ฌ์ž ๊ณ ์ถฉ์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์•ผ๊ฐ„๊ทผ๋ฌด ์‹œ ์•ˆ์ „๊ด€๋ฆฌ ์›์น™์„ ์ค€์ˆ˜ํ•œ๋‹ค.",
236
+ "content": ["๊ณ ์ถฉ ์ ‘์ˆ˜์™€ ์ฒ˜๋ฆฌ ์ ˆ์ฐจ", "๋ฏผ์› ์˜ˆ๋ฐฉ๊ณผ ์ƒ๋‹ด๊ธฐ๋ก ๊ด€๋ฆฌ", "์•ผ๊ฐ„๊ทผ๋ฌด ์‹œ ์ˆœ์ฐฐยท์‘๊ธ‰์ƒํ™ฉ ๋Œ€์‘", "์•ผ๊ฐ„ ํŠน์ด์‚ฌํ•ญ ๋ณด๊ณ  ๋ฐ ์ธ์ˆ˜์ธ๊ณ„"],
237
+ },
238
+ }
239
+
240
+ METHODS = ["์ง‘ํ•ฉ๊ต์œก", "๊ฐœ๋ณ„๊ต์œก", "์˜จ๋ผ์ธ๊ต์œก", "ํšŒ์˜๊ต์œก", "์ž๋ฃŒ๋ฐฐํฌ ํ›„ ํ™•์ธ", "ํ˜„์žฅ์‹ค์Šต ๋ณ‘ํ–‰"]
241
+
242
+
243
+ # -----------------------------
244
+ # ์ƒ์„ฑ ๋กœ์ง
245
+ # -----------------------------
246
+ def build_education_text(selected_items, institution_type, method, custom_content):
247
+ if not selected_items:
248
+ return "", ""
249
+
250
+ purpose_lines = []
251
+ content_lines = []
252
+ for item in selected_items:
253
+ g = GUIDELINES[item]
254
+ purpose_lines.append(f"- {item}: {g['purpose']}")
255
+ content_lines.append(f"[{item}]")
256
+ for c in g["content"]:
257
+ content_lines.append(f"- {c}")
258
+ content_lines.append("")
259
+
260
+ institution_note = {
261
+ "๋ฐฉ๋ฌธ์š”์–‘": "๋ฐฉ๋ฌธ์„œ๋น„์Šค ํ˜„์žฅ์—์„œ ๋‹จ๋… ๊ทผ๋ฌด ์ค‘ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์‘๊ธ‰์ƒํ™ฉ, ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ, ๋ณดํ˜ธ์ž ์š”์ฒญ ๋Œ€์‘์„ ์ค‘์‹ฌ์œผ๋กœ ๊ต์œกํ•จ.",
262
+ "๋ฐฉ๋ฌธ๋ชฉ์š•": "๋ชฉ์š•์ง€์› ๊ณผ์ •์—์„œ ์‹ ์ฒด๋…ธ์ถœ, ๋‚™์ƒ, ํ”ผ๋ถ€์ƒํƒœ ํ™•์ธ, ๊ฐ์—ผ์˜ˆ๋ฐฉ, ์‘๊ธ‰์ƒํ™ฉ ๋Œ€์‘์„ ์ค‘์‹ฌ์œผ๋กœ ๊ต์œกํ•จ.",
263
+ "๋ฐฉ๋ฌธ๊ฐ„ํ˜ธ": "๊ฑด๊ฐ•์ƒํƒœ ๊ด€์ฐฐ, ๊ฐ์—ผ๊ด€๋ฆฌ, ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ, ์˜๋ฃŒ์—ฐ๊ณ„ ๋ฐ ์‘๊ธ‰์ƒํ™ฉ ๋Œ€์‘์„ ์ค‘์‹ฌ์œผ๋กœ ๊ต์œกํ•จ.",
264
+ "์ฃผ๊ฐ„๋ณดํ˜ธ": "์„ผํ„ฐ ๋‚ด ๋‹จ์ฒด์ƒํ™œ, ํ”„๋กœ๊ทธ๋žจ ์ฐธ์—ฌ, ์†ก์˜์•ˆ์ „, ๋‚™์ƒยท๊ฐ์—ผ์˜ˆ๋ฐฉ, ์ˆ˜๊ธ‰์ž ๊ฐ„ ๊ฐˆ๋“ฑ ์˜ˆ๋ฐฉ์„ ์ค‘์‹ฌ์œผ๋กœ ๊ต์œกํ•จ.",
265
+ "์š”์–‘์›": "์ž…์†Œ์ƒํ™œ ์ „๋ฐ˜์˜ ์ธ๊ถŒ๋ณดํ˜ธ, ์š•์ฐฝยท๋‚™์ƒยท๊ฐ์—ผ์˜ˆ๋ฐฉ, ์•ผ๊ฐ„๊ทผ๋ฌด, ์‘๊ธ‰์ƒํ™ฉ ๋Œ€์‘์„ ์ค‘์‹ฌ์œผ๋กœ ๊ต์œกํ•จ.",
266
+ "๊ณต๋™์ƒํ™œ๊ฐ€์ •": "์†Œ๊ทœ๋ชจ ๊ณต๋™์ƒํ™œ ์ค‘ ์ธ๊ถŒ๋ณดํ˜ธ, ์ •์„œ์ง€์›, ์•ผ๊ฐ„์•ˆ์ „, ๊ฐ์—ผ์˜ˆ๋ฐฉ, ๊ณ ์ถฉ์ฒ˜๋ฆฌ๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ๊ต์œกํ•จ.",
267
+ }.get(institution_type, "")
268
+
269
+ purpose = "๊ต์œก ๋ชฉ์ \n" + "\n".join(purpose_lines)
270
+
271
+ content = f"""
272
+ [๊ต์œก๋ฐฉ๋ฒ•]
273
+ {method}
274
+
275
+ [๊ธฐ๊ด€์œ ํ˜•๋ณ„ ๊ต์œก ์ค‘์ ]
276
+ {institution_note}
277
+
278
+ [๊ต์œก๋‚ด์šฉ]
279
+ {chr(10).join(content_lines)}
280
+
281
+ [๊ธฐ๊ด€ ์ถ”๊ฐ€ ๊ต์œก๋‚ด์šฉ]
282
+ {custom_content or '๊ธฐ๊ด€ ์šด์˜๊ทœ์ •๊ณผ ์ข…์‚ฌ์ž ์—…๋ฌด์ˆ˜์น™์— ๋”ฐ๋ผ ์‹ค์ œ ์‚ฌ๋ก€ ์ค‘์‹ฌ์œผ๋กœ ๊ต์œก์„ ์ง„ํ–‰ํ•จ.'}
283
+ """.strip()
284
+
285
+ return purpose, content
286
+
287
+
288
+ def build_result_text(selected_items, att_dict, absent_staff, method):
289
+ item_text = ", ".join(selected_items)
290
+
291
+ # ์ด๋ฆ„ ๊ฐฏ์ˆ˜ ๋Œ€๋žต์  ์นด์šดํŠธ (์‰ผํ‘œ๋‚˜ ๋„์–ด์“ฐ๊ธฐ ๊ธฐ์ค€)
292
+ total_names = 0
293
+ for names in att_dict.values():
294
+ total_names += len([n for n in names.replace(',', ' ').split() if n.strip()])
295
+
296
+ absent_text = absent_staff.strip() or "๋ฏธ์ฐธ์„์ž ์—†์Œ"
297
+
298
+ variants = [
299
+ f"{item_text} ๊ต์œก์„ ํ†ตํ•ด ์ข…์‚ฌ์ž์˜ ์ง€์นจ ์ดํ•ด๋„๋ฅผ ๋†’์ด๊ณ  ์‹ค์ œ ๊ธ‰์—ฌ์ œ๊ณต ๊ณผ์ •์—์„œ ์ค€์ˆ˜ํ•ด์•ผ ํ•  ๋ณด๊ณ ์ฒด๊ณ„์™€ ํ–‰๋™์ˆ˜์น™์„ ์žฌํ™•์ธํ•˜์˜€๋‹ค.",
300
+ f"์ฐธ์„์ž๋“ค์€ {item_text}์˜ ์ฃผ์š” ๋‚ด์šฉ์„ ํ™•์ธํ•˜์˜€์œผ๋ฉฐ, ์ˆ˜๊ธ‰์ž ์•ˆ์ „๊ณผ ๊ถŒ๋ฆฌ๋ณดํ˜ธ๋ฅผ ์œ„ํ•œ ์‹ค์ฒœ๋ฐฉ๋ฒ•์„ ๊ณต์œ ํ•˜์˜€๋‹ค.",
301
+ f"๊ต์œก ํ›„ ์งˆ์˜์‘๋‹ต์„ ํ†ตํ•ด ํ˜„์žฅ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ๋ก€๋ฅผ ์ ๊ฒ€ํ•˜์˜€๊ณ , ์ง€์นจ ๋ฏธ์ค€์ˆ˜ ์˜ˆ๋ฐฉ๊ณผ ๊ธฐ๋ก๊ด€๋ฆฌ์˜ ์ค‘์š”์„ฑ์„ ํ™•์ธํ•˜์˜€๋‹ค.",
302
+ ]
303
+
304
+ return f"""
305
+ ๊ต์œก๊ฒฐ๊ณผ, ์ด {total_names}๋ช…์˜ ์ข…์‚ฌ์ž๊ฐ€ {method} ๋ฐฉ์‹์œผ๋กœ ๊ต์œก์— ์ฐธ์—ฌํ•˜์˜€๋‹ค.
306
+ {random.choice(variants)}
307
+
308
+ ์ฐธ์„์ž ์ „์›์ด ๊ต์œก๋‚ด์šฉ์„ ์ž˜ ์ˆ™์ง€ํ•˜์˜€์œผ๋ฉฐ, ๊ต์œก์ž๋ฃŒ์— ์ œ์‹œ๋œ ์ง€์นจ ๋‚ด์šฉ์„ ์‹ค์ œ ๊ธ‰์—ฌ์ œ๊ณต ๊ณผ์ •์—์„œ ์ค€์ˆ˜ํ•˜๊ธฐ๋กœ ํ•˜์˜€๋‹ค.
309
+ ๊ธฐ๊ด€์€ ๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ ๊ต์œก์ž๋ฃŒ๋ฅผ ์ข…์‚ฌ์ž๊ฐ€ ์‰ฝ๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์žฅ์†Œ์— ๊ณต๊ฐœ ๊ฒŒ์‹œํ•˜๊ณ , ์ข…์‚ฌ์ž๊ฐ€ ์ˆ˜์‹œ๋กœ ์ง€์นจ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์—ฌ ๊ธ‰์—ฌ์ œ๊ณต์— ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ด€๋ฆฌํ•œ๋‹ค.
310
+ ๋˜ํ•œ ์‹ ๊ทœ์ง์›, ๋ฏธ์ฐธ์„์ž, ์ง€์นจ ์ˆ™์ง€๊ฐ€ ๋ถ€์กฑํ•œ ์ข…์‚ฌ์ž์—๊ฒŒ๋Š” ๋ณด์ถฉ๊ต์œก์„ ์‹ค์‹œํ•˜๊ณ , ๊ต์œก๋‚ด์šฉ์ด ํ˜„์žฅ์—์„œ ์ดํ–‰๋˜๋Š”์ง€ ์ง€์†์ ์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•œ๋‹ค.
311
+
312
+ ๋ฏธ์ฐธ์„์ž ๊ด€๋ฆฌ: {absent_text}
313
+ """.strip()
314
+
315
+
316
+ def evaluate_education(data):
317
+ required = {
318
+ "๊ต์œก๋ช…": bool(data["education_title"].strip()),
319
+ "๊ต์œก์ผ์‹œ": bool(data["education_date"]) and bool(data["education_time"].strip()),
320
+ "๊ต์œก์žฅ์†Œ": bool(data["education_place"].strip()),
321
+ "๊ฐ•์‚ฌ๋ช…": bool(data["instructor_name"].strip()),
322
+ "๊ต์œก๋Œ€์ƒ/์ฐธ์„์ž": bool(data["attendees"].strip()),
323
+ "๊ต์œก์ง€์นจ ์„ ํƒ": bool(data["guideline_items"].strip()),
324
+ "๊ต์œก๋ชฉ์ ": bool(data["education_purpose"].strip()),
325
+ "๊ต์œก๋‚ด์šฉ": bool(data["education_content"].strip()),
326
+ "๊ต์œก์ž๋ฃŒ ๏ฟฝ๏ฟฝ๋ถ€/๋ช…์‹œ": bool(data["education_materials"].strip()),
327
+ "๊ต์œก๊ฒฐ๊ณผ": bool(data["education_result"].strip()),
328
+ "๋ฏธ์ฐธ์„์ž ๊ด€๋ฆฌ": "๋ฏธ์ฐธ์„" in data["education_result"] or bool(data["absent_staff"].strip()),
329
+ "ํ›„์†์กฐ์น˜": bool(data["followup_plan"].strip()),
330
+ }
331
+
332
+ score = int(sum(required.values()) / len(required) * 100)
333
+ missing = [k for k, v in required.items() if not v]
334
+
335
+ if score >= 90:
336
+ result = "ํ‰๊ฐ€ ์ ํ•ฉ๋„๊ฐ€ ๋†’์Šต๋‹ˆ๋‹ค. ๊ต์œก์ผ์‹œ, ๊ฐ•์‚ฌ, ์ฐธ์„์ž, ๊ต์œก๋‚ด์šฉ, ๊ฒฐ๊ณผ ๋ฐ ํ›„์†์กฐ์น˜๊ฐ€ ์ž˜ ๋ฐ˜์˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
337
+ elif score >= 75:
338
+ result = "๋Œ€์ฒด๋กœ ์ ํ•ฉํ•˜๋‚˜ ์ผ๋ถ€ ๋ณด์™„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋ถ€์กฑ ํ•ญ๋ชฉ์„ ๋ณด์™„ํ•˜๋ฉด ํ‰๊ฐ€ ๋Œ€์‘๋ ฅ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค."
339
+ else:
340
+ result = "๋ณด์™„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ฐธ์„์ž ๋ช…๋‹จ, ๊ต์œก์ž๋ฃŒ, ๊ต์œก๊ฒฐ๊ณผ, ๋ฏธ์ฐธ์„์ž ๊ด€๋ฆฌ๊ฐ€ ๊ตฌ์ฒด์ ์œผ๋กœ ๋“ค์–ด๊ฐ€์•ผ ํ•ฉ๋‹ˆ๋‹ค."
341
+
342
+ return score, missing, result
343
+
344
+
345
+ def save_uploaded_materials(uploaded_files):
346
+ paths = []
347
+ names = []
348
+ for file in (uploaded_files or [])[:3]:
349
+ suffix = os.path.splitext(file.name)[1].lower() or ".png"
350
+ temp_path = os.path.join(tempfile.gettempdir(), f"edu_material_{datetime.now().strftime('%Y%m%d%H%M%S%f')}{suffix}")
351
+ with open(temp_path, "wb") as f:
352
+ f.write(file.getbuffer())
353
+ paths.append(temp_path)
354
+ names.append(file.name)
355
+ return paths, names
356
+
357
+
358
+ # -----------------------------
359
+ # ์—‘์…€ ์ถœ๋ ฅ (๋””์ž์ธ ์„ ๋ช…ํ™” ๋ฐ ์…€ ์ž˜๋ฆผ ๋ฐฉ์ง€)
360
+ # -----------------------------
361
+ def style_range(ws, cell_range, border_on=True, fill=None, font=None, alignment=None):
362
+ """๋ณ‘ํ•ฉ ์…€ ํฌํ•จ ๋ฒ”์œ„ ์ „์ฒด์— ์Šคํƒ€์ผ ์ ์šฉ (๊ฒ€์ •์ƒ‰ ํ…Œ๋‘๋ฆฌ๋กœ ์„ ๋ช…ํ•˜๊ฒŒ)"""
363
+ thin = Side(style="thin", color="000000")
364
+ border = Border(left=thin, right=thin, top=thin, bottom=thin)
365
+
366
+ cells = ws[cell_range]
367
+ if not isinstance(cells, tuple):
368
+ cells = ((cells,),)
369
+
370
+ for row in cells:
371
+ for cell in row:
372
+ if border_on:
373
+ cell.border = border
374
+ if fill:
375
+ cell.fill = fill
376
+ if font:
377
+ cell.font = font
378
+ if alignment:
379
+ cell.alignment = alignment
380
+
381
+ def write_title_with_approval(ws, title, title_fill, header_fill):
382
+ for col in range(1, 10):
383
+ ws.column_dimensions[get_column_letter(col)].width = 11.5
384
+
385
+ ws.merge_cells("A1:G2")
386
+ c = ws.cell(1, 1)
387
+ c.value = title
388
+ c.font = Font(name="Malgun Gothic", size=24, bold=True)
389
+ c.alignment = Alignment(horizontal="center", vertical="center")
390
+ style_range(ws, "A1:G2", fill=title_fill)
391
+
392
+ # ๊ฒฐ์žฌ๋ผ์ธ (์šฐ์ธก ์ƒ๋‹จ 2์นธ) ์ง€์ €๋ถ„ํ•œ ์„  ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ์…€๋ณ„๋กœ ์Šคํƒ€์ผ ์ง€์ •
393
+ ws.cell(1, 8).value = "๋‹ด๋‹น์ž"
394
+ ws.cell(1, 9).value = "์‹œ์„ค์žฅ"
395
+ ws.cell(2, 8).value = ""
396
+ ws.cell(2, 9).value = ""
397
+
398
+ for rr in [1, 2]:
399
+ for cc in [8, 9]:
400
+ cell = ws.cell(rr, cc)
401
+ cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
402
+ if rr == 1:
403
+ cell.font = Font(name="Malgun Gothic", size=11, bold=True)
404
+ cell.fill = header_fill
405
+ else:
406
+ cell.font = Font(name="Malgun Gothic", size=11)
407
+
408
+ # ์…€ ๋‹จ์œ„๋กœ๋งŒ ์„ ๋ช…ํ•˜๊ฒŒ ํ…Œ๋‘๋ฆฌ๋ฅผ ๊ธ‹์Šต๋‹ˆ๋‹ค.
409
+ cell_name = f"{get_column_letter(cc)}{rr}"
410
+ style_range(ws, f"{cell_name}:{cell_name}")
411
+
412
+ ws.row_dimensions[1].height = 28
413
+ ws.row_dimensions[2].height = 42
414
+
415
+ def write_info_row(ws, row, a, b, c, d, e, f, header_fill):
416
+ ws.cell(row, 1).value = a
417
+ ws.merge_cells(start_row=row, start_column=2, end_row=row, end_column=3)
418
+ ws.cell(row, 2).value = b
419
+
420
+ ws.cell(row, 4).value = c
421
+ ws.merge_cells(start_row=row, start_column=5, end_row=row, end_column=6)
422
+ ws.cell(row, 5).value = d
423
+
424
+ ws.cell(row, 7).value = e
425
+ ws.merge_cells(start_row=row, start_column=8, end_row=row, end_column=9)
426
+ ws.cell(row, 8).value = f
427
+
428
+ for col in [1, 4, 7]:
429
+ ws.cell(row, col).fill = header_fill
430
+ ws.cell(row, col).font = Font(name="Malgun Gothic", size=11, bold=True)
431
+ ws.cell(row, col).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
432
+
433
+ for col in [2, 5, 8]:
434
+ ws.cell(row, col).font = Font(name="Malgun Gothic", size=11)
435
+ ws.cell(row, col).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
436
+
437
+ style_range(ws, f"A{row}:I{row}")
438
+ ws.row_dimensions[row].height = 30
439
+
440
+ def write_attendee_row(ws, row, k1, v1, k2, v2, k3, v3, header_fill):
441
+ """์ฐธ์„์ž ๋ช…๋‹จ ์ „์šฉ 3์—ด ๋ถ„ํ•  ๊ตฌ์กฐ (์ง์ข… | ์ด๋ฆ„ | ์ง์ข… | ์ด๋ฆ„ | ์ง์ข… | ์ด๋ฆ„)"""
442
+ ws.cell(row, 1).value = k1
443
+ ws.merge_cells(start_row=row, start_column=2, end_row=row, end_column=3)
444
+ ws.cell(row, 2).value = v1
445
+
446
+ ws.cell(row, 4).value = k2
447
+ ws.merge_cells(start_row=row, start_column=5, end_row=row, end_column=6)
448
+ ws.cell(row, 5).value = v2
449
+
450
+ ws.cell(row, 7).value = k3
451
+ ws.merge_cells(start_row=row, start_column=8, end_row=row, end_column=9)
452
+ ws.cell(row, 8).value = v3
453
+
454
+ for c in [1, 4, 7]:
455
+ ws.cell(row, c).fill = header_fill
456
+ ws.cell(row, c).font = Font(name="Malgun Gothic", size=11, bold=True)
457
+ ws.cell(row, c).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
458
+
459
+ for c in [2, 5, 8]:
460
+ ws.cell(row, c).font = Font(name="Malgun Gothic", size=11)
461
+ ws.cell(row, c).alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
462
+
463
+ style_range(ws, f"A{row}:I{row}")
464
+
465
+ # ๊ฐ€๋…์„ฑ์„ ๋†’์ด๊ณ  ๋ฌธ์žฅ์ด ์ž˜๋ฆฌ์ง€ ์•Š๋„๋ก ์—ฌ์œ  ๋†’์ด ๊ณ„์‚ฐ
466
+ max_len = max(len(str(v1)), len(str(v2)), len(str(v3)))
467
+ lines = (max_len // 15) + 1
468
+ ws.row_dimensions[row].height = max(35, lines * 22 + 20)
469
+
470
+
471
+ def write_section(ws, row, title, content, header_fill, min_height=100):
472
+ # ์ œ๋ชฉ ํ–‰
473
+ ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=9)
474
+ t_cell = ws.cell(row, 1)
475
+ t_cell.value = title
476
+ t_cell.font = Font(name="Malgun Gothic", size=15, bold=True)
477
+ t_cell.alignment = Alignment(horizontal="center", vertical="center")
478
+ t_cell.fill = header_fill
479
+ style_range(ws, f"A{row}:I{row}")
480
+ ws.row_dimensions[row].height = 30
481
+ row += 1
482
+
483
+ # ๋‚ด์šฉ ํ–‰
484
+ ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=9)
485
+ c_cell = ws.cell(row, 1)
486
+ c_cell.value = content
487
+ c_cell.font = Font(name="Malgun Gothic", size=11)
488
+ c_cell.alignment = Alignment(horizontal="left", vertical="top", wrap_text=True)
489
+ style_range(ws, f"A{row}:I{row}")
490
+
491
+ # ์ค„๋ฐ”๊ฟˆ๊ณผ ํ…์ŠคํŠธ ๊ธธ์ด์— ๋น„๋ก€ํ•ด ์…€ ๋†’์ด ์ •๋ฐ€ ๊ณ„์‚ฐ (์—ฌ์œ  ๊ฐ„๊ฒฉ ํ™•๋Œ€)
492
+ content_str = str(content)
493
+ lines = 0
494
+ for line in content_str.split("\n"):
495
+ lines += (len(line) // 45) + 1
496
+ calculated_height = lines * 24 + 30
497
+ ws.row_dimensions[row].height = max(min_height, calculated_height)
498
+
499
+ return row + 1
500
+
501
+
502
+ def generate_excel(data):
503
+ wb = Workbook()
504
+ title_fill = PatternFill("solid", fgColor="FFEDD5")
505
+ header_fill = PatternFill("solid", fgColor="F3F4F6")
506
+
507
+ ws = wb.active
508
+ ws.title = "๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ๊ต์œก์ผ์ง€"
509
+
510
+ ws.page_setup.paperSize = ws.PAPERSIZE_A4
511
+ ws.page_setup.orientation = "portrait"
512
+ ws.page_setup.fitToWidth = 1
513
+ ws.page_setup.fitToHeight = 0 # 0์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€ ์ถœ๋ ฅ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.
514
+ ws.sheet_properties.pageSetUpPr.fitToPage = True
515
+ ws.page_margins.left = 0.3
516
+ ws.page_margins.right = 0.3
517
+ ws.page_margins.top = 0.4
518
+ ws.page_margins.bottom = 0.4
519
+ ws.sheet_view.showGridLines = False
520
+
521
+ # ์ œ๋ชฉ์—์„œ Beecare ์ œ์™ธ, ์šฐ์ธก ์ƒ๋‹จ ๊ฒฐ์žฌ๋ผ์ธ
522
+ write_title_with_approval(ws, "๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ ๊ต์œก์ผ์ง€", title_fill, header_fill)
523
+
524
+ r = 3
525
+ write_info_row(ws, r, "๊ธฐ๊ด€๋ช…", data["institution_name"], "๊ธฐ๊ด€์œ ํ˜•", data["institution_type"], "๊ต์œก์ผ์ž", data["education_date"], header_fill); r += 1
526
+ write_info_row(ws, r, "๊ต์œก๋ช…", data["education_title"], "๊ต์œก์‹œ๊ฐ„", data["education_time"], "๊ต์œก์žฅ์†Œ", data["education_place"], header_fill); r += 1
527
+ write_info_row(ws, r, "๊ฐ•์‚ฌ๋ช…", data["instructor_name"], "๊ฐ•์‚ฌ์ง์ฑ…", data["instructor_position"], "๊ต์œก๋ฐฉ๋ฒ•", data["education_method"], header_fill); r += 1
528
+
529
+ r = write_section(ws, r, "๊ต์œก์ง€์นจ", data["guideline_items"], header_fill, 80)
530
+
531
+ # ==== ์ฐธ์„์ž ๋ช…๋‹จ (์ง์ข…๋ณ„ ๋ถ„ํ•  ๋ ˆ์ด์•„์›ƒ) ====
532
+ ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=9)
533
+ ws.cell(r, 1).value = "์ฐธ์„์ž ๋ช…๋‹จ"
534
+ ws.cell(r, 1).font = Font(name="Malgun Gothic", size=15, bold=True)
535
+ ws.cell(r, 1).alignment = Alignment(horizontal="center", vertical="center")
536
+ ws.cell(r, 1).fill = header_fill
537
+ style_range(ws, f"A{r}:I{r}")
538
+ ws.row_dimensions[r].height = 30
539
+ r += 1
540
+
541
+ att = data.get("attendees_dict", {})
542
+ write_attendee_row(ws, r, "์‹œ์„ค์žฅ", att.get("์‹œ์„ค์žฅ", ""), "์‚ฌํšŒ๋ณต์ง€์‚ฌ", att.get("์‚ฌํšŒ๋ณต์ง€์‚ฌ", ""), "๋ฌผ๋ฆฌ์น˜๋ฃŒ์‚ฌ", att.get("๋ฌผ๋ฆฌ์น˜๋ฃŒ์‚ฌ", ""), header_fill); r += 1
543
+ write_attendee_row(ws, r, "๊ฐ„ํ˜ธ์กฐ๋ฌด์‚ฌ", att.get("๊ฐ„ํ˜ธ์กฐ๋ฌด์‚ฌ", ""), "์š”์–‘๋ณดํ˜ธ์‚ฌ", att.get("์š”์–‘๋ณดํ˜ธ์‚ฌ", ""), "๊ธฐํƒ€์ง์ข…", att.get("๊ธฐํƒ€", ""), header_fill); r += 1
544
+
545
+ # =========================================
546
+
547
+ r = write_section(ws, r, "๋ฏธ์ฐธ์„์ž ๋ฐ ๋ณด์ถฉ๊ต์œก ๊ณ„ํš", data["absent_staff"] or "๋ฏธ์ฐธ์„์ž ์—†์Œ", header_fill, 70)
548
+ r = write_section(ws, r, "๊ต์œก๋ชฉ์ ", data["education_purpose"], header_fill, 110)
549
+ r = write_section(ws, r, "๊ต์œก๋‚ด์šฉ", data["education_content"], header_fill, 260)
550
+ r = write_section(ws, r, "๊ต์œก์ž๋ฃŒ ์ฒจ๋ถ€/์ž๋ฃŒ๋ช…", data["education_materials"], header_fill, 80)
551
+
552
+ # ==== ์—ฌ๊ธฐ์„œ 1ํŽ˜์ด์ง€ ๋Š๊ธฐ (๊ฐ€๋กœ ํŽ˜์ด์ง€ ๊ตฌ๋ถ„์„  ์‚ฝ์ž…) ====
553
+ ws.row_breaks.append(Break(id=r-1))
554
+
555
+ # ==== 2ํŽ˜์ด์ง€: ๊ต์œก๊ฒฐ๏ฟฝ๏ฟฝ ๋ฐ ์ด๋ฏธ์ง€ ====
556
+ r = write_section(ws, r, "๊ต์œก๊ฒฐ๊ณผ", data["education_result"], header_fill, 150)
557
+
558
+ # ์ž์ฒด์ ๊ฒ€์šฉ 'ํ›„์†์กฐ์น˜'์™€ 'ํ‰๊ฐ€์ ํ•ฉ๋„'๋Š” ์—‘์…€์— ์ถœ๋ ฅํ•˜์ง€ ์•Š๋„๋ก ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค.
559
+
560
+ # ๊ต์œก์ž๋ฃŒ ์ด๋ฏธ์ง€ ์ฒจ๋ถ€: ์ด๋ฏธ์ง€ ํŒŒ์ผ์ผ ๋•Œ๋งŒ ์‚ฝ์ž…
561
+ image_paths = [p for p in data.get("material_paths", []) if os.path.splitext(p)[1].lower() in [".png", ".jpg", ".jpeg"]]
562
+ if image_paths:
563
+ ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=9)
564
+ ws.cell(r, 1).value = "๊ต์œก์ž๋ฃŒ ์ด๋ฏธ์ง€"
565
+ ws.cell(r, 1).font = Font(name="Malgun Gothic", size=15, bold=True)
566
+ ws.cell(r, 1).alignment = Alignment(horizontal="center", vertical="center")
567
+ ws.cell(r, 1).fill = header_fill
568
+ style_range(ws, f"A{r}:I{r}")
569
+ r += 1
570
+
571
+ anchors = ["A", "D", "G"]
572
+ img_row = r
573
+ for idx, img_path in enumerate(image_paths[:3]):
574
+ try:
575
+ img = XLImage(img_path)
576
+ img.width = 210
577
+ img.height = 150
578
+ ws.add_image(img, f"{anchors[idx]}{img_row}")
579
+ except Exception:
580
+ pass
581
+
582
+ # ์ด๋ฏธ์ง€ ์˜์—ญ ๋ณ‘ํ•ฉ ๋ฐ ์Šคํƒ€์ผ ์ ์šฉ
583
+ ws.merge_cells(start_row=img_row, start_column=1, end_row=img_row+7, end_column=9)
584
+ style_range(ws, f"A{img_row}:I{img_row+7}")
585
+ for rr in range(img_row, img_row + 8):
586
+ ws.row_dimensions[rr].height = 22
587
+ r = img_row + 8
588
+
589
+ output = BytesIO()
590
+ wb.save(output)
591
+ output.seek(0)
592
+ return output
593
+
594
+
595
+ # -----------------------------
596
+ # ํ™”๋ฉด
597
+ # -----------------------------
598
+ menu = st.sidebar.radio("๋ฉ”๋‰ด", ["๊ต์œก์ผ์ง€ ์ƒ์„ฑ", "๊ต์œก๊ธฐ๋ก ๋ณด๊ธฐ"])
599
+
600
+ st.sidebar.success("DB ์—ฐ๊ฒฐ")
601
+ st.sidebar.caption(DB_PATH)
602
+
603
+ if menu == "๊ต์œก๊ธฐ๋ก ๋ณด๊ธฐ":
604
+ st.subheader("๐Ÿ“š ์ €์žฅ๋œ ๊ต์œก๊ธฐ๋ก")
605
+ records = load_education_records()
606
+ if records.empty:
607
+ st.info("์ €์žฅ๋œ ๊ต์œก๊ธฐ๋ก์ด ์—†์Šต๋‹ˆ๋‹ค.")
608
+ else:
609
+ st.dataframe(records, use_container_width=True)
610
+
611
+ elif menu == "๊ต์œก์ผ์ง€ ์ƒ์„ฑ":
612
+ st.subheader("๐Ÿ“˜ ๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ ๊ต์œก์ผ์ง€ ์ƒ์„ฑ")
613
+
614
+ col1, col2, col3 = st.columns(3)
615
+ # ์ดˆ๊ธฐ ๊ธฐ๊ด€๋ช…์—์„œ Beecare ์‚ญ์ œ
616
+ institution_name = col1.text_input("๊ธฐ๊ด€๋ช…", "์žฅ๊ธฐ์š”์–‘๊ธฐ๊ด€")
617
+ institution_type = col2.selectbox("๊ธฐ๊ด€์œ ํ˜•", ["๋ฐฉ๋ฌธ์š”์–‘", "๋ฐฉ๋ฌธ๋ชฉ์š•", "๋ฐฉ๋ฌธ๊ฐ„ํ˜ธ", "์ฃผ๊ฐ„๋ณดํ˜ธ", "์š”์–‘์›", "๊ณต๋™์ƒํ™œ๊ฐ€์ •"])
618
+ education_title = col3.text_input("๊ต์œก๋ช…", "๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ ์ข…์‚ฌ์ž ๊ต์œก")
619
+
620
+ col4, col5, col6 = st.columns(3)
621
+ education_date = col4.date_input("๊ต์œก์ผ์ž", value=date.today())
622
+ education_time = col5.text_input("๊ต์œก์‹œ๊ฐ„", "14:00~15:00")
623
+ education_place = col6.text_input("๊ต์œก์žฅ์†Œ", "๊ธฐ๊ด€ ๋‚ด ํ”„๋กœ๊ทธ๋žจ์‹ค")
624
+
625
+ col7, col8, col9 = st.columns(3)
626
+ instructor_name = col7.text_input("๊ฐ•์‚ฌ๋ช…", "์‹œ์„ค์žฅ")
627
+ instructor_position = col8.text_input("๊ฐ•์‚ฌ์ง์ฑ…", "์‹œ์„ค์žฅ")
628
+ education_method = col9.selectbox("๊ต์œก๋ฐฉ๋ฒ•", METHODS)
629
+
630
+ selected_guidelines = st.multiselect(
631
+ "๊ต์œก ์ง€์นจ ์„ ํƒ",
632
+ list(GUIDELINES.keys()),
633
+ default=["02. ๋…ธ์ธ์ธ๊ถŒ๋ณดํ˜ธ์ง€์นจ", "03. ๋…ธ์ธํ•™๋Œ€์˜ˆ๋ฐฉ์ง€์นจ", "07. ๊ฐ์—ผ์˜ˆ๋ฐฉ ๋ฐ ๊ด€๋ฆฌ์ง€์นจ", "09. ๋‚™์ƒ์˜ˆ๋ฐฉ์ง€์นจ"]
634
+ )
635
+
636
+ st.markdown("### ์ง์ข…๋ณ„ ์ฐธ์„์ž ๋ช…๋‹จ ์ž…๋ ฅ")
637
+ st.caption("๊ฐ ์นธ์— ์ฐธ์„์ž์˜ ์ด๋ฆ„์„ ์ง์ ‘ ์‰ผํ‘œ๋‚˜ ๋„์–ด์“ฐ๊ธฐ๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.")
638
+
639
+ a1, a2, a3 = st.columns(3)
640
+ att_director = a1.text_input("์‹œ์„ค์žฅ", placeholder="์˜ˆ: ํ™๊ธธ๋™")
641
+ att_social = a2.text_input("์‚ฌํšŒ๋ณต์ง€์‚ฌ", placeholder="์˜ˆ: ๊น€๋ณต์ง€")
642
+ att_therapist = a3.text_input("๋ฌผ๋ฆฌ์น˜๋ฃŒ์‚ฌ", placeholder="์˜ˆ: ์ด์น˜๋ฃŒ")
643
+
644
+ a4, a5, a6 = st.columns(3)
645
+ att_nurse = a4.text_input("๊ฐ„ํ˜ธ์กฐ๋ฌด์‚ฌ", placeholder="์˜ˆ: ๋ฐ•๊ฐ„ํ˜ธ")
646
+ att_caregiver = a5.text_input("์š”์–‘๋ณดํ˜ธ์‚ฌ", placeholder="์˜ˆ: ์ตœ์š”์–‘, ์ •์š”์–‘")
647
+ att_others = a6.text_input("๊ธฐํƒ€ ์ง์ข…", placeholder="์˜ˆ: ์กฐ๋ฆฌ์› ๊น€์กฐ๋ฆฌ")
648
+
649
+ # ์ž…๋ ฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋”•์…”๋„ˆ๋ฆฌ์™€ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜
650
+ attendees_dict = {
651
+ "์‹œ์„ค์žฅ": att_director,
652
+ "์‚ฌํšŒ๋ณต์ง€์‚ฌ": att_social,
653
+ "๋ฌผ๋ฆฌ์น˜๋ฃŒ์‚ฌ": att_therapist,
654
+ "๊ฐ„ํ˜ธ์กฐ๋ฌด์‚ฌ": att_nurse,
655
+ "์š”์–‘๋ณดํ˜ธ์‚ฌ": att_caregiver,
656
+ "๊ธฐํƒ€": att_others
657
+ }
658
+ attendees_summary = "\n".join([f"[{k}] {v}" for k, v in attendees_dict.items() if v.strip()])
659
+
660
+ absent_staff = st.text_area(
661
+ "๋ฏธ์ฐธ์„์ž ๋ฐ ๋ณด์ถฉ๊ต์œก ๊ณ„ํš",
662
+ placeholder="์˜ˆ: ๋ฐ•์š”์–‘๋ณดํ˜ธ์‚ฌ ์—ฐ์ฐจ๋กœ ๋ฏธ์ฐธ์„, 6์›” 20์ผ ๊ฐœ๋ณ„ ๋ณด์ถฉ๊ต์œก ์˜ˆ์ •",
663
+ height=80
664
+ )
665
+
666
+ custom_content = st.text_area(
667
+ "๊ธฐ๊ด€ ์ถ”๊ฐ€ ๊ต์œก๋‚ด์šฉ",
668
+ placeholder="๊ธฐ๊ด€์—์„œ ์‹ค์ œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ๋ก€, ์ž์ฒด ์šด์˜๊ทœ์ •, ์œ ์˜์‚ฌํ•ญ ๋“ฑ์„ ์ž…๋ ฅํ•˜์„ธ์š”.",
669
+ height=90
670
+ )
671
+
672
+ education_material_files = st.file_uploader(
673
+ "๊ต์œก์ž๋ฃŒ ์ฒจ๋ถ€ (์ด๋ฏธ์ง€/PDF/๋ฌธ์„œ ๊ฐ€๋Šฅ, ์ตœ๋Œ€ 3๊ฐœ)",
674
+ type=["png", "jpg", "jpeg", "pdf", "docx", "xlsx"],
675
+ accept_multiple_files=True
676
+ )
677
+
678
+ material_paths, material_names = save_uploaded_materials(education_material_files)
679
+ material_text = ", ".join(material_names) if material_names else "๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ ๊ต์œก์ž๋ฃŒ ๋น„์น˜ ๋ฐ ๊ณต๊ฐœ๊ฒŒ์‹œ ์ž๋ฃŒ ์‚ฌ์šฉ"
680
+
681
+ purpose, content = build_education_text(
682
+ selected_guidelines,
683
+ institution_type,
684
+ education_method,
685
+ custom_content
686
+ )
687
+
688
+ result_text = build_result_text(
689
+ selected_guidelines,
690
+ attendees_dict,
691
+ absent_staff,
692
+ education_method
693
+ ) if selected_guidelines else ""
694
+
695
+ followup_plan = st.text_area(
696
+ "ํ›„์†์กฐ์น˜ ๋ฐ ํ˜„์žฅ ์ ์šฉ๊ณ„ํš",
697
+ value="๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ ๊ต์œก์ž๋ฃŒ๋ฅผ ๊ธฐ๊ด€ ๋‚ด ์ข…์‚ฌ์ž๊ฐ€ ํ™•์ธํ•˜๊ธฐ ์‰ฌ์šด ์žฅ์†Œ์— ๊ณต๊ฐœ ๊ฒŒ์‹œํ•˜๊ณ , ์ข…์‚ฌ์ž๊ฐ€ ์ˆ˜์‹œ๋กœ ์ง€์นจ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์—ฌ ์‹ค์ œ ๊ธ‰์—ฌ์ œ๊ณต์— ๋ฐ˜์˜ํ•˜๋„๋ก ํ•œ๋‹ค. ๊ต์œก ํ›„ ์ข…์‚ฌ์ž๊ฐ€ ํ˜„์žฅ์—์„œ ์ง€์นจ์„ ์ค€์ˆ˜ํ•˜๋Š”์ง€ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ , ๋ฏธ์ฐธ์„์ž ๋˜๋Š” ์‹ ๊ทœ์ง์›์— ๋Œ€ํ•ด์„œ๋Š” ๋ณด์ถฉ๊ต์œก์„ ์‹ค์‹œํ•œ๋‹ค.",
698
+ height=100
699
+ )
700
+
701
+ if st.button("๐Ÿ“˜ ๊ต์œก์ผ์ง€ ์ƒ์„ฑ", use_container_width=True):
702
+ data = {
703
+ "institution_name": institution_name,
704
+ "institution_type": institution_type,
705
+ "education_title": education_title,
706
+ "guideline_items": "\n".join(selected_guidelines),
707
+ "education_date": str(education_date),
708
+ "education_time": education_time,
709
+ "education_place": education_place,
710
+ "instructor_name": instructor_name,
711
+ "instructor_position": instructor_position,
712
+ "attendees_dict": attendees_dict,
713
+ "attendees": attendees_summary, # ํ‰๊ฐ€ ๋กœ์ง ๋ฐ DB ์ €์žฅ์šฉ
714
+ "absent_staff": absent_staff,
715
+ "education_method": education_method,
716
+ "education_purpose": purpose,
717
+ "education_content": content,
718
+ "education_materials": material_text,
719
+ "education_result": result_text,
720
+ "followup_plan": followup_plan,
721
+ "material_paths": material_paths,
722
+ }
723
+
724
+ score, missing, compliance = evaluate_education(data)
725
+ data["evaluation_score"] = score
726
+ data["missing_items"] = ", ".join(missing) if missing else "๋ถ€์กฑ ํ•ญ๋ชฉ ์—†์Œ"
727
+ data["compliance_result"] = compliance
728
+
729
+ st.session_state["edu_data"] = data
730
+
731
+ if "edu_data" in st.session_state:
732
+ data = st.session_state["edu_data"]
733
+
734
+ st.markdown("---")
735
+ st.subheader("์ƒ์„ฑ ๊ฒฐ๊ณผ ์ˆ˜์ •")
736
+
737
+ data["education_purpose"] = st.text_area("๊ต์œก๋ชฉ์ ", value=data["education_purpose"], height=140)
738
+ data["education_content"] = st.text_area("๊ต์œก๋‚ด์šฉ", value=data["education_content"], height=260)
739
+ data["education_result"] = st.text_area("๊ต์œก๊ฒฐ๊ณผ", value=data["education_result"], height=140)
740
+
741
+ # UI/DB์šฉ์œผ๋กœ๋Š” ์œ ์ง€
742
+ data["followup_plan"] = st.text_area("ํ›„์†์กฐ์น˜ (์—‘์…€ ์ถœ๋ ฅ ์ œ์™ธ)", value=data["followup_plan"], height=100)
743
+
744
+ score, missing, compliance = evaluate_education(data)
745
+ data["evaluation_score"] = score
746
+ data["missing_items"] = ", ".join(missing) if missing else "๋ถ€์กฑ ํ•ญ๋ชฉ ์—†์Œ"
747
+ data["compliance_result"] = compliance
748
+
749
+ st.subheader("ํ‰๊ฐ€ ์ ํ•ฉ๋„ ์ ๊ฒ€ (์ž์ฒด ๋ณด๊ด€์šฉ / ์—‘์…€ ์ถœ๋ ฅ ์ œ์™ธ)")
750
+ e1, e2, e3 = st.columns(3)
751
+ e1.metric("ํ‰๊ฐ€ ์ ํ•ฉ๋„", f"{score}์ ")
752
+ e2.metric("๋ถ€์กฑ ํ•ญ๋ชฉ", data["missing_items"])
753
+ e3.metric("๊ฒฐ์žฌ๋ผ์ธ", "๋‹ด๋‹น์ž/์‹œ์„ค์žฅ 2์นธ")
754
+
755
+ if score >= 90:
756
+ st.markdown(f'<div class="okbox">{compliance}</div>', unsafe_allow_html=True)
757
+ else:
758
+ st.markdown(f'<div class="warnbox">{compliance}</div>', unsafe_allow_html=True)
759
+
760
+ if st.button("๐Ÿ’พ ๊ต์œก๊ธฐ๋ก DB ์ €์žฅ", use_container_width=True):
761
+ save_education_record(data)
762
+ st.success("๊ต์œก๊ธฐ๋ก์ด ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
763
+
764
+ filename = f"๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ๊ต์œก์ผ์ง€_{data['education_date']}.xlsx"
765
+ st.download_button(
766
+ "๐Ÿ“ฅ ๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ ๊ต์œก์ผ์ง€ ์—‘์…€ ๋‹ค์šด๋กœ๋“œ",
767
+ data=generate_excel(data),
768
+ file_name=filename,
769
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
770
+ use_container_width=True,
771
+ )
772
+
773
+ # ํ•˜๋‹จ ์บก์…˜ ์ˆ˜์ •
774
+ st.caption("๊ธ‰์—ฌ์ œ๊ณต์ง€์นจ ๊ต์œก์ผ์ง€ ์ƒ์„ฑ๊ธฐ")