feat: rewrite K-factor engine with history tracking and outlier detection

Replace simple exponential smoothing with a rolling-average K-factor
system backed by a new auto_kfactor_history table. Budget fills are
detected and excluded from calculations, outliers beyond 2-sigma are
flagged, and confidence scores track data quality per customer.
Adds backfill endpoint, auto-create for missing estimation records,
and manual house_factor PUT endpoints for both auto and regular customers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 17:54:27 -05:00
parent 764c094eed
commit c134c05947
5 changed files with 473 additions and 105 deletions

View File

@@ -1,6 +1,6 @@
from sqlalchemy import (Column, Integer,
DECIMAL, TEXT,
VARCHAR, DATE, INTEGER)
DECIMAL, TEXT, Boolean,
VARCHAR, DATE, INTEGER, Index)
from datetime import datetime
from database import Base
@@ -57,11 +57,12 @@ class Auto_Delivery(Base):
estimated_gallons_left_prev_day = Column(DECIMAL(6, 2))
tank_height = Column(VARCHAR(25))
tank_size = Column(VARCHAR(25))
house_factor = Column(DECIMAL(5, 2))
house_factor = Column(DECIMAL(7, 4))
auto_status = Column(INTEGER())
open_ticket_id = Column(Integer, nullable=True)
hot_water_summer = Column(INTEGER())
confidence_score = Column(INTEGER(), default=20)
k_factor_source = Column(VARCHAR(20), default='default')
@@ -92,3 +93,24 @@ class Tickets_Auto_Delivery(Base):
payment_type = Column(Integer, nullable=True)
payment_card_id = Column(Integer, nullable=True)
payment_status = Column(Integer, nullable=True)
is_budget_fill = Column(Boolean, default=False)
class KFactorHistory(Base):
__tablename__ = 'auto_kfactor_history'
id = Column(Integer, primary_key=True, autoincrement=True)
customer_id = Column(INTEGER(), nullable=False, index=True)
ticket_id = Column(Integer, nullable=True)
fill_date = Column(DATE())
gallons_delivered = Column(DECIMAL(6, 2))
total_hdd = Column(DECIMAL(8, 2))
days_in_period = Column(Integer)
k_factor = Column(DECIMAL(7, 4))
is_budget_fill = Column(Boolean, default=False)
is_outlier = Column(Boolean, default=False)
created_at = Column(DATE())
__table_args__ = (
Index('ix_auto_kfactor_history_customer_fill', 'customer_id', fill_date.desc()),
)