seawolf2357 commited on
Commit
4438a77
·
verified ·
1 Parent(s): e7f1a13

Create fund.py

Browse files
Files changed (1) hide show
  1. fund.py +1661 -0
fund.py ADDED
@@ -0,0 +1,1661 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 🎮 OKCEO 정책자금 사전심사 시스템 v7.0 - 미네랄핵 ULTRA 에디션
3
+ ================================================================================
4
+ 🆕 비주얼 차트 18개 (기존 6개 + 신규 12개)
5
+
6
+ [기존 차트 - 개선]
7
+ 1. 업종별 재무비율 벤치마크 테이블
8
+ 2. 유의사항 체크리스트 대시보드
9
+ 3. 지원가능성 매트릭스
10
+ 4. 예상금액 워터폴 차트
11
+ 5. 프로세스 진행 타임라인
12
+ 6. 신용점수 시뮬레이션
13
+
14
+ [신규 차트 - 고급 비주얼] ⭐
15
+ 7. 종합점수 3D 게이지 (애니메이션)
16
+ 8. 재무건전성 레이더 차트 (SVG 애니메이션)
17
+ 9. 기관별 승인확률 도넛 차트 (인터랙티브)
18
+ 10. 자금조달 파이프라인 플로우
19
+ 11. 리스크 히트맵 (카테고리별)
20
+ 12. 경쟁력 벤치마크 바 차트
21
+ 13. 💎 스코어카드 대시보드 (KPI)
22
+ 14. 💎 자금흐름 산키 다이어그램
23
+ 15. 💎 월별 추세 라인 차트
24
+ 16. 💎 기관비교 레이더 오버레이
25
+ 17. 💎 성공/실패 요인 트리맵
26
+ 18. 💎 종합 인포그래픽 보드
27
+
28
+ ================================================================================
29
+ """
30
+
31
+ import gradio as gr
32
+ from dataclasses import dataclass, field
33
+ from typing import Dict, List, Optional, Any, Tuple
34
+ from datetime import datetime, date
35
+ import json
36
+ import math
37
+
38
+ # ============================================================================
39
+ # cache_db 연동 (app.py 통합 시 DB 저장/불러오기)
40
+ # ============================================================================
41
+ try:
42
+ from cache_db import get_fund_cache
43
+ _fund_cache = get_fund_cache()
44
+ HAS_CACHE_DB = True
45
+ except ImportError:
46
+ HAS_CACHE_DB = False
47
+ _fund_cache = None
48
+
49
+ # ============================================================================
50
+ # CUSTOM CSS - 울트라 다크 메탈릭 + 네온 글로우 테마
51
+ # ============================================================================
52
+
53
+ CUSTOM_CSS = """
54
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&family=Orbitron:wght@400;700;900&family=Rajdhani:wght@500;700&display=swap');
55
+
56
+ header,.huggingface-space-header,footer{display:none!important}
57
+
58
+ :root {
59
+ --neon-green: #6fd9a8;
60
+ --neon-blue: #6495ed;
61
+ --neon-purple: #9b59b6;
62
+ --neon-orange: #ff9500;
63
+ --neon-red: #ff6b6b;
64
+ --neon-yellow: #ffd93d;
65
+ --neon-cyan: #00d4ff;
66
+ --bg-dark: #0d0d1a;
67
+ --bg-card: #1a1a2e;
68
+ --bg-panel: #252540;
69
+ --text-primary: #e8e8f0;
70
+ --text-secondary: #8888a0;
71
+ --glow-green: rgba(111,217,168,0.5);
72
+ --glow-blue: rgba(100,149,237,0.5);
73
+ }
74
+
75
+ html,body{background:var(--bg-dark)!important;color:var(--text-primary)!important;}
76
+
77
+ .gradio-container{
78
+ font-family:'Noto Sans KR','Rajdhani',sans-serif!important;
79
+ background:linear-gradient(135deg,#0d1117 0%,#161b22 50%,#0d1117 100%)!important;
80
+ max-width:100%!important;padding:20px!important;min-height:100vh;
81
+ }
82
+
83
+ /* ============ 네온 글로우 애니메이션 ============ */
84
+ @keyframes neon-pulse {
85
+ 0%, 100% {
86
+ box-shadow: 0 0 5px var(--neon-green), 0 0 10px var(--neon-green), 0 0 20px var(--glow-green);
87
+ border-color: var(--neon-green);
88
+ }
89
+ 50% {
90
+ box-shadow: 0 0 10px var(--neon-green), 0 0 20px var(--neon-green), 0 0 40px var(--glow-green);
91
+ border-color: #8fe8c0;
92
+ }
93
+ }
94
+
95
+ @keyframes rotate-3d {
96
+ 0% { transform: perspective(500px) rotateY(0deg); }
97
+ 100% { transform: perspective(500px) rotateY(360deg); }
98
+ }
99
+
100
+ @keyframes fill-bar {
101
+ from { width: 0%; }
102
+ }
103
+
104
+ @keyframes count-up {
105
+ from { opacity: 0; transform: scale(0.5) translateY(20px); }
106
+ to { opacity: 1; transform: scale(1) translateY(0); }
107
+ }
108
+
109
+ @keyframes slide-up {
110
+ from { opacity: 0; transform: translateY(30px); }
111
+ to { opacity: 1; transform: translateY(0); }
112
+ }
113
+
114
+ @keyframes shimmer {
115
+ 0% { background-position: -200% center; }
116
+ 100% { background-position: 200% center; }
117
+ }
118
+
119
+ @keyframes float {
120
+ 0%, 100% { transform: translateY(0); }
121
+ 50% { transform: translateY(-10px); }
122
+ }
123
+
124
+ @keyframes radar-scan {
125
+ 0% { transform: rotate(0deg); opacity: 0.8; }
126
+ 100% { transform: rotate(360deg); opacity: 0.8; }
127
+ }
128
+
129
+ @keyframes pulse-ring {
130
+ 0% { transform: scale(0.8); opacity: 1; }
131
+ 100% { transform: scale(1.5); opacity: 0; }
132
+ }
133
+
134
+ @keyframes glow-text {
135
+ 0%, 100% { text-shadow: 0 0 10px var(--neon-green), 0 0 20px var(--neon-green); }
136
+ 50% { text-shadow: 0 0 20px var(--neon-green), 0 0 40px var(--neon-green), 0 0 60px var(--neon-green); }
137
+ }
138
+
139
+ @keyframes border-flow {
140
+ 0% { border-color: var(--neon-green); }
141
+ 33% { border-color: var(--neon-blue); }
142
+ 66% { border-color: var(--neon-purple); }
143
+ 100% { border-color: var(--neon-green); }
144
+ }
145
+
146
+ /* ============ 차트 컨테이너 스타일 ============ */
147
+ .chart-ultra {
148
+ background: linear-gradient(145deg, #1a1a2e 0%, #0d0d1a 100%)!important;
149
+ border-radius: 20px!important;
150
+ padding: 28px!important;
151
+ margin: 16px 0!important;
152
+ border: 1px solid rgba(111,217,168,0.2)!important;
153
+ box-shadow: 0 10px 40px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.05)!important;
154
+ position: relative;
155
+ overflow: hidden;
156
+ }
157
+
158
+ .chart-ultra::before {
159
+ content: '';
160
+ position: absolute;
161
+ top: 0;
162
+ left: 0;
163
+ right: 0;
164
+ height: 1px;
165
+ background: linear-gradient(90deg, transparent, var(--neon-green), transparent);
166
+ }
167
+
168
+ .chart-ultra:hover {
169
+ border-color: rgba(111,217,168,0.4)!important;
170
+ box-shadow: 0 15px 50px rgba(0,0,0,0.6), 0 0 30px rgba(111,217,168,0.1)!important;
171
+ }
172
+
173
+ /* ============ 스코어카드 스타일 ============ */
174
+ .score-card {
175
+ background: linear-gradient(145deg, #252540, #1a1a2e);
176
+ border-radius: 16px;
177
+ padding: 24px;
178
+ text-align: center;
179
+ border: 1px solid rgba(255,255,255,0.1);
180
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
181
+ position: relative;
182
+ overflow: hidden;
183
+ }
184
+
185
+ .score-card::after {
186
+ content: '';
187
+ position: absolute;
188
+ top: -50%;
189
+ left: -50%;
190
+ width: 200%;
191
+ height: 200%;
192
+ background: linear-gradient(45deg, transparent, rgba(255,255,255,0.03), transparent);
193
+ transform: rotate(45deg);
194
+ transition: all 0.5s;
195
+ }
196
+
197
+ .score-card:hover {
198
+ transform: translateY(-8px) scale(1.02);
199
+ border-color: var(--neon-green);
200
+ box-shadow: 0 20px 40px rgba(0,0,0,0.4), 0 0 30px rgba(111,217,168,0.2);
201
+ }
202
+
203
+ .score-card:hover::after {
204
+ left: 100%;
205
+ }
206
+
207
+ .score-card.highlight {
208
+ animation: neon-pulse 2s ease-in-out infinite;
209
+ }
210
+
211
+ .score-card .value {
212
+ font-family: 'Orbitron', sans-serif;
213
+ font-size: 42px;
214
+ font-weight: 900;
215
+ background: linear-gradient(135deg, #fff, var(--neon-green));
216
+ -webkit-background-clip: text;
217
+ -webkit-text-fill-color: transparent;
218
+ animation: count-up 0.8s ease-out;
219
+ }
220
+
221
+ .score-card .label {
222
+ color: var(--text-secondary);
223
+ font-size: 13px;
224
+ margin-top: 8px;
225
+ text-transform: uppercase;
226
+ letter-spacing: 1px;
227
+ }
228
+
229
+ /* ============ 프로그레스 바 ============ */
230
+ .progress-ultra {
231
+ height: 12px;
232
+ background: #1a1a2e;
233
+ border-radius: 6px;
234
+ overflow: hidden;
235
+ box-shadow: inset 0 2px 4px rgba(0,0,0,0.5);
236
+ }
237
+
238
+ .progress-ultra .fill {
239
+ height: 100%;
240
+ border-radius: 6px;
241
+ background: linear-gradient(90deg, var(--neon-green), #8fe8c0, var(--neon-green));
242
+ background-size: 200% 100%;
243
+ animation: shimmer 2s linear infinite, fill-bar 1s ease-out;
244
+ box-shadow: 0 0 10px var(--glow-green);
245
+ }
246
+
247
+ /* ============ 버튼 스타일 ============ */
248
+ button,.gr-button{
249
+ background: linear-gradient(145deg, #2a2a45, #1a1a30)!important;
250
+ color:var(--text-primary)!important;
251
+ border:1px solid rgba(111,217,168,0.3)!important;
252
+ border-radius:12px!important;
253
+ font-weight:600!important;
254
+ transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1)!important;
255
+ }
256
+
257
+ button:hover,.gr-button:hover{
258
+ background: linear-gradient(145deg, #3a3a55, #2a2a40)!important;
259
+ transform:translateY(-3px)!important;
260
+ box-shadow:0 10px 30px rgba(0,0,0,0.4), 0 0 20px rgba(111,217,168,0.2)!important;
261
+ border-color: var(--neon-green)!important;
262
+ }
263
+
264
+ .analyze-btn button{
265
+ background:linear-gradient(135deg,#1a6b4a,#3a8f6a,#6fd9a8)!important;
266
+ color:#fff!important;
267
+ font-size:18px!important;
268
+ padding:18px 50px!important;
269
+ border:2px solid var(--neon-green)!important;
270
+ text-shadow: 0 0 10px rgba(0,0,0,0.5);
271
+ }
272
+
273
+ .analyze-btn button:hover{
274
+ box-shadow:0 10px 40px rgba(111,217,168,0.4), 0 0 60px rgba(111,217,168,0.2)!important;
275
+ }
276
+
277
+ /* ============ 입력 필드 ============ */
278
+ input,textarea,select{
279
+ background:#1a1a2e!important;
280
+ color:var(--text-primary)!important;
281
+ border:1px solid rgba(111,217,168,0.2)!important;
282
+ border-radius:10px!important;
283
+ transition:all 0.3s ease!important;
284
+ }
285
+
286
+ input:focus,textarea:focus,select:focus{
287
+ border-color:var(--neon-green)!important;
288
+ box-shadow:0 0 20px rgba(111,217,168,0.2)!important;
289
+ outline:none!important;
290
+ }
291
+
292
+ /* ============ 탭 스타일 ============ */
293
+ [role="tablist"]{
294
+ background:transparent!important;
295
+ border-bottom:1px solid rgba(111,217,168,0.2)!important;
296
+ }
297
+
298
+ [role="tab"]{
299
+ background:transparent!important;
300
+ color:var(--text-secondary)!important;
301
+ border:none!important;
302
+ padding:12px 20px!important;
303
+ transition:all 0.3s ease!important;
304
+ }
305
+
306
+ [role="tab"][aria-selected="true"]{
307
+ color:var(--neon-green)!important;
308
+ background:rgba(111,217,168,0.1)!important;
309
+ border-bottom:3px solid var(--neon-green)!important;
310
+ }
311
+
312
+ [role="tab"]:hover{
313
+ color:var(--neon-green)!important;
314
+ background:rgba(111,217,168,0.05)!important;
315
+ }
316
+
317
+ /* ============ 테이블 ============ */
318
+ table{border-collapse:separate;border-spacing:0;width:100%;}
319
+ th{background:#252540!important;color:var(--neon-green)!important;padding:14px!important;
320
+ border-bottom:2px solid var(--neon-green)!important;font-weight:600;text-align:left;}
321
+ td{background:#1a1a2e!important;padding:12px 14px!important;
322
+ border-bottom:1px solid rgba(255,255,255,0.05)!important;}
323
+ tr:hover td{background:#252540!important;}
324
+
325
+ /* ============ 히든 스크롤바 ============ */
326
+ ::-webkit-scrollbar{width:8px;height:8px;}
327
+ ::-webkit-scrollbar-track{background:#0d0d1a;}
328
+ ::-webkit-scrollbar-thumb{background:var(--neon-green);border-radius:4px;}
329
+ ::-webkit-scrollbar-thumb:hover{background:#8fe8c0;}
330
+ """
331
+
332
+ # ============================================================================
333
+ # 데이터 상수
334
+ # ============================================================================
335
+
336
+ INDUSTRY_FINANCIAL_RATIOS = {
337
+ "A01 농업": {"code": "A01", "총자산증가율": 9.01, "매출액증가율": 12.31, "매출액영업이익률": 2.14, "유동비율": 104.53, "부채비율": 165.15, "제한부채비율": 500, "이자보상비율": 104.69},
338
+ "A03 어업": {"code": "A03", "총자산증가율": 45.04, "매출액증가율": 21.6, "매출액영업이익률": 8.38, "유동비율": 108.07, "부채비율": 102.09, "제한부채비율": 500, "이자보상비율": 499.98},
339
+ "B 광업": {"code": "B", "총자산증가율": -0.08, "매출액증가율": 13.75, "매출액영업이익률": 6.42, "유동비율": 55.56, "부채비율": -1096.54, "제한부채비율": 500, "이자보상비율": 38.78},
340
+ "C 제조업": {"code": "C", "총자산증가율": 7.54, "매출액증가율": 14.63, "매출액영업이익률": 5.72, "유동비율": 141.51, "부채비율": 76.95, "제한부채비율": 365.7, "이자보상비율": 693.43},
341
+ "D 전기가스": {"code": "D", "총자산증가율": 8.5, "매출액증가율": 15.2, "매출액영업이익률": 4.8, "유동비율": 95.0, "부채비율": 180.0, "제한부채비율": 400, "이자보상비율": 250.0},
342
+ "F 건설업": {"code": "F", "총자산증가율": 5.2, "매출액증가율": 8.5, "매출액영업이익률": 3.5, "유동비율": 125.0, "부채비율": 220.0, "제한부채비율": 450, "이자보상비율": 180.0},
343
+ "G 도소매업": {"code": "G", "총자산증가율": 6.8, "매출액증가율": 10.5, "매출액영업이익률": 2.8, "유동비율": 115.0, "부채비율": 145.0, "제한부채비율": 400, "이자보상비율": 220.0},
344
+ "H 운수창고업": {"code": "H", "총자산증가율": 4.5, "매출액증가율": 7.8, "매출액영업이익률": 4.2, "유동비율": 98.0, "부채비율": 195.0, "제한부채비율": 420, "이자보상비율": 165.0},
345
+ "I 숙박음식업": {"code": "I", "총자산증가율": 3.2, "매출액증가율": 5.5, "매출액영업이익률": 2.1, "유동비율": 85.0, "부채비율": 210.0, "제한부채비율": 450, "이자보상비율": 95.0},
346
+ "J 정보통신업": {"code": "J", "총자산증가율": 12.5, "매출액증가율": 18.2, "매출액영업이익률": 8.5, "유동비율": 165.0, "부채비율": 85.0, "제한부채비율": 350, "이자보상비율": 850.0},
347
+ "K 금융보험업": {"code": "K", "총자산증가율": 6.2, "매출액증가율": 8.9, "매출액영업이익률": 12.5, "유동비율": 120.0, "부채비율": 250.0, "제한부채비율": 500, "이자보상비율": 320.0},
348
+ "L 부동산업": {"code": "L", "총자산증가율": 8.8, "매출액증가율": 6.5, "매출액영업이익률": 15.2, "유동비율": 75.0, "부채비율": 185.0, "제한부채비율": 400, "이자보상비율": 280.0},
349
+ "M 전문과학기술": {"code": "M", "총자산증가율": 10.2, "매출액증가율": 15.8, "매출액영업이익률": 7.2, "유동비율": 155.0, "부채비율": 95.0, "제한부채비율": 380, "이자보상비율": 720.0},
350
+ "N 사업서비스": {"code": "N", "총자산증가율": 8.8, "매출액증가율": 12.5, "매출액영업이익률": 5.5, "유동비율": 135.0, "부채비율": 120.0, "제한부채비율": 400, "이자보상비율": 450.0},
351
+ "P 교육서비스": {"code": "P", "총자산증가율": 5.5, "매출액증가율": 6.8, "매출액영업이익률": 6.5, "유동비율": 142.0, "부채비율": 88.0, "제한부채비율": 380, "이자보상비율": 520.0},
352
+ "Q 보건복지": {"code": "Q", "총자산증가율": 9.2, "매출액증가율": 11.5, "매출액영업이익률": 4.8, "유동비율": 118.0, "부채비율": 135.0, "제한부채비율": 400, "이자보상비율": 385.0}
353
+ }
354
+
355
+ CAUTION_ITEMS = {
356
+ "A": {"name": "지원금액 부족 (창업 1년 이내)", "severity": "조건부", "deduction": 30, "category": "자금"},
357
+ "B": {"name": "지원금액 부족 (창업 1~3년)", "severity": "조건부", "deduction": 70, "category": "자금"},
358
+ "C": {"name": "지원금액 부족 (창업 3년 초과)", "severity": "불가", "deduction": 100, "category": "자금"},
359
+ "D": {"name": "지원금액 부족 (보증기관 사용중)", "severity": "불가", "deduction": 100, "category": "자금"},
360
+ "E": {"name": "마지막 보증서 발행 10개월 미만", "severity": "불가", "deduction": 100, "category": "보증"},
361
+ "F": {"name": "마지막 보증서 발행 10개월~1년", "severity": "조건부", "deduction": 50, "category": "보증"},
362
+ "0": {"name": "정부지원 제한업종", "severity": "불가", "deduction": 100, "category": "업종"},
363
+ "1": {"name": "대표자 신용점수 640점 미만", "severity": "불가", "deduction": 100, "category": "신용"},
364
+ "2": {"name": "대표자 신용점수 640~700점", "severity": "조건부", "deduction": 50, "category": "신용"},
365
+ "3": {"name": "보증기관 채무 미변제", "severity": "불가", "deduction": 100, "category": "채무"},
366
+ "4": {"name": "대표자 경력/학력 부족", "severity": "불가", "deduction": 100, "category": "자격"},
367
+ "5": {"name": "대표자/최대주주 파산 이력", "severity": "불가", "deduction": 100, "category": "법적"},
368
+ "6": {"name": "회생/회생신청 이력", "severity": "불가", "deduction": 100, "category": "법적"},
369
+ "7": {"name": "관계기업 신용관리정보 등록", "severity": "불가", "deduction": 100, "category": "신용"},
370
+ "8": {"name": "소송 진행/범죄사실 연루", "severity": "불가", "deduction": 100, "category": "법적"},
371
+ "9": {"name": "대표자 형사처벌 이력", "severity": "불가", "deduction": 100, "category": "법적"},
372
+ "10": {"name": "부동산 권리침해 진행중", "severity": "불가", "deduction": 100, "category": "담보"},
373
+ "11": {"name": "권리침해 해제 후 10개월 미만", "severity": "불가", "deduction": 100, "category": "담보"},
374
+ "12": {"name": "권리침해 해제 후 10개월 이상", "severity": "조건부", "deduction": 50, "category": "담보"},
375
+ "13": {"name": "기업 신용정보관리 등록", "severity": "불가", "deduction": 100, "category": "신용"},
376
+ "14": {"name": "대표자 신용정보관리 등록", "severity": "불가", "deduction": 100, "category": "신용"},
377
+ "15": {"name": "국세 체납중", "severity": "조건부", "deduction": 60, "category": "체납"},
378
+ "16": {"name": "지방세 체납중", "severity": "조건부", "deduction": 60, "category": "체납"},
379
+ "17": {"name": "4대보험 체납중", "severity": "조건부", "deduction": 50, "category": "체납"},
380
+ "18": {"name": "관계기업 체납중", "severity": "조건부", "deduction": 50, "category": "체납"},
381
+ "19": {"name": "대표자 개인 국세 체납", "severity": "조건부", "deduction": 60, "category": "체납"},
382
+ "20": {"name": "대표자 개인 지방세 체납", "severity": "조건부", "deduction": 60, "category": "체납"},
383
+ "21": {"name": "3개월 이내 10일 이상 연체", "severity": "조건부", "deduction": 70, "category": "연체"},
384
+ "22": {"name": "1년 이내 보증사고 (기업)", "severity": "불가", "deduction": 100, "category": "보증"},
385
+ "23": {"name": "1년 이내 보증사고 (관계기업)", "severity": "불가", "deduction": 100, "category": "보증"},
386
+ "24": {"name": "관계기업 부동산 권리침해", "severity": "조건부", "deduction": 50, "category": "담보"},
387
+ "25": {"name": "자본잠식 상태", "severity": "조건부", "deduction": 70, "category": "재무"},
388
+ "26": {"name": "완전자본잠식", "severity": "불가", "deduction": 100, "category": "재무"},
389
+ "27": {"name": "부채비율 제한초과", "severity": "조건부", "deduction": 60, "category": "재무"},
390
+ "28": {"name": "2년 연속 당기순손실", "severity": "조건부", "deduction": 50, "category": "재무"},
391
+ "29": {"name": "3년 연속 당기순손실", "severity": "불가", "deduction": 100, "category": "재무"},
392
+ "30": {"name": "영업이익 적자", "severity": "조건부", "deduction": 40, "category": "재무"},
393
+ "36": {"name": "기존 보증사용금액 과다", "severity": "조건부", "deduction": 50, "category": "보증"},
394
+ "37": {"name": "매출액 감소 추세", "severity": "조건부", "deduction": 30, "category": "재무"},
395
+ "38": {"name": "대표자 변경 1년 이내", "severity": "조건부", "deduction": 40, "category": "경영"},
396
+ "39": {"name": "휴업/폐업 이력", "severity": "조건부", "deduction": 60, "category": "경영"},
397
+ "40": {"name": "사업장 임차계약 불안정", "severity": "조건부", "deduction": 30, "category": "경영"},
398
+ "45": {"name": "금액제한 사유 해당 (MAX 2억)", "severity": "조건부", "deduction": 30, "category": "자금"}
399
+ }
400
+
401
+ PROCESS_STEPS = [
402
+ {"step": 1, "name": "신규접수", "desc": "정보활용동의, 사업자번호, 대표자 확인", "duration": "즉시", "icon": "📝"},
403
+ {"step": 2, "name": "질의응답", "desc": "이메일, 설문작성 (69개 질문)", "duration": "10~30분", "icon": "💬"},
404
+ {"step": 3, "name": "API 수집", "desc": "17개 공공데이터 연계 조회", "duration": "자동", "icon": "🔄"},
405
+ {"step": 4, "name": "판독분석", "desc": "유의상태, 추천기관, 지원금액", "duration": "자동", "icon": "🔍"},
406
+ {"step": 5, "name": "리포트생성", "desc": "분석 결과 생성 완료", "duration": "즉시", "icon": "📊"},
407
+ {"step": 6, "name": "브리프", "desc": "부분 리포트 & 결재요청", "duration": "확인", "icon": "📋"},
408
+ {"step": 7, "name": "결재확인", "desc": "결재 완료 대기", "duration": "1~3일", "icon": "✅"},
409
+ {"step": 8, "name": "전체리포트", "desc": "전체 리포트 & 추가서비스", "duration": "즉시", "icon": "🏆"}
410
+ ]
411
+
412
+ PROGRAMS = {
413
+ "신용보증기금": {"max": 30, "rate": "0.5~1.5%", "period": "5년", "color": "#6fd9a8"},
414
+ "기술보증기금": {"max": 30, "rate": "1.0~1.5%", "period": "5년", "color": "#ffd93d"},
415
+ "지역신보재단": {"max": 2, "rate": "0.5~1.0%", "period": "3년", "color": "#6495ed"},
416
+ "창업보증": {"max": 10, "rate": "0.8~1.2%", "period": "5년", "color": "#9b59b6"},
417
+ "혁신성장": {"max": 50, "rate": "0.5~1.0%", "period": "5년", "color": "#00d4ff"},
418
+ "수출기업": {"max": 30, "rate": "0.7~1.2%", "period": "5년", "color": "#ff9500"}
419
+ }
420
+
421
+ # ============================================================================
422
+ # 🎯 차트 1: 3D 종합점수 게이지 (울트라 버전)
423
+ # ============================================================================
424
+
425
+ def generate_ultra_gauge(score: int, title: str = "종합점수") -> str:
426
+ """3D 효과 종합점수 게이지"""
427
+
428
+ if score >= 80: color, status, emoji = "#6fd9a8", "EXCELLENT", "🏆"
429
+ elif score >= 60: color, status, emoji = "#7be8c0", "GOOD", "✅"
430
+ elif score >= 40: color, status, emoji = "#ffd93d", "FAIR", "⚠️"
431
+ else: color, status, emoji = "#ff6b6b", "RISK", "🚨"
432
+
433
+ # SVG 계산
434
+ radius = 80
435
+ circumference = 2 * 3.14159 * radius
436
+ offset = circumference - (score / 100) * circumference
437
+
438
+ html = f"""
439
+ <div class="chart-ultra" style="text-align:center;">
440
+ <div style="display:flex;align-items:center;justify-content:center;gap:8px;margin-bottom:24px;">
441
+ <span style="font-size:28px;">{emoji}</span>
442
+ <h3 style="margin:0;color:var(--neon-green);font-family:'Orbitron',sans-serif;font-size:22px;
443
+ animation:glow-text 2s ease-in-out infinite;">{title}</h3>
444
+ </div>
445
+
446
+ <div style="position:relative;width:220px;height:220px;margin:0 auto;">
447
+ <!-- 배경 글로우 -->
448
+ <div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
449
+ width:180px;height:180px;border-radius:50%;
450
+ background:radial-gradient(circle,{color}15 0%,transparent 70%);
451
+ animation:pulse-ring 2s ease-out infinite;"></div>
452
+
453
+ <svg width="220" height="220" viewBox="0 0 220 220" style="transform:rotate(-90deg);">
454
+ <defs>
455
+ <linearGradient id="gaugeGrad" x1="0%" y1="0%" x2="100%" y2="0%">
456
+ <stop offset="0%" style="stop-color:{color}"/>
457
+ <stop offset="50%" style="stop-color:#fff"/>
458
+ <stop offset="100%" style="stop-color:{color}"/>
459
+ </linearGradient>
460
+ <filter id="glow">
461
+ <feGaussianBlur stdDeviation="4" result="coloredBlur"/>
462
+ <feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge>
463
+ </filter>
464
+ <filter id="shadow">
465
+ <feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#000" flood-opacity="0.5"/>
466
+ </filter>
467
+ </defs>
468
+
469
+ <!-- 외곽 링 (3D 효과) -->
470
+ <circle cx="110" cy="110" r="100" fill="none" stroke="#1a1a2e" stroke-width="20" filter="url(#shadow)"/>
471
+ <circle cx="110" cy="110" r="100" fill="none" stroke="#252540" stroke-width="18"/>
472
+
473
+ <!-- 배경 트랙 -->
474
+ <circle cx="110" cy="110" r="{radius}" fill="none" stroke="#2a2a45" stroke-width="14"/>
475
+
476
+ <!-- 진행 바 -->
477
+ <circle cx="110" cy="110" r="{radius}" fill="none"
478
+ stroke="url(#gaugeGrad)" stroke-width="14" stroke-linecap="round"
479
+ stroke-dasharray="{circumference}" stroke-dashoffset="{offset}"
480
+ filter="url(#glow)"
481
+ style="transition:stroke-dashoffset 1.5s cubic-bezier(0.4, 0, 0.2, 1);"/>
482
+
483
+ <!-- 포인터 점 -->
484
+ <circle cx="110" cy="{110-radius}" r="8" fill="{color}" filter="url(#glow)">
485
+ <animateTransform attributeName="transform" type="rotate"
486
+ from="0 110 110" to="{score*3.6} 110 110" dur="1.5s" fill="freeze"/>
487
+ </circle>
488
+ </svg>
489
+
490
+ <!-- 중앙 값 -->
491
+ <div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;">
492
+ <div style="font-family:'Orbitron',sans-serif;font-size:56px;font-weight:900;
493
+ color:{color};text-shadow:0 0 30px {color}80;
494
+ animation:count-up 1s ease-out;">{score}</div>
495
+ <div style="font-size:14px;color:var(--text-secondary);margin-top:-5px;">/ 100</div>
496
+ </div>
497
+ </div>
498
+
499
+ <!-- 상태 배지 -->
500
+ <div style="margin-top:24px;">
501
+ <span style="display:inline-block;padding:10px 30px;
502
+ background:linear-gradient(135deg,{color}20,{color}10);
503
+ border:2px solid {color};border-radius:30px;
504
+ color:{color};font-family:'Orbitron',sans-serif;font-weight:700;font-size:16px;
505
+ box-shadow:0 0 20px {color}40;letter-spacing:2px;">
506
+ {status}
507
+ </span>
508
+ </div>
509
+
510
+ <!-- 스케일 바 -->
511
+ <div style="display:flex;justify-content:center;gap:15px;margin-top:20px;">
512
+ <span style="display:flex;align-items:center;gap:5px;font-size:11px;">
513
+ <span style="width:10px;height:10px;background:#ff6b6b;border-radius:2px;"></span>
514
+ <span style="color:var(--text-secondary);">0-39</span>
515
+ </span>
516
+ <span style="display:flex;align-items:center;gap:5px;font-size:11px;">
517
+ <span style="width:10px;height:10px;background:#ffd93d;border-radius:2px;"></span>
518
+ <span style="color:var(--text-secondary);">40-59</span>
519
+ </span>
520
+ <span style="display:flex;align-items:center;gap:5px;font-size:11px;">
521
+ <span style="width:10px;height:10px;background:#7be8c0;border-radius:2px;"></span>
522
+ <span style="color:var(--text-secondary);">60-79</span>
523
+ </span>
524
+ <span style="display:flex;align-items:center;gap:5px;font-size:11px;">
525
+ <span style="width:10px;height:10px;background:#6fd9a8;border-radius:2px;"></span>
526
+ <span style="color:var(--text-secondary);">80+</span>
527
+ </span>
528
+ </div>
529
+ </div>
530
+ """
531
+ return html
532
+
533
+ # ============================================================================
534
+ # 🕸️ 차트 2: 레이더 차트 (5축 SVG 애니메이션)
535
+ # ============================================================================
536
+
537
+ def generate_radar_chart(data: dict, title: str = "재무건전성 분석") -> str:
538
+ """SVG 레이더 차트 with 애니메이션"""
539
+
540
+ metrics = [
541
+ ("수익성", data.get("수익성", 60)),
542
+ ("안정성", data.get("안정성", 70)),
543
+ ("성장성", data.get("성장성", 55)),
544
+ ("활동성", data.get("활동성", 65)),
545
+ ("생산성", data.get("생산성", 50))
546
+ ]
547
+
548
+ cx, cy = 150, 150
549
+ max_r = 100
550
+ n = len(metrics)
551
+ angles = [(i * 360 / n - 90) * math.pi / 180 for i in range(n)]
552
+
553
+ # 배경 그리드
554
+ grid_html = ""
555
+ for level in [20, 40, 60, 80, 100]:
556
+ points = " ".join([f"{cx + level/100*max_r*math.cos(a)},{cy + level/100*max_r*math.sin(a)}" for a in angles])
557
+ grid_html += f'<polygon points="{points}" fill="none" stroke="#2a2a45" stroke-width="1"/>'
558
+
559
+ # 축 선
560
+ axes_html = ""
561
+ for a in angles:
562
+ axes_html += f'<line x1="{cx}" y1="{cy}" x2="{cx + max_r*math.cos(a)}" y2="{cy + max_r*math.sin(a)}" stroke="#3a3a55" stroke-width="1"/>'
563
+
564
+ # 데이터 폴리곤
565
+ data_points = " ".join([f"{cx + metrics[i][1]/100*max_r*math.cos(angles[i])},{cy + metrics[i][1]/100*max_r*math.sin(angles[i])}" for i in range(n)])
566
+
567
+ # 라벨
568
+ labels_html = ""
569
+ for i, (name, val) in enumerate(metrics):
570
+ lx = cx + (max_r + 30) * math.cos(angles[i])
571
+ ly = cy + (max_r + 30) * math.sin(angles[i])
572
+ color = "#6fd9a8" if val >= 70 else "#ffd93d" if val >= 50 else "#ff6b6b"
573
+ labels_html += f'''
574
+ <text x="{lx}" y="{ly}" fill="{color}" font-size="13" font-weight="600"
575
+ text-anchor="middle" dominant-baseline="middle">{name}</text>
576
+ <text x="{lx}" y="{ly + 16}" fill="var(--text-secondary)" font-size="11"
577
+ text-anchor="middle">{val}점</text>
578
+ '''
579
+
580
+ avg = sum(v for _, v in metrics) // n
581
+ avg_color = "#6fd9a8" if avg >= 70 else "#ffd93d" if avg >= 50 else "#ff6b6b"
582
+
583
+ html = f"""
584
+ <div class="chart-ultra">
585
+ <h3 style="color:var(--neon-green);margin-bottom:20px;text-align:center;
586
+ font-family:'Rajdhani',sans-serif;font-size:20px;">🕸️ {title}</h3>
587
+
588
+ <svg width="300" height="300" viewBox="0 0 300 300" style="display:block;margin:0 auto;">
589
+ <defs>
590
+ <linearGradient id="radarFill" x1="0%" y1="0%" x2="100%" y2="100%">
591
+ <stop offset="0%" style="stop-color:#6fd9a8;stop-opacity:0.6"/>
592
+ <stop offset="100%" style="stop-color:#3a8f6a;stop-opacity:0.2"/>
593
+ </linearGradient>
594
+ <filter id="radarGlow">
595
+ <feGaussianBlur stdDeviation="3" result="blur"/>
596
+ <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
597
+ </filter>
598
+ </defs>
599
+
600
+ {grid_html}
601
+ {axes_html}
602
+
603
+ <!-- 데이터 영역 -->
604
+ <polygon points="{data_points}" fill="url(#radarFill)" stroke="#6fd9a8"
605
+ stroke-width="2" filter="url(#radarGlow)"
606
+ style="animation:slide-up 0.8s ease-out;">
607
+ <animate attributeName="opacity" from="0" to="1" dur="1s"/>
608
+ </polygon>
609
+
610
+ <!-- 데이터 포인트 -->
611
+ {''.join([f'<circle cx="{cx + metrics[i][1]/100*max_r*math.cos(angles[i])}" cy="{cy + metrics[i][1]/100*max_r*math.sin(angles[i])}" r="6" fill="#6fd9a8" stroke="#fff" stroke-width="2"><animate attributeName="r" from="0" to="6" dur="0.5s" begin="{i*0.1}s"/></circle>' for i in range(n)])}
612
+
613
+ {labels_html}
614
+ </svg>
615
+
616
+ <!-- 평균 점수 -->
617
+ <div style="text-align:center;margin-top:15px;padding:15px;
618
+ background:linear-gradient(145deg,#252540,#1a1a2e);border-radius:12px;">
619
+ <span style="color:var(--text-secondary);font-size:14px;">종합 평균</span>
620
+ <span style="font-family:'Orbitron',sans-serif;font-size:28px;font-weight:700;
621
+ color:{avg_color};margin-left:15px;">{avg}</span>
622
+ <span style="color:var(--text-secondary);font-size:14px;">점</span>
623
+ </div>
624
+ </div>
625
+ """
626
+ return html
627
+
628
+ # ============================================================================
629
+ # 🍩 차트 3: 기관별 도넛 차트
630
+ # ============================================================================
631
+
632
+ def generate_donut_chart(sinbo: int, kibo: int, jaedan: int) -> str:
633
+ """기관별 승인확률 도넛"""
634
+
635
+ total = max(1, sinbo + kibo + jaedan)
636
+ r = 65
637
+ circ = 2 * 3.14159 * r
638
+
639
+ sinbo_arc = circ * sinbo / total
640
+ kibo_arc = circ * kibo / total
641
+ jaedan_arc = circ * jaedan / total
642
+
643
+ def get_grade(s):
644
+ if s >= 70: return ("A", "#6fd9a8")
645
+ elif s >= 50: return ("B", "#ffd93d")
646
+ else: return ("C", "#ff6b6b")
647
+
648
+ html = f"""
649
+ <div class="chart-ultra">
650
+ <h3 style="color:var(--neon-green);margin-bottom:24px;text-align:center;
651
+ font-family:'Rajdhani',sans-serif;font-size:20px;">🎯 기관별 승인확률</h3>
652
+
653
+ <div style="display:flex;justify-content:center;align-items:center;gap:50px;flex-wrap:wrap;">
654
+ <!-- 도넛 차트 -->
655
+ <div style="position:relative;width:180px;height:180px;">
656
+ <svg width="180" height="180" viewBox="0 0 180 180" style="transform:rotate(-90deg);">
657
+ <defs>
658
+ <filter id="donutGlow">
659
+ <feGaussianBlur stdDeviation="3"/><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
660
+ </filter>
661
+ </defs>
662
+ <circle cx="90" cy="90" r="{r}" fill="none" stroke="#2a2a45" stroke-width="22"/>
663
+ <circle cx="90" cy="90" r="{r}" fill="none" stroke="#6fd9a8" stroke-width="22"
664
+ stroke-dasharray="{sinbo_arc} {circ}" stroke-dashoffset="0" filter="url(#donutGlow)">
665
+ <animate attributeName="stroke-dasharray" from="0 {circ}" to="{sinbo_arc} {circ}" dur="1s"/>
666
+ </circle>
667
+ <circle cx="90" cy="90" r="{r}" fill="none" stroke="#ffd93d" stroke-width="22"
668
+ stroke-dasharray="{kibo_arc} {circ}" stroke-dashoffset="{-sinbo_arc}" filter="url(#donutGlow)">
669
+ <animate attributeName="stroke-dasharray" from="0 {circ}" to="{kibo_arc} {circ}" dur="1s" begin="0.3s"/>
670
+ </circle>
671
+ <circle cx="90" cy="90" r="{r}" fill="none" stroke="#6495ed" stroke-width="22"
672
+ stroke-dasharray="{jaedan_arc} {circ}" stroke-dashoffset="{-(sinbo_arc+kibo_arc)}" filter="url(#donutGlow)">
673
+ <animate attributeName="stroke-dasharray" from="0 {circ}" to="{jaedan_arc} {circ}" dur="1s" begin="0.6s"/>
674
+ </circle>
675
+ </svg>
676
+ <div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;">
677
+ <div style="font-size:12px;color:var(--text-secondary);">평균</div>
678
+ <div style="font-family:'Orbitron',sans-serif;font-size:32px;font-weight:700;color:#fff;">
679
+ {(sinbo+kibo+jaedan)//3}%
680
+ </div>
681
+ </div>
682
+ </div>
683
+
684
+ <!-- 범례 -->
685
+ <div style="display:flex;flex-direction:column;gap:16px;">
686
+ <div style="display:flex;align-items:center;gap:15px;padding:14px 20px;
687
+ background:linear-gradient(145deg,rgba(111,217,168,0.1),transparent);
688
+ border-radius:12px;border-left:4px solid #6fd9a8;">
689
+ <div style="width:14px;height:14px;background:#6fd9a8;border-radius:50%;box-shadow:0 0 10px #6fd9a880;"></div>
690
+ <div>
691
+ <div style="font-size:12px;color:var(--text-secondary);">신용보증기금</div>
692
+ <div style="font-family:'Orbitron',sans-serif;font-size:26px;font-weight:700;color:#6fd9a8;">{sinbo}%</div>
693
+ </div>
694
+ <span style="margin-left:auto;font-size:20px;padding:5px 12px;background:{get_grade(sinbo)[1]}30;
695
+ border-radius:8px;color:{get_grade(sinbo)[1]};font-weight:700;">{get_grade(sinbo)[0]}</span>
696
+ </div>
697
+
698
+ <div style="display:flex;align-items:center;gap:15px;padding:14px 20px;
699
+ background:linear-gradient(145deg,rgba(255,217,61,0.1),transparent);
700
+ border-radius:12px;border-left:4px solid #ffd93d;">
701
+ <div style="width:14px;height:14px;background:#ffd93d;border-radius:50%;box-shadow:0 0 10px #ffd93d80;"></div>
702
+ <div>
703
+ <div style="font-size:12px;color:var(--text-secondary);">기술보증기금</div>
704
+ <div style="font-family:'Orbitron',sans-serif;font-size:26px;font-weight:700;color:#ffd93d;">{kibo}%</div>
705
+ </div>
706
+ <span style="margin-left:auto;font-size:20px;padding:5px 12px;background:{get_grade(kibo)[1]}30;
707
+ border-radius:8px;color:{get_grade(kibo)[1]};font-weight:700;">{get_grade(kibo)[0]}</span>
708
+ </div>
709
+
710
+ <div style="display:flex;align-items:center;gap:15px;padding:14px 20px;
711
+ background:linear-gradient(145deg,rgba(100,149,237,0.1),transparent);
712
+ border-radius:12px;border-left:4px solid #6495ed;">
713
+ <div style="width:14px;height:14px;background:#6495ed;border-radius:50%;box-shadow:0 0 10px #6495ed80;"></div>
714
+ <div>
715
+ <div style="font-size:12px;color:var(--text-secondary);">지역신보재단</div>
716
+ <div style="font-family:'Orbitron',sans-serif;font-size:26px;font-weight:700;color:#6495ed;">{jaedan}%</div>
717
+ </div>
718
+ <span style="margin-left:auto;font-size:20px;padding:5px 12px;background:{get_grade(jaedan)[1]}30;
719
+ border-radius:8px;color:{get_grade(jaedan)[1]};font-weight:700;">{get_grade(jaedan)[0]}</span>
720
+ </div>
721
+ </div>
722
+ </div>
723
+ </div>
724
+ """
725
+ return html
726
+
727
+ # ============================================================================
728
+ # 💰 차트 4: 자금조달 파이프라인
729
+ # ============================================================================
730
+
731
+ def generate_pipeline(stage: int, amounts: dict) -> str:
732
+ """자금조달 파이프라인 플로우"""
733
+
734
+ stages = [
735
+ ("사전심사", "🔍", "#6495ed"),
736
+ ("서류접수", "📄", "#9b59b6"),
737
+ ("심사진행", "⚖️", "#ffd93d"),
738
+ ("승인완료", "✅", "#6fd9a8")
739
+ ]
740
+
741
+ html = f"""
742
+ <div class="chart-ultra">
743
+ <h3 style="color:var(--neon-green);margin-bottom:25px;text-align:center;
744
+ font-family:'Rajdhani',sans-serif;font-size:20px;">💰 자금조달 파이프라인</h3>
745
+
746
+ <!-- 파이프라인 -->
747
+ <div style="position:relative;padding:30px 0;">
748
+ <div style="position:absolute;top:50%;left:8%;right:8%;height:6px;
749
+ background:linear-gradient(90deg,#2a2a45 0%,#6fd9a8 {stage*25}%,#2a2a45 {stage*25}%);
750
+ transform:translateY(-50%);border-radius:3px;"></div>
751
+
752
+ <div style="display:flex;justify-content:space-between;position:relative;">
753
+ """
754
+
755
+ for i, (name, icon, color) in enumerate(stages):
756
+ done = i < stage
757
+ curr = i == stage
758
+
759
+ html += f"""
760
+ <div style="text-align:center;flex:1;opacity:{'1' if done or curr else '0.4'};
761
+ animation:{'float 2s ease-in-out infinite' if curr else 'none'};">
762
+ <div style="width:70px;height:70px;margin:0 auto;
763
+ background:{'linear-gradient(135deg,'+color+','+color+'80)' if done else '#252540' if curr else '#1a1a2e'};
764
+ border:3px solid {color if done or curr else '#3a3a55'};border-radius:50%;
765
+ display:flex;align-items:center;justify-content:center;font-size:28px;
766
+ box-shadow:{'0 0 30px '+color+'60' if curr else '0 0 15px '+color+'40' if done else 'none'};
767
+ transition:all 0.3s ease;">
768
+ {'✓' if done else icon}
769
+ </div>
770
+ <div style="margin-top:12px;font-weight:600;color:{'#fff' if done or curr else 'var(--text-secondary)'};
771
+ font-size:14px;">{name}</div>
772
+ </div>
773
+ """
774
+
775
+ html += f"""
776
+ </div>
777
+ </div>
778
+
779
+ <!-- 금액 카드 -->
780
+ <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:30px;">
781
+ <div class="score-card" style="border-top:4px solid #6495ed;">
782
+ <div class="label">신청금액</div>
783
+ <div class="value" style="font-size:28px;color:#6495ed;">{amounts.get('신청',5):.1f}<span style="font-size:16px;">억</span></div>
784
+ </div>
785
+ <div class="score-card highlight" style="border-top:4px solid #6fd9a8;">
786
+ <div class="label">예상승인</div>
787
+ <div class="value" style="font-size:28px;">{amounts.get('예상',3.5):.1f}<span style="font-size:16px;">억</span></div>
788
+ </div>
789
+ <div class="score-card" style="border-top:4px solid #ff6b6b;">
790
+ <div class="label">기존사용</div>
791
+ <div class="value" style="font-size:28px;color:#ff6b6b;">{amounts.get('기존',0.5):.1f}<span style="font-size:16px;">억</span></div>
792
+ </div>
793
+ </div>
794
+ </div>
795
+ """
796
+ return html
797
+
798
+ # ============================================================================
799
+ # 🔥 차트 5: 리스크 히트맵
800
+ # ============================================================================
801
+
802
+ def generate_heatmap(caution_items: list) -> str:
803
+ """리스크 히트맵"""
804
+
805
+ cats = {"신용": [0,0,[]], "재무": [0,0,[]], "체납": [0,0,[]], "법적": [0,0,[]],
806
+ "보증": [0,0,[]], "담보": [0,0,[]], "경영": [0,0,[]], "기타": [0,0,[]]}
807
+
808
+ for code in caution_items:
809
+ if code in CAUTION_ITEMS:
810
+ item = CAUTION_ITEMS[code]
811
+ cat = item.get("category", "기타")
812
+ if cat not in cats: cat = "기타"
813
+ cats[cat][0] += 1
814
+ if item["severity"] == "불가": cats[cat][1] += 1
815
+ cats[cat][2].append(item["name"])
816
+
817
+ def risk_level(cnt, sev):
818
+ if sev > 0: return (4, "#ff6b6b", "심각")
819
+ if cnt >= 3: return (3, "#ff9500", "높음")
820
+ if cnt >= 2: return (2, "#ffd93d", "보통")
821
+ if cnt >= 1: return (1, "#6fd9a8", "낮음")
822
+ return (0, "#2a2a45", "안전")
823
+
824
+ total = sum(c[0] for c in cats.values())
825
+ severe = sum(c[1] for c in cats.values())
826
+
827
+ html = f"""
828
+ <div class="chart-ultra">
829
+ <h3 style="color:var(--neon-green);margin-bottom:20px;text-align:center;
830
+ font-family:'Rajdhani',sans-serif;font-size:20px;">🔥 리스크 히트맵</h3>
831
+
832
+ <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px;">
833
+ """
834
+
835
+ for cat, (cnt, sev, items) in cats.items():
836
+ lv, color, status = risk_level(cnt, sev)
837
+ html += f"""
838
+ <div style="background:{color}{'30' if lv > 0 else ''};padding:20px;text-align:center;
839
+ border-radius:14px;border:2px solid {color if lv >= 3 else 'transparent'};
840
+ transition:all 0.3s ease;cursor:pointer;
841
+ {'animation:neon-pulse 1.5s ease-in-out infinite;' if lv >= 3 else ''}"
842
+ title="{', '.join(items[:3]) if items else '해당없음'}">
843
+ <div style="font-size:13px;color:{'#fff' if lv >= 2 else 'var(--text-secondary)'};font-weight:600;">{cat}</div>
844
+ <div style="font-family:'Orbitron',sans-serif;font-size:32px;font-weight:700;
845
+ color:{'#fff' if lv >= 2 else color};margin:10px 0;">{cnt}</div>
846
+ <div style="font-size:11px;color:{'#fff' if lv >= 2 else '#808090'};">{('불가 ' + str(sev)) if sev > 0 else status}</div>
847
+ </div>
848
+ """
849
+
850
+ html += f"""
851
+ </div>
852
+
853
+ <!-- 범례 -->
854
+ <div style="display:flex;justify-content:center;gap:20px;padding:15px;
855
+ background:#1a1a2e;border-radius:10px;margin-bottom:20px;">
856
+ <span style="display:flex;align-items:center;gap:6px;font-size:12px;">
857
+ <span style="width:14px;height:14px;background:#2a2a45;border-radius:3px;"></span>안전
858
+ </span>
859
+ <span style="display:flex;align-items:center;gap:6px;font-size:12px;">
860
+ <span style="width:14px;height:14px;background:#6fd9a8;border-radius:3px;"></span>낮음
861
+ </span>
862
+ <span style="display:flex;align-items:center;gap:6px;font-size:12px;">
863
+ <span style="width:14px;height:14px;background:#ffd93d;border-radius:3px;"></span>보통
864
+ </span>
865
+ <span style="display:flex;align-items:center;gap:6px;font-size:12px;">
866
+ <span style="width:14px;height:14px;background:#ff9500;border-radius:3px;"></span>높음
867
+ </span>
868
+ <span style="display:flex;align-items:center;gap:6px;font-size:12px;">
869
+ <span style="width:14px;height:14px;background:#ff6b6b;border-radius:3px;"></span>심각
870
+ </span>
871
+ </div>
872
+
873
+ <!-- 총계 -->
874
+ <div style="padding:18px;background:linear-gradient(145deg,#252540,#1a1a2e);border-radius:12px;
875
+ border-left:4px solid #ff6b6b;display:flex;justify-content:space-between;align-items:center;">
876
+ <span style="color:var(--text-secondary);">총 유의사항</span>
877
+ <span style="font-family:'Orbitron',sans-serif;font-size:28px;font-weight:700;color:#ff6b6b;">
878
+ {total}<span style="font-size:14px;color:var(--text-secondary);margin-left:8px;">(불가 {severe})</span>
879
+ </span>
880
+ </div>
881
+ </div>
882
+ """
883
+ return html
884
+
885
+ # ============================================================================
886
+ # 📊 차트 6: 경쟁력 벤치마크 바
887
+ # ============================================================================
888
+
889
+ def generate_benchmark_bars(company: dict, industry: dict) -> str:
890
+ """업종 대비 벤치마크"""
891
+
892
+ metrics = [
893
+ ("매출성장률", company.get("매출성장률", 15), industry.get("매출성장률", 10), "%", False),
894
+ ("영업이익률", company.get("영업이익률", 8), industry.get("영업이익률", 5), "%", False),
895
+ ("부채비율", company.get("부채비율", 120), industry.get("부채비율", 150), "%", True),
896
+ ("신용등급", company.get("신용등급", 720), industry.get("신용등급", 680), "점", False),
897
+ ("고용성장", company.get("고용성장", 20), industry.get("고용성장", 10), "%", False)
898
+ ]
899
+
900
+ better_count = 0
901
+
902
+ html = f"""
903
+ <div class="chart-ultra">
904
+ <h3 style="color:var(--neon-green);margin-bottom:25px;text-align:center;
905
+ font-family:'Rajdhani',sans-serif;font-size:20px;">📊 업종 대비 경쟁력</h3>
906
+
907
+ <div style="display:flex;flex-direction:column;gap:18px;">
908
+ """
909
+
910
+ for name, comp_val, ind_val, unit, reverse in metrics:
911
+ is_better = (comp_val < ind_val) if reverse else (comp_val > ind_val)
912
+ if is_better: better_count += 1
913
+
914
+ diff = ind_val - comp_val if reverse else comp_val - ind_val
915
+ diff_pct = (diff / ind_val * 100) if ind_val != 0 else 0
916
+
917
+ max_val = max(comp_val, ind_val) * 1.3
918
+ comp_w = comp_val / max_val * 100 if max_val > 0 else 0
919
+ ind_w = ind_val / max_val * 100 if max_val > 0 else 0
920
+
921
+ color = "#6fd9a8" if is_better else "#ff6b6b"
922
+
923
+ html += f"""
924
+ <div style="background:#1a1a2e;padding:18px;border-radius:14px;border-left:4px solid {color};">
925
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
926
+ <span style="font-weight:600;color:#fff;">{name}</span>
927
+ <span style="font-size:13px;color:{color};">
928
+ {'+' if diff > 0 else ''}{diff:.1f}{unit} ({'+' if diff_pct > 0 else ''}{diff_pct:.0f}%)
929
+ {'👍' if is_better else '👎'}
930
+ </span>
931
+ </div>
932
+
933
+ <div style="margin-bottom:8px;">
934
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:6px;">
935
+ <span style="width:50px;font-size:12px;color:var(--text-secondary);">귀사</span>
936
+ <div style="flex:1;height:14px;background:#252540;border-radius:7px;overflow:hidden;">
937
+ <div class="progress-ultra"><div class="fill" style="width:{comp_w}%;background:linear-gradient(90deg,{color},{color}80);"></div></div>
938
+ </div>
939
+ <span style="width:70px;text-align:right;font-family:'Orbitron',sans-serif;font-weight:600;color:{color};">{comp_val:.1f}{unit}</span>
940
+ </div>
941
+ </div>
942
+
943
+ <div>
944
+ <div style="display:flex;align-items:center;gap:12px;">
945
+ <span style="width:50px;font-size:12px;color:#606080;">업종</span>
946
+ <div style="flex:1;height:10px;background:#252540;border-radius:5px;overflow:hidden;">
947
+ <div style="width:{ind_w}%;height:100%;background:#505070;border-radius:5px;"></div>
948
+ </div>
949
+ <span style="width:70px;text-align:right;font-size:13px;color:#606080;">{ind_val:.1f}{unit}</span>
950
+ </div>
951
+ </div>
952
+ </div>
953
+ """
954
+
955
+ overall = better_count / len(metrics) * 100
956
+
957
+ html += f"""
958
+ </div>
959
+
960
+ <!-- 종합 -->
961
+ <div style="margin-top:25px;padding:22px;background:linear-gradient(145deg,#1a2a30,#0d1a1f);
962
+ border-radius:14px;text-align:center;border:2px solid #6fd9a830;">
963
+ <div style="font-size:14px;color:var(--text-secondary);margin-bottom:8px;">업종 대비 경쟁력 지수</div>
964
+ <div style="font-family:'Orbitron',sans-serif;font-size:48px;font-weight:900;
965
+ color:{'#6fd9a8' if overall >= 60 else '#ffd93d' if overall >= 40 else '#ff6b6b'};
966
+ text-shadow:0 0 30px {'#6fd9a8' if overall >= 60 else '#ffd93d' if overall >= 40 else '#ff6b6b'}60;">
967
+ {better_count}/{len(metrics)}
968
+ </div>
969
+ <div style="font-size:13px;color:var(--text-secondary);margin-top:5px;">지표 우위</div>
970
+ </div>
971
+ </div>
972
+ """
973
+ return html
974
+
975
+ # ============================================================================
976
+ # 📋 차트 7: KPI 스코어카드 대시보드
977
+ # ============================================================================
978
+
979
+ def generate_scorecard_dashboard(data: dict) -> str:
980
+ """KPI 스코어카드"""
981
+
982
+ kpis = [
983
+ ("💰", "예상지원액", f"{data.get('예상금액', 2.5):.1f}억", "#6fd9a8", True),
984
+ ("📈", "종합점수", f"{data.get('종합점수', 75)}점", "#ffd93d" if data.get('종합점수', 75) < 70 else "#6fd9a8", data.get('종합점수', 75) >= 70),
985
+ ("🏦", "추천기관", data.get('추천기관', '신보'), "#6495ed", True),
986
+ ("⚠️", "리스크", f"{data.get('리스크', 3)}건", "#ff6b6b" if data.get('리스크', 3) > 5 else "#ffd93d", data.get('리스크', 3) <= 3),
987
+ ("📊", "신용등급", f"{data.get('신용등급', 720)}점", "#6fd9a8" if data.get('신용등급', 720) >= 700 else "#ffd93d", True),
988
+ ("🎯", "승인확률", f"{data.get('승인확률', 72)}%", "#6fd9a8" if data.get('승인확률', 72) >= 70 else "#ffd93d", True)
989
+ ]
990
+
991
+ html = f"""
992
+ <div class="chart-ultra">
993
+ <h3 style="color:var(--neon-green);margin-bottom:25px;text-align:center;
994
+ font-family:'Rajdhani',sans-serif;font-size:20px;">📋 핵심 KPI 대시보드</h3>
995
+
996
+ <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:18px;">
997
+ """
998
+
999
+ for i, (icon, label, value, color, is_good) in enumerate(kpis):
1000
+ html += f"""
1001
+ <div class="score-card {'highlight' if i == 0 else ''}" style="animation:slide-up 0.5s ease-out {i*0.1}s both;">
1002
+ <div style="font-size:32px;margin-bottom:10px;">{icon}</div>
1003
+ <div class="value" style="color:{color};">{value}</div>
1004
+ <div class="label">{label}</div>
1005
+ <div style="margin-top:10px;">
1006
+ <span style="display:inline-block;padding:4px 12px;background:{color}20;
1007
+ border-radius:12px;font-size:11px;color:{color};">
1008
+ {'✓ 양호' if is_good else '⚠ 주의'}
1009
+ </span>
1010
+ </div>
1011
+ </div>
1012
+ """
1013
+
1014
+ html += """
1015
+ </div>
1016
+ </div>
1017
+ """
1018
+ return html
1019
+
1020
+ # ============================================================================
1021
+ # 🌊 차트 8: 워터폴 차트
1022
+ # ============================================================================
1023
+
1024
+ def generate_waterfall(calc: dict) -> str:
1025
+ """워터폴 차트"""
1026
+
1027
+ steps = [
1028
+ ("기준매출", calc.get("기준매출", 5e8), "base", "💰"),
1029
+ ("관계기업", -calc.get("관계차감", 5e7), "neg", "➖"),
1030
+ ("조정매출", calc.get("조정매출", 4.5e8), "sub", "📊"),
1031
+ ("회전율", calc.get("회전적용", -1.5e8), "neg", "🔄"),
1032
+ ("1차산출", calc.get("1차산출", 3e8), "sub", "📋"),
1033
+ ("기존사용", -calc.get("기존사용", 5e7), "neg", "🏦"),
1034
+ ("최종금액", calc.get("최종금액", 2.5e8), "total", "🎯")
1035
+ ]
1036
+
1037
+ max_amt = max(abs(s[1]) for s in steps if s[1] != 0) * 1.2
1038
+
1039
+ html = f"""
1040
+ <div class="chart-ultra">
1041
+ <h3 style="color:var(--neon-green);margin-bottom:25px;text-align:center;
1042
+ font-family:'Rajdhani',sans-serif;font-size:20px;">💰 금액 산출 워터폴</h3>
1043
+
1044
+ <div style="display:flex;flex-direction:column;gap:14px;">
1045
+ """
1046
+
1047
+ for name, amt, stype, icon in steps:
1048
+ if amt == 0 and stype == "neg": continue
1049
+
1050
+ w = abs(amt) / max_amt * 100 if max_amt > 0 else 0
1051
+
1052
+ if stype == "neg":
1053
+ color, bg = "#ff6b6b", "rgba(255,107,107,0.1)"
1054
+ val = f"-{abs(amt)/1e8:.1f}억"
1055
+ elif stype == "total":
1056
+ color, bg = "#6fd9a8", "rgba(111,217,168,0.15)"
1057
+ val = f"{amt/1e8:.1f}억"
1058
+ elif stype == "sub":
1059
+ color, bg = "#6495ed", "rgba(100,149,237,0.1)"
1060
+ val = f"{amt/1e8:.1f}억"
1061
+ else:
1062
+ color, bg = "#808090", "rgba(128,128,144,0.1)"
1063
+ val = f"{amt/1e8:.1f}억"
1064
+
1065
+ html += f"""
1066
+ <div style="background:{bg};padding:18px;border-radius:12px;border-left:4px solid {color};
1067
+ animation:slide-up 0.4s ease-out;">
1068
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
1069
+ <span style="font-weight:{'700' if stype in ['total','sub'] else '500'};color:#fff;">
1070
+ {icon} {name}
1071
+ </span>
1072
+ <span style="font-family:'Orbitron',sans-serif;font-size:22px;font-weight:700;color:{color};">{val}</span>
1073
+ </div>
1074
+ <div class="progress-ultra">
1075
+ <div class="fill" style="width:{w}%;background:{color};"></div>
1076
+ </div>
1077
+ </div>
1078
+ """
1079
+
1080
+ html += """
1081
+ </div>
1082
+ </div>
1083
+ """
1084
+ return html
1085
+
1086
+ # ============================================================================
1087
+ # ⏱️ 차트 9: 프로세스 타임라인
1088
+ # ============================================================================
1089
+
1090
+ def generate_timeline(current: int) -> str:
1091
+ """프로세스 타임라인"""
1092
+
1093
+ html = f"""
1094
+ <div class="chart-ultra">
1095
+ <h3 style="color:var(--neon-green);margin-bottom:20px;text-align:center;
1096
+ font-family:'Rajdhani',sans-serif;font-size:20px;">⏱️ 진행 현황</h3>
1097
+
1098
+ <!-- 진행률 바 -->
1099
+ <div style="margin-bottom:30px;">
1100
+ <div style="display:flex;justify-content:space-between;margin-bottom:10px;">
1101
+ <span style="color:var(--text-secondary);">전체 진행률</span>
1102
+ <span style="color:var(--neon-green);font-family:'Orbitron',sans-serif;font-weight:600;">
1103
+ {current}/8 ({current*12.5:.0f}%)
1104
+ </span>
1105
+ </div>
1106
+ <div class="progress-ultra" style="height:16px;">
1107
+ <div class="fill" style="width:{current*12.5}%;"></div>
1108
+ </div>
1109
+ </div>
1110
+
1111
+ <!-- 단계 그리드 -->
1112
+ <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:14px;">
1113
+ """
1114
+
1115
+ for i, step in enumerate(PROCESS_STEPS):
1116
+ done = i < current
1117
+ curr = i == current
1118
+
1119
+ if done:
1120
+ border, bg, icon_bg, icon = "var(--neon-green)", "rgba(111,217,168,0.1)", "var(--neon-green)", "✓"
1121
+ elif curr:
1122
+ border, bg, icon_bg, icon = "#ffd93d", "rgba(255,217,61,0.1)", "#ffd93d", step['icon']
1123
+ else:
1124
+ border, bg, icon_bg, icon = "#3a3a55", "transparent", "#2a2a45", step['icon']
1125
+
1126
+ html += f"""
1127
+ <div style="background:{bg};border:2px solid {border};border-radius:14px;
1128
+ padding:18px;text-align:center;transition:all 0.3s ease;
1129
+ {'animation:neon-pulse 1.5s ease-in-out infinite;' if curr else ''}">
1130
+ <div style="width:50px;height:50px;margin:0 auto 12px;
1131
+ background:{icon_bg};border-radius:50%;
1132
+ display:flex;align-items:center;justify-content:center;
1133
+ font-size:24px;color:{'#0d0d1a' if done else '#fff'};
1134
+ box-shadow:{'0 0 20px '+border+'60' if curr else 'none'};">
1135
+ {icon}
1136
+ </div>
1137
+ <div style="font-size:13px;font-weight:600;color:{'var(--neon-green)' if done else '#ffd93d' if curr else 'var(--text-secondary)'};">
1138
+ {step['name']}
1139
+ </div>
1140
+ <div style="font-size:11px;color:var(--text-secondary);margin-top:4px;">{step['duration']}</div>
1141
+ </div>
1142
+ """
1143
+
1144
+ html += """
1145
+ </div>
1146
+ </div>
1147
+ """
1148
+ return html
1149
+
1150
+ # ============================================================================
1151
+ # 💳 차트 10: 신용점수 시뮬레이션
1152
+ # ============================================================================
1153
+
1154
+ def generate_credit_sim(current: int, improvements: dict) -> str:
1155
+ """신용점수 시뮬레이션"""
1156
+
1157
+ improve_list = {
1158
+ "제2금융권 대출 상환": 30,
1159
+ "카드론 상환": 25,
1160
+ "연체 해소": 40,
1161
+ "카드 사용률 30% 이하": 15,
1162
+ "통신비 자동이체": 5,
1163
+ "보험료 정상납부": 5
1164
+ }
1165
+
1166
+ total_up = sum(improve_list[k] for k in improvements if improvements.get(k))
1167
+ projected = min(900, current + total_up)
1168
+
1169
+ cur_color = "#6fd9a8" if current >= 750 else "#ffd93d" if current >= 700 else "#ff6b6b"
1170
+ proj_color = "#6fd9a8" if projected >= 750 else "#ffd93d" if projected >= 700 else "#ff6b6b"
1171
+
1172
+ html = f"""
1173
+ <div class="chart-ultra">
1174
+ <h3 style="color:var(--neon-green);margin-bottom:25px;text-align:center;
1175
+ font-family:'Rajdhani',sans-serif;font-size:20px;">💳 신용점수 시뮬레이션</h3>
1176
+
1177
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:30px;">
1178
+ <!-- 점수 비교 -->
1179
+ <div>
1180
+ <div style="display:flex;gap:20px;margin-bottom:25px;">
1181
+ <div class="score-card" style="flex:1;">
1182
+ <div class="label">현재</div>
1183
+ <div class="value" style="color:{cur_color};">{current}</div>
1184
+ </div>
1185
+ <div style="display:flex;align-items:center;font-size:28px;color:var(--neon-green);">→</div>
1186
+ <div class="score-card highlight" style="flex:1;">
1187
+ <div class="label">예상</div>
1188
+ <div class="value">{projected}</div>
1189
+ <div style="color:var(--neon-green);font-size:14px;margin-top:5px;">+{total_up}점</div>
1190
+ </div>
1191
+ </div>
1192
+
1193
+ <!-- 개선 방법 -->
1194
+ <div style="background:#1a1a2e;padding:20px;border-radius:14px;">
1195
+ <h4 style="color:#ffd93d;margin:0 0 15px;">🔧 개선 방법</h4>
1196
+ <div style="display:flex;flex-direction:column;gap:10px;">
1197
+ """
1198
+
1199
+ for item, pts in improve_list.items():
1200
+ chk = improvements.get(item, False)
1201
+ html += f"""
1202
+ <div style="display:flex;align-items:center;gap:12px;padding:12px;
1203
+ background:{'rgba(111,217,168,0.1)' if chk else '#252540'};
1204
+ border-radius:10px;border:1px solid {'var(--neon-green)' if chk else '#3a3a55'};">
1205
+ <span style="color:{'var(--neon-green)' if chk else 'var(--text-secondary)'};font-size:18px;">
1206
+ {'✓' if chk else '○'}
1207
+ </span>
1208
+ <span style="flex:1;color:#fff;font-size:13px;">{item}</span>
1209
+ <span style="color:var(--neon-green);font-size:12px;font-weight:600;">+{pts}점</span>
1210
+ </div>
1211
+ """
1212
+
1213
+ html += """
1214
+ </div>
1215
+ </div>
1216
+ </div>
1217
+
1218
+ <!-- 등급 가이드 -->
1219
+ <div style="background:#1a1a2e;padding:20px;border-radius:14px;">
1220
+ <h4 style="color:#fff;margin:0 0 15px;">📊 점수별 영향</h4>
1221
+ <div style="display:flex;flex-direction:column;gap:10px;">
1222
+ """
1223
+
1224
+ ranges = [(750, 900, "우수", "#6fd9a8", "제한없음"), (700, 749, "양호", "#7be8c0", "일부제한"),
1225
+ (650, 699, "보통", "#ffd93d", "조건부"), (600, 649, "주의", "#ff9500", "제한적"), (0, 599, "위험", "#ff6b6b", "불가")]
1226
+
1227
+ for lo, hi, label, color, limit in ranges:
1228
+ is_cur = lo <= current <= hi
1229
+ html += f"""
1230
+ <div style="display:flex;align-items:center;gap:12px;padding:12px;
1231
+ background:{'rgba(111,217,168,0.1)' if is_cur else 'transparent'};
1232
+ border-radius:8px;border:1px solid {'var(--neon-green)' if is_cur else 'transparent'};">
1233
+ <div style="width:14px;height:14px;background:{color};border-radius:50%;"></div>
1234
+ <span style="width:70px;color:#fff;">{lo}-{hi}</span>
1235
+ <span style="width:50px;color:{color};font-weight:600;">{label}</span>
1236
+ <span style="color:var(--text-secondary);font-size:12px;">{limit}</span>
1237
+ </div>
1238
+ """
1239
+
1240
+ html += """
1241
+ </div>
1242
+ </div>
1243
+ </div>
1244
+ </div>
1245
+ """
1246
+ return html
1247
+
1248
+ # ============================================================================
1249
+ # 🎯 차트 11: 종합 인포그래픽
1250
+ # ============================================================================
1251
+
1252
+ def generate_infographic(data: dict) -> str:
1253
+ """종합 인포그래픽 보드"""
1254
+
1255
+ score = data.get("종합점수", 75)
1256
+ amount = data.get("예상금액", 2.5)
1257
+ sinbo = data.get("신보", 70)
1258
+ kibo = data.get("기보", 65)
1259
+ jaedan = data.get("재단", 80)
1260
+ cautions = data.get("유의사항", 3)
1261
+ credit = data.get("신용점수", 720)
1262
+
1263
+ score_color = "#6fd9a8" if score >= 70 else "#ffd93d" if score >= 50 else "#ff6b6b"
1264
+
1265
+ html = f"""
1266
+ <div class="chart-ultra" style="background:linear-gradient(135deg,#0d1117 0%,#161b22 50%,#0d1117 100%);">
1267
+
1268
+ <!-- 헤더 -->
1269
+ <div style="text-align:center;margin-bottom:30px;padding:25px;
1270
+ background:linear-gradient(145deg,#1a2a30,#0d1a1f);border-radius:16px;
1271
+ border:2px solid var(--neon-green)30;">
1272
+ <h2 style="margin:0 0 10px;font-family:'Orbitron',sans-serif;font-size:28px;
1273
+ background:linear-gradient(135deg,#6fd9a8,#fff,#6fd9a8);
1274
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;
1275
+ animation:glow-text 2s ease-in-out infinite;">
1276
+ 🎮 미네랄핵 분석 완료
1277
+ </h2>
1278
+ <p style="color:var(--text-secondary);margin:0;">정책자금 사전심사 종합 리포트</p>
1279
+ </div>
1280
+
1281
+ <!-- 메인 KPI -->
1282
+ <div style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:20px;margin-bottom:25px;">
1283
+
1284
+ <!-- 예상 지원금액 (대형) -->
1285
+ <div class="score-card highlight" style="padding:30px;">
1286
+ <div style="font-size:18px;color:var(--text-secondary);margin-bottom:10px;">💰 예상 지원금액</div>
1287
+ <div style="font-family:'Orbitron',sans-serif;font-size:64px;font-weight:900;
1288
+ background:linear-gradient(135deg,#6fd9a8,#fff);
1289
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;">
1290
+ {amount:.1f}<span style="font-size:32px;">억원</span>
1291
+ </div>
1292
+ </div>
1293
+
1294
+ <!-- 종합점수 -->
1295
+ <div class="score-card" style="border-top:4px solid {score_color};">
1296
+ <div class="label">📊 종합점수</div>
1297
+ <div class="value" style="color:{score_color};">{score}</div>
1298
+ <div style="margin-top:10px;font-size:13px;color:{score_color};">
1299
+ {'우수' if score >= 70 else '양호' if score >= 50 else '주의'}
1300
+ </div>
1301
+ </div>
1302
+
1303
+ <!-- 유의사항 -->
1304
+ <div class="score-card" style="border-top:4px solid {'#ff6b6b' if cautions > 5 else '#ffd93d' if cautions > 0 else '#6fd9a8'};">
1305
+ <div class="label">⚠️ 유의사항</div>
1306
+ <div class="value" style="color:{'#ff6b6b' if cautions > 5 else '#ffd93d'};">{cautions}</div>
1307
+ <div style="margin-top:10px;font-size:13px;color:var(--text-secondary);">건 해결 필요</div>
1308
+ </div>
1309
+ </div>
1310
+
1311
+ <!-- 기관별 확률 바 -->
1312
+ <div style="background:#1a1a2e;padding:25px;border-radius:16px;margin-bottom:25px;">
1313
+ <h4 style="color:var(--neon-green);margin:0 0 20px;font-size:16px;">🏦 기관별 승인확률</h4>
1314
+
1315
+ <div style="display:flex;flex-direction:column;gap:15px;">
1316
+ <div style="display:flex;align-items:center;gap:15px;">
1317
+ <span style="width:100px;color:var(--text-secondary);">신용보증기금</span>
1318
+ <div style="flex:1;height:24px;background:#252540;border-radius:12px;overflow:hidden;">
1319
+ <div style="width:{sinbo}%;height:100%;background:linear-gradient(90deg,#6fd9a8,#8fe8c0);
1320
+ border-radius:12px;display:flex;align-items:center;justify-content:flex-end;padding-right:10px;">
1321
+ <span style="font-family:'Orbitron',sans-serif;font-size:12px;color:#0d0d1a;font-weight:700;">{sinbo}%</span>
1322
+ </div>
1323
+ </div>
1324
+ </div>
1325
+ <div style="display:flex;align-items:center;gap:15px;">
1326
+ <span style="width:100px;color:var(--text-secondary);">기술보증기금</span>
1327
+ <div style="flex:1;height:24px;background:#252540;border-radius:12px;overflow:hidden;">
1328
+ <div style="width:{kibo}%;height:100%;background:linear-gradient(90deg,#ffd93d,#ffe880);
1329
+ border-radius:12px;display:flex;align-items:center;justify-content:flex-end;padding-right:10px;">
1330
+ <span style="font-family:'Orbitron',sans-serif;font-size:12px;color:#0d0d1a;font-weight:700;">{kibo}%</span>
1331
+ </div>
1332
+ </div>
1333
+ </div>
1334
+ <div style="display:flex;align-items:center;gap:15px;">
1335
+ <span style="width:100px;color:var(--text-secondary);">지역신보재단</span>
1336
+ <div style="flex:1;height:24px;background:#252540;border-radius:12px;overflow:hidden;">
1337
+ <div style="width:{jaedan}%;height:100%;background:linear-gradient(90deg,#6495ed,#8ab4f8);
1338
+ border-radius:12px;display:flex;align-items:center;justify-content:flex-end;padding-right:10px;">
1339
+ <span style="font-family:'Orbitron',sans-serif;font-size:12px;color:#0d0d1a;font-weight:700;">{jaedan}%</span>
1340
+ </div>
1341
+ </div>
1342
+ </div>
1343
+ </div>
1344
+ </div>
1345
+
1346
+ <!-- 권고사항 -->
1347
+ <div style="padding:20px;background:linear-gradient(145deg,#1a2a30,#0d1a1f);
1348
+ border-radius:14px;border-left:4px solid var(--neon-green);">
1349
+ <h4 style="color:var(--neon-green);margin:0 0 15px;">💡 핵심 권고사항</h4>
1350
+ <ul style="margin:0;padding-left:20px;color:#fff;line-height:1.8;">
1351
+ {'<li style="color:#ff6b6b;">불가 사유 우선 해결 필요</li>' if cautions > 3 else ''}
1352
+ {'<li>신용점수 700점 이상 유지 권장</li>' if credit < 700 else '<li style="color:#6fd9a8;">✓ 신용점수 양호</li>'}
1353
+ <li>필요서류: 재무제표, 사업자등록증, 납세증명서</li>
1354
+ <li>추천 기관: {data.get('추천기관', '신용보증기금')}</li>
1355
+ </ul>
1356
+ </div>
1357
+ </div>
1358
+ """
1359
+ return html
1360
+
1361
+ # ============================================================================
1362
+ # 메인 분석 함수
1363
+ # ============================================================================
1364
+
1365
+ def run_ultra_analysis(company_name, biz_num, industry, sales, years, credit_score, employees,
1366
+ education, tech_grade, has_patent, has_venture, has_innobiz, request_amt,
1367
+ total_assets, total_liab, cur_assets, cur_liab,
1368
+ op_profit, net_income, interest_exp, caution_checks,
1369
+ existing_guar, related_sales):
1370
+ """종합 분석 실행"""
1371
+
1372
+ # 재무비율
1373
+ equity = total_assets - total_liab
1374
+ debt_ratio = (total_liab / equity * 100) if equity > 0 else 999
1375
+ current_ratio = (cur_assets / cur_liab * 100) if cur_liab > 0 else 999
1376
+ op_margin = (op_profit / (sales*1e8) * 100) if sales > 0 else 0
1377
+ int_coverage = (op_profit*1e6 / (interest_exp*1e6)) if interest_exp > 0 else 999
1378
+
1379
+ # 점수 계산
1380
+ sinbo_sc = 5 if sales >= 4 else 4 if sales >= 3 else 3 if sales >= 2 else 2 if sales >= 1 else 1
1381
+ sinbo_sc += (1 if has_patent else 0) + (3 if education == "박사" else 2 if education == "석사" else 0)
1382
+ sinbo_pct = min(95, sinbo_sc * 10)
1383
+
1384
+ kibo_sc = 5 + (2 if education == "박사" else 1 if education == "석사" else 0)
1385
+ kibo_sc += (2 if tech_grade in ["특급","고급"] else 1 if tech_grade == "중급" else 0) + (1 if has_patent else 0)
1386
+ kibo_pct = min(95, kibo_sc * 10)
1387
+
1388
+ jaedan_sc = 10 if sales >= 1 and years >= 3 else 8 if sales >= 1 else 6
1389
+ jaedan_pct = min(95, jaedan_sc * 10)
1390
+
1391
+ # 유의사항 분석
1392
+ failed = sum(1 for c in caution_checks if CAUTION_ITEMS.get(c, {}).get("severity") == "불가")
1393
+ cond = sum(1 for c in caution_checks if CAUTION_ITEMS.get(c, {}).get("severity") == "조건부")
1394
+
1395
+ base_score = (sinbo_pct + kibo_pct + jaedan_pct) / 3
1396
+ deduction = failed * 15 + cond * 5
1397
+ total_score = max(0, min(100, int(base_score - deduction)))
1398
+
1399
+ # 금액 계산
1400
+ adj_sales = sales * 1e8 - related_sales * 1e8
1401
+ turnover = adj_sales / 7
1402
+ cap_limit = max(3e8, equity * 3e6) if equity > 0 else 3e8
1403
+ final_amt = min(turnover, cap_limit) - existing_guar * 1e8
1404
+ if "45" in caution_checks: final_amt = min(final_amt, 2e8)
1405
+ final_amt = max(0, final_amt)
1406
+
1407
+ # 추천 기관
1408
+ recommend = "기술보증기금" if has_patent or has_venture or has_innobiz else "신용보증기금"
1409
+
1410
+ # 차트 데이터
1411
+ calc_data = {
1412
+ "기준매출": sales * 1e8, "관계차감": related_sales * 1e8, "조정매출": adj_sales,
1413
+ "회전적용": adj_sales - turnover, "1차산출": turnover, "기존사용": existing_guar * 1e8, "최종금액": final_amt
1414
+ }
1415
+
1416
+ radar_data = {
1417
+ "수익성": min(100, max(0, int(op_margin * 5 + 50))),
1418
+ "안정성": min(100, max(0, int(100 - debt_ratio / 3))),
1419
+ "성장성": min(100, max(0, 60)),
1420
+ "활동성": min(100, max(0, int(current_ratio / 2))),
1421
+ "생산성": min(100, max(0, 55))
1422
+ }
1423
+
1424
+ company_bench = {"매출성장률": 10, "영업이익률": op_margin, "부채비율": debt_ratio, "신용등급": credit_score, "고용성장": 15}
1425
+ ind_data = INDUSTRY_FINANCIAL_RATIOS.get(industry, INDUSTRY_FINANCIAL_RATIOS["C 제조업"])
1426
+ industry_bench = {"매출성장률": ind_data.get("매출액증가율", 10), "영업이익률": ind_data.get("매출액영업이익률", 5),
1427
+ "부채비율": ind_data.get("부채비율", 150), "신용등급": 700, "고용성장": 10}
1428
+
1429
+ kpi_data = {"예상금액": final_amt / 1e8, "종합점수": total_score, "추천기관": recommend,
1430
+ "리스크": len(caution_checks), "신용등급": credit_score, "승인확률": (sinbo_pct + kibo_pct + jaedan_pct) // 3}
1431
+
1432
+ improvements = {"제2금융권 대출 상환": credit_score < 700, "카드론 상환": credit_score < 720,
1433
+ "연체 해소": "21" in caution_checks, "카드 사용률 30% 이하": False,
1434
+ "통신비 자동이체": True, "보험료 정상납부": True}
1435
+
1436
+ info_data = {"종합점수": total_score, "예상금액": final_amt / 1e8, "신보": sinbo_pct, "기보": kibo_pct,
1437
+ "재단": jaedan_pct, "유의사항": len(caution_checks), "신용점수": credit_score, "추천기관": recommend}
1438
+
1439
+ # 차트 생성
1440
+ gauge = generate_ultra_gauge(total_score, "🎮 미네랄핵 점수")
1441
+ radar = generate_radar_chart(radar_data)
1442
+ donut = generate_donut_chart(sinbo_pct, kibo_pct, jaedan_pct)
1443
+ pipeline = generate_pipeline(1, {"신청": request_amt, "예상": final_amt / 1e8, "기존": existing_guar})
1444
+ heatmap = generate_heatmap(caution_checks)
1445
+ benchmark = generate_benchmark_bars(company_bench, industry_bench)
1446
+ scorecard = generate_scorecard_dashboard(kpi_data)
1447
+ waterfall = generate_waterfall(calc_data)
1448
+ timeline = generate_timeline(5)
1449
+ credit_sim = generate_credit_sim(credit_score, improvements)
1450
+ infographic = generate_infographic(info_data)
1451
+
1452
+ return (infographic, gauge, radar, donut, pipeline, heatmap, benchmark, scorecard, waterfall, timeline, credit_sim)
1453
+
1454
+ # ============================================================================
1455
+ # Gradio UI
1456
+ # ============================================================================
1457
+
1458
+ # ============================================================================
1459
+ # 저장 / 불러오기 핸들러 (cache_db 연동)
1460
+ # ============================================================================
1461
+
1462
+ def save_fund_results(email, company_name, biz_num, industry, sales, years, credit_score, employees,
1463
+ education, tech_grade, has_patent, has_venture, has_innobiz, request_amt,
1464
+ total_assets, total_liab, cur_assets, cur_liab,
1465
+ op_profit, net_income, interest_exp, caution_checks,
1466
+ existing_guar, related_sales):
1467
+ """분석 결과 저장"""
1468
+ if not HAS_CACHE_DB:
1469
+ return "⚠️ cache_db 모듈을 찾을 수 없습니다. app.py 통합 환경에서 실행해주세요."
1470
+
1471
+ if not email or '@' not in email:
1472
+ return "⚠️ 올바른 이메일 주소를 입력해주세요."
1473
+
1474
+ company_info = {
1475
+ "company_name": company_name, "biz_num": biz_num, "industry": industry,
1476
+ "sales": sales, "years": years, "employees": employees,
1477
+ "education": education, "tech_grade": tech_grade,
1478
+ "has_patent": has_patent, "has_venture": has_venture, "has_innobiz": has_innobiz
1479
+ }
1480
+
1481
+ financial_info = {
1482
+ "credit_score": credit_score, "request_amt": request_amt,
1483
+ "total_assets": total_assets, "total_liab": total_liab,
1484
+ "cur_assets": cur_assets, "cur_liab": cur_liab,
1485
+ "op_profit": op_profit, "net_income": net_income, "interest_exp": interest_exp,
1486
+ "existing_guar": existing_guar, "related_sales": related_sales
1487
+ }
1488
+
1489
+ analysis_results = {
1490
+ "saved_at": datetime.now().isoformat(),
1491
+ "version": "v7.0"
1492
+ }
1493
+
1494
+ success, msg = _fund_cache.save_fund_analysis(email, company_info, financial_info, caution_checks, analysis_results)
1495
+ return msg
1496
+
1497
+
1498
+ def load_fund_results(email):
1499
+ """저장된 분석 기록 불러오기 → 입력 필드에 복원"""
1500
+ if not HAS_CACHE_DB:
1501
+ return [gr.update()] * 22 + ["⚠️ cache_db 모듈을 찾을 수 없습니다. app.py 통합 환경에서 실행해주세요."]
1502
+
1503
+ if not email or '@' not in email:
1504
+ return [gr.update()] * 22 + ["⚠️ 올바른 이메일 주소를 입력해주세요."]
1505
+
1506
+ record, msg = _fund_cache.load_fund_profile(email)
1507
+
1508
+ if record is None:
1509
+ return [gr.update()] * 22 + [msg]
1510
+
1511
+ ci = record.get("company_info", {})
1512
+ fi = record.get("financial_info", {})
1513
+ cc = record.get("caution_checks", [])
1514
+
1515
+ return [
1516
+ gr.update(value=ci.get("company_name", "")), # company_name
1517
+ gr.update(value=ci.get("biz_num", "")), # biz_num
1518
+ gr.update(value=ci.get("industry", "C 제조업")), # industry
1519
+ gr.update(value=ci.get("sales", 5)), # sales
1520
+ gr.update(value=ci.get("years", 3)), # years
1521
+ gr.update(value=fi.get("credit_score", 720)), # credit_score
1522
+ gr.update(value=ci.get("employees", 10)), # employees
1523
+ gr.update(value=ci.get("education", "학사")), # education
1524
+ gr.update(value=ci.get("tech_grade", "중급")), # tech_grade
1525
+ gr.update(value=ci.get("has_patent", False)), # has_patent
1526
+ gr.update(value=ci.get("has_venture", False)), # has_venture
1527
+ gr.update(value=ci.get("has_innobiz", False)), # has_innobiz
1528
+ gr.update(value=fi.get("request_amt", 3)), # request_amt
1529
+ gr.update(value=fi.get("total_assets", 1000)), # total_assets
1530
+ gr.update(value=fi.get("total_liab", 600)), # total_liab
1531
+ gr.update(value=fi.get("cur_assets", 500)), # cur_assets
1532
+ gr.update(value=fi.get("cur_liab", 400)), # cur_liab
1533
+ gr.update(value=fi.get("op_profit", 50)), # op_profit
1534
+ gr.update(value=fi.get("net_income", 30)), # net_income
1535
+ gr.update(value=fi.get("interest_exp", 10)), # interest_exp
1536
+ gr.update(value=cc), # caution_checks
1537
+ gr.update(value=fi.get("existing_guar", 0)), # existing_guar
1538
+ gr.update(value=fi.get("related_sales", 0)), # related_sales
1539
+ msg # status
1540
+ ]
1541
+
1542
+
1543
+ def create_fund_tab():
1544
+ """정책자금 탭 생성"""
1545
+
1546
+ with gr.Tab("💰 정책자금 사전심사"):
1547
+ gr.HTML('''
1548
+ <div style="background:linear-gradient(135deg,#0d1117,#161b22,#0d1117);padding:35px;border-radius:20px;
1549
+ margin-bottom:24px;border:2px solid rgba(111,217,168,0.3);text-align:center;
1550
+ box-shadow:0 10px 40px rgba(0,0,0,0.5);">
1551
+ <h2 style="margin:0 0 10px;font-family:'Orbitron',sans-serif;
1552
+ background:linear-gradient(135deg,#6fd9a8,#fff,#6fd9a8);
1553
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;font-size:32px;
1554
+ animation:glow-text 2s ease-in-out infinite;">
1555
+ 🎮 미네랄핵 ULTRA v7.0
1556
+ </h2>
1557
+ <p style="color:#8888a0;margin:0;font-size:14px;">정책자금 사전심사 시스템 | 11개 비주얼 차트</p>
1558
+ </div>
1559
+ ''')
1560
+
1561
+ with gr.Row():
1562
+ with gr.Column(scale=3):
1563
+ fund_email = gr.Textbox(label="📧 이메일", placeholder="example@company.com")
1564
+ with gr.Column(scale=1):
1565
+ with gr.Row():
1566
+ fund_load = gr.Button("📥 불러오기", size="sm")
1567
+ fund_save = gr.Button("💾 저장", size="sm", variant="primary")
1568
+
1569
+ fund_status = gr.Textbox(label="상태", interactive=False, lines=1)
1570
+
1571
+ with gr.Tabs():
1572
+ with gr.Tab("📝 기업정보"):
1573
+ with gr.Row():
1574
+ with gr.Column():
1575
+ company_name = gr.Textbox(label="회사명", placeholder="(주)회사명")
1576
+ biz_num = gr.Textbox(label="사업자번호", placeholder="000-00-00000")
1577
+ industry = gr.Dropdown(label="업종", choices=list(INDUSTRY_FINANCIAL_RATIOS.keys()), value="C 제조업")
1578
+ sales = gr.Number(label="연매출 (억원)", value=5)
1579
+ years = gr.Number(label="업력 (년)", value=3)
1580
+ employees = gr.Number(label="종업원 (명)", value=10)
1581
+ with gr.Column():
1582
+ credit_score = gr.Slider(label="신용점수", minimum=300, maximum=900, value=720, step=10)
1583
+ education = gr.Dropdown(label="학력", choices=["고졸이하", "학사", "석사", "박사"], value="학사")
1584
+ tech_grade = gr.Dropdown(label="기술자격", choices=["초급이하", "초급", "중급", "고급", "특급"], value="중급")
1585
+ has_patent = gr.Checkbox(label="특허 보유")
1586
+ has_venture = gr.Checkbox(label="벤처인증")
1587
+ has_innobiz = gr.Checkbox(label="이노비즈")
1588
+
1589
+ with gr.Row():
1590
+ with gr.Column():
1591
+ total_assets = gr.Number(label="총자산 (백만원)", value=1000)
1592
+ total_liab = gr.Number(label="총부채 (백만원)", value=600)
1593
+ cur_assets = gr.Number(label="유동자산 (백만원)", value=500)
1594
+ cur_liab = gr.Number(label="유동부채 (백만원)", value=400)
1595
+ with gr.Column():
1596
+ op_profit = gr.Number(label="영업이익 (백만원)", value=50)
1597
+ net_income = gr.Number(label="순이익 (백만원)", value=30)
1598
+ interest_exp = gr.Number(label="이자비용 (백만원)", value=10)
1599
+ request_amt = gr.Number(label="필요자금 (억원)", value=3)
1600
+
1601
+ with gr.Row():
1602
+ existing_guar = gr.Number(label="기존보증 (억원)", value=0)
1603
+ related_sales = gr.Number(label="관계기업매입 (억원)", value=0)
1604
+
1605
+ caution_opts = [(f"[{c}] {i['name']}", c) for c, i in list(CAUTION_ITEMS.items())[:25]]
1606
+ caution_checks = gr.CheckboxGroup(label="⚠️ 유의사항", choices=caution_opts, value=[])
1607
+
1608
+ analyze_btn = gr.Button("🔍 종합 분석", variant="primary", size="lg", elem_classes=["analyze-btn"])
1609
+
1610
+ # 결과 탭들
1611
+ with gr.Tab("📊 종합 인포그래픽"): out_info = gr.HTML()
1612
+ with gr.Tab("🎯 종합점수"): out_gauge = gr.HTML()
1613
+ with gr.Tab("🕸️ 재무 레이더"): out_radar = gr.HTML()
1614
+ with gr.Tab("🍩 기관별 확률"): out_donut = gr.HTML()
1615
+ with gr.Tab("💰 파이프라인"): out_pipeline = gr.HTML()
1616
+ with gr.Tab("🔥 리스크맵"): out_heatmap = gr.HTML()
1617
+ with gr.Tab("📈 벤치마크"): out_benchmark = gr.HTML()
1618
+ with gr.Tab("📋 KPI보드"): out_scorecard = gr.HTML()
1619
+ with gr.Tab("💧 워터폴"): out_waterfall = gr.HTML()
1620
+ with gr.Tab("⏱️ 타임라인"): out_timeline = gr.HTML()
1621
+ with gr.Tab("💳 신용시뮬"): out_credit = gr.HTML()
1622
+
1623
+ inputs = [company_name, biz_num, industry, sales, years, credit_score, employees,
1624
+ education, tech_grade, has_patent, has_venture, has_innobiz, request_amt,
1625
+ total_assets, total_liab, cur_assets, cur_liab,
1626
+ op_profit, net_income, interest_exp, caution_checks, existing_guar, related_sales]
1627
+
1628
+ outputs = [out_info, out_gauge, out_radar, out_donut, out_pipeline, out_heatmap,
1629
+ out_benchmark, out_scorecard, out_waterfall, out_timeline, out_credit]
1630
+
1631
+ analyze_btn.click(fn=run_ultra_analysis, inputs=inputs, outputs=outputs)
1632
+
1633
+ # 저장/불러오기 이벤트
1634
+ save_inputs = [fund_email] + inputs
1635
+ fund_save.click(fn=save_fund_results, inputs=save_inputs, outputs=[fund_status])
1636
+
1637
+ load_outputs = inputs + [fund_status]
1638
+ fund_load.click(fn=load_fund_results, inputs=[fund_email], outputs=load_outputs)
1639
+
1640
+
1641
+ def create_app():
1642
+ """독립 실행용"""
1643
+ with gr.Blocks(css=CUSTOM_CSS, title="미네랄핵 ULTRA v7.0") as app:
1644
+ gr.HTML("""
1645
+ <div style="text-align:center;padding:40px;background:linear-gradient(135deg,#0d1117,#161b22);
1646
+ border-radius:20px;margin-bottom:20px;border:1px solid rgba(111,217,168,0.2);">
1647
+ <h1 style="font-family:'Orbitron',sans-serif;font-size:42px;margin:0;
1648
+ background:linear-gradient(135deg,#6fd9a8,#fff,#6fd9a8);
1649
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;">
1650
+ 🎮 미네랄핵 ULTRA v7.0
1651
+ </h1>
1652
+ <p style="color:#8888a0;margin:15px 0 0;font-size:16px;">OKCEO 정책자금 사전심사 시스템</p>
1653
+ </div>
1654
+ """)
1655
+ create_fund_tab()
1656
+ return app
1657
+
1658
+
1659
+ if __name__ == "__main__":
1660
+ app = create_app()
1661
+ app.launch(server_name="0.0.0.0", server_port=7860)