import 'dotenv/config'; import { z } from 'zod'; import { ParseRestClient } from '../src/db/parse-rest.client.js'; import { VOC_PARSE_CLASSES } from '../src/db/parse-rest.schema.js'; import type { ListingScoreResult, ListingSourceSnapshot } from '../src/modules/listing-ai/domain.js'; import { ParseRestListingAiRepository } from '../src/modules/listing-ai/repositories/parse-rest-listing-ai.repository.js'; const COHORT = 'listing-jd-v3-formal-625'; interface Stored { objectId: string; productId?: string; isCurrent?: boolean; payload: T } async function main() { const env = z.object({ PARSE_SERVER_URL: z.url(), PARSE_APP_ID: z.string().min(1), PARSE_MASTER_KEY: z.string().min(1), SAAS_DEFAULT_WORKSPACE_ID: z.string().default('demashi') }).parse(process.env); const apply = process.argv.includes('--apply=true'); const expected = Number(process.argv.find((value) => value.startsWith('--expected-count='))?.split('=')[1] ?? 625); const client = new ParseRestClient({ serverUrl: env.PARSE_SERVER_URL, appId: env.PARSE_APP_ID, masterKey: env.PARSE_MASTER_KEY }); const workspaceId = env.SAAS_DEFAULT_WORKSPACE_ID; const [scores, sources] = await Promise.all([ client.findAll>(VOC_PARSE_CLASSES.listingCurrentScore, { workspaceId }), client.findAll>(VOC_PARSE_CLASSES.listingSourceSnapshot, { workspaceId, platform: 'jd', isCurrent: true }), ]); const included = new Set(scores.map((row) => row.payload).filter((score) => typeof score.overallScore === 'number' && Number.isFinite(score.overallScore)).map((score) => score.productId)); if (included.size !== expected) throw new Error(`cohort_count_mismatch expected=${expected} actual=${included.size}`); const report = { mode: apply ? 'apply' : 'dry-run', workspaceId, cohort: COHORT, currentSources: sources.length, included: included.size, excluded: sources.filter((row) => !included.has(row.payload.productId)).length }; console.log(JSON.stringify(report, null, 2)); if (!apply) return; const requests = sources.map((row) => ({ method: 'PUT' as const, path: `/classes/${VOC_PARSE_CLASSES.listingSourceSnapshot}/${row.objectId}`, body: { catalogIncluded: included.has(row.payload.productId), catalogCohort: COHORT } })); for (let index = 0; index < requests.length; index += 50) await client.batch(requests.slice(index, index + 50)); const active = await client.count(VOC_PARSE_CLASSES.listingSourceSnapshot, { workspaceId, platform: 'jd', isCurrent: true, catalogIncluded: true, catalogCohort: COHORT }); if (active !== expected) throw new Error(`cohort_apply_verification_failed expected=${expected} actual=${active}`); const repository = new ParseRestListingAiRepository(client); const excludedProductId = sources.find((row) => !included.has(row.payload.productId))?.payload.productId ?? null; const includedProductId = visibleProductId(included); const [visibleSources, summary, firstPage] = await Promise.all([ repository.listAllSources(workspaceId, 'jd'), repository.catalogSummary(workspaceId, 'jd'), repository.listProducts({ workspaceId, platform: 'jd', cursor: null, limit: 25, sort: 'productId' }), ]); const [includedSource, excludedSource] = await Promise.all([ includedProductId ? repository.getSource(workspaceId, 'jd', includedProductId) : null, excludedProductId ? repository.getSource(workspaceId, 'jd', excludedProductId) : null, ]); const firstPageOutsideCohort = firstPage.items.filter((item) => !included.has(item.productId)).length; if (visibleSources.length !== expected || summary.sourceTotal !== expected || firstPage.items.length !== 25 || firstPageOutsideCohort > 0 || !includedSource || excludedSource) { throw new Error(`cohort_repository_verification_failed sources=${visibleSources.length} summary=${summary.sourceTotal} pageItems=${firstPage.items.length} outside=${firstPageOutsideCohort} includedVisible=${Boolean(includedSource)} excludedVisible=${Boolean(excludedSource)}`); } console.log(JSON.stringify({ status: 'completed', active, repositoryVisible: visibleSources.length, catalogTotal: summary.sourceTotal, firstPageItems: firstPage.items.length, firstPageOutsideCohort, includedProductVisible: Boolean(includedSource), excludedProductVisible: Boolean(excludedSource) }, null, 2)); } function visibleProductId(productIds: Set): string | null { return productIds.values().next().value ?? null; } main().catch((error) => { console.error(`[set-listing-catalog-cohort] ${error instanceof Error ? error.message : error}`); process.exitCode = 1; });