Aoi785 commited on
Commit
4c3d1d2
ยท
verified ยท
1 Parent(s): a3467b2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -254
app.py CHANGED
@@ -1,284 +1,137 @@
1
- import time
2
  import gradio as gr
3
 
4
- # ===== ๋ฌผ๋ฆฌ ์ƒ์ˆ˜ =====
5
- RHO = 1000.0
6
- G = 9.80665
7
- PF_TARGET = 0.98
8
-
9
- # ===== ํšจ์œจ ๊ทผ์‚ฌ๊ณก์„  (์ƒ๋Œ€์œ ๋Ÿ‰ r = Q/Qn) =====
10
- def efficiency_from_relative_flow(r: float) -> float:
11
- r = max(0.0, min(1.2, float(r)))
12
- pts = [
13
- (0.00, 0.00), (0.10, 0.40), (0.30, 0.60), (0.50, 0.75),
14
- (0.70, 0.85), (0.90, 0.90), (1.00, 0.91), (1.20, 0.88),
15
- ]
16
- for (x1,y1),(x2,y2) in zip(pts[:-1], pts[1:]):
17
- if x1 <= r <= x2:
18
- t = 0.0 if x2==x1 else (r-x1)/(x2-x1)
19
- return y1 + t*(y2-y1)
20
- return 0.0
21
-
22
- def electrical_power_kw(Q: float, H: float, eta: float) -> float:
23
- if Q <= 0 or H <= 0 or eta <= 0:
24
- return 0.0
25
- return (RHO * G * Q * H * eta) / 1000.0
26
-
27
- # ===== ์šด์ „ ๋ชจ๋“œ (์ •์ƒ/์ €์œ ๋Ÿ‰/๊ณ ์œ ๋Ÿ‰/๋น„์ƒ) =====
28
- def decide_mode(Q, q_low, q_high, rain, dp, rain_thr, dp_lim, emerg):
29
- if emerg: return "๋น„์ƒ"
30
- if Q < q_low: return "์ €์œ ๋Ÿ‰"
31
- if (Q > q_high) or (dp > dp_lim) or (rain >= rain_thr): return "๊ณ ์œ ๋Ÿ‰"
 
 
 
 
 
32
  return "์ •์ƒ"
33
 
34
- def mode_policy(mode, Q, Qn, Hn):
35
- r = min(Q, Qn)/Qn if Qn>0 else 0.0
36
- eta = efficiency_from_relative_flow(r)
37
- p = electrical_power_kw(min(Q,Qn), Hn, eta)
38
-
39
- vane = gate = round(min(100.0, max(10.0, r*100.0)), 1)
40
- curtail = bypass = dumpload = False
41
- soc_low, soc_high = 55, 70
42
-
43
- if mode == "๊ณ ์œ ๋Ÿ‰":
44
- curtail = bypass = dumpload = True
45
- vane = gate = max(20.0, vane - 20.0)
46
- p *= 0.6
47
- soc_low, soc_high = 35, 45
48
-
49
  elif mode == "์ €์œ ๋Ÿ‰":
50
- vane = gate = max(15.0, min(55.0, vane))
51
- p *= 0.85
52
- soc_low, soc_high = 45, 65
53
-
54
- elif mode == "๋น„์ƒ":
55
- # ๐Ÿ”ด ์•ˆ์ „์ •์ง€: ์ „๋ ฅ์ƒ์‚ฐ 0, ๋ฐธ๋ธŒ ๋‹ซํž˜, ์šฐํšŒ ๊ฐœ๋ฐฉ
56
- curtail = True
57
- bypass = True
58
- dumpload = False
59
- vane = gate = 0.0
60
  eta = 0.0
61
- p = 0.0
62
- soc_low, soc_high = 30, 50
63
-
64
- return dict(
65
- eta=eta, p_kw=p, vane=vane, gate=gate,
66
- curtail=curtail, bypass=bypass, dumpload=dumpload,
67
- soc_low=soc_low, soc_high=soc_high
68
- )
69
-
70
- # ===== ๋ทฐ ์š”์†Œ (๋ฐฐ์ง€/๋„๋„›/์นฉ/๊ฒŒ์ด์ง€) =====
71
- def emergency_svg_badge():
72
- # ๋นจ๊ฐ„ ๊ฒฝ๊ณ  ์‚ผ๊ฐํ˜• + ํฐ์ƒ‰ ๋А๋‚Œํ‘œ (์ง„์งœ ์ƒ‰์ƒ ์ ์šฉ)
73
- return """
74
- <div style="display:flex;align-items:center;gap:10px;">
75
- <svg width="28" height="28" viewBox="0 0 64 64" aria-label="๊ฒฝ๊ณ ">
76
- <polygon points="32,6 58,54 6,54" fill="#ef4444" stroke="#b91c1c" stroke-width="3"/>
77
- <rect x="30" y="22" width="4" height="18" fill="#ffffff" rx="2"/>
78
- <circle cx="32" cy="46" r="3" fill="#ffffff"/>
79
- </svg>
80
- <span style="color:#ef4444;font-weight:900;font-size:18px;">๋น„์ƒ ๋ชจ๋“œ</span>
81
- </div>
82
- """
83
-
84
- def mode_badge(mode:str)->str:
85
- if mode == "๋น„์ƒ":
86
- return emergency_svg_badge()
87
- color = {"์ •์ƒ":"#22c55e","์ €์œ ๋Ÿ‰":"#eab308","๊ณ ์œ ๋Ÿ‰":"#f97316"}.get(mode,"#6b7280")
88
- label = f"์šด์ „ ๋ชจ๋“œ : {mode}"
89
- return f"""
90
- <div style="padding:12px 16px;border-radius:14px;background:{color};
91
- color:#fff;font-weight:800;display:inline-flex;gap:8px;align-items:center;">
92
- <span style="font-size:18px">โ—</span>
93
- <span style="font-size:18px">{label}</span>
94
- </div>
95
- """
96
-
97
- def donut(label:str, value:float, maxv:float, unit:str=""):
98
- maxv = max(1e-9, maxv)
99
- pct = max(0.0, min(value/maxv, 1.0))
100
- r = 42
101
- C = 2*3.14159*r
102
- offset = C * (1 - pct)
103
  return f"""
104
- <div style="display:flex;flex-direction:column;align-items:center;gap:6px;">
105
- <svg width="120" height="120" viewBox="0 0 120 120">
106
- <circle cx="60" cy="60" r="{r}" stroke="#e5e7eb" stroke-width="12" fill="none"/>
107
- <circle cx="60" cy="60" r="{r}" stroke="#3b82f6" stroke-width="12" fill="none"
108
- stroke-dasharray="{C:.1f}" stroke-dashoffset="{offset:.1f}"
109
- stroke-linecap="round" transform="rotate(-90 60 60)"/>
110
- <text x="60" y="60" text-anchor="middle" dominant-baseline="middle"
111
- font-size="16" font-weight="700">{value:.1f}{unit}</text>
112
- </svg>
113
- <div style="font-size:14px;color:#475569">{label}</div>
114
- </div>
115
- """
116
-
117
- def chip(label:str, on:bool):
118
- bg = "#10b981" if on else "#e5e7eb"
119
- fg = "#fff" if on else "#111827"
120
- text = "ON" if on else "OFF"
121
- return f"""
122
- <div style="padding:6px 10px;border-radius:999px;background:{bg};color:{fg};
123
- font-size:12px;font-weight:700;display:inline-block;">
124
- {label}: {text}
125
- </div>
126
- """
127
-
128
- def gauge_bar(label:str, value:float, maxv:float, unit:str=""):
129
- maxv = max(1e-9, maxv)
130
- pct = int(round(max(0.0, min(value/maxv,1.0))*100))
131
- return f"""
132
- <div style="margin:4px 0 10px 0;">
133
- <div style="font-size:13px;margin-bottom:4px">{label}: <b>{value:.1f}{unit}</b></div>
134
- <div style="width:100%;height:10px;border-radius:7px;background:#e5e7eb">
135
- <div style="width:{pct}%;height:10px;border-radius:7px;background:#6366f1"></div>
136
  </div>
137
  </div>
138
  """
139
 
140
- # ===== ๋ฉ”์ธ ์‹คํ–‰ =====
141
- def run(Q_in, rain_mm, dp_kpa, emergency, Hn, Qn, rain_thr, dp_limit):
 
 
142
  try:
143
- Q_in=float(Q_in); rain_mm=float(rain_mm); dp_kpa=float(dp_kpa)
144
- Hn=float(Hn); Qn=float(Qn); rain_thr=float(rain_thr); dp_limit=float(dp_limit)
145
- emergency=bool(emergency)
 
146
  except:
147
- return "์ž…๋ ฅ ์˜ค๋ฅ˜๋ฅผ ํ™•์ธํ•˜์„ธ์š”.", "", ""
148
 
149
- if Qn<=0 or Hn<=0:
150
- return "์„ค๊ณ„๊ฐ’(Hโ‚™, Qโ‚™)์€ 0๋ณด๋‹ค ์ปค์•ผ ํ•ฉ๋‹ˆ๋‹ค.", "", ""
151
 
152
- q_low, q_high = 0.4*Qn, 1.2*Qn
153
- mode = decide_mode(Q_in, q_low, q_high, rain_mm, dp_kpa, rain_thr, dp_limit, emergency)
154
- st = mode_policy(mode, Q_in, Qn, Hn)
155
-
156
- # ์š”์•ฝ(ํ•œ๊ตญ์–ด)
157
  summary = (
158
- f"**์œ ๋Ÿ‰ Q**: {Q_in:.2f} mยณ/s | **์„ค๊ณ„ Qโ‚™**: {Qn:.2f} mยณ/s "
159
- f"(์ž„๊ณ„ {q_low:.2f}~{q_high:.2f}) \n"
160
- f"**๋‚™์ฐจ H**: {Hn:.2f} m | **๊ฐ•์šฐ ์˜ˆ๋ณด**: {rain_mm:.1f} mm/24h | **ํŠธ๋ž˜์‹œ๋ž™ ฮ”P**: {dp_kpa:.1f} kPa \n"
161
- f"**ํšจ์œจ ฮท**: {st['eta']:.3f} | **์ถ”์ • ์ถœ๋ ฅ**: {st['p_kw']:.1f} kW | **์—ญ๋ฅ  ๋ชฉํ‘œ**: {PF_TARGET} \n"
162
- f"**๊ฐ€์ด๋“œ๋ฒ ์ธ ๊ฐœ๋„**: {st['vane']:.1f}% | **์ทจ์ˆ˜๋ฌธ ๊ฐœ๋„**: {st['gate']:.1f}%"
 
 
163
  )
164
- if mode == "๋น„์ƒ":
165
- summary += "\n> ๐Ÿ”ด ๋น„์ƒ: ๋ฐœ์ „ ์ •์ง€, ๋ฒ ์ธ/์ทจ์ˆ˜๋ฌธ 0%, ๋ฐ”์ดํŒจ์Šค ๊ฐœ๋ฐฉ, ์šด์˜์ž ์Šน์ธ ๋Œ€๊ธฐ"
166
 
167
- # ์‹œ๊ฐ ์นด๋“œ
168
- p_ref = electrical_power_kw(Qn, Hn, 0.9)
169
- header = mode_badge(mode)
170
  donuts = f"""
171
  <div style="display:flex;gap:28px;flex-wrap:wrap;align-items:center">
172
- {donut("ํšจ์œจ(ฮท)", st['eta']*100, 100, "%")}
173
- {donut("์ถœ๋ ฅ(kW)", st['p_kw'], max(1.0, p_ref), " kW")}
174
  </div>
175
  """
176
- bars = f"""
177
- {gauge_bar("๊ฐ€์ด๋“œ๋ฒ ์ธ ๊ฐœ๋„", st['vane'], 100.0, " %")}
178
- {gauge_bar("์ทจ์ˆ˜๋ฌธ ๊ฐœ๋„", st['gate'], 100.0, " %")}
179
- """
180
- chips = f"""
181
- <div style="display:flex;gap:8px;flex-wrap:wrap">
182
- {chip("์ถœ๋ ฅ ์ œํ•œ", st['curtail'])}
183
- {chip("๋ฐ”์ดํŒจ์Šค", st['bypass'])}
184
- {chip("๋คํ”„๋กœ๋“œ", st['dumpload'])}
185
- <div style="padding:6px 10px;border-radius:999px;background:#0ea5e9;color:#fff;font-size:12px;font-weight:700">
186
- ESS SoC ๋ชฉํ‘œ: {st['soc_low']}โ€“{st['soc_high']}%
187
- </div>
188
- </div>
189
- """
190
-
191
- # ๋น„์ƒ ๋ชจ๋“œ ํ•ต์‹ฌ ๋™์ž‘(์—„์„  5๊ฐœ) โ€“ ๋น„์ƒ์ผ ๋•Œ๋งŒ ๋…ธ์ถœ
192
- emergency_actions_html = ""
193
- if mode == "๋น„์ƒ":
194
- emergency_actions_html = """
195
- <div style="margin-top:8px;padding:10px 12px;border:1px solid #fecaca;border-radius:10px;background:#fef2f2">
196
- <div style="font-weight:800;color:#b91c1c;margin-bottom:6px">๋น„์ƒ ๋ชจ๋“œ ํ•ต์‹ฌ ๋™์ž‘</div>
197
- <ul style="margin:0 0 0 16px; padding:0; color:#7f1d1d">
198
- <li><b>๋ฐœ์ „ ์ •์ง€</b> (์ถœ๋ ฅ 0 kW, ํšจ์œจ 0)</li>
199
- <li><b>๊ฐ€์ด๋“œ๋ฒ ์ธ 0%</b>, <b>์ทจ์ˆ˜๋ฌธ 0%</b> (๋‹จ๊ณ„์  ๋‹ซํž˜)</li>
200
- <li><b>๋ฐ”์ดํŒจ์Šค ๊ฐœ๋ฐฉ</b> (์ˆ˜์œ„/ํ† ํฌ ์ƒ์Šน ๋ฐฉ์ง€)</li>
201
- <li><b>์ธ๋ฒ„ํ„ฐ ์ฐจ๋‹จ ๋ฐ DC ๋งํฌ ๋ฐฉ์ „</b></li>
202
- <li><b>๋กœ๊ทธ ๊ธฐ๋ก ๋ฐ ์šด์˜์ž ์Šน์ธ ๋Œ€๊ธฐ</b> (๋ณต๊ตฌ ์กฐ๊ฑด ํ™•์ธ)</li>
203
- </ul>
204
- </div>
205
- """
206
 
207
- panel = f"""
208
- <div style="display:flex;flex-direction:column;gap:14px">
209
- {header}
210
- {donuts}
211
- {bars}
212
- {chips}
213
- {emergency_actions_html}
214
- </div>
215
- """
216
-
217
- return summary, panel, "" # ์„ธ ๋ฒˆ์งธ ์นธ์€ ์—ฌ์œ (์ถ”๊ฐ€ ์„ค๋ช…์šฉ)
218
-
219
- # ===== ํ”„๋ฆฌ์…‹ =====
220
- def preset_normal(Qn): Qn=float(Qn); return 0.8*Qn, 10.0, 1.0, False
221
- def preset_low(Qn): Qn=float(Qn); return 0.3*Qn, 5.0, 0.5, False
222
- def preset_high(Qn): Qn=float(Qn); return 1.3*Qn, 60.0, 4.0, False
223
- def preset_emerg(Qn): Qn=float(Qn); return 0.8*Qn, 10.0, 1.0, True
224
-
225
- # ===== ์ž๋™์žฌ์ƒ(๋ฐ๋ชจ) =====
226
- def autoplay(Qn, Hn, rain_thr, dp_limit):
227
- steps = [
228
- ("์ •์ƒ", preset_normal(Qn)),
229
- ("์ €์œ ๋Ÿ‰", preset_low(Qn)),
230
- ("๊ณ ์œ ๋Ÿ‰", preset_high(Qn)),
231
- ("๋น„์ƒ", preset_emerg(Qn)),
232
- ]
233
- for name, (Q_in, rain, dp, emer) in steps:
234
- md, panel, extra = run(Q_in, rain, dp, emer, Hn, Qn, rain_thr, dp_limit)
235
- yield (Q_in, rain, dp, emer, md, panel, extra)
236
- time.sleep(1.2)
237
-
238
- # ===== Gradio UI =====
239
- with gr.Blocks(theme=gr.themes.Soft(), title="์†Œ์ˆ˜๋ ฅ ์Šค๋งˆํŠธ ์šด์ „๋ชจ๋“œ ๋Œ€์‹œ๋ณด๋“œ (Emergency Enhanced)") as app:
240
- gr.Markdown("## ๐ŸŒŠ ์†Œ์ˆ˜๋ ฅ ์Šค๋งˆํŠธ ์šด์ „๋ชจ๋“œ ๋Œ€์‹œ๋ณด๋“œ")
241
 
 
 
 
 
 
242
  with gr.Row():
243
- with gr.Column(scale=1):
244
- Q_in = gr.Number(label="ํ˜„์žฌ ์œ ๋Ÿ‰ Q (mยณ/s)", value=3.0)
245
- rain = gr.Number(label="๊ฐ•์šฐ ์˜ˆ๋ณด (mm/24h)", value=10)
246
- dp = gr.Number(label="ํŠธ๋ž˜์‹œ๋ž™ ฮ”P (kPa)", value=1.0)
247
- emer = gr.Checkbox(label="๋น„์ƒ ์•Œ๋žŒ", value=False)
248
-
249
- with gr.Accordion("๊ณ ๊ธ‰ ์„ค์ • (์„ค๊ณ„/์ž„๊ณ„๊ฐ’)", open=False):
250
- with gr.Row():
251
- Hn = gr.Number(label="์„ค๊ณ„ ๋‚™์ฐจ Hโ‚™ (m)", value=8.0)
252
- Qn = gr.Number(label="์„ค๊ณ„ ์œ ๋Ÿ‰ Qโ‚™ (mยณ/s)", value=3.5)
253
- with gr.Row():
254
- rain_thr = gr.Number(label="๊ณ ์œ ๋Ÿ‰ ํŒ๋‹จ ๊ฐ•์šฐ ์ž„๊ณ„ (mm/24h)", value=40.0)
255
- dp_limit = gr.Number(label="ฮ”P ์ž„๊ณ„ (kPa)", value=3.0)
256
-
257
- with gr.Row():
258
- gr.Button("ํ”„๋ฆฌ์…‹: ์ •์ƒ").click(preset_normal, [Qn], [Q_in, rain, dp, emer])
259
- gr.Button("ํ”„๋ฆฌ์…‹: ์ €์œ ๋Ÿ‰").click(preset_low, [Qn], [Q_in, rain, dp, emer])
260
- gr.Button("ํ”„๋ฆฌ์…‹: ๊ณ ์œ ๋Ÿ‰").click(preset_high, [Qn], [Q_in, rain, dp, emer])
261
- gr.Button("ํ”„๋ฆฌ์…‹: ๋น„์ƒ").click(preset_emerg, [Qn], [Q_in, rain, dp, emer])
262
 
263
- run_btn = gr.Button("์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰", variant="primary")
264
- demo_btn = gr.Button("๐ŸŽฌ ์‹œ์—ฐ ์ž๋™์žฌ์ƒ", variant="secondary")
265
 
266
- with gr.Column(scale=2):
267
- out_md = gr.Markdown()
268
- out_vis = gr.HTML()
269
- out_extra = gr.HTML()
270
 
271
- run_btn.click(
272
- fn=run,
273
- inputs=[Q_in, rain, dp, emer, Hn, Qn, rain_thr, dp_limit],
274
- outputs=[out_md, out_vis, out_extra]
275
- )
276
-
277
- demo_btn.click(
278
- fn=autoplay,
279
- inputs=[Qn, Hn, rain_thr, dp_limit],
280
- outputs=[Q_in, rain, dp, emer, out_md, out_vis, out_extra]
281
- )
282
 
 
 
 
283
  if __name__ == "__main__":
284
- app.launch()
 
 
1
  import gradio as gr
2
 
3
+ # ------------------------------
4
+ # ์„ค๊ณ„ ์ƒ์ˆ˜
5
+ # ------------------------------
6
+ Hn = 8.0 # ์„ค๊ณ„ ๋‚™์ฐจ (m)
7
+ Qn = 3.5 # ์„ค๊ณ„ ์œ ๋Ÿ‰ (mยณ/s)
8
+ rain_thr = 40 # ๊ณ ์œ ๋Ÿ‰ ํŒ์ • ๊ฐ•์ˆ˜๋Ÿ‰ (mm/24h)
9
+ dp_limit = 3.0 # ํŠธ๋ž˜์‹œ๋ž™ ๋ง‰ํž˜ ํ•œ๊ณ„ (kPa)
10
+ soc_limit = 90 # ESS ๊ณผ์ถฉ์ „ ํ•œ๊ณ„ (%)
11
+
12
+ PF_TARGET = 0.95 # ๋ชฉํ‘œ ์—ญ๋ฅ 
13
+ RHO = 1000 # ๋ฌผ ๋ฐ€๋„ (kg/mยณ)
14
+ G = 9.81 # ์ค‘๋ ฅ๊ฐ€์†๋„ (m/sยฒ)
15
+
16
+ # ------------------------------
17
+ # ์ถœ๋ ฅ ๊ณ„์‚ฐ ํ•จ์ˆ˜
18
+ # ------------------------------
19
+ def electrical_power_kw(Q, H, eta):
20
+ return RHO * G * Q * H * eta / 1000.0
21
+
22
+ # ------------------------------
23
+ # ๋ชจ๋“œ ํŒ์ • ํ•จ์ˆ˜
24
+ # ------------------------------
25
+ def decide_mode(Q, rain, dp, soc):
26
+ # ์ •๋น„ ๋ชจ๋“œ ์ตœ์šฐ์„  (ํŠธ๋ž˜์‹œ๋ž™ ๋ง‰ํž˜, ESS ๊ณผ์ถฉ์ „)
27
+ if dp > dp_limit or soc >= soc_limit:
28
+ return "์ •๋น„"
29
+ # ์ €์œ ๋Ÿ‰
30
+ if Q < 0.4 * Qn:
31
+ return "์ €์œ ๋Ÿ‰"
32
+ # ๊ณ ์œ ๋Ÿ‰
33
+ if Q > 1.2 * Qn or rain >= rain_thr:
34
+ return "๊ณ ์œ ๋Ÿ‰"
35
+ # ์ •์ƒ
36
  return "์ •์ƒ"
37
 
38
+ # ------------------------------
39
+ # ๋ชจ๋“œ๋ณ„ ๋™์ž‘ ์ •์ฑ…
40
+ # ------------------------------
41
+ def mode_policy(mode, Q, H):
42
+ if mode == "์ •์ƒ":
43
+ eta = 0.90
44
+ p_kw = electrical_power_kw(Q, H, eta)
45
+ return {"eta": eta, "p_kw": p_kw, "desc": "์ •์ƒ ์šด์ „: ํšจ์œจ ์ตœ์ ์ ์—์„œ ๋ฐœ์ „"}
 
 
 
 
 
 
 
46
  elif mode == "์ €์œ ๋Ÿ‰":
47
+ eta = 0.75
48
+ p_kw = electrical_power_kw(Q, H, eta)
49
+ return {"eta": eta, "p_kw": p_kw, "desc": "์ €์œ ๋Ÿ‰ ๋ชจ๋“œ: ์ถœ๋ ฅ ๊ฐ์†Œ, ํšจ์œจ ์ €ํ•˜"}
50
+ elif mode == "๊ณ ์œ ๋Ÿ‰":
51
+ eta = 0.70
52
+ p_kw = electrical_power_kw(Q, H, eta) * 0.6 # ์ปคํ…Œ์ผ๋ง
53
+ return {"eta": eta, "p_kw": p_kw, "desc": "๊ณ ์œ ๋Ÿ‰ ๋ชจ๋“œ: ์ถœ๋ ฅ ์ œํ•œ, ๋ฐ”์ดํŒจ์Šค ๋ณ‘ํ–‰"}
54
+ elif mode == "์ •๋น„":
 
 
55
  eta = 0.0
56
+ p_kw = 0.0
57
+ return {"eta": eta, "p_kw": p_kw, "desc": "์ •๋น„ ๋ชจ๋“œ: ๋ฐœ์ „ ์ค‘์ง€, ์ •๋น„ ํ•„์š”"}
58
+ else:
59
+ return {"eta": 0, "p_kw": 0, "desc": "์•Œ ์ˆ˜ ์—†๋Š” ๋ชจ๋“œ"}
60
+
61
+ # ------------------------------
62
+ # ์‹œ๊ฐํ™” ํ•จ์ˆ˜
63
+ # ------------------------------
64
+ def donut(label, value, max_val, unit=""):
65
+ pct = int((value / max_val) * 100) if max_val > 0 else 0
66
+ if pct > 100: pct = 100
67
+ color = "#16a34a" if pct > 70 else "#facc15" if pct > 40 else "#dc2626"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  return f"""
69
+ <div style="width:160px;height:160px;border-radius:50%;border:14px solid {color};
70
+ display:flex;align-items:center;justify-content:center;font-size:18px;
71
+ font-weight:bold;color:#111;">
72
+ <div style="text-align:center">
73
+ {label}<br>{value:.1f}{unit}<br><span style="font-size:14px;color:#666">{pct}%</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  </div>
75
  </div>
76
  """
77
 
78
+ # ------------------------------
79
+ # ์‹คํ–‰ ํ•จ์ˆ˜
80
+ # ------------------------------
81
+ def run(Q_in, rain_mm, dp_kpa, soc_perc):
82
  try:
83
+ Q_in = float(Q_in)
84
+ rain_mm = float(rain_mm)
85
+ dp_kpa = float(dp_kpa)
86
+ soc_perc = float(soc_perc)
87
  except:
88
+ return "โš ๏ธ ์ž…๋ ฅ ์˜ค๋ฅ˜", "", ""
89
 
90
+ mode = decide_mode(Q_in, rain_mm, dp_kpa, soc_perc)
91
+ st = mode_policy(mode, Q_in, Hn)
92
 
 
 
 
 
 
93
  summary = (
94
+ f"**ํ˜„์žฌ ๋ชจ๋“œ**: {mode}\n\n"
95
+ f"- ํ˜„์žฌ ์œ ๋Ÿ‰ Q: {Q_in:.2f} mยณ/s (์„ค๊ณ„ Qโ‚™={Qn} mยณ/s)\n"
96
+ f"- ๋‚™์ฐจ H: {Hn:.2f} m\n"
97
+ f"- ๊ฐ•์ˆ˜๋Ÿ‰: {rain_mm:.1f} mm/24h\n"
98
+ f"- ํŠธ๋ž˜์‹œ๋ž™ ฮ”P: {dp_kpa:.1f} kPa\n"
99
+ f"- ESS SoC: {soc_perc:.0f} %\n\n"
100
+ f"โ–ถ {st['desc']}"
101
  )
 
 
102
 
 
 
 
103
  donuts = f"""
104
  <div style="display:flex;gap:28px;flex-wrap:wrap;align-items:center">
105
+ {donut("ํšจ์œจ ฮท", st['eta']*100, 100, "%")}
106
+ {donut("์ถœ๋ ฅ P", st['p_kw'], 100, " kW")}
107
  </div>
108
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
+ return summary, donuts, ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
+ # ------------------------------
113
+ # Gradio UI
114
+ # ------------------------------
115
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
116
+ gr.Markdown("## ๐ŸŒŠ ์Šค๋งˆํŠธ ์†Œ์ˆ˜๋ ฅ ๋ฐœ์ „ ๋ชจ๋“œ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ")
117
  with gr.Row():
118
+ Q_in = gr.Number(label="ํ˜„์žฌ ์œ ๋Ÿ‰ Q (mยณ/s)", value=2.0)
119
+ rain_mm = gr.Number(label="๊ฐ•์ˆ˜๋Ÿ‰ (mm/24h)", value=10)
120
+ with gr.Row():
121
+ dp_kpa = gr.Number(label="ํŠธ๋ž˜์‹œ๋ž™ ฮ”P (kPa)", value=1.0)
122
+ soc_perc = gr.Number(label="ESS SoC (%)", value=50)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
+ run_btn = gr.Button("์‹คํ–‰")
 
125
 
126
+ summary = gr.Markdown()
127
+ donuts = gr.HTML()
128
+ extra = gr.Markdown()
 
129
 
130
+ run_btn.click(fn=run, inputs=[Q_in, rain_mm, dp_kpa, soc_perc],
131
+ outputs=[summary, donuts, extra])
 
 
 
 
 
 
 
 
 
132
 
133
+ # ------------------------------
134
+ # ์‹คํ–‰ ์‹œ์ž‘
135
+ # ------------------------------
136
  if __name__ == "__main__":
137
+ demo.launch()