/** * 图片主体识别结果(由后端 jd/subject/classify 返回后规范化) */ export type ImageKind = 'cartoon' | 'real_photo' | 'other' | 'uncertain' export interface ImageInspect { imageType: ImageKind isCartoon: boolean personCount: number petCount: number /** 道具数量(若接口有返回) */ propCount: number reviewStatus: string conclusion: string /** 主体识别 check_id,后续下单可用 */ checkId: number } export class InspectCancelled extends Error { constructor() { super('cancelled') this.name = 'InspectCancelled' } } function asRecord(value: unknown) { return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record) : null } export function mapInspectImageType(raw: unknown): ImageKind { const text = String(raw ?? '') .trim() .toLowerCase() if (text === 'cartoon' || text === '1') return 'cartoon' if (text === 'real_photo' || text === 'real' || text === '2') return 'real_photo' if (text === 'other') return 'other' return 'uncertain' } function countField(data: Record, keys: string[]) { for (const key of keys) { if (!(key in data)) continue const value = Number(data[key]) if (Number.isFinite(value)) return Math.max(0, Math.trunc(value)) } return 0 } function hasOwnCount(data: Record, keys: string[]) { return keys.some((key) => key in data && Number.isFinite(Number(data[key]))) } type KeywordBucket = 'person' | 'pet' | 'prop' | 'other' /** 按 category / 中英文关键词区分人物、宠物、道具 */ function classifyKeywordBucket(item: Record): KeywordBucket { const category = Number(item.category ?? item.Category ?? 0) if (category === 1) return 'person' if (category === 2) return 'pet' if (category === 3) return 'prop' const text = `${item.cn || ''} ${item.en || ''} ${item.name || ''} ${item.label || ''}`.toLowerCase() if (/人|人物|人脸|儿童|成人|person|people|human|face|child|man|woman|boy|girl/.test(text)) { return 'person' } if ( /宠|宠物|猫|狗|兔|鸟|鼠|仓鼠|龙猫|鹦鹉|pet|cat|dog|rabbit|bird|hamster|animal/.test( text, ) ) { return 'pet' } if (/道|具|prop|道具|物件|object/.test(text)) return 'prop' return 'other' } function countFromKeywords(raw: unknown): { personCount: number petCount: number propCount: number hasKeywords: boolean } { if (!Array.isArray(raw) || raw.length === 0) { return { personCount: 0, petCount: 0, propCount: 0, hasKeywords: false } } let personCount = 0 let petCount = 0 let propCount = 0 let useful = false raw.forEach((entry) => { const item = asRecord(entry) if (!item) return const n = Number(item.count ?? item.num ?? 1) const count = Number.isFinite(n) ? Math.max(0, Math.trunc(n)) : 1 if (count <= 0) return const bucket = classifyKeywordBucket(item) if (bucket === 'other') return useful = true if (bucket === 'person') personCount += count else if (bucket === 'pet') petCount += count else if (bucket === 'prop') propCount += count }) return { personCount, petCount, propCount, hasKeywords: useful } } /** 规范化 classify 接口 data(拦截器已解包 code=1000 的 data) */ export function normalizeInspect(body: unknown): ImageInspect { const root = asRecord(body) || {} const nested = asRecord(root.data) // 兼容完整响应 { code, data } / 已解包的 data 本体 const data = nested && (nested.image_type != null || nested.person_count != null || nested.is_cartoon != null || nested.check_id != null || nested.keywords != null) ? nested : root const imageType = mapInspectImageType(data.image_type ?? data.imageType) const isCartoonRaw = data.is_cartoon ?? data.isCartoon const isCartoon = typeof isCartoonRaw === 'boolean' ? isCartoonRaw : imageType === 'cartoon' const fieldPerson = countField(data, [ 'person_count', 'personCount', 'headcount', 'head_count', ]) const fieldPet = countField(data, ['pet_count', 'petCount']) const fieldProp = countField(data, ['prop_count', 'propCount']) const hasFieldPerson = hasOwnCount(data, [ 'person_count', 'personCount', 'headcount', 'head_count', ]) const hasFieldPet = hasOwnCount(data, ['pet_count', 'petCount']) const hasFieldProp = hasOwnCount(data, ['prop_count', 'propCount']) const fromKeywords = countFromKeywords(data.keywords) /** * 严格按类别取数: * - keywords 能分出人物/宠物时优先用(避免把宠物误记成人物) * - 否则回退 person_count / pet_count */ let personCount = fieldPerson let petCount = fieldPet let propCount = fieldProp if (fromKeywords.hasKeywords) { personCount = fromKeywords.personCount petCount = fromKeywords.petCount propCount = fromKeywords.propCount } else { if (!hasFieldPerson) personCount = 0 if (!hasFieldPet) petCount = 0 if (!hasFieldProp) propCount = 0 } return { imageType, isCartoon, personCount, petCount, propCount, reviewStatus: String(data.review_status || ''), conclusion: String(data.conclusion || ''), checkId: countField(data, ['check_id', 'checkId']), } } export function isCartoonImage(result: ImageInspect) { return result.isCartoon || result.imageType === 'cartoon' } export function figureTypeOf(result: ImageInspect) { return result.imageType === 'real_photo' ? 1 : 0 } export function formatSubjectFromInspect(result: ImageInspect) { const parts: string[] = [] if (result.personCount > 0) parts.push(`${result.personCount}人`) if (result.petCount > 0) parts.push(`${result.petCount}宠物`) if (result.propCount > 0) parts.push(`${result.propCount}道具`) return parts.join('+') }