@ancplua/qyl-api-schema 0.5.10 → 0.5.11

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,461 @@
1
+ // =============================================================================
2
+ // GenAI cost provenance and ETL-audit contracts
3
+ // =============================================================================
4
+
5
+ import "@typespec/openapi";
6
+ import "../common/types.tsp";
7
+
8
+ using TypeSpec.OpenAPI;
9
+ using Qyl.Api.Contracts.Common;
10
+
11
+ namespace Qyl.Api.Contracts.Cost;
12
+
13
+ @doc("How a provider-owned API supplied a cost value.")
14
+ enum ProviderCostSourceKind {
15
+ @doc("Actual billed cost returned by a provider organization or cloud billing API.")
16
+ actualBilledCost: "actual_billed_cost",
17
+
18
+ @doc("A public SKU price returned by an official provider or cloud pricing API.")
19
+ officialPublicCatalog: "official_public_catalog",
20
+ }
21
+
22
+ @doc("Current synchronization state for a provider-owned cost source.")
23
+ enum ProviderCostSourceStatus {
24
+ @doc("The source has no configured provider credential or cloud identity.")
25
+ unconfigured: "unconfigured",
26
+
27
+ @doc("The most recent provider fetch completed inside its freshness window.")
28
+ current: "current",
29
+
30
+ @doc("The last successful provider response is older than its freshness window.")
31
+ stale: "stale",
32
+
33
+ @doc("The latest provider fetch failed; no raw credential or upstream response is exposed.")
34
+ syncFailed: "sync_failed",
35
+
36
+ @doc("Provider cost exists but cannot be attributed to the reported trace clusters.")
37
+ notAttributable: "not_attributable",
38
+ }
39
+
40
+ @doc("Granularity at which a cost value can be attributed to telemetry.")
41
+ enum ProviderCostAttribution {
42
+ @doc("The provider API identified the model and period represented by the value.")
43
+ providerModelPeriod: "provider_model_period",
44
+
45
+ @doc("The provider API identified only a provider/account period, not a model or request.")
46
+ providerPeriod: "provider_period",
47
+
48
+ @doc("The value was supplied explicitly as a what-if scenario input.")
49
+ scenario: "scenario",
50
+
51
+ @doc("No defensible attribution is available.")
52
+ unavailable: "unavailable",
53
+ }
54
+
55
+ @doc("Provenance for cost data fetched directly from a provider-owned API.")
56
+ model ProviderCostSource {
57
+ @doc("Canonical GenAI provider or cloud billing system name.")
58
+ provider: string;
59
+
60
+ @doc("Whether the API returned billed cost or a public catalog price.")
61
+ kind: ProviderCostSourceKind;
62
+
63
+ @doc("Current synchronization and attribution state.")
64
+ status: ProviderCostSourceStatus;
65
+
66
+ @doc("Official provider-owned API endpoint used for the fetch.")
67
+ @encodedName("application/json", "source_endpoint")
68
+ sourceEndpoint: UrlString;
69
+
70
+ @doc("Attribution granularity supported by this source response.")
71
+ attribution: ProviderCostAttribution;
72
+
73
+ @doc("UTC time of the latest fetch attempt.")
74
+ @encodedName("application/json", "last_attempt_at")
75
+ lastAttemptAt?: utcDateTime;
76
+
77
+ @doc("UTC time of the latest successful fetch.")
78
+ @encodedName("application/json", "last_success_at")
79
+ lastSuccessAt?: utcDateTime;
80
+
81
+ @doc("Start of the provider billing or catalog validity period.")
82
+ @encodedName("application/json", "period_start")
83
+ periodStart?: utcDateTime;
84
+
85
+ @doc("End of the provider billing or catalog validity period.")
86
+ @encodedName("application/json", "period_end")
87
+ periodEnd?: utcDateTime;
88
+
89
+ @doc("ISO 4217 currency code returned by the provider.")
90
+ @encodedName("application/json", "currency_code")
91
+ currencyCode?: string;
92
+
93
+ @doc("Cost returned by the provider for this period, converted from provider-declared USD units without rate inference.")
94
+ @encodedName("application/json", "reported_cost_usd")
95
+ reportedCostUsd?: CostUsd;
96
+
97
+ @doc("Model identifier returned by the provider when the source supports it.")
98
+ @encodedName("application/json", "model")
99
+ modelName?: string;
100
+
101
+ @doc("Stable, non-sensitive failure category for the last attempt.")
102
+ @encodedName("application/json", "failure_category")
103
+ failureCategory?: string;
104
+ }
105
+
106
+ @doc("Task family inferred for a repeated GenAI workflow cluster.")
107
+ enum GenAiEtlTaskFamily {
108
+ openReasoningGeneration: "open_reasoning_generation",
109
+ classification: "classification",
110
+ sequenceLabeling: "sequence_labeling",
111
+ structuredExtraction: "structured_extraction",
112
+ retrievalEntityResolution: "retrieval_entity_resolution",
113
+ similarityClustering: "similarity_clustering",
114
+ normalizationTransformation: "normalization_transformation",
115
+ numericAnalytical: "numeric_analytical",
116
+ `unknown`: "unknown",
117
+ }
118
+
119
+ @doc("Machine-readable output contract inferred for a workflow cluster.")
120
+ enum GenAiEtlOutputContract {
121
+ prose: "prose",
122
+ label: "label",
123
+ spanList: "span_list",
124
+ record: "record",
125
+ vector: "vector",
126
+ catalogReference: "catalog_reference",
127
+ number: "number",
128
+ boolean: "boolean",
129
+ `unknown`: "unknown",
130
+ }
131
+
132
+ @doc("Lowest-risk implementation class suggested by currently available evidence.")
133
+ enum GenAiEtlCandidatePath {
134
+ exactCache: "exact_cache",
135
+ deterministicCode: "deterministic_code",
136
+ boundedRetrieval: "bounded_retrieval",
137
+ smallClassifierExtractor: "small_classifier_extractor",
138
+ smallerGenerativeModel: "smaller_generative_model",
139
+ frontierModel: "frontier_model",
140
+ insufficientEvidence: "insufficient_evidence",
141
+ }
142
+
143
+ @doc("Task-specific metric that can falsify a proposed replacement.")
144
+ enum GenAiEtlValidationMetric {
145
+ accuracy: "accuracy",
146
+ macroF1: "macro_f1",
147
+ spanF1: "span_f1",
148
+ fieldExactMatch: "field_exact_match",
149
+ schemaValidity: "schema_validity",
150
+ matchAccuracy: "match_accuracy",
151
+ recallAtK: "recall_at_k",
152
+ meanReciprocalRank: "mean_reciprocal_rank",
153
+ exactMatch: "exact_match",
154
+ numericTolerance: "numeric_tolerance",
155
+ humanPreference: "human_preference",
156
+ taskSuccess: "task_success",
157
+ unavailable: "unavailable",
158
+ }
159
+
160
+ @doc("Fallback used for requests outside a promoted replacement's safe coverage.")
161
+ enum GenAiEtlResidualPath {
162
+ frontierModel: "frontier_model",
163
+ humanReview: "human_review",
164
+ retry: "retry",
165
+ degradedResponse: "degraded_response",
166
+ none: "none",
167
+ undetermined: "undetermined",
168
+ }
169
+
170
+ @doc("Observable evidence needed to promote an inferred replacement safely.")
171
+ enum GenAiEtlEvidenceSignal {
172
+ workflowIdentity: "workflow_identity",
173
+ outputContract: "output_contract",
174
+ providerModel: "provider_model",
175
+ tokenUsage: "token_usage",
176
+ providerCost: "provider_cost",
177
+ recurringSchema: "recurring_schema",
178
+ boundedReference: "bounded_reference",
179
+ taskMetric: "task_metric",
180
+ acceptedOutputReplay: "accepted_output_replay",
181
+ downstreamFate: "downstream_fate",
182
+ calibratedConfidence: "calibrated_confidence",
183
+ shadowComparison: "shadow_comparison",
184
+ fallbackPolicy: "fallback_policy",
185
+ }
186
+
187
+ @doc("Promotion gate from replay through limited serving and rollback.")
188
+ enum GenAiEtlPromotionGateKind {
189
+ contractStability: "contract_stability",
190
+ offlineReplay: "offline_replay",
191
+ calibratedConfidence: "calibrated_confidence",
192
+ shadowTraffic: "shadow_traffic",
193
+ limitedServing: "limited_serving",
194
+ rollbackResidualPolicy: "rollback_residual_policy",
195
+ }
196
+
197
+ @doc("Evaluation state of a promotion gate.")
198
+ enum GenAiEtlPromotionGateState {
199
+ passed: "passed",
200
+ failed: "failed",
201
+ notEvaluated: "not_evaluated",
202
+ blockedMissingEvidence: "blocked_missing_evidence",
203
+ }
204
+
205
+ @doc("Promotion readiness for one gate.")
206
+ model GenAiEtlPromotionGate {
207
+ gate: GenAiEtlPromotionGateKind;
208
+ state: GenAiEtlPromotionGateState;
209
+
210
+ @doc("Stable evidence code explaining the current gate state.")
211
+ reason?: string;
212
+ }
213
+
214
+ @doc("Observed economics and evidence for one repeated GenAI workflow cluster.")
215
+ model GenAiEtlAuditCluster {
216
+ @doc("Stable identifier derived from the privacy-safe cluster dimensions.")
217
+ @encodedName("application/json", "cluster_id")
218
+ clusterId: string;
219
+
220
+ @doc("Privacy-safe workflow identity used to group repeated calls.")
221
+ @encodedName("application/json", "workflow_key")
222
+ workflowKey: string;
223
+
224
+ @encodedName("application/json", "service_name")
225
+ serviceName: string;
226
+
227
+ @encodedName("application/json", "span_name")
228
+ spanName: string;
229
+
230
+ @encodedName("application/json", "operation_name")
231
+ operationName?: string;
232
+
233
+ provider?: string;
234
+
235
+ @encodedName("application/json", "model")
236
+ modelName?: string;
237
+
238
+ @encodedName("application/json", "output_contract")
239
+ outputContract: GenAiEtlOutputContract;
240
+
241
+ @encodedName("application/json", "task_family")
242
+ taskFamily: GenAiEtlTaskFamily;
243
+
244
+ @encodedName("application/json", "call_count")
245
+ callCount: Count;
246
+
247
+ @encodedName("application/json", "input_tokens")
248
+ inputTokens: TokenCount;
249
+
250
+ @encodedName("application/json", "output_tokens")
251
+ outputTokens: TokenCount;
252
+
253
+ @encodedName("application/json", "cache_read_input_tokens")
254
+ cacheReadInputTokens: TokenCount;
255
+
256
+ @encodedName("application/json", "cache_creation_input_tokens")
257
+ cacheCreationInputTokens: TokenCount;
258
+
259
+ @encodedName("application/json", "reasoning_output_tokens")
260
+ reasoningOutputTokens: TokenCount;
261
+
262
+ @encodedName("application/json", "error_count")
263
+ errorCount: Count;
264
+
265
+ @encodedName("application/json", "error_rate")
266
+ errorRate: Ratio;
267
+
268
+ @encodedName("application/json", "average_latency_ms")
269
+ averageLatencyMs: DurationMs;
270
+
271
+ @encodedName("application/json", "p95_latency_ms")
272
+ p95LatencyMs: DurationMs;
273
+
274
+ @doc("Provider-sourced cost attributable to this cluster, absent when attribution is not defensible.")
275
+ @encodedName("application/json", "observed_cost_usd")
276
+ observedCostUsd?: CostUsd;
277
+
278
+ @encodedName("application/json", "cost_per_call_usd")
279
+ costPerCallUsd?: CostUsd;
280
+
281
+ @encodedName("application/json", "cost_attribution")
282
+ costAttribution: ProviderCostAttribution;
283
+
284
+ @doc("Fraction of attributable spend represented by this cluster.")
285
+ @encodedName("application/json", "spend_share")
286
+ spendShare?: Ratio;
287
+
288
+ @doc("Fraction of calls with a task-specific correctness signal.")
289
+ @encodedName("application/json", "measurable_coverage")
290
+ measurableCoverage?: Ratio;
291
+
292
+ @doc("Fraction of calls proven safe to route to a residual path.")
293
+ @encodedName("application/json", "safe_deferral_coverage")
294
+ safeDeferralCoverage?: Ratio;
295
+
296
+ @encodedName("application/json", "candidate_path")
297
+ candidatePath: GenAiEtlCandidatePath;
298
+
299
+ @encodedName("application/json", "validation_metric")
300
+ validationMetric: GenAiEtlValidationMetric;
301
+
302
+ @encodedName("application/json", "residual_path")
303
+ residualPath: GenAiEtlResidualPath;
304
+
305
+ @encodedName("application/json", "evidence_signals")
306
+ evidenceSignals: GenAiEtlEvidenceSignal[];
307
+
308
+ @encodedName("application/json", "missing_evidence")
309
+ missingEvidence: GenAiEtlEvidenceSignal[];
310
+
311
+ @encodedName("application/json", "promotion_gates")
312
+ promotionGates: GenAiEtlPromotionGate[];
313
+ }
314
+
315
+ @doc("Aggregate trace-level economics for the requested audit period.")
316
+ model GenAiEtlAuditSummary {
317
+ @encodedName("application/json", "total_calls")
318
+ totalCalls: Count;
319
+
320
+ @encodedName("application/json", "total_input_tokens")
321
+ totalInputTokens: TokenCount;
322
+
323
+ @encodedName("application/json", "total_output_tokens")
324
+ totalOutputTokens: TokenCount;
325
+
326
+ @doc("Provider-sourced cost attributable to the returned trace population.")
327
+ @encodedName("application/json", "attributed_cost_usd")
328
+ attributedCostUsd?: CostUsd;
329
+
330
+ @doc("Fraction of calls covered by provider cost attribution.")
331
+ @encodedName("application/json", "priced_call_coverage")
332
+ pricedCallCoverage: Ratio;
333
+
334
+ @doc("Fraction of attributable spend represented by the returned top clusters.")
335
+ @encodedName("application/json", "economic_concentration")
336
+ economicConcentration?: Ratio;
337
+
338
+ @doc("Fraction of calls with a task-specific correctness signal.")
339
+ @encodedName("application/json", "measurable_coverage")
340
+ measurableCoverage?: Ratio;
341
+
342
+ @doc("Fraction of calls proven safe to defer to a residual path.")
343
+ @encodedName("application/json", "safe_deferral_coverage")
344
+ safeDeferralCoverage?: Ratio;
345
+
346
+ @doc("Fraction of attributable spend attached to ETL-shaped candidates.")
347
+ @encodedName("application/json", "candidate_etl_spend_share")
348
+ candidateEtlSpendShare?: Ratio;
349
+ }
350
+
351
+ @doc("Trace-level ETL audit for a bounded period.")
352
+ model GenAiEtlAuditReport {
353
+ @encodedName("application/json", "generated_at")
354
+ generatedAt: utcDateTime;
355
+
356
+ @encodedName("application/json", "period_start")
357
+ periodStart: utcDateTime;
358
+
359
+ @encodedName("application/json", "period_end")
360
+ periodEnd: utcDateTime;
361
+
362
+ summary: GenAiEtlAuditSummary;
363
+
364
+ @encodedName("application/json", "cost_sources")
365
+ costSources: ProviderCostSource[];
366
+
367
+ clusters: GenAiEtlAuditCluster[];
368
+ }
369
+
370
+ @doc("What-if assumptions for one workflow cluster over the requested period.")
371
+ model GenAiEtlClusterScenario {
372
+ @encodedName("application/json", "cluster_id")
373
+ clusterId: string;
374
+
375
+ @doc("Fraction of cluster calls the replacement is expected to serve safely.")
376
+ coverage: Ratio;
377
+
378
+ @doc("Explicit baseline cost per call. When absent, qyl uses provider-attributed cluster cost and otherwise fails closed.")
379
+ @encodedName("application/json", "frontier_cost_per_call_usd")
380
+ frontierCostPerCallUsd?: CostUsd;
381
+
382
+ @encodedName("application/json", "alternative_cost_per_call_usd")
383
+ alternativeCostPerCallUsd: CostUsd;
384
+
385
+ @encodedName("application/json", "period_maintenance_cost_usd")
386
+ periodMaintenanceCostUsd: CostUsd;
387
+
388
+ @encodedName("application/json", "period_error_cost_usd")
389
+ periodErrorCostUsd: CostUsd;
390
+ }
391
+
392
+ @doc("Batch of cluster scenarios evaluated against one audit period.")
393
+ model GenAiEtlAuditEvaluationRequest {
394
+ @minItems(1)
395
+ @maxItems(100)
396
+ scenarios: GenAiEtlClusterScenario[];
397
+ }
398
+
399
+ @doc("Whether the replaceable-value formula had a defensible baseline cost.")
400
+ enum GenAiEtlCalculationStatus {
401
+ calculated: "calculated",
402
+ missingFrontierCost: "missing_frontier_cost",
403
+ }
404
+
405
+ @doc("Result of replaceable_value = calls * coverage * (frontier - alternative) - maintenance - error.")
406
+ model GenAiEtlClusterEvaluation {
407
+ @encodedName("application/json", "cluster_id")
408
+ clusterId: string;
409
+
410
+ status: GenAiEtlCalculationStatus;
411
+
412
+ @encodedName("application/json", "call_count")
413
+ callCount: Count;
414
+
415
+ coverage: Ratio;
416
+
417
+ @encodedName("application/json", "served_call_count")
418
+ servedCallCount: float64;
419
+
420
+ @encodedName("application/json", "residual_call_count")
421
+ residualCallCount: float64;
422
+
423
+ @encodedName("application/json", "frontier_cost_per_call_usd")
424
+ frontierCostPerCallUsd?: CostUsd;
425
+
426
+ @encodedName("application/json", "frontier_cost_basis")
427
+ frontierCostBasis: ProviderCostAttribution;
428
+
429
+ @encodedName("application/json", "alternative_cost_per_call_usd")
430
+ alternativeCostPerCallUsd: CostUsd;
431
+
432
+ @encodedName("application/json", "current_period_cost_usd")
433
+ currentPeriodCostUsd?: CostUsd;
434
+
435
+ @encodedName("application/json", "gross_replaceable_value_usd")
436
+ grossReplaceableValueUsd?: float64;
437
+
438
+ @encodedName("application/json", "period_maintenance_cost_usd")
439
+ periodMaintenanceCostUsd: CostUsd;
440
+
441
+ @encodedName("application/json", "period_error_cost_usd")
442
+ periodErrorCostUsd: CostUsd;
443
+
444
+ @doc("Net period value; this may be negative when maintenance or error cost exceeds gross savings.")
445
+ @encodedName("application/json", "net_replaceable_value_usd")
446
+ netReplaceableValueUsd?: float64;
447
+ }
448
+
449
+ @doc("Batch result for ETL replacement scenarios.")
450
+ model GenAiEtlAuditEvaluationResponse {
451
+ @encodedName("application/json", "generated_at")
452
+ generatedAt: utcDateTime;
453
+
454
+ @encodedName("application/json", "period_start")
455
+ periodStart: utcDateTime;
456
+
457
+ @encodedName("application/json", "period_end")
458
+ periodEnd: utcDateTime;
459
+
460
+ results: GenAiEtlClusterEvaluation[];
461
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ancplua/qyl-api-schema",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "description": "TypeSpec source of truth for qyl API contracts. Emits OpenAPI, JSON Schema, Qyl.Api.Contracts DTOs, and TypeScript contract types; not an OpenTelemetry package, storage schema, or server implementation.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -58,6 +58,7 @@
58
58
  "models/runner.tsp",
59
59
  "models/mcp-tools.tsp",
60
60
  "models/session.tsp",
61
+ "models/cost-etl-audit.tsp",
61
62
  "otel/enums.tsp",
62
63
  "otel/logs.tsp",
63
64
  "otel/otel-conventions.tsp",
@@ -131,6 +132,6 @@
131
132
  "@typespec/openapi3": "1.13.0",
132
133
  "@typespec/sse": "0.83.0",
133
134
  "ajv": "8.20.0",
134
- "typescript": "6.0.3"
135
+ "typescript": "7.0.2"
135
136
  }
136
137
  }