|
|
import time |
|
|
import gradio as gr |
|
|
|
|
|
|
|
|
DEFAULTS = dict( |
|
|
Hn=8.0, Qn=3.5, |
|
|
rain_thr=40.0, |
|
|
dp_limit=3.0, |
|
|
soc_limit=90.0, |
|
|
rho=1000.0, g=9.80665 |
|
|
) |
|
|
|
|
|
|
|
|
def electrical_power_kw(Q, H, eta, rho=DEFAULTS["rho"], g=DEFAULTS["g"]): |
|
|
if Q <= 0 or H <= 0 or eta <= 0: |
|
|
return 0.0 |
|
|
return (rho*g*Q*H*eta)/1000.0 |
|
|
|
|
|
def efficiency_from_relative_flow(r): |
|
|
r = max(0.0, min(1.3, float(r))) |
|
|
pts = [(0.00,0.00),(0.10,0.40),(0.30,0.60),(0.50,0.78), |
|
|
(0.70,0.86),(0.90,0.90),(1.00,0.91),(1.20,0.88),(1.30,0.85)] |
|
|
for (x1,y1),(x2,y2) in zip(pts[:-1], pts[1:]): |
|
|
if x1 <= r <= x2: |
|
|
t = 0 if x2==x1 else (r-x1)/(x2-x1) |
|
|
return y1 + t*(y2-y1) |
|
|
return 0.0 |
|
|
|
|
|
def decide_mode(Q, Qn, rain, dp, soc, rain_thr, dp_limit, soc_limit): |
|
|
if (dp is not None and dp > dp_limit) or (soc is not None and soc >= soc_limit): |
|
|
return "์ ๋น" |
|
|
if Q < 0.4*Qn: |
|
|
return "์ ์ ๋" |
|
|
if (Q > 1.2*Qn) or (rain >= rain_thr): |
|
|
return "๊ณ ์ ๋" |
|
|
return "์ ์" |
|
|
|
|
|
def mode_policy(mode, Q, Qn, Hn): |
|
|
r = Q/Qn if Qn>0 else 0.0 |
|
|
eta = efficiency_from_relative_flow(r) |
|
|
|
|
|
if mode=="์ ์": |
|
|
eta_eff = max(eta, 0.88) |
|
|
p = electrical_power_kw(Q, Hn, eta_eff) |
|
|
return dict(eta=eta_eff, p_kw=p, curtail=False, bypass=False, service=False, |
|
|
note="ํจ์จ ์ต์ ์ ์ด์ ") |
|
|
|
|
|
if mode=="์ ์ ๋": |
|
|
eta_eff = max(0.70, eta*0.95) |
|
|
p = electrical_power_kw(Q, Hn, eta_eff) * 0.90 |
|
|
return dict(eta=eta_eff, p_kw=p, curtail=False, bypass=False, service=False, |
|
|
note="์ถ๋ ฅ ์ถ์ยทํจ์จ ์ต์ ํ") |
|
|
|
|
|
if mode=="๊ณ ์ ๋": |
|
|
eta_eff = max(0.68, eta*0.90) |
|
|
p = electrical_power_kw(Q, Hn, eta_eff) * 0.60 |
|
|
return dict(eta=eta_eff, p_kw=p, curtail=True, bypass=True, service=False, |
|
|
note="์ถ๋ ฅ ์ ํยท๋ฐ์ดํจ์ค ๋ณํ") |
|
|
|
|
|
if mode=="์ ๋น": |
|
|
return dict(eta=0.0, p_kw=0.0, curtail=True, bypass=True, service=True, |
|
|
note="๋ฐ์ ์ค์งยท์ ๋น ํ์") |
|
|
|
|
|
return dict(eta=0.0, p_kw=0.0, curtail=False, bypass=False, service=False, note="") |
|
|
|
|
|
def mode_badge(mode:str)->str: |
|
|
colors={"์ ์":"#22c55e","์ ์ ๋":"#eab308","๊ณ ์ ๋":"#f97316","์ ๋น":"#ef4444"} |
|
|
col=colors.get(mode,"#6b7280") |
|
|
icon = "โ" if mode!="์ ๋น" else "โ ๏ธ" |
|
|
return f""" |
|
|
<div style="padding:12px 16px;border-radius:14px;background:{col}; |
|
|
color:#fff;font-weight:800;display:inline-flex;gap:10px;align-items:center;font-size:18px"> |
|
|
<span style="font-size:18px">{icon}</span><span>{'์ ๋น ๋ชจ๋' if mode=='์ ๋น' else '์ด์ ๋ชจ๋ : '+mode}</span> |
|
|
</div>""" |
|
|
|
|
|
def donut(label:str, value:float, maxv:float, unit:str=""): |
|
|
disp_val = round(float(value), 1) |
|
|
maxv = max(1e-6, float(maxv)) |
|
|
pct = max(0.0, min(float(value)/maxv, 1.0)) |
|
|
|
|
|
size = 220 |
|
|
r = 86 |
|
|
stroke = 18 |
|
|
C = 2*3.14159*r |
|
|
offset = C*(1-pct) |
|
|
|
|
|
return f""" |
|
|
<div style="display:flex;flex-direction:column;align-items:center;gap:12px;"> |
|
|
<svg width="{size}" height="{size}" viewBox="0 0 {size} {size}"> |
|
|
<circle cx="{size/2}" cy="{size/2}" r="{r}" stroke="#e5e7eb" stroke-width="{stroke}" fill="none"/> |
|
|
<circle cx="{size/2}" cy="{size/2}" r="{r}" stroke="#3b82f6" stroke-width="{stroke}" fill="none" |
|
|
stroke-dasharray="{C:.1f}" stroke-dashoffset="{offset:.1f}" |
|
|
stroke-linecap="round" transform="rotate(-90 {size/2} {size/2})"/> |
|
|
<text x="{size/2}" y="{size/2 - 6}" text-anchor="middle" dominant-baseline="middle" |
|
|
font-size="30" font-weight="800" |
|
|
fill="#ffffff" stroke="#111111" stroke-width="2" paint-order="stroke">{disp_val:.1f}{unit}</text> |
|
|
<text x="{size/2}" y="{size/2 + 28}" text-anchor="middle" dominant-baseline="middle" |
|
|
font-size="15" fill="#64748b">{label}</text> |
|
|
</svg> |
|
|
</div>""" |
|
|
|
|
|
def chip(label:str, on:bool, on_color="#10b981"): |
|
|
bg=on_color if on else "#e5e7eb" |
|
|
fg="#fff" if on else "#111827" |
|
|
txt="ON" if on else "OFF" |
|
|
return f"""<div style="padding:8px 12px;border-radius:999px;background:{bg};color:{fg}; |
|
|
font-size:13px;font-weight:700;display:inline-block;">{label}: {txt}</div>""" |
|
|
|
|
|
def action_box(mode:str)->str: |
|
|
if mode=="์ ๋น": |
|
|
body="""<ul style="margin:0;padding:0;list-style:disc inside;font-size:15px"> |
|
|
<li style="color:#000"><b>๋ฐ์ ์ค์ง</b></li> |
|
|
<li style="color:#000"><b>์ ๋น ํ์</b></li> |
|
|
</ul>""" |
|
|
style='border:1px solid #fecaca;background:#fef2f2;' |
|
|
elif mode=="๊ณ ์ ๋": |
|
|
body='<div style="color:#000;font-size:15px"><b>์ถ๋ ฅ ์ ํยท๋ฐ์ดํจ์ค ๋ณํ</b></div>' |
|
|
style='border:1px solid #fde68a;background:#fffbeb;' |
|
|
elif mode=="์ ์ ๋": |
|
|
body='<div style="color:#000;font-size:15px"><b>์ถ๋ ฅ ์ถ์ยทํจ์จ ์ต์ ํ</b></div>' |
|
|
style='border:1px solid #e5e7eb;background:#f9fafb;' |
|
|
else: |
|
|
body='<div style="color:#000;font-size:15px"><b>ํจ์จ ์ต์ ์ ์ด์ </b></div>' |
|
|
style='border:1px solid #e5e7eb;background:#f9fafb;' |
|
|
return f"""<div style="padding:14px 16px;border-radius:10px;{style}">{body}</div>""" |
|
|
|
|
|
MODE_COLOR = {"์ ์":"#16a34a","์ ์ ๋":"#eab308","๊ณ ์ ๋":"#f59e0b","์ ๋น":"#dc2626"} |
|
|
|
|
|
def _evaluate(Q_in, rain_mm, dp_kpa, soc_pct, |
|
|
Hn=DEFAULTS["Hn"], Qn=DEFAULTS["Qn"], |
|
|
rain_thr=DEFAULTS["rain_thr"], dp_limit=DEFAULTS["dp_limit"], soc_limit=DEFAULTS["soc_limit"]): |
|
|
mode = decide_mode(Q_in, Qn, rain_mm, dp_kpa, soc_pct, rain_thr, dp_limit, soc_limit) |
|
|
st = mode_policy(mode, Q_in, Qn, Hn) |
|
|
p_ref = electrical_power_kw(Qn, Hn, 0.9) |
|
|
|
|
|
header = mode_badge(mode) |
|
|
donuts = f"""<div style="display:flex;gap:40px;flex-wrap:wrap;align-items:center"> |
|
|
{donut("ํจ์จ(ฮท)", st['eta']*100, 100, "%")} |
|
|
{donut("์ถ๋ ฅ(kW)", st['p_kw'], max(1.0, p_ref), " kW")} |
|
|
</div>""" |
|
|
|
|
|
chips = f"""<div style="display:flex;gap:10px;flex-wrap:wrap"> |
|
|
{chip("์ถ๋ ฅ ์ ํ", st['curtail'])} |
|
|
{chip("๋ฐ์ดํจ์ค", st['bypass'])} |
|
|
{chip("์ ๋น ํ์", st['service'], on_color="#ef4444")} |
|
|
</div>""" |
|
|
|
|
|
color = MODE_COLOR.get(mode, "#111827") |
|
|
summary = (f"<div style='font-size:16px'><b style='color:{color}'>ํ์ฌ ๋ชจ๋: {mode}</b></div>\n\n" |
|
|
f"- ํ์ฌ ์ ๋ Q: {Q_in:.1f} mยณ/s (๊ธฐ์ค Qโ={Qn:.1f})\n" |
|
|
f"- ๊ฐ์๋: {rain_mm:.1f} mm/24h (ํ์ฉ {rain_thr:.1f})\n" |
|
|
f"- ํธ๋์๋: {dp_kpa:.1f} kPa (ํ์ฉ {dp_limit:.1f})\n" |
|
|
f"- ESS ์ถฉ์ ์ํ: {soc_pct:.1f}% (ํ์ฉ {soc_limit:.1f}%)\n\n" |
|
|
f"โถ {st['note']}") |
|
|
|
|
|
panel = f"""<div style="display:flex;flex-direction:column;gap:18px">{header}{donuts}{chips}</div>""" |
|
|
extra = action_box(mode) |
|
|
return summary, panel, extra |
|
|
|
|
|
|
|
|
def run(Q_in, rain_mm, dp_kpa, soc_pct): |
|
|
try: |
|
|
Q_in=float(Q_in); rain_mm=float(rain_mm); dp_kpa=float(dp_kpa); soc_pct=float(soc_pct) |
|
|
except Exception: |
|
|
return "โ ๏ธ ์
๋ ฅ ์ค๋ฅ๋ฅผ ํ์ธํ์ธ์.","","" |
|
|
return _evaluate(Q_in, rain_mm, dp_kpa, soc_pct) |
|
|
|
|
|
def preset_normal_and_run(): |
|
|
Q=0.8*DEFAULTS["Qn"]; rain=10.0; dp=1.0; soc=50.0 |
|
|
md, panel, extra = _evaluate(Q, rain, dp, soc) |
|
|
return round(Q,1), round(rain,1), round(dp,1), round(soc,1), md, panel, extra |
|
|
|
|
|
def preset_low_and_run(): |
|
|
Q=0.3*DEFAULTS["Qn"]; rain=5.0; dp=1.0; soc=50.0 |
|
|
md, panel, extra = _evaluate(Q, rain, dp, soc) |
|
|
return round(Q,1), round(rain,1), round(dp,1), round(soc,1), md, panel, extra |
|
|
|
|
|
def preset_high_and_run(): |
|
|
Q=1.3*DEFAULTS["Qn"]; rain=max(50.0, DEFAULTS["rain_thr"]); dp=1.0; soc=50.0 |
|
|
md, panel, extra = _evaluate(Q, rain, dp, soc) |
|
|
return round(Q,1), round(rain,1), round(dp,1), round(soc,1), md, panel, extra |
|
|
|
|
|
def preset_maint_and_run(): |
|
|
Q=0.8*DEFAULTS["Qn"]; rain=10.0; dp=DEFAULTS["dp_limit"]+0.5; soc=50.0 |
|
|
md, panel, extra = _evaluate(Q, rain, dp, soc) |
|
|
return round(Q,1), round(rain,1), round(dp,1), round(soc,1), md, panel, extra |
|
|
|
|
|
def autoplay(): |
|
|
steps = [preset_normal_and_run, preset_low_and_run, preset_high_and_run, preset_maint_and_run] |
|
|
for fn in steps: |
|
|
Q, rain, dp, soc, md, panel, extra = fn() |
|
|
yield (Q, rain, dp, soc, md, panel, extra) |
|
|
time.sleep(2.0) |
|
|
|
|
|
|
|
|
with gr.Blocks(theme=gr.themes.Soft(), title="์์๋ ฅ ์ค๋งํธ ์ด์ ๋ชจ๋ ๋์๋ณด๋") as app: |
|
|
gr.HTML(""" |
|
|
<style> |
|
|
.gradio-container {max-width: 1300px !important;} |
|
|
.quick-col {min-width: 210px;} |
|
|
.quick-col .gr-button {width: 100%;} |
|
|
.quick-title {font-weight:700;margin:8px 0 6px 0;} |
|
|
</style> |
|
|
""") |
|
|
gr.Markdown("## ๐ ์์๋ ฅ ์ค๋งํธ ์ด์ ๋ชจ๋ ๋์๋ณด๋") |
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
Q_in = gr.Number(label="์ ๋ Q (mยณ/s)", value=2.0) |
|
|
rain = gr.Number(label="๊ฐ์๋ (mm/24h)", value=10.0) |
|
|
dp = gr.Number(label="ํธ๋์๋ (kPa)", value=1.0) |
|
|
soc = gr.Number(label="SoC (%)", value=50.0) |
|
|
run_btn = gr.Button("โถ ์คํ", variant="primary") |
|
|
|
|
|
|
|
|
with gr.Column(scale=1, elem_classes=["quick-col"]): |
|
|
gr.Markdown("<div class='quick-title'>โก ์ฆ์ ์คํ</div>") |
|
|
btn_norm = gr.Button("์ ์", variant="secondary") |
|
|
btn_low = gr.Button("์ ์ ๋", variant="secondary") |
|
|
btn_high = gr.Button("๊ณ ์ ๋", variant="secondary") |
|
|
btn_maint = gr.Button("์ ๋น", variant="stop") |
|
|
gr.Markdown("<div class='quick-title'>๐ฌ ์๋ ์์ฐ</div>") |
|
|
demo_btn = gr.Button("์๋ ์์ฐ", variant="primary") |
|
|
|
|
|
|
|
|
with gr.Column(scale=3): |
|
|
out_vis = gr.HTML(elem_classes=["wrap-panel"]) |
|
|
out_md = gr.Markdown() |
|
|
out_extra = gr.HTML() |
|
|
|
|
|
|
|
|
run_btn.click(fn=run, inputs=[Q_in, rain, dp, soc], |
|
|
outputs=[out_md, out_vis, out_extra]) |
|
|
|
|
|
btn_norm.click(fn=preset_normal_and_run, outputs=[Q_in, rain, dp, soc, out_md, out_vis, out_extra]) |
|
|
btn_low.click(fn=preset_low_and_run, outputs=[Q_in, rain, dp, soc, out_md, out_vis, out_extra]) |
|
|
btn_high.click(fn=preset_high_and_run, outputs=[Q_in, rain, dp, soc, out_md, out_vis, out_extra]) |
|
|
btn_maint.click(fn=preset_maint_and_run,outputs=[Q_in, rain, dp, soc, out_md, out_vis, out_extra]) |
|
|
|
|
|
demo_btn.click(fn=autoplay, outputs=[Q_in, rain, dp, soc, out_md, out_vis, out_extra]) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
app.launch() |
|
|
|
|
|
|