Add opt-in semantic-aware profiling to DQProfiler - #1491
Conversation
mwojtyczka
left a comment
There was a problem hiding this comment.
Automated code review (5 findings). Comments posted inline below.
mwojtyczka
left a comment
There was a problem hiding this comment.
Follow-up: ergonomics of the SemanticRegistry customization surface.
|
@mwojtyczka May I ask for another review round, please? Your previous comments has been addressed. Thank you |
Code review findings — semantic-aware profiling (#1491)Verified against head 1.
|
Changes
Introduce opt-in semantic-aware profiling in
DQProfiler. A lightweight classification stage runs between metric collection and check generation, so each column receives one consistent family of checks instead of contradictory overlaps (e.g.vehicle_typeno longer gets bothis_in([...])andmin_max(...)).The feature is fully backward compatible: when a
DQProfileris constructed without asemantic_registry, no detection runs and the generated profiles are byte-identical to today.New public surface (all in
src/databricks/labs/dqx/profiler/semantic.py):DQSemanticType— Pydantic v2 model naming a column's semantic meaning (e.g.enum,key,measurement,text) plus optional properties.DQSemanticTypeDetector— named callable(DQProfileContext) -> DQSemanticType | None.DQProfileContext— frozen context passed to detectors and contextual builders. Carriesdf,column_name,column_type,metrics,options, andsemantic_type. It exposeswith_metrics(...)so the profiler loop can refresh the frozen context after earlier builders write back resolved metrics (e.g. min/max).SemanticRegistry— immutable, name-unique, ordered chain of detectors. Composition surface:SemanticRegistry.default(),SemanticRegistry.of(*detectors),SemanticRegistry(detectors=(...))prepend(detector),append(detector),insert(name, detector)(after the named entry),replace(name, detector)(position-preserving swap),remove(name)model_validator.default_semantic_detectors()and four built-in detectors:DEFAULT_ENUM_DETECTOR,DEFAULT_KEY_DETECTOR,DEFAULT_MEASUREMENT_DETECTOR,DEFAULT_TEXT_DETECTORplus threshold constants (ENUM_MAX_CARDINALITY_RATIO,KEY_MIN_DENSITY_RATIO,KEY_MIN_LENGTH_STABILITY_RATIO).Profiler wiring (
profiler/profiler.py,profiler/profile_builder.py,profiler/profile.py):DQProfiler.__init__gains a keyword-onlysemantic_registry: SemanticRegistry | None = None. Presence of the argument opts the run into semantic detection — there is no per-call override.DQProfilegains an optionalsemantic_type: str | None = Nonefield so generated profiles record why a check was emitted (survives YAML/JSON round-trip; defaults toNone).DQProfileBuildersupports two mutually-exclusive callback shapes: the legacy 5-argumentbuilder(unchanged) and the newcontextual_builder(ctx: DQProfileContext).@register_profile_buildergains akind=\"context\"opt-in for the new shape; existing legacy registrations keep working without changes.null_or_empty,is_in,min_max,has_no_outliers) are migrated to the contextual form.min_maxandhas_no_outliersnow skip emission unlessctx.semantic_typeisNoneor\"measurement\";is_inreuses the enum detector's already-collected distinct values so no second Spark.distinct().collect()runs._detect_enumgate and the legacyis_ingate both computedistinct_count / count_non_nulland honour the profiler-widedistinct_ratiooption, so semantic classification and legacy emission agree on low-repetition columns even in null-heavy datasets.ShortTypeis a first-class enum candidate (added to_supports_distinct).Linked issues
Resolves #1343
Tests
tests/unit/profiler/test_semantic.pycovers each detector (positive + negative),SemanticRegistryimmutability and uniqueness invariants (constructor,of,prepend,append,insert,replace,remove), chain semantics (first-match-wins), the tightened enum cardinality guard, numeric-density and string-length-stability key guards including the empty-string edge case, andDQProfileContext.with_metricssnapshot semantics.tests/unit/test_profile_builder.pycoverskind=\"context\"vs legacy registration, mutually-exclusive callback validation, and per-builder positive/negative behaviour (includingShortTypeforis_in) through the new contextual path.tests/integration/test_profile_semantic.pycovers the default-registry classification of the grounded design columns (vehicle_type,cargo_weight,deal_value,user_id,order_id,user_name,work_description), the no-registry byte-identical default, custom-chain composition viaprepend/SemanticRegistry.of(...), theShortTypeenum →is_inregression guard, the enum-value reuse optimisation (single.distinct()invocation per enum column), and a public-API behavioural test that a contextual builder ordered aftermin_maxobserves the resolvedmin/maxviactx.metrics.Documentation and Demos
docs/dqx/docs/reference/profiler.mdxtagged<FeatureLifecycleStage stage=\"beta\">/<AvailableSinceVersion version=\"0.17.0\">. Documents the opt-in model, the four built-in detectors and their applicability rules and thresholds, the immutableSemanticRegistrycomposition patterns (prepend,append,insert,replace,remove,of), the@register_profile_builder(kind=\"context\")opt-in, and includes a copy-paste example specialised (UUID) detector.