Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Important
Some or all of this functionality is available as part of a preview release. The content and the functionality are subject to change.
This example shows a general pattern for generating a new gold segment from existing silver and gold data.
The scenario in this article creates an Engagement Recency segment that groups constituents by how recently they engaged.
Example segment values
This example uses these segment labels:
- Engaged in Last 30 Days
- Engaged in 31-90 Days
- Engaged in 91-365 Days
- Inactive
- Unclassified
When to use this pattern
Use this pattern when you want to classify constituents into reusable analytical groups rather than storing a single derived metric.
Add the gold tables
Open the Fundraising_GD_CreateSchema notebook and add schema methods for the segment dimension and bridge table.
@staticmethod
def get_dimconstituentsegment_engagementrecency_schema() -> StructType:
return StructType([
StructField("ConstituentSegmentId", StringType()),
StructField("ConstituentSegmentKey", LongType(), nullable=False),
StructField("ConstituentSegmentName", StringType()),
StructField("Order", IntegerType())
])
@staticmethod
def get_dimconstituentsegmentbridge_engagementrecency_schema() -> StructType:
return StructType([
StructField("ConstituentKey", LongType(), nullable=False),
StructField("ConstituentSegmentKey", LongType(), nullable=False),
StructField("ConstituentSegmentMappingId", StringType()),
StructField("ConstituentSegmentBridgeKey", LongType(), nullable=False)
])
Register both tables in get_entities().
def get_entities(self) -> list[tuple[StructType, str, bool]]:
return [
# ... existing tables ...
(NonprofitGoldModel.get_dimconstituentsegment_engagementrecency_schema(), "DimConstituentSegment_EngagementRecency", True),
(NonprofitGoldModel.get_dimconstituentsegmentbridge_engagementrecency_schema(), "DimConstituentSegmentBridge_EngagementRecency", True),
]
Create the segment definitions
Open the Fundraising_GD_CreateSegments notebook and create the reusable segment definition rows.
from pyspark.sql import Row
from pyspark.sql.functions import col, xxhash64
from pyspark.sql.types import IntegerType, StringType, StructField, StructType
segment_rows = [
Row(ConstituentSegmentId="eng-recency-30", ConstituentSegmentName="Engaged in Last 30 Days", Order=1, DaysMin=0, DaysMax=30),
Row(ConstituentSegmentId="eng-recency-31-90", ConstituentSegmentName="Engaged in 31-90 Days", Order=2, DaysMin=31, DaysMax=90),
Row(ConstituentSegmentId="eng-recency-91-365", ConstituentSegmentName="Engaged in 91-365 Days", Order=3, DaysMin=91, DaysMax=365),
Row(ConstituentSegmentId="eng-recency-inactive", ConstituentSegmentName="Inactive", Order=4, DaysMin=366, DaysMax=999999),
Row(ConstituentSegmentId="eng-recency-unclassified", ConstituentSegmentName="Unclassified", Order=5, DaysMin=None, DaysMax=None),
]
segment_definition_schema = StructType([
StructField("ConstituentSegmentId", StringType(), nullable=False),
StructField("ConstituentSegmentName", StringType(), nullable=False),
StructField("Order", IntegerType(), nullable=False),
StructField("DaysMin", IntegerType()),
StructField("DaysMax", IntegerType())
])
segment_definition_df = (
spark.createDataFrame(segment_rows, schema=segment_definition_schema)
.withColumn("ConstituentSegmentKey", xxhash64(col("ConstituentSegmentId")).cast("bigint"))
)
segment_definition_df.select(
"ConstituentSegmentId",
"ConstituentSegmentKey",
"ConstituentSegmentName",
"Order"
).write.mode("overwrite").format("delta").saveAsTable(
f"{gold_lakehouse_name}.DimConstituentSegment_EngagementRecency"
)
The definition table stays reusable, while the DaysMin and DaysMax helper columns stay inside the notebook logic.
Build segment membership
Use the engagement timeline to calculate the latest engagement for each constituent and then map each constituent to one segment.
from delta.tables import DeltaTable
from pyspark.sql.functions import current_date, datediff, lit, max as spark_max, when, xxhash64
segment_df = segment_definition_df.select(
"ConstituentSegmentKey",
"ConstituentSegmentName",
"DaysMin",
"DaysMax"
)
latest_engagement_df = (
get_gold_table("dm_EngagementTimeline")
.join(
get_gold_table("DimDate").select("DateKey", "Date"),
col("EngagementDate") == col("DateKey"),
"left"
)
.groupBy("ConstituentKey")
.agg(spark_max("Date").alias("LastEngagementDate"))
.withColumn("DaysSinceLastEngagement", datediff(current_date(), col("LastEngagementDate")))
)
classified_df = latest_engagement_df.join(
segment_df,
(col("DaysSinceLastEngagement") >= col("DaysMin")) &
(col("DaysSinceLastEngagement") <= col("DaysMax")),
"left"
)
unclassified_key = (
segment_definition_df
.filter(col("ConstituentSegmentName") == "Unclassified")
.select("ConstituentSegmentKey")
.first()["ConstituentSegmentKey"]
)
final_bridge_df = (
classified_df
.withColumn(
"FinalSegmentKey",
when(col("ConstituentSegmentKey").isNotNull(), col("ConstituentSegmentKey"))
.otherwise(lit(unclassified_key))
)
.select(
"ConstituentKey",
col("FinalSegmentKey").alias("ConstituentSegmentKey")
)
.withColumn("ConstituentSegmentMappingId", lit(None).cast("string"))
.withColumn(
"ConstituentSegmentBridgeKey",
xxhash64(col("ConstituentKey"), col("ConstituentSegmentKey")).cast("bigint")
)
)
bridge_table = DeltaTable.forName(spark, f"{gold_lakehouse_name}.DimConstituentSegmentBridge_EngagementRecency")
bridge_table.delete("ConstituentSegmentMappingId IS NULL")
final_bridge_df.write.format("delta").mode("append").saveAsTable(
f"{gold_lakehouse_name}.DimConstituentSegmentBridge_EngagementRecency"
)
This example depends on dm_EngagementTimeline, so run it after the gold enrichment logic that creates that data mart.
Verify the result
- Open
DimConstituentSegment_EngagementRecencyand confirm that all segment definitions exist. - Open
DimConstituentSegmentBridge_EngagementRecencyand confirm that eachConstituentKeyappears at most once. - Confirm that recently engaged constituents land in
Engaged in Last 30 Days. - Confirm that inactive constituents land in
InactiveorUnclassified. - Confirm that rerunning the notebook replaces the custom bridge rows instead of duplicating them.
Considerations
- Segment dimensions define the allowed segment values.
- Bridge tables store constituent membership.
- Prefer deterministic classification logic so reruns produce stable results.
- If a constituent can belong to multiple segments, use a multi-row bridge pattern instead of forcing one final segment.
- If the segment logic depends on a shared gold data mart such as
dm_EngagementTimeline, document that notebook dependency explicitly.