"""Profile-feature schema mirror (#81 phase A). The TypeScript registry in ``services/api/src/profile/registry.ts`` is the *source of truth* — features are computed there because the data lives in the TS-owned SQLite DB. This module is a documentation/typing mirror so Python code (ml/serving, eval harnesses, notebooks) knows what fields to expect on ``profile_features`` payloads without round-tripping the API. Update this file whenever you add or rename a feature in the TS registry. The accompanying test asserts the two stay in sync at the name level. """ from __future__ import annotations from dataclasses import dataclass from typing import Literal Dtype = Literal["numeric", "categorical"] @dataclass(frozen=True) class ProfileFeature: name: str dtype: Dtype description: str PROFILE_FEATURES: tuple[ProfileFeature, ...] = ( ProfileFeature( "completion_rate_30d", "numeric", 'Fraction of tips served in the last 30 days that received a "done" reaction.', ), ProfileFeature( "dismiss_rate_30d", "numeric", 'Fraction of tips served in the last 30 days that received a "dismiss" reaction.', ), ProfileFeature( "mean_dwell_ms_30d", "numeric", "Average dwell time (ms between served and reacted) over the last 30 days.", ), ProfileFeature( "preferred_hour", "numeric", 'Hour-of-day with the most "done" reactions in the last 30 days (0-23).', ), ProfileFeature( "tip_volume_30d", "numeric", "Number of tips served to the user in the last 30 days.", ), ) def feature_names() -> set[str]: return {f.name for f in PROFILE_FEATURES}