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",
|
"receipt_upload": "free",
|
||||||
"expiry_alerts": "free",
|
"expiry_alerts": "free",
|
||||||
"export_csv": "free",
|
"export_csv": "free",
|
||||||
|
"leftover_mode": "free", # Rate-limited at API layer, not tier-gated
|
||||||
|
"staple_library": "free",
|
||||||
|
|
||||||
# Paid tier
|
# Paid tier
|
||||||
"receipt_ocr": "paid", # BYOK-unlockable
|
"receipt_ocr": "paid", # BYOK-unlockable
|
||||||
|
|
@ -32,11 +34,11 @@ KIWI_FEATURES: dict[str, str] = {
|
||||||
"expiry_llm_matching": "paid", # BYOK-unlockable
|
"expiry_llm_matching": "paid", # BYOK-unlockable
|
||||||
"meal_planning": "paid",
|
"meal_planning": "paid",
|
||||||
"dietary_profiles": "paid",
|
"dietary_profiles": "paid",
|
||||||
|
"style_picker": "paid",
|
||||||
|
|
||||||
# Premium tier
|
# Premium tier
|
||||||
"multi_household": "premium",
|
"multi_household": "premium",
|
||||||
"background_monitoring": "premium",
|
"background_monitoring": "premium",
|
||||||
"leftover_mode": "premium",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -47,6 +49,7 @@ def can_use(feature: str, tier: str, has_byok: bool = False) -> bool:
|
||||||
tier,
|
tier,
|
||||||
has_byok=has_byok,
|
has_byok=has_byok,
|
||||||
_features=KIWI_FEATURES,
|
_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."""
|
"""Raise ValueError if the tier cannot access the feature."""
|
||||||
if not can_use(feature, tier, has_byok):
|
if not can_use(feature, tier, has_byok):
|
||||||
from circuitforge_core.tiers.tiers import tier_label
|
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(
|
raise ValueError(
|
||||||
f"Feature '{feature}' requires {needed} tier. "
|
f"Feature '{feature}' requires {needed} tier. "
|
||||||
f"Current tier: {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