| """Tier 1~4 μμ°¨ λ±μ₯ λ λλ¬ (κ°λ° κ°μ΄λ 8Β·9Β·10μ₯) |
| |
| st.empty() placeholder 4κ° + time.sleepμΌλ‘ μ€μΌλ ν€ β μ½ν
μΈ μμ°¨ κ΅μ²΄ |
| μ΄λ―Έ μλ£λ μν(stage>=5)κ±°λ animation_pendingμ΄ μλ λλ μ¦μ μ μ λ λ |
| |
| β
Tier λ°μ΄ν°λ λ°λμ core.pipeline.get_tier_data() ν ν¨μλ‘λ§ λ°μ |
| data.demo.TIER_DATAλ₯Ό μ§μ import νμ§ λ§ κ² (μ€μ μμ΄μ νΈ κ΅μ²΄κ° λ§ν) |
| """ |
| import time |
| from datetime import datetime |
|
|
| import streamlit as st |
|
|
| from components.skeleton import skeleton_html |
| from components.tiers_body import ( |
| tier_1_body_html, |
| tier_2_body_html, |
| tier_3_body_html, |
| tier_4_body_html, |
| ) |
| from core.pipeline import get_tier_data |
|
|
| SPEED_PRESETS = { |
| "fast": {"skel": 0.6, "hold": 0.4}, |
| "normal": {"skel": 1.4, "hold": 0.7}, |
| "real": {"skel": 3.0, "hold": 1.5}, |
| } |
|
|
| TIER_NAMES = {1: "μ΄μ νμ§", 2: "μμΈ λΆμ", 3: "곡μ κ° μν₯ νκ°", 4: "λμ κΆκ³ "} |
|
|
| |
| AGENT_PERSONA = { |
| 1: {"name": "Detection Agent", "tool": "IsolationForest Β· SECOM/PHM"}, |
| 2: {"name": "Cause Agent", "tool": "GPT-5-mini Β· RAG κ²μ"}, |
| 3: {"name": "Impact Agent", "tool": "GPT-5-mini Β· RAG + WIP κ²°ν©"}, |
| 4: {"name": "Response Agent", "tool": "GPT-5-mini Β· RAG + κ·Όκ±° μΆμΆ"}, |
| } |
|
|
|
|
| def _agent_summary(tier_num: int, data) -> str: |
| """Tierλ³ μμ΄μ νΈ νλ 1μ€ μμ½, dataκ° Noneμ΄λ©΄ λΆμ μ€ λ©μμ§""" |
| if data is None: |
| return "λΆμ μ€..." |
| if tier_num == 1: |
| return f"{len(data['features'])}κ° κΈ°μ¬ μΌμ κ²μΆ (μ΄μ μ μ {data['score']})" |
| if tier_num == 2: |
| cites = sum(len(c["citations"]) for c in data["causes"]) |
| return f"μ¬λ΄ λ¬Έμ {cites}건 μΈμ©ν΄ μμΈ {len(data['causes'])}κ° μΆμ " |
| if tier_num == 3: |
| return f"μμ‘΄μ± + WIP κ²°ν©ν΄ μμ μμ¨ μμ€ {data['yield_loss']} %p μΆμ " |
| if tier_num == 4: |
| return f"κ·Όκ±° {len(data['refs'])}건 μΈμ©ν΄ μ¦μ {len(data['immediate'])}건 Β· μ€μ₯κΈ° {len(data['longterm'])}건 κΆκ³ " |
| return "" |
|
|
|
|
| BODY_BUILDERS = { |
| 1: tier_1_body_html, |
| 2: tier_2_body_html, |
| 3: tier_3_body_html, |
| 4: tier_4_body_html, |
| } |
|
|
|
|
| LOADING_HTML = """ |
| <div class="fab-loading"> |
| <div class="fab-loading-spinner"></div> |
| <h3 class="fab-loading-title">4-Tier λ©ν° μμ΄μ νΈ λΆμ μ§ν μ€</h3> |
| <div class="fab-loading-subtitle">μλμ λΆμνκΈ° μν΄ 4λ¨κ³ μμ΄μ νΈκ° μμ°¨ μ€νλ©λλ€</div> |
| <div class="fab-loading-pipeline"> |
| <span class="step t1">Tier 1 μ΄μ νμ§</span> |
| <span class="arrow">β</span> |
| <span class="step t2">Tier 2 μμΈ λΆμ</span> |
| <span class="arrow">β</span> |
| <span class="step t3">Tier 3 μν₯ νκ°</span> |
| <span class="arrow">β</span> |
| <span class="step t4">Tier 4 λμ κΆκ³ </span> |
| </div> |
| <div class="fab-loading-hint">첫 νΈμΆμ LLM 3ν μ§λ ¬λ‘ μ½ 60μ΄ μμλ©λλ€ Β· μ΄ν λμΌ μλμ μΊμλ‘ μ¦μ μλ΅</div> |
| </div> |
| """ |
|
|
|
|
| def render_tier_cascade(): |
| ss = st.session_state |
| |
| loading_slot = st.empty() |
| loading_slot.html(LOADING_HTML) |
| try: |
| data = get_tier_data(ss.selected_alarm_id) |
| finally: |
| loading_slot.empty() |
|
|
| if data is None: |
| st.markdown( |
| """ |
| <div class="fab-empty"> |
| <div class="fab-empty-inner"> |
| <div class="fab-empty-icon" style="font-size: 24px;">β
</div> |
| <h3>λΆμ λ°μ΄ν°κ° μμ΅λλ€</h3> |
| <p>μ νν μλμ λ§€νλ λΆμ κ²°κ³Όκ° μμ΅λλ€</p> |
| </div> |
| </div> |
| """, |
| unsafe_allow_html=True, |
| ) |
| return |
|
|
| if ss.animation_pending: |
| ss.animation_pending = False |
| _run_sequence(data) |
| return |
|
|
| |
| for t in range(1, 5): |
| render_tier(t, data[f"tier{t}"], loading=False, with_actions=(t == 4)) |
|
|
|
|
| def _run_sequence(data): |
| ss = st.session_state |
| timing = SPEED_PRESETS[ss.speed] |
| slots = [st.empty() for _ in range(4)] |
|
|
| for tier_idx in range(1, 5): |
| ss.stage = tier_idx |
| with slots[tier_idx - 1].container(): |
| render_tier(tier_idx, None, loading=True) |
| time.sleep(timing["skel"]) |
| with slots[tier_idx - 1].container(): |
| render_tier( |
| tier_idx, |
| data[f"tier{tier_idx}"], |
| loading=False, |
| with_actions=(tier_idx == 4), |
| ) |
| ss.completed_tiers.add(tier_idx) |
| if tier_idx < 4: |
| time.sleep(timing["hold"]) |
|
|
| ss.stage = 5 |
|
|
|
|
| def render_tier(tier_num: int, data, loading: bool, with_actions: bool = False): |
| """Tier μΉ΄λ ν λ©μ΄λ¦¬λ₯Ό λ¨μΌ markdownμΌλ‘ λ λ, Tier 4 μ‘μ
λ°λ§ λ³λ μμ ―""" |
| status_html = ( |
| '<div class="tier-status"><span class="spinner"></span>μ€ν μ€...</div>' |
| if loading |
| else '<div class="tier-status"><span class="check">β</span>μλ£</div>' |
| ) |
| body_html = skeleton_html(tier_num) if loading else BODY_BUILDERS[tier_num](data) |
| p = AGENT_PERSONA[tier_num] |
| summary = _agent_summary(tier_num, None if loading else data) |
|
|
| st.html( |
| f'<section class="tier-card tier-{tier_num}">' |
| f'<div class="tier-head">' |
| f'<div class="tier-head-left">' |
| f'<div class="agent-id">' |
| f'<div class="agent-name-row">' |
| f'<span class="agent-name">{p["name"]}</span>' |
| f'<span class="chip chip-t{tier_num}">Tier {tier_num} Β· {TIER_NAMES[tier_num]}</span>' |
| f'</div>' |
| f'<div class="agent-summary">{summary} Β· <span class="agent-tool">{p["tool"]}</span></div>' |
| f'</div>' |
| f'</div>' |
| f'{status_html}' |
| f'</div>' |
| f'<div class="tier-body">{body_html}</div>' |
| f'</section>' |
| ) |
|
|
| if tier_num == 4 and with_actions and not loading: |
| _render_action_bar() |
|
|
|
|
| def _render_action_bar(): |
| ss = st.session_state |
|
|
| st.markdown( |
| '<div class="action-bar-head">' |
| '<span class="label">μ΄μμ κ²°μ </span>' |
| '<span>κΆκ³ μλ₯Ό κ²ν νκ³ μ‘μ
μ μ ννμΈμ</span>' |
| '</div>', |
| unsafe_allow_html=True, |
| ) |
|
|
| |
| _, c1, c2, c3, _ = st.columns([2.5, 1, 1, 2.2, 2.5]) |
| with c1: |
| if st.button("κ±°μ ", key="btn-reject", disabled=ss.approved, use_container_width=True): |
| _on_reject() |
| with c2: |
| if st.button("보λ₯", key="btn-hold", disabled=ss.approved, use_container_width=True): |
| _on_hold() |
| with c3: |
| label = "μμ
μ§μμ μμ±λ¨" if ss.approved else "μΉμΈ λ° μμ
μ§μμ μμ±" |
| if st.button(label, key="btn-approve", disabled=ss.approved, type="primary", use_container_width=True): |
| _on_approve() |
|
|
| _render_action_result() |
|
|
|
|
| def _render_action_result(): |
| ss = st.session_state |
| act = ss.get("last_action") |
| if not act: |
| return |
| kind = act["type"] |
| icon = {"approved": "β", "held": "βΈ", "rejected": "β"}[kind] |
| title = act["title"] |
| meta = act["meta"] |
| st.html( |
| f'<div class="action-result {kind}">' |
| f'<div class="action-result-icon">{icon}</div>' |
| f'<div class="action-result-body">' |
| f'<div class="action-result-title">{title}</div>' |
| f'<div class="action-result-meta">{meta}</div>' |
| f'</div>' |
| f'</div>' |
| ) |
|
|
|
|
| def _on_reject(): |
| st.session_state.last_action = { |
| "type": "rejected", |
| "title": "κΆκ³ κ±°μ ", |
| "meta": "μ¬μ μμ§ λͺ¨λ¬μ MVP λ²μ μΈ, νμ λΆμμ λ°μνλ €λ©΄ μ¬μ μ
λ ₯ ν μΈμλνΈ DBμ κΈ°λ‘ νμ", |
| } |
| st.toast("κΆκ³ κ±°μ λ¨", icon="β") |
| st.rerun() |
|
|
|
|
| def _on_hold(): |
| st.session_state.last_action = { |
| "type": "held", |
| "title": "κΆκ³ 보λ₯", |
| "meta": "5λΆ ν λμΌ μλμ΄ μ¬λ°μ μμ , μΆκ° λ°μ΄ν° ν보 ν μ¬κ²ν κΆμ₯", |
| } |
| st.toast("κΆκ³ 보λ₯λ¨", icon="βΈοΈ") |
| st.rerun() |
|
|
|
|
| def _on_approve(): |
| ss = st.session_state |
| ss.approved = True |
|
|
| |
| alarm = next((a for a in ss.alarms if a["id"] == ss.selected_alarm_id), None) |
| work_order = f"W-{datetime.now():%Y%m%d}-{ss.selected_alarm_id[1:].zfill(3)}" |
| incident_doc = "" |
| if alarm: |
| from agents.rag.learn import record_incident |
| tier_data = get_tier_data(ss.selected_alarm_id) |
| if tier_data: |
| path = record_incident(alarm, tier_data, work_order) |
| incident_doc = path.name |
|
|
| |
| for a in ss.alarms: |
| if a["id"] == ss.selected_alarm_id: |
| a["status"] = "done" |
| a["time"] = "λ°©κΈ μ " |
| break |
|
|
| ss.last_action = { |
| "type": "approved", |
| "title": "μΉμΈ μλ£, μμ
μ§μμ λ°ν", |
| "meta": ( |
| f"μμ
μ§μμ <code>{work_order}</code> μμ± Β· " |
| f"μΈμλνΈ DB <code>{incident_doc}</code> μλ κΈ°λ‘ Β· " |
| "μ¬μ΄λλ° μλ μνκ° [μλ£]λ‘ μ νλμμ΅λλ€" |
| ), |
| } |
| st.toast( |
| f"β μμ
μ§μμ {work_order} μμ± Β· μΈμλνΈ DB μλ κΈ°λ‘", |
| icon="β
", |
| ) |
| st.rerun() |
|
|