ids2eval 0.2.2__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 (73) hide show
  1. ids2eval/__init__.py +0 -0
  2. ids2eval/__main__.py +4 -0
  3. ids2eval/_json_utils.py +24 -0
  4. ids2eval/audit/__init__.py +239 -0
  5. ids2eval/audit/_auc.py +48 -0
  6. ids2eval/audit/_by_class.py +29 -0
  7. ids2eval/audit/_checkpoint.py +74 -0
  8. ids2eval/audit/_fit_score.py +40 -0
  9. ids2eval/audit/_materiality.py +28 -0
  10. ids2eval/audit/_status.py +15 -0
  11. ids2eval/audit/_time.py +14 -0
  12. ids2eval/audit/artifact_sensitivity.py +88 -0
  13. ids2eval/audit/class_distribution.py +57 -0
  14. ids2eval/audit/cross_capture_matrix.py +115 -0
  15. ids2eval/audit/cross_dataset_drift.py +74 -0
  16. ids2eval/audit/data_integrity.py +67 -0
  17. ids2eval/audit/dedup.py +67 -0
  18. ids2eval/audit/feature_auc_ranking.py +125 -0
  19. ids2eval/audit/feature_category_ablation.py +98 -0
  20. ids2eval/audit/flow_group_leakage.py +66 -0
  21. ids2eval/audit/homogeneity.py +160 -0
  22. ids2eval/audit/identity_columns.py +171 -0
  23. ids2eval/audit/known_issues.py +144 -0
  24. ids2eval/audit/label_conflict.py +92 -0
  25. ids2eval/audit/leakage.py +99 -0
  26. ids2eval/audit/near_duplicate_class.py +87 -0
  27. ids2eval/audit/one_rule.py +82 -0
  28. ids2eval/audit/port_protocol_shortcut.py +75 -0
  29. ids2eval/audit/repeated_seed_falsification.py +182 -0
  30. ids2eval/audit/resplit.py +92 -0
  31. ids2eval/audit/result_robustness.py +154 -0
  32. ids2eval/audit/row_order_leakage.py +86 -0
  33. ids2eval/audit/scenario_holdout.py +115 -0
  34. ids2eval/audit/schema_fingerprint.py +52 -0
  35. ids2eval/audit/seed_sensitivity.py +69 -0
  36. ids2eval/audit/synthetic_realism.py +73 -0
  37. ids2eval/audit/temporal_leakage.py +58 -0
  38. ids2eval/audit/temporal_realism.py +81 -0
  39. ids2eval/cli.py +284 -0
  40. ids2eval/config.py +362 -0
  41. ids2eval/dashboard/__init__.py +1 -0
  42. ids2eval/dashboard/__main__.py +3 -0
  43. ids2eval/dashboard/server.py +527 -0
  44. ids2eval/dashboard/static/app.js +1064 -0
  45. ids2eval/dashboard/static/charts.js +366 -0
  46. ids2eval/dashboard/static/index.html +33 -0
  47. ids2eval/dashboard/static/style.css +293 -0
  48. ids2eval/data/__init__.py +3 -0
  49. ids2eval/data/cache.py +99 -0
  50. ids2eval/data/chunked_io.py +387 -0
  51. ids2eval/data/dataset.py +208 -0
  52. ids2eval/data/features.py +95 -0
  53. ids2eval/data/label_grouping.py +38 -0
  54. ids2eval/modeling/__init__.py +3 -0
  55. ids2eval/modeling/benchmark.py +360 -0
  56. ids2eval/modeling/classifiers.py +145 -0
  57. ids2eval/modeling/preprocessing.py +73 -0
  58. ids2eval/reporting/__init__.py +2 -0
  59. ids2eval/reporting/cite.py +57 -0
  60. ids2eval/reporting/compare_datasets.py +103 -0
  61. ids2eval/reporting/drift.py +80 -0
  62. ids2eval/reporting/intervention_manifest.py +83 -0
  63. ids2eval/reporting/recommend.py +402 -0
  64. ids2eval/reporting/run_manager.py +184 -0
  65. ids2eval/reporting/scorecard.py +875 -0
  66. ids2eval/reporting/scorecard_plot.py +162 -0
  67. ids2eval/version_info.py +36 -0
  68. ids2eval-0.2.2.dist-info/METADATA +119 -0
  69. ids2eval-0.2.2.dist-info/RECORD +73 -0
  70. ids2eval-0.2.2.dist-info/WHEEL +5 -0
  71. ids2eval-0.2.2.dist-info/entry_points.txt +4 -0
  72. ids2eval-0.2.2.dist-info/licenses/LICENSE +661 -0
  73. ids2eval-0.2.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,293 @@
1
+ :root{--bg:#f6f9fc;--panel:#fff;--panel-2:#f9fbfd;--ink:#10233e;--muted:#5f6f84;--line:#dfe7f0;--hover:#f0f5fb;
2
+ --accent:#1677ff;--accent-ink:#0b61c9;--accent-bg:#edf5ff;--on-accent:#fff;
3
+ --side:#10283f;--side-2:#0b1d30;--side-ink:#c9d5e1;--side-hover:#1c3d60;
4
+ --ok:#1d7b3a;--ok-bg:#eaf8ef;--ok-mark:#24a148;--warn:#8a5d00;--warn-bg:#fff7df;--warn-mark:#e0a100;
5
+ --flag:#b42318;--flag-bg:#feefed;--flag-mark:#d92d20;--track:#e9eef4;--code:#0f1b2a;--code-ink:#e6edf5;
6
+ --shadow:0 8px 24px rgba(21,44,74,.06);color-scheme:light;
7
+ --s1:#2a78d6;--s2:#eb6834;--ref:#98a3b3;--seq-lo:#eef4fc;--seq-hi:#0d366b;--seq-ink-hi:#fff}
8
+ @media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#0d1520;--panel:#131d2b;--panel-2:#172334;
9
+ --ink:#e6edf5;--muted:#93a3b8;--line:#233247;--hover:#1a2840;--accent:#4c9bff;--accent-ink:#8cc0ff;--accent-bg:#13294a;
10
+ --on-accent:#06121f;--side:#0a121c;--side-2:#070d15;--side-ink:#a9b8c9;--side-hover:#16293f;
11
+ --ok:#5fd48e;--ok-bg:#12301f;--ok-mark:#3fb46e;--warn:#f2c25c;--warn-bg:#33270c;--warn-mark:#d9a531;
12
+ --flag:#ff8a7f;--flag-bg:#3a1714;--flag-mark:#f0645a;--track:#1d2a3c;--code:#080e16;--code-ink:#e6edf5;
13
+ --shadow:none;color-scheme:dark;
14
+ --s1:#3987e5;--s2:#d95926;--ref:#6b7a8f;--seq-lo:#16243a;--seq-hi:#9ec5f4;--seq-ink-hi:#0b1520}}
15
+ :root[data-theme="dark"]{--bg:#0d1520;--panel:#131d2b;--panel-2:#172334;
16
+ --ink:#e6edf5;--muted:#93a3b8;--line:#233247;--hover:#1a2840;--accent:#4c9bff;--accent-ink:#8cc0ff;--accent-bg:#13294a;
17
+ --on-accent:#06121f;--side:#0a121c;--side-2:#070d15;--side-ink:#a9b8c9;--side-hover:#16293f;
18
+ --ok:#5fd48e;--ok-bg:#12301f;--ok-mark:#3fb46e;--warn:#f2c25c;--warn-bg:#33270c;--warn-mark:#d9a531;
19
+ --flag:#ff8a7f;--flag-bg:#3a1714;--flag-mark:#f0645a;--track:#1d2a3c;--code:#080e16;--code-ink:#e6edf5;
20
+ --shadow:none;color-scheme:dark;
21
+ --s1:#3987e5;--s2:#d95926;--ref:#6b7a8f;--seq-lo:#16243a;--seq-hi:#9ec5f4;--seq-ink-hi:#0b1520}
22
+ *{box-sizing:border-box}
23
+ html,body{height:100%}
24
+ body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",
25
+ Helvetica,Arial,sans-serif}
26
+ a{color:inherit;text-decoration:none}
27
+ .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
28
+ h1{font-size:1.6rem;margin:0;letter-spacing:-.01em}
29
+ h2{font-size:1rem;margin:0}
30
+ h3{font-size:.85rem;margin:14px 0 6px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
31
+
32
+ /* shell */
33
+ .shell{display:grid;grid-template-columns:232px 1fr;min-height:100vh}
34
+ .sidebar{background:linear-gradient(180deg,var(--side),var(--side-2));color:#fff;padding:20px 14px;position:sticky;top:0;
35
+ height:100vh;display:flex;flex-direction:column;z-index:20}
36
+ .brand{font-size:26px;font-weight:800;margin:0 10px 22px;letter-spacing:-.02em}
37
+ .brand sup{color:#52a8ff;font-size:.55em}
38
+ .newrun{display:block;background:#1677ff;color:#fff;padding:11px 14px;border-radius:10px;font-weight:700;margin-bottom:14px}
39
+ .newrun:hover{background:#0b61c9}
40
+ .nav a{display:block;padding:10px 14px;border-radius:9px;color:var(--side-ink);margin:3px 0}
41
+ .nav a:hover{background:var(--side-hover);color:#fff}
42
+ .nav a.active{background:var(--side-hover);color:#fff;font-weight:600}
43
+ .side-foot{margin-top:auto;color:var(--side-ink);opacity:.7;font-size:12px;padding:0 10px}
44
+ .content{min-width:0;display:flex;flex-direction:column}
45
+ .top{height:58px;background:var(--panel);border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px;
46
+ padding:0 24px;position:sticky;top:0;z-index:10}
47
+ .crumbs{display:flex;gap:6px;align-items:center;color:var(--muted);min-width:0;overflow:hidden;white-space:nowrap}
48
+ .crumbs a:hover{color:var(--accent)}
49
+ .crumbs .cur{color:var(--ink);font-weight:600;overflow:hidden;text-overflow:ellipsis}
50
+ .spacer{flex:1}
51
+ .main{padding:24px;display:flex;flex-direction:column;gap:16px;max-width:1500px;width:100%}
52
+ #menu{display:none}
53
+
54
+ /* controls */
55
+ button,.btn{font:inherit;font-weight:600;border:1px solid var(--line);background:var(--panel);color:var(--ink);
56
+ border-radius:9px;padding:7px 13px;cursor:pointer;display:inline-flex;align-items:center;gap:6px}
57
+ button:hover,.btn:hover{background:var(--hover)}
58
+ button.primary,.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}
59
+ button.primary:hover,.btn.primary:hover{filter:brightness(1.07)}
60
+ button:disabled{opacity:.5;cursor:default}
61
+ button:focus-visible,a:focus-visible,select:focus-visible,input:focus-visible,textarea:focus-visible,
62
+ [tabindex]:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
63
+ .icon-btn{padding:6px 10px}
64
+ select,input[type=text],input[type=search]{font:inherit;padding:7px 10px;border:1px solid var(--line);border-radius:9px;
65
+ background:var(--panel);color:var(--ink)}
66
+ textarea{width:100%;min-height:420px;resize:vertical;font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
67
+ background:var(--panel-2);color:var(--ink);border:1px solid var(--line);border-radius:10px;padding:12px}
68
+ .stack{display:flex;flex-direction:column;gap:10px}
69
+ .row{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
70
+ .chips{display:flex;flex-wrap:wrap;gap:6px}
71
+ .chip{border-radius:999px;padding:4px 11px;font-size:12.5px;font-weight:600;color:var(--muted)}
72
+ .chip.on{background:var(--accent-bg);border-color:var(--accent);color:var(--accent-ink)}
73
+
74
+ /* page header */
75
+ .page-head{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap}
76
+ .page-head .title{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
77
+ .sub{color:var(--muted);margin-top:4px}
78
+ .hint{color:var(--muted);margin:0}
79
+
80
+ /* cards, grids, kpis */
81
+ .card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:18px;box-shadow:var(--shadow);
82
+ min-width:0;display:flex;flex-direction:column;gap:12px}
83
+ .card-head{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap}
84
+ .grid{display:grid;gap:16px}
85
+ .g2{grid-template-columns:repeat(2,minmax(0,1fr))}
86
+ .g2-wide{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr)}
87
+ .kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:12px}
88
+ .kpi{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:13px 15px;color:var(--muted);font-size:13px}
89
+ .kpi b{display:block;font-size:24px;color:var(--ink);margin-top:4px;font-variant-numeric:tabular-nums}
90
+ .kpi.s-ok b{color:var(--ok)}.kpi.s-warning b{color:var(--warn)}.kpi.s-flag b{color:var(--flag)}
91
+ .kpi .kpi-note{font-size:12px;margin-top:2px}
92
+
93
+ /* badges */
94
+ .badge{display:inline-flex;align-items:center;gap:5px;padding:4px 9px;border-radius:999px;font-size:12px;font-weight:700;
95
+ white-space:nowrap;line-height:1}
96
+ .badge .ic{font-size:11px}
97
+ .s-ok.badge{color:var(--ok);background:var(--ok-bg)}
98
+ .s-warning.badge{color:var(--warn);background:var(--warn-bg)}
99
+ .s-flag.badge{color:var(--flag);background:var(--flag-bg)}
100
+ .s-running.badge,.s-info.badge{color:var(--accent-ink);background:var(--accent-bg)}
101
+ .s-none.badge{color:var(--muted);background:var(--hover)}
102
+ .tag{display:inline-block;padding:2px 8px;border-radius:6px;background:var(--hover);color:var(--muted);font-size:12px}
103
+
104
+ /* tabs */
105
+ .tabs{display:flex;gap:20px;border-bottom:1px solid var(--line);overflow-x:auto}
106
+ .tabs a{padding:10px 2px;color:var(--muted);font-weight:600;font-size:13.5px;border-bottom:2px solid transparent;
107
+ white-space:nowrap}
108
+ .tabs a:hover{color:var(--ink)}
109
+ .tabs a.on{color:var(--accent);border-bottom-color:var(--accent)}
110
+
111
+ /* tables */
112
+ .tablewrap{overflow-x:auto;border:1px solid var(--line);border-radius:10px}
113
+ table{width:100%;border-collapse:collapse;font-size:13px}
114
+ th,td{padding:9px 11px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}
115
+ tr:last-child td{border-bottom:0}
116
+ th{color:var(--muted);background:var(--panel-2);font-weight:600;font-size:12px;white-space:nowrap}
117
+ th.sortable{cursor:pointer;user-select:none}
118
+ th.sortable:hover{color:var(--ink)}
119
+ td.num,th.num{text-align:right;font-variant-numeric:tabular-nums}
120
+ td.num{white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
121
+ tr.click{cursor:pointer}
122
+ tr.click:hover td{background:var(--hover)}
123
+ .counts{display:inline-flex;white-space:nowrap;gap:8px;font-variant-numeric:tabular-nums;font-size:12.5px}
124
+ .counts .c-flag{color:var(--flag)}.counts .c-warning{color:var(--warn)}.counts .c-ok{color:var(--ok)}
125
+
126
+ /* progress (check families) */
127
+ .fam{display:grid;grid-template-columns:minmax(0,1fr) 140px;gap:12px;align-items:center}
128
+ .fam .lbl{display:flex;gap:8px;align-items:center;justify-content:space-between}
129
+ .progress{height:8px;background:var(--track);border-radius:999px;overflow:hidden}
130
+ .progress span{display:block;height:100%;border-radius:999px}
131
+ .progress.s-ok span{background:var(--ok-mark)}.progress.s-warning span{background:var(--warn-mark)}
132
+ .progress.s-flag span{background:var(--flag-mark)}
133
+
134
+ /* findings list */
135
+ .finding{display:grid;grid-template-columns:auto minmax(0,1fr);gap:4px 10px;padding:10px 0;border-bottom:1px solid var(--line)}
136
+ .finding:last-child{border-bottom:0}
137
+ .finding a{font-weight:600}
138
+ .finding a:hover{color:var(--accent)}
139
+ .finding p{grid-column:2;margin:0;color:var(--muted);font-size:13px;display:-webkit-box;-webkit-line-clamp:2;
140
+ -webkit-box-orient:vertical;overflow:hidden}
141
+
142
+ /* checks master-detail */
143
+ .split{display:grid;grid-template-columns:300px minmax(0,1fr);gap:16px;align-items:start}
144
+ .checklist{display:flex;flex-direction:column;gap:2px;padding:12px}
145
+ .checklist .grp{font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin:10px 8px 4px}
146
+ .checklist .grp:first-of-type{margin-top:4px}
147
+ .checklist a{display:flex;gap:9px;align-items:center;padding:7px 9px;border-radius:8px;font-size:13px}
148
+ .checklist a:hover{background:var(--hover)}
149
+ .checklist a.sel{background:var(--accent-bg);color:var(--accent-ink);font-weight:600}
150
+ .dot{width:20px;height:20px;border-radius:50%;display:inline-grid;place-items:center;font-size:11px;font-weight:800;flex:none}
151
+ .dot.s-ok{background:var(--ok-bg);color:var(--ok)}.dot.s-warning{background:var(--warn-bg);color:var(--warn)}
152
+ .dot.s-flag{background:var(--flag-bg);color:var(--flag)}.dot.s-none{background:var(--hover);color:var(--muted)}
153
+ .explain{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
154
+ .explain div{background:var(--panel-2);border:1px solid var(--line);border-radius:10px;padding:10px 12px;font-size:13px}
155
+ .explain b{display:block;font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin-bottom:4px}
156
+ .summary{font-size:14.5px;margin:0}
157
+ dl.kv{display:grid;grid-template-columns:minmax(120px,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}
158
+ dl.kv dt{color:var(--muted)}
159
+ dl.kv dd{margin:0;min-width:0;overflow-wrap:anywhere}
160
+ details.raw summary{cursor:pointer;color:var(--muted);font-size:13px}
161
+
162
+ /* bar lists and charts */
163
+ .barlist{display:grid;grid-template-columns:minmax(80px,max-content) minmax(60px,1fr) max-content;gap:6px 10px;
164
+ align-items:center;font-size:12.5px}
165
+ .barlist .bl-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:260px}
166
+ .barlist .bl-track{height:10px}
167
+ .barlist .bl-bar{height:10px;background:var(--accent);border-radius:0 4px 4px 0;min-width:2px}
168
+ .barlist .bl-val{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-variant-numeric:tabular-nums}
169
+ .legend{display:flex;gap:14px;flex-wrap:wrap;font-size:12.5px;color:var(--muted)}
170
+ .legend span{display:inline-flex;align-items:center;gap:6px}
171
+ .sw{width:10px;height:10px;border-radius:3px;display:inline-block}
172
+ .sw.s-ok{background:var(--ok-mark)}.sw.s-warning{background:var(--warn-mark)}.sw.s-flag{background:var(--flag-mark)}
173
+ .hbars{display:flex;flex-direction:column;gap:2px;font-size:12.5px}
174
+ .hb-row{display:grid;grid-template-columns:minmax(0,210px) minmax(60px,1fr) 64px;gap:10px;align-items:center;
175
+ padding:4px 6px;border-radius:6px}
176
+ .hb-row:not(.axis):hover,.hb-row:focus{background:var(--hover);outline:none}
177
+ .hb-label{text-align:right;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
178
+ .hb-track{display:flex;gap:2px;height:14px}
179
+ .hb-track.gridded{background:linear-gradient(to right,var(--line) 1px,transparent 1px) 0 0/25% 100% repeat-x}
180
+ .hb-seg{height:100%;border-radius:3px;min-width:2px}
181
+ .hb-seg.s-ok{background:var(--ok-mark)}.hb-seg.s-warning{background:var(--warn-mark)}.hb-seg.s-flag{background:var(--flag-mark)}
182
+ .hb-seg.acc{background:var(--accent);border-radius:0 4px 4px 0}
183
+ .hb-val{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-variant-numeric:tabular-nums;color:var(--ink)}
184
+ .hb-ticks{position:relative;height:16px;color:var(--muted);font-size:11.5px}
185
+ .hb-ticks span{position:absolute;transform:translateX(-50%)}
186
+ .hb-ticks span:first-child{transform:none}
187
+ /* chart kit (charts.js) */
188
+ .section-title{margin:8px 0 -4px;font-size:1.05rem}
189
+ .viz{display:flex;flex-direction:column;gap:8px;min-width:0}
190
+ .plot{width:100%;min-width:0}
191
+ svg.chart{display:block;overflow:visible}
192
+ svg.chart text{font-family:inherit;font-size:12px;fill:var(--muted)}
193
+ svg.chart .row-label{fill:var(--ink)}
194
+ svg.chart .row-label.mark-flag{fill:var(--flag);font-weight:600}
195
+ svg.chart .row-label.mark-warning{fill:var(--warn);font-weight:600}
196
+ svg.chart .axis-label{font-size:11.5px}
197
+ svg.chart .point-label{fill:var(--ink);font-size:11.5px;pointer-events:none}
198
+ svg.chart .ref-text{font-size:11px}
199
+ svg.chart .grid-line{stroke:var(--line);stroke-width:1}
200
+ svg.chart .ref-line{stroke:var(--muted);stroke-width:1.5}
201
+ svg.chart .connector{stroke:var(--ref);stroke-width:2;pointer-events:none}
202
+ svg.chart .band{fill:var(--s1);opacity:.14}
203
+ svg.chart .dot-mark{stroke:var(--panel);stroke-width:2;pointer-events:none}
204
+ svg.chart .dot-mark.hit-dot{pointer-events:all;cursor:default}
205
+ svg.chart .row-label,svg.chart .tick{pointer-events:none}
206
+ svg.chart .hit{fill:transparent}
207
+ svg.chart .hit:hover,svg.chart .hit:focus{fill:var(--hover);outline:none}
208
+ svg.chart circle.hit:hover,svg.chart circle.hit:focus{fill:var(--accent-bg)}
209
+ svg .c-s1{fill:var(--s1)}svg .c-s2{fill:var(--s2)}svg .c-ref{fill:var(--ref)}
210
+ .hb-seg.c-s1,.sw-dot.c-s1{background:var(--s1)}.hb-seg.c-s2,.sw-dot.c-s2{background:var(--s2)}
211
+ .hb-seg.c-ref,.sw-dot.c-ref{background:var(--ref)}
212
+ .sw-dot{width:10px;height:10px;border-radius:50%;display:inline-block}
213
+ .sw-line{width:14px;height:2px;background:var(--muted);display:inline-block}
214
+ .sw-band{width:14px;height:10px;border-radius:3px;background:var(--s1);opacity:.25;display:inline-block}
215
+ .seq-bar{width:90px;height:10px;border-radius:3px;display:inline-block;background:linear-gradient(to right,var(--seq-lo),var(--seq-hi))}
216
+ .stats{display:flex;flex-wrap:wrap;gap:8px}
217
+ .stat{background:var(--panel-2);border:1px solid var(--line);border-radius:9px;padding:6px 10px;font-size:12px;color:var(--muted);
218
+ min-width:0;max-width:100%}
219
+ .stat b{display:block;color:var(--ink);font-size:14px;overflow-wrap:anywhere}
220
+ .stat.s-flag b{color:var(--flag)}.stat.s-warning b{color:var(--warn)}.stat.s-ok b{color:var(--ok)}
221
+ .heatwrap{border:0}
222
+ table.heat{border-collapse:separate;border-spacing:2px;width:auto;font-size:12px}
223
+ table.heat th,table.heat td{border:0;padding:0}
224
+ table.heat th.col{height:110px;vertical-align:bottom;background:none;padding:0 0 4px}
225
+ table.heat th.col span{writing-mode:vertical-rl;transform:rotate(180deg);white-space:nowrap;font-weight:500;color:var(--muted)}
226
+ table.heat th.rowh{background:none;text-align:right;padding-right:8px;white-space:nowrap;font-weight:500;color:var(--ink)}
227
+ table.heat th.corner{background:none;vertical-align:bottom;font-weight:500;padding:0 8px 4px 0;text-align:right}
228
+ table.heat td.hcell{min-width:42px;height:28px;text-align:center;border-radius:4px;color:var(--ink);
229
+ font-variant-numeric:tabular-nums;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}
230
+ table.heat td.hcell.dark{color:var(--seq-ink-hi)}
231
+ table.heat td.hcell:hover,table.heat td.hcell:focus{outline:2px solid var(--ink);outline-offset:-1px}
232
+ table.heat td.empty-cell{color:var(--muted);background:var(--panel-2)}
233
+ .tip{position:fixed;z-index:50;pointer-events:none;background:var(--panel);color:var(--ink);border:1px solid var(--line);
234
+ border-radius:8px;padding:8px 10px;font-size:12.5px;box-shadow:0 8px 24px rgba(0,0,0,.18);max-width:320px}
235
+ .tip b{display:block;margin-bottom:2px}
236
+
237
+ /* audit matrix */
238
+ .matrix td,.matrix th{padding:6px 8px}
239
+ .matrix th.ds{writing-mode:vertical-rl;transform:rotate(180deg);height:170px;vertical-align:bottom;white-space:nowrap;text-align:left}
240
+ .matrix td.cell{text-align:center;padding:4px}
241
+ .matrix td.cell a{display:inline-grid}
242
+ .matrix td.check{white-space:nowrap}
243
+
244
+ /* code, logs, errors */
245
+ pre.log,pre.code{margin:0;background:var(--code);color:var(--code-ink);border-radius:10px;padding:12px;overflow:auto;
246
+ font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere}
247
+ pre.log{min-height:300px;max-height:62vh}
248
+ pre.code{max-height:40vh}
249
+ .error-box{border:1px solid var(--flag);background:var(--flag-bg);color:var(--flag);border-radius:10px;padding:10px 12px}
250
+ .runprog{display:flex;flex-direction:column;gap:8px}
251
+ .runprog:empty{display:none}
252
+ .msg{font-size:13px}.msg.err{color:var(--flag)}.msg.ok{color:var(--ok)}
253
+ .empty{color:var(--muted);text-align:center;padding:36px 12px}
254
+ .loading{color:var(--muted);padding:24px 0}
255
+
256
+ /* stepper (new run) */
257
+ .wizard{display:flex;gap:8px}
258
+ .wizard div{flex:1;background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:9px;text-align:center;
259
+ color:var(--muted);font-size:13px;font-weight:600}
260
+ .wizard .done{color:var(--ok);border-color:var(--ok-mark)}
261
+ .wizard .active{background:var(--accent-bg);border-color:var(--accent);color:var(--accent-ink)}
262
+ .wizard .bad{background:var(--flag-bg);border-color:var(--flag-mark);color:var(--flag)}
263
+
264
+ /* recommendations */
265
+ .rec-card{border:1px solid var(--line);background:var(--panel-2);border-radius:10px;padding:12px 14px;
266
+ display:flex;flex-direction:column;gap:6px}
267
+ .rec-head{display:flex;align-items:center;gap:8px}
268
+ .rec-card p{margin:0}
269
+
270
+ /* files + scorecard */
271
+ .files{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px}
272
+ .files a{border:1px solid var(--line);border-radius:9px;padding:9px 12px;font-size:13px;overflow:hidden;
273
+ text-overflow:ellipsis;white-space:nowrap}
274
+ .files a:hover{border-color:var(--accent);color:var(--accent)}
275
+ iframe{width:100%;min-height:75vh;border:1px solid var(--line);border-radius:10px;background:#fff}
276
+
277
+ @media (max-width:1100px){.g2,.g2-wide,.split{grid-template-columns:minmax(0,1fr)}}
278
+ @media (max-width:820px){
279
+ .shell{grid-template-columns:1fr}
280
+ .sidebar{position:fixed;left:0;top:0;width:250px;transform:translateX(-100%);transition:transform .18s}
281
+ .sidebar.open{transform:none;box-shadow:0 0 40px rgba(0,0,0,.35)}
282
+ #menu{display:inline-flex}
283
+ .top{padding:0 16px}.main{padding:16px}
284
+ .explain{grid-template-columns:1fr}
285
+ .fam{grid-template-columns:minmax(0,1fr) 90px}
286
+ .hb-row{grid-template-columns:minmax(0,110px) minmax(40px,1fr) 52px;gap:6px}
287
+ .wizard{flex-wrap:wrap}.wizard div{flex-basis:45%}
288
+ }
289
+ @media print{.sidebar,.top{display:none}.shell{display:block}}
290
+ /* not_applicable: the check had no input to test; neutral, never a result color */
291
+ .s-not_applicable.badge,.dot.s-not_applicable{color:var(--muted);background:var(--hover)}
292
+ .sw.s-not_applicable,.hb-seg.s-not_applicable,.progress.s-not_applicable span{background:var(--line)}
293
+ .counts .c-na{color:var(--muted)}
@@ -0,0 +1,3 @@
1
+ """Loading, splitting and preparing IDS datasets: dataset, chunked_io, cache,
2
+ features, label_grouping.
3
+ """
ids2eval/data/cache.py ADDED
@@ -0,0 +1,99 @@
1
+ """Cache the expensive part of the pipeline: raw load + split + label grouping.
2
+
3
+ Deliberately caches BEFORE dedup, not after: dedup_check needs to see
4
+ real, pre-cleanup duplication (see audit/__init__.py's run_audit
5
+ docstring), so a cache boundary placed after dedup would silently make
6
+ dedup_check report zero on every cache hit. Scaling and sampling are
7
+ NOT cached here - they run on an already-capped sample (max_rows /
8
+ MAX_FIT_ROWS) and take seconds, not minutes, so caching them buys
9
+ little while adding staleness risk against config people iterate on
10
+ constantly (preprocessing.scaling/sampling, classifiers.*).
11
+
12
+ Stored under output.dir/.cache/, separate from the user-facing
13
+ train.<fmt>/test.<fmt> written by output.save_preprocessed (those
14
+ represent the final, post-dedup data for external use - a different
15
+ artifact from this internal load-skip cache).
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import hashlib
21
+ import json
22
+ import logging
23
+ import os
24
+ from pathlib import Path
25
+
26
+ import pandas as pd
27
+
28
+ from .. import version_info
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ CACHE_DIRNAME = ".cache"
33
+ MANIFEST_NAME = "manifest.json"
34
+
35
+
36
+ def _input_file_stats(paths: list[str]) -> list[dict]:
37
+ stats = []
38
+ for path in paths:
39
+ try:
40
+ st = os.stat(path)
41
+ stats.append({"path": str(Path(path).resolve()), "size": st.st_size, "mtime": st.st_mtime})
42
+ except FileNotFoundError:
43
+ stats.append({"path": str(Path(path).resolve()), "size": None, "mtime": None})
44
+ return stats
45
+
46
+
47
+ def compute_fingerprint(cfg: dict) -> str:
48
+ dataset_cfg = cfg["dataset"]
49
+ input_paths = dataset_cfg["raw_files"] or [dataset_cfg["train_file"], dataset_cfg["test_file"]]
50
+
51
+ fingerprint_input = {
52
+ "input_files": _input_file_stats(sorted(p for p in input_paths if p)),
53
+ "random_seed": cfg["random_seed"],
54
+ "split_ratio": dataset_cfg["split_ratio"],
55
+ "split_mode": dataset_cfg["split_mode"],
56
+ "group_columns": dataset_cfg["group_columns"],
57
+ "chunk_size": dataset_cfg["chunk_size"],
58
+ "max_rows": dataset_cfg["max_rows"],
59
+ "column_names": dataset_cfg["column_names"],
60
+ "label_column": cfg["schema"]["label_column"],
61
+ "attack_category_column": cfg["schema"]["attack_category_column"],
62
+ "drop_columns": cfg["schema"]["drop_columns"],
63
+ "attack_type_mapping": cfg["label_grouping"]["attack_type_mapping"],
64
+ "ids2eval_version": version_info.get_version_info(),
65
+ }
66
+ encoded = json.dumps(fingerprint_input, sort_keys=True, default=str).encode()
67
+ return hashlib.sha256(encoded).hexdigest()
68
+
69
+
70
+ def try_load(cfg: dict) -> tuple[pd.DataFrame, pd.DataFrame] | None:
71
+ """Return (train_df, test_df) from a valid cache, or None on a miss."""
72
+ cache_dir = Path(cfg["output"]["dir"]) / CACHE_DIRNAME
73
+ manifest_path = cache_dir / MANIFEST_NAME
74
+ train_path, test_path = cache_dir / "train.parquet", cache_dir / "test.parquet"
75
+
76
+ if not (manifest_path.exists() and train_path.exists() and test_path.exists()):
77
+ return None
78
+
79
+ try:
80
+ manifest = json.loads(manifest_path.read_text())
81
+ except (json.JSONDecodeError, OSError):
82
+ return None
83
+
84
+ if manifest.get("fingerprint") != compute_fingerprint(cfg):
85
+ logger.info("Cache found but stale (config or input files changed) - reprocessing")
86
+ return None
87
+
88
+ logger.info("Loading train/test from cache at %s (skips raw load + split + label grouping)", cache_dir)
89
+ return pd.read_parquet(train_path), pd.read_parquet(test_path)
90
+
91
+
92
+ def save(train_df: pd.DataFrame, test_df: pd.DataFrame, cfg: dict) -> None:
93
+ cache_dir = Path(cfg["output"]["dir"]) / CACHE_DIRNAME
94
+ cache_dir.mkdir(parents=True, exist_ok=True)
95
+ train_df.to_parquet(cache_dir / "train.parquet", index=False)
96
+ test_df.to_parquet(cache_dir / "test.parquet", index=False)
97
+ manifest = {"fingerprint": compute_fingerprint(cfg)}
98
+ (cache_dir / MANIFEST_NAME).write_text(json.dumps(manifest, indent=2))
99
+ logger.info("Cached load+split+label-grouping result to %s", cache_dir)