File size: 1,527 Bytes
b622347
 
bba81e5
b622347
 
 
 
 
bba81e5
b622347
 
 
 
bba81e5
 
 
b622347
 
bba81e5
b622347
 
bba81e5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b622347
 
 
 
 
 
bba81e5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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"