datapilot-kit 0.3.0rc1__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 (44) hide show
  1. datapilot/__init__.py +11 -0
  2. datapilot/analysis/__init__.py +3 -0
  3. datapilot/analysis/correlation.py +58 -0
  4. datapilot/analysis/datatype.py +48 -0
  5. datapilot/analysis/duplicate.py +40 -0
  6. datapilot/analysis/health.py +163 -0
  7. datapilot/analysis/insights.py +101 -0
  8. datapilot/analysis/missing.py +56 -0
  9. datapilot/analysis/models.py +108 -0
  10. datapilot/analysis/outliers.py +85 -0
  11. datapilot/analysis/statistics.py +46 -0
  12. datapilot/analysis/summary.py +36 -0
  13. datapilot/api/__init__.py +7 -0
  14. datapilot/api/analyze.py +16 -0
  15. datapilot/assets/style.css +1081 -0
  16. datapilot/cli/__init__.py +3 -0
  17. datapilot/core/__init__.py +3 -0
  18. datapilot/core/loader.py +72 -0
  19. datapilot/core/report.py +104 -0
  20. datapilot/interpretation/__init__.py +3 -0
  21. datapilot/llm/__init__.py +0 -0
  22. datapilot/recommendation/__init__.py +0 -0
  23. datapilot/reporting/__init__.py +3 -0
  24. datapilot/reporting/fragments.py +329 -0
  25. datapilot/reporting/html.py +51 -0
  26. datapilot/reporting/placeholders.py +133 -0
  27. datapilot/reporting/renderer.py +57 -0
  28. datapilot/templates/report.html +528 -0
  29. datapilot/ui/__init__.py +0 -0
  30. datapilot/ui/cards.py +70 -0
  31. datapilot/ui/console.py +12 -0
  32. datapilot/ui/dashboard.py +90 -0
  33. datapilot/ui/panels.py +16 -0
  34. datapilot/ui/renderer.py +21 -0
  35. datapilot/ui/sections.py +89 -0
  36. datapilot/ui/tables.py +21 -0
  37. datapilot/ui/theme.py +20 -0
  38. datapilot/utils/__init__.py +3 -0
  39. datapilot/visualization/__init__.py +0 -0
  40. datapilot_kit-0.3.0rc1.dist-info/METADATA +103 -0
  41. datapilot_kit-0.3.0rc1.dist-info/RECORD +44 -0
  42. datapilot_kit-0.3.0rc1.dist-info/WHEEL +5 -0
  43. datapilot_kit-0.3.0rc1.dist-info/licenses/LICENSE +21 -0
  44. datapilot_kit-0.3.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,57 @@
1
+ """
2
+ HTML renderer for Datapilot reports.
3
+ """
4
+
5
+ from pathlib import Path
6
+
7
+ from datapilot.core.report import Report
8
+
9
+ from .placeholders import build_placeholders
10
+
11
+
12
+ class HTMLRenderer:
13
+ """
14
+ Render Datapilot reports using HTML templates.
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ template_path: str | Path,
20
+ ) -> None:
21
+ """
22
+ Initialize the renderer.
23
+ """
24
+
25
+ self._template_path = Path(
26
+ template_path,
27
+ )
28
+
29
+ def render(
30
+ self,
31
+ report: Report,
32
+ output_path: str | Path,
33
+ ) -> None:
34
+ """
35
+ Render the HTML report.
36
+ """
37
+
38
+ template = self._template_path.read_text(
39
+ encoding="utf-8",
40
+ )
41
+
42
+ replacements = build_placeholders(
43
+ report,
44
+ )
45
+
46
+ html = template
47
+
48
+ for placeholder, value in replacements.items():
49
+ html = html.replace(
50
+ placeholder,
51
+ value,
52
+ )
53
+
54
+ Path(output_path).write_text(
55
+ html,
56
+ encoding="utf-8",
57
+ )
@@ -0,0 +1,528 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Datapilot Report · {{DATASET_NAME}}</title>
7
+ <meta name="generator" content="Datapilot {{DATAPILOT_VERSION}}">
8
+ <link rel="stylesheet" href="style.css">
9
+ </head>
10
+ <body>
11
+
12
+ <!-- ==========================================================================
13
+ HEADER
14
+ ========================================================================== -->
15
+ <header class="dp-header">
16
+ <div class="dp-brand">
17
+ <div class="dp-logo">
18
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
19
+ <path d="M4 18V10.5M9.5 18V6M15 18V13M20 18V8.5" stroke="white" stroke-width="2.2" stroke-linecap="round"/>
20
+ </svg>
21
+ </div>
22
+ <div class="dp-brand-text">
23
+ <div class="dp-name">Datapilot</div>
24
+ <div class="dp-tagline">Data Quality &amp; EDA Report</div>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="dp-header-meta">
29
+ <div class="meta-item">
30
+ <div class="meta-label">Dataset</div>
31
+ <div class="meta-value">{{DATASET_NAME}}</div>
32
+ </div>
33
+ <div class="meta-item">
34
+ <div class="meta-label">Generated</div>
35
+ <div class="meta-value">{{REPORT_GENERATED_AT}}</div>
36
+ </div>
37
+ <div class="meta-item">
38
+ <div class="meta-label">Version</div>
39
+ <div class="meta-value">{{DATAPILOT_VERSION}}</div>
40
+ </div>
41
+ </div>
42
+ </header>
43
+
44
+ <div class="dp-header-title">
45
+ <h1>Data Quality Report — <span class="dp-dataset-name">{{DATASET_NAME}}</span></h1>
46
+ <p class="section-subtitle">{{REPORT_SUMMARY}}</p>
47
+ </div>
48
+
49
+ <!-- ==========================================================================
50
+ SHELL: NAVIGATION RAIL + MAIN CONTENT
51
+ To add a future section (e.g. Distribution Analysis), add one <li> here
52
+ and one .report-section below — nothing else in the page needs to change.
53
+ ========================================================================== -->
54
+ <div class="dp-shell">
55
+
56
+ <nav class="dp-rail" aria-label="Report sections">
57
+ <div class="dp-rail-heading">On this report</div>
58
+ <ul class="dp-rail-list">
59
+ <li><a href="#executive-dashboard"><span class="rail-index">01</span>Executive Dashboard</a></li>
60
+ <li><a href="#dataset-overview"><span class="rail-index">02</span>Dataset Overview</a></li>
61
+ <li><a href="#dataset-health"><span class="rail-index">03</span>Dataset Health</a></li>
62
+ <li><a href="#missing-values"><span class="rail-index">04</span>Missing Values</a></li>
63
+ <li><a href="#duplicate-analysis"><span class="rail-index">05</span>Duplicate Analysis</a></li>
64
+ <li><a href="#statistics"><span class="rail-index">06</span>Statistics</a></li>
65
+ <li><a href="#outlier-analysis"><span class="rail-index">07</span>Outlier Analysis</a></li>
66
+ <li><a href="#correlation-analysis"><span class="rail-index">08</span>Correlation Analysis</a></li>
67
+ <li><a href="#insights"><span class="rail-index">09</span>Insights</a></li>
68
+ <li><a href="#recommendations"><span class="rail-index">10</span>Recommendations</a></li>
69
+ </ul>
70
+
71
+ <!--
72
+ FUTURE SECTIONS — uncomment and extend as each capability ships.
73
+ Keep the same <li><a href="#id"><span class="rail-index">NN</span>Label</a></li> pattern.
74
+
75
+ <div class="dp-rail-divider"></div>
76
+ <div class="dp-rail-group-label">Coming soon</div>
77
+ <ul class="dp-rail-list">
78
+ <li><a href="#distribution-analysis"><span class="rail-index">11</span>Distribution Analysis</a></li>
79
+ <li><a href="#cardinality-analysis"><span class="rail-index">12</span>Cardinality Analysis</a></li>
80
+ <li><a href="#ml-readiness"><span class="rail-index">13</span>ML Readiness</a></li>
81
+ <li><a href="#data-drift"><span class="rail-index">14</span>Data Drift</a></li>
82
+ <li><a href="#feature-importance"><span class="rail-index">15</span>Feature Importance</a></li>
83
+ <li><a href="#export-metadata"><span class="rail-index">16</span>Export Metadata</a></li>
84
+ </ul>
85
+ -->
86
+ </nav>
87
+
88
+ <main class="dp-main">
89
+
90
+ <!-- ======================================================================
91
+ 02. EXECUTIVE DASHBOARD
92
+ ====================================================================== -->
93
+ <section class="report-section" id="executive-dashboard">
94
+ <div class="report-section-head">
95
+ <div class="heading-block">
96
+ <div class="eyebrow">Overview</div>
97
+ <h2 class="section-title">Executive Dashboard</h2>
98
+ <p class="section-subtitle">A snapshot of overall dataset health at a glance.</p>
99
+ </div>
100
+ </div>
101
+
102
+ <div class="kpi-grid">
103
+ <!-- Health score card spans the gauge; class kpi-card--good/warn/bad set by Python per score band -->
104
+ <div class="kpi-card {{HEALTH_SCORE_SEVERITY_CLASS}}">
105
+ <div class="kpi-label">Health Score</div>
106
+ <div class="kpi-value">{{HEALTH_SCORE}}<span class="kpi-unit">/100</span></div>
107
+ <div class="kpi-sub {{HEALTH_SCORE_SEVERITY_CLASS}}">{{HEALTH_STATUS}}</div>
108
+ </div>
109
+
110
+ <div class="kpi-card {{HEALTH_SCORE_SEVERITY_CLASS}}">
111
+ <div class="kpi-label">Grade</div>
112
+ <div class="kpi-value">{{GRADE}}</div>
113
+ <div class="kpi-sub">Overall data quality grade</div>
114
+ </div>
115
+
116
+ <div class="kpi-card">
117
+ <div class="kpi-label">Rows</div>
118
+ <div class="kpi-value">{{ROWS}}</div>
119
+ <div class="kpi-sub">Total records</div>
120
+ </div>
121
+
122
+ <div class="kpi-card">
123
+ <div class="kpi-label">Columns</div>
124
+ <div class="kpi-value">{{COLUMNS}}</div>
125
+ <div class="kpi-sub">Total fields</div>
126
+ </div>
127
+
128
+ <div class="kpi-card {{MISSING_VALUES_SEVERITY_CLASS}}">
129
+ <div class="kpi-label">Missing Values</div>
130
+ <div class="kpi-value">{{MISSING_VALUES_COUNT}}</div>
131
+ <div class="kpi-sub {{MISSING_VALUES_SEVERITY_CLASS}}">{{MISSING_VALUES_PERCENT}}% of all cells</div>
132
+ </div>
133
+
134
+ <div class="kpi-card {{DUPLICATE_ROWS_SEVERITY_CLASS}}">
135
+ <div class="kpi-label">Duplicate Rows</div>
136
+ <div class="kpi-value">{{DUPLICATE_ROWS_COUNT}}</div>
137
+ <div class="kpi-sub {{DUPLICATE_ROWS_SEVERITY_CLASS}}">{{DUPLICATE_ROWS_PERCENT}}% of rows</div>
138
+ </div>
139
+ </div>
140
+ </section>
141
+
142
+ <!-- ======================================================================
143
+ 02. DATASET OVERVIEW
144
+ ====================================================================== -->
145
+ <section class="report-section" id="dataset-overview">
146
+ <div class="report-section-head">
147
+ <div class="heading-block">
148
+ <div class="eyebrow">Structure</div>
149
+ <h2 class="section-title">Dataset Overview</h2>
150
+ <p class="section-subtitle">Shape, memory footprint, and schema of {{DATASET_NAME}}.</p>
151
+ </div>
152
+ </div>
153
+
154
+ <div class="card card-pad">
155
+ <div class="definition-grid">
156
+ <div class="definition-item">
157
+ <div class="definition-label">Rows</div>
158
+ <div class="definition-value">{{ROWS}}</div>
159
+ </div>
160
+ <div class="definition-item">
161
+ <div class="definition-label">Columns</div>
162
+ <div class="definition-value">{{COLUMNS}}</div>
163
+ </div>
164
+ <div class="definition-item">
165
+ <div class="definition-label">Memory Usage</div>
166
+ <div class="definition-value">{{MEMORY_USAGE}}</div>
167
+ </div>
168
+ <div class="definition-item">
169
+ <div class="definition-label">File Format</div>
170
+ <div class="definition-value">{{FILE_FORMAT}}</div>
171
+ </div>
172
+ </div>
173
+
174
+ <div style="margin-top: var(--sp-6); padding-top: var(--sp-5); border-top: 1px solid var(--line-200);">
175
+ <div class="definition-label">Column Names</div>
176
+ <!-- Python injects one .column-chip per column into this block -->
177
+ <div class="column-chip-list">
178
+ {{COLUMN_NAME_CHIPS}}
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </section>
183
+
184
+ <!-- ======================================================================
185
+ 03. DATASET HEALTH
186
+ ====================================================================== -->
187
+ <section class="report-section" id="dataset-health">
188
+ <div class="report-section-head">
189
+ <div class="heading-block">
190
+ <div class="eyebrow">Diagnosis</div>
191
+ <h2 class="section-title">Dataset Health</h2>
192
+ <p class="section-subtitle">Composite score, key strengths, and where to focus first.</p>
193
+ </div>
194
+ </div>
195
+
196
+ <div class="card card-pad">
197
+ <div class="health-hero">
198
+ <!-- --score custom property drives the conic-gradient ring; gauge--good/warn/bad set by Python -->
199
+ <div class="gauge {{HEALTH_SCORE_SEVERITY_CLASS_GAUGE}}" style="--score: {{HEALTH_SCORE}};">
200
+ <div class="gauge-inner">
201
+ <div class="gauge-score">{{HEALTH_SCORE}}</div>
202
+ <div class="gauge-grade">Grade {{GRADE}}</div>
203
+ </div>
204
+ </div>
205
+
206
+ <div class="health-hero-body">
207
+ <div class="health-hero-top">
208
+ <div class="health-hero-title">{{HEALTH_HEADLINE}}</div>
209
+ <span class="badge badge--grade {{HEALTH_SCORE_SEVERITY_CLASS}}">{{HEALTH_STATUS}}</span>
210
+ </div>
211
+
212
+ <p class="section-subtitle mt-0" style="margin-bottom: var(--sp-3);">{{HEALTH_SUMMARY}}</p>
213
+
214
+ <div class="progress-row">
215
+ <span class="progress-label">Overall score</span>
216
+ <div class="progress-track">
217
+ <div class="progress-fill {{HEALTH_SCORE_SEVERITY_CLASS}}" style="width: {{HEALTH_SCORE}}%;"></div>
218
+ </div>
219
+ <span class="progress-value numeral">{{HEALTH_SCORE}}/100</span>
220
+ </div>
221
+ </div>
222
+ </div>
223
+
224
+ <div class="trait-columns">
225
+ <div>
226
+ <h4>Strengths</h4>
227
+ <!-- Python injects one .trait-item per strength -->
228
+ <div class="trait-list trait-list--strength">
229
+ {{STRENGTHS_LIST}}
230
+ </div>
231
+ </div>
232
+ <div>
233
+ <h4>Weaknesses</h4>
234
+ <!-- Python injects one .trait-item per weakness -->
235
+ <div class="trait-list trait-list--weakness">
236
+ {{WEAKNESSES_LIST}}
237
+ </div>
238
+ </div>
239
+ </div>
240
+ </div>
241
+ </section>
242
+
243
+ <!-- ======================================================================
244
+ 04. MISSING VALUES
245
+ ====================================================================== -->
246
+ <section class="report-section" id="missing-values">
247
+ <div class="report-section-head">
248
+ <div class="heading-block">
249
+ <div class="eyebrow">Completeness</div>
250
+ <h2 class="section-title">Missing Values</h2>
251
+ <p class="section-subtitle">Null counts and percentage completeness per column.</p>
252
+ </div>
253
+ <span class="section-tag">{{MISSING_COLUMNS_AFFECTED}} of {{COLUMNS}} columns affected</span>
254
+ </div>
255
+
256
+ <div class="table-wrap">
257
+ <table class="dp-table dp-table--zebra">
258
+ <thead>
259
+ <tr>
260
+ <th>Column</th>
261
+ <th class="num">Missing Count</th>
262
+ <th>Missing %</th>
263
+ <th class="num">Data Type</th>
264
+ <th>Severity</th>
265
+ </tr>
266
+ </thead>
267
+ <tbody>
268
+ <!--
269
+ {{MISSING_TABLE}}
270
+ Python renders one <tr> per column, e.g.:
271
+ <tr>
272
+ <td class="col-name">age</td>
273
+ <td class="num">128</td>
274
+ <td>
275
+ <div class="cell-bar">
276
+ <div class="cell-bar-track"><div class="cell-bar-fill sev-warn" style="width: 12%;"></div></div>
277
+ <span class="cell-bar-pct numeral">12.0%</span>
278
+ </div>
279
+ </td>
280
+ <td class="num">float64</td>
281
+ <td><span class="badge badge--warn">Moderate</span></td>
282
+ </tr>
283
+ -->
284
+ {{MISSING_TABLE}}
285
+ </tbody>
286
+ </table>
287
+ </div>
288
+ </section>
289
+
290
+ <!-- ======================================================================
291
+ 05. DUPLICATE ANALYSIS
292
+ ====================================================================== -->
293
+ <section class="report-section" id="duplicate-analysis">
294
+ <div class="report-section-head">
295
+ <div class="heading-block">
296
+ <div class="eyebrow">Uniqueness</div>
297
+ <h2 class="section-title">Duplicate Analysis</h2>
298
+ <p class="section-subtitle">Repeated records that may skew downstream analysis.</p>
299
+ </div>
300
+ </div>
301
+
302
+ <div class="pill-grid">
303
+ <div class="pill-card">
304
+ <div class="kpi-label">Duplicate Rows</div>
305
+ <div class="kpi-value {{DUPLICATE_ROWS_SEVERITY_CLASS}}">{{DUPLICATE_ROWS_COUNT}}</div>
306
+ </div>
307
+ <div class="pill-card">
308
+ <div class="kpi-label">Duplicate %</div>
309
+ <div class="kpi-value">{{DUPLICATE_ROWS_PERCENT}}%</div>
310
+ </div>
311
+ <div class="pill-card">
312
+ <div class="kpi-label">Unique Rows</div>
313
+ <div class="kpi-value">{{UNIQUE_ROWS_COUNT}}</div>
314
+ </div>
315
+ <div class="pill-card">
316
+ <div class="kpi-label">Duplicate Groups</div>
317
+ <div class="kpi-value">{{DUPLICATE_GROUPS_COUNT}}</div>
318
+ </div>
319
+ </div>
320
+ </section>
321
+
322
+ <!-- ======================================================================
323
+ 06. STATISTICS
324
+ ====================================================================== -->
325
+ <section class="report-section" id="statistics">
326
+ <div class="report-section-head">
327
+ <div class="heading-block">
328
+ <div class="eyebrow">Distribution</div>
329
+ <h2 class="section-title">Statistics</h2>
330
+ <p class="section-subtitle">Descriptive statistics for every numeric column.</p>
331
+ </div>
332
+ </div>
333
+
334
+ <div class="table-wrap">
335
+ <table class="dp-table dp-table--zebra">
336
+ <thead>
337
+ <tr>
338
+ <th>Column</th>
339
+ <th class="num">Count</th>
340
+ <th class="num">Mean</th>
341
+ <th class="num">Std</th>
342
+ <th class="num">Min</th>
343
+ <th class="num">25%</th>
344
+ <th class="num">50%</th>
345
+ <th class="num">75%</th>
346
+ <th class="num">Max</th>
347
+ </tr>
348
+ </thead>
349
+ <tbody>
350
+ <!--
351
+ {{STATISTICS_TABLE}}
352
+ Python renders one <tr> per numeric column with a <td class="col-name"> for the
353
+ column name and <td class="num numeral"> cells for each statistic.
354
+ -->
355
+ {{STATISTICS_TABLE}}
356
+ </tbody>
357
+ </table>
358
+ </div>
359
+ </section>
360
+
361
+ <!-- ======================================================================
362
+ 07. OUTLIER ANALYSIS
363
+ ====================================================================== -->
364
+ <section class="report-section" id="outlier-analysis">
365
+ <div class="report-section-head">
366
+ <div class="heading-block">
367
+ <div class="eyebrow">Anomalies</div>
368
+ <h2 class="section-title">Outlier Analysis</h2>
369
+ <p class="section-subtitle">Columns with values that fall outside expected bounds.</p>
370
+ </div>
371
+ </div>
372
+
373
+ <div class="outlier-grid">
374
+ <!--
375
+ {{OUTLIER_CARDS}}
376
+ Python renders one .outlier-card per flagged column, e.g.:
377
+ <div class="outlier-card">
378
+ <div class="outlier-card-head">
379
+ <span class="outlier-column">transaction_amount</span>
380
+ <span class="badge badge--warn">42 outliers</span>
381
+ </div>
382
+ <div class="outlier-stats">
383
+ <div><div class="outlier-stat-label">Lower Bound</div><div class="outlier-stat-value numeral">-120.50</div></div>
384
+ <div><div class="outlier-stat-label">Upper Bound</div><div class="outlier-stat-value numeral">890.25</div></div>
385
+ <div><div class="outlier-stat-label">Outlier %</div><div class="outlier-stat-value numeral">1.8%</div></div>
386
+ <div><div class="outlier-stat-label">Extreme Count</div><div class="outlier-stat-value numeral">6</div></div>
387
+ </div>
388
+ <div class="outlier-method">Method: IQR (1.5×)</div>
389
+ </div>
390
+ -->
391
+ {{OUTLIER_CARDS}}
392
+ </div>
393
+ </section>
394
+
395
+ <!-- ======================================================================
396
+ 08. CORRELATION ANALYSIS
397
+ ====================================================================== -->
398
+ <section class="report-section" id="correlation-analysis">
399
+ <div class="report-section-head">
400
+ <div class="heading-block">
401
+ <div class="eyebrow">Relationships</div>
402
+ <h2 class="section-title">Correlation Analysis</h2>
403
+ <p class="section-subtitle">Pairwise correlation strength across numeric columns.</p>
404
+ </div>
405
+ </div>
406
+
407
+ <div class="table-wrap">
408
+ <table class="dp-table">
409
+ <!--
410
+ {{CORRELATION_TABLE}}
411
+ Column names are only known at runtime, so Python owns the whole table body —
412
+ including the header row — as a single block. Emit one leading <tr> of <th>
413
+ cells (blank corner cell + one <th> per column), then one <tr> per row with a
414
+ <td class="col-name"> row label and <td class="heat ..."> per value, e.g.:
415
+
416
+ <tr><th></th><th class="num">age</th><th class="num">mrr</th></tr>
417
+ <tr>
418
+ <td class="col-name">age</td>
419
+ <td class="heat heat-pos-3">1.00</td>
420
+ <td class="heat heat-neg-1">-0.22</td>
421
+ </tr>
422
+
423
+ Suggested heat classes by |r|: heat-neutral (<0.2), heat-pos-1/neg-1 (0.2–0.4),
424
+ heat-pos-2/neg-2 (0.4–0.7), heat-pos-3 (>0.7, positive only diagonal/strong).
425
+ -->
426
+ {{CORRELATION_TABLE}}
427
+ </table>
428
+ </div>
429
+ </section>
430
+
431
+ <!-- ======================================================================
432
+ 09. INSIGHTS
433
+ ====================================================================== -->
434
+ <section class="report-section" id="insights">
435
+ <div class="report-section-head">
436
+ <div class="heading-block">
437
+ <div class="eyebrow">Analysis</div>
438
+ <h2 class="section-title">Insights</h2>
439
+ <p class="section-subtitle">Notable patterns Datapilot surfaced automatically.</p>
440
+ </div>
441
+ </div>
442
+
443
+ <div class="alert-grid">
444
+ <!--
445
+ {{INSIGHTS}}
446
+ Python renders one .alert per insight, variant chosen from info / warning / success / danger:
447
+ <div class="alert alert--info">
448
+ <div class="alert-icon">…svg…</div>
449
+ <div class="alert-body">
450
+ <div class="alert-title">Skewed distribution detected</div>
451
+ <div class="alert-text">Column 'income' is right-skewed (skew = 2.4); consider a log transform.</div>
452
+ </div>
453
+ </div>
454
+ -->
455
+ {{INSIGHTS}}
456
+ </div>
457
+ </section>
458
+
459
+ <!-- ======================================================================
460
+ 10. RECOMMENDATIONS
461
+ ====================================================================== -->
462
+ <section class="report-section" id="recommendations">
463
+ <div class="report-section-head">
464
+ <div class="heading-block">
465
+ <div class="eyebrow">Next steps</div>
466
+ <h2 class="section-title">Recommendations</h2>
467
+ <p class="section-subtitle">Prioritized actions to improve dataset quality.</p>
468
+ </div>
469
+ </div>
470
+
471
+ <div class="checklist">
472
+ <!--
473
+ {{RECOMMENDATIONS}}
474
+ Python renders one .checklist-item per recommendation, priority class sets accent color:
475
+ <div class="checklist-item priority-high">
476
+ <span class="checklist-mark"></span>
477
+ <span class="checklist-text"><strong>Impute or drop 'age'.</strong> 18.4% missing exceeds the 15% threshold.</span>
478
+ <span class="checklist-priority-label badge badge--bad">High</span>
479
+ </div>
480
+ -->
481
+ {{RECOMMENDATIONS}}
482
+ </div>
483
+ </section>
484
+
485
+ <!-- ======================================================================
486
+ FUTURE SECTION TEMPLATE (copy this block for new capabilities)
487
+ Uncomment, rename the id/eyebrow/title, and add a matching rail link.
488
+ The grid, spacing, and card system are already wired up.
489
+
490
+ <section class="report-section" id="distribution-analysis">
491
+ <div class="report-section-head">
492
+ <div class="heading-block">
493
+ <div class="eyebrow">Shape</div>
494
+ <h2 class="section-title">Distribution Analysis</h2>
495
+ <p class="section-subtitle">Placeholder subtitle for the new section.</p>
496
+ </div>
497
+ </div>
498
+ <div class="card card-pad">
499
+ {{DISTRIBUTION_ANALYSIS_CONTENT}}
500
+ </div>
501
+ </section>
502
+
503
+ ====================================================================== -->
504
+
505
+ </main>
506
+ </div>
507
+
508
+ <!-- ==========================================================================
509
+ FOOTER
510
+ ========================================================================== -->
511
+ <footer style="border-top: 1px solid var(--line-200); margin-top: var(--sp-12);">
512
+ <div class="dp-shell" style="grid-template-columns: 1fr; padding-top: var(--sp-6); padding-bottom: var(--sp-8);">
513
+ <div class="flex items-center justify-between gap-4" style="flex-wrap: wrap;">
514
+ <div class="flex items-center gap-3">
515
+ <div class="dp-logo" style="width: 28px; height: 28px;">
516
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="width: 14px; height: 14px;">
517
+ <path d="M4 18V10.5M9.5 18V6M15 18V13M20 18V8.5" stroke="white" stroke-width="2.4" stroke-linecap="round"/>
518
+ </svg>
519
+ </div>
520
+ <span class="text-muted" style="font-size: var(--fs-sm);">Generated by <strong style="color: var(--ink-700);">Datapilot</strong> {{DATAPILOT_VERSION}}</span>
521
+ </div>
522
+ <span class="text-faint numeral" style="font-size: var(--fs-xs);">{{REPORT_GENERATED_AT}} · completed in {{GENERATION_DURATION}}</span>
523
+ </div>
524
+ </div>
525
+ </footer>
526
+
527
+ </body>
528
+ </html>
File without changes
datapilot/ui/cards.py ADDED
@@ -0,0 +1,70 @@
1
+ """
2
+ Reusable cards for Datapilot.
3
+ """
4
+
5
+ from rich.align import Align
6
+ from rich.console import Group
7
+ from rich.panel import Panel
8
+ from rich.progress_bar import ProgressBar
9
+ from rich.text import Text
10
+
11
+
12
+ def metric_card(
13
+ title: str,
14
+ value: str,
15
+ subtitle: str = "",
16
+ border_style: str = "accent",
17
+ ) -> Panel:
18
+ """
19
+ Create a reusable metric card.
20
+ """
21
+
22
+ body = Text(justify="center")
23
+
24
+ body.append(f"{title}\n\n", style="heading")
25
+ body.append(f"{value}\n", style="score")
26
+
27
+ if subtitle:
28
+ body.append(subtitle, style="info")
29
+
30
+ return Panel(
31
+ Align.center(body),
32
+ border_style=border_style,
33
+ expand=True,
34
+ )
35
+
36
+
37
+ def hero_card(
38
+ title: str,
39
+ value: str,
40
+ subtitle: str,
41
+ score: int,
42
+ border_style: str = "green",
43
+ ) -> Panel:
44
+ """
45
+ Create the Datapilot hero card.
46
+ """
47
+
48
+ body = Text(justify="center")
49
+
50
+ body.append(f"{title}\n\n", style="heading")
51
+ body.append(f"{value}\n", style="score")
52
+ body.append(f"{subtitle}\n\n", style="accent")
53
+
54
+ progress = ProgressBar(
55
+ total=100,
56
+ completed=score,
57
+ width=40,
58
+ )
59
+
60
+ return Panel(
61
+ Align.center(
62
+ Group(
63
+ body,
64
+ progress,
65
+ )
66
+ ),
67
+ border_style=border_style,
68
+ expand=True,
69
+ padding=(1, 2),
70
+ )
@@ -0,0 +1,12 @@
1
+ """
2
+ Shared Rich console for Datapilot.
3
+ """
4
+
5
+ from rich.console import Console
6
+
7
+ from .theme import DATAPILOT_THEME
8
+
9
+ console = Console(
10
+ theme=DATAPILOT_THEME,
11
+ highlight=False,
12
+ )