Some checks failed
CI / lint-and-test (pull_request) Has been cancelled
- Update dbt model references to use new schema naming (stg_toronto, int_toronto, mart_toronto) - Refactor figure factories to use consistent column naming from new schema - Update callbacks to work with refactored data structures - Add centralized design tokens module for consistent styling - Streamline CLAUDE.md documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
120 lines
3.4 KiB
Python
120 lines
3.4 KiB
Python
"""Summary card figure factories for KPI display."""
|
|
|
|
from typing import Any
|
|
|
|
import plotly.graph_objects as go
|
|
|
|
from portfolio_app.design import (
|
|
COLOR_NEGATIVE,
|
|
COLOR_POSITIVE,
|
|
PAPER_BG,
|
|
PLOT_BG,
|
|
TEXT_PRIMARY,
|
|
)
|
|
|
|
|
|
def create_metric_card_figure(
|
|
value: float | int | str,
|
|
title: str,
|
|
delta: float | None = None,
|
|
delta_suffix: str = "%",
|
|
prefix: str = "",
|
|
suffix: str = "",
|
|
format_spec: str = ",.0f",
|
|
positive_is_good: bool = True,
|
|
) -> go.Figure:
|
|
"""Create a KPI indicator figure.
|
|
|
|
Args:
|
|
value: The main metric value.
|
|
title: Card title.
|
|
delta: Optional change value (for delta indicator).
|
|
delta_suffix: Suffix for delta value (e.g., '%').
|
|
prefix: Prefix for main value (e.g., '$').
|
|
suffix: Suffix for main value.
|
|
format_spec: Python format specification for the value.
|
|
positive_is_good: Whether positive delta is good (green) or bad (red).
|
|
|
|
Returns:
|
|
Plotly Figure object.
|
|
"""
|
|
# Determine numeric value for indicator
|
|
if isinstance(value, int | float):
|
|
number_value: float | None = float(value)
|
|
else:
|
|
number_value = None
|
|
|
|
fig = go.Figure()
|
|
|
|
# Add indicator trace
|
|
indicator_config: dict[str, Any] = {
|
|
"mode": "number",
|
|
"value": number_value if number_value is not None else 0,
|
|
"title": {"text": title, "font": {"size": 14}},
|
|
"number": {
|
|
"font": {"size": 32},
|
|
"prefix": prefix,
|
|
"suffix": suffix,
|
|
"valueformat": format_spec,
|
|
},
|
|
}
|
|
|
|
# Add delta if provided
|
|
if delta is not None:
|
|
indicator_config["mode"] = "number+delta"
|
|
indicator_config["delta"] = {
|
|
"reference": number_value - delta if number_value else 0,
|
|
"relative": False,
|
|
"valueformat": ".1f",
|
|
"suffix": delta_suffix,
|
|
"increasing": {
|
|
"color": COLOR_POSITIVE if positive_is_good else COLOR_NEGATIVE
|
|
},
|
|
"decreasing": {
|
|
"color": COLOR_NEGATIVE if positive_is_good else COLOR_POSITIVE
|
|
},
|
|
}
|
|
|
|
fig.add_trace(go.Indicator(**indicator_config))
|
|
|
|
fig.update_layout(
|
|
height=120,
|
|
margin={"l": 20, "r": 20, "t": 40, "b": 20},
|
|
paper_bgcolor=PAPER_BG,
|
|
plot_bgcolor=PLOT_BG,
|
|
font={"family": "Inter, sans-serif", "color": TEXT_PRIMARY},
|
|
)
|
|
|
|
return fig
|
|
|
|
|
|
def create_summary_metrics(
|
|
metrics: dict[str, dict[str, Any]],
|
|
) -> list[go.Figure]:
|
|
"""Create multiple metric card figures.
|
|
|
|
Args:
|
|
metrics: Dictionary of metric configurations.
|
|
Key: metric name
|
|
Value: dict with 'value', 'title', 'delta' (optional), etc.
|
|
|
|
Returns:
|
|
List of Plotly Figure objects.
|
|
"""
|
|
figures = []
|
|
|
|
for metric_config in metrics.values():
|
|
fig = create_metric_card_figure(
|
|
value=metric_config.get("value", 0),
|
|
title=metric_config.get("title", ""),
|
|
delta=metric_config.get("delta"),
|
|
delta_suffix=metric_config.get("delta_suffix", "%"),
|
|
prefix=metric_config.get("prefix", ""),
|
|
suffix=metric_config.get("suffix", ""),
|
|
format_spec=metric_config.get("format_spec", ",.0f"),
|
|
positive_is_good=metric_config.get("positive_is_good", True),
|
|
)
|
|
figures.append(fig)
|
|
|
|
return figures
|