Aoi785 commited on
Commit
0b8cba3
ยท
verified ยท
1 Parent(s): 26b72e9

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +199 -0
app.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ from dataclasses import dataclass
3
+ import math
4
+
5
+ import gradio as gr
6
+ import matplotlib.pyplot as plt
7
+
8
+ # ---------------------------
9
+ # Small-Hydro 4-Mode Dashboard (Gradio)
10
+ # ---------------------------
11
+
12
+ RHO = 1000.0 # kg/m^3
13
+ G = 9.80665 # m/s^2
14
+ PF_TARGET = 0.98
15
+
16
+ def efficiency_from_relative_flow(r: float) -> float:
17
+ """๊ฐ„์ด ํšจ์œจ๊ณก์„  (r = Q/Qn). ๊ตฌ๊ฐ„ ๋ณด๊ฐ„."""
18
+ r = max(0.0, min(1.2, r))
19
+ curve = [
20
+ (0.00, 0.00),
21
+ (0.10, 0.40),
22
+ (0.30, 0.60),
23
+ (0.50, 0.75),
24
+ (0.70, 0.85),
25
+ (0.90, 0.90),
26
+ (1.00, 0.91),
27
+ (1.20, 0.88),
28
+ ]
29
+ for (x1,y1),(x2,y2) in zip(curve[:-1], curve[1:]):
30
+ if x1 <= r <= x2:
31
+ t = 0.0 if x2 == x1 else (r-x1)/(x2-x1)
32
+ return y1 + t*(y2-y1)
33
+ return 0.0
34
+
35
+ def electrical_power_kw(Q_in: float, head_m: float, eta: float) -> float:
36
+ """P[kW] = rho*g*Q*H*eta/1000"""
37
+ if Q_in <= 0 or head_m <= 0 or eta <= 0:
38
+ return 0.0
39
+ return (RHO * G * Q_in * head_m * eta) / 1000.0
40
+
41
+ def decide_mode(Q_in: float, q_low: float, q_high: float,
42
+ rain_mm: float, dp_kpa: float,
43
+ rain_thr: float, dp_limit: float,
44
+ emergency: bool) -> str:
45
+ if emergency:
46
+ return "Emergency"
47
+ if Q_in < q_low:
48
+ return "Low-Flow"
49
+ if Q_in > q_high or dp_kpa > dp_limit or rain_mm >= rain_thr:
50
+ return "Flood"
51
+ return "Normal"
52
+
53
+ def mode_policy(mode: str, Q_in: float, Qn: float, Hn: float):
54
+ r = min(Q_in, Qn)/Qn if Qn>0 else 0.0
55
+ eta = efficiency_from_relative_flow(r)
56
+ p_kw = electrical_power_kw(min(Q_in, Qn), Hn, eta)
57
+
58
+ gate = round(min(100.0, max(10.0, r*100.0)), 1)
59
+ vane = gate
60
+
61
+ curtail = False
62
+ bypass = False
63
+ dump = False
64
+ soc = (55, 70)
65
+
66
+ if mode == "Flood":
67
+ curtail = True; bypass = True; dump = True
68
+ vane = max(20.0, vane-20.0); gate = vane
69
+ p_kw *= 0.6
70
+ soc = (35, 45)
71
+ elif mode == "Low-Flow":
72
+ vane = max(15.0, min(55.0, vane)); gate = vane
73
+ p_kw *= 0.85
74
+ soc = (45, 65)
75
+ elif mode == "Emergency":
76
+ curtail = True; bypass = True; dump = False
77
+ vane = 0.0; gate = 0.0; p_kw = 0.0
78
+ soc = (30, 50)
79
+
80
+ return {
81
+ "eta": eta,
82
+ "p_kw": p_kw,
83
+ "gate_pct": gate,
84
+ "vane_pct": vane,
85
+ "curtail": curtail,
86
+ "bypass": bypass,
87
+ "dump": dump,
88
+ "soc_low": soc[0],
89
+ "soc_high": soc[1],
90
+ }
91
+
92
+ def plot_efficiency(Qn: float, Q_in: float):
93
+ """ํšจ์œจ๊ณก์„  + ํ˜„์žฌ ์šด์ „์  ์ด๋ฏธ์ง€(PNG) ๋ฐ˜ํ™˜"""
94
+ rs = [i/100 for i in range(0, 121)]
95
+ etas = [efficiency_from_relative_flow(r) for r in rs]
96
+ r_now = min(Q_in, Qn)/Qn if Qn>0 else 0.0
97
+ eta_now = efficiency_from_relative_flow(r_now)
98
+
99
+ fig = plt.figure(figsize=(5,3.2))
100
+ plt.plot(rs, etas, linewidth=2)
101
+ plt.scatter([r_now], [eta_now], s=60)
102
+ plt.xlabel("Relative flow r = Q / Qn")
103
+ plt.ylabel("Efficiency ฮท")
104
+ plt.title("Efficiency curve & current operating point")
105
+ plt.grid(True, alpha=0.3)
106
+ buf = io.BytesIO()
107
+ fig.tight_layout()
108
+ fig.savefig(buf, format="png", dpi=140)
109
+ plt.close(fig)
110
+ buf.seek(0)
111
+ return buf
112
+
113
+ def plot_power_bar(p_kw: float):
114
+ """์ถœ๋ ฅ ๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„ PNG"""
115
+ fig = plt.figure(figsize=(4.2,3.2))
116
+ plt.bar(["Estimated Power"], [p_kw])
117
+ plt.ylabel("kW")
118
+ plt.title("Estimated Electrical Power")
119
+ for idx, v in enumerate([p_kw]):
120
+ plt.text(idx, v, f"{v:.1f} kW", ha="center", va="bottom")
121
+ buf = io.BytesIO()
122
+ fig.tight_layout()
123
+ fig.savefig(buf, format="png", dpi=140)
124
+ plt.close(fig)
125
+ buf.seek(0)
126
+ return buf
127
+
128
+ def badge_for_mode(mode: str) -> str:
129
+ emoji = {"Normal":"๐ŸŸข", "Low-Flow":"๐ŸŸก", "Flood":"๐ŸŸ ", "Emergency":"๐Ÿ”ด"}.get(mode,"โšช")
130
+ return f"### {emoji} Mode: **{mode}**"
131
+
132
+ def run(Q_in, rain_mm, dp_kpa, emergency, Hn, Qn, rain_thr, dp_limit):
133
+ try:
134
+ Q_in = float(Q_in); rain_mm = float(rain_mm); dp_kpa = float(dp_kpa)
135
+ Hn = float(Hn); Qn = float(Qn)
136
+ rain_thr = float(rain_thr); dp_limit = float(dp_limit)
137
+ emergency = bool(emergency)
138
+ except Exception:
139
+ return "โš ๏ธ ์ž…๋ ฅ ์˜ค๋ฅ˜", None, None, None
140
+
141
+ if Qn<=0 or Hn<=0:
142
+ return "โš ๏ธ ์„ค๊ณ„๊ฐ’(Hโ‚™, Qโ‚™)์€ 0๋ณด๋‹ค ์ปค์•ผ ํ•ฉ๋‹ˆ๋‹ค.", None, None, None
143
+
144
+ q_low, q_high = 0.4*Qn, 1.2*Qn
145
+ mode = decide_mode(Q_in, q_low, q_high, rain_mm, dp_kpa, rain_thr, dp_limit, emergency)
146
+ st = mode_policy(mode, Q_in, Qn, Hn)
147
+
148
+ md = []
149
+ md.append(badge_for_mode(mode))
150
+ md.append("")
151
+ md.append(f"- **์œ ๋Ÿ‰ Q**: {Q_in:.2f} mยณ/s | **์„ค๊ณ„ Qโ‚™**: {Qn:.2f} mยณ/s (์ž„๊ณ„: {q_low:.2f} ~ {q_high:.2f})")
152
+ md.append(f"- **๋‚™์ฐจ H**: {Hn:.2f} m | **๊ฐ•์šฐ์˜ˆ๋ณด**: {rain_mm:.1f} mm/24h | **ฮ”P**: {dp_kpa:.1f} kPa")
153
+ md.append("")
154
+ md.append("**์ถ”์ •/์„ค์ •**")
155
+ md.append(f"- ํšจ์œจ ฮท: **{st['eta']:.3f}**")
156
+ md.append(f"- ์ถœ๋ ฅ: **{st['p_kw']:.1f} kW**")
157
+ md.append(f"- ๊ฐ€์ด๋“œ๋ฒ ์ธ: **{st['vane_pct']:.1f}%** | ์ทจ์ˆ˜๋ฌธ: **{st['gate_pct']:.1f}%**")
158
+ md.append(f"- ์ปคํ…Œ์ผ: **{st['curtail']}**, ๋ฐ”์ดํŒจ์Šค: **{st['bypass']}**, ๋คํ”„๋กœ๋“œ: **{st['dump']}**")
159
+ md.append(f"- ESS SoC ๋ชฉํ‘œ: **{st['soc_low']}โ€“{st['soc_high']}%** | ์—ญ๋ฅ  ๋ชฉํ‘œ: **{PF_TARGET}**")
160
+
161
+ eff_img = plot_efficiency(Qn, Q_in)
162
+ p_img = plot_power_bar(st["p_kw"])
163
+ return "\n".join(md), eff_img, p_img, st["p_kw"]
164
+
165
+ # ---------------------------
166
+ # Gradio UI
167
+ # ---------------------------
168
+ with gr.Blocks(theme=gr.themes.Soft(), title="Small-Hydro Smart Modes Dashboard") as app:
169
+ gr.Markdown("## ๐ŸŒŠ ์†Œ์ˆ˜๋ ฅ ์Šค๋งˆํŠธ ์šด์ „๋ชจ๋“œ ๋Œ€์‹œ๋ณด๋“œ")
170
+
171
+ with gr.Row():
172
+ Q_in = gr.Number(label="ํ˜„์žฌ ์œ ๋Ÿ‰ Q (mยณ/s)", value=3.0)
173
+ rain = gr.Number(label="๊ฐ•์šฐ ์˜ˆ๋ณด (mm/24h)", value=10)
174
+ dp = gr.Number(label="ํŠธ๋ž˜์‹œ๋ž™ ฮ”P (kPa)", value=1.0)
175
+ emer = gr.Checkbox(label="๋น„์ƒ ์•Œ๋žŒ", value=False)
176
+
177
+ with gr.Accordion("๊ณ ๊ธ‰ ์„ค์ • (์„ค๊ณ„/์ž„๊ณ„๊ฐ’)", open=False):
178
+ with gr.Row():
179
+ Hn = gr.Number(label="์„ค๊ณ„ ๋‚™์ฐจ Hโ‚™ (m)", value=8.0)
180
+ Qn = gr.Number(label="์„ค๊ณ„ ์œ ๋Ÿ‰ Qโ‚™ (mยณ/s)", value=3.5)
181
+ with gr.Row():
182
+ rain_thr = gr.Number(label="ํ™์ˆ˜ ํŒ๋‹จ ๊ฐ•์šฐ ์ž„๊ณ„ (mm/24h)", value=40.0)
183
+ dp_limit = gr.Number(label="ํŠธ๋ž˜์‹œ๋ž™ ฮ”P ์ž„๊ณ„ (kPa)", value=3.0)
184
+
185
+ run_btn = gr.Button("์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰", variant="primary")
186
+
187
+ out_md = gr.Markdown()
188
+ eff_plot = gr.Image(label="ํšจ์œจ๊ณก์„  & ํ˜„์žฌ ์šด์ „์ ", type="auto")
189
+ p_plot = gr.Image(label="์ถœ๋ ฅ ์ถ”์ • (kW)", type="auto")
190
+
191
+ run_btn.click(
192
+ fn=run,
193
+ inputs=[Q_in, rain, dp, emer, Hn, Qn, rain_thr, dp_limit],
194
+ outputs=[out_md, eff_plot, p_plot, gr.Number(visible=False)]
195
+ )
196
+
197
+ # ๋กœ์ปฌ ํ…Œ์ŠคํŠธ์šฉ
198
+ if __name__ == "__main__":
199
+ app.launch()