import 'dotenv/config'; import { loadConfig } from '../src/config/env.js'; import { createDatabasePool } from '../src/db/pool.js'; import { ParseRestClient } from '../src/db/parse-rest.client.js'; import { ensureListingParseSchemas } from '../src/db/parse-rest.schema.js'; import { ListingAiService } from '../src/modules/listing-ai/listing-ai.service.js'; import { ParseRestListingAiRepository } from '../src/modules/listing-ai/repositories/parse-rest-listing-ai.repository.js'; import { PostgresListingAiRepository } from '../src/modules/listing-ai/repositories/postgres-listing-ai.repository.js'; import { LISTING_RUBRIC_VERSION } from '../src/modules/listing-ai/scoring/rule-engine.js'; const args=new Map(process.argv.slice(2).map((arg)=>{const [key,...rest]=arg.split('=');return[key!,rest.join('=')||'true'];})); async function main(){ const config=loadConfig();const workspaceId=process.env.SAAS_DEFAULT_WORKSPACE_ID??'demashi';let close=async()=>{};let repository; if(config.storageDriver==='parse_rest'){const client=new ParseRestClient({serverUrl:config.parse.serverUrl,appId:config.parse.appId,masterKey:config.parse.masterKey,timeoutMs:config.parse.timeoutMs});await ensureListingParseSchemas(client);repository=new ParseRestListingAiRepository(client);}else{const pool=createDatabasePool(config);repository=new PostgresListingAiRepository(pool);close=async()=>pool.end();} const service=new ListingAiService(repository,undefined,()=>new Date(),5);const key=args.get('--idempotency-key')??process.env.LISTING_SCORE_IDEMPOTENCY_KEY??`${LISTING_RUBRIC_VERSION}-${Date.now()}`; try{ const requested=(args.get('--product-ids')??'').split(',').map((item)=>item.trim()).filter(Boolean);const limit=Math.max(1,Number(args.get('--limit')??Number.POSITIVE_INFINITY)); const all=await repository.listAllSources(workspaceId,'jd');const selected=all.filter((source)=>!requested.length||requested.includes(source.productId)).slice(0,limit); const expected=args.has('--expected-count')?Number(args.get('--expected-count')):null;if(expected!==null&&selected.length!==expected)throw new Error(`listing_score_expected_count_mismatch:${selected.length}:${expected}`); if(args.get('--apply')!=='true'){console.log(JSON.stringify({mode:'dry-run',workspaceId,rubricVersion:LISTING_RUBRIC_VERSION,available:all.length,selected:selected.length,productIds:selected.slice(0,30).map((source)=>source.productId),applyRequired:'rerun with --apply=true and --expected-count='},null,2));return;} const job=await service.enqueueScoreJob({workspaceId,platform:'jd',scope:{mode:'selected',productIds:selected.map((source)=>source.productId)},rubricVersion:LISTING_RUBRIC_VERSION,includeAiSuggestions:false,rescorePolicy:'force',idempotencyKey:key,requestedBy:'listing-score-script'});await service.processJob(workspaceId,job.id);let completed=await repository.getJob(workspaceId,job.id);while(completed&&['queued','running'].includes(completed.status)){await new Promise((resolve)=>setTimeout(resolve,1_000));completed=await repository.getJob(workspaceId,job.id);}console.log(JSON.stringify({mode:'completed',job:completed},null,2));if(!completed||!['completed','partial'].includes(completed.status))process.exitCode=2; }finally{await close();} } main().catch((error)=>{console.error(`[score-listings] ${error instanceof Error?error.message:error}`);process.exitCode=1;});