import type { AcquisitionSlot, AcquisitionSlotCreate, SlotStatus as ContractSlotStatus, JsonObject, } from "@umt/contracts"; import type { SlotStatus as DomainSlotStatus, SlotCompletionSpec, SlotEvaluation, SlotHardConditions, SlotSoftPreferences, SlotSpec, SlotUpgradePolicy, } from "@umt/domain"; // Slot:状态两侧取值相同(REQ §6.1),映射表只为让任一侧增删值时编译/测试失败。 // 规则 JSON 列(hard_conditions / soft_preferences / upgrade_threshold / completion / fallback)↔ domain SlotSpec。 export type { DomainSlotStatus }; const TO_CONTRACT: Record = { empty: "empty", reserved: "reserved", downloading: "downloading", verifying: "verifying", satisfied: "satisfied", attention: "attention", upgrade_pending: "upgrade_pending", }; const TO_DOMAIN: Record = { empty: "empty", reserved: "reserved", downloading: "downloading", verifying: "verifying", satisfied: "satisfied", upgrade_pending: "upgrade_pending", attention: "attention", }; export const DOMAIN_SLOT_STATUSES = Object.keys(TO_CONTRACT) as DomainSlotStatus[]; export const toContractSlotStatus = (s: DomainSlotStatus): ContractSlotStatus => TO_CONTRACT[s]; export const toDomainSlotStatus = (s: ContractSlotStatus): DomainSlotStatus => TO_DOMAIN[s]; type SlotRuleRow = Pick< AcquisitionSlot, "id" | "name" | "hardConditions" | "softPreferences" | "upgradeThreshold" | "completion" | "fallback" >; const isEmpty = (o: JsonObject | null | undefined) => !o || Object.keys(o).length === 0; /** * acquisition_slot 行 → domain SlotSpec。JSON 列按 domain 类型原样解释(写入时由 slotSpecToColumns 保证形状); * fallback.unknownPolicy 对应 SlotSpec.unknownPolicy。 */ export function slotSpecFromRow(row: SlotRuleRow): SlotSpec { const unknownPolicy = row.fallback.unknownPolicy as SlotSpec["unknownPolicy"] | undefined; return { id: row.id, name: row.name, hard: row.hardConditions as SlotHardConditions, ...(unknownPolicy !== undefined ? { unknownPolicy } : {}), ...(isEmpty(row.softPreferences) ? {} : { soft: row.softPreferences as SlotSoftPreferences }), ...(isEmpty(row.upgradeThreshold) ? {} : { upgrade: row.upgradeThreshold as unknown as SlotUpgradePolicy }), ...(isEmpty(row.completion) ? {} : { completion: row.completion as unknown as SlotCompletionSpec }), }; } /** domain SlotSpec → acquisition_slot 规则列(Create/Update 可直接展开) */ export function slotSpecToColumns( spec: SlotSpec, ): Pick< AcquisitionSlotCreate, "name" | "hardConditions" | "softPreferences" | "upgradeThreshold" | "completion" | "fallback" > { const json = (v: unknown): JsonObject => (v ? (JSON.parse(JSON.stringify(v)) as JsonObject) : {}); return { name: spec.name, hardConditions: json(spec.hard), softPreferences: json(spec.soft), upgradeThreshold: json(spec.upgrade), completion: json(spec.completion), fallback: spec.unknownPolicy === undefined ? {} : json({ unknownPolicy: spec.unknownPolicy }), }; } export interface ContractSlotEvaluation { decision: SlotEvaluation["decision"]; /** 可以用这个候选生成计划(accept 或 upgrade) */ acceptable: boolean; /** 与 DownloadPlanOutput.rejected 同形:硬条件不满足 / 字段未知的原因 */ rejected: { code: string; message: string }[]; reasons: string[]; } /** domain 评估结果 → 能力输出/决策记录用的扁平表示 */ export function toContractSlotEvaluation(ev: SlotEvaluation): ContractSlotEvaluation { switch (ev.decision) { case "accept": case "upgrade": return { decision: ev.decision, acceptable: true, rejected: [], reasons: [...ev.reasons] }; case "reject": return { decision: "reject", acceptable: false, rejected: ev.violations.map((v) => ({ code: `${v.field}.${v.code}`, message: v.expected === undefined ? `${v.field}: ${v.code}` : `${v.field}: ${v.code}(期望 ${JSON.stringify(v.expected)},实际 ${JSON.stringify(v.actual ?? null)})`, })), reasons: [], }; case "unknown_field": return { decision: "unknown_field", acceptable: false, rejected: ev.missing.map((m) => ({ code: `unknown_field.${m}`, message: `字段未知,需要补充: ${m}` })), reasons: [], }; } }