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.phpduring classifier construction;mapping/common/row-final/20-normalization.phpduring 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:
dishwasher/category_dishwasher_consumablewith accessorydishwasher_consumable;pc_peripheral/category_printer_consumablewith accessoryprinter_consumable;pc_peripheral/category_printerwith accessoryprinter.
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:
gaming_accessory/category_gaming_streamingwith accessorystreaming;gaming_accessory/category_vrwith accessoryvr;gaming_accessory/category_console_accessorywith the existing accessory subtype selection (manette,simulation,casque_gaming,chargeur,battery,support_kit,protection_ecran,carrying_case, fallbackaccessoire_gaming);console/category_video_gamewith accessoryvideo_game;console/category_consolewith 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:
smartwatch/category_smartwatch, clearing accessory state;smartphone/category_smartphone, clearing accessory state;tablet/category_tablet, clearing accessory state;laptop/category_laptop, clearing accessory state;desktop_pc/category_desktop_pc, clearing accessory state;tv/category_tv_explicit_signal, clearing accessory state;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:
memory_card/category_memory_cardwith accessorymemory_card;usb_storage/category_usb_storagewith accessoryusb_storage;pc_storage/category_storagewith accessorystorage;pc_peripheral/category_networkwith accessorynetwork;pc_peripheral/category_pc_peripheralwith 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:
tv/category_tv_accessorywith accessorytv_accessory;projector/category_projectorwith accessory clearing;tv/category_tvwith accessory clearing;soundbar/category_soundbarwith accessory clearing;wireless_earbuds/category_earbudswith accessory clearing;headphone/category_headphonewith accessory clearing and the existing gaming exclusion (gamer,gaming,ps5,ps4,xbox,switch);speaker/category_audio_accessorywith accessoryaudio_accessory;speaker/category_speakerwith 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:
pc_peripheral/category_camera_lenswith accessorycamera_lens;pc_peripheral/category_camera_accessorywith accessorycamera_accessory;smart_home/category_smart_home_securitywith accessorysecurity;pc_peripheral/category_camerawith accessorycamera.
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:
smart_home/category_smart_speakerwith accessorysmart_speaker;smart_home/category_smart_lightwith accessoryeclairage_connecte;smart_home/category_smart_homewith accessorysmart_home_accessory;smart_home/category_drone, dynamically choosing accessorydrone_accessoryforaccessoire drone, otherwisedrone;smart_home/category_electric_mobilitywith accessoryelectric_mobility;smartwatch/category_smart_ringwith accessorysmart_ring;smart_home/category_trackerwith accessorytracker;smart_home/category_power_stationwith accessorypower_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:
fridge/category_fridge;freezer/category_freezer;washing_machine/category_washing_machine;dryer/category_dryer;dishwasher/category_dishwasher;oven/category_oven;cooktop/category_cooktop;microwave/category_microwave;home_appliance/category_home_appliance;kitchen_appliance/category_kitchen_appliance;vacuum/category_vacuum;home_cleaning/category_home_care;air_treatment/category_air_treatment;fan/category_fan;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.