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.
- ids2eval/__init__.py +0 -0
- ids2eval/__main__.py +4 -0
- ids2eval/_json_utils.py +24 -0
- ids2eval/audit/__init__.py +239 -0
- ids2eval/audit/_auc.py +48 -0
- ids2eval/audit/_by_class.py +29 -0
- ids2eval/audit/_checkpoint.py +74 -0
- ids2eval/audit/_fit_score.py +40 -0
- ids2eval/audit/_materiality.py +28 -0
- ids2eval/audit/_status.py +15 -0
- ids2eval/audit/_time.py +14 -0
- ids2eval/audit/artifact_sensitivity.py +88 -0
- ids2eval/audit/class_distribution.py +57 -0
- ids2eval/audit/cross_capture_matrix.py +115 -0
- ids2eval/audit/cross_dataset_drift.py +74 -0
- ids2eval/audit/data_integrity.py +67 -0
- ids2eval/audit/dedup.py +67 -0
- ids2eval/audit/feature_auc_ranking.py +125 -0
- ids2eval/audit/feature_category_ablation.py +98 -0
- ids2eval/audit/flow_group_leakage.py +66 -0
- ids2eval/audit/homogeneity.py +160 -0
- ids2eval/audit/identity_columns.py +171 -0
- ids2eval/audit/known_issues.py +144 -0
- ids2eval/audit/label_conflict.py +92 -0
- ids2eval/audit/leakage.py +99 -0
- ids2eval/audit/near_duplicate_class.py +87 -0
- ids2eval/audit/one_rule.py +82 -0
- ids2eval/audit/port_protocol_shortcut.py +75 -0
- ids2eval/audit/repeated_seed_falsification.py +182 -0
- ids2eval/audit/resplit.py +92 -0
- ids2eval/audit/result_robustness.py +154 -0
- ids2eval/audit/row_order_leakage.py +86 -0
- ids2eval/audit/scenario_holdout.py +115 -0
- ids2eval/audit/schema_fingerprint.py +52 -0
- ids2eval/audit/seed_sensitivity.py +69 -0
- ids2eval/audit/synthetic_realism.py +73 -0
- ids2eval/audit/temporal_leakage.py +58 -0
- ids2eval/audit/temporal_realism.py +81 -0
- ids2eval/cli.py +284 -0
- ids2eval/config.py +362 -0
- ids2eval/dashboard/__init__.py +1 -0
- ids2eval/dashboard/__main__.py +3 -0
- ids2eval/dashboard/server.py +527 -0
- ids2eval/dashboard/static/app.js +1064 -0
- ids2eval/dashboard/static/charts.js +366 -0
- ids2eval/dashboard/static/index.html +33 -0
- ids2eval/dashboard/static/style.css +293 -0
- ids2eval/data/__init__.py +3 -0
- ids2eval/data/cache.py +99 -0
- ids2eval/data/chunked_io.py +387 -0
- ids2eval/data/dataset.py +208 -0
- ids2eval/data/features.py +95 -0
- ids2eval/data/label_grouping.py +38 -0
- ids2eval/modeling/__init__.py +3 -0
- ids2eval/modeling/benchmark.py +360 -0
- ids2eval/modeling/classifiers.py +145 -0
- ids2eval/modeling/preprocessing.py +73 -0
- ids2eval/reporting/__init__.py +2 -0
- ids2eval/reporting/cite.py +57 -0
- ids2eval/reporting/compare_datasets.py +103 -0
- ids2eval/reporting/drift.py +80 -0
- ids2eval/reporting/intervention_manifest.py +83 -0
- ids2eval/reporting/recommend.py +402 -0
- ids2eval/reporting/run_manager.py +184 -0
- ids2eval/reporting/scorecard.py +875 -0
- ids2eval/reporting/scorecard_plot.py +162 -0
- ids2eval/version_info.py +36 -0
- ids2eval-0.2.2.dist-info/METADATA +119 -0
- ids2eval-0.2.2.dist-info/RECORD +73 -0
- ids2eval-0.2.2.dist-info/WHEEL +5 -0
- ids2eval-0.2.2.dist-info/entry_points.txt +4 -0
- ids2eval-0.2.2.dist-info/licenses/LICENSE +661 -0
- 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)}
|
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)
|