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,1081 @@
1
+ /* ==========================================================================
2
+ DATAPILOT · DATA QUALITY & EDA REPORT
3
+ Stylesheet — vanilla CSS3, no dependencies, no build step
4
+ --------------------------------------------------------------------------
5
+ Structure of this file:
6
+ 1. Design tokens (color, type, spacing, radius, shadow, motion)
7
+ 2. Reset & base
8
+ 3. Type scale
9
+ 4. Layout shell (header, sidebar rail, main content)
10
+ 5. Section primitive (used by every report section — extend here)
11
+ 6. Components
12
+ 6.1 Cards
13
+ 6.2 KPI grid + score gauge
14
+ 6.3 Badges & status chips
15
+ 6.4 Progress bars
16
+ 6.5 Tables
17
+ 6.6 Missing-value bars
18
+ 6.7 Alerts / insight cards
19
+ 6.8 Checklist (recommendations)
20
+ 6.9 Outlier cards
21
+ 6.10 Stat pills / summary cards
22
+ 7. Utilities
23
+ 8. Responsive
24
+ 9. Print
25
+ ========================================================================== */
26
+
27
+
28
+ /* ==========================================================================
29
+ 1. DESIGN TOKENS
30
+ Rationale: Datapilot is a technical instrument, not a marketing surface.
31
+ The palette is a cool "workbench" slate rather than warm neutrals, with a
32
+ single signal-indigo brand color reserved for identity/navigation, and a
33
+ dedicated 3-tone severity system (good/warn/bad) that carries the actual
34
+ analytical meaning of the report — color here is data, not decoration.
35
+ ========================================================================== */
36
+ :root {
37
+ /* -- Neutrals (cool slate, not pure black/white) -- */
38
+ --ink-900: #0f172a; /* primary text */
39
+ --ink-700: #33415c; /* headings on light */
40
+ --ink-600: #526079; /* secondary text */
41
+ --ink-400: #8a97ad; /* tertiary / placeholder text */
42
+ --line-200: #e7ebf2; /* hairline borders */
43
+ --line-300: #d8dfe9; /* stronger borders */
44
+ --surface-0: #ffffff; /* card surface */
45
+ --surface-50: #f6f8fb;/* page background */
46
+ --surface-100: #eef1f7; /* recessed surface (table head, code) */
47
+ --ink-900-rgb: 15, 23, 42;
48
+
49
+ /* -- Brand (Datapilot indigo) -- */
50
+ --brand-50: #eef1ff;
51
+ --brand-100: #dde3ff;
52
+ --brand-500: #4457d6;
53
+ --brand-600: #3548c4;
54
+ --brand-700: #2a399e;
55
+ --brand-900: #1b2566;
56
+
57
+ /* -- Severity system (drives health score, missing %, outliers) -- */
58
+ --good-50: #eefcf3;
59
+ --good-500: #1fa562;
60
+ --good-600: #178a51;
61
+ --good-700: #0f6b3f;
62
+
63
+ --warn-50: #fff8ec;
64
+ --warn-500: #d98d18;
65
+ --warn-600: #b8730c;
66
+ --warn-700: #8f5808;
67
+
68
+ --bad-50: #fdf0f0;
69
+ --bad-500: #d34a4a;
70
+ --bad-600: #b93636;
71
+ --bad-700: #922828;
72
+
73
+ --info-50: #eef6ff;
74
+ --info-500: #2d7fd9;
75
+ --info-600: #1f68b8;
76
+
77
+ /* -- Typography -- */
78
+ --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Inter", Roboto,
79
+ "Helvetica Neue", Arial, sans-serif;
80
+ --font-mono: "SF Mono", "Cascadia Mono", "JetBrains Mono", "IBM Plex Mono",
81
+ Consolas, "Roboto Mono", monospace;
82
+
83
+ --fs-2xs: 0.6875rem; /* 11px — eyebrows, captions */
84
+ --fs-xs: 0.75rem; /* 12px — table text, meta */
85
+ --fs-sm: 0.8125rem; /* 13px — body small */
86
+ --fs-base: 0.9375rem; /* 15px — body */
87
+ --fs-md: 1.0625rem; /* 17px — section lead */
88
+ --fs-lg: 1.375rem; /* 22px — card titles */
89
+ --fs-xl: 1.75rem; /* 28px — section titles */
90
+ --fs-2xl: 2.5rem; /* 40px — KPI numerals */
91
+ --fs-3xl: 3.25rem; /* 52px — hero score */
92
+
93
+ /* -- Spacing (4px base) -- */
94
+ --sp-1: 0.25rem;
95
+ --sp-2: 0.5rem;
96
+ --sp-3: 0.75rem;
97
+ --sp-4: 1rem;
98
+ --sp-5: 1.25rem;
99
+ --sp-6: 1.5rem;
100
+ --sp-8: 2rem;
101
+ --sp-10: 2.5rem;
102
+ --sp-12: 3rem;
103
+ --sp-16: 4rem;
104
+
105
+ /* -- Radius -- */
106
+ --r-sm: 6px;
107
+ --r-md: 10px;
108
+ --r-lg: 14px;
109
+ --r-full: 999px;
110
+
111
+ /* -- Shadow (soft, low-elevation — enterprise, not glossy) -- */
112
+ --shadow-sm: 0 1px 2px rgba(var(--ink-900-rgb), 0.05);
113
+ --shadow-md: 0 2px 8px rgba(var(--ink-900-rgb), 0.06), 0 1px 2px rgba(var(--ink-900-rgb), 0.04);
114
+ --shadow-lg: 0 8px 24px rgba(var(--ink-900-rgb), 0.08), 0 2px 6px rgba(var(--ink-900-rgb), 0.05);
115
+
116
+ /* -- Motion -- */
117
+ --ease: cubic-bezier(0.4, 0, 0.2, 1);
118
+ --dur-fast: 120ms;
119
+ --dur-base: 200ms;
120
+
121
+ /* -- Layout -- */
122
+ --rail-width: 248px;
123
+ --content-max: 1180px;
124
+ }
125
+
126
+ /* Respect reduced motion preference */
127
+ @media (prefers-reduced-motion: reduce) {
128
+ * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
129
+ }
130
+
131
+
132
+ /* ==========================================================================
133
+ 2. RESET & BASE
134
+ ========================================================================== */
135
+ *, *::before, *::after { box-sizing: border-box; }
136
+ html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
137
+
138
+ body {
139
+ margin: 0;
140
+ background: var(--surface-50);
141
+ color: var(--ink-900);
142
+ font-family: var(--font-sans);
143
+ font-size: var(--fs-base);
144
+ line-height: 1.55;
145
+ -webkit-font-smoothing: antialiased;
146
+ text-rendering: optimizeLegibility;
147
+ }
148
+
149
+ img, svg { display: block; max-width: 100%; }
150
+ table { border-collapse: collapse; width: 100%; }
151
+ a { color: var(--brand-600); text-decoration: none; }
152
+ a:hover { text-decoration: underline; }
153
+ ul, ol { margin: 0; padding: 0; list-style: none; }
154
+ h1, h2, h3, h4, p { margin: 0; }
155
+
156
+ /* Visible keyboard focus everywhere */
157
+ :focus-visible {
158
+ outline: 2px solid var(--brand-500);
159
+ outline-offset: 2px;
160
+ border-radius: var(--r-sm);
161
+ }
162
+
163
+
164
+ /* ==========================================================================
165
+ 3. TYPE SCALE
166
+ ========================================================================== */
167
+ .eyebrow {
168
+ font-size: var(--fs-2xs);
169
+ font-weight: 700;
170
+ letter-spacing: 0.08em;
171
+ text-transform: uppercase;
172
+ color: var(--brand-600);
173
+ }
174
+
175
+ .section-title {
176
+ font-size: var(--fs-xl);
177
+ font-weight: 650;
178
+ letter-spacing: -0.01em;
179
+ color: var(--ink-900);
180
+ }
181
+
182
+ .section-subtitle {
183
+ font-size: var(--fs-sm);
184
+ color: var(--ink-600);
185
+ margin-top: var(--sp-1);
186
+ }
187
+
188
+ .numeral {
189
+ font-family: var(--font-mono);
190
+ font-variant-numeric: tabular-nums;
191
+ letter-spacing: -0.02em;
192
+ }
193
+
194
+
195
+ /* ==========================================================================
196
+ 4. LAYOUT SHELL
197
+ ========================================================================== */
198
+
199
+ /* ---- Top header ---- */
200
+ .dp-header {
201
+ background: var(--surface-0);
202
+ border-bottom: 1px solid var(--line-200);
203
+ padding: var(--sp-5) var(--sp-8);
204
+ display: flex;
205
+ align-items: center;
206
+ justify-content: space-between;
207
+ gap: var(--sp-6);
208
+ flex-wrap: wrap;
209
+ }
210
+
211
+ .dp-brand {
212
+ display: flex;
213
+ align-items: center;
214
+ gap: var(--sp-3);
215
+ }
216
+
217
+ .dp-logo {
218
+ width: 40px;
219
+ height: 40px;
220
+ border-radius: var(--r-md);
221
+ background: linear-gradient(155deg, var(--brand-500), var(--brand-700));
222
+ display: flex;
223
+ align-items: center;
224
+ justify-content: center;
225
+ flex-shrink: 0;
226
+ box-shadow: var(--shadow-sm);
227
+ }
228
+
229
+ .dp-logo svg { width: 20px; height: 20px; }
230
+
231
+ .dp-brand-text .dp-name {
232
+ font-size: var(--fs-lg);
233
+ font-weight: 700;
234
+ letter-spacing: -0.01em;
235
+ color: var(--ink-900);
236
+ line-height: 1.2;
237
+ }
238
+
239
+ .dp-brand-text .dp-tagline {
240
+ font-size: var(--fs-2xs);
241
+ color: var(--ink-400);
242
+ text-transform: uppercase;
243
+ letter-spacing: 0.06em;
244
+ font-weight: 600;
245
+ }
246
+
247
+ .dp-header-meta {
248
+ display: flex;
249
+ gap: var(--sp-8);
250
+ align-items: center;
251
+ flex-wrap: wrap;
252
+ }
253
+
254
+ .dp-header-meta .meta-item {
255
+ text-align: right;
256
+ }
257
+
258
+ .dp-header-meta .meta-label {
259
+ font-size: var(--fs-2xs);
260
+ text-transform: uppercase;
261
+ letter-spacing: 0.06em;
262
+ color: var(--ink-400);
263
+ font-weight: 600;
264
+ margin-bottom: 2px;
265
+ }
266
+
267
+ .dp-header-meta .meta-value {
268
+ font-size: var(--fs-sm);
269
+ font-weight: 600;
270
+ color: var(--ink-700);
271
+ font-family: var(--font-mono);
272
+ }
273
+
274
+ .dp-header-title {
275
+ padding: var(--sp-6) var(--sp-8) 0;
276
+ max-width: var(--content-max);
277
+ margin: 0 auto;
278
+ }
279
+
280
+ .dp-header-title h1 {
281
+ font-size: 1.9rem;
282
+ font-weight: 700;
283
+ letter-spacing: -0.015em;
284
+ color: var(--ink-900);
285
+ }
286
+
287
+ .dp-header-title .dp-dataset-name {
288
+ color: var(--brand-600);
289
+ }
290
+
291
+ /* ---- Shell: sidebar rail + main content ---- */
292
+ .dp-shell {
293
+ max-width: var(--content-max);
294
+ margin: 0 auto;
295
+ padding: var(--sp-8);
296
+ display: grid;
297
+ grid-template-columns: var(--rail-width) 1fr;
298
+ gap: var(--sp-10);
299
+ align-items: start;
300
+ }
301
+
302
+ /* ---- Sidebar navigation rail ---- */
303
+ .dp-rail {
304
+ position: sticky;
305
+ top: var(--sp-8);
306
+ background: var(--surface-0);
307
+ border: 1px solid var(--line-200);
308
+ border-radius: var(--r-lg);
309
+ padding: var(--sp-5);
310
+ box-shadow: var(--shadow-sm);
311
+ }
312
+
313
+ .dp-rail-heading {
314
+ font-size: var(--fs-2xs);
315
+ text-transform: uppercase;
316
+ letter-spacing: 0.08em;
317
+ font-weight: 700;
318
+ color: var(--ink-400);
319
+ padding: 0 var(--sp-3) var(--sp-3);
320
+ }
321
+
322
+ .dp-rail-list li + li { margin-top: 1px; }
323
+
324
+ .dp-rail-list a {
325
+ display: flex;
326
+ align-items: center;
327
+ gap: var(--sp-3);
328
+ padding: var(--sp-2) var(--sp-3);
329
+ border-radius: var(--r-sm);
330
+ font-size: var(--fs-sm);
331
+ font-weight: 500;
332
+ color: var(--ink-600);
333
+ transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
334
+ }
335
+
336
+ .dp-rail-list a:hover,
337
+ .dp-rail-list a:focus-visible {
338
+ background: var(--brand-50);
339
+ color: var(--brand-700);
340
+ text-decoration: none;
341
+ }
342
+
343
+ .dp-rail-list .rail-index {
344
+ font-family: var(--font-mono);
345
+ font-size: var(--fs-2xs);
346
+ color: var(--ink-400);
347
+ width: 18px;
348
+ flex-shrink: 0;
349
+ }
350
+
351
+ .dp-rail-list a:hover .rail-index,
352
+ .dp-rail-list a:focus-visible .rail-index { color: var(--brand-500); }
353
+
354
+ .dp-rail-divider {
355
+ height: 1px;
356
+ background: var(--line-200);
357
+ margin: var(--sp-3) 0;
358
+ }
359
+
360
+ /* Group heading for a future capability group inside the rail (e.g. "Coming soon") */
361
+ .dp-rail-group-label {
362
+ font-size: var(--fs-2xs);
363
+ color: var(--ink-400);
364
+ padding: var(--sp-2) var(--sp-3) var(--sp-1);
365
+ }
366
+
367
+ /* ---- Main content column ---- */
368
+ .dp-main {
369
+ display: flex;
370
+ flex-direction: column;
371
+ gap: var(--sp-10);
372
+ min-width: 0;
373
+ }
374
+
375
+
376
+ /* ==========================================================================
377
+ 5. SECTION PRIMITIVE
378
+ Every report section (current or future) is a .report-section. New
379
+ sections (Distribution Analysis, Cardinality, ML Readiness, Data Drift,
380
+ Feature Importance, Export Metadata, ...) drop in using this exact
381
+ pattern without touching header, rail, or grid structure elsewhere.
382
+ ========================================================================== */
383
+ .report-section {
384
+ scroll-margin-top: var(--sp-8);
385
+ }
386
+
387
+ .report-section-head {
388
+ display: flex;
389
+ align-items: baseline;
390
+ justify-content: space-between;
391
+ gap: var(--sp-4);
392
+ margin-bottom: var(--sp-5);
393
+ flex-wrap: wrap;
394
+ }
395
+
396
+ .report-section-head .heading-block .eyebrow { margin-bottom: var(--sp-1); }
397
+
398
+ .report-section-head .section-tag {
399
+ font-size: var(--fs-xs);
400
+ color: var(--ink-400);
401
+ font-family: var(--font-mono);
402
+ }
403
+
404
+
405
+ /* ==========================================================================
406
+ 6. COMPONENTS
407
+ ========================================================================== */
408
+
409
+ /* ---- 6.1 Cards ---- */
410
+ .card {
411
+ background: var(--surface-0);
412
+ border: 1px solid var(--line-200);
413
+ border-radius: var(--r-lg);
414
+ box-shadow: var(--shadow-sm);
415
+ }
416
+
417
+ .card-pad { padding: var(--sp-6); }
418
+
419
+ .card-title {
420
+ font-size: var(--fs-md);
421
+ font-weight: 650;
422
+ color: var(--ink-900);
423
+ }
424
+
425
+ /* ---- 6.2 KPI grid + score gauge ---- */
426
+ .kpi-grid {
427
+ display: grid;
428
+ grid-template-columns: repeat(6, 1fr);
429
+ gap: var(--sp-4);
430
+ }
431
+
432
+ .kpi-card {
433
+ background: var(--surface-0);
434
+ border: 1px solid var(--line-200);
435
+ border-left: 3px solid var(--brand-500);
436
+ border-radius: var(--r-md);
437
+ padding: var(--sp-5);
438
+ box-shadow: var(--shadow-sm);
439
+ transition: box-shadow var(--dur-base) var(--ease), transform var(--dur-base) var(--ease);
440
+ }
441
+
442
+ .kpi-card:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
443
+
444
+ .kpi-card--good { border-left-color: var(--good-500); }
445
+ .kpi-card--warn { border-left-color: var(--warn-500); }
446
+ .kpi-card--bad { border-left-color: var(--bad-500); }
447
+
448
+ .kpi-label {
449
+ font-size: var(--fs-2xs);
450
+ text-transform: uppercase;
451
+ letter-spacing: 0.06em;
452
+ font-weight: 700;
453
+ color: var(--ink-400);
454
+ margin-bottom: var(--sp-2);
455
+ }
456
+
457
+ .kpi-value {
458
+ font-family: var(--font-mono);
459
+ font-size: var(--fs-2xl);
460
+ font-weight: 600;
461
+ letter-spacing: -0.02em;
462
+ color: var(--ink-900);
463
+ line-height: 1.1;
464
+ }
465
+
466
+ .kpi-value .kpi-unit {
467
+ font-size: var(--fs-md);
468
+ color: var(--ink-400);
469
+ font-weight: 500;
470
+ margin-left: 2px;
471
+ }
472
+
473
+ .kpi-sub {
474
+ margin-top: var(--sp-2);
475
+ font-size: var(--fs-xs);
476
+ color: var(--ink-600);
477
+ }
478
+
479
+ .kpi-sub.sev-good { color: var(--good-600); }
480
+ .kpi-sub.sev-warn { color: var(--warn-600); }
481
+ .kpi-sub.sev-bad { color: var(--bad-600); }
482
+
483
+ /* Health score gauge — pure CSS conic-gradient ring.
484
+ Python sets the inline custom property, e.g.:
485
+ <div class="gauge" style="--score: {{HEALTH_SCORE}};"> */
486
+ .gauge {
487
+ --score: 0; /* 0–100, set inline per-report by Python */
488
+ --ring-color: var(--brand-600);
489
+ width: 148px;
490
+ height: 148px;
491
+ border-radius: 50%;
492
+ background: conic-gradient(var(--ring-color) calc(var(--score) * 1%), var(--surface-100) 0);
493
+ display: flex;
494
+ align-items: center;
495
+ justify-content: center;
496
+ flex-shrink: 0;
497
+ }
498
+
499
+ .gauge-inner {
500
+ width: 118px;
501
+ height: 118px;
502
+ border-radius: 50%;
503
+ background: var(--surface-0);
504
+ display: flex;
505
+ flex-direction: column;
506
+ align-items: center;
507
+ justify-content: center;
508
+ box-shadow: inset 0 0 0 1px var(--line-200);
509
+ }
510
+
511
+ .gauge-score {
512
+ font-family: var(--font-mono);
513
+ font-size: var(--fs-2xl);
514
+ font-weight: 700;
515
+ color: var(--ink-900);
516
+ line-height: 1;
517
+ }
518
+
519
+ .gauge-grade {
520
+ margin-top: var(--sp-1);
521
+ font-size: var(--fs-2xs);
522
+ font-weight: 700;
523
+ letter-spacing: 0.04em;
524
+ color: var(--ink-400);
525
+ text-transform: uppercase;
526
+ }
527
+
528
+ /* Gauge severity variants — Python selects the class by score band */
529
+ .gauge--good { --ring-color: var(--good-600); }
530
+ .gauge--warn { --ring-color: var(--warn-500); }
531
+ .gauge--bad { --ring-color: var(--bad-600); }
532
+
533
+ /* ---- 6.3 Badges & status chips ---- */
534
+ .badge {
535
+ display: inline-flex;
536
+ align-items: center;
537
+ gap: 6px;
538
+ padding: 4px 12px;
539
+ border-radius: var(--r-full);
540
+ font-size: var(--fs-xs);
541
+ font-weight: 650;
542
+ letter-spacing: 0.01em;
543
+ }
544
+
545
+ .badge::before {
546
+ content: "";
547
+ width: 6px;
548
+ height: 6px;
549
+ border-radius: 50%;
550
+ background: currentColor;
551
+ }
552
+
553
+ .badge--good { background: var(--good-50); color: var(--good-700); }
554
+ .badge--warn { background: var(--warn-50); color: var(--warn-700); }
555
+ .badge--bad { background: var(--bad-50); color: var(--bad-700); }
556
+ .badge--info { background: var(--info-50); color: var(--info-600); }
557
+ .badge--neutral { background: var(--surface-100); color: var(--ink-600); }
558
+
559
+ .badge--grade {
560
+ font-family: var(--font-mono);
561
+ font-size: var(--fs-sm);
562
+ padding: 4px 14px;
563
+ }
564
+
565
+ /* ---- 6.4 Progress bars ---- */
566
+ .progress-track {
567
+ width: 100%;
568
+ height: 8px;
569
+ border-radius: var(--r-full);
570
+ background: var(--surface-100);
571
+ overflow: hidden;
572
+ }
573
+
574
+ .progress-fill {
575
+ height: 100%;
576
+ border-radius: var(--r-full);
577
+ background: var(--brand-600);
578
+ /* Python sets width inline: style="width: {{HEALTH_SCORE}}%;" */
579
+ }
580
+
581
+ .progress-fill.sev-good { background: var(--good-600); }
582
+ .progress-fill.sev-warn { background: var(--warn-500); }
583
+ .progress-fill.sev-bad { background: var(--bad-600); }
584
+
585
+ .progress-row {
586
+ display: flex;
587
+ align-items: center;
588
+ gap: var(--sp-4);
589
+ }
590
+
591
+ .progress-row .progress-label {
592
+ width: 100px;
593
+ flex-shrink: 0;
594
+ font-size: var(--fs-xs);
595
+ color: var(--ink-600);
596
+ }
597
+
598
+ .progress-row .progress-value {
599
+ width: 52px;
600
+ flex-shrink: 0;
601
+ text-align: right;
602
+ font-family: var(--font-mono);
603
+ font-size: var(--fs-xs);
604
+ color: var(--ink-700);
605
+ }
606
+
607
+ /* ---- 6.5 Tables ---- */
608
+ .table-wrap {
609
+ overflow-x: auto;
610
+ border: 1px solid var(--line-200);
611
+ border-radius: var(--r-lg);
612
+ background: var(--surface-0);
613
+ }
614
+
615
+ .dp-table {
616
+ min-width: 100%;
617
+ }
618
+
619
+ /* Applies to every <th>, regardless of whether it sits in <thead> or a
620
+ dynamically-generated header row inside <tbody> (e.g. the correlation
621
+ matrix, whose header depends on runtime column names) */
622
+ .dp-table th {
623
+ background: var(--surface-100);
624
+ text-align: left;
625
+ font-size: var(--fs-2xs);
626
+ text-transform: uppercase;
627
+ letter-spacing: 0.05em;
628
+ font-weight: 700;
629
+ color: var(--ink-600);
630
+ padding: var(--sp-3) var(--sp-4);
631
+ border-bottom: 1px solid var(--line-300);
632
+ white-space: nowrap;
633
+ }
634
+
635
+ .dp-table thead th {
636
+ position: sticky;
637
+ top: 0;
638
+ }
639
+
640
+ .dp-table tbody td {
641
+ padding: var(--sp-3) var(--sp-4);
642
+ font-size: var(--fs-sm);
643
+ color: var(--ink-700);
644
+ border-bottom: 1px solid var(--line-200);
645
+ vertical-align: middle;
646
+ }
647
+
648
+ .dp-table tbody tr:last-child td { border-bottom: none; }
649
+ .dp-table tbody tr:hover { background: var(--surface-50); }
650
+
651
+ .dp-table td.num,
652
+ .dp-table th.num {
653
+ font-family: var(--font-mono);
654
+ text-align: right;
655
+ font-variant-numeric: tabular-nums;
656
+ }
657
+
658
+ .dp-table td.col-name {
659
+ font-family: var(--font-mono);
660
+ font-size: var(--fs-xs);
661
+ color: var(--ink-900);
662
+ font-weight: 600;
663
+ }
664
+
665
+ /* Zebra option — apply .dp-table--zebra for dense/statistics tables */
666
+ .dp-table--zebra tbody tr:nth-child(even) { background: var(--surface-50); }
667
+ .dp-table--zebra tbody tr:nth-child(even):hover { background: var(--brand-50); }
668
+
669
+ /* ---- 6.6 Missing-value / severity inline bars (used inside table cells) ---- */
670
+ .cell-bar {
671
+ display: flex;
672
+ align-items: center;
673
+ gap: var(--sp-3);
674
+ min-width: 140px;
675
+ }
676
+
677
+ .cell-bar .cell-bar-track {
678
+ flex: 1;
679
+ height: 6px;
680
+ border-radius: var(--r-full);
681
+ background: var(--surface-100);
682
+ overflow: hidden;
683
+ }
684
+
685
+ .cell-bar .cell-bar-fill { height: 100%; border-radius: var(--r-full); }
686
+ .cell-bar .cell-bar-fill.sev-good { background: var(--good-500); }
687
+ .cell-bar .cell-bar-fill.sev-warn { background: var(--warn-500); }
688
+ .cell-bar .cell-bar-fill.sev-bad { background: var(--bad-500); }
689
+
690
+ .cell-bar .cell-bar-pct {
691
+ font-family: var(--font-mono);
692
+ font-size: var(--fs-xs);
693
+ width: 44px;
694
+ text-align: right;
695
+ flex-shrink: 0;
696
+ }
697
+
698
+ /* Correlation heat cells — Python assigns .heat-1..5 by |r| magnitude */
699
+ .dp-table td.heat {
700
+ text-align: center;
701
+ font-family: var(--font-mono);
702
+ font-size: var(--fs-xs);
703
+ font-weight: 600;
704
+ }
705
+
706
+ .heat-pos-1 { background: var(--brand-50); }
707
+ .heat-pos-2 { background: var(--brand-100); }
708
+ .heat-pos-3 { background: #c7d1fb; color: var(--brand-900); }
709
+ .heat-neg-1 { background: var(--bad-50); }
710
+ .heat-neg-2 { background: #f7d9d9; color: var(--bad-700); }
711
+ .heat-neutral { color: var(--ink-400); }
712
+
713
+ /* ---- 6.7 Alerts / insight cards ---- */
714
+ .alert-grid {
715
+ display: grid;
716
+ gap: var(--sp-4);
717
+ }
718
+
719
+ .alert {
720
+ display: flex;
721
+ gap: var(--sp-4);
722
+ padding: var(--sp-5);
723
+ border-radius: var(--r-md);
724
+ border: 1px solid var(--line-200);
725
+ border-left: 3px solid var(--ink-400);
726
+ background: var(--surface-0);
727
+ }
728
+
729
+ .alert-icon {
730
+ width: 32px;
731
+ height: 32px;
732
+ border-radius: var(--r-sm);
733
+ flex-shrink: 0;
734
+ display: flex;
735
+ align-items: center;
736
+ justify-content: center;
737
+ }
738
+
739
+ .alert-icon svg { width: 16px; height: 16px; }
740
+
741
+ .alert-body { min-width: 0; }
742
+
743
+ .alert-title {
744
+ font-size: var(--fs-sm);
745
+ font-weight: 650;
746
+ color: var(--ink-900);
747
+ margin-bottom: 2px;
748
+ }
749
+
750
+ .alert-text {
751
+ font-size: var(--fs-sm);
752
+ color: var(--ink-600);
753
+ }
754
+
755
+ .alert--info { border-left-color: var(--info-500); }
756
+ .alert--info .alert-icon { background: var(--info-50); color: var(--info-600); }
757
+
758
+ .alert--warning { border-left-color: var(--warn-500); }
759
+ .alert--warning .alert-icon { background: var(--warn-50); color: var(--warn-700); }
760
+
761
+ .alert--success { border-left-color: var(--good-500); }
762
+ .alert--success .alert-icon { background: var(--good-50); color: var(--good-700); }
763
+
764
+ .alert--danger { border-left-color: var(--bad-500); }
765
+ .alert--danger .alert-icon { background: var(--bad-50); color: var(--bad-700); }
766
+
767
+ /* ---- 6.8 Checklist (recommendations) ---- */
768
+ .checklist { display: flex; flex-direction: column; gap: var(--sp-3); }
769
+
770
+ .checklist-item {
771
+ display: flex;
772
+ align-items: flex-start;
773
+ gap: var(--sp-4);
774
+ padding: var(--sp-4) var(--sp-5);
775
+ background: var(--surface-0);
776
+ border: 1px solid var(--line-200);
777
+ border-radius: var(--r-md);
778
+ }
779
+
780
+ .checklist-mark {
781
+ width: 22px;
782
+ height: 22px;
783
+ border-radius: var(--r-sm);
784
+ border: 1.5px solid var(--line-300);
785
+ flex-shrink: 0;
786
+ margin-top: 1px;
787
+ display: flex;
788
+ align-items: center;
789
+ justify-content: center;
790
+ }
791
+
792
+ /* Priority variant — Python sets via class on .checklist-item */
793
+ .checklist-item.priority-high .checklist-mark { border-color: var(--bad-500); background: var(--bad-50); }
794
+ .checklist-item.priority-medium .checklist-mark { border-color: var(--warn-500); background: var(--warn-50); }
795
+ .checklist-item.priority-low .checklist-mark { border-color: var(--good-500); background: var(--good-50); }
796
+
797
+ .checklist-text {
798
+ font-size: var(--fs-sm);
799
+ color: var(--ink-700);
800
+ }
801
+
802
+ .checklist-text strong { color: var(--ink-900); font-weight: 650; }
803
+
804
+ .checklist-priority-label {
805
+ margin-left: auto;
806
+ flex-shrink: 0;
807
+ }
808
+
809
+ /* ---- 6.9 Outlier cards ---- */
810
+ .outlier-grid {
811
+ display: grid;
812
+ grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
813
+ gap: var(--sp-4);
814
+ }
815
+
816
+ .outlier-card {
817
+ background: var(--surface-0);
818
+ border: 1px solid var(--line-200);
819
+ border-radius: var(--r-md);
820
+ padding: var(--sp-5);
821
+ }
822
+
823
+ .outlier-card-head {
824
+ display: flex;
825
+ align-items: center;
826
+ justify-content: space-between;
827
+ margin-bottom: var(--sp-3);
828
+ }
829
+
830
+ .outlier-column {
831
+ font-family: var(--font-mono);
832
+ font-size: var(--fs-sm);
833
+ font-weight: 650;
834
+ color: var(--ink-900);
835
+ }
836
+
837
+ .outlier-stats {
838
+ display: grid;
839
+ grid-template-columns: 1fr 1fr;
840
+ gap: var(--sp-3);
841
+ margin-top: var(--sp-3);
842
+ }
843
+
844
+ .outlier-stat-label {
845
+ font-size: var(--fs-2xs);
846
+ color: var(--ink-400);
847
+ text-transform: uppercase;
848
+ letter-spacing: 0.05em;
849
+ margin-bottom: 2px;
850
+ }
851
+
852
+ .outlier-stat-value {
853
+ font-family: var(--font-mono);
854
+ font-size: var(--fs-sm);
855
+ color: var(--ink-700);
856
+ font-weight: 600;
857
+ }
858
+
859
+ .outlier-method {
860
+ margin-top: var(--sp-3);
861
+ padding-top: var(--sp-3);
862
+ border-top: 1px dashed var(--line-200);
863
+ font-size: var(--fs-xs);
864
+ color: var(--ink-400);
865
+ }
866
+
867
+ /* ---- 6.10 Stat pills / summary cards (Duplicate Analysis, Overview) ---- */
868
+ .pill-grid {
869
+ display: grid;
870
+ grid-template-columns: repeat(4, 1fr);
871
+ gap: var(--sp-4);
872
+ }
873
+
874
+ .pill-card {
875
+ background: var(--surface-0);
876
+ border: 1px solid var(--line-200);
877
+ border-radius: var(--r-md);
878
+ padding: var(--sp-5);
879
+ text-align: center;
880
+ }
881
+
882
+ .pill-card .kpi-value { font-size: var(--fs-xl); }
883
+
884
+ .definition-grid {
885
+ display: grid;
886
+ grid-template-columns: repeat(4, 1fr);
887
+ gap: var(--sp-5);
888
+ }
889
+
890
+ .definition-item {
891
+ padding: var(--sp-4) 0;
892
+ border-top: 2px solid var(--line-200);
893
+ }
894
+
895
+ .definition-label {
896
+ font-size: var(--fs-2xs);
897
+ text-transform: uppercase;
898
+ letter-spacing: 0.05em;
899
+ color: var(--ink-400);
900
+ font-weight: 700;
901
+ margin-bottom: var(--sp-1);
902
+ }
903
+
904
+ .definition-value {
905
+ font-family: var(--font-mono);
906
+ font-size: var(--fs-lg);
907
+ font-weight: 650;
908
+ color: var(--ink-900);
909
+ }
910
+
911
+ .column-chip-list {
912
+ display: flex;
913
+ flex-wrap: wrap;
914
+ gap: var(--sp-2);
915
+ margin-top: var(--sp-2);
916
+ }
917
+
918
+ .column-chip {
919
+ font-family: var(--font-mono);
920
+ font-size: var(--fs-xs);
921
+ padding: 3px 10px;
922
+ border-radius: var(--r-sm);
923
+ background: var(--surface-100);
924
+ color: var(--ink-700);
925
+ border: 1px solid var(--line-200);
926
+ }
927
+
928
+ /* Strengths / weaknesses lists inside the Health card */
929
+ .trait-list { display: flex; flex-direction: column; gap: var(--sp-2); }
930
+
931
+ .trait-item {
932
+ display: flex;
933
+ align-items: flex-start;
934
+ gap: var(--sp-2);
935
+ font-size: var(--fs-sm);
936
+ color: var(--ink-700);
937
+ }
938
+
939
+ .trait-item .trait-icon {
940
+ flex-shrink: 0;
941
+ margin-top: 4px;
942
+ width: 6px;
943
+ height: 6px;
944
+ border-radius: 50%;
945
+ }
946
+
947
+ .trait-list--strength .trait-icon { background: var(--good-500); }
948
+ .trait-list--weakness .trait-icon { background: var(--bad-500); }
949
+
950
+ .trait-columns {
951
+ display: grid;
952
+ grid-template-columns: 1fr 1fr;
953
+ gap: var(--sp-6);
954
+ margin-top: var(--sp-5);
955
+ padding-top: var(--sp-5);
956
+ border-top: 1px solid var(--line-200);
957
+ }
958
+
959
+ .trait-columns h4 {
960
+ font-size: var(--fs-xs);
961
+ text-transform: uppercase;
962
+ letter-spacing: 0.05em;
963
+ font-weight: 700;
964
+ color: var(--ink-400);
965
+ margin-bottom: var(--sp-3);
966
+ }
967
+
968
+ /* Health hero layout (score gauge + summary) */
969
+ .health-hero {
970
+ display: flex;
971
+ gap: var(--sp-8);
972
+ align-items: center;
973
+ }
974
+
975
+ .health-hero-body { flex: 1; min-width: 0; }
976
+
977
+ .health-hero-top {
978
+ display: flex;
979
+ align-items: center;
980
+ gap: var(--sp-4);
981
+ margin-bottom: var(--sp-4);
982
+ flex-wrap: wrap;
983
+ }
984
+
985
+ .health-hero-title {
986
+ font-size: var(--fs-lg);
987
+ font-weight: 650;
988
+ color: var(--ink-900);
989
+ }
990
+
991
+
992
+ /* ==========================================================================
993
+ 7. UTILITIES
994
+ ========================================================================== */
995
+ .text-muted { color: var(--ink-600); }
996
+ .text-faint { color: var(--ink-400); }
997
+ .mt-0 { margin-top: 0; }
998
+ .flex { display: flex; }
999
+ .flex-col { flex-direction: column; }
1000
+ .items-center { align-items: center; }
1001
+ .justify-between { justify-content: space-between; }
1002
+ .gap-2 { gap: var(--sp-2); }
1003
+ .gap-3 { gap: var(--sp-3); }
1004
+ .gap-4 { gap: var(--sp-4); }
1005
+ .w-full { width: 100%; }
1006
+
1007
+ /* Generic empty-state, for a metric that has no data yet (e.g. future sections) */
1008
+ .empty-state {
1009
+ padding: var(--sp-10);
1010
+ text-align: center;
1011
+ color: var(--ink-400);
1012
+ font-size: var(--fs-sm);
1013
+ border: 1px dashed var(--line-300);
1014
+ border-radius: var(--r-md);
1015
+ }
1016
+
1017
+
1018
+ /* ==========================================================================
1019
+ 8. RESPONSIVE
1020
+ ========================================================================== */
1021
+ @media (max-width: 1080px) {
1022
+ .kpi-grid { grid-template-columns: repeat(3, 1fr); }
1023
+ .pill-grid, .definition-grid { grid-template-columns: repeat(2, 1fr); }
1024
+ }
1025
+
1026
+ @media (max-width: 960px) {
1027
+ .dp-shell {
1028
+ grid-template-columns: 1fr;
1029
+ }
1030
+ .dp-rail {
1031
+ position: relative;
1032
+ top: 0;
1033
+ }
1034
+ .dp-rail-list {
1035
+ display: flex;
1036
+ flex-wrap: wrap;
1037
+ gap: var(--sp-2);
1038
+ }
1039
+ .dp-rail-list li { flex: 0 0 auto; }
1040
+ .dp-rail-divider,
1041
+ .dp-rail-heading,
1042
+ .dp-rail-group-label { display: none; }
1043
+ }
1044
+
1045
+ @media (max-width: 720px) {
1046
+ .dp-header { padding: var(--sp-4); }
1047
+ .dp-header-title { padding: var(--sp-4) var(--sp-4) 0; }
1048
+ .dp-shell { padding: var(--sp-4); gap: var(--sp-6); }
1049
+ .kpi-grid { grid-template-columns: repeat(2, 1fr); }
1050
+ .pill-grid, .definition-grid, .trait-columns { grid-template-columns: 1fr; }
1051
+ .health-hero { flex-direction: column; align-items: flex-start; }
1052
+ .dp-header-meta { gap: var(--sp-4); }
1053
+ .dp-header-meta .meta-item { text-align: left; }
1054
+ }
1055
+
1056
+ @media (max-width: 480px) {
1057
+ .kpi-grid { grid-template-columns: 1fr 1fr; }
1058
+ .fs-3xl, .gauge { transform: scale(0.9); }
1059
+ }
1060
+
1061
+
1062
+ /* ==========================================================================
1063
+ 9. PRINT
1064
+ Printable, paginated output for sharing/archiving reports as PDF.
1065
+ ========================================================================== */
1066
+ @media print {
1067
+ body { background: #fff; }
1068
+ .dp-rail { display: none; }
1069
+ .dp-shell { grid-template-columns: 1fr; padding: 0; max-width: 100%; }
1070
+ .dp-header { padding: var(--sp-4) 0; }
1071
+ .dp-header-title { padding: var(--sp-4) 0 0; }
1072
+ .report-section { break-inside: avoid; page-break-inside: avoid; }
1073
+ .card, .kpi-card, .outlier-card, .alert, .checklist-item {
1074
+ box-shadow: none;
1075
+ border: 1px solid var(--line-300);
1076
+ }
1077
+ .kpi-card:hover { transform: none; }
1078
+ a { color: inherit; text-decoration: none; }
1079
+ .table-wrap { border: 1px solid var(--line-300); }
1080
+ .dp-table thead th { position: static; }
1081
+ }