feat(features): mirror invalidatedBy into Python ProfileFeature (#61)

Adds invalidated_by: tuple[str, ...] to ProfileFeature, mirroring the
invalidatedBy bus subjects from registry.ts. Adds a test that parses the
TS source and asserts Python stays in sync — same drift-detection pattern
used for names and ttlSec.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 07:10:36 +00:00
parent a75be0d832
commit 17b9516903
2 changed files with 48 additions and 4 deletions

View File

@@ -10,10 +10,12 @@ 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.
Feature-spec fields (issue #61):
freshness — "batched": value cached in profile store, recomputed on TTL/event.
ttl_sec — cache lifetime in seconds; mirrors ``ttlSec`` in registry.ts.
source — where the value originates.
fallback — raw value returned when the feature is unavailable (null stored).
freshness "batched": value cached in profile store, recomputed on TTL/event.
ttl_sec — cache lifetime in seconds; mirrors ``ttlSec`` in registry.ts.
source — where the value originates.
fallback — raw value returned when the feature is unavailable (null stored).
invalidated_by — bus event subjects that trigger recompute for the affected user;
mirrors ``invalidatedBy`` in registry.ts. Empty = TTL-only refresh.
"""
from __future__ import annotations
@@ -37,6 +39,7 @@ class ProfileFeature:
ttl_sec: int
source: str
fallback: str
invalidated_by: tuple[str, ...] = ()
PROFILE_FEATURES: tuple[ProfileFeature, ...] = (
@@ -48,6 +51,7 @@ PROFILE_FEATURES: tuple[ProfileFeature, ...] = (
ttl_sec=6 * _HOUR,
source="profile_store",
fallback="0.0",
invalidated_by=("signals.tip.feedback",),
),
ProfileFeature(
name="dismiss_rate_30d",
@@ -57,6 +61,7 @@ PROFILE_FEATURES: tuple[ProfileFeature, ...] = (
ttl_sec=6 * _HOUR,
source="profile_store",
fallback="0.0",
invalidated_by=("signals.tip.feedback",),
),
ProfileFeature(
name="mean_dwell_ms_30d",
@@ -66,6 +71,7 @@ PROFILE_FEATURES: tuple[ProfileFeature, ...] = (
ttl_sec=6 * _HOUR,
source="profile_store",
fallback="null — serving normalises to 0.0",
invalidated_by=("signals.tip.feedback",),
),
ProfileFeature(
name="preferred_hour",
@@ -75,6 +81,7 @@ PROFILE_FEATURES: tuple[ProfileFeature, ...] = (
ttl_sec=_DAY,
source="profile_store",
fallback="null — serving normalises to 0.5 (neutral alignment)",
invalidated_by=("signals.tip.feedback",),
),
ProfileFeature(
name="tip_volume_30d",
@@ -84,6 +91,7 @@ PROFILE_FEATURES: tuple[ProfileFeature, ...] = (
ttl_sec=_HOUR,
source="profile_store",
fallback="0",
invalidated_by=("signals.tip.served",),
),
)