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.
Files changed (242) hide show
  1. ml4t/diagnostic/AGENT.md +25 -0
  2. ml4t/diagnostic/__init__.py +166 -0
  3. ml4t/diagnostic/backends/__init__.py +10 -0
  4. ml4t/diagnostic/backends/adapter.py +192 -0
  5. ml4t/diagnostic/backends/polars_backend.py +899 -0
  6. ml4t/diagnostic/caching/__init__.py +40 -0
  7. ml4t/diagnostic/caching/cache.py +331 -0
  8. ml4t/diagnostic/caching/decorators.py +131 -0
  9. ml4t/diagnostic/caching/smart_cache.py +339 -0
  10. ml4t/diagnostic/config/AGENT.md +24 -0
  11. ml4t/diagnostic/config/README.md +267 -0
  12. ml4t/diagnostic/config/__init__.py +219 -0
  13. ml4t/diagnostic/config/barrier_config.py +277 -0
  14. ml4t/diagnostic/config/base.py +301 -0
  15. ml4t/diagnostic/config/event_config.py +148 -0
  16. ml4t/diagnostic/config/feature_config.py +404 -0
  17. ml4t/diagnostic/config/multi_signal_config.py +55 -0
  18. ml4t/diagnostic/config/portfolio_config.py +215 -0
  19. ml4t/diagnostic/config/report_config.py +391 -0
  20. ml4t/diagnostic/config/sharpe_config.py +202 -0
  21. ml4t/diagnostic/config/signal_config.py +206 -0
  22. ml4t/diagnostic/config/trade_analysis_config.py +310 -0
  23. ml4t/diagnostic/config/validation.py +279 -0
  24. ml4t/diagnostic/core/__init__.py +29 -0
  25. ml4t/diagnostic/core/numba_utils.py +315 -0
  26. ml4t/diagnostic/core/purging.py +372 -0
  27. ml4t/diagnostic/core/sampling.py +471 -0
  28. ml4t/diagnostic/errors/__init__.py +205 -0
  29. ml4t/diagnostic/evaluation/AGENT.md +26 -0
  30. ml4t/diagnostic/evaluation/__init__.py +437 -0
  31. ml4t/diagnostic/evaluation/autocorrelation.py +531 -0
  32. ml4t/diagnostic/evaluation/barrier_analysis.py +1050 -0
  33. ml4t/diagnostic/evaluation/binary_metrics.py +910 -0
  34. ml4t/diagnostic/evaluation/dashboard.py +715 -0
  35. ml4t/diagnostic/evaluation/diagnostic_plots.py +1037 -0
  36. ml4t/diagnostic/evaluation/distribution/__init__.py +499 -0
  37. ml4t/diagnostic/evaluation/distribution/moments.py +299 -0
  38. ml4t/diagnostic/evaluation/distribution/tails.py +777 -0
  39. ml4t/diagnostic/evaluation/distribution/tests.py +470 -0
  40. ml4t/diagnostic/evaluation/drift/__init__.py +139 -0
  41. ml4t/diagnostic/evaluation/drift/analysis.py +432 -0
  42. ml4t/diagnostic/evaluation/drift/domain_classifier.py +517 -0
  43. ml4t/diagnostic/evaluation/drift/population_stability_index.py +310 -0
  44. ml4t/diagnostic/evaluation/drift/wasserstein.py +388 -0
  45. ml4t/diagnostic/evaluation/event_analysis.py +647 -0
  46. ml4t/diagnostic/evaluation/excursion.py +390 -0
  47. ml4t/diagnostic/evaluation/feature_diagnostics.py +873 -0
  48. ml4t/diagnostic/evaluation/feature_outcome.py +666 -0
  49. ml4t/diagnostic/evaluation/framework.py +935 -0
  50. ml4t/diagnostic/evaluation/metric_registry.py +255 -0
  51. ml4t/diagnostic/evaluation/metrics/AGENT.md +23 -0
  52. ml4t/diagnostic/evaluation/metrics/__init__.py +133 -0
  53. ml4t/diagnostic/evaluation/metrics/basic.py +160 -0
  54. ml4t/diagnostic/evaluation/metrics/conditional_ic.py +469 -0
  55. ml4t/diagnostic/evaluation/metrics/feature_outcome.py +475 -0
  56. ml4t/diagnostic/evaluation/metrics/ic_statistics.py +446 -0
  57. ml4t/diagnostic/evaluation/metrics/importance_analysis.py +338 -0
  58. ml4t/diagnostic/evaluation/metrics/importance_classical.py +375 -0
  59. ml4t/diagnostic/evaluation/metrics/importance_mda.py +371 -0
  60. ml4t/diagnostic/evaluation/metrics/importance_shap.py +715 -0
  61. ml4t/diagnostic/evaluation/metrics/information_coefficient.py +527 -0
  62. ml4t/diagnostic/evaluation/metrics/interactions.py +772 -0
  63. ml4t/diagnostic/evaluation/metrics/monotonicity.py +226 -0
  64. ml4t/diagnostic/evaluation/metrics/risk_adjusted.py +324 -0
  65. ml4t/diagnostic/evaluation/multi_signal.py +550 -0
  66. ml4t/diagnostic/evaluation/portfolio_analysis/__init__.py +83 -0
  67. ml4t/diagnostic/evaluation/portfolio_analysis/analysis.py +734 -0
  68. ml4t/diagnostic/evaluation/portfolio_analysis/metrics.py +589 -0
  69. ml4t/diagnostic/evaluation/portfolio_analysis/results.py +334 -0
  70. ml4t/diagnostic/evaluation/report_generation.py +824 -0
  71. ml4t/diagnostic/evaluation/signal_selector.py +452 -0
  72. ml4t/diagnostic/evaluation/stat_registry.py +139 -0
  73. ml4t/diagnostic/evaluation/stationarity/__init__.py +97 -0
  74. ml4t/diagnostic/evaluation/stationarity/analysis.py +518 -0
  75. ml4t/diagnostic/evaluation/stationarity/augmented_dickey_fuller.py +296 -0
  76. ml4t/diagnostic/evaluation/stationarity/kpss_test.py +308 -0
  77. ml4t/diagnostic/evaluation/stationarity/phillips_perron.py +365 -0
  78. ml4t/diagnostic/evaluation/stats/AGENT.md +43 -0
  79. ml4t/diagnostic/evaluation/stats/__init__.py +191 -0
  80. ml4t/diagnostic/evaluation/stats/backtest_overfitting.py +219 -0
  81. ml4t/diagnostic/evaluation/stats/bootstrap.py +228 -0
  82. ml4t/diagnostic/evaluation/stats/deflated_sharpe_ratio.py +591 -0
  83. ml4t/diagnostic/evaluation/stats/false_discovery_rate.py +295 -0
  84. ml4t/diagnostic/evaluation/stats/hac_standard_errors.py +108 -0
  85. ml4t/diagnostic/evaluation/stats/minimum_track_record.py +408 -0
  86. ml4t/diagnostic/evaluation/stats/moments.py +164 -0
  87. ml4t/diagnostic/evaluation/stats/rademacher_adjustment.py +436 -0
  88. ml4t/diagnostic/evaluation/stats/reality_check.py +155 -0
  89. ml4t/diagnostic/evaluation/stats/sharpe_inference.py +219 -0
  90. ml4t/diagnostic/evaluation/themes.py +330 -0
  91. ml4t/diagnostic/evaluation/threshold_analysis.py +957 -0
  92. ml4t/diagnostic/evaluation/trade_analysis.py +1136 -0
  93. ml4t/diagnostic/evaluation/trade_dashboard/__init__.py +32 -0
  94. ml4t/diagnostic/evaluation/trade_dashboard/app.py +315 -0
  95. ml4t/diagnostic/evaluation/trade_dashboard/export/__init__.py +18 -0
  96. ml4t/diagnostic/evaluation/trade_dashboard/export/csv.py +82 -0
  97. ml4t/diagnostic/evaluation/trade_dashboard/export/html.py +276 -0
  98. ml4t/diagnostic/evaluation/trade_dashboard/io.py +166 -0
  99. ml4t/diagnostic/evaluation/trade_dashboard/normalize.py +304 -0
  100. ml4t/diagnostic/evaluation/trade_dashboard/stats.py +386 -0
  101. ml4t/diagnostic/evaluation/trade_dashboard/style.py +79 -0
  102. ml4t/diagnostic/evaluation/trade_dashboard/tabs/__init__.py +21 -0
  103. ml4t/diagnostic/evaluation/trade_dashboard/tabs/patterns.py +354 -0
  104. ml4t/diagnostic/evaluation/trade_dashboard/tabs/shap_analysis.py +280 -0
  105. ml4t/diagnostic/evaluation/trade_dashboard/tabs/stat_validation.py +186 -0
  106. ml4t/diagnostic/evaluation/trade_dashboard/tabs/worst_trades.py +236 -0
  107. ml4t/diagnostic/evaluation/trade_dashboard/types.py +129 -0
  108. ml4t/diagnostic/evaluation/trade_shap/__init__.py +102 -0
  109. ml4t/diagnostic/evaluation/trade_shap/alignment.py +188 -0
  110. ml4t/diagnostic/evaluation/trade_shap/characterize.py +413 -0
  111. ml4t/diagnostic/evaluation/trade_shap/cluster.py +302 -0
  112. ml4t/diagnostic/evaluation/trade_shap/explain.py +208 -0
  113. ml4t/diagnostic/evaluation/trade_shap/hypotheses/__init__.py +23 -0
  114. ml4t/diagnostic/evaluation/trade_shap/hypotheses/generator.py +290 -0
  115. ml4t/diagnostic/evaluation/trade_shap/hypotheses/matcher.py +251 -0
  116. ml4t/diagnostic/evaluation/trade_shap/hypotheses/templates.yaml +467 -0
  117. ml4t/diagnostic/evaluation/trade_shap/models.py +386 -0
  118. ml4t/diagnostic/evaluation/trade_shap/normalize.py +116 -0
  119. ml4t/diagnostic/evaluation/trade_shap/pipeline.py +263 -0
  120. ml4t/diagnostic/evaluation/trade_shap_dashboard.py +283 -0
  121. ml4t/diagnostic/evaluation/trade_shap_diagnostics.py +588 -0
  122. ml4t/diagnostic/evaluation/validated_cv.py +535 -0
  123. ml4t/diagnostic/evaluation/visualization.py +1050 -0
  124. ml4t/diagnostic/evaluation/volatility/__init__.py +45 -0
  125. ml4t/diagnostic/evaluation/volatility/analysis.py +351 -0
  126. ml4t/diagnostic/evaluation/volatility/arch.py +258 -0
  127. ml4t/diagnostic/evaluation/volatility/garch.py +460 -0
  128. ml4t/diagnostic/integration/__init__.py +48 -0
  129. ml4t/diagnostic/integration/backtest_contract.py +671 -0
  130. ml4t/diagnostic/integration/data_contract.py +316 -0
  131. ml4t/diagnostic/integration/engineer_contract.py +226 -0
  132. ml4t/diagnostic/logging/__init__.py +77 -0
  133. ml4t/diagnostic/logging/logger.py +245 -0
  134. ml4t/diagnostic/logging/performance.py +234 -0
  135. ml4t/diagnostic/logging/progress.py +234 -0
  136. ml4t/diagnostic/logging/wandb.py +412 -0
  137. ml4t/diagnostic/metrics/__init__.py +9 -0
  138. ml4t/diagnostic/metrics/percentiles.py +128 -0
  139. ml4t/diagnostic/py.typed +1 -0
  140. ml4t/diagnostic/reporting/__init__.py +43 -0
  141. ml4t/diagnostic/reporting/base.py +130 -0
  142. ml4t/diagnostic/reporting/html_renderer.py +275 -0
  143. ml4t/diagnostic/reporting/json_renderer.py +51 -0
  144. ml4t/diagnostic/reporting/markdown_renderer.py +117 -0
  145. ml4t/diagnostic/results/AGENT.md +24 -0
  146. ml4t/diagnostic/results/__init__.py +105 -0
  147. ml4t/diagnostic/results/barrier_results/__init__.py +36 -0
  148. ml4t/diagnostic/results/barrier_results/hit_rate.py +304 -0
  149. ml4t/diagnostic/results/barrier_results/precision_recall.py +266 -0
  150. ml4t/diagnostic/results/barrier_results/profit_factor.py +297 -0
  151. ml4t/diagnostic/results/barrier_results/tearsheet.py +397 -0
  152. ml4t/diagnostic/results/barrier_results/time_to_target.py +305 -0
  153. ml4t/diagnostic/results/barrier_results/validation.py +38 -0
  154. ml4t/diagnostic/results/base.py +177 -0
  155. ml4t/diagnostic/results/event_results.py +349 -0
  156. ml4t/diagnostic/results/feature_results.py +787 -0
  157. ml4t/diagnostic/results/multi_signal_results.py +431 -0
  158. ml4t/diagnostic/results/portfolio_results.py +281 -0
  159. ml4t/diagnostic/results/sharpe_results.py +448 -0
  160. ml4t/diagnostic/results/signal_results/__init__.py +74 -0
  161. ml4t/diagnostic/results/signal_results/ic.py +581 -0
  162. ml4t/diagnostic/results/signal_results/irtc.py +110 -0
  163. ml4t/diagnostic/results/signal_results/quantile.py +392 -0
  164. ml4t/diagnostic/results/signal_results/tearsheet.py +456 -0
  165. ml4t/diagnostic/results/signal_results/turnover.py +213 -0
  166. ml4t/diagnostic/results/signal_results/validation.py +147 -0
  167. ml4t/diagnostic/signal/AGENT.md +17 -0
  168. ml4t/diagnostic/signal/__init__.py +69 -0
  169. ml4t/diagnostic/signal/_report.py +152 -0
  170. ml4t/diagnostic/signal/_utils.py +261 -0
  171. ml4t/diagnostic/signal/core.py +275 -0
  172. ml4t/diagnostic/signal/quantile.py +148 -0
  173. ml4t/diagnostic/signal/result.py +214 -0
  174. ml4t/diagnostic/signal/signal_ic.py +129 -0
  175. ml4t/diagnostic/signal/turnover.py +182 -0
  176. ml4t/diagnostic/splitters/AGENT.md +19 -0
  177. ml4t/diagnostic/splitters/__init__.py +36 -0
  178. ml4t/diagnostic/splitters/base.py +501 -0
  179. ml4t/diagnostic/splitters/calendar.py +421 -0
  180. ml4t/diagnostic/splitters/calendar_config.py +91 -0
  181. ml4t/diagnostic/splitters/combinatorial.py +1064 -0
  182. ml4t/diagnostic/splitters/config.py +322 -0
  183. ml4t/diagnostic/splitters/cpcv/__init__.py +57 -0
  184. ml4t/diagnostic/splitters/cpcv/combinations.py +119 -0
  185. ml4t/diagnostic/splitters/cpcv/partitioning.py +263 -0
  186. ml4t/diagnostic/splitters/cpcv/purge_engine.py +379 -0
  187. ml4t/diagnostic/splitters/cpcv/windows.py +190 -0
  188. ml4t/diagnostic/splitters/group_isolation.py +329 -0
  189. ml4t/diagnostic/splitters/persistence.py +316 -0
  190. ml4t/diagnostic/splitters/utils.py +207 -0
  191. ml4t/diagnostic/splitters/walk_forward.py +757 -0
  192. ml4t/diagnostic/utils/__init__.py +42 -0
  193. ml4t/diagnostic/utils/config.py +542 -0
  194. ml4t/diagnostic/utils/dependencies.py +318 -0
  195. ml4t/diagnostic/utils/sessions.py +127 -0
  196. ml4t/diagnostic/validation/__init__.py +54 -0
  197. ml4t/diagnostic/validation/dataframe.py +274 -0
  198. ml4t/diagnostic/validation/returns.py +280 -0
  199. ml4t/diagnostic/validation/timeseries.py +299 -0
  200. ml4t/diagnostic/visualization/AGENT.md +19 -0
  201. ml4t/diagnostic/visualization/__init__.py +223 -0
  202. ml4t/diagnostic/visualization/backtest/__init__.py +98 -0
  203. ml4t/diagnostic/visualization/backtest/cost_attribution.py +762 -0
  204. ml4t/diagnostic/visualization/backtest/executive_summary.py +895 -0
  205. ml4t/diagnostic/visualization/backtest/interactive_controls.py +673 -0
  206. ml4t/diagnostic/visualization/backtest/statistical_validity.py +874 -0
  207. ml4t/diagnostic/visualization/backtest/tearsheet.py +565 -0
  208. ml4t/diagnostic/visualization/backtest/template_system.py +373 -0
  209. ml4t/diagnostic/visualization/backtest/trade_plots.py +1172 -0
  210. ml4t/diagnostic/visualization/barrier_plots.py +782 -0
  211. ml4t/diagnostic/visualization/core.py +1060 -0
  212. ml4t/diagnostic/visualization/dashboards/__init__.py +36 -0
  213. ml4t/diagnostic/visualization/dashboards/base.py +582 -0
  214. ml4t/diagnostic/visualization/dashboards/importance.py +801 -0
  215. ml4t/diagnostic/visualization/dashboards/interaction.py +263 -0
  216. ml4t/diagnostic/visualization/dashboards.py +43 -0
  217. ml4t/diagnostic/visualization/data_extraction/__init__.py +48 -0
  218. ml4t/diagnostic/visualization/data_extraction/importance.py +649 -0
  219. ml4t/diagnostic/visualization/data_extraction/interaction.py +504 -0
  220. ml4t/diagnostic/visualization/data_extraction/types.py +113 -0
  221. ml4t/diagnostic/visualization/data_extraction/validation.py +66 -0
  222. ml4t/diagnostic/visualization/feature_plots.py +888 -0
  223. ml4t/diagnostic/visualization/interaction_plots.py +618 -0
  224. ml4t/diagnostic/visualization/portfolio/__init__.py +41 -0
  225. ml4t/diagnostic/visualization/portfolio/dashboard.py +514 -0
  226. ml4t/diagnostic/visualization/portfolio/drawdown_plots.py +341 -0
  227. ml4t/diagnostic/visualization/portfolio/returns_plots.py +487 -0
  228. ml4t/diagnostic/visualization/portfolio/risk_plots.py +301 -0
  229. ml4t/diagnostic/visualization/report_generation.py +1343 -0
  230. ml4t/diagnostic/visualization/signal/__init__.py +103 -0
  231. ml4t/diagnostic/visualization/signal/dashboard.py +911 -0
  232. ml4t/diagnostic/visualization/signal/event_plots.py +514 -0
  233. ml4t/diagnostic/visualization/signal/ic_plots.py +635 -0
  234. ml4t/diagnostic/visualization/signal/multi_signal_dashboard.py +974 -0
  235. ml4t/diagnostic/visualization/signal/multi_signal_plots.py +603 -0
  236. ml4t/diagnostic/visualization/signal/quantile_plots.py +625 -0
  237. ml4t/diagnostic/visualization/signal/turnover_plots.py +400 -0
  238. ml4t/diagnostic/visualization/trade_shap/__init__.py +90 -0
  239. ml4t_diagnostic-0.1.0a1.dist-info/METADATA +1044 -0
  240. ml4t_diagnostic-0.1.0a1.dist-info/RECORD +242 -0
  241. ml4t_diagnostic-0.1.0a1.dist-info/WHEEL +4 -0
  242. 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
+ ]