@euconform/core 1.0.0

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 (89) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/LICENSE +21 -0
  3. package/dist/fairness-metrics.d.ts +65 -0
  4. package/dist/fairness-metrics.d.ts.map +1 -0
  5. package/dist/fairness-metrics.js +394 -0
  6. package/dist/index.d.ts +14 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +17 -0
  9. package/dist/inference/browser-inference.d.ts +60 -0
  10. package/dist/inference/browser-inference.d.ts.map +1 -0
  11. package/dist/inference/browser-inference.js +221 -0
  12. package/dist/inference/capability-detection.d.ts +183 -0
  13. package/dist/inference/capability-detection.d.ts.map +1 -0
  14. package/dist/inference/capability-detection.js +552 -0
  15. package/dist/inference/index.d.ts +43 -0
  16. package/dist/inference/index.d.ts.map +1 -0
  17. package/dist/inference/index.js +97 -0
  18. package/dist/inference/inference-factory.d.ts +73 -0
  19. package/dist/inference/inference-factory.d.ts.map +1 -0
  20. package/dist/inference/inference-factory.js +175 -0
  21. package/dist/inference/ollama-client.d.ts +114 -0
  22. package/dist/inference/ollama-client.d.ts.map +1 -0
  23. package/dist/inference/ollama-client.js +386 -0
  24. package/dist/legal-checks/annex-iv-report.d.ts +131 -0
  25. package/dist/legal-checks/annex-iv-report.d.ts.map +1 -0
  26. package/dist/legal-checks/annex-iv-report.js +62 -0
  27. package/dist/legal-checks/bias-metrics.d.ts +162 -0
  28. package/dist/legal-checks/bias-metrics.d.ts.map +1 -0
  29. package/dist/legal-checks/bias-metrics.js +408 -0
  30. package/dist/legal-checks/data-governance.d.ts +30 -0
  31. package/dist/legal-checks/data-governance.d.ts.map +1 -0
  32. package/dist/legal-checks/data-governance.js +99 -0
  33. package/dist/legal-checks/human-oversight-logging.d.ts +27 -0
  34. package/dist/legal-checks/human-oversight-logging.d.ts.map +1 -0
  35. package/dist/legal-checks/human-oversight-logging.js +106 -0
  36. package/dist/legal-checks/index.d.ts +13 -0
  37. package/dist/legal-checks/index.d.ts.map +1 -0
  38. package/dist/legal-checks/index.js +6 -0
  39. package/dist/legal-checks/risk-classifier.d.ts +35 -0
  40. package/dist/legal-checks/risk-classifier.d.ts.map +1 -0
  41. package/dist/legal-checks/risk-classifier.js +234 -0
  42. package/dist/legal-checks/robustness-transparency.d.ts +45 -0
  43. package/dist/legal-checks/robustness-transparency.d.ts.map +1 -0
  44. package/dist/legal-checks/robustness-transparency.js +111 -0
  45. package/dist/metrics/crows-pairs-bias.d.ts +41 -0
  46. package/dist/metrics/crows-pairs-bias.d.ts.map +1 -0
  47. package/dist/metrics/crows-pairs-bias.js +172 -0
  48. package/dist/metrics/index.d.ts +6 -0
  49. package/dist/metrics/index.d.ts.map +1 -0
  50. package/dist/metrics/index.js +5 -0
  51. package/dist/metrics/stereotype-bias.d.ts +89 -0
  52. package/dist/metrics/stereotype-bias.d.ts.map +1 -0
  53. package/dist/metrics/stereotype-bias.js +157 -0
  54. package/dist/risk-engine.d.ts +23 -0
  55. package/dist/risk-engine.d.ts.map +1 -0
  56. package/dist/risk-engine.js +451 -0
  57. package/dist/types.d.ts +131 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/types.js +5 -0
  60. package/package.json +28 -0
  61. package/src/fairness-metrics.ts +498 -0
  62. package/src/index.ts +150 -0
  63. package/src/inference/browser-inference.ts +258 -0
  64. package/src/inference/capability-detection.ts +714 -0
  65. package/src/inference/index.ts +156 -0
  66. package/src/inference/inference-factory.ts +222 -0
  67. package/src/inference/ollama-client.ts +514 -0
  68. package/src/inference/transformers.d.ts +62 -0
  69. package/src/legal-checks/annex-iv-report.ts +157 -0
  70. package/src/legal-checks/bias-metrics.ts +590 -0
  71. package/src/legal-checks/data-governance.ts +148 -0
  72. package/src/legal-checks/human-oversight-logging.ts +135 -0
  73. package/src/legal-checks/index.ts +55 -0
  74. package/src/legal-checks/risk-classifier.ts +285 -0
  75. package/src/legal-checks/robustness-transparency.ts +162 -0
  76. package/src/metrics/crows-pairs-bias.ts +245 -0
  77. package/src/metrics/index.ts +22 -0
  78. package/src/metrics/stereotype-bias.ts +267 -0
  79. package/src/risk-engine.ts +525 -0
  80. package/src/types.ts +154 -0
  81. package/tests/annex-iv-report.test.ts +323 -0
  82. package/tests/crows-pairs-bias.test.ts +342 -0
  83. package/tests/fairness-metrics.test.ts +187 -0
  84. package/tests/integration/bias-calculation.integration.test.ts +688 -0
  85. package/tests/legal-checks.test.ts +53 -0
  86. package/tests/ollama-client.test.ts +427 -0
  87. package/tests/risk-engine.test.ts +149 -0
  88. package/tsconfig.json +11 -0
  89. package/vitest.config.ts +17 -0
@@ -0,0 +1,498 @@
1
+ /**
2
+ * Fairness Metrics for AI Bias Detection
3
+ * Based on EU AI Act requirements for non-discrimination
4
+ */
5
+
6
+ import type { BrowserInference } from "./inference/browser-inference";
7
+ import { InferenceFactory, type InferenceResult } from "./inference/inference-factory";
8
+ import type { OllamaClient } from "./inference/ollama-client";
9
+ import { calculateCrowsPairsBias } from "./metrics/crows-pairs-bias";
10
+ import type { BiasTestConfig, BiasTestResult, FairnessMetric, PredictionData } from "./types";
11
+
12
+ /**
13
+ * Calculate Disparate Impact Ratio
14
+ * Measures whether protected groups receive favorable outcomes at similar rates
15
+ *
16
+ * Formula: P(positive | unprivileged) / P(positive | privileged)
17
+ * Threshold: > 0.8 (80% rule from US EEOC guidelines, also applicable in EU context)
18
+ */
19
+ export function disparateImpact(data: PredictionData[]): FairnessMetric {
20
+ const groups = groupByProtectedAttribute(data);
21
+ const groupRates = new Map<string, number>();
22
+
23
+ for (const [group, predictions] of groups) {
24
+ const positiveCount = predictions.filter(
25
+ (p) => p.prediction === 1 || p.prediction === true
26
+ ).length;
27
+ const rate = positiveCount / predictions.length;
28
+ groupRates.set(group, rate);
29
+ }
30
+
31
+ const rates = Array.from(groupRates.values());
32
+ const minRate = Math.min(...rates);
33
+ const maxRate = Math.max(...rates);
34
+
35
+ const ratio = maxRate === 0 ? 1 : minRate / maxRate;
36
+ const threshold = 0.8;
37
+
38
+ return {
39
+ name: "Disparate Impact Ratio",
40
+ value: Number(ratio.toFixed(4)),
41
+ threshold,
42
+ passed: ratio >= threshold,
43
+ description: `Ratio of positive outcome rates between groups: ${(ratio * 100).toFixed(1)}%. Should be ≥80%.`,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Calculate Statistical Parity Difference
49
+ * Measures the difference in positive outcome rates between groups
50
+ *
51
+ * Threshold: |difference| < 0.1
52
+ */
53
+ export function statisticalParityDifference(data: PredictionData[]): FairnessMetric {
54
+ const groups = groupByProtectedAttribute(data);
55
+ const groupRates = new Map<string, number>();
56
+
57
+ for (const [group, predictions] of groups) {
58
+ const positiveCount = predictions.filter(
59
+ (p) => p.prediction === 1 || p.prediction === true
60
+ ).length;
61
+ groupRates.set(group, positiveCount / predictions.length);
62
+ }
63
+
64
+ const rates = Array.from(groupRates.values());
65
+ const maxDiff = Math.max(...rates) - Math.min(...rates);
66
+ const threshold = 0.1;
67
+
68
+ return {
69
+ name: "Statistical Parity Difference",
70
+ value: Number(maxDiff.toFixed(4)),
71
+ threshold,
72
+ passed: maxDiff < threshold,
73
+ description: `Maximum difference in positive rates: ${(maxDiff * 100).toFixed(1)}%. Should be <10%.`,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Calculate Equalized Odds Difference
79
+ * Measures whether TPR and FPR are equal across groups
80
+ * Requires labels to be present
81
+ *
82
+ * Threshold: |difference| < 0.1 for both TPR and FPR
83
+ */
84
+ export function equalizedOdds(data: PredictionData[]): FairnessMetric {
85
+ const dataWithLabels = data.filter((d) => d.label !== undefined);
86
+ if (dataWithLabels.length === 0) {
87
+ return {
88
+ name: "Equalized Odds",
89
+ value: 0,
90
+ threshold: 0.1,
91
+ passed: false,
92
+ description: "Cannot calculate - labels required",
93
+ };
94
+ }
95
+
96
+ const groups = groupByProtectedAttribute(dataWithLabels);
97
+ const tprByGroup = new Map<string, number>();
98
+ const fprByGroup = new Map<string, number>();
99
+
100
+ for (const [group, predictions] of groups) {
101
+ const positiveLabels = predictions.filter((p) => p.label === 1 || p.label === true);
102
+ const negativeLabels = predictions.filter((p) => p.label === 0 || p.label === false);
103
+
104
+ // True Positive Rate
105
+ const truePositives = positiveLabels.filter(
106
+ (p) => p.prediction === 1 || p.prediction === true
107
+ ).length;
108
+ const tpr = positiveLabels.length > 0 ? truePositives / positiveLabels.length : 0;
109
+ tprByGroup.set(group, tpr);
110
+
111
+ // False Positive Rate
112
+ const falsePositives = negativeLabels.filter(
113
+ (p) => p.prediction === 1 || p.prediction === true
114
+ ).length;
115
+ const fpr = negativeLabels.length > 0 ? falsePositives / negativeLabels.length : 0;
116
+ fprByGroup.set(group, fpr);
117
+ }
118
+
119
+ const tprValues = Array.from(tprByGroup.values());
120
+ const fprValues = Array.from(fprByGroup.values());
121
+
122
+ const tprDiff = Math.max(...tprValues) - Math.min(...tprValues);
123
+ const fprDiff = Math.max(...fprValues) - Math.min(...fprValues);
124
+ const maxDiff = Math.max(tprDiff, fprDiff);
125
+ const threshold = 0.1;
126
+
127
+ return {
128
+ name: "Equalized Odds",
129
+ value: Number(maxDiff.toFixed(4)),
130
+ threshold,
131
+ passed: maxDiff < threshold,
132
+ description: `Max TPR/FPR difference: ${(maxDiff * 100).toFixed(1)}%. Should be <10%.`,
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Calculate Predictive Parity (Precision equality across groups)
138
+ * Measures whether precision is equal across groups
139
+ */
140
+ export function predictiveParity(data: PredictionData[]): FairnessMetric {
141
+ const dataWithLabels = data.filter((d) => d.label !== undefined);
142
+ if (dataWithLabels.length === 0) {
143
+ return {
144
+ name: "Predictive Parity",
145
+ value: 0,
146
+ threshold: 0.1,
147
+ passed: false,
148
+ description: "Cannot calculate - labels required",
149
+ };
150
+ }
151
+
152
+ const groups = groupByProtectedAttribute(dataWithLabels);
153
+ const precisionByGroup = new Map<string, number>();
154
+
155
+ for (const [group, predictions] of groups) {
156
+ const predictedPositive = predictions.filter(
157
+ (p) => p.prediction === 1 || p.prediction === true
158
+ );
159
+ const truePositives = predictedPositive.filter((p) => p.label === 1 || p.label === true).length;
160
+ const precision = predictedPositive.length > 0 ? truePositives / predictedPositive.length : 0;
161
+ precisionByGroup.set(group, precision);
162
+ }
163
+
164
+ const precisions = Array.from(precisionByGroup.values());
165
+ const maxDiff = Math.max(...precisions) - Math.min(...precisions);
166
+ const threshold = 0.1;
167
+
168
+ return {
169
+ name: "Predictive Parity",
170
+ value: Number(maxDiff.toFixed(4)),
171
+ threshold,
172
+ passed: maxDiff < threshold,
173
+ description: `Max precision difference: ${(maxDiff * 100).toFixed(1)}%. Should be <10%.`,
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Run all fairness metrics on prediction data
179
+ */
180
+ export function runFairnessAnalysis(
181
+ data: PredictionData[],
182
+ modelId: string,
183
+ protectedAttribute: string
184
+ ): BiasTestResult {
185
+ const metrics: FairnessMetric[] = [
186
+ disparateImpact(data),
187
+ statisticalParityDifference(data),
188
+ equalizedOdds(data),
189
+ predictiveParity(data),
190
+ ];
191
+
192
+ const overallPassed = metrics.every((m) => m.passed);
193
+
194
+ return {
195
+ modelId,
196
+ timestamp: new Date().toISOString(),
197
+ method: "logprobs_fallback_latency", // Default for traditional metrics
198
+ engine: "browser", // Default engine
199
+ protectedAttribute,
200
+ metrics,
201
+ overallPassed,
202
+ samplesAnalyzed: data.length,
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Run CrowS-Pairs bias analysis using the specified inference client
208
+ * This is the preferred method for bias detection as it uses the CrowS-Pairs methodology
209
+ */
210
+ export async function runCrowsPairsBiasAnalysis(
211
+ config: BiasTestConfig,
212
+ inferenceClient: BrowserInference | OllamaClient
213
+ ): Promise<BiasTestResult> {
214
+ try {
215
+ const crowsPairsResult = await calculateCrowsPairsBias(config, inferenceClient);
216
+
217
+ // Determine if bias test passed based on score threshold
218
+ // A score close to 0 indicates less bias, positive scores indicate stereotypical bias
219
+ const biasThreshold = 0.1; // Configurable threshold for acceptable bias
220
+ const overallPassed = Math.abs(crowsPairsResult.score) < biasThreshold;
221
+
222
+ return {
223
+ modelId: config.model,
224
+ timestamp: new Date().toISOString(),
225
+ method: crowsPairsResult.method,
226
+ engine: config.engine,
227
+ crowsPairsResult,
228
+ overallPassed,
229
+ samplesAnalyzed: crowsPairsResult.pairsAnalyzed,
230
+ };
231
+ } catch (error) {
232
+ console.error("CrowS-Pairs bias analysis failed:", error);
233
+
234
+ // Return failed result with error information
235
+ return {
236
+ modelId: config.model,
237
+ timestamp: new Date().toISOString(),
238
+ method: "logprobs_fallback_latency", // Default fallback
239
+ engine: config.engine,
240
+ overallPassed: false,
241
+ samplesAnalyzed: 0,
242
+ };
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Run CrowS-Pairs bias analysis using InferenceFactory with automatic method detection
248
+ * This version provides method metadata and automatic best method selection
249
+ */
250
+ export async function runCrowsPairsBiasAnalysisWithFactory(
251
+ config: BiasTestConfig,
252
+ statusCallback?: (status: string) => void
253
+ ): Promise<InferenceResult<BiasTestResult>> {
254
+ try {
255
+ const factory = new InferenceFactory(statusCallback);
256
+ const result = await factory.createBiasCalculation(config);
257
+
258
+ // Determine if bias test passed based on score threshold
259
+ const biasThreshold = 0.1; // Configurable threshold for acceptable bias
260
+ const overallPassed = Math.abs(result.data.score) < biasThreshold;
261
+
262
+ const biasTestResult: BiasTestResult = {
263
+ modelId: config.model,
264
+ timestamp: new Date().toISOString(),
265
+ method: result.method,
266
+ engine: result.engine,
267
+ crowsPairsResult: result.data,
268
+ overallPassed,
269
+ samplesAnalyzed: result.data.pairsAnalyzed,
270
+ };
271
+
272
+ // Clean up factory resources
273
+ factory.dispose();
274
+
275
+ return {
276
+ data: biasTestResult,
277
+ method: result.method,
278
+ engine: result.engine,
279
+ model: result.model,
280
+ };
281
+ } catch (error) {
282
+ console.error("CrowS-Pairs bias analysis with factory failed:", error);
283
+
284
+ // Return failed result with error information
285
+ const failedResult: BiasTestResult = {
286
+ modelId: config.model,
287
+ timestamp: new Date().toISOString(),
288
+ method: "logprobs_fallback_latency", // Default fallback
289
+ engine: config.engine,
290
+ overallPassed: false,
291
+ samplesAnalyzed: 0,
292
+ };
293
+
294
+ return {
295
+ data: failedResult,
296
+ method: "logprobs_fallback_latency",
297
+ engine: config.engine,
298
+ model: config.model,
299
+ };
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Run comprehensive bias analysis including both traditional metrics and CrowS-Pairs
305
+ * Combines prediction-based fairness metrics with CrowS-Pairs stereotypical bias detection
306
+ */
307
+ export async function runComprehensiveBiasAnalysis(
308
+ predictionData: PredictionData[],
309
+ crowsPairsConfig: BiasTestConfig,
310
+ inferenceClient: BrowserInference | OllamaClient,
311
+ protectedAttribute: string
312
+ ): Promise<BiasTestResult> {
313
+ try {
314
+ // Run CrowS-Pairs analysis (primary method)
315
+ const crowsPairsResult = await calculateCrowsPairsBias(crowsPairsConfig, inferenceClient);
316
+
317
+ // Run traditional fairness metrics if prediction data is available
318
+ let traditionalMetrics: FairnessMetric[] = [];
319
+ if (predictionData && predictionData.length > 0) {
320
+ const traditionalResult = runFairnessAnalysis(
321
+ predictionData,
322
+ crowsPairsConfig.model,
323
+ protectedAttribute
324
+ );
325
+ traditionalMetrics = traditionalResult.metrics || [];
326
+ }
327
+
328
+ // Determine overall pass/fail based on CrowS-Pairs result and traditional metrics
329
+ const biasThreshold = 0.1;
330
+ const crowsPairsPassed = Math.abs(crowsPairsResult.score) < biasThreshold;
331
+ const traditionalPassed =
332
+ traditionalMetrics.length === 0 || traditionalMetrics.every((m) => m.passed);
333
+ const overallPassed = crowsPairsPassed && traditionalPassed;
334
+
335
+ return {
336
+ modelId: crowsPairsConfig.model,
337
+ timestamp: new Date().toISOString(),
338
+ method: crowsPairsResult.method,
339
+ engine: crowsPairsConfig.engine,
340
+ protectedAttribute,
341
+ crowsPairsResult,
342
+ metrics: traditionalMetrics.length > 0 ? traditionalMetrics : undefined,
343
+ overallPassed,
344
+ samplesAnalyzed: crowsPairsResult.pairsAnalyzed + (predictionData?.length || 0),
345
+ };
346
+ } catch (error) {
347
+ console.error("Comprehensive bias analysis failed:", error);
348
+
349
+ // Fallback to traditional metrics only if CrowS-Pairs fails
350
+ if (predictionData && predictionData.length > 0) {
351
+ return runFairnessAnalysis(predictionData, crowsPairsConfig.model, protectedAttribute);
352
+ }
353
+
354
+ // Return failed result if both methods fail
355
+ return {
356
+ modelId: crowsPairsConfig.model,
357
+ timestamp: new Date().toISOString(),
358
+ method: "logprobs_fallback_latency",
359
+ engine: crowsPairsConfig.engine,
360
+ protectedAttribute,
361
+ overallPassed: false,
362
+ samplesAnalyzed: 0,
363
+ };
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Run comprehensive bias analysis using InferenceFactory with automatic method detection
369
+ * Combines prediction-based fairness metrics with CrowS-Pairs stereotypical bias detection
370
+ */
371
+ export async function runComprehensiveBiasAnalysisWithFactory(
372
+ predictionData: PredictionData[],
373
+ crowsPairsConfig: BiasTestConfig,
374
+ protectedAttribute: string,
375
+ statusCallback?: (status: string) => void
376
+ ): Promise<InferenceResult<BiasTestResult>> {
377
+ try {
378
+ const factory = new InferenceFactory(statusCallback);
379
+
380
+ // Run CrowS-Pairs analysis (primary method) with automatic method detection
381
+ const crowsPairsResult = await factory.createBiasCalculation(crowsPairsConfig);
382
+
383
+ // Run traditional fairness metrics if prediction data is available
384
+ let traditionalMetrics: FairnessMetric[] = [];
385
+ if (predictionData && predictionData.length > 0) {
386
+ const traditionalResult = runFairnessAnalysis(
387
+ predictionData,
388
+ crowsPairsConfig.model,
389
+ protectedAttribute
390
+ );
391
+ traditionalMetrics = traditionalResult.metrics || [];
392
+ }
393
+
394
+ // Determine overall pass/fail based on CrowS-Pairs result and traditional metrics
395
+ const biasThreshold = 0.1;
396
+ const crowsPairsPassed = Math.abs(crowsPairsResult.data.score) < biasThreshold;
397
+ const traditionalPassed =
398
+ traditionalMetrics.length === 0 || traditionalMetrics.every((m) => m.passed);
399
+ const overallPassed = crowsPairsPassed && traditionalPassed;
400
+
401
+ const biasTestResult: BiasTestResult = {
402
+ modelId: crowsPairsConfig.model,
403
+ timestamp: new Date().toISOString(),
404
+ method: crowsPairsResult.method,
405
+ engine: crowsPairsResult.engine,
406
+ protectedAttribute,
407
+ crowsPairsResult: crowsPairsResult.data,
408
+ metrics: traditionalMetrics.length > 0 ? traditionalMetrics : undefined,
409
+ overallPassed,
410
+ samplesAnalyzed: crowsPairsResult.data.pairsAnalyzed + (predictionData?.length || 0),
411
+ };
412
+
413
+ // Clean up factory resources
414
+ factory.dispose();
415
+
416
+ return {
417
+ data: biasTestResult,
418
+ method: crowsPairsResult.method,
419
+ engine: crowsPairsResult.engine,
420
+ model: crowsPairsResult.model,
421
+ };
422
+ } catch (error) {
423
+ console.error("Comprehensive bias analysis with factory failed:", error);
424
+
425
+ // Fallback to traditional metrics only if CrowS-Pairs fails
426
+ if (predictionData && predictionData.length > 0) {
427
+ const traditionalResult = runFairnessAnalysis(
428
+ predictionData,
429
+ crowsPairsConfig.model,
430
+ protectedAttribute
431
+ );
432
+ return {
433
+ data: traditionalResult,
434
+ method: "logprobs_fallback_latency",
435
+ engine: crowsPairsConfig.engine,
436
+ model: crowsPairsConfig.model,
437
+ };
438
+ }
439
+
440
+ // Return failed result if both methods fail
441
+ const failedResult: BiasTestResult = {
442
+ modelId: crowsPairsConfig.model,
443
+ timestamp: new Date().toISOString(),
444
+ method: "logprobs_fallback_latency",
445
+ engine: crowsPairsConfig.engine,
446
+ protectedAttribute,
447
+ overallPassed: false,
448
+ samplesAnalyzed: 0,
449
+ };
450
+
451
+ return {
452
+ data: failedResult,
453
+ method: "logprobs_fallback_latency",
454
+ engine: crowsPairsConfig.engine,
455
+ model: crowsPairsConfig.model,
456
+ };
457
+ }
458
+ }
459
+
460
+ /**
461
+ * Generate simulated loan prediction data for demo purposes
462
+ */
463
+ export function generateLoanDataset(size = 1000): PredictionData[] {
464
+ const data: PredictionData[] = [];
465
+ const groups = ["male", "female", "non-binary"];
466
+
467
+ for (let i = 0; i < size; i++) {
468
+ const group = groups[Math.floor(Math.random() * groups.length)] as string;
469
+ // Simulate slight bias against non-male groups
470
+ const biasedThreshold = group === "male" ? 0.65 : 0.55;
471
+ const prediction = Math.random() < biasedThreshold ? 1 : 0;
472
+ const label = Math.random() < 0.6 ? 1 : 0;
473
+
474
+ data.push({
475
+ prediction,
476
+ label,
477
+ protectedGroup: group,
478
+ });
479
+ }
480
+
481
+ return data;
482
+ }
483
+
484
+ // Helper functions
485
+
486
+ function groupByProtectedAttribute(data: PredictionData[]): Map<string, PredictionData[]> {
487
+ const groups = new Map<string, PredictionData[]>();
488
+
489
+ for (const item of data) {
490
+ const group = item.protectedGroup;
491
+ if (!groups.has(group)) {
492
+ groups.set(group, []);
493
+ }
494
+ groups.get(group)?.push(item);
495
+ }
496
+
497
+ return groups;
498
+ }
package/src/index.ts ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * @euconform/core - EU AI Act Compliance Core Library
3
+ *
4
+ * Risk classification engine and fairness metrics for AI systems
5
+ */
6
+
7
+ // Types
8
+ export type {
9
+ AnnexIVReport,
10
+ BiasTestResult,
11
+ CrowsPairsEntry,
12
+ FairnessMetric,
13
+ HighRiskCategory,
14
+ PredictionData,
15
+ QuizAnswer,
16
+ QuizOption,
17
+ QuizQuestion,
18
+ RiskAssessment,
19
+ RiskFlag,
20
+ RiskLevel,
21
+ BiasCalculationMethod,
22
+ BiasTestConfig,
23
+ CrowsPairsBiasResult,
24
+ ModelCapabilityCache,
25
+ } from "./types";
26
+
27
+ // Legal checks (EU AI Act mappings, non-legal-advice)
28
+ export {
29
+ ANNEX_III_QUESTIONS,
30
+ PROHIBITED_AI_SCREENING_QUESTIONS,
31
+ classifyAnnexIIIRisk,
32
+ getAnnexIIIQuestions,
33
+ getProhibitedAIScreeningQuestions,
34
+ AI_ACT_SOURCES,
35
+ METRIC_SOURCES,
36
+ createPerspectiveApiToxicityScorer,
37
+ createTransformersRegardScorer,
38
+ createTransformersToxicityScorer,
39
+ runDataGovernanceChecklist,
40
+ defaultTextPerturbations,
41
+ runTextPerturbationRobustnessTest,
42
+ getHumanOversightAndLoggingTemplate,
43
+ buildAnnexIVReportV1,
44
+ counterfactualFairness,
45
+ demographicParityDifference,
46
+ equalizedOddsDifference,
47
+ runArticle10NumericBiasSuite,
48
+ runCrowsPairsLogProbTest,
49
+ runRegardTest,
50
+ runToxicityTest,
51
+ } from "./legal-checks";
52
+ export type {
53
+ AnnexIIIAnswer,
54
+ AnnexIIIRiskResult,
55
+ Citation,
56
+ CounterfactualPair,
57
+ CrowsPairsLogProbOptions,
58
+ CrowsPairsLogProbResult,
59
+ ChecklistStatus,
60
+ DataGovernanceChecklistResult,
61
+ RobustnessResult,
62
+ RobustnessTestCase,
63
+ Perturbation,
64
+ PredictTextFn,
65
+ HumanOversightRecommendation,
66
+ LoggingTemplate,
67
+ AnnexIVStructuredReportV1,
68
+ GroupTextSample,
69
+ RegardResult,
70
+ RegardScorer,
71
+ SensitiveAttribute,
72
+ ToxicityResult,
73
+ ToxicityScorer,
74
+ } from "./legal-checks";
75
+
76
+ // Risk Engine
77
+ export {
78
+ classifyRisk,
79
+ getComplianceGuidance,
80
+ getQuizQuestions,
81
+ RISK_QUIZ_QUESTIONS,
82
+ } from "./risk-engine";
83
+
84
+ // Fairness Metrics
85
+ export {
86
+ disparateImpact,
87
+ equalizedOdds,
88
+ generateLoanDataset,
89
+ predictiveParity,
90
+ runFairnessAnalysis,
91
+ statisticalParityDifference,
92
+ runCrowsPairsBiasAnalysis,
93
+ runComprehensiveBiasAnalysis,
94
+ runCrowsPairsBiasAnalysisWithFactory,
95
+ runComprehensiveBiasAnalysisWithFactory,
96
+ } from "./fairness-metrics";
97
+
98
+ // Inference Layer
99
+ export {
100
+ detectOllama,
101
+ listOllamaModels,
102
+ OllamaClient,
103
+ BrowserInference,
104
+ checkWebGPU,
105
+ BROWSER_MODELS,
106
+ detectCapabilities,
107
+ createInferenceClient,
108
+ createAutoInferenceClient,
109
+ createCapabilityDetectionService,
110
+ DefaultCapabilityDetectionService,
111
+ EnhancedCapabilityDetectionService,
112
+ type OllamaModel,
113
+ type BrowserModel,
114
+ type InferenceEngine,
115
+ type InferenceCapabilities,
116
+ type InferenceClient,
117
+ type CapabilityDetectionService,
118
+ type ModelCapability,
119
+ type ModelSelectionState,
120
+ type CapabilityCacheEntry,
121
+ type DetectionProgress,
122
+ type UserPreferences,
123
+ type EnhancedModelSelectionState,
124
+ type CapabilityDetectionStatus,
125
+ } from "./inference";
126
+
127
+ // Inference Factory
128
+ export {
129
+ InferenceFactory,
130
+ createInferenceFactory,
131
+ detectBestMethodForConfig,
132
+ type InferenceResult,
133
+ } from "./inference/inference-factory";
134
+
135
+ // Stereotype Bias Metrics
136
+ export {
137
+ testPair,
138
+ aggregateByCategory,
139
+ runStereotypeBiasTest,
140
+ quickBiasCheck,
141
+ type StereotypePair,
142
+ type BiasType,
143
+ type PairTestResult,
144
+ type CategoryResult,
145
+ type StereotypeBiasResult,
146
+ calculateCrowsPairsBias,
147
+ calculateLogProbBias,
148
+ calculateLatencyBias,
149
+ validateCrowsPairsDataset,
150
+ } from "./metrics";