A custom widget for SAP Analytics Cloud rendering the Member Enrollment
executive/leadership “Snap” dashboard: Set Up/Completed counts by
Wave, a headline Enrollment Status breakdown, Defaulted broken out by
Wave 1/Wave 2a, a multiple-attempts count, and a combined bar+cumulative-
line daily timeline. This is one of a 3-widget suite (see
GOLD_VIEW_SPEC.md §10l) mirroring the Employer Election suite’s
structure — this widget plays the same role as
AE Snap Report does there. The fuller
Enrollment-Status-per-Wave breakdown, election-detail metrics (HSA/FSA/
Supp Life/Retirement/Vision), and the waiver-trend-by-membership-type
metric live on the sibling sac-member-operational-widget instead;
per-member row detail lives on sac-member-detail-widget.
Design doc: ../ae-member-enrollment-report/GOLD_VIEW_SPEC.md
defines the Gold-layer dataset. BUILD_PLAN_FOR_BLAIR.md’s
“SUPERSEDED — consolidated into ONE aggregate cube” section has the
current authoritative cube SQL and architecture — this widget binds to
a single consolidated Analytic Model (AM_MEMBER_ENROLLMENT_SUMMARY)
shared with sac-member-operational-widget, not two separate cubes as
originally designed. That redesign happened 2026-09-30 after discovering
a SAC custom widget can only bind to one Analytic Model total, no matter
how many named dataBindings its widget.json declares.
GLD_AE_Member_Enrollment is shared with the FLEX dashboard (sac-flex-member-widget). FLEX members carry Wave “Group A”–”Group F”, and this widget drops any row whose Wave starts with Group , next to its Portico exclusion. Blank-Wave rows are kept, so Traditional numbers don’t change. In a headless check, output was byte-identical with and without FLEX rows injected. Gold’s Employer column is also no longer NULL: it now holds “Name (Number)”. Details: ../flex-member-enrollment-report/GOLD_CHANGES.md.
The Employer Selections widget went through a real debugging cycle to learn that SAC’s Optimized-story View mode doesn’t deliver internal click/change events to a custom widget’s shadow DOM. This widget applies that lesson from day one:
widget.json — manifest: properties (width, height, asOfLabel),
a single aggregateData data binding (22 measures, 8 dimensions — see
below), one exposed scripting method (refresh).main.js — defines the <com-porticobenefits-memberenrollment> custom
element. Renders: a Set Up/Completed-by-Wave card row (Wave 1/2a/2b/3),
overall Enrollment Status tiles (Set Up / Completed / Started, Not
Completed — a display relabel of “Abandoned”, same underlying data /
Not Started / Multiple Attempts, each with a progress bar), Defaulted
panels broken out by Wave 1/Wave 2a (the only two waves with a PSP
step), and a Daily Completion Tracker comparing this cycle’s daily
completions against the prior cycle’s (added 2026-09-30) — two bar
series (this cycle / prior cycle, completions only, aligned by day-of-
cycle rather than literal date since the two cycles are a calendar year
apart) plus two cumulative lines (solid this-cycle, dashed prior-cycle)
on an independent right-axis scale. Hand-rolled inline SVG throughout —
no external chart library, same reasoning as the Employer widget (SAC
widget iframes are CSP-strict). Falls back to built-in mock data when no
data binding is bound, so the whole layout is reviewable standalone.icon.svg — icon shown in the SAC widget panel.preview.html — standalone local test harness; drives the widget through
the real onCustomWidgetBeforeUpdate/onCustomWidgetAfterUpdate
lifecycle hooks, same pattern as the Employer widget’s harness.One binding, aggregateData, shared with sac-member-operational-
widget — both bind to the same AM_MEMBER_ENROLLMENT_SUMMARY model,
each filtering client-side by a RowKind discriminator dimension. This
widget only reads two row-kinds (StatusByWave and DailyTrend); the
other two (ElectionSummary, WaiverTrend) exist on the same model for
Operational’s use. See main.js’s own header comment for the exact
dimension/measure order (SAC binds by position, not name — order matters
when binding in the SAC Builder panel: Measures before Dimensions, then
each list in the documented order).
Never Gold-layer member-level rows directly (Gold is one row per Member —
this widget only ever sees counts). See BUILD_PLAN_FOR_BLAIR.md’s
“SUPERSEDED — consolidated into ONE aggregate cube” section for the full
cube SQL and design.
Enrollment_Status
value.GOLD_VIEW_SPEC.md §7: what
“Total Not Started By Day” actually means (snapshot vs. cohort trend) —
not yet decided, not currently plotted.BUILD_PLAN_FOR_BLAIR.md’s cube design rule). Never add an
EventDate control — the YoY timeline/waiver panels need both cycles.BUILD_PLAN_FOR_BLAIR.md) is confirmed fixed and the
vDimMember join is restored in Gold, TotalEligibleLives/
TotalCoveredLives will start populating for real — no widget change
needed, they’re already wired up as placeholders.