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

Update app.py

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