| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- import 'dotenv/config';
- import { mkdir, writeFile } from 'node:fs/promises';
- import { dirname, resolve } from 'node:path';
- import { loadConfig } from '../src/config/env.js';
- import { ParseRestClient } from '../src/db/parse-rest.client.js';
- import type { ListingCurrentScoreSlot, ListingScoreResult, ListingSourceSnapshot } from '../src/modules/listing-ai/domain.js';
- import { ParseRestListingAiRepository } from '../src/modules/listing-ai/repositories/parse-rest-listing-ai.repository.js';
- import {
- composeListingAiScore,
- LISTING_AI_CRITERIA,
- listingAiEvidenceCatalog,
- type ListingAiCriterionId,
- type ListingAiLevel,
- type ListingAiScoreOutput,
- } from '../src/modules/listing-ai/scoring/ai-rubric.js';
- import { canonicalHash, normalizeListingTitle, scoreListing } from '../src/modules/listing-ai/scoring/rule-engine.js';
- const EXPECTED = 625;
- const MODEL = 'codex-grounded-evaluator-v1';
- const REQUESTED_BY = 'codex-grounded-batch';
- const PROMOTION = /(今日|限时|秒杀|抢购|狂欢|开抢|钜惠|低至|到手|下单|晒图|仅需|咨询|客服|惊喜|豪礼|爆款|店长推荐|好评率|免费|免息|清单发客服)/u;
- const EXTREME = /(最好|最佳|最强|第一|顶级|国家级|百分百|100%|终身)/iu;
- const BENEFIT = /(省时|省心|耐用|高效|快速|便捷|安全|节能|防护|稳定|易清洗|免安装|大容量|适用|满足|提升|降低|减少|无忧|保温|预约|自动|静音)/u;
- const USE_CASE = /(学校|食堂|酒店|餐厅|饭店|商超|便利店|工厂|车间|医院|办公室|奶茶店|火锅店|后厨|家庭|家用|商用)/u;
- const UNIT = /\d+(?:\.\d+)?\s*(?:L|升|W|KW|kW|V|伏|kg|斤|cm|mm|米|m³\/min|Pa|℃|°C|盘|门|层|人|档|级|冰格)/giu;
- const BRACKET_PROMO = /[【\[].{0,24}?(?:免费|热销|爆款|好评|秒杀|低至|推荐|抢购|优惠|咨询|终身).{0,24}?[】\]]/gu;
- type Assessment = ListingAiScoreOutput['assessments'][number];
- type CriterionCounts = Record<ListingAiCriterionId, Record<ListingAiLevel, number>>;
- function normalized(value: string | null | undefined): string {
- return normalizeListingTitle(value ?? '').toLocaleLowerCase();
- }
- function occurrences(haystack: string, needle: string): number {
- if (!needle) return 0;
- let count = 0;
- let index = 0;
- while ((index = haystack.indexOf(needle, index)) >= 0) {
- count += 1;
- index += Math.max(1, needle.length);
- }
- return count;
- }
- function usefulValue(value: string): boolean {
- const item = normalized(value);
- return item.length >= 2
- && item.length <= 40
- && !/^(?:是|否|其他|其它|支持|不支持|有|无|1|0|标准|默认)$/u.test(item);
- }
- function evidenceIds(source: ListingSourceSnapshot): string[] {
- return listingAiEvidenceCatalog(source).map((item) => item.id);
- }
- function pickEvidence(source: ListingSourceSnapshot, prefixes: string[], fallback = 'title'): string[] {
- const ids = evidenceIds(source);
- const selected: string[] = [];
- for (const prefix of prefixes) {
- const id = ids.find((candidate) => candidate === prefix || candidate.startsWith(prefix));
- if (id && !selected.includes(id)) selected.push(id);
- if (selected.length === 3) break;
- }
- if (!selected.length) {
- const candidate = ids.find((id) => id === fallback) ?? ids[0];
- if (candidate) selected.push(candidate);
- }
- if (!selected.length) throw new Error(`listing_evidence_empty:${source.productId}`);
- return selected;
- }
- function assessment(
- criterionId: ListingAiCriterionId,
- level: ListingAiLevel,
- source: ListingSourceSnapshot,
- prefixes: string[],
- reason: string,
- confidence: number,
- ): Assessment {
- return { criterionId, level, evidenceIds: pickEvidence(source, prefixes), reason, confidence };
- }
- function categoryTerms(source: ListingSourceSnapshot): string[] {
- return [...new Set([
- ...(source.categoryContext?.coreTerms ?? []),
- ...(source.categoryContext?.aliases ?? []),
- source.categoryContext?.displayName ?? '',
- ...(source.categoryContext?.names ?? []),
- ].map(normalized).filter((value) => value.length >= 2 && value.length <= 40))];
- }
- function groundedValues(source: ListingSourceSnapshot): string[] {
- const values = [
- ...source.attributes.flatMap((item) => [item.name, ...item.values]),
- ...source.skus.slice(0, 30).flatMap((sku) => [sku.name ?? '', ...sku.attributes.flatMap((item) => [item.name, ...item.values]), ...(sku.saleAttributes ?? []).flatMap((item) => [item.name, ...item.values])]),
- ...source.features.filter((item) => !/^[01]$/.test(item.value.trim())).flatMap((item) => [item.key, item.value]),
- ];
- return [...new Set(values.map(normalized).filter(usefulValue))];
- }
- function supportedTitleValues(source: ListingSourceSnapshot): string[] {
- const title = normalized(source.title);
- return groundedValues(source).filter((value) => value.length >= 2 && title.includes(value)).slice(0, 20);
- }
- function numericClaims(value: string): string[] {
- return [...new Set(value.match(UNIT)?.map(normalized) ?? [])];
- }
- function duplicatePressure(title: string, terms: string[]): number {
- const normalizedTitle = normalized(title);
- const repeatedTerms = terms.filter((term) => occurrences(normalizedTitle, term) > 1).length;
- const synonymSaturation = Math.max(0, terms.filter((term) => normalizedTitle.includes(term)).length - 2);
- const repeatedNgrams = new Set<string>();
- const chars = Array.from(normalizedTitle.replace(/[\s()()【】\[\]\/|,,、::·—-]/gu, ''));
- for (const width of [2, 3, 4]) {
- const seen = new Set<string>();
- for (let index = 0; index + width <= chars.length; index += 1) {
- const token = chars.slice(index, index + width).join('');
- if (seen.has(token) && !/德玛仕|商用/u.test(token)) repeatedNgrams.add(token);
- seen.add(token);
- }
- }
- return repeatedTerms + synonymSaturation + Math.min(4, repeatedNgrams.size);
- }
- function textRisk(value: string): number {
- return Number(PROMOTION.test(value)) + Number(EXTREME.test(value)) + (value.match(BRACKET_PROMO)?.length ?? 0);
- }
- function levelByCount(count: number, thresholds: [number, number, number]): ListingAiLevel {
- return count >= thresholds[2] ? 'strong' : count >= thresholds[1] ? 'pass' : count >= thresholds[0] ? 'weak' : 'fail';
- }
- function judge(source: ListingSourceSnapshot): ListingAiScoreOutput {
- const title = source.title ?? '';
- const titleText = normalized(title);
- const titleLength = Array.from(titleText).length;
- const categories = categoryTerms(source);
- const categoryMatches = categories.filter((term) => titleText.includes(term));
- const earliestCategory = categoryMatches.length ? Math.min(...categoryMatches.map((term) => titleText.indexOf(term))) : -1;
- const duplicates = duplicatePressure(title, categories);
- const titlePromo = textRisk(title);
- const supported = supportedTitleValues(source);
- const titleNumbers = numericClaims(title);
- const attributeSignal = [...new Set([...supported, ...titleNumbers])];
- const marketing = source.marketing?.sellingPoints.map((item) => item.value.trim()).filter(Boolean) ?? [];
- const adword = source.marketing?.adword?.trim() ?? '';
- const marketingText = marketing.join(';');
- const marketingNumbers = numericClaims(marketingText);
- const marketingRisk = textRisk(marketingText);
- const grounded = groundedValues(source);
- const supportedMarketing = grounded.filter((value) => marketingText.toLocaleLowerCase().includes(value)).slice(0, 20);
- const distinctMarketing = [...new Set(marketing.map(normalized))];
- const informativeMarketing = distinctMarketing.filter((value) => value.length >= 6 && !PROMOTION.test(value));
- const hasBenefit = BENEFIT.test(marketingText);
- const hasUseCase = USE_CASE.test(marketingText);
- const directConflict = detectDirectConflict(source, `${title} ${marketingText}`);
- let searchLevel: ListingAiLevel;
- if (!titleText) searchLevel = 'fail';
- else if (categoryMatches.length && earliestCategory >= 0 && earliestCategory <= 20) searchLevel = 'strong';
- else if (categoryMatches.length) searchLevel = 'pass';
- else if (categories.length) searchLevel = 'weak';
- else searchLevel = /机|柜|炉|锅|器|台|车|箱|槽/u.test(titleText) ? 'weak' : 'fail';
- let hierarchyLevel: ListingAiLevel;
- if (!titleText) hierarchyLevel = 'fail';
- else if (titleLength >= 30 && titleLength <= 50 && duplicates === 0 && titlePromo === 0) hierarchyLevel = 'strong';
- else if (titleLength <= 65 && duplicates <= 2 && titlePromo <= 1) hierarchyLevel = 'pass';
- else if (titleLength <= 90 && duplicates <= 6) hierarchyLevel = 'weak';
- else hierarchyLevel = 'fail';
- const relevanceLevel = levelByCount(attributeSignal.length, [1, 2, 4]);
- const differentiatingSignals = [...new Set([
- ...attributeSignal,
- ...(USE_CASE.test(title) ? ['适用场景'] : []),
- ...(BENEFIT.test(title) ? ['价值表达'] : []),
- ])];
- const titleDifferentiationLevel = levelByCount(differentiatingSignals.length, [1, 2, 4]);
- let factualLevel: ListingAiLevel;
- if (directConflict.length) factualLevel = 'fail';
- else if (supported.length >= 3 && titlePromo === 0) factualLevel = 'strong';
- else if (supported.length >= 1 || titleNumbers.length) factualLevel = 'pass';
- else if (titleText) factualLevel = 'weak';
- else factualLevel = 'fail';
- let fabLevel: ListingAiLevel;
- if (!marketing.length) fabLevel = 'fail';
- else if (adword && hasBenefit && (hasUseCase || marketingNumbers.length >= 2)) fabLevel = 'strong';
- else if (adword && (hasBenefit || hasUseCase || marketingNumbers.length)) fabLevel = 'pass';
- else fabLevel = 'weak';
- let credibleLevel: ListingAiLevel;
- if (!marketing.length) credibleLevel = 'fail';
- else if (directConflict.length) credibleLevel = 'fail';
- else if (adword && marketingRisk === 0 && supportedMarketing.length >= 2 && marketingNumbers.length) credibleLevel = 'strong';
- else if (marketingRisk <= 1 && (supportedMarketing.length || marketingNumbers.length)) credibleLevel = 'pass';
- else if (informativeMarketing.length) credibleLevel = 'weak';
- else credibleLevel = 'fail';
- const sellingDifferentiationLevel = !marketing.length ? 'fail' : !adword ? 'weak' : levelByCount(informativeMarketing.length, [1, 2, 3]);
- let sellingConsistencyLevel: ListingAiLevel;
- if (!marketing.length) sellingConsistencyLevel = 'fail';
- else if (directConflict.length) sellingConsistencyLevel = 'fail';
- else if (supportedMarketing.length >= 2 && marketingRisk === 0) sellingConsistencyLevel = 'strong';
- else if (supportedMarketing.length || marketingNumbers.length || distinctMarketing.every((value) => titleText.includes(value.slice(0, Math.min(8, value.length))))) sellingConsistencyLevel = 'pass';
- else sellingConsistencyLevel = 'weak';
- const rows: Assessment[] = [
- assessment('title.search_intent', searchLevel, source, ['title', 'categoryContext.coreTerms'], categoryMatches.length ? `标题可识别品类表达“${categoryMatches.slice(0, 2).join('、')}”,最早位置为第${earliestCategory + 1}个字符。` : '标题中未找到当前类目上下文可验证的核心品类表达。', categories.length ? 0.9 : 0.68),
- assessment('title.information_hierarchy', hierarchyLevel, source, ['title'], `标题共${titleLength}个可见字符,检测到${duplicates}项重复压力和${titlePromo}项促销干扰。`, 0.86),
- assessment('title.attribute_relevance', relevanceLevel, source, ['title', 'attributes', 'skus'], attributeSignal.length ? `标题包含${attributeSignal.length}项可由属性或规格支持的决策信息:${attributeSignal.slice(0, 4).join('、')}。` : '标题未包含可由当前属性或规格直接支持的明确决策信息。', 0.82),
- assessment('title.differentiation', titleDifferentiationLevel, source, ['title', 'attributes', 'skus'], differentiatingSignals.length ? `标题识别到${differentiatingSignals.length}项具体属性、场景或价值信号。` : '标题主要停留在品牌和品类层面,缺少可验证的具体差异信息。', 0.76),
- assessment('title.factual_consistency', factualLevel, source, ['title', 'attributes', 'skus'], directConflict.length ? `发现跨字段直接冲突:${directConflict.slice(0, 2).join(';')}。` : `未发现直接矛盾;标题有${supported.length}项属性值获得交叉支持。`, directConflict.length ? 0.94 : 0.78),
- assessment('selling_points.fab_benefit', fabLevel, source, ['marketing.sellingPoints', 'attributes', 'title'], !marketing.length ? '当前没有可评分的商品广告语或有效规格短标题。' : `共${marketing.length}条营销文本;产品级广告语${adword ? '存在' : '缺失'},用户收益和场景表达${hasBenefit || hasUseCase ? '可识别' : '不足'}。`, adword ? 0.86 : 0.92),
- assessment('selling_points.specific_credible', credibleLevel, source, ['marketing.sellingPoints', 'attributes', 'skus'], !marketing.length ? '当前没有营销文本可验证具体性与可信度。' : `识别到${marketingNumbers.length}项量化信息、${supportedMarketing.length}项属性支持和${marketingRisk}项促销或绝对化风险。`, 0.84),
- assessment('selling_points.differentiation', sellingDifferentiationLevel, source, ['marketing.sellingPoints', 'attributes'], !marketing.length ? '当前没有营销文本可形成差异化表达。' : `共${distinctMarketing.length}条去重文本,其中${informativeMarketing.length}条包含非纯促销的实质信息。`, 0.78),
- assessment('selling_points.consistency', sellingConsistencyLevel, source, ['marketing.sellingPoints', 'title', 'attributes'], directConflict.length ? `营销表达与商品事实存在冲突:${directConflict.slice(0, 2).join(';')}。` : `未发现直接矛盾,${supportedMarketing.length}项营销信息可由属性或规格交叉支持。`, directConflict.length ? 0.94 : 0.76),
- ];
- const suggestions = suggestionsFor(source, rows, { titleLength, duplicates, titlePromo, adword, marketingRisk, directConflict });
- return { assessments: rows, summary: `Codex依据当前商品标题、类目、广告语、有效规格与属性完成固定九项语义判定;不使用评论、竞品或图片视觉内容。`, suggestions };
- }
- function detectDirectConflict(source: ListingSourceSnapshot, copy: string): string[] {
- const text = normalized(copy);
- const conflicts: string[] = [];
- const allAttributes = [
- ...source.attributes,
- ...source.skus.flatMap((sku) => [...sku.attributes, ...(sku.saleAttributes ?? [])]),
- ];
- const voltageValues = allAttributes.filter((item) => /电压/u.test(item.name)).flatMap((item) => item.values).map(normalized);
- const voltageClaims = [...text.matchAll(/(?:^|\D)(220|380)\s*v?(?:\D|$)/giu)].map((match) => match[1]);
- if (voltageValues.length && voltageClaims.length && voltageClaims.some((claim) => !voltageValues.some((value) => value.includes(claim!)))) conflicts.push(`电压宣称${[...new Set(voltageClaims)].join('/')}与属性${voltageValues.join('/')}不一致`);
- const stars = allAttributes.filter((item) => /消毒星级/u.test(item.name)).flatMap((item) => item.values).map(normalized);
- if (stars.length && /二星/u.test(text) && !stars.some((value) => /二星/u.test(value))) conflicts.push(`标题或卖点宣称二星级,但属性为${stars.join('/')}`);
- if (stars.length && /一星/u.test(text) && !stars.some((value) => /一星/u.test(value))) conflicts.push(`标题或卖点宣称一星级,但属性为${stars.join('/')}`);
- const powerValues = allAttributes.filter((item) => /功率/u.test(item.name)).flatMap((item) => item.values).map(normalized);
- const powerClaims = powerWatts(text);
- if (powerValues.length && powerClaims.length && powerClaims.some((claim) => !powerValues.some((value) => powerValueSupports(value, claim)))) {
- conflicts.push(`功率宣称${[...new Set(powerClaims)].map((value) => `${value}W`).join('/')}与属性${powerValues.join('/')}不一致`);
- }
- return conflicts;
- }
- function powerWatts(value: string): number[] {
- return [...value.matchAll(/(\d+(?:\.\d+)?)\s*(kw|w|瓦)/giu)]
- .map((match) => Math.round(Number(match[1]) * (match[2]?.toLocaleLowerCase() === 'kw' ? 1_000 : 1)))
- .filter((item) => item >= 100 && item <= 10_000_000);
- }
- function powerValueSupports(value: string, claim: number): boolean {
- const watts = powerWatts(value);
- if (!watts.length) return false;
- if (/以上|及以上|≥/u.test(value)) return claim >= Math.min(...watts);
- if (/以下|及以下|≤/u.test(value)) return claim <= Math.max(...watts);
- if (/[-–—~至]/u.test(value) && watts.length >= 2) return claim >= Math.min(...watts) && claim <= Math.max(...watts);
- return watts.some((candidate) => Math.abs(candidate - claim) <= Math.max(10, candidate * 0.02));
- }
- function suggestionsFor(
- source: ListingSourceSnapshot,
- rows: Assessment[],
- signals: { titleLength: number; duplicates: number; titlePromo: number; adword: string; marketingRisk: number; directConflict: string[] },
- ): string[] {
- const output: string[] = [];
- if (signals.titleLength > 60 || signals.duplicates >= 3) output.push('精简标题中的同义品类词和重复词根,保留品牌、核心品类、关键规格与主要场景。');
- if (signals.titlePromo) output.push('从标题中移除限时、免费、爆款、好评率等促销噪声,避免干扰信息层级。');
- if (!signals.adword) output.push('补充产品级商品广告语,用具体特性说明用户收益,不要只依赖规格短标题。');
- if (signals.marketingRisk) output.push('删除卖点中的时效促销、客服引导和绝对化表达,改用可由商品属性验证的事实。');
- if (signals.directConflict.length) output.push(`修正跨字段冲突:${signals.directConflict.join(';')}。`);
- if (rows.some((row) => row.criterionId === 'title.attribute_relevance' && ['fail', 'weak'].includes(row.level))) output.push('在标题中增加一至两个可由属性或规格支持的关键决策信息。');
- if (rows.some((row) => row.criterionId === 'selling_points.fab_benefit' && ['fail', 'weak'].includes(row.level))) output.push('按“具体特性—使用优势—用户收益”重写核心卖点,并绑定当前商品事实。');
- return [...new Set(output)].slice(0, 8);
- }
- function buildScore(source: ListingSourceSnapshot, now: string): ListingScoreResult {
- const baseline = scoreListing(source, { now });
- const output = judge(source);
- const result = composeListingAiScore({ source, baseline, output, model: MODEL, now });
- return {
- ...result,
- inputFingerprint: canonicalHash({ sourceHash: source.sourceHash, model: MODEL, rubricVersion: result.rubricVersion, promptVersion: result.promptVersion }),
- executionKey: `codex-grounded|${source.productId}|${source.sourceHash}`,
- requestedBy: REQUESTED_BY,
- rescorePolicy: 'force',
- };
- }
- function percentile(values: number[], ratio: number): number | null {
- if (!values.length) return null;
- const sorted = [...values].sort((left, right) => left - right);
- return sorted[Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * ratio))]!;
- }
- function report(results: ListingScoreResult[], sources: ListingSourceSnapshot[]) {
- const sourceByProduct = new Map(sources.map((source) => [source.productId, source]));
- const scores = results.map((item) => item.overallScore).filter((value): value is number => value !== null);
- const criteria = Object.fromEntries(LISTING_AI_CRITERIA.map((criterion) => [criterion.id, { fail: 0, weak: 0, pass: 0, strong: 0 }])) as CriterionCounts;
- for (const result of results) {
- for (const row of result.dimensions.flatMap((dimension) => dimension.evidence).filter((item) => item.source === 'ai' && item.ruleId.startsWith('ai.'))) {
- const id = row.ruleId.slice(3) as ListingAiCriterionId;
- if (criteria[id] && row.level && row.level !== 'unknown') criteria[id][row.level] += 1;
- }
- }
- const distribution = scores.reduce<Record<string, number>>((output, score) => {
- const key = score < 60 ? '<60' : score < 70 ? '60-69.5' : score < 80 ? '70-79.5' : score < 90 ? '80-89.5' : '90-100';
- output[key] = (output[key] ?? 0) + 1;
- return output;
- }, {});
- return {
- model: MODEL,
- products: results.length,
- scored: scores.length,
- minimum: scores.length ? Math.min(...scores) : null,
- p25: percentile(scores, 0.25),
- median: percentile(scores, 0.5),
- p75: percentile(scores, 0.75),
- maximum: scores.length ? Math.max(...scores) : null,
- average: scores.length ? Math.round(scores.reduce((sum, value) => sum + value, 0) / scores.length * 10) / 10 : null,
- distribution,
- criteria,
- lowest: [...results].sort((left, right) => (left.overallScore ?? 101) - (right.overallScore ?? 101)).slice(0, 10).map((item) => ({ productId: item.productId, title: sourceByProduct.get(item.productId)?.title, score: item.overallScore, dimensions: Object.fromEntries(item.dimensions.map((dimension) => [dimension.dimension, dimension.score])) })),
- highest: [...results].sort((left, right) => (right.overallScore ?? -1) - (left.overallScore ?? -1)).slice(0, 10).map((item) => ({ productId: item.productId, title: sourceByProduct.get(item.productId)?.title, score: item.overallScore, dimensions: Object.fromEntries(item.dimensions.map((dimension) => [dimension.dimension, dimension.score])) })),
- };
- }
- async function concurrent<T>(items: T[], worker: (item: T) => Promise<void>, concurrency = 5): Promise<void> {
- let cursor = 0;
- await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, async () => {
- while (cursor < items.length) await worker(items[cursor++]!);
- }));
- }
- async function main() {
- const args = new Map(process.argv.slice(2).map((arg) => { const [key, ...rest] = arg.split('='); return [key!, rest.join('=') || 'true']; }));
- const apply = args.get('--apply') === 'true';
- const expected = Number(args.get('--expected-count') ?? EXPECTED);
- if (expected !== EXPECTED) throw new Error(`expected_count_must_be_${EXPECTED}`);
- const config = loadConfig();
- if (config.storageDriver !== 'parse_rest') throw new Error('codex_scoring_requires_parse_rest');
- const client = new ParseRestClient({ serverUrl: config.parse.serverUrl, appId: config.parse.appId, masterKey: config.parse.masterKey, timeoutMs: config.parse.timeoutMs });
- const repository = new ParseRestListingAiRepository(client);
- const sources = await repository.listAllSources(config.auth.defaultWorkspaceId, 'jd');
- if (sources.length !== expected) throw new Error(`source_count_mismatch:${sources.length}:${expected}`);
- const unavailable = sources.filter((source) => source.detailStatus !== 'available');
- if (unavailable.length) throw new Error(`source_detail_unavailable:${unavailable.length}`);
- const now = new Date().toISOString();
- const baselines = sources.map((source) => scoreListing(source, { now }));
- const results = sources.map((source) => buildScore(source, now));
- const invalid = results.filter((result) => result.overallScore === null || result.knownOverallMaxScore !== 100 || result.dimensions.length !== 5 || result.dimensions.some((dimension) => dimension.score === null));
- if (invalid.length) throw new Error(`invalid_results:${invalid.length}`);
- const summary = report(results, sources);
- if (!apply) {
- console.log(JSON.stringify({ mode: 'dry-run', ...summary, applyRequired: '--apply=true --expected-count=625' }, null, 2));
- return;
- }
- const existing = await repository.listCurrentScores(config.auth.defaultWorkspaceId);
- const formal = existing.filter((score) => score.scoreKind === 'hybrid_ai');
- const rules = existing.filter((score) => score.scoreKind !== 'hybrid_ai');
- const backupPath = resolve(args.get('--backup') ?? `logs/listing-current-scores-backup-${now.replace(/[:.]/g, '-')}.json`);
- await mkdir(dirname(backupPath), { recursive: true });
- await writeFile(backupPath, JSON.stringify({ workspaceId: config.auth.defaultWorkspaceId, slots: ['rule_precheck', 'formal_ai'] satisfies ListingCurrentScoreSlot[], createdAt: now, scores: existing }, null, 2), 'utf8');
- await concurrent(baselines, async (result) => { await repository.upsertCurrentScore(result); });
- await concurrent(results, async (result) => { await repository.upsertCurrentScore(result); });
- const current = await repository.listCurrentScores(config.auth.defaultWorkspaceId);
- const verified = current.filter((score) => score.scoreKind === 'hybrid_ai');
- const verifiedByProduct = new Map(verified.map((score) => [score.productId, score]));
- const mismatches = sources.filter((source) => {
- const score = verifiedByProduct.get(source.productId);
- return !score || score.model !== MODEL || score.sourceHash !== source.sourceHash || score.overallScore === null;
- });
- if (mismatches.length) throw new Error(`write_verification_failed:${mismatches.length}`);
- console.log(JSON.stringify({ mode: 'applied', backupPath, replacedRuleScores: rules.length, replacedFormalScores: formal.length, verified: verified.length, ...summary }, null, 2));
- }
- main().catch((error) => {
- console.error(`[score-listings-codex] ${error instanceof Error ? error.stack ?? error.message : error}`);
- process.exitCode = 1;
- });
|