Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/ol_dbt/models/dimensional/_bridge_tables.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,41 @@ 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_link
description: >
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:
- from_platform
- from_user_id
- to_platform
columns:
- name: from_platform
description: Platform identifier of the source account ('mitxonline' today).
tests:
- not_null
- name: from_user_id
description: Raw platform-specific user ID of the source account.
tests:
- not_null
- 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: 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.
87 changes: 87 additions & 0 deletions src/ol_dbt/models/dimensional/bridge_user_account_link.sql
Original file line number Diff line number Diff line change
@@ -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
68 changes: 68 additions & 0 deletions src/ol_dbt/models/dimensional/bridge_user_account_merge.sql
Original file line number Diff line number Diff line change
@@ -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
34 changes: 34 additions & 0 deletions src/ol_dbt/models/reporting/_reporting__models.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1381,3 +1381,37 @@ 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_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 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
67 changes: 67 additions & 0 deletions src/ol_dbt/models/reporting/edxorg_mitlearn_user_mapping.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
-- 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 (
select
dim_user.mitxonline_application_user_id
, dim_user.mitxonline_openedx_user_id
, case
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 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 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
, 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
)
Comment thread
Copilot marked this conversation as resolved.

, 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
35 changes: 35 additions & 0 deletions src/ol_dbt/models/staging/mitxonline/_mitxonline__sources.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading