Annual Summary
Data Entity
Description
A derived, precomputed year-in-review record for a single peer mentor, aggregating their activity counts, hours, contacts reached, events attended, achievements earned, and engagement streaks for a given calendar year. Powers the Spotify Wrapped-style 'Annual Summary (Wrapped)' feature and the Advantage Calculator impact metrics.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Primary key — unique identifier for this summary record | PKrequiredunique |
user_id |
uuid |
Foreign key referencing the peer mentor this summary belongs to | required |
organization_id |
uuid |
Foreign key referencing the organization the user belonged to during this year. Enables org-scoped aggregate queries and multi-tenant isolation. | required |
year |
integer |
The calendar year this summary covers (e.g. 2024). Combined with user_id this forms a natural unique key. | required |
total_activities |
integer |
Total number of approved activity records logged by the user during the year | required |
total_hours |
decimal |
Sum of all activity durations in hours for the year, rounded to 1 decimal place | required |
total_contacts_reached |
integer |
Count of distinct contact IDs referenced across all activities in the year | required |
total_events_attended |
integer |
Count of event registrations with confirmed attendance for the year | required |
activity_type_breakdown |
json |
JSON object mapping each activity type key to its count for the year. Example: {"home_visit": 42, "phone_call": 28, "group_activity": 15}. Used to render the breakdown slide in the Wrapped screen. | required |
top_activity_type |
string |
The activity type key with the highest count. Derived from activity_type_breakdown at compute time and stored for fast retrieval. | - |
monthly_breakdown |
json |
JSON array of 12 objects, one per calendar month, each containing {month: 1-12, activity_count: int, hours: decimal}. Used to render the monthly trend slide. | required |
longest_streak_days |
integer |
The longest consecutive streak of active days (days with at least one logged activity) in the year | required |
achievements_earned_count |
integer |
Total number of achievement badges earned during the year. Denormalized from achievements table for fast rendering. | required |
achievement_ids |
json |
JSON array of achievement IDs earned during the year. Used to render the achievements slide. Example: ["first-home-visit", "50-activities"] | required |
impact_score |
decimal |
Calculated composite impact metric combining hours, contact reach, and activity diversity. Used by the Advantage Calculator for impact visualization. | - |
share_token |
string |
A short random token used to construct a public shareable URL for the summary without exposing the user_id. Set when the user first shares their summary. | unique |
computed_at |
datetime |
Timestamp of when the aggregation job last computed or recomputed this summary record | required |
is_published |
boolean |
Whether this summary has been made accessible to the user in the app. False while year is still in progress or while feature flag is disabled for the org. | required |
created_at |
datetime |
Record creation timestamp | required |
updated_at |
datetime |
Record last-updated timestamp, set on every recompute | required |
Database Indexes
idx_annual_summary_user_year
Columns: user_id, year
idx_annual_summary_org_year
Columns: organization_id, year
idx_annual_summary_share_token
Columns: share_token
idx_annual_summary_year
Columns: year
Validation Rules
user_id_must_reference_active_user
error
Validation failed
year_must_be_valid_calendar_year
error
Validation failed
numeric_fields_must_be_non_negative
error
Validation failed
monthly_breakdown_must_have_12_entries
error
Validation failed
activity_type_breakdown_must_be_valid_json_object
error
Validation failed
share_token_must_be_unique_if_set
error
Validation failed
Business Rules
one_summary_per_user_per_year
Exactly one annual_summary record may exist per (user_id, year) pair. The summary-aggregation-service uses an upsert pattern: insert on first compute, update on recompute. The unique index on (user_id, year) enforces this at the database level.
feature_flag_gates_publication
is_published must remain false until the feature flag for Annual Summary is enabled for the user's organization. The annual-summary-service checks the feature flag before returning data to the UI; wrapped-summary-screen never renders if the flag is off.
derived_data_only_from_approved_activities
total_activities and total_hours must only aggregate activities with status='approved'. Pending or rejected activities must be excluded. This ensures Wrapped metrics reflect verified contributions, consistent with Bufdir reporting.
org_tenant_isolation
All reads and aggregation queries must be scoped to the user's organization_id. The annual-summary-service and annual-summary-repository must enforce this to prevent cross-tenant data leakage in multi-tenant deployments.
share_token_set_on_first_share
share_token is null by default and is only generated and persisted when the user first triggers sharing via share-export-service. Once set, it is immutable.
share_content_must_exclude_contact_identifiable_data
Any publicly shareable representation of the summary (via share_token URL or exported image) must strip all contact names, IDs, and relationship data. Only aggregated metrics (counts, hours, badges) may be shared. Enforced by share-export-service at render time.