Files
UHD_Media_Tool/packages/mapping/src/release.ts
T
rootandClaude Opus 5.5 42ecc93fa5 feat(mapping): centralize domain <-> contracts mappings (B5)
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]>
2026-09-27 19:20:30 +08:00

73 lines
2.9 KiB
TypeScript

import type { ContentType as ContractContentType, Segment as ContractSegment, JsonObject } from "@umt/contracts";
import {
type ContentType as DomainContentType,
type Segment as DomainSegment,
PARSER_VERSION,
type ParsedRelease,
} from "@umt/domain";
// 标题解析结果 ↔ source_release.parsed / parser_version 列;综艺内容类型与分段 ↔ content_unit 列。
/** ParsedRelease → source_release 的 parsed(jsonb)与 parser_version(text) */
export function toParsedColumns(p: ParsedRelease): { parsed: JsonObject; parserVersion: string } {
return { parsed: JSON.parse(JSON.stringify(p)) as JsonObject, parserVersion: String(p.parserVersion) };
}
const REQUIRED_ARRAYS = ["audio", "hdr", "variantTags", "explicitIds", "evidence"] as const;
/**
* source_release 列 → ParsedRelease。版本不是当前 PARSER_VERSION 或形状不对时返回 null,
* 调用方应按原标题重新解析(规则变化后旧结果作废)。
*/
export function fromParsedColumns(cols: { parsed: JsonObject; parserVersion: string | null }): ParsedRelease | null {
if (cols.parserVersion !== String(PARSER_VERSION)) return null;
const p = cols.parsed;
if (p.parserVersion !== PARSER_VERSION || typeof p.raw !== "string" || typeof p.isEncode !== "boolean") return null;
if (typeof p.contentType !== "string" || typeof p.confidence !== "string") return null;
if (REQUIRED_ARRAYS.some((k) => !Array.isArray(p[k]))) return null;
return p as unknown as ParsedRelease;
}
const CONTENT_TYPE: Record<DomainContentType, ContractContentType> = {
unknown: "unknown",
main: "main",
extra: "extra",
behind_the_scenes: "behind_the_scenes",
performance_only: "performance_only",
preview: "preview",
reaction: "reaction",
};
const CONTENT_TYPE_BACK: Record<ContractContentType, DomainContentType> = {
main: "main",
extra: "extra",
behind_the_scenes: "behind_the_scenes",
performance_only: "performance_only",
preview: "preview",
reaction: "reaction",
unknown: "unknown",
};
export const DOMAIN_CONTENT_TYPES = Object.keys(CONTENT_TYPE) as DomainContentType[];
export const toContractContentType = (t: DomainContentType): ContractContentType => CONTENT_TYPE[t];
export const toDomainContentType = (t: ContractContentType): DomainContentType => CONTENT_TYPE_BACK[t];
const SEGMENT: Record<DomainSegment, ContractSegment> = {
full: "full",
upper: "upper",
middle: "middle",
lower: "lower",
};
const SEGMENT_BACK: Record<ContractSegment, DomainSegment | undefined> = {
full: "full",
upper: "upper",
middle: "middle",
lower: "lower",
// domain 用"字段缺失"表示未知
unknown: undefined,
};
export const DOMAIN_SEGMENTS = Object.keys(SEGMENT) as DomainSegment[];
/** 解析结果没有分段信息(undefined)时落为 unknown */
export const toContractSegment = (s: DomainSegment | undefined): ContractSegment => (s ? SEGMENT[s] : "unknown");
export const toDomainSegment = (s: ContractSegment): DomainSegment | undefined => SEGMENT_BACK[s];