@bytebrand/export-cars 1.26.47 → 1.26.48

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 (52) hide show
  1. package/dist/configs/ooyyo.config.d.ts +14 -0
  2. package/dist/configs/ooyyo.config.js +40 -6
  3. package/dist/configs/ooyyo.config.js.map +1 -1
  4. package/dist/exporters/ooyyo.exporter.d.ts +5 -4
  5. package/dist/exporters/ooyyo.exporter.js +45 -25
  6. package/dist/exporters/ooyyo.exporter.js.map +1 -1
  7. package/dist/formats/ooyyo-xml.builder.d.ts +7 -2
  8. package/dist/formats/ooyyo-xml.builder.js +59 -36
  9. package/dist/formats/ooyyo-xml.builder.js.map +1 -1
  10. package/dist/interfaces/export-item.interface.d.ts +30 -2
  11. package/dist/interfaces/params.interface.d.ts +3 -0
  12. package/dist/services/ooyyo-feed-run.d.ts +81 -0
  13. package/dist/services/ooyyo-feed-run.js +183 -0
  14. package/dist/services/ooyyo-feed-run.js.map +1 -0
  15. package/dist/services/ooyyo-feed-verify.d.ts +13 -0
  16. package/dist/services/ooyyo-feed-verify.js +104 -0
  17. package/dist/services/ooyyo-feed-verify.js.map +1 -0
  18. package/dist/transformers/ooyyo.transformer.d.ts +51 -11
  19. package/dist/transformers/ooyyo.transformer.js +268 -95
  20. package/dist/transformers/ooyyo.transformer.js.map +1 -1
  21. package/dist/tsconfig.build.tsbuildinfo +1 -1
  22. package/docs/marktde/AUDIT.md +84 -84
  23. package/docs/marktde/PARTNER-QUESTIONS.md +49 -49
  24. package/docs/marktde/RUNBOOK.md +58 -58
  25. package/docs/marktde/TRACKING.md +75 -69
  26. package/docs/meinestadt/PARTNER-QUESTIONS.md +17 -0
  27. package/docs/meinestadt/TRACKING.md +107 -0
  28. package/docs/ooyyo/AUDIT.md +81 -0
  29. package/docs/ooyyo/PARTNER-QUESTIONS.md +32 -0
  30. package/docs/ooyyo/RUNBOOK.md +67 -0
  31. package/docs/ooyyo/TRACKING.md +54 -0
  32. package/package.json +66 -65
  33. package/src/configs/marktde.config.ts +70 -70
  34. package/src/configs/ooyyo.config.ts +65 -15
  35. package/src/exporters/marktde.exporter.ts +78 -78
  36. package/src/exporters/ooyyo.exporter.ts +75 -48
  37. package/src/formats/ooyyo-xml.builder.ts +87 -48
  38. package/src/interfaces/export-item.interface.ts +43 -2
  39. package/src/interfaces/params.interface.ts +5 -0
  40. package/src/services/marktde-feed-run.ts +233 -233
  41. package/src/services/ooyyo-feed-run.ts +265 -0
  42. package/src/services/ooyyo-feed-verify.ts +131 -0
  43. package/src/transformers/marktde.transformer.ts +255 -255
  44. package/src/transformers/ooyyo.transformer.ts +378 -109
  45. package/test/fixtures/ooyyo/OoyyoFormat.xml +125 -0
  46. package/test/fixtures/ooyyo/OoyyoFormat.xsd +116 -0
  47. package/test/fixtures/ooyyo.fixture.ts +53 -0
  48. package/test/mapping-csv.test.ts +65 -65
  49. package/test/marktde-feed-run.test.ts +263 -263
  50. package/test/marktde-mapping.test.ts +280 -280
  51. package/test/ooyyo-feed-run.test.ts +293 -0
  52. package/test/ooyyo-mapping.test.ts +276 -0
@@ -0,0 +1,107 @@
1
+ # meinestadt.de — measurement design (ADV-327)
2
+
3
+ Status 13.11.2026: DEV only. Implemented: the listing link (export-cars 1.26.44), the reporting channel
4
+ `CLASSIFIEDS_MEINESTADT` (web-funnel-tracking-health 1.5.5) and the lead source for the VDP enquiry
5
+ (user-actions-contact, ADV-327 fix). Not implemented, by design, until meinestadt confirms a path in
6
+ writing: partner click ids, postbacks / pixels, the daily report import. No production switch.
7
+
8
+ ## 1. Evidence
9
+
10
+ | Source | Link parameters | Note |
11
+ |---|---|---|
12
+ | Original 32-field workbook (tab `meinestadt`) | `source=meineStadt&medium=cpc&campaign=listing` | historic |
13
+ | export-cars ≤ 1.26.43 / Confluence | `source=classifieds&medium=listing&campaign=meinestadt` | partner only in `campaign` |
14
+ | export-cars ≥ 1.26.44 (current DEV) | `source=meinestadt&medium=listing&campaign=listing` + identical `utm_source/utm_medium/utm_campaign` | partner in `source`; no paid medium |
15
+
16
+ - ADV-260: fe-main stores `source / medium / campaign / utm_*` + click ids in the `marketing` cookie
17
+ (`CommonUtils.ts` `MARKETING_PARAM_REGISTRY`, merge per key, 24 h) and the first landing URL / external
18
+ referrer in `landing_context` (first wins). car-basic-data (VDP), car-search-endpoint (SRL),
19
+ user-actions-contact (forms, checkout) read both cookies server-side into the BigQuery event rows.
20
+ - Before 1.5.5 every variant fell into generic rules: the workbook variant (`medium=cpc`) as `PAID_OTHER`,
21
+ the legacy variant as `UNRESOLVED` (`medium=listing` is unmapped). Partner identity was lost.
22
+ - Before the ADV-327 fix the VDP enquiry "Frage zum Fahrzeug" (`VehicleDetailedNextPage.onEnquirySubmit`)
23
+ sent no `marketing` object, and user-actions-contact built the Odoo message only from the body, so
24
+ those REQs reached Odoo without utm source / medium / campaign. DEV 09.11.2026, same visit with a
25
+ Criteo cookie: enquiry → source "(nicht bekannt)", offer request → `criteo / cpc / retargeting`.
26
+ This affected every channel, not only meinestadt.
27
+ - No meinestadt click id, postback, pixel or reporting API is known. Daily e-mails from
28
+ auto@meinestadt.de with `meinestadt-daily_reporting_AUTODE_<date>.csv` existed historically; no file was
29
+ available for this ticket, so its schema is not verified.
30
+
31
+ ## 2. Path
32
+
33
+ ```
34
+ meinestadt listing ──listingUrl──► https://www.auto.de/search/vehicle/<car _id>?source=meinestadt&medium=listing&campaign=listing&utm_*=…
35
+ → fe-main: marketing cookie + landing_context (ADV-260) raw values unchanged in every event row
36
+ → views: reporting_channel CLASSIFIEDS_MEINESTADT, family Classifieds, detail meinestadt.de, publisher MEINESTADT
37
+ → form (VDP enquiry / offer request / checkout) → REQ → Odoo utm source "meinestadt" (body marketing, or the cookie)
38
+ → fact_lead.crm_source → source_resolved_v1 = 'meinestadt' (customer SQL, no change needed for the new link)
39
+ → rpt_episode_touchpoint → canonical 90-day intent episode (original episode_start_date) → fact_sale / CM
40
+ ```
41
+
42
+ - Grain: VISITOR session (traffic_class VISITOR; KNOWN_CRAWLER / BOT_SUSPECT rules run before attribution).
43
+ - Raw vs normalized: `source / medium / campaign / utm_*` stay as received in the event rows and in Odoo;
44
+ the channel is derived in the views. Historical rows are not rewritten — the views classify at query
45
+ time, the producer label (`resolved_channel`, message-schemes) is untouched.
46
+ - Rule (web-funnel-tracking-health `MEINESTADT_UTM`): `source` (legacy or utm, lower-cased) in
47
+ `meinestadt` / `meinstadt`, or `source = classifieds` and `campaign` in `meinestadt` / `meinstadt`; any
48
+ medium; after the click-id rules (a Google click id wins), before the paid UTM rules; not `PAID_*`, so
49
+ the paid click-id health rates are unaffected (paid / free is a contract attribute).
50
+ - Listing-level join without a partner id: feed `listingUrl` car `_id` = VDP id = lead car id.
51
+ - Click reconciliation: platform `meinestadt` in `buildClickReconciliationView`, aggregate only (no click
52
+ id). Used only once `fact_marketing_media` holds meinestadt rows. No artificial 1:1 mapping.
53
+ - Off-site leads (phone, partner-hosted forms) never create a website session.
54
+ - Consent: the cookie capture is first-party and not consent-gated today (open compliance question noted
55
+ in fe-main `CookieUtils.getConsentState`); every event row carries `marketingConsent` /
56
+ `analyticsConsent` from the request headers. Nothing is sent to meinestadt (no pixel / postback).
57
+ Finance / bank data never leaves auto.de.
58
+
59
+ ## 3. Episodes and sales (customer SQL, prod-only, not applied)
60
+
61
+ - New link: `crm_source = meinestadt` → `source_resolved_v1/v2 = 'meinestadt'` with the existing rules.
62
+ - Legacy link (`classifieds` + campaign `meinestadt`): resolves to `classifieds` today; proposal D-rule
63
+ `source_resolved_v2 = 'meinestadt'`, method `PARTNER_PARAM_EXACT` (raw values kept). File:
64
+ `release-runbooks/adv-327/ADV-327-customer-sql-proposal.sql`.
65
+ - Same person within a fixed 90-day episode → no new acquisition (`rpt_customer_intent_episode`).
66
+ Sales: distinct `fact_sale` ids per episode, cohort = `episode_start_date`, `sales_reported_at` separate.
67
+
68
+ ## 4. Costs (blocked on the real report and a contract)
69
+
70
+ - Target: the existing `fact_marketing_media` / `fact_marketing_cost` (no new table, no data mart).
71
+ `source_system = meinestadt_daily_report`, `channel = meinestadt`, report date (partner timezone →
72
+ Europe/Berlin), billable / non-billable clicks, spend, currency, credits as negative rows, invoice
73
+ number, contract version, file name + SHA-256 for idempotent upsert and correction replay, status
74
+ `actual` / `estimated` / `missing` per day.
75
+ - The feed field `cpc` is a bid / configuration value, never actual cost; no cost from the historic 0.08.
76
+ - `rpt_lead_cost_live.paid_channel_v3` gets `meinestadt` only while a CPC contract is effective.
77
+ - The parser is written against a real CSV only (separator, encoding, decimal comma, timezone, columns).
78
+
79
+ ## 5. QA (DEV)
80
+
81
+ | Case | Expected | Status |
82
+ |---|---|---|
83
+ | new link, legacy link, workbook link (`meineStadt` + `cpc`) | CLASSIFIEDS_MEINESTADT event + session | ✅ 12.11 real DEV requests + unit tests |
84
+ | other classifieds campaign | not meinestadt | ✅ control → UNRESOLVED |
85
+ | Google click id + meinestadt utm | click id wins | ✅ rule order test |
86
+ | raw values kept | event rows hold `meineStadt` / `classifieds` as sent | ✅ BQ |
87
+ | fe-main capture of the link | cookie = link parameters, no parameterConflict | ✅ fe-main function run on the URLs |
88
+ | health metrics | Classifieds in `channel_family_share`, 0 WARN | ✅ day run 12.11 |
89
+ | VDP enquiry → REQ → Odoo source (no body marketing, cookie only) | utm source from the cookie | ✅ 13.11 DEV: REQ584784 `meinestadt / listing / listing`, REQ584783 `classifieds / listing / meinestadt` (raw); sessions CLASSIFIEDS_MEINESTADT, `develop_web_journey_lead_v1` CONFIRMED |
90
+ | offer request with body marketing | unchanged (body wins) | ✅ unit test |
91
+ | no consent | capture unchanged, consent flag on rows | documented, not changed |
92
+ | bot | KNOWN_CRAWLER / BOT_SUSPECT, not counted | existing rules |
93
+ | new / repeat episode, 2 sales, CSV replay / credit / no-report day, DST | — | prod customer SQL / blocked on CSV |
94
+
95
+ Note: Odoo `cookieid` is filled only for signed-in senders (user-actions-contact, unchanged); the website
96
+ session ↔ REQ link is `lead_funnel_event` (session id + Pub/Sub messageId → REQ), as for every form.
97
+
98
+ ## 6. Launch blockers
99
+
100
+ Contract with auto.de GmbH, partner spec and approval of the link parameters, real daily CSV + field
101
+ definitions, legal / privacy sign-off, customer-SQL owner OK, feed and image acceptance (ADV-325/326).
102
+
103
+ ## 7. Rollback
104
+
105
+ - Link: export-cars ≤ 1.26.43 (dry-run feed only, nothing published).
106
+ - Channel: web-funnel-tracking-health 1.5.4 + view rebuild (event `emubc2hjemu`).
107
+ - Lead source fallback: revert the user-actions-contact commit and rebuild the service.
@@ -0,0 +1,81 @@
1
+ # OOYYO feed — audit and contract (ADV-331)
2
+
3
+ Status 13.11.2026: DEV only, dry-run by default. Nothing is hosted, sent or submitted; OOYYO's
4
+ crawler is not enabled. Export type `ooyyo`, export-cars ≥ 1.26.48.
5
+
6
+ ## Sources
7
+
8
+ | What | Where |
9
+ |---|---|
10
+ | Official XSD / XML template | `test/fixtures/ooyyo/OoyyoFormat.{xsd,xml}` — downloaded 13.11.2026 from the links on ooyyo.com/c=CDA31D7114F5854F111BFE6F9D671453 (d10y2oj5s0rxih.cloudfront.net/ooyyo_format/), 4,883 / 5,595 bytes = the attachments of Aleksandar Ciric's mail of 16.10.2026. sha256 xsd `58947bf2…ac07c3`, xml `08b3e74a…adefd` |
11
+ | Importer rules (binding) | Aleksandar Ciric (OOYYO), 16.10. and 19.10.2026, quoted in ADV-331 / ADV-333 |
12
+ | Legacy exporter | `OoyyoExporter` / `OoyyoTransformer` / `OoyyoXmlBuilder` before 1.26.48; DEV Cronicle `6.0 Export Ooyyo - part 1` (ejtpmrpb002) → `6.1 … part 2` (ejtpmsrmw03, shell `ftp put` of `autode-ooyyo.tar.gz` to the legacy Jelastic FTP) |
13
+
14
+ ## What the XSD does and does not enforce
15
+
16
+ `<ooyyo>` = `xsd:choice minOccurs=0 maxOccurs=unbounded` of `<ad>`; `<ad>` = choice of 25 optional
17
+ elements; all values `xsd:string`; `price`, `engine_size`, `mileage`, `power` need a `unit` attribute
18
+ (any string). So the XSD accepts an empty file, an ad without price / photo / year, and `unit="euro"`.
19
+ The partner's real acceptance rules are therefore implemented separately (`validateOoyyoItem`) and
20
+ applied twice: to every item before it is written and to the finished file when it is read back
21
+ (`verifyOoyyoXml`). The test suite re-derives the element names and required-unit list from the XSD
22
+ file, so a schema change fails the build.
23
+
24
+ ## Partner acceptance rules (19.10.2026) → implementation
25
+
26
+ | Rule | Implementation |
27
+ |---|---|
28
+ | `<price unit="EUR">` exactly; missing / other unit = rejected | builder writes `unit="EUR"`; verifier allows only `EUR` |
29
+ | price = integer vehicle purchase price | `price.currentSalesPrice` only, whole euros; never `rate.*` (leasing) or `price.purchasePrice` (dealer cost) |
30
+ | rejected without photo | ≥ 1 validated clean photo (ADV-336 selection: 3/4 hero first, URL check), else `no_valid_image` |
31
+ | rejected without year | first-registration year, else stored construction year; nothing defaulted, else `no_valid_year` |
32
+ | rejected when make / model not recognisable | empty / `SONSTIGE` / `Andere` / `selector_unknown` … → `no_make` / `no_recognisable_model` |
33
+ | vehicle URL and picture URLs ≤ 1,500 chars, HTTPS | `url_too_long` / `no_valid_image`; all URLs `https://` |
34
+ | partly valid file REPLACES the whole OOYYO inventory | FeedGuard (complete + fresh snapshot, min / drop vs last good, lock) + read-back verify of every ad + count equality; temp → atomic rename; blocked run keeps the last good `.xml` / `.xml.gz` and exits 1 |
35
+ | download failure / 0 readable ads: OOYYO keeps old inventory | not relied upon — an empty file is never published (`published_too_small`) |
36
+ | one XML at a fixed HTTPS URL, plain or gzip ending `.gz`, FTP discouraged | outputs `ooyyo.xml` + `ooyyo.xml.gz` (real gzip of the XML, round-trip checked); the legacy `autode-ooyyo.tar.gz` (tar, FTP) is no longer produced. Hosting is NOT set up |
37
+
38
+ ## Field mapping (source → XML)
39
+
40
+ | Element | Source | Rule |
41
+ |---|---|---|
42
+ | `url` | `_id` | `https://www.<domain>/search/vehicle/<id>?source=ooyyo&medium=listing&campaign=listing&utm_source=ooyyo&utm_medium=listing&utm_campaign=listing` (ADV-333) |
43
+ | `title` | `mainData.make model subModel` | German, missing parts left out (legacy prefixed `constructionYear`, also for used cars) |
44
+ | `content` | `offer.offerDescription` | plain text: mobile.de `\\` → line break, `**` dropped; nothing added; empty → left out |
45
+ | `price unit="EUR"` | `price.currentSalesPrice` | integer > 0 |
46
+ | `make` / `model` / `trim` | `mainData.make` / `model` / `subModel` | as stored |
47
+ | `engine_size unit="cc"` | `engineData.cubicCapacity` | > 0 else left out (BEV) |
48
+ | `body_type` / `color` / `fuel_type` / `transmission` | selectors | German labels (`de` dictionary), `Außenfarbe ` prefix removed, unknown → left out |
49
+ | `year` | `mainData.firstRegistration` / `constructionYear` | see rules |
50
+ | `mileage unit="km"` | `mainData.mileage` | ≥ 0 (0 for new cars is real) |
51
+ | `euronorm` | `environmentEmissions.emissionClass` | `1`…`6`, `6b`, `6c`, `6d`, `6d-TEMP` (legacy values); unknown left out (legacy wrote `default`) |
52
+ | `power unit="kw"` | `engineData.powerKW` | > 0 |
53
+ | `doors` / `seats` / `gears` | `mainData.doors` / `seats`, `driveSuspension.gears` | > 0 |
54
+ | `location/city` | `location.city` | as stored |
55
+ | `location/postcode` | `location.zipCode` | STRING; DE 4-digit → leading zero restored (`4103` → `04103`); invalid → left out |
56
+ | `location/region` | postal code table (`DePostalCodesService`) | Bundesland of the postcode (legacy wrote the country code `DE`) |
57
+ | `pictures/picture` | Imbo images | validated clean list, hero first, no cap (20 is a ranking recommendation), never `noimage` |
58
+ | `vin` | `metaData.vin` | only a valid ISO 3779 VIN |
59
+ | `is_new` | `mainData.condition` | `1` new, `0` used |
60
+ | `date` / `time` | `metaData.creationDate` | `DD/MM/YYYY` / `HH:MM` 24 h, Europe/Berlin (legacy `hh:mm:ss`, 12 h, host TZ) |
61
+
62
+ Empty optional elements are left out entirely (all are optional in the XSD); the legacy builder wrote
63
+ `<engine_size/>` / `<power/>` without the required `unit`, which made the file schema-invalid.
64
+ Values are CDATA-wrapped as in the template; `]]>` is split, XML-invalid control characters removed.
65
+
66
+ Excluded cars (counted in `last-run-report.json.excluded`): `duplicate_id`, `filtered` (Cronicle
67
+ rules, no images), `reserved_or_sold`, `no_valid_price`, `no_make`, `no_recognisable_model`,
68
+ `no_valid_year`, `no_valid_image`, `url_too_long`.
69
+
70
+ ## Legacy DEV output (last run 09.11.2026 09:01, 1,771 ads) — defects found
71
+
72
+ - `price unit="euro"` on every ad (rejected by the current importer).
73
+ - 5,001 `…/carimage/noimage/…` placeholder pictures (the template forbids "No image available" images).
74
+ - `region` = `DE` (country code, not a region); 415 of 3,094 DEV cars have a 4-digit postcode
75
+ (leading zero lost because CBD stores a number).
76
+ - `euronorm` = `default` on 65 ads; empty `<engine_size/>` / `<power/>` without `unit` → XSD-invalid.
77
+ - `time` as `hh:mm:ss` (12-hour clock); year invented for new cars without a year (`now - 2 months`).
78
+ - Output directory emptied before the run (an outage published an empty / partial file); tar.gz built
79
+ with the external `zip` tooling.
80
+
81
+ DEV dry-run results with 1.26.48: see `RUNBOOK.md` → "DEV runs".
@@ -0,0 +1,32 @@
1
+ # OOYYO — open partner questions (ADV-331 / ADV-333)
2
+
3
+ Contact: Aleksandar Ciric (OOYYO). `submission@ooyyo.net` is dead. Draft only — nothing is sent from
4
+ engineering; Marketing sends after review.
5
+
6
+ Already answered (16.10. / 19.10.2026): OOYYO XML only; fixed HTTPS URL, plain or `.gz`; daily pull
7
+ shortly after midnight; full replacement (partly valid file replaces everything, failed download / 0
8
+ readable ads keeps the old inventory); `unit="EUR"` mandatory; photo / year / price / recognisable
9
+ make + model mandatory; URLs ≤ 1,500 chars; UTMs allowed and kept, direct link without redirect; free
10
+ listing has no click reporting and no click id; Featured Vehicles is a separate paid product.
11
+
12
+ Open:
13
+
14
+ 1. Time zone of the daily pull ("shortly after midnight") — asked by Marketing on 19.10., no answer yet.
15
+ We need it to schedule our export so the file is fresh but never mid-write (we publish atomically).
16
+ 2. How should the 20-car test sample reach the importer for the non-live test: file attachment or a
17
+ temporary URL? If a URL: may it carry a secret token in the path / query (the partner mentioned an
18
+ optional token), and does the importer accept a self-signed / basic-auth protected URL?
19
+ 3. Retry behaviour when the URL answers 5xx / times out at the pull time: one retry later the same
20
+ night, or next day only? Any alerting on their side we can subscribe to?
21
+ 4. Grace period for deletions: a car missing in one file — removed immediately at that import?
22
+ 5. Make / model recognition: is there a reference list (or a report of unmatched values after the
23
+ test import) so we can map our values (e.g. "Mercedes-Benz", "Škoda", sub-brands) up front?
24
+ 6. Accepted values for `fuel_type`, `transmission`, `body_type`, `color`: German labels OK (we send
25
+ e.g. `Benzin`, `Elektro`, `Automatik`, `SUV/Geländewagen`), or English keywords preferred?
26
+ 7. `euronorm`: are our values `6`, `6d`, `6d-TEMP` OK, or `Euro 6` style?
27
+ 8. `date` / `time`: listing creation date in Europe/Berlin — correct meaning ("date of the ad")?
28
+ 9. Electric cars: is an ad without `engine_size` fine (we leave it out rather than send 0)?
29
+ 10. Crawler: User-Agent and source IPs of the feed fetcher and of any page crawler, so we can classify
30
+ them as automation in our traffic reporting (ADV-333) and allow-list them at Cloudflare.
31
+ 11. Does OOYYO ever request our vehicle pages itself (link checking / crawling) after the import?
32
+ 12. Rejection report after each production import (counts + reasons) — available beyond the test?
@@ -0,0 +1,67 @@
1
+ # OOYYO feed — runbook (ADV-331)
2
+
3
+ ## Parameters (Cronicle event, export-cars plugin, `exportType=ooyyo`)
4
+
5
+ | Param | Default | Meaning |
6
+ |---|---|---|
7
+ | `String` / `Number` / `Date` | — | Cronicle filter rules (DEV 6.0: none) |
8
+ | `ooyyoDryRun` | dry-run | only the exact text `false` makes a live run |
9
+ | `ooyyoMaxImages` | no cut | cut the picture list (OOYYO accepts all; 20 is a ranking recommendation) |
10
+ | `ooyyoImageConcurrency` | 16 | parallel image URL checks |
11
+ | `feedGuardMinSourceItems` | 1000 | DEV snapshot ~3,100 cars |
12
+ | `feedGuardMinPublishedItems` | 1 | set a real minimum from a PROD dry-run before going live |
13
+ | `feedGuardMaxDropPercent` | 20 | max drop of source / ads against the last good run |
14
+ | `feedGuardMaxSourceAgeHours` | 26 | newest base export chunk must be younger |
15
+ | `feedGuardMaxStaleHours` | 48 | last good file reported STALE after this |
16
+ | `feedGuardAcceptDrop` | off | `true` for ONE run after a real, approved stock reduction |
17
+
18
+ No new parameter is needed for a run: the defaults give a safe dry-run.
19
+
20
+ ## Files
21
+
22
+ | Mode | Output | State |
23
+ |---|---|---|
24
+ | dry-run | `resources/ooyyo-dryrun/ooyyo.xml` + `ooyyo.xml.gz` | `resources/ooyyo-state/dryrun/` |
25
+ | live | `resources/ooyyo-export/ooyyo.xml` + `ooyyo.xml.gz` | `resources/ooyyo-state/export/` |
26
+
27
+ State dir: `last-good.json`, `last-run-report.json` (guard code, exclusions, verify issues, missing
28
+ optional fields, padded postcodes, bytes + sha256, image stats), `run.lock`. A blocked run leaves
29
+ `ooyyo.xml.rejected`; the published files and their `.prev` copies stay.
30
+
31
+ ## Run order inside one run
32
+
33
+ source snapshot check → map + partner rules + image check per car → temp XML → read back + verify
34
+ every ad and the count → count guard vs last good → gzip + round-trip check → rename `.xml.gz`, then
35
+ `.xml` (both atomic) → report. Any failure: exit 1, nothing replaced.
36
+
37
+ ## Exit codes and the chain
38
+
39
+ Blocked or failed runs exit 1, so the chained `6.1 … part 2` does not run. A dry-run exits 0 and DEV
40
+ `6.1` then uploads `resources/ooyyo-export/autode-ooyyo.tar.gz` — the LEGACY file from 09.11.2026,
41
+ which 1.26.48 no longer writes — to the legacy Jelastic FTP (as `autode-ooyyo-develop.tar.gz`). `6.1`
42
+ is not changed (same decision as meinestadt `5.1`). To run 6.0 alone use the API with `chain: ""`:
43
+
44
+ ```
45
+ # on the DEV Cronicle host; the key never leaves the host
46
+ K=$(sudo -n node bin/storage-cli.js get global/api_keys/0 | node -e '…print .key…')
47
+ curl -s -X POST 127.0.0.1:<WebServer.http_port>/api/app/run_event/v1 \
48
+ -d '{"api_key":"'$K'","id":"ejtpmrpb002","chain":""}'
49
+ ```
50
+
51
+ ## Before anything goes live (not done; each step needs explicit OK)
52
+
53
+ 1. Marketing approval; 20-car sample (from a PROD dry-run, not DEV data) to Aleksandar for the
54
+ NON-LIVE importer test; accepted / rejected counts and reasons reconciled (target: 0 rejected).
55
+ 2. Answers to `PARTNER-QUESTIONS.md` (pull time zone, retry, sample transport, value lists).
56
+ 3. Hosting: a fixed HTTPS URL serving `resources/ooyyo-export/ooyyo.xml(.gz)` read-only, optional
57
+ secret token kept out of logs; replace / disable `6.1` (legacy FTP, credentials in the script).
58
+ 4. Schedule the export so it finishes well before the partner's midnight pull; monitoring on
59
+ `last-run-report.json` (status, stale) and on the served file's age.
60
+ 5. `feedGuardMinPublishedItems` from a PROD dry-run; only then `ooyyoDryRun=false`.
61
+ 6. Stop-poll / rollback: serve the `.prev` file or an HTTP 5xx (OOYYO keeps the old inventory on a
62
+ failed download) — never an empty or partial file.
63
+ 7. Tracking: `web-funnel-tracking-health` ≥ 1.5.7 (CLASSIFIEDS_OOYYO) on the target Cronicle.
64
+
65
+ ## DEV runs
66
+
67
+ (filled in after the DEV run, see below)
@@ -0,0 +1,54 @@
1
+ # OOYYO — measurement (ADV-333)
2
+
3
+ Status 13.11.2026: DEV only. Implemented: the feed `<url>` scheme (export-cars 1.26.48) and the
4
+ reporting channel `CLASSIFIEDS_OOYYO` (web-funnel-tracking-health 1.5.7). No partner click id, cost,
5
+ postback or pixel exists for the free listing, so none is modelled.
6
+
7
+ ## Evidence
8
+
9
+ - Partner, 19.10.2026: OOYYO links DIRECTLY to the `<url>` of the XML, no redirect, query parameters
10
+ (our UTMs) unchanged; URL ≤ 1,500 chars; free listing = no click reporting, no click id.
11
+ - Historic ACF-6139 link: `?source=classifieds&medium=listing&campaign=ooyyo` (Confluence tag
12
+ "ooyyo [organic]"). Before 1.5.7 that resolved to generic classifieds / UTM rules — the partner
13
+ identity was lost, same finding as AutoUncle (ADV-324).
14
+ - ADV-260: fe-main keeps `source/medium/campaign/utm_*` in the marketing cookie (first external
15
+ context wins, internal navigation does not overwrite it) and sends it with every lead.
16
+
17
+ ## Path
18
+
19
+ ```
20
+ OOYYO result ──(direct link, no redirect)──► https://www.auto.de/search/vehicle/<car _id>
21
+ ?source=ooyyo&medium=listing&campaign=listing&utm_source=ooyyo&utm_medium=listing&utm_campaign=listing
22
+ → fe-main marketing cookie (ADV-260) → visitor session, landing_url keeps the raw query
23
+ → views: reporting_channel CLASSIFIEDS_OOYYO, family Classifieds, detail OOYYO, publisher OOYYO
24
+ (historic source=classifieds&campaign=ooyyo resolves to the same channel)
25
+ → form → REQ → Odoo crm_source "ooyyo" (VDP enquiries need the user-actions-contact ADV-327 cookie
26
+ fallback — DEV since 13.11, not on PROD)
27
+ → rpt_episode_touchpoint → canonical 90-day episode (original episode_start_date) → sale / CM
28
+ ```
29
+
30
+ - Legacy and utm pairs carry identical values, so fe-main's `getUtmQueryParameter` sees no
31
+ parameter conflict; GA4 sees `ooyyo / listing`.
32
+ - Not PAID_*: the listing is free; it stays out of the paid click-id health rates and gets no cost.
33
+ Spend for OOYYO is "verified no fee" only for the free XML listing; any Featured Vehicles test needs
34
+ its own cost import first.
35
+ - Reconciliation view: platform `ooyyo`, but there are no media rows → sessions only, no exact join.
36
+
37
+ ## Crawler / bot filtering
38
+
39
+ Sessions are VISITOR / KNOWN_CRAWLER / INTERNAL by published crawler IP ranges and proven automation
40
+ signatures (ADV-371). OOYYO publishes no ranges; its feed fetch reads the XML file only and creates
41
+ no tracking event. No OOYYO rule is guessed — partner question 10 asks for UA / IPs; once known, add
42
+ them as verified automation with evidence.
43
+
44
+ ## Not done (by design)
45
+
46
+ - No `ooyyo_clickid` or similar parameter, no postback, no conversion upload.
47
+ - No retroactive rewrite of historic `crm_source` values.
48
+ - Customer SQL (`source_resolved_v1 = LOWER(crm_source)`) already yields `ooyyo` for the new link; the
49
+ historic `classifieds` + campaign `ooyyo` rows stay `classifieds` until the data owner applies the
50
+ classifieds-campaign rule proposed for ADV-324 / ADV-327 / ADV-330.
51
+
52
+ ## QA
53
+
54
+ See `RUNBOOK.md` → "DEV runs" for the DEV test (link → VDP → enquiry → Odoo REQ → views).
package/package.json CHANGED
@@ -1,65 +1,66 @@
1
- {
2
- "name": "@bytebrand/export-cars",
3
- "version": "1.26.47",
4
- "description": "",
5
- "author": "bytebrand",
6
- "license": "UNLICENSED",
7
- "scripts": {
8
- "prebuild": "rimraf dist",
9
- "build": "nest build",
10
- "prepublishOnly": "npm run build",
11
- "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
12
- "lint": "eslint -c .eslintrc.js --ext .ts src/",
13
- "start:dev": "nest start --watch",
14
- "test": "ts-node --transpile-only test/google-feed-envkv.test.ts && ts-node --transpile-only test/google-feed-item-row.test.ts && ts-node --transpile-only test/publication-gate.test.ts && ts-node --transpile-only test/images-regression.test.ts && ts-node --transpile-only test/clean-images.test.ts && ts-node --transpile-only test/image-url-validator.test.ts && ts-node --transpile-only test/openai-image-fields.test.ts && ts-node --transpile-only test/openai-ads-mapping.test.ts && ts-node --transpile-only test/openai-ads-feed-run.test.ts && ts-node --transpile-only test/openai-ads-finance.test.ts && ts-node --transpile-only test/openai-ads-price-cap.test.ts && ts-node --transpile-only test/meinstadt-mapping.test.ts && ts-node --transpile-only test/meinstadt-feed-run.test.ts && ts-node --transpile-only test/autouncle-mapping.test.ts && ts-node --transpile-only test/autouncle-feed-run.test.ts && ts-node --transpile-only test/mapping-csv.test.ts && ts-node --transpile-only test/marktde-mapping.test.ts && ts-node --transpile-only test/marktde-feed-run.test.ts && ts-node --transpile-only test/criteo-images.test.ts && ts-node --transpile-only test/criteo-mapping.test.ts && ts-node --transpile-only test/criteo-feed-run.test.ts"
15
- },
16
- "dependencies": {
17
- "@bytebrand/autode-request": "^1.19.2",
18
- "@bytebrand/bigquery-logger": "^2.1.2",
19
- "@bytebrand/car-basic-data-scope": "^9.11.24",
20
- "@bytebrand/car-description": "^1.17.20",
21
- "@bytebrand/car-schema": "^3.14.15",
22
- "@bytebrand/chunkable": "^5.19.8",
23
- "@bytebrand/cronicle-helper": "^1.21.8",
24
- "@bytebrand/fe-financing": "^5.14.19",
25
- "@bytebrand/i18n-dictionaries": "^1.5.0",
26
- "@bytebrand/import-helper": "^0.45.0",
27
- "@bytebrand/schema-tools": "^3.18.8",
28
- "@bytebrand/zipper": "^2.2.0",
29
- "@nestjs/cache-manager": "^2.1.0",
30
- "@nestjs/common": "^10.2.7",
31
- "@nestjs/core": "^10.2.7",
32
- "adm-zip": "0.5.5",
33
- "axios": "^1.5.1",
34
- "bottleneck": "^2.19.5",
35
- "cache-manager": "^5.2.4",
36
- "csv-write-stream": "^2.0.0",
37
- "google-spreadsheet": "^3.3.0",
38
- "lodash": "^4.17.21",
39
- "moment": "^2.29.4",
40
- "nest-commander": "~3.15.0",
41
- "reflect-metadata": "^0.1.13",
42
- "vin-generator": "^1.0.1"
43
- },
44
- "devDependencies": {
45
- "@nestjs/cli": "^10.1.18",
46
- "@types/google-spreadsheet": "^3.3.0",
47
- "@types/inquirer": "^8.2.4",
48
- "@types/lodash": "^4.14.186",
49
- "@types/node": "^16.18.58",
50
- "@typescript-eslint/eslint-plugin": "^6.8.0",
51
- "@typescript-eslint/parser": "^6.8.0",
52
- "eslint": "^8.0.1",
53
- "eslint-config-prettier": "^8.3.0",
54
- "eslint-plugin-prettier": "^4.0.0",
55
- "gts": "^3.1.1",
56
- "prettier": "^2.3.2",
57
- "ts-node": "^10.0.0",
58
- "tsconfig-paths": "4.1.0",
59
- "typescript": "^5.2.2"
60
- },
61
- "engines": {
62
- "node": ">=14.0.0"
63
- },
64
- "gitHead": "e827f35865032821bf3a081f03a026d3d3f8a540"
65
- }
1
+ {
2
+ "name": "@bytebrand/export-cars",
3
+ "version": "1.26.48",
4
+ "description": "",
5
+ "author": "bytebrand",
6
+ "license": "UNLICENSED",
7
+ "scripts": {
8
+ "prebuild": "rimraf dist",
9
+ "build": "nest build",
10
+ "prepublishOnly": "npm run build",
11
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
12
+ "lint": "eslint -c .eslintrc.js --ext .ts src/",
13
+ "start:dev": "nest start --watch",
14
+ "test": "ts-node --transpile-only test/google-feed-envkv.test.ts && ts-node --transpile-only test/google-feed-item-row.test.ts && ts-node --transpile-only test/publication-gate.test.ts && ts-node --transpile-only test/images-regression.test.ts && ts-node --transpile-only test/clean-images.test.ts && ts-node --transpile-only test/image-url-validator.test.ts && ts-node --transpile-only test/openai-image-fields.test.ts && ts-node --transpile-only test/openai-ads-mapping.test.ts && ts-node --transpile-only test/openai-ads-feed-run.test.ts && ts-node --transpile-only test/openai-ads-finance.test.ts && ts-node --transpile-only test/openai-ads-price-cap.test.ts && ts-node --transpile-only test/meinstadt-mapping.test.ts && ts-node --transpile-only test/meinstadt-feed-run.test.ts && ts-node --transpile-only test/autouncle-mapping.test.ts && ts-node --transpile-only test/autouncle-feed-run.test.ts && ts-node --transpile-only test/mapping-csv.test.ts && ts-node --transpile-only test/marktde-mapping.test.ts && ts-node --transpile-only test/marktde-feed-run.test.ts && ts-node --transpile-only test/ooyyo-mapping.test.ts && ts-node --transpile-only test/ooyyo-feed-run.test.ts && ts-node --transpile-only test/criteo-images.test.ts && ts-node --transpile-only test/criteo-mapping.test.ts && ts-node --transpile-only test/criteo-feed-run.test.ts"
15
+ },
16
+ "dependencies": {
17
+ "@bytebrand/autode-request": "^1.19.2",
18
+ "@bytebrand/bigquery-logger": "^2.1.2",
19
+ "@bytebrand/car-basic-data-scope": "^9.11.24",
20
+ "@bytebrand/car-description": "^1.17.20",
21
+ "@bytebrand/car-schema": "^3.14.15",
22
+ "@bytebrand/chunkable": "^5.19.8",
23
+ "@bytebrand/cronicle-helper": "^1.21.8",
24
+ "@bytebrand/fe-financing": "^5.14.19",
25
+ "@bytebrand/i18n-dictionaries": "^1.5.0",
26
+ "@bytebrand/import-helper": "^0.45.0",
27
+ "@bytebrand/schema-tools": "^3.18.8",
28
+ "@bytebrand/zipper": "^2.2.0",
29
+ "@nestjs/cache-manager": "^2.1.0",
30
+ "@nestjs/common": "^10.2.7",
31
+ "@nestjs/core": "^10.2.7",
32
+ "adm-zip": "0.5.5",
33
+ "axios": "^1.5.1",
34
+ "bottleneck": "^2.19.5",
35
+ "cache-manager": "^5.2.4",
36
+ "csv-write-stream": "^2.0.0",
37
+ "fast-xml-parser": "4.2.5",
38
+ "google-spreadsheet": "^3.3.0",
39
+ "lodash": "^4.17.21",
40
+ "moment": "^2.29.4",
41
+ "nest-commander": "~3.15.0",
42
+ "reflect-metadata": "^0.1.13",
43
+ "vin-generator": "^1.0.1"
44
+ },
45
+ "devDependencies": {
46
+ "@nestjs/cli": "^10.1.18",
47
+ "@types/google-spreadsheet": "^3.3.0",
48
+ "@types/inquirer": "^8.2.4",
49
+ "@types/lodash": "^4.14.186",
50
+ "@types/node": "^16.18.58",
51
+ "@typescript-eslint/eslint-plugin": "^6.8.0",
52
+ "@typescript-eslint/parser": "^6.8.0",
53
+ "eslint": "^8.0.1",
54
+ "eslint-config-prettier": "^8.3.0",
55
+ "eslint-plugin-prettier": "^4.0.0",
56
+ "gts": "^3.1.1",
57
+ "prettier": "^2.3.2",
58
+ "ts-node": "^10.0.0",
59
+ "tsconfig-paths": "4.1.0",
60
+ "typescript": "^5.2.2"
61
+ },
62
+ "engines": {
63
+ "node": ">=14.0.0"
64
+ },
65
+ "gitHead": "e827f35865032821bf3a081f03a026d3d3f8a540"
66
+ }
@@ -1,70 +1,70 @@
1
- import { toNumber } from 'lodash';
2
-
3
- import { ExportType, FileType, ImageType } from '../enums';
4
- import { Params } from '../interfaces/params.interface';
5
- import { FeedGuardOptions } from '../services/feed-guard';
6
- import { Config } from './config';
7
-
8
- const text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '');
9
-
10
- const positiveNumber = (value: unknown, fallback: number): number => {
11
- const parsed = toNumber(text(value));
12
- return text(value) && Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
13
- };
14
-
15
- // Guard defaults (same as AutoUncle). The DEV job exports one dealer via the Cronicle rules, so the
16
- // published count is far below the base snapshot: minPublishedItems only forbids an empty file.
17
- export const MARKTDE_DEFAULTS = {
18
- imageConcurrency: 16,
19
- minSourceItems: 1000,
20
- minPublishedItems: 1,
21
- maxDropPercent: 20,
22
- maxSourceAgeHours: 26,
23
- maxStaleHours: 48,
24
- };
25
-
26
- /**
27
- * markt.de vehicle feed (ADV-328): the legacy mobile.de-style CSV (mapping sheet `mobile_yescar`,
28
- * ACF-4370) plus a ZIP of it, with clean ADV-336 images. There is no current markt.de spec yet, so
29
- * the format (columns, file names, no header row, `;` separator, UTF-8) stays as it was.
30
- * Dry-run is the default: the files go to resources/marktde-dryrun, which no Cronicle step reads.
31
- * Only marktdeDryRun=false writes resources/marktde-export (the directory `9c.1 … part 2` uploads
32
- * from). Each mode has its own guard state.
33
- */
34
- export class MarktdeConfig extends Config {
35
- dryRun: boolean;
36
- imageConcurrency: number;
37
- // 0 = no limit (no partner limit known)
38
- maxImages: number;
39
- stateDir: string;
40
- feedGuard: FeedGuardOptions;
41
-
42
- constructor(params: Params) {
43
- super(params);
44
- this.campaign = ExportType.MARKTDE;
45
- this.dryRun = text(params.marktdeDryRun).toLowerCase() !== 'false';
46
- const mode = this.dryRun ? 'dryrun' : 'export';
47
- this.pathesConfig.localConvertedDirectory = `resources/${ExportType.MARKTDE}-${mode}`;
48
- this.stateDir = `resources/${ExportType.MARKTDE}-state/${mode}`;
49
- this.imageUrl = `https://images.${this.domain}/carimage`;
50
- this.mappingSpreadsheetTitle = 'mobile_yescar';
51
- // MobileTransformer still builds its legacy `image` column; marktde.transformer replaces it with
52
- // the validated clean list.
53
- this.imageType = ImageType.default;
54
- this.imageConcurrency = positiveNumber(params.marktdeImageConcurrency, MARKTDE_DEFAULTS.imageConcurrency);
55
- this.maxImages = Math.floor(positiveNumber(params.marktdeMaxImages, 0));
56
- this.feedGuard = {
57
- stateDir: this.stateDir,
58
- minSourceItems: positiveNumber(params.feedGuardMinSourceItems, MARKTDE_DEFAULTS.minSourceItems),
59
- minPublishedItems: positiveNumber(params.feedGuardMinPublishedItems, MARKTDE_DEFAULTS.minPublishedItems),
60
- maxDropPercent: positiveNumber(params.feedGuardMaxDropPercent, MARKTDE_DEFAULTS.maxDropPercent),
61
- maxSourceAgeHours: positiveNumber(params.feedGuardMaxSourceAgeHours, MARKTDE_DEFAULTS.maxSourceAgeHours),
62
- maxStaleHours: positiveNumber(params.feedGuardMaxStaleHours, MARKTDE_DEFAULTS.maxStaleHours),
63
- acceptDrop: text(params.feedGuardAcceptDrop).toLowerCase() === 'true',
64
- };
65
- this.outputFiles = [
66
- `${this.pathesConfig.localConvertedDirectory}/api_export_cars_${ExportType.MARKTDE}.${FileType.CSV}`,
67
- `${this.pathesConfig.localConvertedDirectory}/api_export_cars_${ExportType.MARKTDE}.${FileType.ZIP}`,
68
- ];
69
- }
70
- }
1
+ import { toNumber } from 'lodash';
2
+
3
+ import { ExportType, FileType, ImageType } from '../enums';
4
+ import { Params } from '../interfaces/params.interface';
5
+ import { FeedGuardOptions } from '../services/feed-guard';
6
+ import { Config } from './config';
7
+
8
+ const text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '');
9
+
10
+ const positiveNumber = (value: unknown, fallback: number): number => {
11
+ const parsed = toNumber(text(value));
12
+ return text(value) && Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
13
+ };
14
+
15
+ // Guard defaults (same as AutoUncle). The DEV job exports one dealer via the Cronicle rules, so the
16
+ // published count is far below the base snapshot: minPublishedItems only forbids an empty file.
17
+ export const MARKTDE_DEFAULTS = {
18
+ imageConcurrency: 16,
19
+ minSourceItems: 1000,
20
+ minPublishedItems: 1,
21
+ maxDropPercent: 20,
22
+ maxSourceAgeHours: 26,
23
+ maxStaleHours: 48,
24
+ };
25
+
26
+ /**
27
+ * markt.de vehicle feed (ADV-328): the legacy mobile.de-style CSV (mapping sheet `mobile_yescar`,
28
+ * ACF-4370) plus a ZIP of it, with clean ADV-336 images. There is no current markt.de spec yet, so
29
+ * the format (columns, file names, no header row, `;` separator, UTF-8) stays as it was.
30
+ * Dry-run is the default: the files go to resources/marktde-dryrun, which no Cronicle step reads.
31
+ * Only marktdeDryRun=false writes resources/marktde-export (the directory `9c.1 … part 2` uploads
32
+ * from). Each mode has its own guard state.
33
+ */
34
+ export class MarktdeConfig extends Config {
35
+ dryRun: boolean;
36
+ imageConcurrency: number;
37
+ // 0 = no limit (no partner limit known)
38
+ maxImages: number;
39
+ stateDir: string;
40
+ feedGuard: FeedGuardOptions;
41
+
42
+ constructor(params: Params) {
43
+ super(params);
44
+ this.campaign = ExportType.MARKTDE;
45
+ this.dryRun = text(params.marktdeDryRun).toLowerCase() !== 'false';
46
+ const mode = this.dryRun ? 'dryrun' : 'export';
47
+ this.pathesConfig.localConvertedDirectory = `resources/${ExportType.MARKTDE}-${mode}`;
48
+ this.stateDir = `resources/${ExportType.MARKTDE}-state/${mode}`;
49
+ this.imageUrl = `https://images.${this.domain}/carimage`;
50
+ this.mappingSpreadsheetTitle = 'mobile_yescar';
51
+ // MobileTransformer still builds its legacy `image` column; marktde.transformer replaces it with
52
+ // the validated clean list.
53
+ this.imageType = ImageType.default;
54
+ this.imageConcurrency = positiveNumber(params.marktdeImageConcurrency, MARKTDE_DEFAULTS.imageConcurrency);
55
+ this.maxImages = Math.floor(positiveNumber(params.marktdeMaxImages, 0));
56
+ this.feedGuard = {
57
+ stateDir: this.stateDir,
58
+ minSourceItems: positiveNumber(params.feedGuardMinSourceItems, MARKTDE_DEFAULTS.minSourceItems),
59
+ minPublishedItems: positiveNumber(params.feedGuardMinPublishedItems, MARKTDE_DEFAULTS.minPublishedItems),
60
+ maxDropPercent: positiveNumber(params.feedGuardMaxDropPercent, MARKTDE_DEFAULTS.maxDropPercent),
61
+ maxSourceAgeHours: positiveNumber(params.feedGuardMaxSourceAgeHours, MARKTDE_DEFAULTS.maxSourceAgeHours),
62
+ maxStaleHours: positiveNumber(params.feedGuardMaxStaleHours, MARKTDE_DEFAULTS.maxStaleHours),
63
+ acceptDrop: text(params.feedGuardAcceptDrop).toLowerCase() === 'true',
64
+ };
65
+ this.outputFiles = [
66
+ `${this.pathesConfig.localConvertedDirectory}/api_export_cars_${ExportType.MARKTDE}.${FileType.CSV}`,
67
+ `${this.pathesConfig.localConvertedDirectory}/api_export_cars_${ExportType.MARKTDE}.${FileType.ZIP}`,
68
+ ];
69
+ }
70
+ }