"""RFM (Recency, Frequency, Monetary) feature computation.""" from datetime import date import pandas as pd from sqlalchemy import Engine, text RFM_SEGMENT_MAP = { (4, 4): "Champions", (4, 3): "Loyal", (3, 4): "Loyal", (3, 3): "Loyal", (4, 2): "Potential Loyalist", (3, 2): "Potential Loyalist", (4, 1): "Promising", (3, 1): "Promising", (2, 4): "At Risk", (2, 3): "At Risk", (2, 2): "Need Attention", (2, 1): "About To Sleep", (1, 4): "Lost", (1, 3): "Lost", (1, 2): "Lost", (1, 1): "Lost", } def _assign_rfm_segment(r_score: int, f_score: int) -> str: return RFM_SEGMENT_MAP.get((r_score, f_score), "Unknown") def compute_rfm(engine: Engine, reference_date: date | None = None) -> pd.DataFrame: """ Compute RFM scores for each customer_unique_id. Returns a DataFrame with columns: customer_unique_id, customer_key, recency_days, frequency, monetary, r_score, f_score, m_score, rfm_score, rfm_segment """ if reference_date is None: with engine.connect() as conn: result = conn.execute( text("SELECT MAX(order_date_id) FROM warehouse.fact_orders") ) reference_date = result.scalar() sql = text(""" SELECT dc.customer_unique_id, :ref_date - MAX(f.order_date_id) AS recency_days, COUNT(DISTINCT f.order_id) AS frequency, SUM(f.price + f.freight_value) AS monetary FROM warehouse.fact_orders f JOIN warehouse.dim_customers dc ON f.customer_key = dc.customer_key WHERE f.order_status NOT IN ('canceled', 'unavailable') GROUP BY dc.customer_unique_id """) with engine.connect() as conn: df = pd.read_sql(sql, conn, params={"ref_date": reference_date}) # Quartile scoring (1=worst, 4=best) df["r_score"] = pd.qcut(df["recency_days"], q=4, labels=[4, 3, 2, 1]).astype(int) df["f_score"] = pd.qcut(df["frequency"].rank(method="first"), q=4, labels=[1, 2, 3, 4]).astype(int) df["m_score"] = pd.qcut(df["monetary"].rank(method="first"), q=4, labels=[1, 2, 3, 4]).astype(int) df["rfm_score"] = df["r_score"].astype(str) + df["f_score"].astype(str) + df["m_score"].astype(str) df["rfm_segment"] = df.apply( lambda row: _assign_rfm_segment(row["r_score"], row["f_score"]), axis=1 ) return df