from datetime import datetime, timedelta from sqlmodel import select from .models import Visit, TokenBalance from .db import get_session # 重みは適宜調整 W_RECENCY = 0.5 W_FREQUENCY = 0.3 W_POINTS = 0.2 # 総ポイント寄与(名称だけ修正) def calc_score(user_id: int) -> float: now = datetime.utcnow() with get_session() as s: visits = s.exec( select(Visit).where(Visit.user_id == user_id).order_by(Visit.created_at.desc()) ).all() if not visits: return 0.0 last_visit = visits[0].created_at days_since = (now - last_visit).days # 最近度:30日で線形減衰(0〜1) recency_score = max(0.0, 1.0 - days_since / 30.0) # 頻度:直近90日で10回来店で満点(0〜1) recent_90 = now - timedelta(days=90) freq_score = min( 1.0, len([v for v in visits if v.created_at > recent_90]) / 10.0 ) # ポイント寄与:合計1000ptで満点(0〜1) balances = s.exec(select(TokenBalance).where(TokenBalance.user_id == user_id)).all() points_amount = sum(tb.amount for tb in balances) points_score = min(1.0, points_amount / 1000.0) return W_RECENCY * recency_score + W_FREQUENCY * freq_score + W_POINTS * points_score def segment(score: float) -> str: if score >= 0.75: return "loyal" if score >= 0.4: return "active" if score > 0.0: return "at-risk" return "new"