ml4t-diagnostic 0.1.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ml4t/diagnostic/AGENT.md +25 -0
- ml4t/diagnostic/__init__.py +166 -0
- ml4t/diagnostic/backends/__init__.py +10 -0
- ml4t/diagnostic/backends/adapter.py +192 -0
- ml4t/diagnostic/backends/polars_backend.py +899 -0
- ml4t/diagnostic/caching/__init__.py +40 -0
- ml4t/diagnostic/caching/cache.py +331 -0
- ml4t/diagnostic/caching/decorators.py +131 -0
- ml4t/diagnostic/caching/smart_cache.py +339 -0
- ml4t/diagnostic/config/AGENT.md +24 -0
- ml4t/diagnostic/config/README.md +267 -0
- ml4t/diagnostic/config/__init__.py +219 -0
- ml4t/diagnostic/config/barrier_config.py +277 -0
- ml4t/diagnostic/config/base.py +301 -0
- ml4t/diagnostic/config/event_config.py +148 -0
- ml4t/diagnostic/config/feature_config.py +404 -0
- ml4t/diagnostic/config/multi_signal_config.py +55 -0
- ml4t/diagnostic/config/portfolio_config.py +215 -0
- ml4t/diagnostic/config/report_config.py +391 -0
- ml4t/diagnostic/config/sharpe_config.py +202 -0
- ml4t/diagnostic/config/signal_config.py +206 -0
- ml4t/diagnostic/config/trade_analysis_config.py +310 -0
- ml4t/diagnostic/config/validation.py +279 -0
- ml4t/diagnostic/core/__init__.py +29 -0
- ml4t/diagnostic/core/numba_utils.py +315 -0
- ml4t/diagnostic/core/purging.py +372 -0
- ml4t/diagnostic/core/sampling.py +471 -0
- ml4t/diagnostic/errors/__init__.py +205 -0
- ml4t/diagnostic/evaluation/AGENT.md +26 -0
- ml4t/diagnostic/evaluation/__init__.py +437 -0
- ml4t/diagnostic/evaluation/autocorrelation.py +531 -0
- ml4t/diagnostic/evaluation/barrier_analysis.py +1050 -0
- ml4t/diagnostic/evaluation/binary_metrics.py +910 -0
- ml4t/diagnostic/evaluation/dashboard.py +715 -0
- ml4t/diagnostic/evaluation/diagnostic_plots.py +1037 -0
- ml4t/diagnostic/evaluation/distribution/__init__.py +499 -0
- ml4t/diagnostic/evaluation/distribution/moments.py +299 -0
- ml4t/diagnostic/evaluation/distribution/tails.py +777 -0
- ml4t/diagnostic/evaluation/distribution/tests.py +470 -0
- ml4t/diagnostic/evaluation/drift/__init__.py +139 -0
- ml4t/diagnostic/evaluation/drift/analysis.py +432 -0
- ml4t/diagnostic/evaluation/drift/domain_classifier.py +517 -0
- ml4t/diagnostic/evaluation/drift/population_stability_index.py +310 -0
- ml4t/diagnostic/evaluation/drift/wasserstein.py +388 -0
- ml4t/diagnostic/evaluation/event_analysis.py +647 -0
- ml4t/diagnostic/evaluation/excursion.py +390 -0
- ml4t/diagnostic/evaluation/feature_diagnostics.py +873 -0
- ml4t/diagnostic/evaluation/feature_outcome.py +666 -0
- ml4t/diagnostic/evaluation/framework.py +935 -0
- ml4t/diagnostic/evaluation/metric_registry.py +255 -0
- ml4t/diagnostic/evaluation/metrics/AGENT.md +23 -0
- ml4t/diagnostic/evaluation/metrics/__init__.py +133 -0
- ml4t/diagnostic/evaluation/metrics/basic.py +160 -0
- ml4t/diagnostic/evaluation/metrics/conditional_ic.py +469 -0
- ml4t/diagnostic/evaluation/metrics/feature_outcome.py +475 -0
- ml4t/diagnostic/evaluation/metrics/ic_statistics.py +446 -0
- ml4t/diagnostic/evaluation/metrics/importance_analysis.py +338 -0
- ml4t/diagnostic/evaluation/metrics/importance_classical.py +375 -0
- ml4t/diagnostic/evaluation/metrics/importance_mda.py +371 -0
- ml4t/diagnostic/evaluation/metrics/importance_shap.py +715 -0
- ml4t/diagnostic/evaluation/metrics/information_coefficient.py +527 -0
- ml4t/diagnostic/evaluation/metrics/interactions.py +772 -0
- ml4t/diagnostic/evaluation/metrics/monotonicity.py +226 -0
- ml4t/diagnostic/evaluation/metrics/risk_adjusted.py +324 -0
- ml4t/diagnostic/evaluation/multi_signal.py +550 -0
- ml4t/diagnostic/evaluation/portfolio_analysis/__init__.py +83 -0
- ml4t/diagnostic/evaluation/portfolio_analysis/analysis.py +734 -0
- ml4t/diagnostic/evaluation/portfolio_analysis/metrics.py +589 -0
- ml4t/diagnostic/evaluation/portfolio_analysis/results.py +334 -0
- ml4t/diagnostic/evaluation/report_generation.py +824 -0
- ml4t/diagnostic/evaluation/signal_selector.py +452 -0
- ml4t/diagnostic/evaluation/stat_registry.py +139 -0
- ml4t/diagnostic/evaluation/stationarity/__init__.py +97 -0
- ml4t/diagnostic/evaluation/stationarity/analysis.py +518 -0
- ml4t/diagnostic/evaluation/stationarity/augmented_dickey_fuller.py +296 -0
- ml4t/diagnostic/evaluation/stationarity/kpss_test.py +308 -0
- ml4t/diagnostic/evaluation/stationarity/phillips_perron.py +365 -0
- ml4t/diagnostic/evaluation/stats/AGENT.md +43 -0
- ml4t/diagnostic/evaluation/stats/__init__.py +191 -0
- ml4t/diagnostic/evaluation/stats/backtest_overfitting.py +219 -0
- ml4t/diagnostic/evaluation/stats/bootstrap.py +228 -0
- ml4t/diagnostic/evaluation/stats/deflated_sharpe_ratio.py +591 -0
- ml4t/diagnostic/evaluation/stats/false_discovery_rate.py +295 -0
- ml4t/diagnostic/evaluation/stats/hac_standard_errors.py +108 -0
- ml4t/diagnostic/evaluation/stats/minimum_track_record.py +408 -0
- ml4t/diagnostic/evaluation/stats/moments.py +164 -0
- ml4t/diagnostic/evaluation/stats/rademacher_adjustment.py +436 -0
- ml4t/diagnostic/evaluation/stats/reality_check.py +155 -0
- ml4t/diagnostic/evaluation/stats/sharpe_inference.py +219 -0
- ml4t/diagnostic/evaluation/themes.py +330 -0
- ml4t/diagnostic/evaluation/threshold_analysis.py +957 -0
- ml4t/diagnostic/evaluation/trade_analysis.py +1136 -0
- ml4t/diagnostic/evaluation/trade_dashboard/__init__.py +32 -0
- ml4t/diagnostic/evaluation/trade_dashboard/app.py +315 -0
- ml4t/diagnostic/evaluation/trade_dashboard/export/__init__.py +18 -0
- ml4t/diagnostic/evaluation/trade_dashboard/export/csv.py +82 -0
- ml4t/diagnostic/evaluation/trade_dashboard/export/html.py +276 -0
- ml4t/diagnostic/evaluation/trade_dashboard/io.py +166 -0
- ml4t/diagnostic/evaluation/trade_dashboard/normalize.py +304 -0
- ml4t/diagnostic/evaluation/trade_dashboard/stats.py +386 -0
- ml4t/diagnostic/evaluation/trade_dashboard/style.py +79 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/__init__.py +21 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/patterns.py +354 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/shap_analysis.py +280 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/stat_validation.py +186 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/worst_trades.py +236 -0
- ml4t/diagnostic/evaluation/trade_dashboard/types.py +129 -0
- ml4t/diagnostic/evaluation/trade_shap/__init__.py +102 -0
- ml4t/diagnostic/evaluation/trade_shap/alignment.py +188 -0
- ml4t/diagnostic/evaluation/trade_shap/characterize.py +413 -0
- ml4t/diagnostic/evaluation/trade_shap/cluster.py +302 -0
- ml4t/diagnostic/evaluation/trade_shap/explain.py +208 -0
- ml4t/diagnostic/evaluation/trade_shap/hypotheses/__init__.py +23 -0
- ml4t/diagnostic/evaluation/trade_shap/hypotheses/generator.py +290 -0
- ml4t/diagnostic/evaluation/trade_shap/hypotheses/matcher.py +251 -0
- ml4t/diagnostic/evaluation/trade_shap/hypotheses/templates.yaml +467 -0
- ml4t/diagnostic/evaluation/trade_shap/models.py +386 -0
- ml4t/diagnostic/evaluation/trade_shap/normalize.py +116 -0
- ml4t/diagnostic/evaluation/trade_shap/pipeline.py +263 -0
- ml4t/diagnostic/evaluation/trade_shap_dashboard.py +283 -0
- ml4t/diagnostic/evaluation/trade_shap_diagnostics.py +588 -0
- ml4t/diagnostic/evaluation/validated_cv.py +535 -0
- ml4t/diagnostic/evaluation/visualization.py +1050 -0
- ml4t/diagnostic/evaluation/volatility/__init__.py +45 -0
- ml4t/diagnostic/evaluation/volatility/analysis.py +351 -0
- ml4t/diagnostic/evaluation/volatility/arch.py +258 -0
- ml4t/diagnostic/evaluation/volatility/garch.py +460 -0
- ml4t/diagnostic/integration/__init__.py +48 -0
- ml4t/diagnostic/integration/backtest_contract.py +671 -0
- ml4t/diagnostic/integration/data_contract.py +316 -0
- ml4t/diagnostic/integration/engineer_contract.py +226 -0
- ml4t/diagnostic/logging/__init__.py +77 -0
- ml4t/diagnostic/logging/logger.py +245 -0
- ml4t/diagnostic/logging/performance.py +234 -0
- ml4t/diagnostic/logging/progress.py +234 -0
- ml4t/diagnostic/logging/wandb.py +412 -0
- ml4t/diagnostic/metrics/__init__.py +9 -0
- ml4t/diagnostic/metrics/percentiles.py +128 -0
- ml4t/diagnostic/py.typed +1 -0
- ml4t/diagnostic/reporting/__init__.py +43 -0
- ml4t/diagnostic/reporting/base.py +130 -0
- ml4t/diagnostic/reporting/html_renderer.py +275 -0
- ml4t/diagnostic/reporting/json_renderer.py +51 -0
- ml4t/diagnostic/reporting/markdown_renderer.py +117 -0
- ml4t/diagnostic/results/AGENT.md +24 -0
- ml4t/diagnostic/results/__init__.py +105 -0
- ml4t/diagnostic/results/barrier_results/__init__.py +36 -0
- ml4t/diagnostic/results/barrier_results/hit_rate.py +304 -0
- ml4t/diagnostic/results/barrier_results/precision_recall.py +266 -0
- ml4t/diagnostic/results/barrier_results/profit_factor.py +297 -0
- ml4t/diagnostic/results/barrier_results/tearsheet.py +397 -0
- ml4t/diagnostic/results/barrier_results/time_to_target.py +305 -0
- ml4t/diagnostic/results/barrier_results/validation.py +38 -0
- ml4t/diagnostic/results/base.py +177 -0
- ml4t/diagnostic/results/event_results.py +349 -0
- ml4t/diagnostic/results/feature_results.py +787 -0
- ml4t/diagnostic/results/multi_signal_results.py +431 -0
- ml4t/diagnostic/results/portfolio_results.py +281 -0
- ml4t/diagnostic/results/sharpe_results.py +448 -0
- ml4t/diagnostic/results/signal_results/__init__.py +74 -0
- ml4t/diagnostic/results/signal_results/ic.py +581 -0
- ml4t/diagnostic/results/signal_results/irtc.py +110 -0
- ml4t/diagnostic/results/signal_results/quantile.py +392 -0
- ml4t/diagnostic/results/signal_results/tearsheet.py +456 -0
- ml4t/diagnostic/results/signal_results/turnover.py +213 -0
- ml4t/diagnostic/results/signal_results/validation.py +147 -0
- ml4t/diagnostic/signal/AGENT.md +17 -0
- ml4t/diagnostic/signal/__init__.py +69 -0
- ml4t/diagnostic/signal/_report.py +152 -0
- ml4t/diagnostic/signal/_utils.py +261 -0
- ml4t/diagnostic/signal/core.py +275 -0
- ml4t/diagnostic/signal/quantile.py +148 -0
- ml4t/diagnostic/signal/result.py +214 -0
- ml4t/diagnostic/signal/signal_ic.py +129 -0
- ml4t/diagnostic/signal/turnover.py +182 -0
- ml4t/diagnostic/splitters/AGENT.md +19 -0
- ml4t/diagnostic/splitters/__init__.py +36 -0
- ml4t/diagnostic/splitters/base.py +501 -0
- ml4t/diagnostic/splitters/calendar.py +421 -0
- ml4t/diagnostic/splitters/calendar_config.py +91 -0
- ml4t/diagnostic/splitters/combinatorial.py +1064 -0
- ml4t/diagnostic/splitters/config.py +322 -0
- ml4t/diagnostic/splitters/cpcv/__init__.py +57 -0
- ml4t/diagnostic/splitters/cpcv/combinations.py +119 -0
- ml4t/diagnostic/splitters/cpcv/partitioning.py +263 -0
- ml4t/diagnostic/splitters/cpcv/purge_engine.py +379 -0
- ml4t/diagnostic/splitters/cpcv/windows.py +190 -0
- ml4t/diagnostic/splitters/group_isolation.py +329 -0
- ml4t/diagnostic/splitters/persistence.py +316 -0
- ml4t/diagnostic/splitters/utils.py +207 -0
- ml4t/diagnostic/splitters/walk_forward.py +757 -0
- ml4t/diagnostic/utils/__init__.py +42 -0
- ml4t/diagnostic/utils/config.py +542 -0
- ml4t/diagnostic/utils/dependencies.py +318 -0
- ml4t/diagnostic/utils/sessions.py +127 -0
- ml4t/diagnostic/validation/__init__.py +54 -0
- ml4t/diagnostic/validation/dataframe.py +274 -0
- ml4t/diagnostic/validation/returns.py +280 -0
- ml4t/diagnostic/validation/timeseries.py +299 -0
- ml4t/diagnostic/visualization/AGENT.md +19 -0
- ml4t/diagnostic/visualization/__init__.py +223 -0
- ml4t/diagnostic/visualization/backtest/__init__.py +98 -0
- ml4t/diagnostic/visualization/backtest/cost_attribution.py +762 -0
- ml4t/diagnostic/visualization/backtest/executive_summary.py +895 -0
- ml4t/diagnostic/visualization/backtest/interactive_controls.py +673 -0
- ml4t/diagnostic/visualization/backtest/statistical_validity.py +874 -0
- ml4t/diagnostic/visualization/backtest/tearsheet.py +565 -0
- ml4t/diagnostic/visualization/backtest/template_system.py +373 -0
- ml4t/diagnostic/visualization/backtest/trade_plots.py +1172 -0
- ml4t/diagnostic/visualization/barrier_plots.py +782 -0
- ml4t/diagnostic/visualization/core.py +1060 -0
- ml4t/diagnostic/visualization/dashboards/__init__.py +36 -0
- ml4t/diagnostic/visualization/dashboards/base.py +582 -0
- ml4t/diagnostic/visualization/dashboards/importance.py +801 -0
- ml4t/diagnostic/visualization/dashboards/interaction.py +263 -0
- ml4t/diagnostic/visualization/dashboards.py +43 -0
- ml4t/diagnostic/visualization/data_extraction/__init__.py +48 -0
- ml4t/diagnostic/visualization/data_extraction/importance.py +649 -0
- ml4t/diagnostic/visualization/data_extraction/interaction.py +504 -0
- ml4t/diagnostic/visualization/data_extraction/types.py +113 -0
- ml4t/diagnostic/visualization/data_extraction/validation.py +66 -0
- ml4t/diagnostic/visualization/feature_plots.py +888 -0
- ml4t/diagnostic/visualization/interaction_plots.py +618 -0
- ml4t/diagnostic/visualization/portfolio/__init__.py +41 -0
- ml4t/diagnostic/visualization/portfolio/dashboard.py +514 -0
- ml4t/diagnostic/visualization/portfolio/drawdown_plots.py +341 -0
- ml4t/diagnostic/visualization/portfolio/returns_plots.py +487 -0
- ml4t/diagnostic/visualization/portfolio/risk_plots.py +301 -0
- ml4t/diagnostic/visualization/report_generation.py +1343 -0
- ml4t/diagnostic/visualization/signal/__init__.py +103 -0
- ml4t/diagnostic/visualization/signal/dashboard.py +911 -0
- ml4t/diagnostic/visualization/signal/event_plots.py +514 -0
- ml4t/diagnostic/visualization/signal/ic_plots.py +635 -0
- ml4t/diagnostic/visualization/signal/multi_signal_dashboard.py +974 -0
- ml4t/diagnostic/visualization/signal/multi_signal_plots.py +603 -0
- ml4t/diagnostic/visualization/signal/quantile_plots.py +625 -0
- ml4t/diagnostic/visualization/signal/turnover_plots.py +400 -0
- ml4t/diagnostic/visualization/trade_shap/__init__.py +90 -0
- ml4t_diagnostic-0.1.0a1.dist-info/METADATA +1044 -0
- ml4t_diagnostic-0.1.0a1.dist-info/RECORD +242 -0
- ml4t_diagnostic-0.1.0a1.dist-info/WHEEL +4 -0
- ml4t_diagnostic-0.1.0a1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Dashboard components for rich visualization.
|
|
2
|
+
|
|
3
|
+
This package provides the dashboard architecture for creating interactive,
|
|
4
|
+
multi-tab analytical dashboards. Dashboards compose multiple visualizations
|
|
5
|
+
with interactive controls (tabs, dropdowns, filters) into cohesive analytical
|
|
6
|
+
experiences.
|
|
7
|
+
|
|
8
|
+
Architecture:
|
|
9
|
+
- BaseDashboard: Abstract base class defining dashboard interface
|
|
10
|
+
- DashboardSection: Container for a single dashboard section (tab)
|
|
11
|
+
- FeatureImportanceDashboard: Multi-tab importance analysis
|
|
12
|
+
- FeatureInteractionDashboard: Network and matrix interaction views
|
|
13
|
+
|
|
14
|
+
Design Principles:
|
|
15
|
+
- Progressive disclosure: Summary → Detail → Deep-dive
|
|
16
|
+
- Modular composition: Dashboards work standalone or compose
|
|
17
|
+
- Interactive controls: Tabs, dropdowns, filters, drill-down
|
|
18
|
+
- LLM-ready: Structured data enables future interpretation
|
|
19
|
+
- Professional output: Publication-quality HTML with embedded JS
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .base import THEMES, BaseDashboard, DashboardSection, get_theme
|
|
23
|
+
from .importance import FeatureImportanceDashboard
|
|
24
|
+
from .interaction import FeatureInteractionDashboard
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
# Theme utilities
|
|
28
|
+
"THEMES",
|
|
29
|
+
"get_theme",
|
|
30
|
+
# Base classes
|
|
31
|
+
"BaseDashboard",
|
|
32
|
+
"DashboardSection",
|
|
33
|
+
# Dashboard implementations
|
|
34
|
+
"FeatureImportanceDashboard",
|
|
35
|
+
"FeatureInteractionDashboard",
|
|
36
|
+
]
|
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
"""Base classes and utilities for dashboard components.
|
|
2
|
+
|
|
3
|
+
This module provides the foundation for creating interactive,
|
|
4
|
+
multi-tab analytical dashboards with consistent styling.
|
|
5
|
+
|
|
6
|
+
Classes:
|
|
7
|
+
BaseDashboard: Abstract base class defining dashboard interface
|
|
8
|
+
DashboardSection: Container for a single dashboard section (tab)
|
|
9
|
+
|
|
10
|
+
Functions:
|
|
11
|
+
get_theme: Get theme configuration by name
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
19
|
+
|
|
20
|
+
import plotly.graph_objects as go
|
|
21
|
+
|
|
22
|
+
from ...evaluation.themes import DARK_TEMPLATE, DEFAULT_TEMPLATE
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
# Theme mapping for dashboard use
|
|
28
|
+
THEMES = {
|
|
29
|
+
"light": {"template": DEFAULT_TEMPLATE, "plot_bgcolor": "#ffffff", "font_color": "#000000"},
|
|
30
|
+
"dark": {"template": DARK_TEMPLATE, "plot_bgcolor": "#1e1e1e", "font_color": "#ffffff"},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_theme(theme_name: str) -> dict:
|
|
35
|
+
"""Get theme configuration.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
theme_name : str
|
|
40
|
+
Theme name ('light' or 'dark')
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
dict
|
|
45
|
+
Theme configuration with template, plot_bgcolor, font_color
|
|
46
|
+
"""
|
|
47
|
+
return THEMES.get(theme_name, THEMES["light"])
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DashboardSection:
|
|
51
|
+
"""Container for a single dashboard section (tab).
|
|
52
|
+
|
|
53
|
+
Each section represents one view or perspective on the analysis,
|
|
54
|
+
typically containing multiple plots, tables, or text content.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
title: str,
|
|
60
|
+
description: str = "",
|
|
61
|
+
content: str = "",
|
|
62
|
+
plots: list[go.Figure] | None = None,
|
|
63
|
+
):
|
|
64
|
+
"""Initialize dashboard section.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
title : str
|
|
69
|
+
Section title (shown in tab or header)
|
|
70
|
+
description : str, default=""
|
|
71
|
+
Optional description text (HTML supported)
|
|
72
|
+
content : str, default=""
|
|
73
|
+
Section content (HTML)
|
|
74
|
+
plots : list of go.Figure, optional
|
|
75
|
+
Plotly figures to include in section
|
|
76
|
+
"""
|
|
77
|
+
self.title = title
|
|
78
|
+
self.description = (
|
|
79
|
+
f'<div class="section-description">{description}</div>' if description else ""
|
|
80
|
+
)
|
|
81
|
+
self.content = content
|
|
82
|
+
self.plots = plots or []
|
|
83
|
+
|
|
84
|
+
def add_plot(self, fig: go.Figure, container_id: str | None = None) -> None:
|
|
85
|
+
"""Add a Plotly figure to this section.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
fig : go.Figure
|
|
90
|
+
Plotly figure to add
|
|
91
|
+
container_id : str, optional
|
|
92
|
+
HTML id for plot container. Auto-generated if None.
|
|
93
|
+
"""
|
|
94
|
+
self.plots.append(fig)
|
|
95
|
+
|
|
96
|
+
# Generate plot HTML and append to content
|
|
97
|
+
plot_id = container_id or f"plot-{len(self.plots)}"
|
|
98
|
+
plot_html = fig.to_html(include_plotlyjs=False, div_id=plot_id, config={"responsive": True})
|
|
99
|
+
|
|
100
|
+
self.content += f'<div class="plot-container">{plot_html}</div>'
|
|
101
|
+
|
|
102
|
+
def add_html(self, html: str) -> None:
|
|
103
|
+
"""Add custom HTML content to section.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
html : str
|
|
108
|
+
HTML content to append
|
|
109
|
+
"""
|
|
110
|
+
self.content += html
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class BaseDashboard(ABC):
|
|
114
|
+
"""Abstract base class for interactive dashboards.
|
|
115
|
+
|
|
116
|
+
All dashboards follow a common pattern:
|
|
117
|
+
1. Extract structured data from analysis results
|
|
118
|
+
2. Create multiple visualizations (plots, tables)
|
|
119
|
+
3. Compose into interactive HTML with tabs/controls
|
|
120
|
+
4. Optionally export to PDF or JSON
|
|
121
|
+
|
|
122
|
+
Subclasses must implement:
|
|
123
|
+
- generate(): Transform raw results into complete HTML
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
title: str,
|
|
129
|
+
theme: Literal["light", "dark"] = "light",
|
|
130
|
+
width: int | None = None,
|
|
131
|
+
height: int | None = None,
|
|
132
|
+
):
|
|
133
|
+
"""Initialize dashboard.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
title : str
|
|
138
|
+
Dashboard title (displayed at top)
|
|
139
|
+
theme : {'light', 'dark'}, default='light'
|
|
140
|
+
Visual theme for all plots and styling
|
|
141
|
+
width : int, optional
|
|
142
|
+
Dashboard width in pixels. If None, uses responsive width.
|
|
143
|
+
height : int, optional
|
|
144
|
+
Dashboard height in pixels. If None, uses auto height.
|
|
145
|
+
"""
|
|
146
|
+
self.title = title
|
|
147
|
+
self.theme = theme
|
|
148
|
+
self.width = width
|
|
149
|
+
self.height = height
|
|
150
|
+
self.theme_config = THEMES[theme]
|
|
151
|
+
self.created_at = datetime.now()
|
|
152
|
+
|
|
153
|
+
# Sections storage (populated by subclasses)
|
|
154
|
+
self.sections: list[DashboardSection] = []
|
|
155
|
+
|
|
156
|
+
@abstractmethod
|
|
157
|
+
def generate(self, analysis_results: Any, **kwargs: Any) -> str:
|
|
158
|
+
"""Generate complete dashboard HTML from analysis results.
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
analysis_results : Any
|
|
163
|
+
Raw analysis results (format depends on dashboard type)
|
|
164
|
+
**kwargs
|
|
165
|
+
Additional dashboard-specific parameters
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
str
|
|
170
|
+
Complete HTML document with embedded CSS/JS
|
|
171
|
+
"""
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
def save(self, output_path: str, analysis_results: Any, **kwargs: Any) -> str:
|
|
175
|
+
"""Generate and save dashboard to file.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
output_path : str
|
|
180
|
+
Path for output HTML file
|
|
181
|
+
analysis_results : Any
|
|
182
|
+
Raw analysis results (format depends on dashboard type)
|
|
183
|
+
**kwargs
|
|
184
|
+
Additional dashboard-specific parameters
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
str
|
|
189
|
+
Path to saved file
|
|
190
|
+
"""
|
|
191
|
+
html = self.generate(analysis_results, **kwargs)
|
|
192
|
+
|
|
193
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
194
|
+
f.write(html)
|
|
195
|
+
|
|
196
|
+
return output_path
|
|
197
|
+
|
|
198
|
+
def _build_header(self) -> str:
|
|
199
|
+
"""Build dashboard header HTML."""
|
|
200
|
+
return f"""
|
|
201
|
+
<div class="dashboard-header">
|
|
202
|
+
<h1>{self.title}</h1>
|
|
203
|
+
<p class="timestamp">Generated: {self.created_at.strftime("%Y-%m-%d %H:%M:%S")}</p>
|
|
204
|
+
</div>
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
def _build_navigation(self) -> str:
|
|
208
|
+
"""Build tab navigation HTML."""
|
|
209
|
+
if len(self.sections) <= 1:
|
|
210
|
+
return "" # No navigation needed for single-section dashboards
|
|
211
|
+
|
|
212
|
+
tabs_html = []
|
|
213
|
+
for i, section in enumerate(self.sections):
|
|
214
|
+
active_class = "active" if i == 0 else ""
|
|
215
|
+
tabs_html.append(
|
|
216
|
+
f'<button class="tab-button {active_class}" onclick="switchTab(event, \'section-{i}\')">'
|
|
217
|
+
f"{section.title}</button>"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return f"""
|
|
221
|
+
<div class="tab-navigation">
|
|
222
|
+
{"".join(tabs_html)}
|
|
223
|
+
</div>
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
def _build_sections(self) -> str:
|
|
227
|
+
"""Build all dashboard sections HTML."""
|
|
228
|
+
sections_html = []
|
|
229
|
+
|
|
230
|
+
for i, section in enumerate(self.sections):
|
|
231
|
+
active_class = "active" if i == 0 else ""
|
|
232
|
+
sections_html.append(f"""
|
|
233
|
+
<div id="section-{i}" class="tab-content {active_class}">
|
|
234
|
+
<h2>{section.title}</h2>
|
|
235
|
+
{section.description}
|
|
236
|
+
{section.content}
|
|
237
|
+
</div>
|
|
238
|
+
""")
|
|
239
|
+
|
|
240
|
+
return "".join(sections_html)
|
|
241
|
+
|
|
242
|
+
def _get_base_styles(self) -> str:
|
|
243
|
+
"""Get base CSS styles for dashboard."""
|
|
244
|
+
bg_color = self.theme_config["plot_bgcolor"]
|
|
245
|
+
text_color = self.theme_config["font_color"]
|
|
246
|
+
border_color = "#555" if self.theme == "dark" else "#ddd"
|
|
247
|
+
|
|
248
|
+
return f"""
|
|
249
|
+
<style>
|
|
250
|
+
body {{
|
|
251
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
252
|
+
margin: 0;
|
|
253
|
+
padding: 20px;
|
|
254
|
+
background-color: {bg_color};
|
|
255
|
+
color: {text_color};
|
|
256
|
+
}}
|
|
257
|
+
|
|
258
|
+
.dashboard-header {{
|
|
259
|
+
text-align: center;
|
|
260
|
+
margin-bottom: 30px;
|
|
261
|
+
padding-bottom: 20px;
|
|
262
|
+
border-bottom: 2px solid {border_color};
|
|
263
|
+
}}
|
|
264
|
+
|
|
265
|
+
.dashboard-header h1 {{
|
|
266
|
+
margin: 0;
|
|
267
|
+
font-size: 2em;
|
|
268
|
+
font-weight: 600;
|
|
269
|
+
}}
|
|
270
|
+
|
|
271
|
+
.timestamp {{
|
|
272
|
+
margin: 10px 0 0 0;
|
|
273
|
+
font-size: 0.9em;
|
|
274
|
+
opacity: 0.7;
|
|
275
|
+
}}
|
|
276
|
+
|
|
277
|
+
.tab-navigation {{
|
|
278
|
+
display: flex;
|
|
279
|
+
gap: 5px;
|
|
280
|
+
margin-bottom: 20px;
|
|
281
|
+
border-bottom: 2px solid {border_color};
|
|
282
|
+
}}
|
|
283
|
+
|
|
284
|
+
.tab-button {{
|
|
285
|
+
padding: 12px 24px;
|
|
286
|
+
background: transparent;
|
|
287
|
+
border: none;
|
|
288
|
+
border-bottom: 3px solid transparent;
|
|
289
|
+
cursor: pointer;
|
|
290
|
+
font-size: 1em;
|
|
291
|
+
color: {text_color};
|
|
292
|
+
transition: all 0.3s ease;
|
|
293
|
+
}}
|
|
294
|
+
|
|
295
|
+
.tab-button:hover {{
|
|
296
|
+
background-color: {"rgba(255,255,255,0.05)" if self.theme == "dark" else "rgba(0,0,0,0.05)"};
|
|
297
|
+
}}
|
|
298
|
+
|
|
299
|
+
.tab-button.active {{
|
|
300
|
+
border-bottom-color: #1f77b4;
|
|
301
|
+
font-weight: 600;
|
|
302
|
+
}}
|
|
303
|
+
|
|
304
|
+
.tab-content {{
|
|
305
|
+
display: none;
|
|
306
|
+
animation: fadeIn 0.3s;
|
|
307
|
+
}}
|
|
308
|
+
|
|
309
|
+
.tab-content.active {{
|
|
310
|
+
display: block;
|
|
311
|
+
}}
|
|
312
|
+
|
|
313
|
+
@keyframes fadeIn {{
|
|
314
|
+
from {{ opacity: 0; }}
|
|
315
|
+
to {{ opacity: 1; }}
|
|
316
|
+
}}
|
|
317
|
+
|
|
318
|
+
.section-description {{
|
|
319
|
+
margin: 10px 0 20px 0;
|
|
320
|
+
padding: 15px;
|
|
321
|
+
background-color: {"rgba(255,255,255,0.05)" if self.theme == "dark" else "rgba(0,0,0,0.05)"};
|
|
322
|
+
border-left: 4px solid #1f77b4;
|
|
323
|
+
border-radius: 4px;
|
|
324
|
+
}}
|
|
325
|
+
|
|
326
|
+
.plot-container {{
|
|
327
|
+
margin: 20px 0;
|
|
328
|
+
}}
|
|
329
|
+
|
|
330
|
+
.insights-panel {{
|
|
331
|
+
margin: 30px 0;
|
|
332
|
+
padding: 20px;
|
|
333
|
+
background-color: {"rgba(100,150,255,0.1)" if self.theme == "dark" else "rgba(100,150,255,0.05)"};
|
|
334
|
+
border-radius: 8px;
|
|
335
|
+
border: 1px solid {border_color};
|
|
336
|
+
}}
|
|
337
|
+
|
|
338
|
+
.insights-panel h3 {{
|
|
339
|
+
margin-top: 0;
|
|
340
|
+
color: #1f77b4;
|
|
341
|
+
}}
|
|
342
|
+
|
|
343
|
+
.insights-panel ul {{
|
|
344
|
+
margin: 10px 0;
|
|
345
|
+
padding-left: 20px;
|
|
346
|
+
}}
|
|
347
|
+
|
|
348
|
+
.insights-panel li {{
|
|
349
|
+
margin: 8px 0;
|
|
350
|
+
line-height: 1.5;
|
|
351
|
+
}}
|
|
352
|
+
|
|
353
|
+
.metric-grid {{
|
|
354
|
+
display: grid;
|
|
355
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
356
|
+
gap: 15px;
|
|
357
|
+
margin: 20px 0;
|
|
358
|
+
}}
|
|
359
|
+
|
|
360
|
+
.metric-card {{
|
|
361
|
+
padding: 15px;
|
|
362
|
+
background-color: {"rgba(255,255,255,0.05)" if self.theme == "dark" else "rgba(0,0,0,0.05)"};
|
|
363
|
+
border-radius: 6px;
|
|
364
|
+
border: 1px solid {border_color};
|
|
365
|
+
}}
|
|
366
|
+
|
|
367
|
+
.metric-label {{
|
|
368
|
+
font-size: 0.85em;
|
|
369
|
+
opacity: 0.7;
|
|
370
|
+
margin-bottom: 5px;
|
|
371
|
+
}}
|
|
372
|
+
|
|
373
|
+
.metric-value {{
|
|
374
|
+
font-size: 1.5em;
|
|
375
|
+
font-weight: 600;
|
|
376
|
+
}}
|
|
377
|
+
|
|
378
|
+
.metric-sublabel {{
|
|
379
|
+
font-size: 0.75em;
|
|
380
|
+
opacity: 0.6;
|
|
381
|
+
margin-top: 5px;
|
|
382
|
+
}}
|
|
383
|
+
|
|
384
|
+
.feature-table {{
|
|
385
|
+
width: 100%;
|
|
386
|
+
border-collapse: collapse;
|
|
387
|
+
margin: 20px 0;
|
|
388
|
+
font-size: 0.95em;
|
|
389
|
+
}}
|
|
390
|
+
|
|
391
|
+
.feature-table thead {{
|
|
392
|
+
background-color: {"rgba(255,255,255,0.1)" if self.theme == "dark" else "rgba(0,0,0,0.1)"};
|
|
393
|
+
}}
|
|
394
|
+
|
|
395
|
+
.feature-table th {{
|
|
396
|
+
padding: 12px 15px;
|
|
397
|
+
text-align: left;
|
|
398
|
+
font-weight: 600;
|
|
399
|
+
border-bottom: 2px solid {border_color};
|
|
400
|
+
}}
|
|
401
|
+
|
|
402
|
+
.feature-table td {{
|
|
403
|
+
padding: 10px 15px;
|
|
404
|
+
border-bottom: 1px solid {border_color};
|
|
405
|
+
}}
|
|
406
|
+
|
|
407
|
+
.feature-table tbody tr:hover {{
|
|
408
|
+
background-color: {"rgba(255,255,255,0.05)" if self.theme == "dark" else "rgba(0,0,0,0.02)"};
|
|
409
|
+
}}
|
|
410
|
+
|
|
411
|
+
.badge {{
|
|
412
|
+
display: inline-block;
|
|
413
|
+
padding: 4px 10px;
|
|
414
|
+
border-radius: 12px;
|
|
415
|
+
font-size: 0.85em;
|
|
416
|
+
font-weight: 600;
|
|
417
|
+
}}
|
|
418
|
+
|
|
419
|
+
.badge-high {{
|
|
420
|
+
background-color: rgba(40, 167, 69, 0.2);
|
|
421
|
+
color: #28a745;
|
|
422
|
+
}}
|
|
423
|
+
|
|
424
|
+
.badge-medium {{
|
|
425
|
+
background-color: rgba(255, 193, 7, 0.2);
|
|
426
|
+
color: #ffc107;
|
|
427
|
+
}}
|
|
428
|
+
|
|
429
|
+
.badge-low {{
|
|
430
|
+
background-color: rgba(220, 53, 69, 0.2);
|
|
431
|
+
color: #dc3545;
|
|
432
|
+
}}
|
|
433
|
+
|
|
434
|
+
.badge-n\\/a {{
|
|
435
|
+
background-color: rgba(108, 117, 125, 0.2);
|
|
436
|
+
color: #6c757d;
|
|
437
|
+
}}
|
|
438
|
+
|
|
439
|
+
/* Search box styling */
|
|
440
|
+
#feature-search {{
|
|
441
|
+
width: 100%;
|
|
442
|
+
padding: 10px;
|
|
443
|
+
font-size: 16px;
|
|
444
|
+
border: 1px solid {border_color};
|
|
445
|
+
border-radius: 4px;
|
|
446
|
+
margin-bottom: 15px;
|
|
447
|
+
background-color: {bg_color};
|
|
448
|
+
color: {text_color};
|
|
449
|
+
}}
|
|
450
|
+
|
|
451
|
+
#feature-search:focus {{
|
|
452
|
+
outline: none;
|
|
453
|
+
border-color: #1f77b4;
|
|
454
|
+
box-shadow: 0 0 5px rgba(31, 119, 180, 0.3);
|
|
455
|
+
}}
|
|
456
|
+
|
|
457
|
+
/* Table zebra striping */
|
|
458
|
+
.feature-table tbody tr:nth-child(even) {{
|
|
459
|
+
background-color: {"rgba(255,255,255,0.02)" if self.theme == "dark" else "rgba(0,0,0,0.02)"};
|
|
460
|
+
}}
|
|
461
|
+
|
|
462
|
+
.feature-table tbody tr:nth-child(odd) {{
|
|
463
|
+
background-color: transparent;
|
|
464
|
+
}}
|
|
465
|
+
|
|
466
|
+
/* Low agreement highlighting */
|
|
467
|
+
.feature-table tbody tr.low-agreement {{
|
|
468
|
+
background-color: {"rgba(255,200,100,0.1)" if self.theme == "dark" else "rgba(255,200,100,0.15)"} !important;
|
|
469
|
+
border-left: 3px solid #ff9800;
|
|
470
|
+
}}
|
|
471
|
+
|
|
472
|
+
/* Info icon tooltips */
|
|
473
|
+
.info-icon {{
|
|
474
|
+
display: inline-block;
|
|
475
|
+
width: 16px;
|
|
476
|
+
height: 16px;
|
|
477
|
+
line-height: 16px;
|
|
478
|
+
text-align: center;
|
|
479
|
+
border-radius: 50%;
|
|
480
|
+
background-color: #1f77b4;
|
|
481
|
+
color: white;
|
|
482
|
+
font-size: 12px;
|
|
483
|
+
cursor: help;
|
|
484
|
+
margin-left: 5px;
|
|
485
|
+
}}
|
|
486
|
+
|
|
487
|
+
/* Improved tab navigation */
|
|
488
|
+
.tab-navigation {{
|
|
489
|
+
border-bottom: 2px solid {border_color};
|
|
490
|
+
margin-bottom: 30px;
|
|
491
|
+
}}
|
|
492
|
+
</style>
|
|
493
|
+
"""
|
|
494
|
+
|
|
495
|
+
def _get_base_scripts(self) -> str:
|
|
496
|
+
"""Get base JavaScript for interactivity."""
|
|
497
|
+
return """
|
|
498
|
+
<script>
|
|
499
|
+
function switchTab(event, sectionId) {
|
|
500
|
+
// Hide all tab contents
|
|
501
|
+
const contents = document.getElementsByClassName('tab-content');
|
|
502
|
+
for (let content of contents) {
|
|
503
|
+
content.classList.remove('active');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Deactivate all tab buttons
|
|
507
|
+
const buttons = document.getElementsByClassName('tab-button');
|
|
508
|
+
for (let button of buttons) {
|
|
509
|
+
button.classList.remove('active');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Show selected tab
|
|
513
|
+
document.getElementById(sectionId).classList.add('active');
|
|
514
|
+
event.currentTarget.classList.add('active');
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Plotly responsive resizing
|
|
518
|
+
window.addEventListener('resize', function() {
|
|
519
|
+
const plots = document.querySelectorAll('.js-plotly-plot');
|
|
520
|
+
plots.forEach(plot => {
|
|
521
|
+
Plotly.Plots.resize(plot);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// Table sorting functionality
|
|
526
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
527
|
+
const table = document.getElementById('feature-importance-table');
|
|
528
|
+
if (!table) return;
|
|
529
|
+
|
|
530
|
+
const headers = table.querySelectorAll('thead th');
|
|
531
|
+
let sortDirection = {}; // Track sort direction for each column
|
|
532
|
+
|
|
533
|
+
headers.forEach((header, colIndex) => {
|
|
534
|
+
header.style.cursor = 'pointer';
|
|
535
|
+
header.style.userSelect = 'none';
|
|
536
|
+
sortDirection[colIndex] = 1; // 1 for ascending, -1 for descending
|
|
537
|
+
|
|
538
|
+
header.addEventListener('click', function() {
|
|
539
|
+
const tbody = table.querySelector('tbody');
|
|
540
|
+
const rows = Array.from(tbody.querySelectorAll('tr'));
|
|
541
|
+
|
|
542
|
+
// Sort rows
|
|
543
|
+
rows.sort((a, b) => {
|
|
544
|
+
let aValue = a.cells[colIndex].innerText.trim();
|
|
545
|
+
let bValue = b.cells[colIndex].innerText.trim();
|
|
546
|
+
|
|
547
|
+
// Handle different data types
|
|
548
|
+
// Try parsing as number first
|
|
549
|
+
const aNum = parseFloat(aValue.replace('%', '').replace(',', ''));
|
|
550
|
+
const bNum = parseFloat(bValue.replace('%', '').replace(',', ''));
|
|
551
|
+
|
|
552
|
+
if (!isNaN(aNum) && !isNaN(bNum)) {
|
|
553
|
+
return (aNum - bNum) * sortDirection[colIndex];
|
|
554
|
+
} else {
|
|
555
|
+
// String comparison
|
|
556
|
+
return aValue.localeCompare(bValue) * sortDirection[colIndex];
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// Clear and repopulate tbody
|
|
561
|
+
tbody.innerHTML = '';
|
|
562
|
+
rows.forEach(row => tbody.appendChild(row));
|
|
563
|
+
|
|
564
|
+
// Toggle sort direction for next click
|
|
565
|
+
sortDirection[colIndex] *= -1;
|
|
566
|
+
|
|
567
|
+
// Visual indicator
|
|
568
|
+
headers.forEach(h => h.style.opacity = '0.7');
|
|
569
|
+
header.style.opacity = '1.0';
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
</script>
|
|
574
|
+
"""
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
__all__ = [
|
|
578
|
+
"THEMES",
|
|
579
|
+
"get_theme",
|
|
580
|
+
"BaseDashboard",
|
|
581
|
+
"DashboardSection",
|
|
582
|
+
]
|