@bytebrand/export-cars 1.26.46 → 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 (50) 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/mapping.service.d.ts +2 -0
  13. package/dist/services/mapping.service.js +33 -13
  14. package/dist/services/mapping.service.js.map +1 -1
  15. package/dist/services/ooyyo-feed-run.d.ts +81 -0
  16. package/dist/services/ooyyo-feed-run.js +183 -0
  17. package/dist/services/ooyyo-feed-run.js.map +1 -0
  18. package/dist/services/ooyyo-feed-verify.d.ts +13 -0
  19. package/dist/services/ooyyo-feed-verify.js +104 -0
  20. package/dist/services/ooyyo-feed-verify.js.map +1 -0
  21. package/dist/transformers/ooyyo.transformer.d.ts +51 -11
  22. package/dist/transformers/ooyyo.transformer.js +268 -95
  23. package/dist/transformers/ooyyo.transformer.js.map +1 -1
  24. package/dist/tsconfig.build.tsbuildinfo +1 -1
  25. package/docs/marktde/AUDIT.md +1 -1
  26. package/docs/marktde/RUNBOOK.md +3 -3
  27. package/docs/marktde/TRACKING.md +8 -2
  28. package/docs/meinestadt/PARTNER-QUESTIONS.md +17 -0
  29. package/docs/meinestadt/TRACKING.md +107 -0
  30. package/docs/ooyyo/AUDIT.md +81 -0
  31. package/docs/ooyyo/PARTNER-QUESTIONS.md +32 -0
  32. package/docs/ooyyo/RUNBOOK.md +67 -0
  33. package/docs/ooyyo/TRACKING.md +54 -0
  34. package/package.json +3 -2
  35. package/src/configs/ooyyo.config.ts +65 -15
  36. package/src/constants/fb.constants.ts +34 -34
  37. package/src/exporters/ooyyo.exporter.ts +75 -48
  38. package/src/formats/ooyyo-xml.builder.ts +87 -48
  39. package/src/interfaces/export-item.interface.ts +43 -2
  40. package/src/interfaces/params.interface.ts +5 -0
  41. package/src/services/mapping.service.ts +54 -13
  42. package/src/services/ooyyo-feed-run.ts +265 -0
  43. package/src/services/ooyyo-feed-verify.ts +131 -0
  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 -0
  49. package/test/ooyyo-feed-run.test.ts +293 -0
  50. package/test/ooyyo-mapping.test.ts +276 -0
@@ -13,7 +13,7 @@ letters are code evidence, not a contract.
13
13
  | Row format | `MobileTransformer` + Google sheet `147qz9sT…` tab `mobile_yescar` (165 rules, ACF-4370 reused the YesCar / mobile.de CSV). `;` separated, every cell quoted, no header row, UTF-8, 517–528 columns per row |
14
14
  | Files | `api_export_cars_marktde.csv` + `.zip` (was `resources/marktde-export/`) |
15
15
  | DEV Cronicle | `9c.0 Export markt.de - part 1` `ekpy4pqg21u` (rule `metaData.ownerId:xo1ag9JooQebXhnfSclw3NhZyG63`) → `9c.1 … part 2` `ekyvib2rm06` (plain `ftp -pinv 185.15.211.90`, uploads `develop_marktde.zip/.csv`, credentials in the script) → `9d.0 facebook shop`. Triggered by the chain after `9b.1 Export Focus`. No run in the job history |
16
- | DEV runtime defects found | (a) the Google Sheets API key in `9c.0` is **expired** — the mapping sheet cannot load, every row-building run fails; (b) the filtered dealer has 0 cars in the DEV snapshot; (c) the host has no `zip` binary — the legacy run wrote the CSV, failed the ZIP and still reported `code 0`; (d) `9c.1` uploads names the exporter never writes; (e) 185.15.211.90 is the legacy Jelastic FTP (port 21 closed, see ADV-319 4.1) |
16
+ | DEV runtime defects found | (a) the Google Sheets API key in `9c.0` is **expired** — the mapping sheet could not load (fixed in 1.26.47: public CSV, no key); (b) the filtered dealer has 0 cars in the DEV snapshot; (c) the host has no `zip` binary — the legacy run wrote the CSV, failed the ZIP and still reported `code 0`; (d) `9c.1` uploads names the exporter never writes; (e) 185.15.211.90 is the legacy Jelastic FTP (port 21 closed, see ADV-319 4.1) |
17
17
  | PROD | not checked (DEV-only task). Old contract / account belong to the former auto.de Media GmbH — not transferable (Daniel, 16.10) |
18
18
 
19
19
  ## 2. Source → target matrix (columns the feed relies on)
@@ -4,7 +4,7 @@
4
4
 
5
5
  | Param | Default | Meaning |
6
6
  |---|---|---|
7
- | `apikey` | — | Google Sheets API key for the mapping sheet `mobile_yescar`. **DEV 9c.0: expired (12.11.2026)** |
7
+ | `apikey` | — | no longer used for the mapping (since 1.26.47 the tab `mobile_yescar` is read from the sheet's public CSV, no key); the sheet must stay readable by everyone with the link. The DEV 9c.0 key is expired (12.11.2026) |
8
8
  | `String` / `Number` / `Date` | — | Cronicle filter rules (DEV 9c.0: one dealer, 0 cars today) |
9
9
  | `marktdeDryRun` | dry-run | only the exact text `false` makes a live run |
10
10
  | `marktdeMaxImages` | no cut | cut the image list (no markt.de limit known) |
@@ -42,7 +42,7 @@ the API with `chain: ""` (see below) or from the UI with the chain removed for t
42
42
  D=/home/deploy/export-cars-<ver>-dryrun/<name> # job.json (chmod 600) + credentials + resources/cars-export symlinks
43
43
  NODE_PATH=<pkg>/dist:/opt/cronicle/node_modules node <pkg>/dist/main.js < job.json > run.log
44
44
  ```
45
- While the event's Sheets key is expired: `node harness.js <pkg> < job.json` — the harness runs the same
45
+ Before 1.26.47 (Sheets API key expired): `node harness.js <pkg> < job.json` — the harness runs the same
46
46
  `main.js` but loads the mapping rules from the public CSV of the same sheet tab (gviz) with the same
47
47
  post-processing as `MappingService`.
48
48
 
@@ -50,7 +50,7 @@ post-processing as `MappingService`.
50
50
 
51
51
  1. Signed contract / account for auto.de GmbH, current markt.de feed spec + test importer
52
52
  (`PARTNER-QUESTIONS.md`); written acceptance of a test import of the 20-row sample.
53
- 2. New Sheets API key in the 9c.0 params (or move the mapping out of Google Sheets).
53
+ 2. Keep the mapping sheet shared with everyone who has the link (1.26.47 reads its public CSV; a private sheet fails the run).
54
54
  3. Dealer scope (the rule in 9c.0) and `feedGuardMinPublishedItems` from a PROD dry-run.
55
55
  4. Replace `9c.1`: transport and file names from the spec, credentials out of the script; never the
56
56
  legacy Jelastic host. Only then `marktdeDryRun=false`.
@@ -3,6 +3,8 @@
3
3
  Status 12.11.2026: DEV only. Implemented: the clickout link (export-cars 1.26.45) and the reporting
4
4
  channel `CLASSIFIEDS_MARKTDE` (web-funnel-tracking-health 1.5.6). Not implemented, by design, until
5
5
  markt.de confirms a path in writing: partner click ids, hosted-enquiry ingestion, cost import.
6
+ 13.11.2026: DEV end-to-end lead test (link → VDP → enquiry → Odoo REQ → session / lead views), see QA.
7
+ Customer-SQL proposal (not applied): `release-runbooks/adv-328/ADV-330-customer-sql-proposal.sql`.
6
8
 
7
9
  ## Evidence
8
10
 
@@ -23,7 +25,8 @@ markt.de listing ──(structured field OM)──► https://www.auto.de/search
23
25
  ?source=marktde&medium=listing&campaign=listing&utm_source=marktde&utm_medium=listing&utm_campaign=listing
24
26
  → fe-main marketing cookie (ADV-260) → visitor session, landing_url keeps the raw query
25
27
  → views: reporting_channel CLASSIFIEDS_MARKTDE, family Classifieds, detail markt.de, publisher MARKTDE
26
- → form → REQ → Odoo crm_source "marktde" (lower-case source, no SQL change needed)
28
+ → form → REQ → Odoo crm_source "marktde" (lower-case source, no SQL change needed; VDP enquiries
29
+ need the user-actions-contact ADV-327 cookie fallback — DEV since 13.11, not on PROD)
27
30
  → rpt_episode_touchpoint → canonical 90-day episode (original episode_start_date) → sale / CM
28
31
  ```
29
32
 
@@ -63,7 +66,10 @@ stay unchanged (live report layout changes need approval).
63
66
  | `utm_source=markt.de`, other medium (`cpc`) | CLASSIFIEDS_MARKTDE, not PAID_OTHER | unit test ✓ |
64
67
  | Google click id + markt.de utm | click id wins (PAID_SEARCH_GOOGLE) | rule order test ✓ |
65
68
  | no consent / revoked | no marketing cookie → lead without source (unchanged behaviour) | not run (needs browser) |
66
- | lead → REQ → episode → sale | crm_source marktde, episode keeps original start | not run (no markt.de traffic) |
69
+ | VDP enquiry → REQ → Odoo source (cookie only, no body marketing) | utm source from the marketing cookie | ✅ 13.11 DEV: REQ584786 `marktde / listing / listing`, REQ584785 `classifieds / listing / marktde` (raw, legacy link) |
70
+ | same visits in the views | session CLASSIFIEDS_MARKTDE / Classifieds / markt.de / MARKTDE, VISITOR, lead_persisted; lead view REQ joined, crm_status CONFIRMED | ✅ 13.11 DEV (`testmk3-new-1794562402`, `testmk3-legacy-1794562402`) |
71
+ | producer label in the lead view (`resolved_channel`) | not the reporting channel (message-schemes knows no partner classifieds; REFERRAL with the markt.de referrer) — report by the session's `reporting_channel` | documented, unchanged (same for meinestadt / AutoUncle) |
72
+ | episode → sale / CM | crm_source marktde → `source_resolved_v1 = marktde`; legacy link needs the proposed D-rule; episode keeps original start | not run: prod customer SQL; DEV `crm_source_resolved_v1` empty (DEV CRM export stale since 29.10) |
67
73
  | partner id, hosted leads, invoice import, credits, double import | — | blocked on partner spec |
68
74
 
69
75
  Launch blockers: contract (auto.de GmbH), clickout permission, spec, legal / data QA, feed + image tests.
@@ -52,3 +52,20 @@ only — we need the CURRENT automotive specification in writing.
52
52
  20. Update frequency and timing; full file each time or deltas? How do you remove listings that are no longer
53
53
  in the file (sold / withdrawn), and how fast?
54
54
  21. Can we run a test import first and get the parsed item count and rejection report?
55
+
56
+ ## Measurement and reporting (ADV-327)
57
+
58
+ 22. Daily click report: please send the latest original `meinestadt-daily_reporting_AUTODE_<date>.csv` and its
59
+ field definitions — separator, encoding, decimal format, timezone of the report date, currency, net/gross.
60
+ 23. Which clicks are billable vs non-billable (bots, duplicates, invalid clicks)? How are corrections, credit
61
+ notes and late revisions delivered (new file for the same day, separate file, invoice only)?
62
+ 24. Report delivery: e-mail attachment, SFTP, URL or API? Recipient / account? What happens on a day without
63
+ clicks (empty file or no file)?
64
+ 25. Is there a budget cap per day / month, and does the report show when it was reached?
65
+ 26. Do you append your own click or listing identifier to the `listingUrl` (name and format)? If not, we
66
+ reconcile in aggregate per day only.
67
+ 27. Do you offer or require a conversion pixel, postback or server-to-server event? We send nothing back today
68
+ and would only add a partner-confirmed event with a consent basis and a DPA.
69
+ 28. Do you redirect through your own tracking host before our page, and does the redirect keep all query
70
+ parameters?
71
+ 29. Data retention and consent requirements on your side for the click data in the report.
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebrand/export-cars",
3
- "version": "1.26.46",
3
+ "version": "1.26.48",
4
4
  "description": "",
5
5
  "author": "bytebrand",
6
6
  "license": "UNLICENSED",
@@ -11,7 +11,7 @@
11
11
  "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
12
12
  "lint": "eslint -c .eslintrc.js --ext .ts src/",
13
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/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"
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
15
  },
16
16
  "dependencies": {
17
17
  "@bytebrand/autode-request": "^1.19.2",
@@ -34,6 +34,7 @@
34
34
  "bottleneck": "^2.19.5",
35
35
  "cache-manager": "^5.2.4",
36
36
  "csv-write-stream": "^2.0.0",
37
+ "fast-xml-parser": "4.2.5",
37
38
  "google-spreadsheet": "^3.3.0",
38
39
  "lodash": "^4.17.21",
39
40
  "moment": "^2.29.4",
@@ -1,15 +1,65 @@
1
- import { Params } from '../interfaces/params.interface';
2
- import { Config } from './config';
3
- import { ExportType, FileType, ImageType } from '../enums';
4
-
5
- export class OoyyoConfig extends Config {
6
- constructor(params: Params) {
7
- super(params);
8
- this.pathesConfig.localConvertedDirectory = `resources/${ExportType.OOYYO}-export`;
9
- this.imageType = ImageType.default;
10
- this.outputFiles = [
11
- `${this.pathesConfig.localConvertedDirectory}/api_export_cars_${ExportType.OOYYO}.${FileType.XML}`,
12
- `${this.pathesConfig.localConvertedDirectory}/autode-${ExportType.OOYYO}.${FileType.TAR_GZ}`,
13
- ];
14
- }
15
- }
1
+ import { toNumber } from 'lodash';
2
+
3
+ import { ExportType, 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 / meinestadt / markt.de). minPublishedItems only forbids an
16
+ // empty file; before a live run set it from a PROD dry-run (see docs/ooyyo/RUNBOOK.md).
17
+ export const OOYYO_DEFAULTS = {
18
+ imageConcurrency: 16,
19
+ minSourceItems: 1000,
20
+ minPublishedItems: 1,
21
+ maxDropPercent: 20,
22
+ maxSourceAgeHours: 26,
23
+ maxStaleHours: 48,
24
+ };
25
+
26
+ /**
27
+ * OOYYO vehicle XML (ADV-331): one full-inventory XML + the same file gzipped (`.xml.gz`), the two
28
+ * forms OOYYO accepts from a fixed HTTPS URL (partner, 16.10.2026; FTP not recommended).
29
+ * Dry-run is the default: the files go to resources/ooyyo-dryrun, which no Cronicle step reads and
30
+ * nothing serves. Only ooyyoDryRun=false writes resources/ooyyo-export. The legacy
31
+ * autode-ooyyo.tar.gz (FTP package) is no longer produced. Each mode has its own guard state.
32
+ */
33
+ export class OoyyoConfig extends Config {
34
+ dryRun: boolean;
35
+ imageConcurrency: number;
36
+ // 0 = no limit (OOYYO accepts all pictures; 20 is only a ranking recommendation)
37
+ maxImages: number;
38
+ stateDir: string;
39
+ feedGuard: FeedGuardOptions;
40
+
41
+ constructor(params: Params) {
42
+ super(params);
43
+ this.campaign = ExportType.OOYYO;
44
+ this.dryRun = text(params.ooyyoDryRun).toLowerCase() !== 'false';
45
+ const mode = this.dryRun ? 'dryrun' : 'export';
46
+ this.pathesConfig.localConvertedDirectory = `resources/${ExportType.OOYYO}-${mode}`;
47
+ this.stateDir = `resources/${ExportType.OOYYO}-state/${mode}`;
48
+ this.imageType = ImageType.clean;
49
+ this.imageConcurrency = positiveNumber(params.ooyyoImageConcurrency, OOYYO_DEFAULTS.imageConcurrency);
50
+ this.maxImages = Math.floor(positiveNumber(params.ooyyoMaxImages, 0));
51
+ this.feedGuard = {
52
+ stateDir: this.stateDir,
53
+ minSourceItems: positiveNumber(params.feedGuardMinSourceItems, OOYYO_DEFAULTS.minSourceItems),
54
+ minPublishedItems: positiveNumber(params.feedGuardMinPublishedItems, OOYYO_DEFAULTS.minPublishedItems),
55
+ maxDropPercent: positiveNumber(params.feedGuardMaxDropPercent, OOYYO_DEFAULTS.maxDropPercent),
56
+ maxSourceAgeHours: positiveNumber(params.feedGuardMaxSourceAgeHours, OOYYO_DEFAULTS.maxSourceAgeHours),
57
+ maxStaleHours: positiveNumber(params.feedGuardMaxStaleHours, OOYYO_DEFAULTS.maxStaleHours),
58
+ acceptDrop: text(params.feedGuardAcceptDrop).toLowerCase() === 'true',
59
+ };
60
+ this.outputFiles = [
61
+ `${this.pathesConfig.localConvertedDirectory}/${ExportType.OOYYO}.xml`,
62
+ `${this.pathesConfig.localConvertedDirectory}/${ExportType.OOYYO}.xml.gz`,
63
+ ];
64
+ }
65
+ }
@@ -1,34 +1,34 @@
1
- export const FB_MAX_TRIM_LENGTH = 50;
2
- export const FB_MAX_IMAGE_COUNT_FOR_DEAFULT = 47;
3
- export const FB_MIN_MILEAGES = 900;
4
- export const DEFAULT_ZIP = '06796';
5
- export const DEFAULT_CITY = 'Sandersdorf-Brehna';
6
- export const DEFAULT_ADDR1 = 'Max-Planck-Str. 19';
7
- export const TIMESTAMP_FIRST_YEAR = 1970;
8
- export const META_PUSH_PRIORITY_STANDARD = 'standard';
9
- export const META_PUSH_PRIORITY_ALLOWED_VALUES = ['dealer_priority_1', 'dealer_priority_2'];
10
-
11
- // MarketingImageExportConfig channel code for the Meta AIA overlay images (image-generator's
12
- // MetaAiaImagesService.CHANNEL_CODE) — distinct from this feed's now-disabled legacy "fb" config.
13
- export const META_AIA_CHANNEL_CODE = 'meta_aia';
14
- // Only the financing variant exists so far; leasing/other modes will be added once image-generator
15
- // renders them too (see MetaAiaImagesService's MetaAiaMode).
16
- export const META_AIA_MODE_FINANCING = 'financing';
17
-
18
- // Values must match Meta's Automotive Model Ads offer_type enum exactly (lease/finance/cash) —
19
- // the old 'financing'/'leasing'/'buy' values are not recognized by the AMA Vehicle Offers Feed.
20
- export enum OfferType {
21
- financing = 'finance',
22
- leasing = 'lease',
23
- buy = 'cash',
24
- }
25
-
26
- export const AMOUNT_QUALIFIER_APR = 'APR';
27
- export const TERM_QUALIFIER_MONTHS = 'months';
28
-
29
- export const OFFER_DISCLAIMER = 'Für Ihre Finanzierungswünsche stellen wir eine Bonitätsanfrage. Bonität ' +
30
- 'vorausgesetzt, ist dies ein repräsentatives Berechnungsbeispiel gem. der Angaben, welches 2/3 aller Kunden, ' +
31
- 'im Sinne des § 17a Abs. 4 PAngV, erhalten. Dieses freibleibende Angebot der Openbank Deutschland AG, ' +
32
- 'Santander-Platz 1, 41061 Mönchengladbach wird vermittelt durch die auto.de GmbH, Max-Planck-Str. 19, 06796 ' +
33
- 'Sandersdorf-Brehna, die als ungebundener Vermittler nicht beratend tätig ist. Irrtümer vorbehalten. Preise ' +
34
- 'ggf. inkl. MwSt.';
1
+ export const FB_MAX_TRIM_LENGTH = 50;
2
+ export const FB_MAX_IMAGE_COUNT_FOR_DEAFULT = 47;
3
+ export const FB_MIN_MILEAGES = 900;
4
+ export const DEFAULT_ZIP = '06796';
5
+ export const DEFAULT_CITY = 'Sandersdorf-Brehna';
6
+ export const DEFAULT_ADDR1 = 'Max-Planck-Str. 19';
7
+ export const TIMESTAMP_FIRST_YEAR = 1970;
8
+ export const META_PUSH_PRIORITY_STANDARD = 'standard';
9
+ export const META_PUSH_PRIORITY_ALLOWED_VALUES = ['dealer_priority_1', 'dealer_priority_2'];
10
+
11
+ // MarketingImageExportConfig channel code for the Meta AIA overlay images (image-generator's
12
+ // MetaAiaImagesService.CHANNEL_CODE) — distinct from this feed's now-disabled legacy "fb" config.
13
+ export const META_AIA_CHANNEL_CODE = 'meta_aia';
14
+ // Only the financing variant exists so far; leasing/other modes will be added once image-generator
15
+ // renders them too (see MetaAiaImagesService's MetaAiaMode).
16
+ export const META_AIA_MODE_FINANCING = 'financing';
17
+
18
+ // Values must match Meta's Automotive Model Ads offer_type enum exactly (lease/finance/cash) —
19
+ // the old 'financing'/'leasing'/'buy' values are not recognized by the AMA Vehicle Offers Feed.
20
+ export enum OfferType {
21
+ financing = 'finance',
22
+ leasing = 'lease',
23
+ buy = 'cash',
24
+ }
25
+
26
+ export const AMOUNT_QUALIFIER_APR = 'APR';
27
+ export const TERM_QUALIFIER_MONTHS = 'months';
28
+
29
+ export const OFFER_DISCLAIMER = 'Für Ihre Finanzierungswünsche stellen wir eine Bonitätsanfrage. Bonität ' +
30
+ 'vorausgesetzt, ist dies ein repräsentatives Berechnungsbeispiel gem. der Angaben, welches 2/3 aller Kunden, ' +
31
+ 'im Sinne des § 17a Abs. 4 PAngV, erhalten. Dieses freibleibende Angebot der Openbank Deutschland AG, ' +
32
+ 'Santander-Platz 1, 41061 Mönchengladbach wird vermittelt durch die auto.de GmbH, Max-Planck-Str. 19, 06796 ' +
33
+ 'Sandersdorf-Brehna, die als ungebundener Vermittler nicht beratend tätig ist. Irrtümer vorbehalten. Preise ' +
34
+ 'ggf. inkl. MwSt.';