From 6abd91944e5640aca0809e43de4613c4a68e29d5 Mon Sep 17 00:00:00 2001 From: rlougee Date: Thu, 23 Jul 2026 16:14:32 -0400 Subject: [PATCH 1/2] feat(user-account-merge): add account merge mapping and audit logs for user enrollments --- .../models/dimensional/_bridge_tables.yml | 27 ++++++++ .../dimensional/bridge_user_account_merge.sql | 68 +++++++++++++++++++ .../models/reporting/_reporting__models.yml | 18 +++++ .../edxorg_mitlearn_user_mapping.sql | 55 +++++++++++++++ .../mitxonline/_mitxonline__sources.yml | 35 ++++++++++ .../mitxonline/_stg_mitxonline__models.yml | 57 ++++++++++++++++ ...gres__courses_courserunenrollmentaudit.sql | 20 ++++++ ...stgres__courses_programenrollmentaudit.sql | 20 ++++++ 8 files changed, 300 insertions(+) create mode 100644 src/ol_dbt/models/dimensional/bridge_user_account_merge.sql create mode 100644 src/ol_dbt/models/reporting/edxorg_mitlearn_user_mapping.sql create mode 100644 src/ol_dbt/models/staging/mitxonline/stg__mitxonline__app__postgres__courses_courserunenrollmentaudit.sql create mode 100644 src/ol_dbt/models/staging/mitxonline/stg__mitxonline__app__postgres__courses_programenrollmentaudit.sql diff --git a/src/ol_dbt/models/dimensional/_bridge_tables.yml b/src/ol_dbt/models/dimensional/_bridge_tables.yml index 9ade59c89..c34a59555 100644 --- a/src/ol_dbt/models/dimensional/_bridge_tables.yml +++ b/src/ol_dbt/models/dimensional/_bridge_tables.yml @@ -175,3 +175,30 @@ models: - name: userorganization_is_manager description: If true, the user is a manager of this organization and can access organization dashboard features. + +- name: bridge_user_account_merge + description: > + Verified account-merge redirects derived from mitxonline audit history. + Grain: one row per (platform, platform_user_id_before). + tests: + - dbt_utils.unique_combination_of_columns: + combination_of_columns: + - platform + - platform_user_id_before + columns: + - name: platform + description: Platform identifier string (only 'mitxonline' today). + tests: + - not_null + - name: platform_user_id_before + description: Raw platform-specific user ID that was merged away (redirected from). + tests: + - not_null + - name: platform_user_id_after + description: Raw platform-specific user ID that is now canonical (redirected to). + tests: + - not_null + - name: merged_on + description: Timestamp of the merge (most recent, if the merge was reversed). + tests: + - not_null diff --git a/src/ol_dbt/models/dimensional/bridge_user_account_merge.sql b/src/ol_dbt/models/dimensional/bridge_user_account_merge.sql new file mode 100644 index 000000000..1b47bef55 --- /dev/null +++ b/src/ol_dbt/models/dimensional/bridge_user_account_merge.sql @@ -0,0 +1,68 @@ +{{ config( + materialized='table' +) }} + +-- Verified account-merge redirects derived from mitxonline audit history +-- (an admin reassigning a record's owner confirms two accounts are the same +-- person). Only mitxonline today; `platform` allows adding others later. +-- Grain: one row per (platform, platform_user_id_before). A merge can be +-- reversed later, so only the most recent direction per account pair is kept. +with courserunenrollment_audit as ( + select * from {{ ref('stg__mitxonline__app__postgres__courses_courserunenrollmentaudit') }} +) + +, programenrollment_audit as ( + select * from {{ ref('stg__mitxonline__app__postgres__courses_programenrollmentaudit') }} +) + +, user_changes as ( + select + json_extract_scalar(enrollmentaudit_data_before, '$.user') as platform_user_id_before + , json_extract_scalar(enrollmentaudit_data_after, '$.user') as platform_user_id_after + , enrollmentaudit_created_on + from courserunenrollment_audit + where json_extract_scalar(enrollmentaudit_data_before, '$.user') + != json_extract_scalar(enrollmentaudit_data_after, '$.user') + + union all + + select + json_extract_scalar(enrollmentaudit_data_before, '$.user') as platform_user_id_before + , json_extract_scalar(enrollmentaudit_data_after, '$.user') as platform_user_id_after + , enrollmentaudit_created_on + from programenrollment_audit + where json_extract_scalar(enrollmentaudit_data_before, '$.user') + != json_extract_scalar(enrollmentaudit_data_after, '$.user') +) + +, grouped as ( + select + cast(platform_user_id_before as integer) as platform_user_id_before + , cast(platform_user_id_after as integer) as platform_user_id_after + , max(enrollmentaudit_created_on) as last_merged_on + from user_changes + group by platform_user_id_before, platform_user_id_after +) + +-- Normalize to an unordered pair so a reversed merge (both directions +-- present) competes against itself, keeping only the most recent direction. +-- Note: QUALIFY is not supported by Trino; using ROW_NUMBER subquery. +, ranked as ( + select + * + , row_number() over ( + partition by + least(platform_user_id_before, platform_user_id_after) + , greatest(platform_user_id_before, platform_user_id_after) + order by last_merged_on desc + ) as row_num + from grouped +) + +select + 'mitxonline' as platform + , platform_user_id_before + , platform_user_id_after + , last_merged_on as merged_on +from ranked +where row_num = 1 diff --git a/src/ol_dbt/models/reporting/_reporting__models.yml b/src/ol_dbt/models/reporting/_reporting__models.yml index 94a1128d0..02a1db2e2 100644 --- a/src/ol_dbt/models/reporting/_reporting__models.yml +++ b/src/ol_dbt/models/reporting/_reporting__models.yml @@ -1381,3 +1381,21 @@ models: - name: user_latest_row_desc description: integer, row number indicating the latest row for each user based on application and course run dates + +- name: edxorg_mitlearn_user_mapping + description: > + Cross-platform identity crosswalk between edX.org and MIT Learn accounts, + connected via each account's link to MITx Online. Only includes users + linked on at least one side. Grain: one row per MITx Online account + (merged-away accounts are redirected to their canonical replacement via + bridge_user_account_merge). + columns: + - name: mitxonline_openedx_user_id + description: MITx Online Open edX user ID, nullable. The connecting hop between + edX.org and MIT Learn -- not itself part of the crosswalk. + - name: edxorg_user_id + description: edX.org user ID, nullable. Not populated if dim_user shows a link + that int__mitx__users can't independently confirm for that MITx Online account + (see model for the identity-collapsing collision this guards against). + - name: mitlearn_application_user_id + description: MIT Learn user ID, nullable. diff --git a/src/ol_dbt/models/reporting/edxorg_mitlearn_user_mapping.sql b/src/ol_dbt/models/reporting/edxorg_mitlearn_user_mapping.sql new file mode 100644 index 000000000..c812319e3 --- /dev/null +++ b/src/ol_dbt/models/reporting/edxorg_mitlearn_user_mapping.sql @@ -0,0 +1,55 @@ +-- Referencing int__mitx__users (not just dim_user) is intentional: dim_user +-- can fold unrelated accounts together when their computed emails collide, +-- and its own output is the thing being validated, so it can't be used to +-- catch its own collisions -- int__mitx__users has the pre-collapse link. +with mitx_users as ( + select user_mitxonline_id, user_edxorg_id + from {{ ref('int__mitx__users') }} +) + +, users as ( + select + dim_user.mitxonline_application_user_id + , dim_user.mitxonline_openedx_user_id + , case + when mitx_users.user_edxorg_id = dim_user.edxorg_openedx_user_id + then dim_user.edxorg_openedx_user_id + end as edxorg_user_id + , dim_user.mitlearn_user_id as mitlearn_application_user_id + from {{ ref('dim_user') }} as dim_user + left join mitx_users + on dim_user.mitxonline_application_user_id = mitx_users.user_mitxonline_id + where dim_user.mitxonline_application_user_id is not null +) + +, merges as ( + select platform_user_id_before, platform_user_id_after + from {{ ref('bridge_user_account_merge') }} + where platform = 'mitxonline' +) + +, merged_away_links as ( + select + merges.platform_user_id_after as mitxonline_user_id + , users.edxorg_user_id + , users.mitlearn_application_user_id + from merges + inner join users + on merges.platform_user_id_before = users.mitxonline_application_user_id +) + +, mapped as ( + select + users.mitxonline_openedx_user_id + , coalesce(users.edxorg_user_id, merged_away_links.edxorg_user_id) as edxorg_user_id + , coalesce(users.mitlearn_application_user_id, merged_away_links.mitlearn_application_user_id) + as mitlearn_application_user_id + from users + left join merged_away_links + on users.mitxonline_application_user_id = merged_away_links.mitxonline_user_id + where users.mitxonline_application_user_id not in (select platform_user_id_before from merges) +) + +select mitxonline_openedx_user_id, edxorg_user_id, mitlearn_application_user_id +from mapped +where edxorg_user_id is not null or mitlearn_application_user_id is not null diff --git a/src/ol_dbt/models/staging/mitxonline/_mitxonline__sources.yml b/src/ol_dbt/models/staging/mitxonline/_mitxonline__sources.yml index 70f1947f9..64cce965d 100644 --- a/src/ol_dbt/models/staging/mitxonline/_mitxonline__sources.yml +++ b/src/ol_dbt/models/staging/mitxonline/_mitxonline__sources.yml @@ -411,6 +411,23 @@ sources: warn_after: {count: 1, period: day} error_after: {count: 2, period: day} loaded_at_field: "from_iso8601_timestamp(created_on || 'Z')" + - name: raw__mitxonline__app__postgres__courses_courserunenrollmentaudit + columns: + - name: id + description: int, sequential ID for this audit record + - name: enrollment_id + description: int, foreign key referencing courses_courserunenrollment + - name: acting_user_id + description: int, ID of the user (often an admin/support account) who made the + change + - name: data_before + description: JSON string, full enrollment record state before the change + - name: data_after + description: JSON string, full enrollment record state after the change + - name: created_on + description: timestamp, specifying when this audit record was created + - name: updated_on + description: timestamp, specifying when this audit record was last updated - name: raw__mitxonline__app__postgres__courses_programenrollment columns: - name: id @@ -431,6 +448,24 @@ sources: - name: enrollment_mode description: str, enrollment mode for user + - name: raw__mitxonline__app__postgres__courses_programenrollmentaudit + columns: + - name: id + description: int, sequential ID for this audit record + - name: enrollment_id + description: int, foreign key referencing courses_programenrollment + - name: acting_user_id + description: int, ID of the user (often an admin/support account) who made the + change + - name: data_before + description: JSON string, full enrollment record state before the change + - name: data_after + description: JSON string, full enrollment record state after the change + - name: created_on + description: timestamp, specifying when this audit record was created + - name: updated_on + description: timestamp, specifying when this audit record was last updated + - name: raw__mitxonline__app__postgres__courses_courserun columns: - name: id diff --git a/src/ol_dbt/models/staging/mitxonline/_stg_mitxonline__models.yml b/src/ol_dbt/models/staging/mitxonline/_stg_mitxonline__models.yml index e030a1e89..6785dc46a 100644 --- a/src/ol_dbt/models/staging/mitxonline/_stg_mitxonline__models.yml +++ b/src/ol_dbt/models/staging/mitxonline/_stg_mitxonline__models.yml @@ -77,6 +77,34 @@ models: datepart: day field: from_iso8601_timestamp(courserunenrollment_created_on || 'Z') interval: 2 +- name: stg__mitxonline__app__postgres__courses_courserunenrollmentaudit + description: > + Audit log of changes to courses_courserunenrollment records, including + cases where an enrollment's owning user_id was reassigned (e.g. an admin + merging duplicate accounts). + columns: + - name: enrollmentaudit_id + description: int, sequential ID for this audit record + tests: + - unique + - not_null + - name: courserunenrollment_id + description: int, foreign key referencing courses_courserunenrollment + tests: + - not_null + - name: acting_user_id + description: int, ID of the user (often an admin/support account) who made the + change + - name: enrollmentaudit_data_before + description: JSON string, full enrollment record state before the change + - name: enrollmentaudit_data_after + description: JSON string, full enrollment record state after the change + - name: enrollmentaudit_created_on + description: timestamp, specifying when this audit record was created + tests: + - not_null + - name: enrollmentaudit_updated_on + description: timestamp, specifying when this audit record was last updated - name: stg__mitxonline__app__postgres__courses_programenrollment columns: - name: programenrollment_id @@ -114,6 +142,35 @@ models: arguments: column_list: ["program_id", "user_id"] +- name: stg__mitxonline__app__postgres__courses_programenrollmentaudit + description: > + Audit log of changes to courses_programenrollment records, including + cases where an enrollment's owning user_id was reassigned (e.g. an admin + merging duplicate accounts). + columns: + - name: enrollmentaudit_id + description: int, sequential ID for this audit record + tests: + - unique + - not_null + - name: programenrollment_id + description: int, foreign key referencing courses_programenrollment + tests: + - not_null + - name: acting_user_id + description: int, ID of the user (often an admin/support account) who made the + change + - name: enrollmentaudit_data_before + description: JSON string, full enrollment record state before the change + - name: enrollmentaudit_data_after + description: JSON string, full enrollment record state after the change + - name: enrollmentaudit_created_on + description: timestamp, specifying when this audit record was created + tests: + - not_null + - name: enrollmentaudit_updated_on + description: timestamp, specifying when this audit record was last updated + - name: stg__mitxonline__app__postgres__ecommerce_basketdiscount columns: - name: basketdiscount_id diff --git a/src/ol_dbt/models/staging/mitxonline/stg__mitxonline__app__postgres__courses_courserunenrollmentaudit.sql b/src/ol_dbt/models/staging/mitxonline/stg__mitxonline__app__postgres__courses_courserunenrollmentaudit.sql new file mode 100644 index 000000000..c1f77ffbf --- /dev/null +++ b/src/ol_dbt/models/staging/mitxonline/stg__mitxonline__app__postgres__courses_courserunenrollmentaudit.sql @@ -0,0 +1,20 @@ +-- MITx Online CourseRunEnrollment audit log (before/after change tracking) + +with source as ( + select * from {{ source('ol_warehouse_raw_data', 'raw__mitxonline__app__postgres__courses_courserunenrollmentaudit') }} +) + +, cleaned as ( + + select + id as enrollmentaudit_id + , enrollment_id as courserunenrollment_id + , acting_user_id + , data_before as enrollmentaudit_data_before + , data_after as enrollmentaudit_data_after + , {{ cast_timestamp_to_iso8601('created_on') }} as enrollmentaudit_created_on + , {{ cast_timestamp_to_iso8601('updated_on') }} as enrollmentaudit_updated_on + from source +) + +select * from cleaned diff --git a/src/ol_dbt/models/staging/mitxonline/stg__mitxonline__app__postgres__courses_programenrollmentaudit.sql b/src/ol_dbt/models/staging/mitxonline/stg__mitxonline__app__postgres__courses_programenrollmentaudit.sql new file mode 100644 index 000000000..925648726 --- /dev/null +++ b/src/ol_dbt/models/staging/mitxonline/stg__mitxonline__app__postgres__courses_programenrollmentaudit.sql @@ -0,0 +1,20 @@ +-- MITx Online ProgramEnrollment audit log (before/after change tracking) + +with source as ( + select * from {{ source('ol_warehouse_raw_data', 'raw__mitxonline__app__postgres__courses_programenrollmentaudit') }} +) + +, cleaned as ( + + select + id as enrollmentaudit_id + , enrollment_id as programenrollment_id + , acting_user_id + , data_before as enrollmentaudit_data_before + , data_after as enrollmentaudit_data_after + , {{ cast_timestamp_to_iso8601('created_on') }} as enrollmentaudit_created_on + , {{ cast_timestamp_to_iso8601('updated_on') }} as enrollmentaudit_updated_on + from source +) + +select * from cleaned From ab47df9d83bb17f307da87f6f513e42138654036 Mon Sep 17 00:00:00 2001 From: rlougee Date: Thu, 23 Jul 2026 17:21:33 -0400 Subject: [PATCH 2/2] feat(user-account-merge): update account merge mapping and introduce platform link logic --- .../models/dimensional/_bridge_tables.yml | 37 +++++--- .../dimensional/bridge_user_account_link.sql | 87 +++++++++++++++++++ .../models/reporting/_reporting__models.yml | 22 ++++- .../edxorg_mitlearn_user_mapping.sql | 42 +++++---- 4 files changed, 157 insertions(+), 31 deletions(-) create mode 100644 src/ol_dbt/models/dimensional/bridge_user_account_link.sql diff --git a/src/ol_dbt/models/dimensional/_bridge_tables.yml b/src/ol_dbt/models/dimensional/_bridge_tables.yml index c34a59555..3ed78a87b 100644 --- a/src/ol_dbt/models/dimensional/_bridge_tables.yml +++ b/src/ol_dbt/models/dimensional/_bridge_tables.yml @@ -176,29 +176,40 @@ models: description: If true, the user is a manager of this organization and can access organization dashboard features. -- name: bridge_user_account_merge +- name: bridge_user_account_link description: > - Verified account-merge redirects derived from mitxonline audit history. - Grain: one row per (platform, platform_user_id_before). + Edges between platform-specific user accounts, independent of dim_user's + own identity-collapsing. from_platform = to_platform is a + same-platform redirect from an old account to its canonical replacement + (derived from mitxonline audit history) -- a redirect to follow. Different + platforms is a cross-platform co-reference (e.g. a MITx Online account's + linked edX.org account) -- not a redirect. Grain: one row per + (from_platform, from_user_id, to_platform). tests: - dbt_utils.unique_combination_of_columns: combination_of_columns: - - platform - - platform_user_id_before + - from_platform + - from_user_id + - to_platform columns: - - name: platform - description: Platform identifier string (only 'mitxonline' today). + - name: from_platform + description: Platform identifier of the source account ('mitxonline' today). tests: - not_null - - name: platform_user_id_before - description: Raw platform-specific user ID that was merged away (redirected from). + - name: from_user_id + description: Raw platform-specific user ID of the source account. tests: - not_null - - name: platform_user_id_after - description: Raw platform-specific user ID that is now canonical (redirected to). + - name: to_platform + description: Platform identifier of the target account. Same as from_platform + for a merge redirect; a different platform (e.g. 'edxorg') for a cross-platform + link. tests: - not_null - - name: merged_on - description: Timestamp of the merge (most recent, if the merge was reversed). + - name: to_user_id + description: Raw platform-specific user ID of the target account. tests: - not_null + - name: observed_on + description: Timestamp of the merge (most recent, if reversed). Null for cross-platform + link rows. diff --git a/src/ol_dbt/models/dimensional/bridge_user_account_link.sql b/src/ol_dbt/models/dimensional/bridge_user_account_link.sql new file mode 100644 index 000000000..038fd34db --- /dev/null +++ b/src/ol_dbt/models/dimensional/bridge_user_account_link.sql @@ -0,0 +1,87 @@ +{{ config( + materialized='table' +) }} + +-- Edges between platform accounts, independent of dim_user's own identity- +-- collapsing (grouping by hashed email). from_platform = to_platform is a +-- merge redirect (old account -> canonical, confirmed via mitxonline audit +-- history); different platforms is a co-reference, not a redirect. +-- Grain: (from_platform, from_user_id, to_platform). +with courserunenrollment_audit as ( + select * from {{ ref('stg__mitxonline__app__postgres__courses_courserunenrollmentaudit') }} +) + +, programenrollment_audit as ( + select * from {{ ref('stg__mitxonline__app__postgres__courses_programenrollmentaudit') }} +) + +, user_changes as ( + select + json_extract_scalar(enrollmentaudit_data_before, '$.user') as platform_user_id_before + , json_extract_scalar(enrollmentaudit_data_after, '$.user') as platform_user_id_after + , enrollmentaudit_created_on + from courserunenrollment_audit + where json_extract_scalar(enrollmentaudit_data_before, '$.user') + != json_extract_scalar(enrollmentaudit_data_after, '$.user') + + union all + + select + json_extract_scalar(enrollmentaudit_data_before, '$.user') as platform_user_id_before + , json_extract_scalar(enrollmentaudit_data_after, '$.user') as platform_user_id_after + , enrollmentaudit_created_on + from programenrollment_audit + where json_extract_scalar(enrollmentaudit_data_before, '$.user') + != json_extract_scalar(enrollmentaudit_data_after, '$.user') +) + +, grouped as ( + select + cast(platform_user_id_before as integer) as platform_user_id_before + , cast(platform_user_id_after as integer) as platform_user_id_after + , max(enrollmentaudit_created_on) as last_merged_on + from user_changes + group by platform_user_id_before, platform_user_id_after +) + +-- Normalize to an unordered pair so a reversed merge (both directions +-- present) competes against itself, keeping only the most recent direction. +-- Note: QUALIFY is not supported by Trino; using ROW_NUMBER subquery. +, ranked_merges as ( + select + * + , row_number() over ( + partition by + least(platform_user_id_before, platform_user_id_after) + , greatest(platform_user_id_before, platform_user_id_after) + order by last_merged_on desc + ) as row_num + from grouped +) + +, account_merges as ( + select + 'mitxonline' as from_platform + , platform_user_id_before as from_user_id + , 'mitxonline' as to_platform + , platform_user_id_after as to_user_id + , last_merged_on as observed_on + from ranked_merges + where row_num = 1 +) + +, platform_links as ( + select + 'mitxonline' as from_platform + , user_mitxonline_id as from_user_id + , 'edxorg' as to_platform + , user_edxorg_id as to_user_id + , cast(null as varchar) as observed_on + from {{ ref('int__mitx__users') }} + where user_mitxonline_id is not null + and user_edxorg_id is not null +) + +select * from account_merges +union all +select * from platform_links diff --git a/src/ol_dbt/models/reporting/_reporting__models.yml b/src/ol_dbt/models/reporting/_reporting__models.yml index 02a1db2e2..d981e1d58 100644 --- a/src/ol_dbt/models/reporting/_reporting__models.yml +++ b/src/ol_dbt/models/reporting/_reporting__models.yml @@ -1388,14 +1388,30 @@ models: connected via each account's link to MITx Online. Only includes users linked on at least one side. Grain: one row per MITx Online account (merged-away accounts are redirected to their canonical replacement via - bridge_user_account_merge). + bridge_user_account_link). + tests: + - dbt_utils.expression_is_true: + expression: "edxorg_user_id is not null or mitlearn_application_user_id is not\ + \ null" columns: - name: mitxonline_openedx_user_id description: MITx Online Open edX user ID, nullable. The connecting hop between edX.org and MIT Learn -- not itself part of the crosswalk. + tests: + - unique: + config: + severity: warn - name: edxorg_user_id description: edX.org user ID, nullable. Not populated if dim_user shows a link - that int__mitx__users can't independently confirm for that MITx Online account - (see model for the identity-collapsing collision this guards against). + that bridge_user_account_link can't independently confirm for that MITx Online + account (see model for the identity-collapsing collision this guards against). + tests: + - unique: + config: + severity: warn - name: mitlearn_application_user_id description: MIT Learn user ID, nullable. + tests: + - unique: + config: + severity: warn diff --git a/src/ol_dbt/models/reporting/edxorg_mitlearn_user_mapping.sql b/src/ol_dbt/models/reporting/edxorg_mitlearn_user_mapping.sql index c812319e3..9db4ec2f2 100644 --- a/src/ol_dbt/models/reporting/edxorg_mitlearn_user_mapping.sql +++ b/src/ol_dbt/models/reporting/edxorg_mitlearn_user_mapping.sql @@ -1,10 +1,11 @@ --- Referencing int__mitx__users (not just dim_user) is intentional: dim_user --- can fold unrelated accounts together when their computed emails collide, --- and its own output is the thing being validated, so it can't be used to --- catch its own collisions -- int__mitx__users has the pre-collapse link. -with mitx_users as ( - select user_mitxonline_id, user_edxorg_id - from {{ ref('int__mitx__users') }} +-- Referencing bridge_user_account_link (not just dim_user) is intentional: +-- dim_user can fold unrelated accounts together when their computed emails +-- collide, and its own output is the thing being validated, so it can't be +-- used to catch its own collisions -- the bridge has the pre-collapse link. +with mitxonline_edxorg_link as ( + select from_user_id as mitxonline_application_user_id, to_user_id as user_edxorg_id + from {{ ref('bridge_user_account_link') }} + where from_platform = 'mitxonline' and to_platform = 'edxorg' ) , users as ( @@ -12,30 +13,41 @@ with mitx_users as ( dim_user.mitxonline_application_user_id , dim_user.mitxonline_openedx_user_id , case - when mitx_users.user_edxorg_id = dim_user.edxorg_openedx_user_id + when mitxonline_edxorg_link.user_edxorg_id = dim_user.edxorg_openedx_user_id then dim_user.edxorg_openedx_user_id end as edxorg_user_id , dim_user.mitlearn_user_id as mitlearn_application_user_id from {{ ref('dim_user') }} as dim_user - left join mitx_users - on dim_user.mitxonline_application_user_id = mitx_users.user_mitxonline_id + left join mitxonline_edxorg_link + on dim_user.mitxonline_application_user_id = mitxonline_edxorg_link.mitxonline_application_user_id where dim_user.mitxonline_application_user_id is not null ) , merges as ( - select platform_user_id_before, platform_user_id_after - from {{ ref('bridge_user_account_merge') }} - where platform = 'mitxonline' + select from_user_id as platform_user_id_before, to_user_id as platform_user_id_after + from {{ ref('bridge_user_account_link') }} + where from_platform = 'mitxonline' and to_platform = 'mitxonline' ) +-- A canonical account can have more than one account merged into it, so +-- aggregate per canonical account. max() only stands in for coalesce here -- +-- the count(distinct ...) guard nulls the result out instead of guessing if +-- two merged-away accounts carry genuinely conflicting non-null values. , merged_away_links as ( select merges.platform_user_id_after as mitxonline_user_id - , users.edxorg_user_id - , users.mitlearn_application_user_id + , case + when count(distinct users.edxorg_user_id) <= 1 + then max(users.edxorg_user_id) + end as edxorg_user_id + , case + when count(distinct users.mitlearn_application_user_id) <= 1 + then max(users.mitlearn_application_user_id) + end as mitlearn_application_user_id from merges inner join users on merges.platform_user_id_before = users.mitxonline_application_user_id + group by merges.platform_user_id_after ) , mapped as (