@contractspec/module.provider-ranking 0.7.6 → 0.7.10

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/README.md CHANGED
@@ -1,34 +1,70 @@
1
1
  # @contractspec/module.provider-ranking
2
2
 
3
- Website: https://contractspec.io/
3
+ Website: https://contractspec.io
4
4
 
5
- **AI provider ranking module with persistence and pipeline orchestration**
5
+ **Provider-ranking module that adds persistence adapters and pipeline orchestration on top of lower-level ranking logic.**
6
6
 
7
- AI provider ranking module for benchmarks, model rankings, and ingestion. Re-exports entities, storage adapters, and pipeline orchestration.
7
+ ## What It Provides
8
+
9
+ - Coordinates ingest, storage, scoring, and ranking refresh flows over provider benchmark data.
10
+ - Exposes entities, pipelines, and storage interfaces for ranking-aware applications.
11
+ - Sits between ranking libraries and deployable MCP or application shells.
12
+ - `src/pipeline/` contains pipeline stages and orchestration helpers.
8
13
 
9
14
  ## Installation
10
15
 
11
- ```bash
12
- bun add @contractspec/module.provider-ranking
13
- ```
16
+ `npm install @contractspec/module.provider-ranking`
14
17
 
15
- ## Exports
18
+ or
16
19
 
17
- - `.` — Main entry: entities, storage, and pipelines
18
- - `./entities` — Schema entities (BenchmarkResult, ModelRanking, IngestionRun)
19
- - `./storage` — Postgres storage adapter (PostgresProviderRankingStore)
20
- - `./pipeline` — IngestionPipeline and RankingPipeline orchestration
20
+ `bun add @contractspec/module.provider-ranking`
21
21
 
22
22
  ## Usage
23
23
 
24
- ```typescript
25
- import {
26
- PostgresProviderRankingStore,
27
- RankingPipeline,
28
- } from "@contractspec/module.provider-ranking";
24
+ Import the root entrypoint from `@contractspec/module.provider-ranking`, or choose a documented subpath when you only need one part of the package surface.
25
+
26
+ ## Architecture
27
+
28
+ - `src/entities/` defines ranking-related domain shapes.
29
+ - `src/pipeline/` contains ingestion and ranking orchestration stages.
30
+ - `src/storage/` contains persistence-facing interfaces and implementations.
31
+ - `src/index.ts` exposes the composed module surface.
32
+
33
+ ## Public Entry Points
34
+
35
+ - Exports entities, pipeline helpers, named pipeline entrypoints, and storage surfaces.
36
+ - Export `.` resolves through `./src/index.ts`.
37
+ - Export `./entities` resolves through `./src/entities/index.ts`.
38
+ - Export `./pipeline` resolves through `./src/pipeline/index.ts`.
39
+ - Export `./pipeline/ingestion-pipeline` resolves through `./src/pipeline/ingestion-pipeline.ts`.
40
+ - Export `./pipeline/ranking-pipeline` resolves through `./src/pipeline/ranking-pipeline.ts`.
41
+ - Export `./storage` resolves through `./src/storage/index.ts`.
42
+
43
+ ## Local Commands
44
+
45
+ - `bun run dev` — contractspec-bun-build dev
46
+ - `bun run build` — bun run prebuild && bun run build:bundle && bun run build:types
47
+ - `bun run lint` — bun lint:fix
48
+ - `bun run lint:check` — biome check .
49
+ - `bun run lint:fix` — biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .
50
+ - `bun run typecheck` — tsc --noEmit
51
+ - `bun run publish:pkg` — bun publish --tolerate-republish --ignore-scripts --verbose
52
+ - `bun run publish:pkg:canary` — bun publish:pkg --tag canary
53
+ - `bun run clean` — rimraf dist .turbo
54
+ - `bun run build:bundle` — contractspec-bun-build transpile
55
+ - `bun run build:types` — contractspec-bun-build types
56
+ - `bun run prebuild` — contractspec-bun-build prebuild
57
+
58
+ ## Recent Updates
59
+
60
+ - Replace eslint+prettier by biomejs to optimize speed.
61
+ - Resolve lint, build, and type errors across nine packages.
62
+ - Add Composio universal fallback, fix provider-ranking types, and expand package exports.
63
+ - Add first-class transport, auth, versioning, and BYOK support across all integrations.
64
+ - Add AI provider ranking system with ranking-driven model selection.
29
65
 
30
- const store = new PostgresProviderRankingStore({ database });
31
- const ranking = new RankingPipeline({ store });
66
+ ## Notes
32
67
 
33
- const result = await ranking.refresh();
34
- ```
68
+ - Depends on `lib.provider-ranking` for scoring logic and ingesters -- this module adds persistence and orchestration.
69
+ - Pipeline stages must be idempotent; re-ingesting the same benchmark data should not create duplicates.
70
+ - Storage adapters are swappable -- always code against the interface, not the implementation.
@@ -99,6 +99,136 @@ var providerRankingSchemaContribution = {
99
99
  entities: providerRankingEntities
100
100
  };
101
101
 
102
+ // src/pipeline/ingestion-pipeline.ts
103
+ import { normalizeBenchmarkResults } from "@contractspec/lib.provider-ranking/scoring";
104
+
105
+ class IngestionPipeline {
106
+ store;
107
+ registry;
108
+ ingesterOptions;
109
+ constructor(options) {
110
+ this.store = options.store;
111
+ this.registry = options.ingesterRegistry;
112
+ this.ingesterOptions = options.ingesterOptions;
113
+ }
114
+ async ingest(source, params) {
115
+ const ingester = this.registry.get(source);
116
+ if (!ingester) {
117
+ throw new Error(`No ingester registered for source: ${source}`);
118
+ }
119
+ return this.runIngester(ingester, params);
120
+ }
121
+ async ingestAll(params) {
122
+ const results = [];
123
+ for (const ingester of this.registry.list()) {
124
+ const result = await this.runIngester(ingester, params);
125
+ results.push(result);
126
+ }
127
+ return results;
128
+ }
129
+ mergeOptions(params) {
130
+ const merged = { ...this.ingesterOptions };
131
+ if (params?.fromDate)
132
+ merged.fromDate = new Date(params.fromDate);
133
+ if (params?.toDate)
134
+ merged.toDate = new Date(params.toDate);
135
+ if (params?.dimensions?.length)
136
+ merged.dimensions = params.dimensions;
137
+ return merged;
138
+ }
139
+ async runIngester(ingester, params) {
140
+ const ingestionId = `ingest-${ingester.source}-${Date.now()}`;
141
+ const run = {
142
+ id: ingestionId,
143
+ source: ingester.source,
144
+ status: "running",
145
+ resultsCount: 0,
146
+ startedAt: new Date,
147
+ completedAt: null,
148
+ error: null
149
+ };
150
+ await this.store.createIngestionRun(run);
151
+ try {
152
+ const opts = this.mergeOptions(params);
153
+ const rawResults = await ingester.ingest(opts);
154
+ const normalized = normalizeBenchmarkResults(rawResults);
155
+ for (const result of normalized) {
156
+ await this.store.upsertBenchmarkResult(result);
157
+ }
158
+ await this.store.updateIngestionRun(ingestionId, {
159
+ status: "completed",
160
+ resultsCount: normalized.length,
161
+ completedAt: new Date
162
+ });
163
+ return {
164
+ ingestionId,
165
+ source: ingester.source,
166
+ resultsCount: normalized.length,
167
+ status: "completed"
168
+ };
169
+ } catch (error) {
170
+ const errorMessage = error instanceof Error ? error.message : String(error);
171
+ await this.store.updateIngestionRun(ingestionId, {
172
+ status: "failed",
173
+ completedAt: new Date,
174
+ error: errorMessage
175
+ });
176
+ return {
177
+ ingestionId,
178
+ source: ingester.source,
179
+ resultsCount: 0,
180
+ status: "failed"
181
+ };
182
+ }
183
+ }
184
+ }
185
+
186
+ // src/pipeline/ranking-pipeline.ts
187
+ import { computeModelRankings } from "@contractspec/lib.provider-ranking/scoring";
188
+
189
+ class RankingPipeline {
190
+ store;
191
+ constructor(options) {
192
+ this.store = options.store;
193
+ }
194
+ async refresh(params) {
195
+ let allResults = await this.loadAllBenchmarkResults();
196
+ if (params?.dimensions?.length) {
197
+ const dimSet = new Set(params.dimensions);
198
+ allResults = allResults.filter((r) => dimSet.has(r.dimension));
199
+ }
200
+ const existingRankings = params?.forceRecalculate ? new Map : new Map((await this.store.listModelRankings({
201
+ limit: 1e4,
202
+ requiredTransport: params?.requiredTransport,
203
+ requiredAuthMethod: params?.requiredAuthMethod
204
+ })).rankings.map((r) => [r.modelId, r]));
205
+ const newRankings = computeModelRankings(allResults, params?.weightOverrides ? { weightOverrides: params.weightOverrides } : undefined, existingRankings);
206
+ for (const ranking of newRankings) {
207
+ await this.store.upsertModelRanking(ranking);
208
+ }
209
+ return {
210
+ modelsRanked: newRankings.length,
211
+ updatedAt: new Date
212
+ };
213
+ }
214
+ async loadAllBenchmarkResults() {
215
+ const pageSize = 500;
216
+ let offset = 0;
217
+ const allResults = [];
218
+ while (true) {
219
+ const page = await this.store.listBenchmarkResults({
220
+ limit: pageSize,
221
+ offset
222
+ });
223
+ allResults.push(...page.results);
224
+ if (allResults.length >= page.total || page.results.length < pageSize) {
225
+ break;
226
+ }
227
+ offset += pageSize;
228
+ }
229
+ return allResults;
230
+ }
231
+ }
102
232
  // src/storage/index.ts
103
233
  class PostgresProviderRankingStore {
104
234
  database;
@@ -395,137 +525,6 @@ function parseJson(value) {
395
525
  }
396
526
  return value;
397
527
  }
398
-
399
- // src/pipeline/ingestion-pipeline.ts
400
- import { normalizeBenchmarkResults } from "@contractspec/lib.provider-ranking/scoring";
401
-
402
- class IngestionPipeline {
403
- store;
404
- registry;
405
- ingesterOptions;
406
- constructor(options) {
407
- this.store = options.store;
408
- this.registry = options.ingesterRegistry;
409
- this.ingesterOptions = options.ingesterOptions;
410
- }
411
- async ingest(source, params) {
412
- const ingester = this.registry.get(source);
413
- if (!ingester) {
414
- throw new Error(`No ingester registered for source: ${source}`);
415
- }
416
- return this.runIngester(ingester, params);
417
- }
418
- async ingestAll(params) {
419
- const results = [];
420
- for (const ingester of this.registry.list()) {
421
- const result = await this.runIngester(ingester, params);
422
- results.push(result);
423
- }
424
- return results;
425
- }
426
- mergeOptions(params) {
427
- const merged = { ...this.ingesterOptions };
428
- if (params?.fromDate)
429
- merged.fromDate = new Date(params.fromDate);
430
- if (params?.toDate)
431
- merged.toDate = new Date(params.toDate);
432
- if (params?.dimensions?.length)
433
- merged.dimensions = params.dimensions;
434
- return merged;
435
- }
436
- async runIngester(ingester, params) {
437
- const ingestionId = `ingest-${ingester.source}-${Date.now()}`;
438
- const run = {
439
- id: ingestionId,
440
- source: ingester.source,
441
- status: "running",
442
- resultsCount: 0,
443
- startedAt: new Date,
444
- completedAt: null,
445
- error: null
446
- };
447
- await this.store.createIngestionRun(run);
448
- try {
449
- const opts = this.mergeOptions(params);
450
- const rawResults = await ingester.ingest(opts);
451
- const normalized = normalizeBenchmarkResults(rawResults);
452
- for (const result of normalized) {
453
- await this.store.upsertBenchmarkResult(result);
454
- }
455
- await this.store.updateIngestionRun(ingestionId, {
456
- status: "completed",
457
- resultsCount: normalized.length,
458
- completedAt: new Date
459
- });
460
- return {
461
- ingestionId,
462
- source: ingester.source,
463
- resultsCount: normalized.length,
464
- status: "completed"
465
- };
466
- } catch (error) {
467
- const errorMessage = error instanceof Error ? error.message : String(error);
468
- await this.store.updateIngestionRun(ingestionId, {
469
- status: "failed",
470
- completedAt: new Date,
471
- error: errorMessage
472
- });
473
- return {
474
- ingestionId,
475
- source: ingester.source,
476
- resultsCount: 0,
477
- status: "failed"
478
- };
479
- }
480
- }
481
- }
482
-
483
- // src/pipeline/ranking-pipeline.ts
484
- import { computeModelRankings } from "@contractspec/lib.provider-ranking/scoring";
485
-
486
- class RankingPipeline {
487
- store;
488
- constructor(options) {
489
- this.store = options.store;
490
- }
491
- async refresh(params) {
492
- let allResults = await this.loadAllBenchmarkResults();
493
- if (params?.dimensions?.length) {
494
- const dimSet = new Set(params.dimensions);
495
- allResults = allResults.filter((r) => dimSet.has(r.dimension));
496
- }
497
- const existingRankings = params?.forceRecalculate ? new Map : new Map((await this.store.listModelRankings({
498
- limit: 1e4,
499
- requiredTransport: params?.requiredTransport,
500
- requiredAuthMethod: params?.requiredAuthMethod
501
- })).rankings.map((r) => [r.modelId, r]));
502
- const newRankings = computeModelRankings(allResults, params?.weightOverrides ? { weightOverrides: params.weightOverrides } : undefined, existingRankings);
503
- for (const ranking of newRankings) {
504
- await this.store.upsertModelRanking(ranking);
505
- }
506
- return {
507
- modelsRanked: newRankings.length,
508
- updatedAt: new Date
509
- };
510
- }
511
- async loadAllBenchmarkResults() {
512
- const pageSize = 500;
513
- let offset = 0;
514
- const allResults = [];
515
- while (true) {
516
- const page = await this.store.listBenchmarkResults({
517
- limit: pageSize,
518
- offset
519
- });
520
- allResults.push(...page.results);
521
- if (allResults.length >= page.total || page.results.length < pageSize) {
522
- break;
523
- }
524
- offset += pageSize;
525
- }
526
- return allResults;
527
- }
528
- }
529
528
  export {
530
529
  providerRankingSchemaContribution,
531
530
  providerRankingEntities,
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export * from './entities';
2
- export * from './storage';
3
2
  export * from './pipeline';
3
+ export * from './storage';
package/dist/index.js CHANGED
@@ -100,6 +100,136 @@ var providerRankingSchemaContribution = {
100
100
  entities: providerRankingEntities
101
101
  };
102
102
 
103
+ // src/pipeline/ingestion-pipeline.ts
104
+ import { normalizeBenchmarkResults } from "@contractspec/lib.provider-ranking/scoring";
105
+
106
+ class IngestionPipeline {
107
+ store;
108
+ registry;
109
+ ingesterOptions;
110
+ constructor(options) {
111
+ this.store = options.store;
112
+ this.registry = options.ingesterRegistry;
113
+ this.ingesterOptions = options.ingesterOptions;
114
+ }
115
+ async ingest(source, params) {
116
+ const ingester = this.registry.get(source);
117
+ if (!ingester) {
118
+ throw new Error(`No ingester registered for source: ${source}`);
119
+ }
120
+ return this.runIngester(ingester, params);
121
+ }
122
+ async ingestAll(params) {
123
+ const results = [];
124
+ for (const ingester of this.registry.list()) {
125
+ const result = await this.runIngester(ingester, params);
126
+ results.push(result);
127
+ }
128
+ return results;
129
+ }
130
+ mergeOptions(params) {
131
+ const merged = { ...this.ingesterOptions };
132
+ if (params?.fromDate)
133
+ merged.fromDate = new Date(params.fromDate);
134
+ if (params?.toDate)
135
+ merged.toDate = new Date(params.toDate);
136
+ if (params?.dimensions?.length)
137
+ merged.dimensions = params.dimensions;
138
+ return merged;
139
+ }
140
+ async runIngester(ingester, params) {
141
+ const ingestionId = `ingest-${ingester.source}-${Date.now()}`;
142
+ const run = {
143
+ id: ingestionId,
144
+ source: ingester.source,
145
+ status: "running",
146
+ resultsCount: 0,
147
+ startedAt: new Date,
148
+ completedAt: null,
149
+ error: null
150
+ };
151
+ await this.store.createIngestionRun(run);
152
+ try {
153
+ const opts = this.mergeOptions(params);
154
+ const rawResults = await ingester.ingest(opts);
155
+ const normalized = normalizeBenchmarkResults(rawResults);
156
+ for (const result of normalized) {
157
+ await this.store.upsertBenchmarkResult(result);
158
+ }
159
+ await this.store.updateIngestionRun(ingestionId, {
160
+ status: "completed",
161
+ resultsCount: normalized.length,
162
+ completedAt: new Date
163
+ });
164
+ return {
165
+ ingestionId,
166
+ source: ingester.source,
167
+ resultsCount: normalized.length,
168
+ status: "completed"
169
+ };
170
+ } catch (error) {
171
+ const errorMessage = error instanceof Error ? error.message : String(error);
172
+ await this.store.updateIngestionRun(ingestionId, {
173
+ status: "failed",
174
+ completedAt: new Date,
175
+ error: errorMessage
176
+ });
177
+ return {
178
+ ingestionId,
179
+ source: ingester.source,
180
+ resultsCount: 0,
181
+ status: "failed"
182
+ };
183
+ }
184
+ }
185
+ }
186
+
187
+ // src/pipeline/ranking-pipeline.ts
188
+ import { computeModelRankings } from "@contractspec/lib.provider-ranking/scoring";
189
+
190
+ class RankingPipeline {
191
+ store;
192
+ constructor(options) {
193
+ this.store = options.store;
194
+ }
195
+ async refresh(params) {
196
+ let allResults = await this.loadAllBenchmarkResults();
197
+ if (params?.dimensions?.length) {
198
+ const dimSet = new Set(params.dimensions);
199
+ allResults = allResults.filter((r) => dimSet.has(r.dimension));
200
+ }
201
+ const existingRankings = params?.forceRecalculate ? new Map : new Map((await this.store.listModelRankings({
202
+ limit: 1e4,
203
+ requiredTransport: params?.requiredTransport,
204
+ requiredAuthMethod: params?.requiredAuthMethod
205
+ })).rankings.map((r) => [r.modelId, r]));
206
+ const newRankings = computeModelRankings(allResults, params?.weightOverrides ? { weightOverrides: params.weightOverrides } : undefined, existingRankings);
207
+ for (const ranking of newRankings) {
208
+ await this.store.upsertModelRanking(ranking);
209
+ }
210
+ return {
211
+ modelsRanked: newRankings.length,
212
+ updatedAt: new Date
213
+ };
214
+ }
215
+ async loadAllBenchmarkResults() {
216
+ const pageSize = 500;
217
+ let offset = 0;
218
+ const allResults = [];
219
+ while (true) {
220
+ const page = await this.store.listBenchmarkResults({
221
+ limit: pageSize,
222
+ offset
223
+ });
224
+ allResults.push(...page.results);
225
+ if (allResults.length >= page.total || page.results.length < pageSize) {
226
+ break;
227
+ }
228
+ offset += pageSize;
229
+ }
230
+ return allResults;
231
+ }
232
+ }
103
233
  // src/storage/index.ts
104
234
  class PostgresProviderRankingStore {
105
235
  database;
@@ -396,137 +526,6 @@ function parseJson(value) {
396
526
  }
397
527
  return value;
398
528
  }
399
-
400
- // src/pipeline/ingestion-pipeline.ts
401
- import { normalizeBenchmarkResults } from "@contractspec/lib.provider-ranking/scoring";
402
-
403
- class IngestionPipeline {
404
- store;
405
- registry;
406
- ingesterOptions;
407
- constructor(options) {
408
- this.store = options.store;
409
- this.registry = options.ingesterRegistry;
410
- this.ingesterOptions = options.ingesterOptions;
411
- }
412
- async ingest(source, params) {
413
- const ingester = this.registry.get(source);
414
- if (!ingester) {
415
- throw new Error(`No ingester registered for source: ${source}`);
416
- }
417
- return this.runIngester(ingester, params);
418
- }
419
- async ingestAll(params) {
420
- const results = [];
421
- for (const ingester of this.registry.list()) {
422
- const result = await this.runIngester(ingester, params);
423
- results.push(result);
424
- }
425
- return results;
426
- }
427
- mergeOptions(params) {
428
- const merged = { ...this.ingesterOptions };
429
- if (params?.fromDate)
430
- merged.fromDate = new Date(params.fromDate);
431
- if (params?.toDate)
432
- merged.toDate = new Date(params.toDate);
433
- if (params?.dimensions?.length)
434
- merged.dimensions = params.dimensions;
435
- return merged;
436
- }
437
- async runIngester(ingester, params) {
438
- const ingestionId = `ingest-${ingester.source}-${Date.now()}`;
439
- const run = {
440
- id: ingestionId,
441
- source: ingester.source,
442
- status: "running",
443
- resultsCount: 0,
444
- startedAt: new Date,
445
- completedAt: null,
446
- error: null
447
- };
448
- await this.store.createIngestionRun(run);
449
- try {
450
- const opts = this.mergeOptions(params);
451
- const rawResults = await ingester.ingest(opts);
452
- const normalized = normalizeBenchmarkResults(rawResults);
453
- for (const result of normalized) {
454
- await this.store.upsertBenchmarkResult(result);
455
- }
456
- await this.store.updateIngestionRun(ingestionId, {
457
- status: "completed",
458
- resultsCount: normalized.length,
459
- completedAt: new Date
460
- });
461
- return {
462
- ingestionId,
463
- source: ingester.source,
464
- resultsCount: normalized.length,
465
- status: "completed"
466
- };
467
- } catch (error) {
468
- const errorMessage = error instanceof Error ? error.message : String(error);
469
- await this.store.updateIngestionRun(ingestionId, {
470
- status: "failed",
471
- completedAt: new Date,
472
- error: errorMessage
473
- });
474
- return {
475
- ingestionId,
476
- source: ingester.source,
477
- resultsCount: 0,
478
- status: "failed"
479
- };
480
- }
481
- }
482
- }
483
-
484
- // src/pipeline/ranking-pipeline.ts
485
- import { computeModelRankings } from "@contractspec/lib.provider-ranking/scoring";
486
-
487
- class RankingPipeline {
488
- store;
489
- constructor(options) {
490
- this.store = options.store;
491
- }
492
- async refresh(params) {
493
- let allResults = await this.loadAllBenchmarkResults();
494
- if (params?.dimensions?.length) {
495
- const dimSet = new Set(params.dimensions);
496
- allResults = allResults.filter((r) => dimSet.has(r.dimension));
497
- }
498
- const existingRankings = params?.forceRecalculate ? new Map : new Map((await this.store.listModelRankings({
499
- limit: 1e4,
500
- requiredTransport: params?.requiredTransport,
501
- requiredAuthMethod: params?.requiredAuthMethod
502
- })).rankings.map((r) => [r.modelId, r]));
503
- const newRankings = computeModelRankings(allResults, params?.weightOverrides ? { weightOverrides: params.weightOverrides } : undefined, existingRankings);
504
- for (const ranking of newRankings) {
505
- await this.store.upsertModelRanking(ranking);
506
- }
507
- return {
508
- modelsRanked: newRankings.length,
509
- updatedAt: new Date
510
- };
511
- }
512
- async loadAllBenchmarkResults() {
513
- const pageSize = 500;
514
- let offset = 0;
515
- const allResults = [];
516
- while (true) {
517
- const page = await this.store.listBenchmarkResults({
518
- limit: pageSize,
519
- offset
520
- });
521
- allResults.push(...page.results);
522
- if (allResults.length >= page.total || page.results.length < pageSize) {
523
- break;
524
- }
525
- offset += pageSize;
526
- }
527
- return allResults;
528
- }
529
- }
530
529
  export {
531
530
  providerRankingSchemaContribution,
532
531
  providerRankingEntities,
@@ -99,6 +99,136 @@ var providerRankingSchemaContribution = {
99
99
  entities: providerRankingEntities
100
100
  };
101
101
 
102
+ // src/pipeline/ingestion-pipeline.ts
103
+ import { normalizeBenchmarkResults } from "@contractspec/lib.provider-ranking/scoring";
104
+
105
+ class IngestionPipeline {
106
+ store;
107
+ registry;
108
+ ingesterOptions;
109
+ constructor(options) {
110
+ this.store = options.store;
111
+ this.registry = options.ingesterRegistry;
112
+ this.ingesterOptions = options.ingesterOptions;
113
+ }
114
+ async ingest(source, params) {
115
+ const ingester = this.registry.get(source);
116
+ if (!ingester) {
117
+ throw new Error(`No ingester registered for source: ${source}`);
118
+ }
119
+ return this.runIngester(ingester, params);
120
+ }
121
+ async ingestAll(params) {
122
+ const results = [];
123
+ for (const ingester of this.registry.list()) {
124
+ const result = await this.runIngester(ingester, params);
125
+ results.push(result);
126
+ }
127
+ return results;
128
+ }
129
+ mergeOptions(params) {
130
+ const merged = { ...this.ingesterOptions };
131
+ if (params?.fromDate)
132
+ merged.fromDate = new Date(params.fromDate);
133
+ if (params?.toDate)
134
+ merged.toDate = new Date(params.toDate);
135
+ if (params?.dimensions?.length)
136
+ merged.dimensions = params.dimensions;
137
+ return merged;
138
+ }
139
+ async runIngester(ingester, params) {
140
+ const ingestionId = `ingest-${ingester.source}-${Date.now()}`;
141
+ const run = {
142
+ id: ingestionId,
143
+ source: ingester.source,
144
+ status: "running",
145
+ resultsCount: 0,
146
+ startedAt: new Date,
147
+ completedAt: null,
148
+ error: null
149
+ };
150
+ await this.store.createIngestionRun(run);
151
+ try {
152
+ const opts = this.mergeOptions(params);
153
+ const rawResults = await ingester.ingest(opts);
154
+ const normalized = normalizeBenchmarkResults(rawResults);
155
+ for (const result of normalized) {
156
+ await this.store.upsertBenchmarkResult(result);
157
+ }
158
+ await this.store.updateIngestionRun(ingestionId, {
159
+ status: "completed",
160
+ resultsCount: normalized.length,
161
+ completedAt: new Date
162
+ });
163
+ return {
164
+ ingestionId,
165
+ source: ingester.source,
166
+ resultsCount: normalized.length,
167
+ status: "completed"
168
+ };
169
+ } catch (error) {
170
+ const errorMessage = error instanceof Error ? error.message : String(error);
171
+ await this.store.updateIngestionRun(ingestionId, {
172
+ status: "failed",
173
+ completedAt: new Date,
174
+ error: errorMessage
175
+ });
176
+ return {
177
+ ingestionId,
178
+ source: ingester.source,
179
+ resultsCount: 0,
180
+ status: "failed"
181
+ };
182
+ }
183
+ }
184
+ }
185
+
186
+ // src/pipeline/ranking-pipeline.ts
187
+ import { computeModelRankings } from "@contractspec/lib.provider-ranking/scoring";
188
+
189
+ class RankingPipeline {
190
+ store;
191
+ constructor(options) {
192
+ this.store = options.store;
193
+ }
194
+ async refresh(params) {
195
+ let allResults = await this.loadAllBenchmarkResults();
196
+ if (params?.dimensions?.length) {
197
+ const dimSet = new Set(params.dimensions);
198
+ allResults = allResults.filter((r) => dimSet.has(r.dimension));
199
+ }
200
+ const existingRankings = params?.forceRecalculate ? new Map : new Map((await this.store.listModelRankings({
201
+ limit: 1e4,
202
+ requiredTransport: params?.requiredTransport,
203
+ requiredAuthMethod: params?.requiredAuthMethod
204
+ })).rankings.map((r) => [r.modelId, r]));
205
+ const newRankings = computeModelRankings(allResults, params?.weightOverrides ? { weightOverrides: params.weightOverrides } : undefined, existingRankings);
206
+ for (const ranking of newRankings) {
207
+ await this.store.upsertModelRanking(ranking);
208
+ }
209
+ return {
210
+ modelsRanked: newRankings.length,
211
+ updatedAt: new Date
212
+ };
213
+ }
214
+ async loadAllBenchmarkResults() {
215
+ const pageSize = 500;
216
+ let offset = 0;
217
+ const allResults = [];
218
+ while (true) {
219
+ const page = await this.store.listBenchmarkResults({
220
+ limit: pageSize,
221
+ offset
222
+ });
223
+ allResults.push(...page.results);
224
+ if (allResults.length >= page.total || page.results.length < pageSize) {
225
+ break;
226
+ }
227
+ offset += pageSize;
228
+ }
229
+ return allResults;
230
+ }
231
+ }
102
232
  // src/storage/index.ts
103
233
  class PostgresProviderRankingStore {
104
234
  database;
@@ -395,137 +525,6 @@ function parseJson(value) {
395
525
  }
396
526
  return value;
397
527
  }
398
-
399
- // src/pipeline/ingestion-pipeline.ts
400
- import { normalizeBenchmarkResults } from "@contractspec/lib.provider-ranking/scoring";
401
-
402
- class IngestionPipeline {
403
- store;
404
- registry;
405
- ingesterOptions;
406
- constructor(options) {
407
- this.store = options.store;
408
- this.registry = options.ingesterRegistry;
409
- this.ingesterOptions = options.ingesterOptions;
410
- }
411
- async ingest(source, params) {
412
- const ingester = this.registry.get(source);
413
- if (!ingester) {
414
- throw new Error(`No ingester registered for source: ${source}`);
415
- }
416
- return this.runIngester(ingester, params);
417
- }
418
- async ingestAll(params) {
419
- const results = [];
420
- for (const ingester of this.registry.list()) {
421
- const result = await this.runIngester(ingester, params);
422
- results.push(result);
423
- }
424
- return results;
425
- }
426
- mergeOptions(params) {
427
- const merged = { ...this.ingesterOptions };
428
- if (params?.fromDate)
429
- merged.fromDate = new Date(params.fromDate);
430
- if (params?.toDate)
431
- merged.toDate = new Date(params.toDate);
432
- if (params?.dimensions?.length)
433
- merged.dimensions = params.dimensions;
434
- return merged;
435
- }
436
- async runIngester(ingester, params) {
437
- const ingestionId = `ingest-${ingester.source}-${Date.now()}`;
438
- const run = {
439
- id: ingestionId,
440
- source: ingester.source,
441
- status: "running",
442
- resultsCount: 0,
443
- startedAt: new Date,
444
- completedAt: null,
445
- error: null
446
- };
447
- await this.store.createIngestionRun(run);
448
- try {
449
- const opts = this.mergeOptions(params);
450
- const rawResults = await ingester.ingest(opts);
451
- const normalized = normalizeBenchmarkResults(rawResults);
452
- for (const result of normalized) {
453
- await this.store.upsertBenchmarkResult(result);
454
- }
455
- await this.store.updateIngestionRun(ingestionId, {
456
- status: "completed",
457
- resultsCount: normalized.length,
458
- completedAt: new Date
459
- });
460
- return {
461
- ingestionId,
462
- source: ingester.source,
463
- resultsCount: normalized.length,
464
- status: "completed"
465
- };
466
- } catch (error) {
467
- const errorMessage = error instanceof Error ? error.message : String(error);
468
- await this.store.updateIngestionRun(ingestionId, {
469
- status: "failed",
470
- completedAt: new Date,
471
- error: errorMessage
472
- });
473
- return {
474
- ingestionId,
475
- source: ingester.source,
476
- resultsCount: 0,
477
- status: "failed"
478
- };
479
- }
480
- }
481
- }
482
-
483
- // src/pipeline/ranking-pipeline.ts
484
- import { computeModelRankings } from "@contractspec/lib.provider-ranking/scoring";
485
-
486
- class RankingPipeline {
487
- store;
488
- constructor(options) {
489
- this.store = options.store;
490
- }
491
- async refresh(params) {
492
- let allResults = await this.loadAllBenchmarkResults();
493
- if (params?.dimensions?.length) {
494
- const dimSet = new Set(params.dimensions);
495
- allResults = allResults.filter((r) => dimSet.has(r.dimension));
496
- }
497
- const existingRankings = params?.forceRecalculate ? new Map : new Map((await this.store.listModelRankings({
498
- limit: 1e4,
499
- requiredTransport: params?.requiredTransport,
500
- requiredAuthMethod: params?.requiredAuthMethod
501
- })).rankings.map((r) => [r.modelId, r]));
502
- const newRankings = computeModelRankings(allResults, params?.weightOverrides ? { weightOverrides: params.weightOverrides } : undefined, existingRankings);
503
- for (const ranking of newRankings) {
504
- await this.store.upsertModelRanking(ranking);
505
- }
506
- return {
507
- modelsRanked: newRankings.length,
508
- updatedAt: new Date
509
- };
510
- }
511
- async loadAllBenchmarkResults() {
512
- const pageSize = 500;
513
- let offset = 0;
514
- const allResults = [];
515
- while (true) {
516
- const page = await this.store.listBenchmarkResults({
517
- limit: pageSize,
518
- offset
519
- });
520
- allResults.push(...page.results);
521
- if (allResults.length >= page.total || page.results.length < pageSize) {
522
- break;
523
- }
524
- offset += pageSize;
525
- }
526
- return allResults;
527
- }
528
- }
529
528
  export {
530
529
  providerRankingSchemaContribution,
531
530
  providerRankingEntities,
@@ -1,7 +1,6 @@
1
+ import type { IngesterOptions, IngesterRegistry } from '@contractspec/lib.provider-ranking/ingesters';
1
2
  import type { ProviderRankingStore } from '@contractspec/lib.provider-ranking/store';
2
3
  import type { BenchmarkDimension, BenchmarkSource, IngestionRun } from '@contractspec/lib.provider-ranking/types';
3
- import type { IngesterOptions } from '@contractspec/lib.provider-ranking/ingesters';
4
- import type { IngesterRegistry } from '@contractspec/lib.provider-ranking/ingesters';
5
4
  export interface IngestionPipelineOptions {
6
5
  store: ProviderRankingStore;
7
6
  ingesterRegistry: IngesterRegistry;
@@ -1,5 +1,5 @@
1
1
  import type { ProviderRankingStore } from '@contractspec/lib.provider-ranking/store';
2
- import type { BenchmarkDimension, DimensionWeightConfig, ProviderTransportSupport, ProviderAuthSupport } from '@contractspec/lib.provider-ranking/types';
2
+ import type { BenchmarkDimension, DimensionWeightConfig, ProviderAuthSupport, ProviderTransportSupport } from '@contractspec/lib.provider-ranking/types';
3
3
  export interface RankingPipelineOptions {
4
4
  store: ProviderRankingStore;
5
5
  }
@@ -1,6 +1,6 @@
1
+ import type { DatabaseProvider } from '@contractspec/lib.contracts-integrations';
1
2
  import type { ProviderRankingStore } from '@contractspec/lib.provider-ranking/store';
2
3
  import type { BenchmarkResult, BenchmarkResultListResult, BenchmarkResultQuery, IngestionRun, ModelProfile, ModelRanking, RankingListResult, RankingQuery } from '@contractspec/lib.provider-ranking/types';
3
- import type { DatabaseProvider } from '@contractspec/lib.contracts-integrations';
4
4
  export interface PostgresProviderRankingStoreOptions {
5
5
  database: DatabaseProvider;
6
6
  schema?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/module.provider-ranking",
3
- "version": "0.7.6",
3
+ "version": "0.7.10",
4
4
  "description": "AI provider ranking module with persistence and pipeline orchestration",
5
5
  "keywords": [
6
6
  "contractspec",
@@ -20,19 +20,19 @@
20
20
  "dev": "contractspec-bun-build dev",
21
21
  "clean": "rimraf dist .turbo",
22
22
  "lint": "bun lint:fix",
23
- "lint:fix": "eslint src --fix",
24
- "lint:check": "eslint src",
23
+ "lint:fix": "biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .",
24
+ "lint:check": "biome check .",
25
25
  "prebuild": "contractspec-bun-build prebuild",
26
26
  "typecheck": "tsc --noEmit"
27
27
  },
28
28
  "dependencies": {
29
- "@contractspec/lib.provider-ranking": "0.7.6",
30
- "@contractspec/lib.schema": "3.7.6",
31
- "@contractspec/lib.contracts-integrations": "3.7.6"
29
+ "@contractspec/lib.provider-ranking": "0.7.8",
30
+ "@contractspec/lib.schema": "3.7.8",
31
+ "@contractspec/lib.contracts-integrations": "3.8.2"
32
32
  },
33
33
  "devDependencies": {
34
- "@contractspec/tool.typescript": "3.7.6",
35
- "@contractspec/tool.bun": "3.7.6",
34
+ "@contractspec/tool.typescript": "3.7.8",
35
+ "@contractspec/tool.bun": "3.7.8",
36
36
  "typescript": "^5.9.3"
37
37
  },
38
38
  "exports": {