Coverage for server / services / growthbook / catalog.py: 100%
20 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-10-04 09:33 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-10-04 09:33 +0000
1"""
2Read-only feature catalog helpers — the catalog is GrowthBook UNION the local registry.
4Modified by Allan Ninal
5"""
7from __future__ import annotations
9from typing import Optional
11from server.services.growthbook.client import growthbook_client
12from server.services.growthbook.registry import FEATURE_REGISTRY, FeatureDefinition, get_feature_definition
15def _catalog_keys() -> list[str]:
16 """Every feature key the platform knows about — GrowthBook UNION the registry.
18 This was a swap, not a union: a non-empty GrowthBook list replaced the registry
19 outright. GrowthBook was seeded with `teacher.*` flags only, so the instant it
20 answered, all four `student.*` definitions — which exist ONLY here — vanished
21 from the catalog and `/effective` began reporting `features: []` for every
22 student, while per-key evaluation kept saying those same features were enabled.
24 The registry is not a fallback for GrowthBook being unreachable. It is the
25 declaration of which features exist, and a key GrowthBook has never heard of is
26 precisely the one that must not be dropped. Evaluation still prefers GrowthBook
27 per key; only enumeration is unioned.
28 """
29 return sorted(set(growthbook_client.list_feature_keys()) | set(FEATURE_REGISTRY))
32def _audience_as_feature_type(feature_key: str, definition: Optional[FeatureDefinition]) -> str:
33 if definition is not None:
34 if definition.audience in ("teacher", "student"):
35 return definition.audience
36 return "platform"
37 if feature_key.startswith("teacher."):
38 return "teacher"
39 if feature_key.startswith("student."):
40 return "student"
41 return "platform"
44def feature_exists(feature_key: str) -> bool:
45 if growthbook_client.has_feature(feature_key):
46 return True
47 return get_feature_definition(feature_key) is not None