@euconform/core 1.0.0 → 1.2.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 (34) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +109 -0
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/inference/ollama-client.d.ts +1 -0
  8. package/dist/inference/ollama-client.d.ts.map +1 -1
  9. package/dist/inference/ollama-client.js +15 -5
  10. package/dist/legal-checks/annex-iv-report.d.ts +3 -0
  11. package/dist/legal-checks/annex-iv-report.d.ts.map +1 -1
  12. package/dist/legal-checks/annex-iv-report.js +1 -0
  13. package/dist/legal-checks/deadline-timeline.d.ts +57 -0
  14. package/dist/legal-checks/deadline-timeline.d.ts.map +1 -0
  15. package/dist/legal-checks/deadline-timeline.js +265 -0
  16. package/dist/legal-checks/gap-analysis.d.ts +48 -0
  17. package/dist/legal-checks/gap-analysis.d.ts.map +1 -0
  18. package/dist/legal-checks/gap-analysis.js +397 -0
  19. package/dist/legal-checks/gpai-classifier.d.ts +49 -0
  20. package/dist/legal-checks/gpai-classifier.d.ts.map +1 -0
  21. package/dist/legal-checks/gpai-classifier.js +197 -0
  22. package/dist/legal-checks/index.d.ts +6 -0
  23. package/dist/legal-checks/index.d.ts.map +1 -1
  24. package/dist/legal-checks/index.js +3 -0
  25. package/package.json +20 -1
  26. package/src/index.ts +25 -0
  27. package/src/inference/ollama-client.ts +21 -5
  28. package/src/legal-checks/annex-iv-report.ts +4 -0
  29. package/src/legal-checks/deadline-timeline.ts +313 -0
  30. package/src/legal-checks/gap-analysis.ts +454 -0
  31. package/src/legal-checks/gpai-classifier.ts +281 -0
  32. package/src/legal-checks/index.ts +33 -0
  33. package/tests/gpai-classifier.test.ts +125 -0
  34. package/tests/gpai-timeline.test.ts +55 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * GPAI / Foundation Model Compliance Classifier
3
+ * Based on EU AI Act (Regulation EU 2024/1689) – Art. 53–55 + Art. 51 + Art. 71
4
+ *
5
+ * Obligations enforceable since August 2, 2025 (Phase 2, Art. 113(2)).
6
+ *
7
+ * IMPORTANT: This module provides technical orientation, not legal advice.
8
+ */
9
+ /**
10
+ * The 9 GPAI compliance questions (Art. 53–55).
11
+ * Order matters: q1–q9 map to i18n keys gpai_q1–gpai_q9.
12
+ */
13
+ export const GPAI_QUESTIONS = [
14
+ {
15
+ id: "gpai-technical-docs",
16
+ question: "Art. 53(1)(a): Technical documentation (Annex XI/XII)?",
17
+ description: "Have you prepared and maintain up-to-date technical documentation for the GPAI model per Annex XI/XII (general-purpose) or the systemic-risk extension?",
18
+ options: [
19
+ { value: "yes", label: "Yes, implemented" },
20
+ { value: "no", label: "No, missing" },
21
+ { value: "unsure", label: "In progress / unsure" },
22
+ ],
23
+ },
24
+ {
25
+ id: "gpai-downstream-info",
26
+ question: "Art. 53(1)(b): Downstream provider information?",
27
+ description: "Do you provide downstream providers / deployers with the information they need to comply with their own EU AI Act obligations (capabilities, limitations, intended use)?",
28
+ options: [
29
+ { value: "yes", label: "Yes, implemented" },
30
+ { value: "no", label: "No, missing" },
31
+ { value: "unsure", label: "In progress / unsure" },
32
+ ],
33
+ },
34
+ {
35
+ id: "gpai-copyright-policy",
36
+ question: "Art. 53(1)(c–d): Copyright policy + training data summary?",
37
+ description: "Have you established a copyright compliance policy (Art. 53(1)(c)) and published a sufficiently detailed training data summary for copyright purposes (Art. 53(1)(d))?",
38
+ options: [
39
+ { value: "yes", label: "Yes, implemented" },
40
+ { value: "no", label: "No, missing" },
41
+ { value: "unsure", label: "In progress / unsure" },
42
+ ],
43
+ },
44
+ {
45
+ id: "gpai-eu-database",
46
+ question: "Art. 71: EU AI Database registration?",
47
+ description: "Is the GPAI model registered in the EU AI Database as required under Art. 71 (providers of GPAI models must register before placing the model on the EU market)?",
48
+ options: [
49
+ { value: "yes", label: "Yes, registered" },
50
+ { value: "no", label: "No, not registered" },
51
+ { value: "unsure", label: "In progress / unsure" },
52
+ ],
53
+ },
54
+ {
55
+ id: "gpai-open-source",
56
+ question: "Art. 53(2): Open-source model release?",
57
+ description: "Is the model released as open source with weights publicly available? Open-source GPAI models are exempt from Art. 53(1)(a–c) obligations (technical docs, downstream info, copyright policy) but not from Art. 53(1)(d) (training data summary) or Art. 55 systemic-risk obligations.",
58
+ options: [
59
+ { value: "yes", label: "Yes, open source" },
60
+ { value: "no", label: "No, proprietary" },
61
+ { value: "unsure", label: "Unsure / partially open" },
62
+ ],
63
+ },
64
+ {
65
+ id: "gpai-systemic-risk",
66
+ question: "Art. 51 + Annex XIII: Trained with ≥ 10²⁵ FLOP?",
67
+ description: "Was the model trained using compute exceeding 10²⁵ floating-point operations (FLOP)? Models at or above this threshold are designated as GPAI models with systemic risk (Art. 51) and are subject to the additional Art. 55 obligations below.",
68
+ options: [
69
+ { value: "yes", label: "Yes (systemic risk)" },
70
+ { value: "no", label: "No (below threshold)" },
71
+ { value: "unsure", label: "Unknown / unsure" },
72
+ ],
73
+ },
74
+ {
75
+ id: "gpai-red-teaming",
76
+ question: "Art. 55(1)(a): Adversarial testing / red-teaming?",
77
+ description: "Have you conducted model evaluations, adversarial testing, and red-teaming to identify and mitigate systemic risks (required only for Art. 51 systemic-risk models)?",
78
+ options: [
79
+ { value: "yes", label: "Yes, conducted" },
80
+ { value: "no", label: "No, not done" },
81
+ { value: "unsure", label: "In progress / unsure" },
82
+ ],
83
+ },
84
+ {
85
+ id: "gpai-incident-reporting",
86
+ question: "Art. 55(1)(b): Serious incident reporting mechanisms?",
87
+ description: "Do you have mechanisms to track, document, and report serious incidents caused by the GPAI model to the AI Office and affected national authorities (required only for Art. 51 systemic-risk models)?",
88
+ options: [
89
+ { value: "yes", label: "Yes, in place" },
90
+ { value: "no", label: "No, missing" },
91
+ { value: "unsure", label: "In progress / unsure" },
92
+ ],
93
+ },
94
+ {
95
+ id: "gpai-cybersecurity",
96
+ question: "Art. 55(1)(c): Cybersecurity measures?",
97
+ description: "Have you implemented adequate cybersecurity protections for the GPAI model and its infrastructure, including adversarial robustness measures (required only for Art. 51 systemic-risk models)?",
98
+ options: [
99
+ { value: "yes", label: "Yes, implemented" },
100
+ { value: "no", label: "No, missing" },
101
+ { value: "unsure", label: "In progress / unsure" },
102
+ ],
103
+ },
104
+ ];
105
+ /** Returns the full ordered list of GPAI compliance questions */
106
+ export function getGPAIQuestions() {
107
+ return GPAI_QUESTIONS;
108
+ }
109
+ /** Returns true if the model meets the systemic risk threshold (Art. 51) */
110
+ export function isSystemicRiskModel(answers) {
111
+ const systemicAnswer = answers.find((a) => a.questionId === "gpai-systemic-risk");
112
+ return systemicAnswer?.value === "yes";
113
+ }
114
+ /** Push a flag if the answer value is "no" or "unsure" */
115
+ function checkObligation(value, obligation, articleRef, flags) {
116
+ if (value === "no") {
117
+ flags.push({ obligation, status: "missing", articleRef });
118
+ }
119
+ else if (value === "unsure") {
120
+ flags.push({ obligation, status: "partial", articleRef });
121
+ }
122
+ }
123
+ /** Evaluate Art. 53 obligations and populate flags / notes */
124
+ function evaluateArt53Obligations(byId, isOpenSource, flags, legalBasis, notes) {
125
+ if (!isOpenSource) {
126
+ checkObligation(byId.get("gpai-technical-docs"), "technical-docs", "Art. 53(1)(a)", flags);
127
+ checkObligation(byId.get("gpai-downstream-info"), "downstream-info", "Art. 53(1)(b)", flags);
128
+ checkObligation(byId.get("gpai-copyright-policy"), "copyright-policy", "Art. 53(1)(c–d)", flags);
129
+ }
130
+ else {
131
+ // Open-source: Art. 53(1)(a–c) exempt, but training data summary (Art. 53(1)(d)) still applies
132
+ const copyrightVal = byId.get("gpai-copyright-policy");
133
+ checkObligation(copyrightVal, "copyright-policy", "Art. 53(1)(d)", flags);
134
+ if (copyrightVal === "no" || copyrightVal === "unsure") {
135
+ notes.push("Open-source models are exempt from Art. 53(1)(c) copyright policy obligations, but Art. 53(1)(d) training data summary still applies.");
136
+ }
137
+ }
138
+ const euDatabase = byId.get("gpai-eu-database");
139
+ checkObligation(euDatabase, "eu-database", "Art. 71", flags);
140
+ if (euDatabase === "no") {
141
+ legalBasis.push("Verordnung (EU) 2024/1689 – Art. 71 (EU AI Database)");
142
+ }
143
+ }
144
+ /** Evaluate Art. 55 systemic risk obligations and populate flags */
145
+ function evaluateArt55Obligations(byId, flags, legalBasis) {
146
+ legalBasis.push("Verordnung (EU) 2024/1689 – Art. 51 + Art. 55 (Systemic Risk)");
147
+ legalBasis.push("Verordnung (EU) 2024/1689 – Annex XIII (Systemic Risk Indicators)");
148
+ checkObligation(byId.get("gpai-red-teaming"), "red-teaming", "Art. 55(1)(a)", flags);
149
+ checkObligation(byId.get("gpai-incident-reporting"), "incident-reporting", "Art. 55(1)(b)", flags);
150
+ checkObligation(byId.get("gpai-cybersecurity"), "cybersecurity", "Art. 55(1)(c)", flags);
151
+ }
152
+ /** Determine overall compliance level from accumulated flags */
153
+ function determineLevel(flags, isOpenSource) {
154
+ const criticalObligations = isOpenSource
155
+ ? ["eu-database"]
156
+ : ["technical-docs", "downstream-info", "copyright-policy", "eu-database"];
157
+ const allCriticalMissing = !isOpenSource &&
158
+ criticalObligations.every((obl) => flags.some((f) => f.obligation === obl && f.status === "missing"));
159
+ if (allCriticalMissing)
160
+ return "non-compliant";
161
+ if (flags.length > 0)
162
+ return "partial";
163
+ return "compliant";
164
+ }
165
+ /**
166
+ * Classify GPAI compliance based on answers to the 9 questions.
167
+ * Returns a GPAIComplianceResult with level, flags, and legal basis.
168
+ *
169
+ * Classification logic:
170
+ * - Open-source (Art. 53(2)): exempt from docs/downstream/copyright obligations
171
+ * - Systemic risk (Art. 51): Art. 55 obligations apply
172
+ * - Critical Art. 53 all missing → "non-compliant"
173
+ * - Any "unsure" or partial → "partial"
174
+ * - All applicable "yes" → "compliant"
175
+ */
176
+ export function classifyGPAICompliance(answers) {
177
+ const byId = new Map(answers.map((a) => [a.questionId, a.value]));
178
+ const isOpenSource = byId.get("gpai-open-source") === "yes";
179
+ const hasSystemicRisk = byId.get("gpai-systemic-risk") === "yes";
180
+ const flags = [];
181
+ const legalBasis = ["Verordnung (EU) 2024/1689 – Art. 53 (GPAI obligations)"];
182
+ const notes = [];
183
+ evaluateArt53Obligations(byId, isOpenSource, flags, legalBasis, notes);
184
+ if (hasSystemicRisk) {
185
+ evaluateArt55Obligations(byId, flags, legalBasis);
186
+ }
187
+ else {
188
+ notes.push("Art. 55 systemic risk obligations (red-teaming, incident reporting, cybersecurity) are not applicable as the model does not meet the Art. 51 threshold (≥ 10²⁵ FLOP).");
189
+ }
190
+ return {
191
+ level: determineLevel(flags, isOpenSource),
192
+ isSystemicRisk: hasSystemicRisk,
193
+ flags,
194
+ legalBasis,
195
+ notes,
196
+ };
197
+ }
@@ -10,4 +10,10 @@ export type { HumanOversightRecommendation, LoggingTemplate } from "./human-over
10
10
  export { getHumanOversightAndLoggingTemplate } from "./human-oversight-logging";
11
11
  export type { AnnexIVStructuredReportV1 } from "./annex-iv-report";
12
12
  export { buildAnnexIVReportV1 } from "./annex-iv-report";
13
+ export type { DeadlineStatus, AffectedAudience, DeadlineObligation, DeadlinePhase, } from "./deadline-timeline";
14
+ export { EU_AI_ACT_TIMELINE, DIGITAL_OMNIBUS_PROPOSALS, getDeadlineStatus, getDaysUntilDeadline, getAudiencesForRiskLevel, getNextDeadline, getTimelineForRiskLevel, getTimelineForAudiences, } from "./deadline-timeline";
15
+ export type { GPAIObligationType, GPAIComplianceLevel, GPAIComplianceFlag, GPAIComplianceResult, } from "./gpai-classifier";
16
+ export { GPAI_QUESTIONS, classifyGPAICompliance, getGPAIQuestions, isSystemicRiskModel, } from "./gpai-classifier";
17
+ export type { GapPriority, GapAction, GapAnalysisResult } from "./gap-analysis";
18
+ export { generateAnnexIIIGapAnalysis, generateGPAIGapAnalysis } from "./gap-analysis";
13
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/legal-checks/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EACL,mBAAmB,EACnB,iCAAiC,EACjC,oBAAoB,EACpB,oBAAoB,EACpB,iCAAiC,GAClC,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,QAAQ,EACR,kBAAkB,EAClB,wBAAwB,EACxB,uBAAuB,EACvB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,cAAc,GACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,cAAc,EACd,cAAc,EACd,kCAAkC,EAClC,8BAA8B,EAC9B,gCAAgC,EAChC,sBAAsB,EACtB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,wBAAwB,EACxB,aAAa,EACb,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,eAAe,EAAE,6BAA6B,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAE/D,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,aAAa,GACd,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,wBAAwB,EACxB,iCAAiC,GAClC,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,4BAA4B,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC/F,OAAO,EAAE,mCAAmC,EAAE,MAAM,2BAA2B,CAAC;AAEhF,YAAY,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/legal-checks/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EACL,mBAAmB,EACnB,iCAAiC,EACjC,oBAAoB,EACpB,oBAAoB,EACpB,iCAAiC,GAClC,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,QAAQ,EACR,kBAAkB,EAClB,wBAAwB,EACxB,uBAAuB,EACvB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,cAAc,GACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,cAAc,EACd,cAAc,EACd,kCAAkC,EAClC,8BAA8B,EAC9B,gCAAgC,EAChC,sBAAsB,EACtB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,wBAAwB,EACxB,aAAa,EACb,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,eAAe,EAAE,6BAA6B,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAE/D,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,aAAa,GACd,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,wBAAwB,EACxB,iCAAiC,GAClC,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,4BAA4B,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC/F,OAAO,EAAE,mCAAmC,EAAE,MAAM,2BAA2B,CAAC;AAEhF,YAAY,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,wBAAwB,EACxB,eAAe,EACf,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -4,3 +4,6 @@ export { runDataGovernanceChecklist } from "./data-governance";
4
4
  export { defaultTextPerturbations, runTextPerturbationRobustnessTest, } from "./robustness-transparency";
5
5
  export { getHumanOversightAndLoggingTemplate } from "./human-oversight-logging";
6
6
  export { buildAnnexIVReportV1 } from "./annex-iv-report";
7
+ export { EU_AI_ACT_TIMELINE, DIGITAL_OMNIBUS_PROPOSALS, getDeadlineStatus, getDaysUntilDeadline, getAudiencesForRiskLevel, getNextDeadline, getTimelineForRiskLevel, getTimelineForAudiences, } from "./deadline-timeline";
8
+ export { GPAI_QUESTIONS, classifyGPAICompliance, getGPAIQuestions, isSystemicRiskModel, } from "./gpai-classifier";
9
+ export { generateAnnexIIIGapAnalysis, generateGPAIGapAnalysis } from "./gap-analysis";
package/package.json CHANGED
@@ -1,9 +1,27 @@
1
1
  {
2
2
  "name": "@euconform/core",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "license": "MIT OR EUPL-1.2",
5
5
  "author": "Benedikt Hiepler",
6
+ "description": "Core engine for EU AI Act compliance, risk classification, and fairness metrics.",
6
7
  "type": "module",
8
+ "keywords": [
9
+ "eu-ai-act",
10
+ "compliance",
11
+ "ai-risk",
12
+ "fairness",
13
+ "responsible-ai",
14
+ "audit"
15
+ ],
16
+ "homepage": "https://github.com/Hiepler/EuConform#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/Hiepler/EuConform/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/Hiepler/EuConform.git",
23
+ "directory": "packages/core"
24
+ },
7
25
  "exports": {
8
26
  ".": "./src/index.ts",
9
27
  "./risk-engine": "./src/risk-engine.ts",
@@ -12,6 +30,7 @@
12
30
  },
13
31
  "devDependencies": {
14
32
  "@types/node": "^22.15.30",
33
+ "@vitest/coverage-v8": "^2.1.8",
15
34
  "typescript": "5.9.2",
16
35
  "vitest": "^2.1.8",
17
36
  "@euconform/typescript-config": "0.0.0"
package/src/index.ts CHANGED
@@ -31,6 +31,12 @@ export {
31
31
  classifyAnnexIIIRisk,
32
32
  getAnnexIIIQuestions,
33
33
  getProhibitedAIScreeningQuestions,
34
+ GPAI_QUESTIONS,
35
+ classifyGPAICompliance,
36
+ getGPAIQuestions,
37
+ isSystemicRiskModel,
38
+ generateAnnexIIIGapAnalysis,
39
+ generateGPAIGapAnalysis,
34
40
  AI_ACT_SOURCES,
35
41
  METRIC_SOURCES,
36
42
  createPerspectiveApiToxicityScorer,
@@ -48,6 +54,14 @@ export {
48
54
  runCrowsPairsLogProbTest,
49
55
  runRegardTest,
50
56
  runToxicityTest,
57
+ EU_AI_ACT_TIMELINE,
58
+ DIGITAL_OMNIBUS_PROPOSALS,
59
+ getDeadlineStatus,
60
+ getDaysUntilDeadline,
61
+ getAudiencesForRiskLevel,
62
+ getNextDeadline,
63
+ getTimelineForRiskLevel,
64
+ getTimelineForAudiences,
51
65
  } from "./legal-checks";
52
66
  export type {
53
67
  AnnexIIIAnswer,
@@ -71,6 +85,17 @@ export type {
71
85
  SensitiveAttribute,
72
86
  ToxicityResult,
73
87
  ToxicityScorer,
88
+ DeadlineStatus,
89
+ AffectedAudience,
90
+ DeadlineObligation,
91
+ DeadlinePhase,
92
+ GPAIObligationType,
93
+ GPAIComplianceLevel,
94
+ GPAIComplianceFlag,
95
+ GPAIComplianceResult,
96
+ GapPriority,
97
+ GapAction,
98
+ GapAnalysisResult,
74
99
  } from "./legal-checks";
75
100
 
76
101
  // Risk Engine
@@ -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 {
@@ -412,8 +424,7 @@ export class OllamaClient {
412
424
  // First, check if model supports log-probabilities
413
425
  const supportsLogProbs = await this.detectLogProbSupport();
414
426
 
415
- if (supportsLogProbs) {
416
- // Try to get exact log-probabilities
427
+ if (supportsLogProbs && !this.isCloudModel()) {
417
428
  try {
418
429
  const response = await fetch(`${this.baseUrl}/api/generate`, {
419
430
  method: "POST",
@@ -461,8 +472,12 @@ export class OllamaClient {
461
472
  }
462
473
 
463
474
  // Fallback to latency-based method
464
- console.log(`⚡ Falling back to latency-based method for ${this.model}`);
475
+ const isCloud = this.isCloudModel();
476
+ console.log(
477
+ `⚡ Falling back to latency-based method for ${this.model}${isCloud ? " (cloud model)" : ""}`
478
+ );
465
479
  const startTime = performance.now();
480
+ const numPredict = isCloud ? 1 : 0;
466
481
 
467
482
  const response = await fetch(`${this.baseUrl}/api/generate`, {
468
483
  method: "POST",
@@ -472,7 +487,7 @@ export class OllamaClient {
472
487
  prompt,
473
488
  stream: false,
474
489
  options: {
475
- num_predict: 0, // Only evaluate prompt
490
+ num_predict: numPredict,
476
491
  temperature: 0,
477
492
  },
478
493
  }),
@@ -481,7 +496,8 @@ export class OllamaClient {
481
496
  const endTime = performance.now();
482
497
 
483
498
  if (!response.ok) {
484
- throw new Error(`Ollama request failed: ${response.statusText}`);
499
+ const errorText = await response.text();
500
+ throw new Error(`Ollama request failed: ${response.statusText}. ${errorText}`);
485
501
  }
486
502
 
487
503
  const data = await response.json();
@@ -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
  }