File size: 27,195 Bytes
2477ef1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
import os
import sqlite3
import random
import tempfile
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.drawing.image import Image as XLImage

st.set_page_config(page_title="μž¬λ‚œν›ˆλ ¨μΌμ§€", page_icon="🚨", layout="wide")

CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
DB_CANDIDATES = [
    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"),
]
DB_PATH = next((p for p in DB_CANDIDATES if os.path.exists(p)), None)
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")

st.markdown("""
<style>
.block-container { padding-top: 2rem; }
.hero {background: linear-gradient(135deg, #991B1B, #F97316); padding:30px; border-radius:22px; text-align:center; margin-bottom:22px;}
.hero h1 { color:white; margin:0; font-size:36px; }
.hero p { color:white; margin-top:10px; font-size:18px; }
.notice {background:#fff8e1; border-left:7px solid #f59e0b; padding:15px; border-radius:12px; margin-bottom:18px;}
.okbox {background:#e8f5e9; border-left:7px solid #43a047; padding:14px; border-radius:12px; margin-bottom:14px;}
.warnbox {background:#fff3e0; border-left:7px solid #fb8c00; padding:14px; border-radius:12px; margin-bottom:14px;}
.dangerbox {background:#ffebee; border-left:7px solid #e53935; padding:14px; border-radius:12px; margin-bottom:14px;}
</style>
""", unsafe_allow_html=True)

st.markdown("""
<div class="hero">
<h1>🚨 μž¬λ‚œν›ˆλ ¨μΌμ§€ 생성기</h1>
<p>ν™”μž¬Β·μ§€μ§„Β·κ°μ—Όλ³‘Β·μ‘κΈ‰μƒν™© ν›ˆλ ¨μ„ μ‹œλ‚˜λ¦¬μ˜€, μ—­ν• λΆ„λ‹΄, 결과평가, κ°œμ„ μ‘°μΉ˜κΉŒμ§€ κΈ°λ‘ν•©λ‹ˆλ‹€.</p>
</div>
""", unsafe_allow_html=True)

st.markdown("""
<div class="notice"><strong>ν•„μˆ˜ ꡬ쑰:</strong> ν›ˆλ ¨μΌμ‹œ, μž₯μ†Œ, ν›ˆλ ¨μ’…λ₯˜, μ°Έμ„μž, μ‹œλ‚˜λ¦¬μ˜€, μ—­ν• λΆ„λ‹΄, ν›ˆλ ¨κ³Όμ •, 결과평가, κ°œμ„ μ‘°μΉ˜, 사진첨뢀λ₯Ό ν¬ν•¨ν•©λ‹ˆλ‹€.</div>
""", unsafe_allow_html=True)

st.markdown("""
<div class="notice">
β€» μƒμ„±λœ λ¬Έμ„œλŠ” κ·ΈλŒ€λ‘œ μ‚¬μš©ν•˜μ…”λ„ 되며, κΈ°κ΄€μ˜ 운영방침, μˆ˜κΈ‰μž μƒνƒœ, 보호자 의견 및 평가기쀀에 따라 μΆ”κ°€ 보완사항은 μ–Όλ§ˆλ“ μ§€ μˆ˜μ • κ°€λŠ₯ν•©λ‹ˆλ‹€. κΈ°κ΄€μ˜ 상황에 맞게 ν™œμš©ν•˜μ‹œκΈ° λ°”λžλ‹ˆλ‹€.
</div>
""", unsafe_allow_html=True)


def get_conn():
    return sqlite3.connect(DB_PATH, check_same_thread=False)


def init_db():
    conn = get_conn(); cur = conn.cursor()
    cur.execute("""
        CREATE TABLE IF NOT EXISTS disaster_training_records (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            institution_name TEXT,
            institution_type TEXT,
            training_title TEXT,
            training_type TEXT,
            training_date TEXT,
            training_time TEXT,
            training_place TEXT,
            supervisor TEXT,
            participants TEXT,
            scenario TEXT,
            role_division TEXT,
            training_process TEXT,
            evaluation_result TEXT,
            improvement_plan TEXT,
            emergency_contacts TEXT,
            evaluation_score INTEGER,
            missing_items TEXT,
            compliance_result TEXT,
            photo_count INTEGER,
            created_at TEXT
        )
    """)
    conn.commit(); conn.close()


def save_record(data):
    conn = get_conn(); cur = conn.cursor()
    cur.execute("""
        INSERT INTO disaster_training_records (
            institution_name, institution_type, training_title, training_type,
            training_date, training_time, training_place, supervisor,
            participants, scenario, role_division, training_process,
            evaluation_result, improvement_plan, emergency_contacts,
            evaluation_score, missing_items, compliance_result, photo_count, created_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    """, (
        data["institution_name"], data["institution_type"], data["training_title"], data["training_type"],
        data["training_date"], data["training_time"], data["training_place"], data["supervisor"],
        data["participants"], data["scenario"], data["role_division"], data["training_process"],
        data["evaluation_result"], data["improvement_plan"], data["emergency_contacts"],
        data["evaluation_score"], data["missing_items"], data["compliance_result"], data.get("photo_count", 0),
        datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    ))
    conn.commit(); conn.close()


def load_records():
    conn = get_conn()
    try:
        df = pd.read_sql_query("SELECT * FROM disaster_training_records ORDER BY id DESC", conn)
    except Exception:
        df = pd.DataFrame()
    conn.close(); return df


init_db()

TRAINING_TYPES = ["ν™”μž¬ λŒ€ν”Όν›ˆλ ¨", "μ§€μ§„ λŒ€ν”Όν›ˆλ ¨", "응급상황 λŒ€μ‘ν›ˆλ ¨", "감염병 λŒ€μ‘ν›ˆλ ¨", "μ •μ „/λ‹¨μˆ˜ λŒ€μ‘ν›ˆλ ¨", "μ‹€μ’…Β·λ°°νšŒ λŒ€μ‘ν›ˆλ ¨", "μ•Όκ°„ μž¬λ‚œμƒν™© λŒ€μ‘ν›ˆλ ¨", "기타"]
INSTITUTION_POINTS = {
    "λ°©λ¬Έμš”μ–‘": "μž¬κ°€ λ°©λ¬Έ 쀑 응급상황 발견 μ‹œ 119 μ‹ κ³ , 보호자 연락, κΈ°κ΄€ 보고, μ•ˆμ „ν™•λ³΄ 절차λ₯Ό μ€‘μ‹¬μœΌλ‘œ ν›ˆλ ¨ν•©λ‹ˆλ‹€.",
    "λ°©λ¬Έλͺ©μš•": "λͺ©μš• 쀑 낙상, ν˜Έν‘κ³€λž€, 피뢀손상 λ“± 상황 λ°œμƒ μ‹œ μ¦‰μ‹œ 쀑단, μ•ˆμ „ν™•λ³΄, 보호자 연락, κΈ°κ΄€ 보고λ₯Ό μ€‘μ‹¬μœΌλ‘œ ν›ˆλ ¨ν•©λ‹ˆλ‹€.",
    "λ°©λ¬Έκ°„ν˜Έ": "κ±΄κ°•μƒνƒœ κΈ‰λ³€, 감염 μ˜μ‹¬, μ‘κΈ‰μ²˜μΉ˜, μ˜λ£ŒκΈ°κ΄€ 연계 절차λ₯Ό μ€‘μ‹¬μœΌλ‘œ ν›ˆλ ¨ν•©λ‹ˆλ‹€.",
    "μ£Όκ°„λ³΄ν˜Έ": "μ„Όν„° λ‚΄ ν™”μž¬Β·μ§€μ§„Β·μ†‘μ˜ 쀑 사고·감염병 λ°œμƒ μ‹œ λŒ€ν”Όμœ λ„μ™€ λŒ€ν”Όμ·¨μ•½μž 지원을 μ€‘μ‹¬μœΌλ‘œ ν›ˆλ ¨ν•©λ‹ˆλ‹€.",
    "μš”μ–‘μ›": "μž…μ†Œμž λŒ€ν”Ό, μ•Όκ°„ 순찰, 측별 μ—­ν• λΆ„λ‹΄, μ‚°μ†ŒΒ·μΉ¨μƒΒ·νœ μ²΄μ–΄ 이용자의 이동지원을 μ€‘μ‹¬μœΌλ‘œ ν›ˆλ ¨ν•©λ‹ˆλ‹€.",
    "κ³΅λ™μƒν™œκ°€μ •": "μ†Œκ·œλͺ¨ μƒν™œκ³΅κ°„μ—μ„œ ν™”μž¬Β·μ•Όκ°„μ‘κΈ‰Β·κ³΅λ™μƒν™œ κ°ˆλ“± 상황 λ°œμƒ μ‹œ μ‹ μ†ν•œ 보고와 λŒ€ν”Όλ₯Ό μ€‘μ‹¬μœΌλ‘œ ν›ˆλ ¨ν•©λ‹ˆλ‹€.",
}

TRAINING_RULE_POINTS = {
    "ν™”μž¬ λŒ€ν”Όν›ˆλ ¨": ["초기 발견 및 μ „νŒŒ", "119 μ‹ κ³ ", "μ†Œν™”κΈ° μœ„μΉ˜ 확인", "λŒ€ν”Όμ·¨μ•½μž μš°μ„  이동", "λŒ€ν”Ό μ™„λ£Œ ν›„ 인원점검"],
    "μ§€μ§„ λŒ€ν”Όν›ˆλ ¨": ["λ‚™ν•˜λ¬Ό νšŒν”Ό", "책상·벽면 보호", "μΆœμž…λ¬Έ 개방", "μ•ˆμ „ν•œ μ™ΈλΆ€ μž₯μ†Œ 이동", "λΆ€μƒμž 확인"],
    "응급상황 λŒ€μ‘ν›ˆλ ¨": ["μ˜μ‹Β·ν˜Έν‘ 확인", "119 μ‹ κ³ ", "μ‹¬νμ†Œμƒμˆ  λ˜λŠ” μ‘κΈ‰μ²˜μΉ˜", "보호자 연락", "응급기둝 μž‘μ„±"],
    "감염병 λŒ€μ‘ν›ˆλ ¨": ["μ¦μƒμž 뢄리", "마슀크 및 보호ꡬ 착용", "μ†μœ„μƒ", "곡간 μ†Œλ…", "λ³΄κ±΄μ†Œ 및 보호자 보고"],
    "μ •μ „/λ‹¨μˆ˜ λŒ€μ‘ν›ˆλ ¨": ["비상쑰λͺ… 확인", "의료μž₯λΉ„ μ‚¬μš© μˆ˜κΈ‰μž 확인", "κΈ‰μˆ˜Β·κΈ‰μ‹ λŒ€μ²΄λ°©μ•ˆ", "μ•ˆμ „ 이동", "볡ꡬ상황 곡유"],
    "μ‹€μ’…Β·λ°°νšŒ λŒ€μ‘ν›ˆλ ¨": ["λ§ˆμ§€λ§‰ λͺ©κ²©μž₯μ†Œ 확인", "μ‹€λ‚΄μ™Έ μˆ˜μƒ‰", "보호자 연락", "112 μ‹ κ³  κ²€ν† ", "μž¬λ°œλ°©μ§€ 동선 점검"],
    "μ•Όκ°„ μž¬λ‚œμƒν™© λŒ€μ‘ν›ˆλ ¨": ["μ•Όκ°„ 순찰", "μ΅œμ†ŒμΈλ ₯ μ—­ν• λΆ„λ‹΄", "λŒ€ν”Όμ·¨μ•½μž μš°μ„  지원", "비상연락망 가동", "μΈμˆ˜μΈκ³„ 기둝"],
    "기타": ["상황 μ „νŒŒ", "μ•ˆμ „ 확보", "μ—­ν• λΆ„λ‹΄", "관계기관 연락", "결과평가 및 κ°œμ„ μ‘°μΉ˜"],
}


def build_rule_application(training_type, institution_type):
    points = TRAINING_RULE_POINTS.get(training_type, TRAINING_RULE_POINTS["기타"])
    institution_point = INSTITUTION_POINTS.get(institution_type, "κΈ°κ΄€ 상황에 λ§žλŠ” μž¬λ‚œμƒν™© λŒ€μ‘μ ˆμ°¨λ₯Ό μ€‘μ‹¬μœΌλ‘œ ν›ˆλ ¨ν•©λ‹ˆλ‹€.")
    lines = [
        "[μž¬λ‚œν›ˆλ ¨κ·œμΉ™ μ μš©μ‚¬ν•­]",
        f"- ν›ˆλ ¨μœ ν˜•λ³„ 쀑점: {', '.join(points)}",
        f"- κΈ°κ΄€μœ ν˜•λ³„ 쀑점: {institution_point}",
        "- μ—­ν• λΆ„λ‹΄: 총괄, 신고·연락, λŒ€ν”Όμœ λ„, λŒ€ν”Όμ·¨μ•½μž 지원, μ‘κΈ‰μ²˜μΉ˜, 기둝 담당을 ꡬ뢄함",
        "- 결과평가: 잘된점, 미흑점, κ°œμ„ μ‘°μΉ˜, 보좩ꡐ윑 ν•„μš” μ—¬λΆ€λ₯Ό 기둝함",
        "- ν‰κ°€λŒ€μ‘: 사진첨뢀, 비상연락망, μ°Έμ„μž λͺ…단, ν›ˆλ ¨ 진행과정이 μ„œλ‘œ μΌμΉ˜ν•˜λ„λ‘ 점검함",
    ]
    return "\n".join(lines)


def save_uploaded_photos(files):
    paths = []
    for file in (files or [])[:3]:
        suffix = os.path.splitext(file.name)[1].lower() or ".png"
        temp_path = os.path.join(tempfile.gettempdir(), f"disaster_photo_{datetime.now().strftime('%Y%m%d%H%M%S%f')}{suffix}")
        with open(temp_path, "wb") as f:
            f.write(file.getbuffer())
        paths.append(temp_path)
    return paths


def default_scenario(training_type, institution_type):
    base = INSTITUTION_POINTS.get(institution_type, "κΈ°κ΄€ 상황에 λ§žλŠ” μž¬λ‚œμƒν™© λŒ€μ‘μ ˆμ°¨λ₯Ό μ€‘μ‹¬μœΌλ‘œ ν›ˆλ ¨ν•©λ‹ˆλ‹€.")
    if "ν™”μž¬" in training_type:
        situation = "κΈ°κ΄€ λ‚΄ ν™”μž¬ 경보가 λ°œμƒν•˜μ—¬ 초기 μ‹ κ³ , μˆ˜κΈ‰μž μ•ˆμ „ν™•μΈ, λŒ€ν”Όμœ λ„, 비상연락망 가동이 ν•„μš”ν•œ 상황을 가정함."
    elif "μ§€μ§„" in training_type:
        situation = "μ§€μ§„ λ°œμƒμœΌλ‘œ λ‚™ν•˜λ¬Όκ³Ό 이동 μœ„ν—˜μ΄ ν™•μΈλ˜μ–΄ 책상·벽면 보호, λŒ€ν”Όλ‘œ 확인, μ•ˆμ „ν•œ μž₯μ†Œ 이동이 ν•„μš”ν•œ 상황을 가정함."
    elif "응급" in training_type:
        situation = "μˆ˜κΈ‰μž ν˜Έν‘κ³€λž€ λ˜λŠ” μ˜μ‹μ €ν•˜κ°€ λ°œμƒν•˜μ—¬ 119 μ‹ κ³ , 보호자 연락, μ‘κΈ‰μ²˜μΉ˜, 기둝이 ν•„μš”ν•œ 상황을 가정함."
    elif "감염" in training_type:
        situation = "λ°œμ—΄Β·ν˜Έν‘κΈ° μ¦μƒμžκ°€ λ°œμƒν•˜μ—¬ 격리, 마슀크 착용, 보호자 μ•ˆλ‚΄, μ†Œλ… 및 보고가 ν•„μš”ν•œ 상황을 가정함."
    elif "μ•Όκ°„" in training_type:
        situation = "μ•Όκ°„ μ‹œκ°„λŒ€ ν™”μž¬ λ˜λŠ” 응급상황이 λ°œμƒν•˜μ—¬ μ΅œμ†Œ 인λ ₯으둜 μˆœνšŒν™•μΈ, λŒ€ν”Όμ·¨μ•½μž 지원, 비상연락망 가동이 ν•„μš”ν•œ 상황을 가정함."
    else:
        situation = "κΈ°κ΄€ 운영 쀑 예기치 λͺ»ν•œ μž¬λ‚œΒ·μ•ˆμ „μ‚¬κ³ κ°€ λ°œμƒν•œ 상황을 가정함."
    return f"{situation}\n{base}"


def build_role_division(institution_type, custom_roles):
    if custom_roles.strip():
        return custom_roles.strip()
    return """
μ‹œμ„€μž₯: ν›ˆλ ¨ 총괄, 비상연락망 가동, 관계기관 μ‹ κ³  및 ν›ˆλ ¨ κ²°κ³Ό 확인을 λ‹΄λ‹Ήν•œλ‹€.
μ‚¬νšŒλ³΅μ§€μ‚¬: μˆ˜κΈ‰μž λͺ…단 확인, 보호자 연락, λŒ€ν”Όμ™„λ£Œ μ—¬λΆ€ 확인, ν›ˆλ ¨κΈ°λ‘ μž‘μ„±μ„ λ‹΄λ‹Ήν•œλ‹€.
μš”μ–‘λ³΄ν˜Έμ‚¬: μˆ˜κΈ‰μž 이동보쑰, λŒ€ν”Όμ·¨μ•½μž 지원, λ‚™μƒμ˜ˆλ°©κ³Ό μ•ˆμ „ν™•μΈμ„ λ‹΄λ‹Ήν•œλ‹€.
κ°„ν˜Έ(쑰무)사: μ‘κΈ‰μƒνƒœ 확인, ν™œλ ₯μ§•ν›„ 확인, μ‘κΈ‰μ²˜μΉ˜ 및 μ˜λ£ŒκΈ°κ΄€ 연계 ν•„μš”μ„±μ„ ν™•μΈν•œλ‹€.
μš΄μ „μ›/기타직원: μ°¨λŸ‰Β·μΆœμž…κ΅¬Β·λŒ€ν”Όλ‘œ 확보, μ†Œν™”κΈ° μœ„μΉ˜ 확인, μ™ΈλΆ€ 이동 μ•ˆμ „μ„ μ§€μ›ν•œλ‹€.
""".strip()


def build_training_text(data):
    return f"""
[ν›ˆλ ¨ κ°œμš”]
- ν›ˆλ ¨λͺ…: {data['training_title']}
- ν›ˆλ ¨μ’…λ₯˜: {data['training_type']}
- ν›ˆλ ¨μΌμ‹œ: {data['training_date']} {data['training_time']}
- ν›ˆλ ¨μž₯μ†Œ: {data['training_place']}
- μ΄κ΄„μ±…μž„μž: {data['supervisor']}

[ν›ˆλ ¨ μ‹œλ‚˜λ¦¬μ˜€]
{data['scenario']}

[μ—­ν• λΆ„λ‹΄]
{data['role_division']}

[ν›ˆλ ¨ μ§„ν–‰κ³Όμ •]
{data['training_process']}

[비상연락망]
{data['emergency_contacts']}

{data.get('rule_application', build_rule_application(data['training_type'], data['institution_type']))}
""".strip()


def evaluate_record(data):
    required = {
        "ν›ˆλ ¨λͺ…": bool(data["training_title"].strip()),
        "ν›ˆλ ¨μΌμ‹œ": bool(data["training_date"]) and bool(data["training_time"].strip()),
        "ν›ˆλ ¨μž₯μ†Œ": bool(data["training_place"].strip()),
        "μ°Έμ„μž": bool(data["participants"].strip()),
        "μ‹œλ‚˜λ¦¬μ˜€": bool(data["scenario"].strip()),
        "μ—­ν• λΆ„λ‹΄": bool(data["role_division"].strip()),
        "ν›ˆλ ¨κ³Όμ •": bool(data["training_process"].strip()),
        "결과평가": bool(data["evaluation_result"].strip()),
        "κ°œμ„ μ‘°μΉ˜": bool(data["improvement_plan"].strip()),
        "비상연락망": bool(data["emergency_contacts"].strip()),
        "μž¬λ‚œν›ˆλ ¨κ·œμΉ™": bool(data.get("rule_application", "").strip()),
    }
    score = int(sum(required.values()) / len(required) * 100)
    missing = [k for k, v in required.items() if not v]
    if score >= 90:
        msg = "평가 적합도가 λ†’μŠ΅λ‹ˆλ‹€. ν›ˆλ ¨ μ‹œλ‚˜λ¦¬μ˜€, μ—­ν• λΆ„λ‹΄, μ§„ν–‰κ³Όμ •, 결과평가, κ°œμ„ μ‘°μΉ˜κ°€ 잘 λ°˜μ˜λ˜μ—ˆμŠ΅λ‹ˆλ‹€."
    elif score >= 75:
        msg = "λŒ€μ²΄λ‘œ μ ν•©ν•˜λ‚˜ 일뢀 보완이 ν•„μš”ν•©λ‹ˆλ‹€. λΆ€μ‘± ν•­λͺ©μ„ λ³΄μ™„ν•˜λ©΄ 평가 λŒ€μ‘λ ₯이 λ†’μ•„μ§‘λ‹ˆλ‹€."
    else:
        msg = "보완이 ν•„μš”ν•©λ‹ˆλ‹€. ν›ˆλ ¨μΌμ§€μ—λŠ” μ‹œλ‚˜λ¦¬μ˜€, μ°Έμ„μž, μ—­ν• λΆ„λ‹΄, 결과평가, κ°œμ„ μ‘°μΉ˜κ°€ ꡬ체적으둜 λ“€μ–΄κ°€μ•Ό ν•©λ‹ˆλ‹€."
    return score, missing, msg


def _border():
    s = Side(style="thin", color="000000")
    return Border(left=s, right=s, top=s, bottom=s)


def style_range(ws, min_row, max_row, min_col=1, max_col=9, fill=None, font=None, alignment=None):
    for row in ws.iter_rows(min_row=min_row, max_row=max_row, min_col=min_col, max_col=max_col):
        for cell in row:
            cell.border = _border()
            if fill is not None:
                cell.fill = fill
            if font is not None:
                cell.font = font
            if alignment is not None:
                cell.alignment = alignment


def setup_print(ws, max_row):
    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.25
    ws.page_margins.right = 0.25
    ws.page_margins.top = 0.35
    ws.page_margins.bottom = 0.35
    ws.sheet_view.showGridLines = False
    ws.print_area = f"A1:I{max_row}"
    for col in range(1, 10):
        ws.column_dimensions[get_column_letter(col)].width = 12


def write_title(ws, title, title_fill, header_fill):
    ws.merge_cells(start_row=1, start_column=1, end_row=2, end_column=7)
    ws.cell(1, 1).value = title
    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, 1, 2, 1, 7, fill=title_fill, font=Font(name="Malgun Gothic", size=24, bold=True), alignment=Alignment(horizontal="center", vertical="center", wrap_text=True))
    ws.cell(1, 8).value = "λ‹΄λ‹Ήμž"; ws.cell(1, 9).value = "μ‹œμ„€μž₯"
    ws.cell(2, 8).value = ""; ws.cell(2, 9).value = ""
    style_range(ws, 1, 1, 8, 9, fill=header_fill, font=Font(name="Malgun Gothic", size=11, bold=True), alignment=Alignment(horizontal="center", vertical="center"))
    style_range(ws, 2, 2, 8, 9, font=Font(name="Malgun Gothic", size=11), alignment=Alignment(horizontal="center", vertical="center"))
    ws.row_dimensions[1].height = 28; ws.row_dimensions[2].height = 42


def write_info(ws, r, a, b, c, d, e, f, header_fill):
    ws.cell(r, 1).value = a; ws.merge_cells(start_row=r, start_column=2, end_row=r, end_column=3); ws.cell(r, 2).value = b
    ws.cell(r, 4).value = c; ws.merge_cells(start_row=r, start_column=5, end_row=r, end_column=6); ws.cell(r, 5).value = d
    ws.cell(r, 7).value = e; ws.merge_cells(start_row=r, start_column=8, end_row=r, end_column=9); ws.cell(r, 8).value = f
    for col in [1, 4, 7]:
        ws.cell(r, col).fill = header_fill; ws.cell(r, col).font = Font(name="Malgun Gothic", size=10, bold=True); ws.cell(r, col).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
    for col in [2, 5, 8]:
        ws.cell(r, col).font = Font(name="Malgun Gothic", size=10); ws.cell(r, col).alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
    style_range(ws, r, r, 1, 9); ws.row_dimensions[r].height = 28


def height_for_text(text, chars=58, max_height=420):
    lines = 0
    for line in str(text or "").split("\n"):
        lines += max(1, len(line) // chars + 1)
    return min(max(70, lines * 18 + 18), max_height)


def write_section(ws, r, title, content, header_fill, min_height=90):
    ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=9)
    ws.cell(r, 1).value = title
    style_range(ws, r, r, 1, 9, fill=header_fill, font=Font(name="Malgun Gothic", size=13, bold=True), alignment=Alignment(horizontal="center", vertical="center", wrap_text=True))
    ws.row_dimensions[r].height = 28
    r += 1
    ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=9)
    ws.cell(r, 1).value = str(content or "")
    style_range(ws, r, r, 1, 9, font=Font(name="Malgun Gothic", size=10), alignment=Alignment(horizontal="left", vertical="top", wrap_text=True))
    ws.row_dimensions[r].height = max(min_height, height_for_text(content))
    return r + 1


def write_internal_sheet(wb, data, header_fill):
    ws = wb.create_sheet("내뢀점검_좜λ ₯μ œμ™Έ")
    ws.sheet_state = "hidden"
    rows = [("평가 적합도", f"{data['evaluation_score']}점"), ("λΆ€μ‘± ν•­λͺ©", data["missing_items"]), ("점검 의견", data["compliance_result"])]
    for i, (a, b) in enumerate(rows, 1):
        ws.cell(i, 1).value = a; ws.cell(i, 2).value = b
        ws.cell(i, 1).font = Font(name="Malgun Gothic", size=10, bold=True); ws.cell(i, 1).fill = header_fill
        ws.cell(i, 2).font = Font(name="Malgun Gothic", size=10); ws.cell(i, 2).alignment = Alignment(wrap_text=True, vertical="top")
    ws.column_dimensions["A"].width = 18; ws.column_dimensions["B"].width = 90


def generate_excel(data):
    wb = Workbook()
    title_fill = PatternFill("solid", fgColor="FEE2E2")
    header_fill = PatternFill("solid", fgColor="F3F4F6")
    ws = wb.active
    ws.title = "μž¬λ‚œν›ˆλ ¨μΌμ§€"
    write_title(ws, "μž¬λ‚œν›ˆλ ¨μΌμ§€", title_fill, header_fill)
    r = 3
    write_info(ws, r, "κΈ°κ΄€λͺ…", data["institution_name"], "κΈ°κ΄€μœ ν˜•", data["institution_type"], "ν›ˆλ ¨μΌμž", data["training_date"], header_fill); r += 1
    write_info(ws, r, "ν›ˆλ ¨λͺ…", data["training_title"], "ν›ˆλ ¨μ’…λ₯˜", data["training_type"], "ν›ˆλ ¨μ‹œκ°„", data["training_time"], header_fill); r += 1
    write_info(ws, r, "μž₯μ†Œ", data["training_place"], "총괄", data["supervisor"], "사진", f"{data.get('photo_count', 0)}μž₯", header_fill); r += 1
    r = write_section(ws, r, "μ°Έμ„μž λͺ…단", data["participants"], header_fill, 70)
    r = write_section(ws, r, "ν›ˆλ ¨ κΈ°λ‘λ‚΄μš©", build_training_text(data), header_fill, 260)
    r = write_section(ws, r, "ν›ˆλ ¨ 결과평가", data["evaluation_result"], header_fill, 100)
    r = write_section(ws, r, "κ°œμ„ μ‘°μΉ˜ 및 ν›„μ†κ³„νš", data["improvement_plan"], header_fill, 100)
    r = write_section(ws, r, "μž¬λ‚œν›ˆλ ¨κ·œμΉ™ μ μš©μ‚¬ν•­", data.get("rule_application", ""), header_fill, 90)
    if data.get("photo_paths"):
        ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=9)
        ws.cell(r, 1).value = "ν›ˆλ ¨μ‚¬μ§„"
        style_range(ws, r, r, 1, 9, fill=header_fill, font=Font(name="Malgun Gothic", size=13, bold=True), alignment=Alignment(horizontal="center", vertical="center"))
        r += 1
        anchors = ["A", "D", "G"]
        img_row = r
        for idx, photo_path in enumerate(data.get("photo_paths", [])[:3]):
            try:
                img = XLImage(photo_path); img.width = 210; img.height = 150; ws.add_image(img, f"{anchors[idx]}{img_row}")
            except Exception:
                pass
        style_range(ws, img_row, img_row + 7, 1, 9)
        for rr in range(img_row, img_row + 8): ws.row_dimensions[rr].height = 22
        r = img_row + 8
    ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=9)
    ws.cell(r, 1).value = "β€» μƒμ„±λœ λ¬Έμ„œλŠ” κ·ΈλŒ€λ‘œ μ‚¬μš©ν•˜μ…”λ„ 되며, κΈ°κ΄€μ˜ 운영방침, μˆ˜κΈ‰μž μƒνƒœ, 보호자 의견 및 평가기쀀에 따라 μΆ”κ°€ 보완사항은 μ–Όλ§ˆλ“ μ§€ μˆ˜μ • κ°€λŠ₯ν•©λ‹ˆλ‹€. κΈ°κ΄€μ˜ 상황에 맞게 ν™œμš©ν•˜μ‹œκΈ° λ°”λžλ‹ˆλ‹€."
    style_range(ws, r, r, 1, 9,
                font=Font(name="Malgun Gothic", size=8.5, color="595959", italic=True),
                alignment=Alignment(horizontal="left", vertical="center", wrap_text=True))
    ws.row_dimensions[r].height = 36
    r += 1
    setup_print(ws, r - 1)
    write_internal_sheet(wb, data, header_fill)
    output = BytesIO(); wb.save(output); output.seek(0); return output


menu = st.sidebar.radio("메뉴", ["μž¬λ‚œν›ˆλ ¨μΌμ§€ 생성", "μ €μž₯기둝 보기"])
st.sidebar.success("DB μ—°κ²°"); st.sidebar.caption(DB_PATH)

if menu == "μ €μž₯기둝 보기":
    st.subheader("πŸ“š μ €μž₯된 μž¬λ‚œν›ˆλ ¨ 기둝")
    records = load_records()
    if records.empty: st.info("μ €μž₯된 μž¬λ‚œν›ˆλ ¨ 기둝이 μ—†μŠ΅λ‹ˆλ‹€.")
    else: st.dataframe(records, use_container_width=True)

else:
    st.subheader("🚨 μž¬λ‚œν›ˆλ ¨μΌμ§€ 생성")
    c1, c2, c3 = st.columns(3)
    institution_name = c1.text_input("κΈ°κ΄€λͺ…", "μž₯κΈ°μš”μ–‘κΈ°κ΄€")
    institution_type = c2.selectbox("κΈ°κ΄€μœ ν˜•", ["λ°©λ¬Έμš”μ–‘", "λ°©λ¬Έλͺ©μš•", "λ°©λ¬Έκ°„ν˜Έ", "μ£Όκ°„λ³΄ν˜Έ", "μš”μ–‘μ›", "κ³΅λ™μƒν™œκ°€μ •"])
    training_title = c3.text_input("ν›ˆλ ¨λͺ…", "μž¬λ‚œμƒν™© λŒ€μ‘ν›ˆλ ¨")
    c4, c5, c6 = st.columns(3)
    training_type = c4.selectbox("ν›ˆλ ¨μ’…λ₯˜", TRAINING_TYPES)
    training_date = c5.date_input("ν›ˆλ ¨μΌμž", value=date.today())
    training_time = c6.text_input("ν›ˆλ ¨μ‹œκ°„", "14:00~14:40")
    c7, c8 = st.columns(2)
    training_place = c7.text_input("ν›ˆλ ¨μž₯μ†Œ", "κΈ°κ΄€ λ‚΄μ™ΈλΆ€ 및 λŒ€ν”Όμž₯μ†Œ")
    supervisor = c8.text_input("μ΄κ΄„μ±…μž„μž", "μ‹œμ„€μž₯")
    participants = st.text_area("μ°Έμ„μž λͺ…단", placeholder="예: μ‹œμ„€μž₯ 홍길동, μ‚¬νšŒλ³΅μ§€μ‚¬ 김볡지, μš”μ–‘λ³΄ν˜Έμ‚¬ μ΄μš”μ–‘ μ™Έ 8λͺ…", height=80)
    scenario = st.text_area("ν›ˆλ ¨ μ‹œλ‚˜λ¦¬μ˜€", value=default_scenario(training_type, institution_type), height=120)
    role_division = st.text_area("μ—­ν• λΆ„λ‹΄", value=build_role_division(institution_type, ""), height=130)
    default_process = """1. ν›ˆλ ¨ μ‹œμž‘ μ „ ν›ˆλ ¨ λͺ©μ κ³Ό μ•ˆμ „μˆ˜μΉ™μ„ μ•ˆλ‚΄ν•˜μ˜€λ‹€.
2. μž¬λ‚œμƒν™© λ°œμƒμ„ κ°€μ •ν•˜κ³  졜초 발견자 μ‹ κ³  및 κΈ°κ΄€ λ‚΄ 보고λ₯Ό μ‹€μ‹œν•˜μ˜€λ‹€.
3. μˆ˜κΈ‰μž μƒνƒœμ™€ λŒ€ν”Όμ·¨μ•½μžλ₯Ό ν™•μΈν•˜κ³  역할뢄담에 따라 μ•ˆμ „ν•œ μž₯μ†Œλ‘œ 이동을 μ§€μ›ν•˜μ˜€λ‹€.
4. 119 μ‹ κ³ , 보호자 연락, 비상연락망 가동 절차λ₯Ό ν™•μΈν•˜μ˜€λ‹€.
5. λŒ€ν”Ό μ™„λ£Œ ν›„ 인원 확인, νŠΉμ΄μ‚¬ν•­ 점검, ν›ˆλ ¨ 결과평가λ₯Ό μ‹€μ‹œν•˜μ˜€λ‹€."""
    training_process = st.text_area("ν›ˆλ ¨ μ§„ν–‰κ³Όμ •", value=default_process, height=150)
    emergency_contacts = st.text_area("비상연락망", value="119, 보호자 μ—°λ½μ²˜, μ‹œμ„€μž₯, μ‚¬νšŒλ³΅μ§€μ‚¬, κ΄€ν•  μ§€μžμ²΄, ν˜‘λ ₯μ˜λ£ŒκΈ°κ΄€ 연락망을 확인함.", height=80)
    rule_application = st.text_area("μž¬λ‚œν›ˆλ ¨κ·œμΉ™ μ μš©μ‚¬ν•­", value=build_rule_application(training_type, institution_type), height=120)
    evaluation_result = st.text_area("ν›ˆλ ¨ 결과평가", value="μ°Έμ„μžλ“€μ΄ μž¬λ‚œμƒν™© λ°œμƒ μ‹œ μ‹ κ³ , λŒ€ν”Όμœ λ„, 인원확인, 보호자 연락 절차λ₯Ό μˆ™μ§€ν•˜μ˜€λ‹€. λŒ€ν”Όμ·¨μ•½μž 지원과 비상연락망 가동 절차λ₯Ό μž¬ν™•μΈν•˜μ˜€λ‹€.", height=110)
    improvement_plan = st.text_area("κ°œμ„ μ‘°μΉ˜ 및 ν›„μ†κ³„νš", value="λŒ€ν”Όλ‘œ μž₯애물을 μ •κΈ° μ κ²€ν•˜κ³ , μ‹ κ·œμ§μ› 및 λ―Έμ°Έμ„μžμ—κ²Œ λ³΄μΆ©κ΅μœ‘μ„ μ‹€μ‹œν•œλ‹€. λ‹€μŒ ν›ˆλ ¨ μ‹œ 역할별 μˆ˜ν–‰μ‹œκ°„κ³Ό λŒ€ν”Όμ·¨μ•½μž 지원 절차λ₯Ό μΆ”κ°€ μ κ²€ν•œλ‹€.", height=110)
    photos = st.file_uploader("ν›ˆλ ¨μ‚¬μ§„ 첨뢀 (μ΅œλŒ€ 3μž₯)", type=["png", "jpg", "jpeg"], accept_multiple_files=True)

    if st.button("🚨 μž¬λ‚œν›ˆλ ¨μΌμ§€ 생성", use_container_width=True):
        photo_paths = save_uploaded_photos(photos)
        data = {
            "institution_name": institution_name,
            "institution_type": institution_type,
            "training_title": training_title,
            "training_type": training_type,
            "training_date": str(training_date),
            "training_time": training_time,
            "training_place": training_place,
            "supervisor": supervisor,
            "participants": participants,
            "scenario": scenario,
            "role_division": role_division,
            "training_process": training_process,
            "evaluation_result": evaluation_result,
            "improvement_plan": improvement_plan,
            "emergency_contacts": emergency_contacts,
            "rule_application": rule_application,
            "photo_paths": photo_paths,
            "photo_count": len(photo_paths),
        }
        score, missing, compliance = evaluate_record(data)
        data["evaluation_score"] = score
        data["missing_items"] = ", ".join(missing) if missing else "λΆ€μ‘± ν•­λͺ© μ—†μŒ"
        data["compliance_result"] = compliance
        st.session_state["disaster_data"] = data

    if "disaster_data" in st.session_state:
        data = st.session_state["disaster_data"]
        st.markdown("---"); st.subheader("생성 κ²°κ³Ό μˆ˜μ •")
        data["scenario"] = st.text_area("μ‹œλ‚˜λ¦¬μ˜€ μˆ˜μ •", value=data["scenario"], height=120)
        data["role_division"] = st.text_area("μ—­ν• λΆ„λ‹΄ μˆ˜μ •", value=data["role_division"], height=130)
        data["training_process"] = st.text_area("ν›ˆλ ¨ μ§„ν–‰κ³Όμ • μˆ˜μ •", value=data["training_process"], height=150)
        data["evaluation_result"] = st.text_area("결과평가 μˆ˜μ •", value=data["evaluation_result"], height=110)
        data["improvement_plan"] = st.text_area("κ°œμ„ μ‘°μΉ˜ μˆ˜μ •", value=data["improvement_plan"], height=110)
        data["rule_application"] = st.text_area("μž¬λ‚œν›ˆλ ¨κ·œμΉ™ μ μš©μ‚¬ν•­ μˆ˜μ •", value=data.get("rule_application", ""), height=120)
        score, missing, compliance = evaluate_record(data)
        data["evaluation_score"] = score
        data["missing_items"] = ", ".join(missing) if missing else "λΆ€μ‘± ν•­λͺ© μ—†μŒ"
        data["compliance_result"] = compliance
        e1, e2, e3 = st.columns(3)
        e1.metric("평가 적합도", f"{score}점"); e2.metric("λΆ€μ‘± ν•­λͺ©", data["missing_items"]); e3.metric("사진", f"{data.get('photo_count', 0)}μž₯")
        if score >= 90: st.markdown(f'<div class="okbox">{compliance}</div>', unsafe_allow_html=True)
        else: st.markdown(f'<div class="warnbox">{compliance}</div>', unsafe_allow_html=True)
        if st.button("πŸ’Ύ μž¬λ‚œν›ˆλ ¨ 기둝 DB μ €μž₯", use_container_width=True):
            save_record(data); st.success("μž¬λ‚œν›ˆλ ¨ 기둝이 μ €μž₯λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
        filename = f"μž¬λ‚œν›ˆλ ¨μΌμ§€_{data['institution_type']}_{data['training_date']}.xlsx"
        st.download_button("πŸ“₯ μž¬λ‚œν›ˆλ ¨μΌμ§€ μ—‘μ…€ λ‹€μš΄λ‘œλ“œ", data=generate_excel(data), file_name=filename, mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", use_container_width=True)

st.caption("μž¬λ‚œν›ˆλ ¨μΌμ§€ 생성기")