feat: tiers -- leftover_mode rate-limited free, style_picker paid+, staple_library free
This commit is contained in:
parent
59b6a8265f
commit
27ec14b40f
2 changed files with 30 additions and 2 deletions
12
app/tiers.py
12
app/tiers.py
|
|
@ -25,6 +25,8 @@ KIWI_FEATURES: dict[str, str] = {
|
|||
"receipt_upload": "free",
|
||||
"expiry_alerts": "free",
|
||||
"export_csv": "free",
|
||||
"leftover_mode": "free", # Rate-limited at API layer, not tier-gated
|
||||
"staple_library": "free",
|
||||
|
||||
# Paid tier
|
||||
"receipt_ocr": "paid", # BYOK-unlockable
|
||||
|
|
@ -32,11 +34,11 @@ KIWI_FEATURES: dict[str, str] = {
|
|||
"expiry_llm_matching": "paid", # BYOK-unlockable
|
||||
"meal_planning": "paid",
|
||||
"dietary_profiles": "paid",
|
||||
"style_picker": "paid",
|
||||
|
||||
# Premium tier
|
||||
"multi_household": "premium",
|
||||
"background_monitoring": "premium",
|
||||
"leftover_mode": "premium",
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -47,6 +49,7 @@ def can_use(feature: str, tier: str, has_byok: bool = False) -> bool:
|
|||
tier,
|
||||
has_byok=has_byok,
|
||||
_features=KIWI_FEATURES,
|
||||
_byok_unlockable=KIWI_BYOK_UNLOCKABLE,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -54,7 +57,12 @@ def require_feature(feature: str, tier: str, has_byok: bool = False) -> None:
|
|||
"""Raise ValueError if the tier cannot access the feature."""
|
||||
if not can_use(feature, tier, has_byok):
|
||||
from circuitforge_core.tiers.tiers import tier_label
|
||||
needed = tier_label(feature, has_byok=has_byok, _features=KIWI_FEATURES)
|
||||
needed = tier_label(
|
||||
feature,
|
||||
has_byok=has_byok,
|
||||
_features=KIWI_FEATURES,
|
||||
_byok_unlockable=KIWI_BYOK_UNLOCKABLE,
|
||||
)
|
||||
raise ValueError(
|
||||
f"Feature '{feature}' requires {needed} tier. "
|
||||
f"Current tier: {tier}."
|
||||
|
|
|
|||
20
tests/test_tiers.py
Normal file
20
tests/test_tiers.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
from app.tiers import can_use
|
||||
|
||||
|
||||
def test_leftover_mode_free_tier():
|
||||
"""Leftover mode is now available to free users (rate-limited at API layer, not hard-gated)."""
|
||||
assert can_use("leftover_mode", "free") is True
|
||||
|
||||
|
||||
def test_style_picker_requires_paid():
|
||||
assert can_use("style_picker", "free") is False
|
||||
assert can_use("style_picker", "paid") is True
|
||||
|
||||
|
||||
def test_staple_library_is_free():
|
||||
assert can_use("staple_library", "free") is True
|
||||
|
||||
|
||||
def test_recipe_suggestions_byok_unlockable():
|
||||
assert can_use("recipe_suggestions", "free", has_byok=False) is False
|
||||
assert can_use("recipe_suggestions", "free", has_byok=True) is True
|
||||
Loading…
Reference in a new issue