""" layout.py — MicroPlastiNet M4 Dashboard Layout Light theme using dash-bootstrap-components (FLATLY). """ import dash_bootstrap_components as dbc from dash import dcc, html # ─── Color Tokens ────────────────────────────────────────────────────────────── # Light palette: page is soft slate-50, cards are white, text dark slate. COLORS = { "bg_deep": "#f8fafc", # page background "bg_panel": "#ffffff", # tab strip / panels "bg_card": "#ffffff", # cards "bg_hover": "#f1f5f9", # hover surface "accent": "#0f766e", # primary brand (deep teal) "accent_hi": "#0d9488", # primary hover "accent_lo": "#ccfbf1", # primary tint "accent_cyan": "#0284c7", # info / data series "accent_teal": "#0f766e", # legacy alias "accent_amber":"#d97706", # warning "accent_red": "#dc2626", # danger "accent_green":"#16a34a", # success "text_primary":"#0f172a", # body text "text_muted": "#64748b", # secondary text "border": "#e2e8f0", # hairline borders "high": "#dc2626", "medium": "#d97706", "low": "#16a34a", } TAB_STYLE = { "backgroundColor": COLORS["bg_panel"], "color": COLORS["text_muted"], "border": f"1px solid {COLORS['border']}", "borderBottom": "none", "padding": "10px 22px", "fontFamily": "'Inter', 'Segoe UI', sans-serif", "fontSize": "13px", "letterSpacing": "0.03em", "fontWeight": "500", } TAB_SELECTED_STYLE = { **TAB_STYLE, "backgroundColor": COLORS["bg_card"], "color": COLORS["accent"], "fontWeight": "600", "borderTop": f"2px solid {COLORS['accent']}", "borderBottom": f"1px solid {COLORS['bg_card']}", } CARD_STYLE = { "backgroundColor": COLORS["bg_card"], "border": f"1px solid {COLORS['border']}", "borderRadius": "8px", "padding": "16px", } GLOBAL_STYLE = { "backgroundColor": COLORS["bg_deep"], "minHeight": "100vh", "fontFamily": "'Inter', 'Segoe UI', system-ui, sans-serif", "color": COLORS["text_primary"], } def stat_card(label: str, value: str, color: str = None, icon: str = ""): return html.Div( [ html.Div(label, style={"fontSize": "11px", "color": COLORS["text_muted"], "textTransform": "uppercase", "letterSpacing": "0.08em", "marginBottom": "4px"}), html.Div( [html.Span(icon + " " if icon else ""), html.Span(value, style={"fontSize": "22px", "fontWeight": "700", "color": color or COLORS["accent_cyan"]})], ), ], style={**CARD_STYLE, "minWidth": "120px", "flex": "1"}, ) def header(): return html.Div( [ html.Div( [ # Logo + Title html.Div( [ # Logo icon (simple geometric using Div) html.Div( style={ "width": "36px", "height": "27px", "marginRight": "12px", "flexShrink": "0", "position": "relative", }, children=[ html.Div(style={ "position": "absolute", "left": "6px", "top": "9px", "width": "10px", "height": "10px", "borderRadius": "50%", "backgroundColor": COLORS["accent"], }), html.Div(style={ "position": "absolute", "left": "22px", "top": "2px", "width": "8px", "height": "8px", "borderRadius": "50%", "backgroundColor": COLORS["accent_hi"], "opacity": "0.85", }), html.Div(style={ "position": "absolute", "left": "22px", "top": "17px", "width": "8px", "height": "8px", "borderRadius": "50%", "backgroundColor": COLORS["accent_hi"], "opacity": "0.85", }), ], ), html.Div( [ html.Span("MicroPlasti", style={"color": COLORS["text_primary"], "fontWeight": "700", "fontSize": "20px"}), html.Span("Net", style={"color": COLORS["accent"], "fontWeight": "700", "fontSize": "20px"}), html.Div("Microplastic Source Attribution · Coastal Georgia Watersheds", style={"fontSize": "11px", "color": COLORS["text_muted"], "marginTop": "1px", "letterSpacing": "0.04em"}), ] ), ], style={"display": "flex", "alignItems": "center"}, ), # Right-side status indicators (no live indicator — this is a research prototype, not live monitoring) html.Div( [ html.Div( children=[ html.Span("SYNTHETIC DATA · PROTOTYPE", style={"fontSize": "10px", "color": COLORS["accent"], "letterSpacing": "0.1em", "fontWeight": "600", "marginRight": "10px", "padding": "3px 9px", "backgroundColor": COLORS["accent_lo"], "borderRadius": "4px"}), ], style={"display": "flex", "alignItems": "center", "marginRight": "12px"}), html.Div(id="header-clock", style={"fontSize": "12px", "color": COLORS["text_muted"], "fontVariantNumeric": "tabular-nums"}), ], style={"display": "flex", "alignItems": "center"}, ), ], style={"display": "flex", "justifyContent": "space-between", "alignItems": "center"}, ), # Top KPI bar html.Div( id="kpi-bar", style={"display": "flex", "gap": "12px", "marginTop": "16px"}, ), ], style={ "backgroundColor": COLORS["bg_panel"], "borderBottom": f"1px solid {COLORS['border']}", "padding": "16px 28px", }, ) def tab_live_map(): return dcc.Tab( label="Watershed Map", value="tab-map", style=TAB_STYLE, selected_style=TAB_SELECTED_STYLE, children=[ html.Div( [ # Left: map html.Div( dcc.Graph(id="map-graph", config={"displayModeBar": False, "scrollZoom": True}, style={"height": "calc(100vh - 220px)", "minHeight": "520px"}), style={"flex": "1", "minWidth": "0"}, ), # Right: station detail panel html.Div( [ html.Div("SELECT A STATION", id="station-panel-header", style={"fontSize": "10px", "color": COLORS["text_muted"], "letterSpacing": "0.1em", "marginBottom": "14px"}), html.Div(id="station-detail-panel", children=[ html.Div("Click any station on the map to view details.", style={"color": COLORS["text_muted"], "fontSize": "13px"}), ]), ], style={**CARD_STYLE, "width": "280px", "flexShrink": "0", "overflowY": "auto", "maxHeight": "calc(100vh - 220px)"}, ), ], style={"display": "flex", "gap": "12px", "padding": "16px", "alignItems": "flex-start"}, ), ], ) def tab_time_series(): return dcc.Tab( label="Station Trends", value="tab-ts", style=TAB_STYLE, selected_style=TAB_SELECTED_STYLE, children=[ html.Div( [ html.Div( [ dbc.Label("Station", style={"fontSize": "12px", "color": COLORS["text_muted"], "marginBottom": "4px"}), dcc.Dropdown(id="ts-station-select", placeholder="Select station…", style={"backgroundColor": COLORS["bg_card"], "color": "#000"}, clearable=False), ], style={"width": "260px"}, ), ], style={"padding": "16px 16px 0", "display": "flex", "gap": "16px", "alignItems": "flex-end"}, ), html.Div( dcc.Graph(id="ts-graph", config={"displayModeBar": "hover"}, style={"height": "420px"}), style={"padding": "0 16px"}, ), html.Div(id="ts-anomaly-table", style={"padding": "0 16px 16px"}), ], ) def tab_polymer(): return dcc.Tab( label="Polymer Breakdown", value="tab-polymer", style=TAB_STYLE, selected_style=TAB_SELECTED_STYLE, children=[ html.Div( [ html.Div( [ dbc.Label("Station", style={"fontSize": "12px", "color": COLORS["text_muted"]}), dcc.Dropdown(id="poly-station-select", placeholder="Select station…", style={"backgroundColor": COLORS["bg_card"], "color": "#000"}, clearable=False), ], style={"width": "260px"}, ), ], style={"padding": "16px 16px 0"}, ), html.Div( [ html.Div( dcc.Graph(id="poly-pie", config={"displayModeBar": False}, style={"height": "360px"}), style={**CARD_STYLE, "flex": "1"}, ), html.Div( dcc.Graph(id="poly-confidence", config={"displayModeBar": False}, style={"height": "360px"}), style={**CARD_STYLE, "flex": "1"}, ), ], style={"display": "flex", "gap": "12px", "padding": "16px"}, ), html.Div( [ html.Div("All Stations — Polymer Stacked Bar", style={"fontSize": "12px", "color": COLORS["text_muted"], "marginBottom": "8px", "letterSpacing": "0.05em", "textTransform": "uppercase"}), dcc.Graph(id="poly-stacked-bar", config={"displayModeBar": "hover"}, style={"height": "260px"}), ], style={**CARD_STYLE, "margin": "0 16px 16px"}, ), ], ) def tab_source_attribution(): return dcc.Tab( label="Source Attribution", value="tab-attr", style=TAB_STYLE, selected_style=TAB_SELECTED_STYLE, children=[ html.Div( html.Div( [ html.Strong("⚠️ Honest disclosure: ", style={"color": "#78350f"}), "M3 source-attribution head currently FAILS on held-out evaluation " "(top-1 accuracy = 0.0, mean Spearman ρ = −0.27 — see assets/m3_eval_report.json). " "The probability bars and flow map below visualize the model's outputs but do not yet recover true upstream sources. " "Source attribution remains an open research problem and is the primary motivation for pursuing this work with real datasets.", ], style={"backgroundColor": "#fef3c7", "border": "1px solid #d97706", "color": "#78350f", "padding": "10px 14px", "borderRadius": "4px", "fontSize": "12px", "lineHeight": "1.55"}, ), style={"padding": "14px 16px 0"}, ), html.Div( [ html.Div( [ dbc.Label("Station / Contamination Event", style={"fontSize": "12px", "color": COLORS["text_muted"]}), dcc.Dropdown(id="attr-station-select", placeholder="Select station…", style={"backgroundColor": COLORS["bg_card"], "color": "#000"}, clearable=False), ], style={"width": "300px"}, ), ], style={"padding": "16px 16px 0"}, ), html.Div( [ # Left: probability bars html.Div( [ html.Div("TOP 5 LIKELY SOURCES", style={"fontSize": "10px", "color": COLORS["text_muted"], "letterSpacing": "0.1em", "marginBottom": "14px"}), html.Div(id="attr-source-bars"), ], style={**CARD_STYLE, "flex": "1"}, ), # Right: attribution map html.Div( [ html.Div("HYDROLOGICAL FLOW MAP", style={"fontSize": "10px", "color": COLORS["text_muted"], "letterSpacing": "0.1em", "marginBottom": "10px"}), dcc.Graph(id="attr-map", config={"displayModeBar": False}, style={"height": "340px"}), ], style={**CARD_STYLE, "flex": "1"}, ), ], style={"display": "flex", "gap": "12px", "padding": "16px"}, ), # M3 graph embed html.Div( [ html.Div("GNN GRAPH — SOURCE ATTRIBUTION NETWORK", style={"fontSize": "10px", "color": COLORS["text_muted"], "letterSpacing": "0.1em", "marginBottom": "10px"}), html.Div(id="m3-graph-embed", children=html.Iframe( src="/assets/m3_graph.html", style={ "width": "100%", "height": "420px", "border": f"1px solid {COLORS['border']}", "borderRadius": "6px", "backgroundColor": COLORS["bg_panel"], } )), ], style={**CARD_STYLE, "margin": "0 16px 16px"}, ), ], ) def tab_predictive(): return dcc.Tab( label="Forecast & Alerts", value="tab-predict", style=TAB_STYLE, selected_style=TAB_SELECTED_STYLE, children=[ html.Div( [ html.Div("7-DAY FORECAST · All Stations", style={"fontSize": "10px", "color": COLORS["text_muted"], "letterSpacing": "0.1em", "marginBottom": "6px"}), html.Div(id="alert-station-list", style={"marginBottom": "16px"}), ], style={"padding": "16px 16px 0"}, ), html.Div( [ html.Div( [ dbc.Label("Station Forecast Detail", style={"fontSize": "12px", "color": COLORS["text_muted"]}), dcc.Dropdown(id="forecast-station-select", placeholder="Select station…", style={"backgroundColor": COLORS["bg_card"], "color": "#000"}, clearable=False), ], style={"width": "260px", "marginBottom": "12px"}, ), dcc.Graph(id="forecast-graph", config={"displayModeBar": "hover"}, style={"height": "380px"}), ], style={**CARD_STYLE, "margin": "0 16px 16px"}, ), ], ) def tab_reports(): return dcc.Tab( label="Reports", value="tab-reports", style=TAB_STYLE, selected_style=TAB_SELECTED_STYLE, children=[ html.Div( [ html.Div( [ html.Div("GENERATE COMPLIANCE REPORT (PROTOTYPE)", style={"fontSize": "10px", "color": COLORS["text_muted"], "letterSpacing": "0.1em", "marginBottom": "14px"}), html.Div( [ html.Div( [ dbc.Label("Station", style={"fontSize": "12px", "color": COLORS["text_muted"]}), dcc.Dropdown(id="report-station-select", placeholder="Select station…", style={"backgroundColor": COLORS["bg_card"], "color": "#000"}, clearable=False), ], style={"flex": "1"}, ), html.Div( [ dbc.Label("Report Mode", style={"fontSize": "12px", "color": COLORS["text_muted"]}), dcc.Dropdown( id="report-mode-select", options=[ {"label": "Template (offline)", "value": "template"}, {"label": "OpenAI GPT-4 (key required)", "value": "openai"}, ], value="template", style={"backgroundColor": COLORS["bg_card"], "color": "#000"}, clearable=False, ), ], style={"flex": "1"}, ), html.Button( "Generate Report", id="generate-report-btn", style={ "backgroundColor": COLORS["accent_cyan"], "color": "#000", "border": "none", "borderRadius": "6px", "padding": "8px 20px", "fontWeight": "600", "cursor": "pointer", "fontSize": "13px", "alignSelf": "flex-end", "height": "38px", }, n_clicks=0, ), ], style={"display": "flex", "gap": "12px", "alignItems": "flex-end"}, ), ], style={**CARD_STYLE, "marginBottom": "16px"}, ), # Report display area html.Div( [ html.Div("GENERATED REPORT", style={"fontSize": "10px", "color": COLORS["text_muted"], "letterSpacing": "0.1em", "marginBottom": "14px", "display": "flex", "justifyContent": "space-between", "alignItems": "center"}, ), html.Div(id="report-loading-indicator", style={"display": "none"}), html.Div( id="report-display", children=html.Div( "Select a station and click Generate Report to produce a structured compliance document. Note: synthetic-data prototype — not for actual regulatory submission.", style={"color": COLORS["text_muted"], "fontSize": "13px", "padding": "40px", "textAlign": "center"} ), style={"backgroundColor": COLORS["bg_panel"], "border": f"1px solid {COLORS['border']}", "borderRadius": "6px", "padding": "24px", "maxHeight": "480px", "overflowY": "auto", "fontFamily": "'Georgia', 'Times New Roman', serif", "lineHeight": "1.7"}, ), html.Div( [ html.A("Download PDF", id="download-pdf-link", href="/assets/sample_report.pdf", target="_blank", style={"color": COLORS["accent_cyan"], "fontSize": "12px", "textDecoration": "none", "border": f"1px solid {COLORS['accent_cyan']}", "padding": "6px 14px", "borderRadius": "4px"}), html.A("Download Markdown", id="download-md-link", href="/assets/sample_report.md", target="_blank", style={"color": COLORS["text_muted"], "fontSize": "12px", "textDecoration": "none", "border": f"1px solid {COLORS['border']}", "padding": "6px 14px", "borderRadius": "4px"}), ], style={"display": "flex", "gap": "10px", "marginTop": "14px"}, ), ], style=CARD_STYLE, ), ], style={"padding": "16px"}, ), ], ) GLOBAL_DISCLOSURE_BANNER = html.Div( [ html.Strong("⚠️ Honest disclosure: ", style={"color": "#78350f"}), "Engineering prototype on ", html.Strong("synthetic data only", style={"color": "#78350f"}), ". No access to NOAA NCEI, Rochman SLoPP/FLOPP, Kaggle Microplastic CV, MP-Set, HydroSHEDS, or ERA5 — these sources sit behind credentialed APIs, lab-internal sharing, or institutional Copernicus/commercial licenses I don't currently have access to. " "Stations, concentrations, polymer mixes, source attributions, forecasts, and alerts shown below are procedurally generated. " "M3 source-attribution head currently FAILS on held-out evaluation (top-1 = 0.0, Spearman ρ = −0.27). " "Loaders pick up real files from data/raw/ once access is granted. Not for operational monitoring or regulatory submission.", ], style={ "backgroundColor": "#fef3c7", "borderBottom": "2px solid #d97706", "color": "#78350f", "padding": "10px 28px", "fontSize": "12px", "lineHeight": "1.55", }, ) def make_layout(station_options: list) -> html.Div: """Build full app layout.""" return html.Div( [ dcc.Interval(id="clock-interval", interval=1000, n_intervals=0), dcc.Store(id="selected-station-store", data=station_options[0]["value"] if station_options else "STN-001"), GLOBAL_DISCLOSURE_BANNER, header(), dcc.Tabs( id="main-tabs", value="tab-map", style={"backgroundColor": COLORS["bg_panel"], "borderBottom": f"1px solid {COLORS['border']}"}, children=[ tab_live_map(), tab_time_series(), tab_polymer(), tab_source_attribution(), tab_predictive(), tab_reports(), ], ), # Hidden dropdowns seed options from data html.Div(id="dummy-output", style={"display": "none"}), ], style=GLOBAL_STYLE, )