New @umt/mapping (depends on domain + contracts only): download status + pause reason, evidence level / media kind, identity resolution status, slot status / spec columns / evaluation, ParsedRelease <-> source_release.parsed, SiteRelease <-> source_release, PathBinding, OrganizePlan -> organization_record / file results / organize.plan output. Tables are Record<union, ...> so adding a value on either side fails typecheck or tests. Co-Authored-By: Claude Opus 5.5 <[email protected]>
107 lines
3.6 KiB
TypeScript
107 lines
3.6 KiB
TypeScript
import type {
|
|
ExternalIdentity,
|
|
ExternalIdentityCreate,
|
|
IdentityResolveOutput,
|
|
IdentityStatus,
|
|
MediaEntity,
|
|
ResolutionStatus,
|
|
} from "@umt/contracts";
|
|
import type { IdentityCandidate, IdentityResolution, RankedCandidate } from "@umt/domain";
|
|
import { toContractEvidenceLevel, toDomainMediaType } from "./evidence.ts";
|
|
|
|
// 身份解析:domain.resolveIdentity(auto / ambiguous / no_match)→ contracts 的能力输出状态
|
|
// 与 source_release.resolution_status。
|
|
|
|
export type DomainResolutionStatus = IdentityResolution["status"];
|
|
export type ContractResolveStatus = IdentityResolveOutput["status"];
|
|
|
|
export interface ResolveStatusOptions {
|
|
/**
|
|
* auto 时是否可直接接受(automation 总开关/授权/人工锁都允许)。
|
|
* false 时产出 proposed:证据足够但需要确认,进待识别池。
|
|
*/
|
|
autoAccept: boolean;
|
|
}
|
|
|
|
const RESOLVE_STATUS: Record<DomainResolutionStatus, (o: ResolveStatusOptions) => ContractResolveStatus> = {
|
|
auto: (o) => (o.autoAccept ? "resolved" : "proposed"),
|
|
ambiguous: () => "ambiguous",
|
|
no_match: () => "unidentifiable",
|
|
};
|
|
|
|
export const DOMAIN_RESOLUTION_STATUSES = Object.keys(RESOLVE_STATUS) as DomainResolutionStatus[];
|
|
|
|
export function toContractResolveStatus(
|
|
status: DomainResolutionStatus,
|
|
opts: ResolveStatusOptions,
|
|
): ContractResolveStatus {
|
|
return RESOLVE_STATUS[status](opts);
|
|
}
|
|
|
|
const RELEASE_RESOLUTION: Record<ContractResolveStatus, ResolutionStatus> = {
|
|
resolved: "resolved",
|
|
// 待确认的提议与歧义一样进待识别池
|
|
proposed: "ambiguous",
|
|
ambiguous: "ambiguous",
|
|
unidentifiable: "unidentifiable",
|
|
};
|
|
|
|
/** 能力输出状态 → source_release.resolution_status */
|
|
export function toReleaseResolutionStatus(status: ContractResolveStatus): ResolutionStatus {
|
|
return RELEASE_RESOLUTION[status];
|
|
}
|
|
|
|
/**
|
|
* 排名候选 → external_identity 新建输入。本地候选(provider=local)不是外部身份,返回 null;
|
|
* 被否决的候选同样返回 null。
|
|
*/
|
|
export function rankedToIdentityCreate(
|
|
r: RankedCandidate,
|
|
target: { mediaEntityId: string } | { contentUnitId: string },
|
|
status: IdentityStatus = "proposed",
|
|
): ExternalIdentityCreate | null {
|
|
const level = toContractEvidenceLevel(r.level);
|
|
if (!level || r.candidate.provider === "local") return null;
|
|
return {
|
|
...target,
|
|
provider: r.candidate.provider,
|
|
entityType: r.candidate.entityType,
|
|
externalId: r.candidate.externalId,
|
|
status,
|
|
evidenceLevel: level,
|
|
evidence: {
|
|
titleMatch: r.titleMatch,
|
|
yearMatch: r.yearMatch,
|
|
typeMatch: r.typeMatch,
|
|
rules: r.evidence,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 本地作品 → domain 身份候选(本地优先召回)。externalId 用本地 UUID;
|
|
* 已接受的外部身份作为 linkedExternalIds,用于与远端候选判同。
|
|
* 媒体类型无法映射到 movie/tv(合集/其他)时返回 null。
|
|
*/
|
|
export function localIdentityCandidate(
|
|
entity: Pick<MediaEntity, "id" | "kind" | "title" | "originalTitle" | "year">,
|
|
aliases: readonly string[],
|
|
identities: readonly Pick<ExternalIdentity, "provider" | "externalId" | "status">[] = [],
|
|
): IdentityCandidate | null {
|
|
const entityType = toDomainMediaType(entity.kind);
|
|
if (!entityType) return null;
|
|
const titles = [entity.title, ...(entity.originalTitle ? [entity.originalTitle] : [])];
|
|
return {
|
|
provider: "local",
|
|
entityType,
|
|
externalId: entity.id,
|
|
titles,
|
|
...(entity.year !== null ? { year: entity.year } : {}),
|
|
aliases: [...aliases],
|
|
localEntityId: entity.id,
|
|
linkedExternalIds: identities
|
|
.filter((i) => i.status === "accepted")
|
|
.map((i) => ({ provider: i.provider, id: i.externalId })),
|
|
};
|
|
}
|