| 12345678910111213141516171819202122232425262728293031323334353637383940 |
- 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 { FmodeAiClient } from '../src/modules/ai-gateway/client.js';
- import { FmodeListingAiScoringProvider, 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';
- 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 = args.get('--workspace') ?? config.auth.defaultWorkspaceId;
- const limit = Math.max(1, Math.min(10, Number(args.get('--limit') ?? 1)));
- 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 requestedIds = (args.get('--product-ids') ?? '').split(',').map((item) => item.trim()).filter(Boolean);
- const sources = (await repository.listAllSources(workspaceId, 'jd')).filter((source) => requestedIds.length ? requestedIds.includes(source.productId) : source.detailStatus === 'available' && Boolean(source.marketing?.sellingPoints.length) && Boolean(source.categoryContext?.ruleVersion) && Boolean(source.vocEvidence?.length) && Boolean(source.descriptionStructure?.observed)).slice(0, limit);
- if (!sources.length) throw new Error('listing_ai_no_ready_sources');
- const provider = new FmodeListingAiScoringProvider(new FmodeAiClient(config.ai), config.listingAi.model);
- const service = new ListingAiService(repository, provider, () => new Date(), config.listingAi.concurrency, config.listingAi.maxAiItemsPerJob);
- const key = args.get('--idempotency-key') ?? `listing-jd-ai-v5-${Date.now()}`;
- try {
- const job = await service.enqueueScoreJob({ workspaceId, platform: 'jd', scope: { mode: 'selected', productIds: sources.map((source) => source.productId) }, includeAiSuggestions: true, rescorePolicy: args.get('--force') === 'true' ? 'force' : 'reuse', idempotencyKey: key, requestedBy: 'listing-ai-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); }
- const results = await Promise.all(sources.map((source) => repository.getCurrentScore(workspaceId, source.productId, 'formal_ai')));
- console.log(JSON.stringify({ job: completed, results: results.map((result) => result ? { productId: result.productId, overallScore: result.overallScore, aiStatus: result.aiStatus, model: result.model } : null) }, null, 2));
- if (!completed || !['completed', 'partial'].includes(completed.status)) process.exitCode = 2;
- } finally { await close(); }
- }
- main().catch((error) => { console.error(`[score-listings-ai] ${error instanceof Error ? error.message : error}`); process.exitCode = 1; });
|