Create scoring.py
Browse files- src/scoring.py +34 -0
src/scoring.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import datetime, timedelta
|
| 2 |
+
from sqlmodel import select
|
| 3 |
+
from .models import Visti, TokenBalance
|
| 4 |
+
from .db import get_session
|
| 5 |
+
|
| 6 |
+
# 重みは適宜調整
|
| 7 |
+
W_RECENCY = 0.5
|
| 8 |
+
W_FREQUENCY = 0.3
|
| 9 |
+
W_POINTS30 = 0.2
|
| 10 |
+
|
| 11 |
+
def calc_score(user_id: int) -> float:
|
| 12 |
+
now = datetime.utcnow()
|
| 13 |
+
cutoff_30 = now - timedelta(days=30)
|
| 14 |
+
with get_session() as s:
|
| 15 |
+
visits = s.exec(select(Visit).where(Visit.user_id == user_id).order_by(Visit.created_at.desc())).all()
|
| 16 |
+
if not visits:
|
| 17 |
+
return 0.0
|
| 18 |
+
last_visit = visits[0].created_at
|
| 19 |
+
days_since = (now - last_visit).days
|
| 20 |
+
recency_score = max(0.0, 1.0 -days_since/ 30.0) #30日で減衰
|
| 21 |
+
freq_score = min(1.0, len([v for v in visits if v.created_at > now -timedelta(days=90)])/ 10.0) #90日10回で満点
|
| 22 |
+
points30 = s.exec(select(TokenBalance).where(TokenBalance.user_id == user_id)).all()
|
| 23 |
+
points_amounts = sum(tb.amount for tb in points30)
|
| 24 |
+
points30_score = min(1.0, points_amounts / 1000.0)
|
| 25 |
+
return W_RECENCY * recency_score + W_FREQUENCY * freq_score + W_POINTS30 *points30_score
|
| 26 |
+
|
| 27 |
+
def segment(socre: float) -> str:
|
| 28 |
+
if score >= 0.75:
|
| 29 |
+
return "loyal"
|
| 30 |
+
if score >= 0.4:
|
| 31 |
+
return "active"
|
| 32 |
+
if score > 0.0:
|
| 33 |
+
return "at-risk"
|
| 34 |
+
return "new"
|