@bytebrand/export-cars 1.26.40 → 1.26.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/configs/autouncle.config.js +22 -0
  2. package/dist/configs/autouncle.config.js.map +1 -1
  3. package/dist/exporters/autouncle.exporter.js +10 -0
  4. package/dist/exporters/autouncle.exporter.js.map +1 -1
  5. package/dist/services/autouncle-feed-run.d.ts +6 -0
  6. package/dist/services/autouncle-feed-run.js +11 -1
  7. package/dist/services/autouncle-feed-run.js.map +1 -1
  8. package/dist/transformers/autouncle.transformer.d.ts +4 -0
  9. package/dist/transformers/autouncle.transformer.js +10 -5
  10. package/dist/transformers/autouncle.transformer.js.map +1 -1
  11. package/dist/transformers/google-feed-envkv.d.ts +2 -0
  12. package/dist/transformers/google-feed-envkv.js +9 -1
  13. package/dist/transformers/google-feed-envkv.js.map +1 -1
  14. package/dist/tsconfig.build.tsbuildinfo +1 -1
  15. package/docs/autouncle/AUDIT.md +103 -97
  16. package/docs/autouncle/PARTNER-QUESTIONS.md +3 -1
  17. package/docs/autouncle/RUNBOOK.md +2 -0
  18. package/docs/criteo/RUNBOOK.md +86 -86
  19. package/package.json +1 -1
  20. package/src/configs/autouncle.config.ts +26 -1
  21. package/src/configs/criteo.config.ts +54 -54
  22. package/src/exporters/autouncle.exporter.ts +12 -0
  23. package/src/exporters/criteo.exporter.ts +63 -63
  24. package/src/services/autouncle-feed-run.ts +19 -2
  25. package/src/services/criteo-feed-run.ts +253 -253
  26. package/src/transformers/autouncle.transformer.ts +20 -4
  27. package/src/transformers/google-feed-envkv.ts +10 -0
  28. package/test/autouncle-feed-run.test.ts +25 -2
  29. package/test/autouncle-mapping.test.ts +41 -2
  30. package/test/criteo-feed-run.test.ts +365 -365
  31. package/test/criteo-mapping.test.ts +122 -122
  32. package/test/fixtures/criteo-20-cases.json +581 -581
@@ -1,63 +1,63 @@
1
- import { Injectable } from '@nestjs/common';
2
- import { ModuleRef } from '@nestjs/core';
3
-
4
- import { FsChunkInterface } from '@bytebrand/chunkable';
5
- import { LoggerService } from '@bytebrand/cronicle-helper';
6
-
7
- import { CarService, ConfigService, LoaderService, RulesService } from '../services';
8
- import { FeedGuard } from '../services/feed-guard';
9
- import { CriteoFeedRun, CriteoSourceCar } from '../services/criteo-feed-run';
10
- import { Exporter } from '../interfaces/exporter.interface';
11
- import { CriteoConfig } from '../configs/criteo.config';
12
- import { CriteoTransformer } from 'src/transformers/criteo.transformer';
13
-
14
- /**
15
- * ExportType.CRITEO (ADV-319). Wires the Cronicle/Nest services into CriteoFeedRun, which holds the
16
- * logic. The output directory is no longer emptied: the new XML replaces the old one only after the
17
- * fail-closed guard passed.
18
- */
19
- @Injectable()
20
- export class CriteoExporter implements Exporter {
21
- constructor(
22
- private carService: CarService,
23
- private configService: ConfigService,
24
- private rulesService: RulesService,
25
- private loaderService: LoaderService,
26
- private moduleRef: ModuleRef,
27
- ) { }
28
-
29
- async export(): Promise<void> {
30
- const config = this.configService.config as CriteoConfig;
31
- const [xmlFile] = config.outputFiles;
32
- const transformer = await this.moduleRef.create(CriteoTransformer);
33
-
34
- const run = new CriteoFeedRun(
35
- {
36
- forEachChunk: fn => this.loaderService.process((chunk: FsChunkInterface) => fn(chunk.data as CriteoSourceCar[])),
37
- filterCar: source => this.carService.filterCar(source, this.rulesService.rules),
38
- transform: source => transformer.transform(source),
39
- guard: new FeedGuard(config.feedGuard),
40
- },
41
- {
42
- runId: config.runId,
43
- domain: config.domain,
44
- sourceDirectory: config.pathesConfig.localDirectory,
45
- sourceBaseFileName: config.pathesConfig.baseFileName,
46
- xmlFile,
47
- stateDir: config.stateDir,
48
- },
49
- );
50
-
51
- try {
52
- const report = await run.run();
53
- if (report.stale) {
54
- console.log(`criteo feed STALE: last good file ${report.lastGoodAgeHours?.toFixed(1)} h old`);
55
- }
56
- } catch (error) {
57
- // main.ts would report a rejected run as {"complete":1,"code":0}; fail the job so the chained
58
- // upload step does not run (report + lock already handled by the run).
59
- LoggerService.exitError(`criteo feed not published: ${error?.message ?? error}`);
60
- process.exit(1);
61
- }
62
- }
63
- }
1
+ import { Injectable } from '@nestjs/common';
2
+ import { ModuleRef } from '@nestjs/core';
3
+
4
+ import { FsChunkInterface } from '@bytebrand/chunkable';
5
+ import { LoggerService } from '@bytebrand/cronicle-helper';
6
+
7
+ import { CarService, ConfigService, LoaderService, RulesService } from '../services';
8
+ import { FeedGuard } from '../services/feed-guard';
9
+ import { CriteoFeedRun, CriteoSourceCar } from '../services/criteo-feed-run';
10
+ import { Exporter } from '../interfaces/exporter.interface';
11
+ import { CriteoConfig } from '../configs/criteo.config';
12
+ import { CriteoTransformer } from 'src/transformers/criteo.transformer';
13
+
14
+ /**
15
+ * ExportType.CRITEO (ADV-319). Wires the Cronicle/Nest services into CriteoFeedRun, which holds the
16
+ * logic. The output directory is no longer emptied: the new XML replaces the old one only after the
17
+ * fail-closed guard passed.
18
+ */
19
+ @Injectable()
20
+ export class CriteoExporter implements Exporter {
21
+ constructor(
22
+ private carService: CarService,
23
+ private configService: ConfigService,
24
+ private rulesService: RulesService,
25
+ private loaderService: LoaderService,
26
+ private moduleRef: ModuleRef,
27
+ ) { }
28
+
29
+ async export(): Promise<void> {
30
+ const config = this.configService.config as CriteoConfig;
31
+ const [xmlFile] = config.outputFiles;
32
+ const transformer = await this.moduleRef.create(CriteoTransformer);
33
+
34
+ const run = new CriteoFeedRun(
35
+ {
36
+ forEachChunk: fn => this.loaderService.process((chunk: FsChunkInterface) => fn(chunk.data as CriteoSourceCar[])),
37
+ filterCar: source => this.carService.filterCar(source, this.rulesService.rules),
38
+ transform: source => transformer.transform(source),
39
+ guard: new FeedGuard(config.feedGuard),
40
+ },
41
+ {
42
+ runId: config.runId,
43
+ domain: config.domain,
44
+ sourceDirectory: config.pathesConfig.localDirectory,
45
+ sourceBaseFileName: config.pathesConfig.baseFileName,
46
+ xmlFile,
47
+ stateDir: config.stateDir,
48
+ },
49
+ );
50
+
51
+ try {
52
+ const report = await run.run();
53
+ if (report.stale) {
54
+ console.log(`criteo feed STALE: last good file ${report.lastGoodAgeHours?.toFixed(1)} h old`);
55
+ }
56
+ } catch (error) {
57
+ // main.ts would report a rejected run as {"complete":1,"code":0}; fail the job so the chained
58
+ // upload step does not run (report + lock already handled by the run).
59
+ LoggerService.exitError(`criteo feed not published: ${error?.message ?? error}`);
60
+ process.exit(1);
61
+ }
62
+ }
63
+ }
@@ -4,8 +4,11 @@ import { ImboImageResponseInterface } from '@bytebrand/chunkable';
4
4
  import { DataSchemes } from '@bytebrand/car-schema';
5
5
 
6
6
  import { AutouncleJsonBuilder } from '../formats/autouncle-json.builder';
7
- import { AutouncleAddress, AutouncleExclusion, getAutouncleExclusion, mapAutouncleItem } from '../transformers/autouncle.transformer';
7
+ import {
8
+ AUTOUNCLE_ENVKV_ATTRIBUTES, AutouncleAddress, AutouncleExclusion, getAutouncleExclusion, mapAutouncleItem,
9
+ } from '../transformers/autouncle.transformer';
8
10
  import { FeedGuard, FeedGuardError, LastGood, inspectSourceSnapshot } from './feed-guard';
11
+ import { PublicationGate } from './publication-gate';
9
12
 
10
13
  export interface AutouncleSourceCar {
11
14
  car: DataSchemes.Car;
@@ -26,6 +29,8 @@ export interface AutouncleRunDeps {
26
29
  imageStats: () => Record<string, number>;
27
30
  translate: (key: string) => string;
28
31
  guard: FeedGuard;
32
+ // EnVKV C8 publication gate (CBD enrichment log); absent = off
33
+ gate?: Pick<PublicationGate, 'mode' | 'estimatedAttributes'>;
29
34
  }
30
35
 
31
36
  export interface AutouncleRunOptions {
@@ -54,6 +59,8 @@ export interface AutouncleRunReport {
54
59
  excluded: Record<string, number>;
55
60
  imagesPerItem: { min: number | null; max: number | null; total: number };
56
61
  images: Record<string, number>;
62
+ // published items with an estimated EnVKV value per attribute; with gate enforce those were sent empty
63
+ envkv: { gate: 'off' | 'audit' | 'enforce'; estimated: Record<string, number> };
57
64
  lastGood: LastGood | null;
58
65
  lastGoodAgeHours: number | null;
59
66
  stale: boolean;
@@ -93,6 +100,7 @@ export class AutouncleFeedRun {
93
100
  excluded: {},
94
101
  imagesPerItem: { min: null, max: null, total: 0 },
95
102
  images: {},
103
+ envkv: { gate: this.deps.gate?.mode ?? 'off', estimated: {} },
96
104
  lastGood,
97
105
  lastGoodAgeHours: guard.staleHours(lastGood, startedAt),
98
106
  stale: guard.isStale(lastGood, startedAt),
@@ -141,11 +149,20 @@ export class AutouncleFeedRun {
141
149
  continue;
142
150
  }
143
151
  const images = await this.deps.resolveImages(source);
144
- const mapped = mapAutouncleItem(source.car, { domain: this.options.domain, address, images, translate: this.deps.translate });
152
+ const estimated = (this.deps.gate ? this.deps.gate.estimatedAttributes(source.car) : [])
153
+ .filter(attribute => AUTOUNCLE_ENVKV_ATTRIBUTES.includes(attribute));
154
+ const mapped = mapAutouncleItem(source.car, {
155
+ domain: this.options.domain,
156
+ address,
157
+ images,
158
+ translate: this.deps.translate,
159
+ withheldEnvkv: this.deps.gate?.mode === 'enforce' ? estimated : [],
160
+ });
145
161
  if (mapped.ok === false) {
146
162
  increment(report.excluded, mapped.reason);
147
163
  continue;
148
164
  }
165
+ estimated.forEach(attribute => increment(report.envkv.estimated, attribute));
149
166
  await builder.addItem(mapped.item, first);
150
167
  first = false;
151
168
  dealers.add(ownerId);