Surface detected ADF motifs additively in discovery inventory (#64) - #90
Open
matthewmoorcroft wants to merge 2 commits into
Open
matthewmoorcroft wants to merge 2 commits into
matthewmoorcroft wants to merge 2 commits into
Conversation
The discover/profile path already runs the motif detector but reduced each DetectedMotif to a count for the profile report and discarded the rest. Surface the full detections additively in metadata/inventory.json as a per-pipeline `motifs` list, decoupled from the lineage block and without collapsing member activities (collapse stays a convert decision in motifs/collapser.py, untouched). - discovery_inventory.build_source_inventory gains an optional, source-neutral motifs_by_pipeline param and a _motif_entry projector; the key is omitted when a pipeline has no detected motif, so every existing inventory key stays byte-compatible and coverage / mcp consumers are unaffected. - The ADF loader detects motifs once (detect_motifs_by_pipeline) and shares the result with both the inventory emitter and the profile-row count, so the two views stay consistent from a single detection pass. Each motif entry carries motif_id, display_name, databricks_replacement, member_task_keys (participating activities), source_type_hint, and confidence_notes (the detector reports rationale, not a numeric score). Co-authored-by: Isaac <no-reply@databricks.com>
…sertion Address cross-review of #64. Blocking fix: the emitter serialized every detector result directly, so a detector that reports the same match more than once (e.g. two upstreams pairing with the same notification activity) produced duplicate inventory entries. Add `_dedupe_motif_entries`: collapse entries sharing both motif_id and the exact same member set (order-insensitive) to the first occurrence, preserving first-seen order, while keeping genuinely overlapping-but-distinct matches (same motif_id, different member set). New regression test covers both an exact duplicate (collapses to one) and an overlapping-but-distinct match (both survive). Test hardening: the real-fixture test now asserts the exact expected member set for the pipeline_complex_etl bulk-copy motif, so a partial-member regression fails instead of passing on a non-empty check. Co-authored-by: Isaac <no-reply@databricks.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
Discovery already ran the ADF motif detector for the profile report, but only retained a count. This PR adds the full detections to
metadata/inventory.jsonas an additive, per-pipelinemotifs[]field so routing can see multi-activity deterministic capabilities.The field is deliberately separate from
lineageand discovery does not collapse any activities.pipelines[].motifs[]schemaThe key is emitted only when a pipeline has at least one detected motif. Each entry is:
{ "motif_id": "metadata_driven_bulk_copy", "display_name": "Metadata-Driven Bulk Copy", "member_task_keys": ["Lookup ETL Config", "ForEach Table In Config"], "databricks_replacement": "for_each_ingestion", "source_type_hint": "database", "confidence_notes": [ "Lookup 'Lookup ETL Config' appears to read a control/metadata table" ] }motif_id— stable detector definition id.display_name— human-readable motif name.member_task_keys— the exact source activities participating in this match; these remain individually present inactivities[].databricks_replacement— the Databricks-native replacement associated with the definition.source_type_hint— optional detector hint about the source involved.confidence_notes— human-readable detection rationale; the detector does not invent a numeric confidence score.The first four names align with the shared
MotifAnnotationvocabulary while retaining detector-specificsource_type_hintandconfidence_notes.How it works
detect_motifs_by_pipelineonce for every pipeline. Detection is best-effort: a detector exception is logged and treated as no motifs rather than failing discovery.build_profile_rows, keeping the full inventory entries and the profile report'scollapsible_patternscount consistent from one pass.build_source_inventoryprojects matches beside each pipeline'sactivitiesandlineagekeys. Pipelines without a match omitmotifsentirely, preserving the prior shape.motif_idand the same member set—deduplicate to the first occurrence. Overlapping matches with different member sets remain separate detections.Decoupled and non-collapsing
pipelines[].motifsis its own discovery concept. It is not nested underpipelines[].lineage, andlineage.motifsremains untouched/empty because that slot belongs to convert-time IR lineage. The discovery emitter only surfaces the match: it does not callmotifs/collapser.py, does not replace member nodes, and does not remove them fromactivities[].For example, a
Lookup+ForEachbulk-copy motif produces onemotifs[]entry while bothLookup ETL ConfigandForEach Table In Configremain separate activity entries.Why routing consumes it
Routing assesses the deterministic option for a connected component from two signals:
strategyis alreadydeterministic; andmember_task_keys.The component is deterministic-capable only when every activity is covered by one of those signals. Routing also surfaces the de-duplicated motif ids in
options.deterministic.motifs. The inventory field is therefore a capability signal, not a request to collapse or convert anything during discovery.Non-breaking behavior
motifsis additive and omitted when empty.Verification
The branch adds source-neutral emitter tests for additive/no-collapse behavior, omission when empty, decoupling from lineage, and duplicate/overlap handling, plus real ADF discover-path coverage proving motif members remain intact. The PR was verified with
make testandmake fmt.This pull request and its description were written by Isaac.