/** 从样例 JSON 泛化出 JSON Schema(阶段0专用,无业务状态机) */ type JsonSchema = Record; function inferValueSchema(value: unknown): JsonSchema { if (value === null) { return { type: "null" }; } if (Array.isArray(value)) { const itemSchemas = value.map((item) => inferValueSchema(item)); return { type: "array", items: mergeSchemas(itemSchemas), minItems: value.length > 0 ? 1 : 0, }; } switch (typeof value) { case "boolean": return { type: "boolean" }; case "number": return Number.isInteger(value) ? { type: "integer" } : { type: "number" }; case "string": return { type: "string" }; case "object": { const obj = value as Record; const properties: Record = {}; for (const [key, child] of Object.entries(obj)) { properties[key] = inferValueSchema(child); } return { type: "object", properties, additionalProperties: true, }; } default: return {}; } } function mergeSchemas(schemas: JsonSchema[]): JsonSchema { if (schemas.length === 0) { return {}; } if (schemas.length === 1) { return schemas[0]!; } const types = new Set(schemas.map((s) => s.type).filter(Boolean)); if (types.size === 1) { const type = [...types][0]; if (type === "object") { const propMap = new Map(); for (const schema of schemas) { const props = schema.properties as Record | undefined; if (!props) { continue; } for (const [key, child] of Object.entries(props)) { const list = propMap.get(key) ?? []; list.push(child); propMap.set(key, list); } } const properties: Record = {}; for (const [key, children] of propMap) { properties[key] = mergeSchemas(children); } return { type: "object", properties, additionalProperties: true }; } if (type === "array") { const items = mergeSchemas( schemas.map((s) => (s.items as JsonSchema | undefined) ?? {}), ); return { type: "array", items, minItems: 1 }; } } return { oneOf: schemas }; } function annotateQuoteContract(schema: JsonSchema): JsonSchema { const props = schema.properties as Record | undefined; if (!props) { return schema; } if (props.rates) { props.rates = { ...props.rates, description: "报价 rates 分支(standard/guaranteed/fastest)", }; } if (props.data && typeof props.data === "object") { const dataProps = (props.data as JsonSchema).properties as | Record | undefined; if (dataProps?.rates) { dataProps.rates = { ...dataProps.rates, description: "嵌套 data.rates 报价分支", }; } } return schema; } export type QuoteContractSchemaDocument = { $schema: string; $id: string; title: string; description: string; generatedAt: string; sourceHar: { url: string; method: string; status: number; }; contractHints: { minRateTiers: number; placeIdPattern: string; quoteUrlPatterns: string[]; }; body: JsonSchema; }; export function buildQuoteContractSchema(input: { body: unknown; harEntry: { url: string; method: string; status: number }; }): QuoteContractSchemaDocument { const bodySchema = annotateQuoteContract(inferValueSchema(input.body)); return { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "https://chajia.local/schemas/mothership-quote-contract.json", title: "MotherShip Quote Response Contract", description: "从 baseline.har 最佳 quote 响应泛化;供阶段1 Contract-Driven Capture 白名单校验。", generatedAt: new Date().toISOString(), sourceHar: { url: input.harEntry.url, method: input.harEntry.method, status: input.harEntry.status, }, contractHints: { minRateTiers: 4, placeIdPattern: "^[A-Za-z0-9_-]{8,128}$", quoteUrlPatterns: [ "services.mothership.com/axel/quote", ], primaryRatesPath: "data.rates", allRatesPath: "data.availableRates", tierMapping: { "standard/lowest": "data.rates.standard.lowest", "standard/fastest": "data.rates.standard.fastest", "guaranteed/lowest": "data.rates.guaranteed.lowest | data.rates.guaranteed.bestValue", "guaranteed/fastest": "data.rates.guaranteed.fastest | data.rates.guaranteed.bestValue", }, note: "axel/quote 的 guaranteed 分支可能仅含 bestValue,无独立 lowest/fastest 键;阶段1 需映射而非 UI tab 切换。", }, body: bodySchema, }; }