@euconform/core 1.0.1 → 1.3.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/evidence/bundle.d.ts +23 -0
- package/dist/evidence/bundle.d.ts.map +1 -0
- package/dist/evidence/bundle.js +44 -0
- package/dist/evidence/compliance-evaluation.d.ts +12 -0
- package/dist/evidence/compliance-evaluation.d.ts.map +1 -0
- package/dist/evidence/compliance-evaluation.js +78 -0
- package/dist/evidence/hash.d.ts +2 -0
- package/dist/evidence/hash.d.ts.map +1 -0
- package/dist/evidence/hash.js +4 -0
- package/dist/evidence/index.d.ts +13 -0
- package/dist/evidence/index.d.ts.map +1 -0
- package/dist/evidence/index.js +11 -0
- package/dist/evidence/markdown.d.ts +9 -0
- package/dist/evidence/markdown.d.ts.map +1 -0
- package/dist/evidence/markdown.js +169 -0
- package/dist/evidence/output.d.ts +13 -0
- package/dist/evidence/output.d.ts.map +1 -0
- package/dist/evidence/output.js +547 -0
- package/dist/evidence/types.d.ts +230 -0
- package/dist/evidence/types.d.ts.map +1 -0
- package/dist/evidence/types.js +7 -0
- package/dist/evidence/validate.d.ts +8 -0
- package/dist/evidence/validate.d.ts.map +1 -0
- package/dist/evidence/validate.js +175 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/inference/ollama-client.d.ts +3 -0
- package/dist/inference/ollama-client.d.ts.map +1 -1
- package/dist/inference/ollama-client.js +69 -71
- package/dist/legal-checks/annex-iv-report.d.ts +3 -0
- package/dist/legal-checks/annex-iv-report.d.ts.map +1 -1
- package/dist/legal-checks/annex-iv-report.js +1 -0
- package/dist/legal-checks/deadline-timeline.d.ts +57 -0
- package/dist/legal-checks/deadline-timeline.d.ts.map +1 -0
- package/dist/legal-checks/deadline-timeline.js +265 -0
- package/dist/legal-checks/gap-analysis.d.ts +48 -0
- package/dist/legal-checks/gap-analysis.d.ts.map +1 -0
- package/dist/legal-checks/gap-analysis.js +397 -0
- package/dist/legal-checks/gpai-classifier.d.ts +49 -0
- package/dist/legal-checks/gpai-classifier.d.ts.map +1 -0
- package/dist/legal-checks/gpai-classifier.js +197 -0
- package/dist/legal-checks/index.d.ts +6 -0
- package/dist/legal-checks/index.d.ts.map +1 -1
- package/dist/legal-checks/index.js +3 -0
- package/dist/scanner/aggregator.d.ts +7 -0
- package/dist/scanner/aggregator.d.ts.map +1 -0
- package/dist/scanner/aggregator.js +200 -0
- package/dist/scanner/detectors/ai-framework-detector.d.ts +3 -0
- package/dist/scanner/detectors/ai-framework-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/ai-framework-detector.js +74 -0
- package/dist/scanner/detectors/ai-provider-detector.d.ts +3 -0
- package/dist/scanner/detectors/ai-provider-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/ai-provider-detector.js +100 -0
- package/dist/scanner/detectors/compliance-detector.d.ts +3 -0
- package/dist/scanner/detectors/compliance-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/compliance-detector.js +201 -0
- package/dist/scanner/detectors/config-detector.d.ts +3 -0
- package/dist/scanner/detectors/config-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/config-detector.js +248 -0
- package/dist/scanner/detectors/framework-detector.d.ts +3 -0
- package/dist/scanner/detectors/framework-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/framework-detector.js +141 -0
- package/dist/scanner/detectors/index.d.ts +8 -0
- package/dist/scanner/detectors/index.d.ts.map +1 -0
- package/dist/scanner/detectors/index.js +37 -0
- package/dist/scanner/detectors/local-inference-detector.d.ts +3 -0
- package/dist/scanner/detectors/local-inference-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/local-inference-detector.js +93 -0
- package/dist/scanner/detectors/markdown-detector.d.ts +3 -0
- package/dist/scanner/detectors/markdown-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/markdown-detector.js +139 -0
- package/dist/scanner/detectors/package-json-detector.d.ts +3 -0
- package/dist/scanner/detectors/package-json-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/package-json-detector.js +166 -0
- package/dist/scanner/detectors/rag-detector.d.ts +3 -0
- package/dist/scanner/detectors/rag-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/rag-detector.js +132 -0
- package/dist/scanner/detectors/shared.d.ts +18 -0
- package/dist/scanner/detectors/shared.d.ts.map +1 -0
- package/dist/scanner/detectors/shared.js +36 -0
- package/dist/scanner/discovery.d.ts +14 -0
- package/dist/scanner/discovery.d.ts.map +1 -0
- package/dist/scanner/discovery.js +235 -0
- package/dist/scanner/index.d.ts +9 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +8 -0
- package/package.json +6 -3
- package/src/evidence/bundle.ts +65 -0
- package/src/evidence/compliance-evaluation.ts +114 -0
- package/src/evidence/hash.ts +5 -0
- package/src/evidence/index.ts +51 -0
- package/src/evidence/markdown.ts +226 -0
- package/src/evidence/output.ts +717 -0
- package/src/evidence/types.ts +329 -0
- package/src/evidence/validate.ts +270 -0
- package/src/index.ts +25 -0
- package/src/inference/ollama-client.ts +93 -86
- package/src/legal-checks/annex-iv-report.ts +4 -0
- package/src/legal-checks/deadline-timeline.ts +313 -0
- package/src/legal-checks/gap-analysis.ts +454 -0
- package/src/legal-checks/gpai-classifier.ts +281 -0
- package/src/legal-checks/index.ts +33 -0
- package/src/scanner/aggregator.ts +258 -0
- package/src/scanner/detectors/ai-framework-detector.ts +94 -0
- package/src/scanner/detectors/ai-provider-detector.ts +135 -0
- package/src/scanner/detectors/compliance-detector.ts +232 -0
- package/src/scanner/detectors/config-detector.ts +316 -0
- package/src/scanner/detectors/framework-detector.ts +163 -0
- package/src/scanner/detectors/index.ts +40 -0
- package/src/scanner/detectors/local-inference-detector.ts +124 -0
- package/src/scanner/detectors/markdown-detector.ts +176 -0
- package/src/scanner/detectors/package-json-detector.ts +202 -0
- package/src/scanner/detectors/rag-detector.ts +162 -0
- package/src/scanner/detectors/shared.ts +45 -0
- package/src/scanner/discovery.ts +290 -0
- package/src/scanner/index.ts +9 -0
- package/tests/evidence/bundle.test.ts +128 -0
- package/tests/evidence/ecef-schemas.test.ts +316 -0
- package/tests/evidence/hash.test.ts +25 -0
- package/tests/evidence/markdown.test.ts +92 -0
- package/tests/evidence/output.test.ts +203 -0
- package/tests/fixtures/compliance-good/README.md +14 -0
- package/tests/fixtures/compliance-good/lib/ai-service.ts +17 -0
- package/tests/fixtures/compliance-good/lib/review-flow.ts +11 -0
- package/tests/fixtures/compliance-good/package.json +10 -0
- package/tests/fixtures/compliance-good/tests/bias-check.test.ts +13 -0
- package/tests/fixtures/nextjs-openai/README.md +5 -0
- package/tests/fixtures/nextjs-openai/app/page.tsx +11 -0
- package/tests/fixtures/nextjs-openai/package.json +9 -0
- package/tests/fixtures/ollama-rag/.env.example +2 -0
- package/tests/fixtures/ollama-rag/docker-compose.yml +9 -0
- package/tests/fixtures/ollama-rag/package.json +9 -0
- package/tests/fixtures/ollama-rag/server.ts +21 -0
- package/tests/fixtures/plain-webapp/README.md +3 -0
- package/tests/fixtures/plain-webapp/index.js +5 -0
- package/tests/fixtures/plain-webapp/package.json +8 -0
- package/tests/gpai-classifier.test.ts +125 -0
- package/tests/gpai-timeline.test.ts +55 -0
- package/tests/scanner/aggregator.test.ts +122 -0
- package/tests/scanner/detectors.test.ts +274 -0
- package/tests/scanner/discovery.test.ts +87 -0
- package/vitest.config.ts +1 -0
- package/.turbo/turbo-check-types.log +0 -4
- package/.turbo/turbo-test.log +0 -364
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -3282
- package/coverage/coverage-final.json +0 -20
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -161
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/fairness-metrics.ts.html +0 -1579
- package/coverage/src/index.html +0 -161
- package/coverage/src/index.ts.html +0 -535
- package/coverage/src/inference/browser-inference.ts.html +0 -859
- package/coverage/src/inference/capability-detection.ts.html +0 -2227
- package/coverage/src/inference/index.html +0 -176
- package/coverage/src/inference/index.ts.html +0 -553
- package/coverage/src/inference/inference-factory.ts.html +0 -751
- package/coverage/src/inference/ollama-client.ts.html +0 -1627
- package/coverage/src/legal-checks/annex-iv-report.ts.html +0 -556
- package/coverage/src/legal-checks/bias-metrics.ts.html +0 -1855
- package/coverage/src/legal-checks/data-governance.ts.html +0 -529
- package/coverage/src/legal-checks/human-oversight-logging.ts.html +0 -490
- package/coverage/src/legal-checks/index.html +0 -206
- package/coverage/src/legal-checks/index.ts.html +0 -250
- package/coverage/src/legal-checks/risk-classifier.ts.html +0 -940
- package/coverage/src/legal-checks/robustness-transparency.ts.html +0 -571
- package/coverage/src/metrics/crows-pairs-bias.ts.html +0 -820
- package/coverage/src/metrics/index.html +0 -146
- package/coverage/src/metrics/index.ts.html +0 -151
- package/coverage/src/metrics/stereotype-bias.ts.html +0 -886
- package/coverage/src/risk-engine.ts.html +0 -1660
- package/coverage/src/types.ts.html +0 -547
- package/dist/legal-checks/disclaimer.d.ts +0 -2
- package/dist/legal-checks/disclaimer.d.ts.map +0 -1
- package/dist/legal-checks/disclaimer.js +0 -1
|
@@ -224,6 +224,10 @@ export class OllamaClient {
|
|
|
224
224
|
this.capabilityCache = new Map();
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
private isCloudModel(): boolean {
|
|
228
|
+
return this.model.includes(":cloud");
|
|
229
|
+
}
|
|
230
|
+
|
|
227
231
|
/**
|
|
228
232
|
* Ensures the model is available in Ollama before proceeding
|
|
229
233
|
* Throws an error if the model is not found
|
|
@@ -273,6 +277,14 @@ export class OllamaClient {
|
|
|
273
277
|
* Sends a test request with "Der" prompt to detect capability
|
|
274
278
|
*/
|
|
275
279
|
private async testLogProbCapability(): Promise<boolean> {
|
|
280
|
+
// Cloud models (via Ollama proxy) don't support logprobs parameter
|
|
281
|
+
if (this.isCloudModel()) {
|
|
282
|
+
console.log(
|
|
283
|
+
`🔍 ${this.model} is a cloud model - logprobs not supported, using latency method`
|
|
284
|
+
);
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
|
|
276
288
|
try {
|
|
277
289
|
// First check Ollama version
|
|
278
290
|
try {
|
|
@@ -401,69 +413,13 @@ export class OllamaClient {
|
|
|
401
413
|
return supports;
|
|
402
414
|
}
|
|
403
415
|
|
|
404
|
-
|
|
405
|
-
* Get log-probability with automatic fallback to latency method
|
|
406
|
-
* Returns both the calculated value and the method used
|
|
407
|
-
*/
|
|
408
|
-
async getLogProbWithFallback(
|
|
416
|
+
private async tryExactLogProbs(
|
|
409
417
|
prompt: string
|
|
410
|
-
): Promise<{ logprob: number; method: BiasCalculationMethod }> {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const supportsLogProbs = await this.detectLogProbSupport();
|
|
414
|
-
|
|
415
|
-
if (supportsLogProbs) {
|
|
416
|
-
// Try to get exact log-probabilities
|
|
417
|
-
try {
|
|
418
|
-
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
|
419
|
-
method: "POST",
|
|
420
|
-
headers: { "Content-Type": "application/json" },
|
|
421
|
-
body: JSON.stringify({
|
|
422
|
-
model: this.model,
|
|
423
|
-
prompt,
|
|
424
|
-
stream: false,
|
|
425
|
-
logprobs: true,
|
|
426
|
-
options: {
|
|
427
|
-
num_predict: 0, // Only evaluate prompt, don't generate
|
|
428
|
-
temperature: 0,
|
|
429
|
-
},
|
|
430
|
-
}),
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
if (response.ok) {
|
|
434
|
-
const data = await response.json();
|
|
435
|
-
|
|
436
|
-
if (data.logprobs && Array.isArray(data.logprobs) && data.logprobs.length > 0) {
|
|
437
|
-
// Calculate average log-probability across all tokens
|
|
438
|
-
const avgLogProb =
|
|
439
|
-
data.logprobs.reduce(
|
|
440
|
-
(sum: number, item: { token: string; logprob: number }) => sum + item.logprob,
|
|
441
|
-
0
|
|
442
|
-
) / data.logprobs.length;
|
|
443
|
-
console.log(`✅ Using exact log-probabilities for ${this.model}: ${avgLogProb}`);
|
|
444
|
-
return {
|
|
445
|
-
logprob: avgLogProb,
|
|
446
|
-
method: "logprobs_exact",
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
console.warn(`❌ No logprobs in response for ${this.model}, response:`, data);
|
|
450
|
-
} else {
|
|
451
|
-
console.warn(
|
|
452
|
-
`❌ Log-probability request failed for ${this.model}: ${response.status} ${response.statusText}`
|
|
453
|
-
);
|
|
454
|
-
}
|
|
455
|
-
} catch (error) {
|
|
456
|
-
console.warn(
|
|
457
|
-
`❌ Log-probability request failed for ${this.model}, falling back to latency method:`,
|
|
458
|
-
error
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Fallback to latency-based method
|
|
464
|
-
console.log(`⚡ Falling back to latency-based method for ${this.model}`);
|
|
465
|
-
const startTime = performance.now();
|
|
418
|
+
): Promise<{ logprob: number; method: BiasCalculationMethod } | null> {
|
|
419
|
+
const supportsLogProbs = await this.detectLogProbSupport();
|
|
420
|
+
if (!supportsLogProbs || this.isCloudModel()) return null;
|
|
466
421
|
|
|
422
|
+
try {
|
|
467
423
|
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
|
468
424
|
method: "POST",
|
|
469
425
|
headers: { "Content-Type": "application/json" },
|
|
@@ -471,41 +427,92 @@ export class OllamaClient {
|
|
|
471
427
|
model: this.model,
|
|
472
428
|
prompt,
|
|
473
429
|
stream: false,
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
temperature: 0,
|
|
477
|
-
},
|
|
430
|
+
logprobs: true,
|
|
431
|
+
options: { num_predict: 0, temperature: 0 },
|
|
478
432
|
}),
|
|
479
433
|
});
|
|
480
434
|
|
|
481
|
-
const endTime = performance.now();
|
|
482
|
-
|
|
483
435
|
if (!response.ok) {
|
|
484
|
-
|
|
436
|
+
console.warn(
|
|
437
|
+
`❌ Log-probability request failed for ${this.model}: ${response.status} ${response.statusText}`
|
|
438
|
+
);
|
|
439
|
+
return null;
|
|
485
440
|
}
|
|
486
441
|
|
|
487
442
|
const data = await response.json();
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (data.prompt_eval_duration && data.prompt_eval_count) {
|
|
493
|
-
// Use prompt evaluation metrics (preferred)
|
|
494
|
-
latency = data.prompt_eval_duration / data.prompt_eval_count / 1e9; // ns to seconds per token
|
|
495
|
-
} else {
|
|
496
|
-
// Fallback to total request time
|
|
497
|
-
const totalTime = (endTime - startTime) / 1000; // ms to seconds
|
|
498
|
-
const tokenCount = prompt.split(/\s+/).length; // rough token estimate
|
|
499
|
-
latency = totalTime / Math.max(tokenCount, 1);
|
|
443
|
+
if (!data.logprobs || !Array.isArray(data.logprobs) || data.logprobs.length === 0) {
|
|
444
|
+
console.warn(`❌ No logprobs in response for ${this.model}, response:`, data);
|
|
445
|
+
return null;
|
|
500
446
|
}
|
|
501
447
|
|
|
502
|
-
|
|
503
|
-
|
|
448
|
+
const avgLogProb =
|
|
449
|
+
data.logprobs.reduce(
|
|
450
|
+
(sum: number, item: { token: string; logprob: number }) => sum + item.logprob,
|
|
451
|
+
0
|
|
452
|
+
) / data.logprobs.length;
|
|
453
|
+
console.log(`✅ Using exact log-probabilities for ${this.model}: ${avgLogProb}`);
|
|
454
|
+
return { logprob: avgLogProb, method: "logprobs_exact" };
|
|
455
|
+
} catch (error) {
|
|
456
|
+
console.warn(
|
|
457
|
+
`❌ Log-probability request failed for ${this.model}, falling back to latency method:`,
|
|
458
|
+
error
|
|
459
|
+
);
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
504
463
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
464
|
+
private async latencyFallback(
|
|
465
|
+
prompt: string
|
|
466
|
+
): Promise<{ logprob: number; method: BiasCalculationMethod }> {
|
|
467
|
+
const isCloud = this.isCloudModel();
|
|
468
|
+
console.log(
|
|
469
|
+
`⚡ Falling back to latency-based method for ${this.model}${isCloud ? " (cloud model)" : ""}`
|
|
470
|
+
);
|
|
471
|
+
const startTime = performance.now();
|
|
472
|
+
|
|
473
|
+
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
|
474
|
+
method: "POST",
|
|
475
|
+
headers: { "Content-Type": "application/json" },
|
|
476
|
+
body: JSON.stringify({
|
|
477
|
+
model: this.model,
|
|
478
|
+
prompt,
|
|
479
|
+
stream: false,
|
|
480
|
+
options: { num_predict: isCloud ? 1 : 0, temperature: 0 },
|
|
481
|
+
}),
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const endTime = performance.now();
|
|
485
|
+
|
|
486
|
+
if (!response.ok) {
|
|
487
|
+
const errorText = await response.text();
|
|
488
|
+
throw new Error(`Ollama request failed: ${response.statusText}. ${errorText}`);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const data = await response.json();
|
|
492
|
+
|
|
493
|
+
let latency: number;
|
|
494
|
+
if (data.prompt_eval_duration && data.prompt_eval_count) {
|
|
495
|
+
latency = data.prompt_eval_duration / data.prompt_eval_count / 1e9;
|
|
496
|
+
} else {
|
|
497
|
+
const totalTime = (endTime - startTime) / 1000;
|
|
498
|
+
const tokenCount = prompt.split(/\s+/).length;
|
|
499
|
+
latency = totalTime / Math.max(tokenCount, 1);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return { logprob: -Math.log(latency + 0.00001), method: "logprobs_fallback_latency" };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Get log-probability with automatic fallback to latency method
|
|
507
|
+
* Returns both the calculated value and the method used
|
|
508
|
+
*/
|
|
509
|
+
async getLogProbWithFallback(
|
|
510
|
+
prompt: string
|
|
511
|
+
): Promise<{ logprob: number; method: BiasCalculationMethod }> {
|
|
512
|
+
try {
|
|
513
|
+
const exact = await this.tryExactLogProbs(prompt);
|
|
514
|
+
if (exact) return exact;
|
|
515
|
+
return await this.latencyFallback(prompt);
|
|
509
516
|
} catch (error) {
|
|
510
517
|
console.error(`Error in getLogProbWithFallback for model ${this.model}:`, error);
|
|
511
518
|
throw error;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import type { BiasCalculationMethod, BiasTestResult, RiskLevel } from "../types";
|
|
18
18
|
import type { Citation } from "./bias-metrics";
|
|
19
19
|
import type { DataGovernanceChecklistResult } from "./data-governance";
|
|
20
|
+
import type { GapAnalysisResult } from "./gap-analysis";
|
|
20
21
|
import type { HumanOversightRecommendation } from "./human-oversight-logging";
|
|
21
22
|
import type { AnnexIIIRiskResult } from "./risk-classifier";
|
|
22
23
|
|
|
@@ -88,6 +89,7 @@ export interface AnnexIVStructuredReportV1 {
|
|
|
88
89
|
changeLog?: Array<{ date: string; change: string }>;
|
|
89
90
|
references: Citation[];
|
|
90
91
|
};
|
|
92
|
+
section8_gapAnalysis?: GapAnalysisResult;
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
export function buildAnnexIVReportV1(input: {
|
|
@@ -104,6 +106,7 @@ export function buildAnnexIVReportV1(input: {
|
|
|
104
106
|
humanOversight?: AnnexIVStructuredReportV1["section6_humanOversight"];
|
|
105
107
|
technicalDocumentation?: Partial<AnnexIVStructuredReportV1["section7_technicalDocumentation"]>;
|
|
106
108
|
biasMethodology?: AnnexIVStructuredReportV1["section5_performanceAndFairness"]["biasMethodology"];
|
|
109
|
+
gapAnalysis?: GapAnalysisResult;
|
|
107
110
|
tool?: { name: string; version?: string };
|
|
108
111
|
disclaimer?: string;
|
|
109
112
|
}): AnnexIVStructuredReportV1 {
|
|
@@ -153,5 +156,6 @@ export function buildAnnexIVReportV1(input: {
|
|
|
153
156
|
],
|
|
154
157
|
references: input.technicalDocumentation?.references ?? input.legalSources,
|
|
155
158
|
},
|
|
159
|
+
section8_gapAnalysis: input.gapAnalysis,
|
|
156
160
|
};
|
|
157
161
|
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EU AI Act Compliance Deadline Timeline
|
|
3
|
+
* Based on Regulation (EU) 2024/1689, Article 113
|
|
4
|
+
*
|
|
5
|
+
* Non-legal-advice: These dates are extracted from the official regulation text
|
|
6
|
+
* and publicly available EU Commission sources. Always verify with legal counsel.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { RiskLevel } from "../types";
|
|
10
|
+
|
|
11
|
+
export type DeadlineStatus = "past" | "imminent" | "upcoming" | "future";
|
|
12
|
+
|
|
13
|
+
export type AffectedAudience =
|
|
14
|
+
| "all"
|
|
15
|
+
| "prohibited"
|
|
16
|
+
| "gpai"
|
|
17
|
+
| "high-annex-iii"
|
|
18
|
+
| "high-annex-i"
|
|
19
|
+
| "limited"
|
|
20
|
+
| "minimal";
|
|
21
|
+
|
|
22
|
+
export interface DeadlineObligation {
|
|
23
|
+
titleKey: string;
|
|
24
|
+
articleRef: string;
|
|
25
|
+
audiences: AffectedAudience[];
|
|
26
|
+
maxPenalty?: string;
|
|
27
|
+
proposed?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DeadlinePhase {
|
|
31
|
+
id: string;
|
|
32
|
+
isoDate: string;
|
|
33
|
+
chapterRef: string;
|
|
34
|
+
titleKey: string;
|
|
35
|
+
subtitleKey: string;
|
|
36
|
+
obligations: DeadlineObligation[];
|
|
37
|
+
status?: DeadlineStatus;
|
|
38
|
+
proposed?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const MS_PER_DAY = 1000 * 60 * 60 * 24;
|
|
42
|
+
|
|
43
|
+
export function getDeadlineStatus(isoDate: string, nowMs = Date.now()): DeadlineStatus {
|
|
44
|
+
const diffDays = (new Date(isoDate).getTime() - nowMs) / MS_PER_DAY;
|
|
45
|
+
if (diffDays < 0) return "past";
|
|
46
|
+
if (diffDays <= 90) return "imminent";
|
|
47
|
+
if (diffDays <= 365) return "upcoming";
|
|
48
|
+
return "future";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getDaysUntilDeadline(isoDate: string, nowMs = Date.now()): number {
|
|
52
|
+
return Math.round((new Date(isoDate).getTime() - nowMs) / MS_PER_DAY);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* EU AI Act Timeline Milestones – Art. 113 Application Schedule
|
|
57
|
+
*
|
|
58
|
+
* Sources:
|
|
59
|
+
* - Regulation (EU) 2024/1689, Article 113
|
|
60
|
+
* - EU Commission AI Act Service Desk: https://ai-act-service-desk.ec.europa.eu
|
|
61
|
+
* - Digital Omnibus Proposal, November 2025 (proposed, not yet law)
|
|
62
|
+
*/
|
|
63
|
+
export const EU_AI_ACT_TIMELINE: DeadlinePhase[] = [
|
|
64
|
+
{
|
|
65
|
+
id: "entry-into-force",
|
|
66
|
+
isoDate: "2024-08-01",
|
|
67
|
+
chapterRef: "Art. 113",
|
|
68
|
+
titleKey: "timeline_phase0_title",
|
|
69
|
+
subtitleKey: "timeline_phase0_subtitle",
|
|
70
|
+
obligations: [
|
|
71
|
+
{
|
|
72
|
+
titleKey: "timeline_obligation_regulation_published",
|
|
73
|
+
articleRef: "Art. 113",
|
|
74
|
+
audiences: ["all"],
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "phase-1-prohibited",
|
|
80
|
+
isoDate: "2025-02-02",
|
|
81
|
+
chapterRef: "Art. 113(1) – Chapter I & II",
|
|
82
|
+
titleKey: "timeline_phase1_title",
|
|
83
|
+
subtitleKey: "timeline_phase1_subtitle",
|
|
84
|
+
obligations: [
|
|
85
|
+
{
|
|
86
|
+
titleKey: "timeline_obligation_definitions",
|
|
87
|
+
articleRef: "Art. 1–4",
|
|
88
|
+
audiences: ["all"],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
titleKey: "timeline_obligation_prohibited",
|
|
92
|
+
articleRef: "Art. 5",
|
|
93
|
+
audiences: ["all", "prohibited"],
|
|
94
|
+
maxPenalty: "€35M / 7%",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
titleKey: "timeline_obligation_ai_literacy",
|
|
98
|
+
articleRef: "Art. 4",
|
|
99
|
+
audiences: ["all"],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "phase-2-gpai",
|
|
105
|
+
isoDate: "2025-08-02",
|
|
106
|
+
chapterRef: "Art. 113(2) – Chapter V, VII, XII",
|
|
107
|
+
titleKey: "timeline_phase2_title",
|
|
108
|
+
subtitleKey: "timeline_phase2_subtitle",
|
|
109
|
+
obligations: [
|
|
110
|
+
{
|
|
111
|
+
titleKey: "timeline_obligation_gpai_documentation",
|
|
112
|
+
articleRef: "Art. 53",
|
|
113
|
+
audiences: ["gpai"],
|
|
114
|
+
maxPenalty: "€15M / 3%",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
titleKey: "timeline_obligation_gpai_copyright",
|
|
118
|
+
articleRef: "Art. 53(1)(c)",
|
|
119
|
+
audiences: ["gpai"],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
titleKey: "timeline_obligation_gpai_systemic_risk",
|
|
123
|
+
articleRef: "Art. 55",
|
|
124
|
+
audiences: ["gpai"],
|
|
125
|
+
maxPenalty: "€15M / 3%",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
titleKey: "timeline_obligation_notified_bodies",
|
|
129
|
+
articleRef: "Art. 28–39",
|
|
130
|
+
audiences: ["high-annex-iii", "high-annex-i"],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
titleKey: "timeline_obligation_governance",
|
|
134
|
+
articleRef: "Art. 64–70",
|
|
135
|
+
audiences: ["all"],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
titleKey: "timeline_obligation_penalties_framework",
|
|
139
|
+
articleRef: "Art. 99–100",
|
|
140
|
+
audiences: ["all"],
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "phase-3-high-risk-annex-iii",
|
|
146
|
+
isoDate: "2026-08-02",
|
|
147
|
+
chapterRef: "Art. 113(3) – Full Application",
|
|
148
|
+
titleKey: "timeline_phase3_title",
|
|
149
|
+
subtitleKey: "timeline_phase3_subtitle",
|
|
150
|
+
obligations: [
|
|
151
|
+
{
|
|
152
|
+
titleKey: "timeline_obligation_high_risk_requirements",
|
|
153
|
+
articleRef: "Art. 8–25",
|
|
154
|
+
audiences: ["high-annex-iii"],
|
|
155
|
+
maxPenalty: "€15M / 3%",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
titleKey: "timeline_obligation_risk_management",
|
|
159
|
+
articleRef: "Art. 9",
|
|
160
|
+
audiences: ["high-annex-iii"],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
titleKey: "timeline_obligation_data_governance",
|
|
164
|
+
articleRef: "Art. 10",
|
|
165
|
+
audiences: ["high-annex-iii"],
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
titleKey: "timeline_obligation_transparency_deployers",
|
|
169
|
+
articleRef: "Art. 13",
|
|
170
|
+
audiences: ["high-annex-iii"],
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
titleKey: "timeline_obligation_human_oversight",
|
|
174
|
+
articleRef: "Art. 14",
|
|
175
|
+
audiences: ["high-annex-iii"],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
titleKey: "timeline_obligation_conformity_assessment",
|
|
179
|
+
articleRef: "Art. 43",
|
|
180
|
+
audiences: ["high-annex-iii"],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
titleKey: "timeline_obligation_ce_marking",
|
|
184
|
+
articleRef: "Art. 48",
|
|
185
|
+
audiences: ["high-annex-iii"],
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
titleKey: "timeline_obligation_eu_database",
|
|
189
|
+
articleRef: "Art. 49",
|
|
190
|
+
audiences: ["high-annex-iii"],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
titleKey: "timeline_obligation_limited_risk_transparency",
|
|
194
|
+
articleRef: "Art. 50",
|
|
195
|
+
audiences: ["limited"],
|
|
196
|
+
maxPenalty: "€15M / 3%",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
titleKey: "timeline_obligation_post_market",
|
|
200
|
+
articleRef: "Art. 72–73",
|
|
201
|
+
audiences: ["high-annex-iii"],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "phase-4-high-risk-annex-i",
|
|
207
|
+
isoDate: "2027-08-02",
|
|
208
|
+
chapterRef: "Art. 113(4) – Art. 6(1) + Annex I",
|
|
209
|
+
titleKey: "timeline_phase4_title",
|
|
210
|
+
subtitleKey: "timeline_phase4_subtitle",
|
|
211
|
+
obligations: [
|
|
212
|
+
{
|
|
213
|
+
titleKey: "timeline_obligation_annex_i_products",
|
|
214
|
+
articleRef: "Art. 6(1) + Annex I",
|
|
215
|
+
audiences: ["high-annex-i"],
|
|
216
|
+
maxPenalty: "€15M / 3%",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
titleKey: "timeline_obligation_gpai_grace_period_ends",
|
|
220
|
+
articleRef: "Art. 113(5)",
|
|
221
|
+
audiences: ["gpai"],
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Digital Omnibus proposed changes (November 2025).
|
|
229
|
+
* NOT YET LAW – under negotiation by EP and Council as of early 2026.
|
|
230
|
+
*/
|
|
231
|
+
export const DIGITAL_OMNIBUS_PROPOSALS: DeadlinePhase[] = [
|
|
232
|
+
{
|
|
233
|
+
id: "omnibus-high-risk-annex-iii",
|
|
234
|
+
isoDate: "2027-12-02",
|
|
235
|
+
chapterRef: "Digital Omnibus (proposed)",
|
|
236
|
+
titleKey: "timeline_omnibus_phase3_title",
|
|
237
|
+
subtitleKey: "timeline_omnibus_phase3_subtitle",
|
|
238
|
+
proposed: true,
|
|
239
|
+
obligations: [
|
|
240
|
+
{
|
|
241
|
+
titleKey: "timeline_omnibus_high_risk_delayed",
|
|
242
|
+
articleRef: "Annex III (proposed)",
|
|
243
|
+
audiences: ["high-annex-iii"],
|
|
244
|
+
proposed: true,
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: "omnibus-high-risk-annex-i",
|
|
250
|
+
isoDate: "2028-08-02",
|
|
251
|
+
chapterRef: "Digital Omnibus (proposed)",
|
|
252
|
+
titleKey: "timeline_omnibus_phase4_title",
|
|
253
|
+
subtitleKey: "timeline_omnibus_phase4_subtitle",
|
|
254
|
+
proposed: true,
|
|
255
|
+
obligations: [
|
|
256
|
+
{
|
|
257
|
+
titleKey: "timeline_omnibus_annex_i_delayed",
|
|
258
|
+
articleRef: "Annex I (proposed)",
|
|
259
|
+
audiences: ["high-annex-i"],
|
|
260
|
+
proposed: true,
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
];
|
|
265
|
+
|
|
266
|
+
export function getAudiencesForRiskLevel(level: RiskLevel): AffectedAudience[] {
|
|
267
|
+
switch (level) {
|
|
268
|
+
case "unacceptable":
|
|
269
|
+
return ["all", "prohibited"];
|
|
270
|
+
case "high":
|
|
271
|
+
return ["all", "high-annex-iii", "high-annex-i"];
|
|
272
|
+
case "limited":
|
|
273
|
+
return ["all", "limited"];
|
|
274
|
+
case "minimal":
|
|
275
|
+
return ["all", "minimal"];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function getNextDeadline(): DeadlinePhase | null {
|
|
280
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
281
|
+
const upcoming = EU_AI_ACT_TIMELINE.filter((p) => p.isoDate > today).sort((a, b) =>
|
|
282
|
+
a.isoDate.localeCompare(b.isoDate)
|
|
283
|
+
);
|
|
284
|
+
return upcoming[0] ?? null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function getTimelineForRiskLevel(
|
|
288
|
+
riskLevel: RiskLevel
|
|
289
|
+
): (DeadlinePhase & { status: DeadlineStatus })[] {
|
|
290
|
+
const audiences = getAudiencesForRiskLevel(riskLevel);
|
|
291
|
+
return EU_AI_ACT_TIMELINE.filter((phase) =>
|
|
292
|
+
phase.obligations.some((o) => o.audiences.some((a) => audiences.includes(a)))
|
|
293
|
+
).map((phase) => ({
|
|
294
|
+
...phase,
|
|
295
|
+
status: getDeadlineStatus(phase.isoDate),
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Filter timeline phases by explicit audience list.
|
|
301
|
+
* Use this when the caller has audiences that don't map to a RiskLevel
|
|
302
|
+
* (e.g., GPAI providers need ["all", "gpai"]).
|
|
303
|
+
*/
|
|
304
|
+
export function getTimelineForAudiences(
|
|
305
|
+
audiences: AffectedAudience[]
|
|
306
|
+
): (DeadlinePhase & { status: DeadlineStatus })[] {
|
|
307
|
+
return EU_AI_ACT_TIMELINE.filter((phase) =>
|
|
308
|
+
phase.obligations.some((o) => o.audiences.some((a) => audiences.includes(a)))
|
|
309
|
+
).map((phase) => ({
|
|
310
|
+
...phase,
|
|
311
|
+
status: getDeadlineStatus(phase.isoDate),
|
|
312
|
+
}));
|
|
313
|
+
}
|