score-listings.ts 3.3 KB

12345678910111213141516171819202122232425
  1. import 'dotenv/config';
  2. import { loadConfig } from '../src/config/env.js';
  3. import { createDatabasePool } from '../src/db/pool.js';
  4. import { ParseRestClient } from '../src/db/parse-rest.client.js';
  5. import { ensureListingParseSchemas } from '../src/db/parse-rest.schema.js';
  6. import { ListingAiService } from '../src/modules/listing-ai/listing-ai.service.js';
  7. import { ParseRestListingAiRepository } from '../src/modules/listing-ai/repositories/parse-rest-listing-ai.repository.js';
  8. import { PostgresListingAiRepository } from '../src/modules/listing-ai/repositories/postgres-listing-ai.repository.js';
  9. import { LISTING_RUBRIC_VERSION } from '../src/modules/listing-ai/scoring/rule-engine.js';
  10. const args=new Map(process.argv.slice(2).map((arg)=>{const [key,...rest]=arg.split('=');return[key!,rest.join('=')||'true'];}));
  11. async function main(){
  12. const config=loadConfig();const workspaceId=process.env.SAAS_DEFAULT_WORKSPACE_ID??'demashi';let close=async()=>{};let repository;
  13. 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();}
  14. 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()}`;
  15. try{
  16. 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));
  17. const all=await repository.listAllSources(workspaceId,'jd');const selected=all.filter((source)=>!requested.length||requested.includes(source.productId)).slice(0,limit);
  18. 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}`);
  19. 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=<selected>'},null,2));return;}
  20. 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;
  21. }finally{await close();}
  22. }
  23. main().catch((error)=>{console.error(`[score-listings] ${error instanceof Error?error.message:error}`);process.exitCode=1;});