@contractspec/module.provider-ranking 0.1.2

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.
@@ -0,0 +1,38 @@
1
+ import type { ProviderRankingStore } from '@contractspec/lib.provider-ranking/store';
2
+ 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
+ export interface IngestionPipelineOptions {
6
+ store: ProviderRankingStore;
7
+ ingesterRegistry: IngesterRegistry;
8
+ ingesterOptions?: IngesterOptions;
9
+ }
10
+ export interface IngestParams {
11
+ fromDate?: string;
12
+ toDate?: string;
13
+ dimensions?: BenchmarkDimension[];
14
+ }
15
+ export interface IngestionPipelineResult {
16
+ ingestionId: string;
17
+ source: BenchmarkSource;
18
+ resultsCount: number;
19
+ status: IngestionRun['status'];
20
+ }
21
+ /**
22
+ * Orchestrates the full ingestion flow:
23
+ * 1. Create ingestion run record
24
+ * 2. Fetch data via the appropriate ingester
25
+ * 3. Normalize scores
26
+ * 4. Store results
27
+ * 5. Update ingestion run status
28
+ */
29
+ export declare class IngestionPipeline {
30
+ private readonly store;
31
+ private readonly registry;
32
+ private readonly ingesterOptions?;
33
+ constructor(options: IngestionPipelineOptions);
34
+ ingest(source: BenchmarkSource, params?: IngestParams): Promise<IngestionPipelineResult>;
35
+ ingestAll(params?: IngestParams): Promise<IngestionPipelineResult[]>;
36
+ private mergeOptions;
37
+ private runIngester;
38
+ }
@@ -0,0 +1,87 @@
1
+ // @bun
2
+ // src/pipeline/ingestion-pipeline.ts
3
+ import { normalizeBenchmarkResults } from "@contractspec/lib.provider-ranking/scoring";
4
+
5
+ class IngestionPipeline {
6
+ store;
7
+ registry;
8
+ ingesterOptions;
9
+ constructor(options) {
10
+ this.store = options.store;
11
+ this.registry = options.ingesterRegistry;
12
+ this.ingesterOptions = options.ingesterOptions;
13
+ }
14
+ async ingest(source, params) {
15
+ const ingester = this.registry.get(source);
16
+ if (!ingester) {
17
+ throw new Error(`No ingester registered for source: ${source}`);
18
+ }
19
+ return this.runIngester(ingester, params);
20
+ }
21
+ async ingestAll(params) {
22
+ const results = [];
23
+ for (const ingester of this.registry.list()) {
24
+ const result = await this.runIngester(ingester, params);
25
+ results.push(result);
26
+ }
27
+ return results;
28
+ }
29
+ mergeOptions(params) {
30
+ const merged = { ...this.ingesterOptions };
31
+ if (params?.fromDate)
32
+ merged.fromDate = new Date(params.fromDate);
33
+ if (params?.toDate)
34
+ merged.toDate = new Date(params.toDate);
35
+ if (params?.dimensions?.length)
36
+ merged.dimensions = params.dimensions;
37
+ return merged;
38
+ }
39
+ async runIngester(ingester, params) {
40
+ const ingestionId = `ingest-${ingester.source}-${Date.now()}`;
41
+ const run = {
42
+ id: ingestionId,
43
+ source: ingester.source,
44
+ status: "running",
45
+ resultsCount: 0,
46
+ startedAt: new Date,
47
+ completedAt: null,
48
+ error: null
49
+ };
50
+ await this.store.createIngestionRun(run);
51
+ try {
52
+ const opts = this.mergeOptions(params);
53
+ const rawResults = await ingester.ingest(opts);
54
+ const normalized = normalizeBenchmarkResults(rawResults);
55
+ for (const result of normalized) {
56
+ await this.store.upsertBenchmarkResult(result);
57
+ }
58
+ await this.store.updateIngestionRun(ingestionId, {
59
+ status: "completed",
60
+ resultsCount: normalized.length,
61
+ completedAt: new Date
62
+ });
63
+ return {
64
+ ingestionId,
65
+ source: ingester.source,
66
+ resultsCount: normalized.length,
67
+ status: "completed"
68
+ };
69
+ } catch (error) {
70
+ const errorMessage = error instanceof Error ? error.message : String(error);
71
+ await this.store.updateIngestionRun(ingestionId, {
72
+ status: "failed",
73
+ completedAt: new Date,
74
+ error: errorMessage
75
+ });
76
+ return {
77
+ ingestionId,
78
+ source: ingester.source,
79
+ resultsCount: 0,
80
+ status: "failed"
81
+ };
82
+ }
83
+ }
84
+ }
85
+ export {
86
+ IngestionPipeline
87
+ };
@@ -0,0 +1,31 @@
1
+ import type { ProviderRankingStore } from '@contractspec/lib.provider-ranking/store';
2
+ import type { BenchmarkDimension, DimensionWeightConfig, ProviderTransportSupport, ProviderAuthSupport } from '@contractspec/lib.provider-ranking/types';
3
+ export interface RankingPipelineOptions {
4
+ store: ProviderRankingStore;
5
+ }
6
+ export interface RefreshParams {
7
+ weightOverrides?: DimensionWeightConfig[];
8
+ dimensions?: BenchmarkDimension[];
9
+ forceRecalculate?: boolean;
10
+ /** Filter rankings to models whose provider supports this transport. */
11
+ requiredTransport?: ProviderTransportSupport;
12
+ /** Filter rankings to models whose provider supports this auth method. */
13
+ requiredAuthMethod?: ProviderAuthSupport;
14
+ }
15
+ export interface RankingPipelineResult {
16
+ modelsRanked: number;
17
+ updatedAt: Date;
18
+ }
19
+ /**
20
+ * Orchestrates the ranking refresh flow:
21
+ * 1. Load all benchmark results from the store
22
+ * 2. Load existing rankings (for previousRank tracking)
23
+ * 3. Compute new composite rankings via the scoring engine
24
+ * 4. Persist updated rankings
25
+ */
26
+ export declare class RankingPipeline {
27
+ private readonly store;
28
+ constructor(options: RankingPipelineOptions);
29
+ refresh(params?: RefreshParams): Promise<RankingPipelineResult>;
30
+ private loadAllBenchmarkResults;
31
+ }
@@ -0,0 +1,50 @@
1
+ // @bun
2
+ // src/pipeline/ranking-pipeline.ts
3
+ import { computeModelRankings } from "@contractspec/lib.provider-ranking/scoring";
4
+
5
+ class RankingPipeline {
6
+ store;
7
+ constructor(options) {
8
+ this.store = options.store;
9
+ }
10
+ async refresh(params) {
11
+ let allResults = await this.loadAllBenchmarkResults();
12
+ if (params?.dimensions?.length) {
13
+ const dimSet = new Set(params.dimensions);
14
+ allResults = allResults.filter((r) => dimSet.has(r.dimension));
15
+ }
16
+ const existingRankings = params?.forceRecalculate ? new Map : new Map((await this.store.listModelRankings({
17
+ limit: 1e4,
18
+ requiredTransport: params?.requiredTransport,
19
+ requiredAuthMethod: params?.requiredAuthMethod
20
+ })).rankings.map((r) => [r.modelId, r]));
21
+ const newRankings = computeModelRankings(allResults, params?.weightOverrides ? { weightOverrides: params.weightOverrides } : undefined, existingRankings);
22
+ for (const ranking of newRankings) {
23
+ await this.store.upsertModelRanking(ranking);
24
+ }
25
+ return {
26
+ modelsRanked: newRankings.length,
27
+ updatedAt: new Date
28
+ };
29
+ }
30
+ async loadAllBenchmarkResults() {
31
+ const pageSize = 500;
32
+ let offset = 0;
33
+ const allResults = [];
34
+ while (true) {
35
+ const page = await this.store.listBenchmarkResults({
36
+ limit: pageSize,
37
+ offset
38
+ });
39
+ allResults.push(...page.results);
40
+ if (allResults.length >= page.total || page.results.length < pageSize) {
41
+ break;
42
+ }
43
+ offset += pageSize;
44
+ }
45
+ return allResults;
46
+ }
47
+ }
48
+ export {
49
+ RankingPipeline
50
+ };
@@ -0,0 +1,30 @@
1
+ import type { ProviderRankingStore } from '@contractspec/lib.provider-ranking/store';
2
+ 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
+ export interface PostgresProviderRankingStoreOptions {
5
+ database: DatabaseProvider;
6
+ schema?: string;
7
+ createTablesIfMissing?: boolean;
8
+ }
9
+ export declare class PostgresProviderRankingStore implements ProviderRankingStore {
10
+ private readonly database;
11
+ private readonly schema;
12
+ private readonly createTablesIfMissing;
13
+ private ensured;
14
+ constructor(options: PostgresProviderRankingStoreOptions);
15
+ upsertBenchmarkResult(result: BenchmarkResult): Promise<void>;
16
+ getBenchmarkResult(id: string): Promise<BenchmarkResult | null>;
17
+ listBenchmarkResults(query: BenchmarkResultQuery): Promise<BenchmarkResultListResult>;
18
+ upsertModelRanking(ranking: ModelRanking): Promise<void>;
19
+ getModelRanking(modelId: string): Promise<ModelRanking | null>;
20
+ listModelRankings(query: RankingQuery): Promise<RankingListResult>;
21
+ getModelProfile(modelId: string): Promise<ModelProfile | null>;
22
+ createIngestionRun(run: IngestionRun): Promise<void>;
23
+ updateIngestionRun(id: string, update: Partial<IngestionRun>): Promise<void>;
24
+ getIngestionRun(id: string): Promise<IngestionRun | null>;
25
+ private ensureTables;
26
+ private table;
27
+ private mapBenchmarkResult;
28
+ private mapModelRanking;
29
+ private mapIngestionRun;
30
+ }
@@ -0,0 +1,300 @@
1
+ // @bun
2
+ // src/storage/index.ts
3
+ class PostgresProviderRankingStore {
4
+ database;
5
+ schema;
6
+ createTablesIfMissing;
7
+ ensured = false;
8
+ constructor(options) {
9
+ this.database = options.database;
10
+ this.schema = options.schema ?? "lssm_ranking";
11
+ this.createTablesIfMissing = options.createTablesIfMissing ?? true;
12
+ }
13
+ async upsertBenchmarkResult(result) {
14
+ await this.ensureTables();
15
+ await this.database.execute(`INSERT INTO ${this.table("benchmark_result")}
16
+ (id, model_id, provider_key, source, dimension, score, raw_score, metadata, measured_at, ingested_at)
17
+ VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8::jsonb, $9, $10)
18
+ ON CONFLICT (id)
19
+ DO UPDATE SET
20
+ score = EXCLUDED.score,
21
+ raw_score = EXCLUDED.raw_score,
22
+ metadata = EXCLUDED.metadata,
23
+ measured_at = EXCLUDED.measured_at,
24
+ ingested_at = EXCLUDED.ingested_at;`, [
25
+ result.id,
26
+ result.modelId,
27
+ result.providerKey,
28
+ result.source,
29
+ result.dimension,
30
+ result.score,
31
+ JSON.stringify(result.rawScore),
32
+ result.metadata ? JSON.stringify(result.metadata) : null,
33
+ result.measuredAt.toISOString(),
34
+ result.ingestedAt.toISOString()
35
+ ]);
36
+ }
37
+ async getBenchmarkResult(id) {
38
+ await this.ensureTables();
39
+ const rows = await this.database.query(`SELECT * FROM ${this.table("benchmark_result")} WHERE id = $1;`, [id]);
40
+ return rows.rows[0] ? this.mapBenchmarkResult(rows.rows[0]) : null;
41
+ }
42
+ async listBenchmarkResults(query) {
43
+ await this.ensureTables();
44
+ const limit = query.limit ?? 50;
45
+ const offset = query.offset ?? 0;
46
+ const countFilters = [];
47
+ const countParams = [];
48
+ if (query.source) {
49
+ countParams.push(query.source);
50
+ countFilters.push(`source = $${countParams.length}`);
51
+ }
52
+ if (query.modelId) {
53
+ countParams.push(query.modelId);
54
+ countFilters.push(`model_id = $${countParams.length}`);
55
+ }
56
+ if (query.dimension) {
57
+ countParams.push(query.dimension);
58
+ countFilters.push(`dimension = $${countParams.length}`);
59
+ }
60
+ if (query.providerKey) {
61
+ countParams.push(query.providerKey);
62
+ countFilters.push(`provider_key = $${countParams.length}`);
63
+ }
64
+ const where = countFilters.length ? `WHERE ${countFilters.join(" AND ")}` : "";
65
+ const countResult = await this.database.query(`SELECT COUNT(*)::int as total FROM ${this.table("benchmark_result")} ${where};`, countParams);
66
+ const total = Number(countResult.rows[0]?.total ?? 0);
67
+ const dataParams = [
68
+ limit,
69
+ offset,
70
+ ...countParams
71
+ ];
72
+ const dataFilters = countFilters.map((_f, i) => _f.replace(`$${i + 1}`, `$${i + 3}`));
73
+ const dataWhere = dataFilters.length ? `WHERE ${dataFilters.join(" AND ")}` : "";
74
+ const rows = await this.database.query(`SELECT * FROM ${this.table("benchmark_result")}
75
+ ${dataWhere}
76
+ ORDER BY ingested_at DESC
77
+ LIMIT $1 OFFSET $2;`, dataParams);
78
+ const results = rows.rows.map((row) => this.mapBenchmarkResult(row));
79
+ const nextOffset = offset + results.length < total ? offset + results.length : undefined;
80
+ return { results, total, nextOffset };
81
+ }
82
+ async upsertModelRanking(ranking) {
83
+ await this.ensureTables();
84
+ await this.database.execute(`INSERT INTO ${this.table("model_ranking")}
85
+ (model_id, provider_key, composite_score, dimension_scores, rank, previous_rank, updated_at)
86
+ VALUES ($1, $2, $3, $4::jsonb, $5, $6, $7)
87
+ ON CONFLICT (model_id)
88
+ DO UPDATE SET
89
+ provider_key = EXCLUDED.provider_key,
90
+ composite_score = EXCLUDED.composite_score,
91
+ dimension_scores = EXCLUDED.dimension_scores,
92
+ rank = EXCLUDED.rank,
93
+ previous_rank = EXCLUDED.previous_rank,
94
+ updated_at = EXCLUDED.updated_at;`, [
95
+ ranking.modelId,
96
+ ranking.providerKey,
97
+ ranking.compositeScore,
98
+ JSON.stringify(ranking.dimensionScores),
99
+ ranking.rank,
100
+ ranking.previousRank,
101
+ ranking.updatedAt.toISOString()
102
+ ]);
103
+ }
104
+ async getModelRanking(modelId) {
105
+ await this.ensureTables();
106
+ const rows = await this.database.query(`SELECT * FROM ${this.table("model_ranking")} WHERE model_id = $1;`, [modelId]);
107
+ return rows.rows[0] ? this.mapModelRanking(rows.rows[0]) : null;
108
+ }
109
+ async listModelRankings(query) {
110
+ await this.ensureTables();
111
+ const limit = query.limit ?? 50;
112
+ const offset = query.offset ?? 0;
113
+ const countFilters = [];
114
+ const countParams = [];
115
+ if (query.providerKey) {
116
+ countParams.push(query.providerKey);
117
+ countFilters.push(`provider_key = $${countParams.length}`);
118
+ }
119
+ const where = countFilters.length ? `WHERE ${countFilters.join(" AND ")}` : "";
120
+ const countResult = await this.database.query(`SELECT COUNT(*)::int as total FROM ${this.table("model_ranking")} ${where};`, countParams);
121
+ const total = Number(countResult.rows[0]?.total ?? 0);
122
+ const dataParams = [
123
+ limit,
124
+ offset,
125
+ ...countParams
126
+ ];
127
+ const dataFilters = countFilters.map((_f, i) => _f.replace(`$${i + 1}`, `$${i + 3}`));
128
+ const dataWhere = dataFilters.length ? `WHERE ${dataFilters.join(" AND ")}` : "";
129
+ const orderBy = query.dimension ? `(dimension_scores->>'${query.dimension}')::jsonb->>'score' DESC NULLS LAST` : "rank ASC";
130
+ const rows = await this.database.query(`SELECT * FROM ${this.table("model_ranking")}
131
+ ${dataWhere}
132
+ ORDER BY ${orderBy}
133
+ LIMIT $1 OFFSET $2;`, dataParams);
134
+ const rankings = rows.rows.map((row) => this.mapModelRanking(row));
135
+ const nextOffset = offset + rankings.length < total ? offset + rankings.length : undefined;
136
+ return { rankings, total, nextOffset };
137
+ }
138
+ async getModelProfile(modelId) {
139
+ await this.ensureTables();
140
+ const ranking = await this.getModelRanking(modelId);
141
+ const benchResults = await this.database.query(`SELECT * FROM ${this.table("benchmark_result")}
142
+ WHERE model_id = $1
143
+ ORDER BY ingested_at DESC;`, [modelId]);
144
+ if (!ranking && benchResults.rows.length === 0)
145
+ return null;
146
+ return {
147
+ modelId,
148
+ providerKey: ranking?.providerKey ?? String(benchResults.rows[0]?.provider_key ?? "unknown"),
149
+ displayName: modelId,
150
+ contextWindow: 0,
151
+ costPerMillion: null,
152
+ capabilities: [],
153
+ ranking: ranking ?? null,
154
+ benchmarkResults: benchResults.rows.map((row) => this.mapBenchmarkResult(row))
155
+ };
156
+ }
157
+ async createIngestionRun(run) {
158
+ await this.ensureTables();
159
+ await this.database.execute(`INSERT INTO ${this.table("ingestion_run")}
160
+ (id, source, status, results_count, started_at, completed_at, error)
161
+ VALUES ($1, $2, $3, $4, $5, $6, $7);`, [
162
+ run.id,
163
+ run.source,
164
+ run.status,
165
+ run.resultsCount,
166
+ run.startedAt.toISOString(),
167
+ run.completedAt?.toISOString() ?? null,
168
+ run.error
169
+ ]);
170
+ }
171
+ async updateIngestionRun(id, update) {
172
+ await this.ensureTables();
173
+ const sets = [];
174
+ const params = [id];
175
+ if (update.status !== undefined) {
176
+ params.push(update.status);
177
+ sets.push(`status = $${params.length}`);
178
+ }
179
+ if (update.resultsCount !== undefined) {
180
+ params.push(update.resultsCount);
181
+ sets.push(`results_count = $${params.length}`);
182
+ }
183
+ if (update.completedAt !== undefined) {
184
+ params.push(update.completedAt?.toISOString() ?? null);
185
+ sets.push(`completed_at = $${params.length}`);
186
+ }
187
+ if (update.error !== undefined) {
188
+ params.push(update.error);
189
+ sets.push(`error = $${params.length}`);
190
+ }
191
+ if (sets.length === 0)
192
+ return;
193
+ await this.database.execute(`UPDATE ${this.table("ingestion_run")} SET ${sets.join(", ")} WHERE id = $1;`, params);
194
+ }
195
+ async getIngestionRun(id) {
196
+ await this.ensureTables();
197
+ const rows = await this.database.query(`SELECT * FROM ${this.table("ingestion_run")} WHERE id = $1;`, [id]);
198
+ return rows.rows[0] ? this.mapIngestionRun(rows.rows[0]) : null;
199
+ }
200
+ async ensureTables() {
201
+ if (this.ensured || !this.createTablesIfMissing)
202
+ return;
203
+ await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.schema};`);
204
+ await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.table("benchmark_result")} (
205
+ id text PRIMARY KEY,
206
+ model_id text NOT NULL,
207
+ provider_key text NOT NULL,
208
+ source text NOT NULL,
209
+ dimension text NOT NULL,
210
+ score double precision NOT NULL,
211
+ raw_score jsonb,
212
+ metadata jsonb,
213
+ measured_at timestamptz NOT NULL,
214
+ ingested_at timestamptz NOT NULL
215
+ );`);
216
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS benchmark_result_model_idx
217
+ ON ${this.table("benchmark_result")} (model_id);`);
218
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS benchmark_result_source_idx
219
+ ON ${this.table("benchmark_result")} (source);`);
220
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS benchmark_result_dimension_idx
221
+ ON ${this.table("benchmark_result")} (dimension);`);
222
+ await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.table("model_ranking")} (
223
+ model_id text PRIMARY KEY,
224
+ provider_key text NOT NULL,
225
+ composite_score double precision NOT NULL,
226
+ dimension_scores jsonb NOT NULL,
227
+ rank int NOT NULL,
228
+ previous_rank int,
229
+ updated_at timestamptz NOT NULL
230
+ );`);
231
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS model_ranking_rank_idx
232
+ ON ${this.table("model_ranking")} (rank);`);
233
+ await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.table("ingestion_run")} (
234
+ id text PRIMARY KEY,
235
+ source text NOT NULL,
236
+ status text NOT NULL,
237
+ results_count int NOT NULL DEFAULT 0,
238
+ started_at timestamptz NOT NULL,
239
+ completed_at timestamptz,
240
+ error text
241
+ );`);
242
+ this.ensured = true;
243
+ }
244
+ table(name) {
245
+ return `${this.schema}.${name}`;
246
+ }
247
+ mapBenchmarkResult(row) {
248
+ return {
249
+ id: String(row.id),
250
+ modelId: String(row.model_id),
251
+ providerKey: String(row.provider_key),
252
+ source: String(row.source),
253
+ dimension: String(row.dimension),
254
+ score: Number(row.score),
255
+ rawScore: parseJson(row.raw_score),
256
+ metadata: parseJson(row.metadata) ?? {},
257
+ measuredAt: new Date(String(row.measured_at)),
258
+ ingestedAt: new Date(String(row.ingested_at))
259
+ };
260
+ }
261
+ mapModelRanking(row) {
262
+ return {
263
+ modelId: String(row.model_id),
264
+ providerKey: String(row.provider_key),
265
+ compositeScore: Number(row.composite_score),
266
+ dimensionScores: parseJson(row.dimension_scores) ?? {},
267
+ rank: Number(row.rank),
268
+ previousRank: row.previous_rank != null ? Number(row.previous_rank) : null,
269
+ updatedAt: new Date(String(row.updated_at))
270
+ };
271
+ }
272
+ mapIngestionRun(row) {
273
+ return {
274
+ id: String(row.id),
275
+ source: String(row.source),
276
+ status: String(row.status),
277
+ resultsCount: Number(row.results_count),
278
+ startedAt: new Date(String(row.started_at)),
279
+ completedAt: row.completed_at ? new Date(String(row.completed_at)) : null,
280
+ error: row.error ? String(row.error) : null
281
+ };
282
+ }
283
+ }
284
+ function parseJson(value) {
285
+ if (value == null)
286
+ return null;
287
+ if (typeof value === "object")
288
+ return value;
289
+ if (typeof value === "string") {
290
+ try {
291
+ return JSON.parse(value);
292
+ } catch {
293
+ return null;
294
+ }
295
+ }
296
+ return value;
297
+ }
298
+ export {
299
+ PostgresProviderRankingStore
300
+ };