4dig.v2 / app.py
Aoi785's picture
Update app.py
dab8017 verified
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)
# ===== Gradio UI: 3์—ด๋กœ ์žฌ๋ฐฐ์น˜ (๊ฐ€์šด๋ฐ์— '์ฆ‰์‹œ ์‹คํ–‰') =====
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()