Aller au contenu

Category routing modularization

plugins/ccx-feeds-industrial/includes/mapping/common/category-routing.php owns the shared category/keyword/title based vertical routing used both during row classification and again during final-row normalization.

The migration goal is to keep the public entrypoint ccx_feeds_apply_category_vertical_routing(array $row): array and its first-match-wins semantics stable while moving coherent rule families into includes/mapping/common/category-routing/ modules.

Documentation discipline

Each structural step updates code, Runtime contracts, and this document together. Prepared modules must not be documented as active runtime delegation until their call site has actually been replaced and validated.

Current runtime authority

category-routing.php remains the public orchestration entrypoint and delegates the printer/consumables family to category-routing/20-printer-consumables.php, the gaming family to category-routing/30-gaming.php, the core-device family to category-routing/40-core-devices.php, the storage/network/peripheral family to category-routing/45-storage-peripherals.php, the TV/audio family to category-routing/50-tv-audio.php, the camera/photo family to category-routing/60-camera.php, the smart-home / connected-device family to category-routing/70-smart-home.php, and the historical home-appliance tail to category-routing/80-home-appliances.php.

The former inline phone-accessory and tablet-accessory routing has been intentionally removed because these product families are no longer part of the supported runtime classification surface. The related $phone_ctx and $tablet_ctx detection is also gone. Runtime now proceeds directly from printer/consumables delegation to gaming delegation.

The entrypoint is called from:

  • mapping/row-to-item/40-classification.php during classifier construction;
  • mapping/common/row-final/20-normalization.php during final-row normalization.

Because the function returns immediately on the first matching rule, ordering remains part of the compatibility contract.

Wired boundaries

20-printer-consumables.php

Status: wired and validated.

The module owns the historical three-rule prefix and is loaded by category-routing.php through:

require_once __DIR__ . '/category-routing/20-printer-consumables.php';

Runtime delegates immediately after shared context construction through:

ccx_feeds_category_routing_printer_consumables_v1($row, $hay)

and returns immediately when the module returns a routed row. The historical inline category_dishwasher_consumable, category_printer_consumable, and category_printer branches have been removed from category-routing.php.

The module preserves the exact first-match order:

  1. dishwasher / category_dishwasher_consumable with accessory dishwasher_consumable;
  2. pc_peripheral / category_printer_consumable with accessory printer_consumable;
  3. pc_peripheral / category_printer with accessory printer.

The dishwasher-consumable rule still executes before the much later delegated generic dishwasher appliance routing in 80-home-appliances.php, preserving the historical consumable-versus-appliance distinction.

tests/Runtime/CategoryRoutingPrinterConsumablesBoundaryContractTest.php locks module load, delegation call, null-guard return, absence of the three exact quoted reason tokens from category-routing.php, the module rule order, absence of the removed mobile-accessory reason tokens, and runtime sequencing printer/consumables delegation -> gaming delegation.

tests/Runtime/CategoryRoutingHomeAppliancesBoundaryContractTest.php uses the printer/consumables delegation itself as the earlier precedence marker instead of the removed inline category_dishwasher_consumable token.

30-gaming.php

Status: wired and validated.

The module owns the contiguous historical gaming block and is loaded by category-routing.php through:

require_once __DIR__ . '/category-routing/30-gaming.php';

Runtime delegates immediately after printer/consumables routing and before core-device routing through:

ccx_feeds_category_routing_gaming_v1($row, $hay, $platform, $console_ctx)

and returns immediately when the module returns a routed row. The historical inline gaming reason branches have been removed from category-routing.php.

The module preserves the exact first-match order:

  1. gaming_accessory / category_gaming_streaming with accessory streaming;
  2. gaming_accessory / category_vr with accessory vr;
  3. gaming_accessory / category_console_accessory with the existing accessory subtype selection (manette, simulation, casque_gaming, chargeur, battery, support_kit, protection_ecran, carrying_case, fallback accessoire_gaming);
  4. console / category_video_game with accessory video_game;
  5. console / category_console with accessory clearing.

The module takes the already normalized $hay, the platform value computed by ccx_feeds_category_routing_platform_v1(), and the historical $console_ctx boolean as explicit inputs. Matching branches reuse ccx_feeds_category_routing_set_v1() so platform propagation, confidence/reason codes, accessory handling, and console semantics remain unchanged.

The module returns null when no gaming rule matches so the parent router continues to core-device and later rule families.

tests/Runtime/CategoryRoutingGamingBoundaryContractTest.php locks the delegated runtime boundary: module load, delegation call, absence of the five exact quoted gaming reason tokens from category-routing.php, absence of the removed mobile-accessory reason tokens, and the ordering printer/consumables delegation -> gaming delegation -> core-device delegation.

40-core-devices.php

Status: wired and validated.

The module owns the contiguous historical core-device block and is loaded by category-routing.php through:

require_once __DIR__ . '/category-routing/40-core-devices.php';

Runtime delegates after gaming routing and before the storage/network/peripheral delegation through:

ccx_feeds_category_routing_core_devices_v1($row, $hay)

and returns immediately when the module returns a routed row. The seven historical inline core-device reason branches have been removed from category-routing.php.

The module preserves the exact first-match order:

  1. smartwatch / category_smartwatch, clearing accessory state;
  2. smartphone / category_smartphone, clearing accessory state;
  3. tablet / category_tablet, clearing accessory state;
  4. laptop / category_laptop, clearing accessory state;
  5. desktop_pc / category_desktop_pc, clearing accessory state;
  6. tv / category_tv_explicit_signal, clearing accessory state;
  7. monitor / category_monitor, clearing accessory state.

The explicit-TV rule intentionally remains before the monitor rule inside the module so products carrying both explicit TV and QHD/monitor signals continue to route to tv. tests/Runtime/CategoryVerticalRoutingTest.php locks this behavior.

The module takes the already normalized $hay as its only routing context besides $row. Every matching branch reuses ccx_feeds_category_routing_set_v1() and returns null when no core-device rule matches.

The delegated boundary preserves the runtime order gaming delegation -> core-device delegation -> storage/network/peripheral delegation -> TV/audio delegation. tests/Runtime/CategoryRoutingCoreDevicesBoundaryContractTest.php locks module load, delegation call, absence of the seven exact quoted core-device reasons from category-routing.php, explicit-TV-before-monitor ordering inside the module, and this runtime boundary.

45-storage-peripherals.php

Status: wired and validated.

The module owns the contiguous historical storage/network/peripheral block and is loaded by category-routing.php through:

require_once __DIR__ . '/category-routing/45-storage-peripherals.php';

Runtime delegates after core-device routing and before TV/audio routing through:

ccx_feeds_category_routing_storage_peripherals_v1($row, $hay)

and returns immediately when the module returns a routed row. The five historical inline storage/network/peripheral reason branches have been removed from category-routing.php.

It preserves the exact first-match order:

  1. memory_card / category_memory_card with accessory memory_card;
  2. usb_storage / category_usb_storage with accessory usb_storage;
  3. pc_storage / category_storage with accessory storage;
  4. pc_peripheral / category_network with accessory network;
  5. pc_peripheral / category_pc_peripheral with dynamic accessory subtype selection.

The category_pc_peripheral rule preserves the historical subtype order and fallback exactly: keyboard, mouse, webcam, microphone, headset, hub, laptop_stand, dock, fallback misc_pc_accessory.

The module takes the already normalized $hay as its only routing context besides $row. Every matching branch reuses ccx_feeds_category_routing_set_v1() and returns null when no storage/network/peripheral rule matches.

tests/Runtime/CategoryRoutingStoragePeripheralsBoundaryContractTest.php locks the delegated runtime boundary: module load, delegation call, absence of the five exact quoted storage/network/peripheral reason tokens from category-routing.php, module reason/subtype order, and the runtime sequence core-device delegation -> storage/network/peripheral delegation -> TV/audio delegation.

50-tv-audio.php

Status: wired and validated.

The module owns the contiguous historical TV/audio block and is loaded by category-routing.php through:

require_once __DIR__ . '/category-routing/50-tv-audio.php';

Runtime delegates after storage/network/peripheral routing and before camera/photo routing through:

ccx_feeds_category_routing_tv_audio_v1($row, $hay)

and returns immediately when the module returns a routed row. The old inline TV/audio reason branches have been removed from category-routing.php.

The module preserves the exact first-match order:

  1. tv / category_tv_accessory with accessory tv_accessory;
  2. projector / category_projector with accessory clearing;
  3. tv / category_tv with accessory clearing;
  4. soundbar / category_soundbar with accessory clearing;
  5. wireless_earbuds / category_earbuds with accessory clearing;
  6. headphone / category_headphone with accessory clearing and the existing gaming exclusion (gamer, gaming, ps5, ps4, xbox, switch);
  7. speaker / category_audio_accessory with accessory audio_accessory;
  8. speaker / category_speaker with accessory clearing.

The module takes the already normalized $hay as its only routing context besides $row. Every matching branch reuses ccx_feeds_category_routing_set_v1() so vertical/spec assignment, confidence/reason codes, accessory handling, and platform clearing remain unchanged.

The earlier explicit-TV rule lives inside 40-core-devices.php, whose delegation still executes before storage/network/peripheral and TV/audio delegation. This preserves the special handling for explicit TV signals such as smart tv, the moving style, and matching Samsung model identifiers.

The module returns null when no TV/audio rule matches so the parent router continues to camera/photo and later rule families.

tests/Runtime/CategoryRoutingTvAudioBoundaryContractTest.php locks the delegated runtime boundary: module load, delegation call, absence of the historical exact quoted TV/audio reason tokens from category-routing.php, and preservation of core-device -> storage/network/peripheral -> TV/audio sequencing.

60-camera.php

Status: wired and validated.

The module owns the contiguous historical camera/photo block and is loaded by category-routing.php through:

require_once __DIR__ . '/category-routing/60-camera.php';

Runtime delegates after TV/audio routing and before the smart-home delegation through:

ccx_feeds_category_routing_camera_v1($row, $hay)

and returns immediately when the module returns a routed row. The old inline camera/photo reason branches have been removed from category-routing.php.

The module preserves the exact first-match order:

  1. pc_peripheral / category_camera_lens with accessory camera_lens;
  2. pc_peripheral / category_camera_accessory with accessory camera_accessory;
  3. smart_home / category_smart_home_security with accessory security;
  4. pc_peripheral / category_camera with accessory camera.

The category_smart_home_security rule intentionally remains between camera accessories and the generic camera rule. This historical ordering remains part of the compatibility contract inside 60-camera.php.

The module takes the already normalized $hay as its only routing context besides $row. Every matching branch reuses ccx_feeds_category_routing_set_v1(), preserving vertical/spec assignment, confidence/reason codes, accessory handling, and platform clearing.

The module returns null when no camera rule matches so the parent router continues to the smart-home delegation and later rule families.

tests/Runtime/CategoryRoutingCameraBoundaryContractTest.php locks the delegated runtime boundary: module load, delegation call, absence of the four exact quoted camera reason tokens from category-routing.php, preservation of the module's historical reason order, and the runtime position TV/audio delegation -> camera delegation -> smart-home delegation.

70-smart-home.php

Status: wired and validated.

The module owns the contiguous historical smart-home / connected-device block and is loaded by category-routing.php through:

require_once __DIR__ . '/category-routing/70-smart-home.php';

Runtime delegates after camera routing and before the home-appliance tail through:

ccx_feeds_category_routing_smart_home_v1($row, $hay)

and returns immediately when the module returns a routed row. The old inline smart-home reason branches have been removed from category-routing.php.

It preserves the exact first-match order:

  1. smart_home / category_smart_speaker with accessory smart_speaker;
  2. smart_home / category_smart_light with accessory eclairage_connecte;
  3. smart_home / category_smart_home with accessory smart_home_accessory;
  4. smart_home / category_drone, dynamically choosing accessory drone_accessory for accessoire drone, otherwise drone;
  5. smart_home / category_electric_mobility with accessory electric_mobility;
  6. smartwatch / category_smart_ring with accessory smart_ring;
  7. smart_home / category_tracker with accessory tracker;
  8. smart_home / category_power_station with accessory power_station.

The module takes the already normalized $hay as its only routing context besides $row. Every matching branch reuses ccx_feeds_category_routing_set_v1(), preserving vertical/spec assignment, confidence/reason codes, accessory handling, and platform clearing.

The category_drone rule intentionally preserves its historical dynamic accessory mapping, and category_smart_ring intentionally routes to the smartwatch vertical rather than smart_home.

The module returns null when no smart-home rule matches so the parent router continues to the delegated home-appliance tail.

tests/Runtime/CategoryRoutingSmartHomeBoundaryContractTest.php locks the delegated runtime boundary: module load, delegation call, absence of the eight exact quoted smart-home reason tokens from category-routing.php, dynamic drone accessory behavior, the smartwatch smart-ring mapping, and the runtime ordering camera delegation -> smart-home delegation -> home-appliance delegation.

80-home-appliances.php

Status: wired and validated.

The module owns the contiguous historical home-appliance tail and is loaded by category-routing.php through:

require_once __DIR__ . '/category-routing/80-home-appliances.php';

At the historical tail position, runtime delegates through ccx_feeds_category_routing_home_appliances_v1($row, $hay) and returns immediately when the module returns a routed row. The old inline home-appliance branches have been removed from category-routing.php.

The module preserves the exact first-match order:

  1. fridge / category_fridge;
  2. freezer / category_freezer;
  3. washing_machine / category_washing_machine;
  4. dryer / category_dryer;
  5. dishwasher / category_dishwasher;
  6. oven / category_oven;
  7. cooktop / category_cooktop;
  8. microwave / category_microwave;
  9. home_appliance / category_home_appliance;
  10. kitchen_appliance / category_kitchen_appliance;
  11. vacuum / category_vacuum;
  12. home_cleaning / category_home_care;
  13. air_treatment / category_air_treatment;
  14. fan / category_fan;
  15. heater / category_heater.

Every matching branch delegates to the existing shared ccx_feeds_category_routing_set_v1() helper with clear_accessory=true, preserving current vertical/spec assignment, confidence/reason-code behavior, accessory clearing, and platform clearing.

The module returns null when no home-appliance rule matches so the parent router continues its historical flow unchanged.

The earlier category_dishwasher_consumable rule now lives inside 20-printer-consumables.php and still runs before the delegated generic dishwasher appliance rule. This preserves the current distinction between dishwasher consumables and dishwasher products.

tests/Runtime/CategoryRoutingHomeAppliancesBoundaryContractTest.php locks the delegated runtime boundary and the precedence printer/consumables delegation -> home-appliance delegation.

Removed runtime surface

Phone and tablet accessories

Status: removed intentionally.

The former inline rules category_phone_accessory and category_tablet_accessory are no longer part of runtime category routing. Their supporting $phone_ctx and $tablet_ctx detection was removed at the same time. No replacement module is provided because these product families are no longer supported by this routing layer.

The runtime contracts for printer/consumables and gaming explicitly lock the absence of both reason tokens and the direct sequencing from printer/consumables delegation to gaming delegation.

Validation checkpoint

Latest validation after removing obsolete mobile-accessory routing:

  • CategoryRoutingGamingBoundaryContractTest: 2 tests, 30 assertions;
  • CategoryRoutingPrinterConsumablesBoundaryContractTest: 2 tests, 25 assertions;
  • CategoryVerticalRoutingTest: 2 tests, 7 assertions;
  • full PHPUnit suite: 787 tests, 5514 assertions;
  • 3 PHPUnit warnings and no failures.

The next step is a repository audit on refactor/category-routing-remove-mobile-accessories. If the audit remains green, the branch is ready to integrate into refactor/frontend-product-models-assets.