From b8bb71900d872191953843b0552d49104e40e4fb Mon Sep 17 00:00:00 2001 From: Edwin Eames Date: Tue, 4 Nov 2025 22:45:12 -0500 Subject: [PATCH] added script for calculating auto useage --- app/models/auto.py | 2 +- app/routers/fixstuff.py | 205 ++++++++++++++++++++++++++++++++++++++++ main.py | 3 +- 3 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 app/routers/fixstuff.py diff --git a/app/models/auto.py b/app/models/auto.py index 6a41890..8e31643 100644 --- a/app/models/auto.py +++ b/app/models/auto.py @@ -60,7 +60,7 @@ class Auto_Delivery(Base): house_factor = Column(DECIMAL(5, 2)) auto_status = Column(INTEGER()) open_ticket_id = Column(Integer, nullable=True) - hot_water_summer = Column(INTEGER()) + hot_water_summer = Column(INTEGER()) class Tickets_Auto_Delivery(Base): diff --git a/app/routers/fixstuff.py b/app/routers/fixstuff.py new file mode 100644 index 0000000..a109be9 --- /dev/null +++ b/app/routers/fixstuff.py @@ -0,0 +1,205 @@ +from fastapi import APIRouter +from fastapi.responses import JSONResponse +from fastapi.encoders import jsonable_encoder +from database import session +from sqlalchemy import func +from datetime import date +from decimal import Decimal + +from app.models.auto import Auto_Delivery, Tickets_Auto_Delivery, Auto_Temp +from app.models.delivery import Delivery + +# Constants from fuel_estimator +HOT_WATER_DAILY_USAGE = Decimal('2.0') +K_FACTOR_SMOOTHING_WEIGHT = Decimal('0.7') + + + +router = APIRouter( + prefix="/fixstuff", + tags=["fixstuff"], + responses={404: {"description": "Not found"}}, +) + + + +@router.get("/lastdelivered", status_code=200) +def fix_customer_last_delivered(): + """ + Updates the last_fill date in the auto_delivery table for each customer + by finding the most recent completed delivery (ticket with non-NULL fill_date) + from the auto_tickets table, matched by account_number. + + Returns statistics and a list of changes made. + """ + auto_deliveries = session.query(Auto_Delivery).all() + changes = [] + total_customers = len(auto_deliveries) + tickets_found = 0 + updates_made = 0 + for ad in auto_deliveries: + latest_ticket = session.query(Tickets_Auto_Delivery).filter( + Tickets_Auto_Delivery.account_number == ad.account_number, + Tickets_Auto_Delivery.fill_date.isnot(None) + ).order_by(Tickets_Auto_Delivery.fill_date.desc()).first() + if latest_ticket: + tickets_found += 1 + if ad.last_fill != latest_ticket.fill_date: + updates_made += 1 + old_date = ad.last_fill + ad.last_fill = latest_ticket.fill_date + changes.append({ + "id": ad.id, + "customer_full_name": ad.customer_full_name, + "before_date": str(old_date) if old_date else None, + "new_date": str(latest_ticket.fill_date) + }) + session.add(ad) + + session.commit() + result = { + "total_customers": total_customers, + "tickets_found": tickets_found, + "updates_made": updates_made, + "changes": changes + } + return JSONResponse(content=jsonable_encoder(result)) + + +@router.get("/estimate_gallons/{update_db}", status_code=200) +def estimate_customer_gallons(update_db: int): + """ + Estimates current gallons for each customer based on delivery history and weather. + update_db: 0 for estimation only (no DB changes), 1 for estimation with DB updates. + No tickets: assume 100 gallons. Single delivery: use weather for 2000 sq ft home. + Multiple deliveries: use historical average. Includes address and scaling factor. + When update_db=1, updates estimated_gallons_left and house_factor in database. + """ + auto_deliveries = session.query(Auto_Delivery).all() + estimates = [] + for ad in auto_deliveries: + tickets = session.query(Tickets_Auto_Delivery).filter( + Tickets_Auto_Delivery.account_number == ad.account_number, + Tickets_Auto_Delivery.fill_date.isnot(None) + ).order_by(Tickets_Auto_Delivery.fill_date).all() + + # Get tank size and hot water setting + tank_size = Decimal(ad.tank_size) if ad.tank_size else Decimal('275') + # Adjust effective tank capacity (not filled to 100%) + if tank_size == 275: + effective_tank = Decimal('250') + elif tank_size == 330: + effective_tank = Decimal('300') + else: + effective_tank = tank_size + hot_water = ad.hot_water_summer == 1 + + calculated_scaling = None # For DB update + + if not tickets: + estimated_gallons = Decimal('100') + else: + last_fill = tickets[-1].fill_date + estimated_gallons_left = effective_tank + today = date.today() + + if len(tickets) == 1: + # Single delivery: use weather data for 2000 sq ft home, only heat when temp <=70 + if last_fill < today: + # Get daily weather data + temp_days = session.query(Auto_Temp).filter( + Auto_Temp.todays_date > last_fill, + Auto_Temp.todays_date <= today + ).all() + heating_usage = Decimal('0') + hot_water_usage = Decimal('0') + house_factor_2000_sqft = Decimal('0.005') # gallons per degree day + for temp in temp_days: + degree_day = max(0, 65 - float(temp.temp_avg)) + heating_usage += house_factor_2000_sqft * Decimal(degree_day) + if hot_water: + hot_water_usage += HOT_WATER_DAILY_USAGE + total_usage = heating_usage + hot_water_usage + estimated_gallons_left = max(Decimal('0'), estimated_gallons_left - total_usage) + else: + # Multiple deliveries: calculate house_factor (gallons per degree day) + daily_heating_usages = [] + avg_degree_per_days = [] + for i in range(len(tickets) - 1): + prev_ticket = tickets[i] + next_ticket = tickets[i + 1] + days = (next_ticket.fill_date - prev_ticket.fill_date).days + if days > 0: + # Calculate degree days for this interval from temp_avg + interval_temps = session.query(Auto_Temp).filter( + Auto_Temp.todays_date > prev_ticket.fill_date, + Auto_Temp.todays_date <= next_ticket.fill_date + ).all() + total_degree_days = sum(max(0, 65 - float(temp.temp_avg)) for temp in interval_temps) + total_degree_days = Decimal(total_degree_days) + avg_degree_per_day = total_degree_days / days + + total_hot_water = HOT_WATER_DAILY_USAGE * days + gallons_heating = prev_ticket.gallons_delivered - total_hot_water + if gallons_heating > 0 and total_degree_days > 0: + daily_heating = gallons_heating / days + daily_heating_usages.append(daily_heating) + avg_degree_per_days.append(avg_degree_per_day) + + if daily_heating_usages and avg_degree_per_days: + average_daily_heating = sum(daily_heating_usages) / len(daily_heating_usages) + average_degree_days_per_day = sum(avg_degree_per_days) / len(avg_degree_per_days) + house_factor = average_daily_heating / average_degree_days_per_day + calculated_scaling = house_factor + else: + house_factor = Decimal('0.005') # Default + calculated_scaling = house_factor + + # Calculate usage from last_fill to today using temperature-dependent heating + if last_fill < today: + temp_days = session.query(Auto_Temp).filter( + Auto_Temp.todays_date > last_fill, + Auto_Temp.todays_date <= today + ).all() + heating_usage = Decimal('0') + hot_water_usage = Decimal('0') + for temp in temp_days: + degree_day = max(0, 65 - float(temp.temp_avg)) + heating_usage += house_factor * Decimal(degree_day) + if hot_water: + hot_water_usage += HOT_WATER_DAILY_USAGE + total_usage = heating_usage + hot_water_usage + estimated_gallons_left = max(Decimal('0'), estimated_gallons_left - total_usage) + + estimated_gallons = estimated_gallons_left + + # Update database if requested + if update_db == 1: + ad.estimated_gallons_left = estimated_gallons + if calculated_scaling is not None: + ad.house_factor = calculated_scaling + session.add(ad) + + last_5 = tickets[-5:] if tickets else [] + scaling_factor = float(ad.house_factor) if ad.house_factor else None + estimates.append({ + "id": ad.id, + "customer_full_name": ad.customer_full_name, + "account_number": ad.account_number, + "address": ad.customer_address, + "estimated_gallons": float(estimated_gallons), + "scaling_factor": scaling_factor, + "last_5_deliveries": [ + { + "fill_date": str(t.fill_date), + "gallons_delivered": float(t.gallons_delivered), + "price_per_gallon": float(t.price_per_gallon), + "total_amount_customer": float(t.total_amount_customer) + } for t in last_5 + ] + }) + + if update_db == 1: + session.commit() + + return JSONResponse(content=jsonable_encoder(estimates)) diff --git a/main.py b/main.py index 3abf048..c961a1b 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ from fastapi import FastAPI -from app.routers import main, delivery, confirm +from app.routers import main, delivery, confirm, fixstuff from fastapi.middleware.cors import CORSMiddleware import os from config import load_config @@ -14,6 +14,7 @@ app = FastAPI() app.include_router(main.router) app.include_router(delivery.router) app.include_router(confirm.router) +app.include_router(fixstuff.router) # print(ApplicationConfig.origins)