diff --git a/docs/docs/in_depth/feature-group-matching.md b/docs/docs/in_depth/feature-group-matching.md index 0929e6f1..5afeb462 100644 --- a/docs/docs/in_depth/feature-group-matching.md +++ b/docs/docs/in_depth/feature-group-matching.md @@ -25,6 +25,8 @@ def match_feature_group_criteria(cls, feature_name, options, data_access_collect Do not call `FeatureChainParser.match_configuration_feature_chain_parser` directly from a match hook: it raises on an option value the `PROPERTY_MAPPING` rejects, and an exception out of the hook aborts feature identification for every candidate, not just yours. `match_parser_criteria` turns that rejection into a non-match, and the reason still reaches the user in the "No feature groups found" error. +The options view depends on the caller: feature resolution passes declared (pre-default) options, while filter matching runs after intake and passes the resolved feature's effective (post-default) options merged onto the filter feature's own. Matching logic that reads option values can see different values on the two paths. See [Applying declared defaults](property-mapping.md#applying-declared-defaults). + ### 2. PROPERTY_MAPPING Configuration The `PROPERTY_MAPPING` defines how configuration-based features are validated: diff --git a/docs/docs/in_depth/property-mapping.md b/docs/docs/in_depth/property-mapping.md index 33d35134..88ef5123 100644 --- a/docs/docs/in_depth/property-mapping.md +++ b/docs/docs/in_depth/property-mapping.md @@ -378,7 +378,8 @@ For an engine-driven request intake has already run, so the compute-boundary cal no-op. It carries the work only for direct `FeatureSet` use that bypasses the engine, and there the collapsing twins raise instead of merging. -Both sites run *after* resolution, so materialization never changes **matching**. It does change +Both sites run *after* resolution, so materialization never changes **resolution matching** (filter +matching runs after intake and does observe materialized values, see below). It does change how features **group**: intake materialization deliberately canonicalizes default-equivalent twins, so a feature that passes a declared default explicitly and one that omits it become equal and merge into a single feature (with a warning naming the duplicated request) instead of computing twice. @@ -393,7 +394,7 @@ Which stage sees which view of the options: | Stage | Options view | | --- | --- | -| Parse, bind, match, resolve (subtype resolution applies defaults internally) | declared (pre-default) | +| Parse, bind, resolution match, resolve (subtype resolution applies defaults internally) | declared (pre-default) | | `input_features()` and child option inheritance | declared (pre-default) | | Splitting, planning, filter matching, `validate_input_features`, compute | effective (post-default) | @@ -402,8 +403,9 @@ path the safety net materializes inside `run_calculate_feature`, which is after `validate_input_features`, not before it. Filter matching is the one place the same classmethod sees both views: `GlobalFilter` calls -`match_feature_group_criteria` again after intake, so it observes effective options where feature -resolution saw declared ones. +`match_feature_group_criteria` again after intake, passing the filter feature's own options +enriched from the resolved feature's effective ones (`unify_options`), so a key the filter feature +omits arrives materialized where feature resolution saw it declared. ``` python graph_type = feature.options.get("graph_type") # the declared default when the caller omitted it diff --git a/mloda/core/abstract_plugins/components/feature_chainer/feature_chain_parser_mixin.py b/mloda/core/abstract_plugins/components/feature_chainer/feature_chain_parser_mixin.py index fd656bc9..71b4f370 100644 --- a/mloda/core/abstract_plugins/components/feature_chainer/feature_chain_parser_mixin.py +++ b/mloda/core/abstract_plugins/components/feature_chainer/feature_chain_parser_mixin.py @@ -247,6 +247,9 @@ def match_feature_group_criteria( variable ``MLODA_ALLOW_FORWARDED_NAME_MISMATCH=1`` downgrades this error to a warning. + The options view depends on the caller: feature resolution passes declared (pre-default) + options, filter matching a post-intake merge. See ``FeatureGroup.match_feature_group_criteria``. + Args: feature_name: Feature name to match options: Options containing configuration diff --git a/mloda/core/abstract_plugins/feature_group.py b/mloda/core/abstract_plugins/feature_group.py index 6e6a218c..8946550d 100644 --- a/mloda/core/abstract_plugins/feature_group.py +++ b/mloda/core/abstract_plugins/feature_group.py @@ -602,6 +602,13 @@ def match_feature_group_criteria( You can disallow them by removing them. However, often you can just use the default. If you want to implement a concrete implementation, e.g. just accept specific names, then you can overwrite this function. + + The options view depends on the caller. Feature resolution passes declared (pre-default) + options. GlobalFilter matches a filter feature after intake, passing that feature's own + options enriched from the resolved feature's effective (post-default) ones: a key the filter + feature declares itself keeps its declared value, an omitted key arrives materialized. + Matching logic that reads option values can therefore see different values on the two paths. + See ``docs/in_depth/property-mapping.md`` ("Applying declared defaults"). """ base_feature_name = cls.get_column_base_feature(feature_name)