kiwi/app/tiers.py
pyr0ball 8cbde774e5 chore: initial commit — kiwi Phase 2 complete
Pantry tracker app with:
- FastAPI backend + Vue 3 SPA frontend
- SQLite via circuitforge-core (migrations 001-005)
- Inventory CRUD, barcode scan, receipt OCR pipeline
- Expiry prediction (deterministic + LLM fallback)
- CF-core tier system integration
- Cloud session support (menagerie)
2026-03-30 22:20:48 -07:00

61 lines
1.8 KiB
Python

"""
Kiwi tier gates.
Tiers: free < paid < premium
(Ultra not used in Kiwi — no human-in-the-loop operations.)
Uses circuitforge-core can_use() with Kiwi's feature map.
"""
from __future__ import annotations
from circuitforge_core.tiers.tiers import can_use as _can_use, BYOK_UNLOCKABLE
# Features that unlock when the user supplies their own LLM backend.
KIWI_BYOK_UNLOCKABLE: frozenset[str] = frozenset({
"recipe_suggestions",
"expiry_llm_matching",
"receipt_ocr",
})
# Feature → minimum tier required
KIWI_FEATURES: dict[str, str] = {
# Free tier
"inventory_crud": "free",
"barcode_scan": "free",
"receipt_upload": "free",
"expiry_alerts": "free",
"export_csv": "free",
# Paid tier
"receipt_ocr": "paid", # BYOK-unlockable
"recipe_suggestions": "paid", # BYOK-unlockable
"expiry_llm_matching": "paid", # BYOK-unlockable
"meal_planning": "paid",
"dietary_profiles": "paid",
# Premium tier
"multi_household": "premium",
"background_monitoring": "premium",
"leftover_mode": "premium",
}
def can_use(feature: str, tier: str, has_byok: bool = False) -> bool:
"""Return True if the given tier can access the feature."""
return _can_use(
feature,
tier,
has_byok=has_byok,
_features=KIWI_FEATURES,
)
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)
raise ValueError(
f"Feature '{feature}' requires {needed} tier. "
f"Current tier: {tier}."
)