import logging from fastapi import APIRouter, Depends from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder from pydantic import BaseModel from database import session from datetime import date, timedelta from decimal import Decimal from sqlalchemy import func from app.models.auto import Auto_Delivery, Tickets_Auto_Delivery, Auto_Temp from app.models.customer import Customer_Customer from app.models.delivery import Delivery from app.models.auth import Auth_User from app.auth import get_current_user logger = logging.getLogger(__name__) HOT_WATER_DAILY_USAGE = Decimal('1.0') HDD_FORECAST_DAYS = 7 class HouseFactorUpdate(BaseModel): house_factor: float router = APIRouter( prefix="/delivery", tags=["delivery"], responses={404: {"description": "Not found"}}, ) def _get_avg_hdd(days: int = HDD_FORECAST_DAYS) -> Decimal: """Get average HDD over the last N days as a forecast proxy.""" cutoff = date.today() - timedelta(days=days) rows = session.query(Auto_Temp.temp_avg).filter( Auto_Temp.todays_date > cutoff ).all() if not rows: return Decimal('0') total = sum(max(0, 65 - float(r.temp_avg)) for r in rows) return Decimal(str(round(total / len(rows), 2))) def _enrich_auto(auto_obj, avg_hdd: Decimal) -> dict: """Add computed gallons_per_day and days_remaining to a serialized auto delivery.""" data = jsonable_encoder(auto_obj) k = Decimal(str(auto_obj.house_factor)) if auto_obj.house_factor else Decimal('0') hot_water = HOT_WATER_DAILY_USAGE if auto_obj.hot_water_summer == 1 else Decimal('0') daily_burn = k * avg_hdd + hot_water data['gallons_per_day'] = float(round(daily_burn, 2)) data['avg_hdd'] = float(avg_hdd) data['hot_water_summer'] = auto_obj.hot_water_summer if daily_burn > 0 and auto_obj.estimated_gallons_left is not None: days_left = int(auto_obj.estimated_gallons_left / daily_burn) data['days_remaining'] = min(days_left, 999) else: data['days_remaining'] = 999 return data @router.get("/all/customers", status_code=200) def get_delivery_customers(current_user: Auth_User = Depends(get_current_user)): logger.info(f"GET /delivery/all/customers - User: {current_user.username}") automatics = ( session.query(Auto_Delivery) .filter(Auto_Delivery.auto_status.in_([1, 3])) .order_by(Auto_Delivery.estimated_gallons_left.asc()) .all() ) avg_hdd = _get_avg_hdd() enriched = [_enrich_auto(a, avg_hdd) for a in automatics] return JSONResponse(content=enriched, status_code=200) @router.get("/driver/{driver_employee_id}", status_code=200) def get_delivery_for_specific_driver(driver_employee_id: int, current_user: Auth_User = Depends(get_current_user)): logger.info(f"GET /delivery/driver/{driver_employee_id} - User: {current_user.username}") automatics = ( session.query(Delivery) .filter(Delivery.driver_employee_id == driver_employee_id) .filter(Delivery.automatic == 1) .filter(Delivery.delivery_status == 0) .all() ) return JSONResponse(content=jsonable_encoder(automatics), status_code=200) @router.get("/delivery/{ticket_id}", status_code=200) def get_delivery_by_openticket(ticket_id, current_user: Auth_User = Depends(get_current_user)): logger.info(f"GET /delivery/delivery/{ticket_id} - User: {current_user.username}") get_delivery = ( session.query(Auto_Delivery) .filter(Auto_Delivery.id == ticket_id) .first() ) return JSONResponse(content=jsonable_encoder(get_delivery), status_code=200) @router.get("/finddelivery/{ticket_id}", status_code=200) def get_delivery_by_findticket(ticket_id, current_user: Auth_User = Depends(get_current_user)): logger.info(f"GET /delivery/finddelivery/{ticket_id} - User: {current_user.username}") get_delivery = ( session.query(Auto_Delivery) .filter(Auto_Delivery.open_ticket_id == ticket_id) .first() ) return JSONResponse(content=jsonable_encoder(get_delivery), status_code=200) @router.get("/autoticket/{delivery_id_order}", status_code=200) def get_auto_by_ticket(delivery_id_order, current_user: Auth_User = Depends(get_current_user)): logger.info(f"GET /delivery/autoticket/{delivery_id_order} - User: {current_user.username}") get_delivery = ( session.query(Tickets_Auto_Delivery) .filter(Tickets_Auto_Delivery.id == delivery_id_order) .first() ) return JSONResponse(content=jsonable_encoder(get_delivery), status_code=200) @router.get("/all/profile/{customer_id}", status_code=200) def get_autos_customers(customer_id, current_user: Auth_User = Depends(get_current_user)): logger.info(f"GET /delivery/all/profile/{customer_id} - User: {current_user.username}") get_delivery = ( session.query(Tickets_Auto_Delivery) .filter(Tickets_Auto_Delivery.customer_id == customer_id) .order_by(Tickets_Auto_Delivery.id.desc()) .limit(5) .all() ) return JSONResponse(content=jsonable_encoder(get_delivery), status_code=200) @router.get("/all/profile/profile/{customer_id}", status_code=200) def get_autos_customers_extended(customer_id, current_user: Auth_User = Depends(get_current_user)): logger.info(f"GET /delivery/all/profile/profile/{customer_id} - User: {current_user.username}") get_delivery = ( session.query(Tickets_Auto_Delivery) .filter(Tickets_Auto_Delivery.customer_id == customer_id) .order_by(Tickets_Auto_Delivery.id.desc()) .limit(25) .all() ) return JSONResponse(content=jsonable_encoder(get_delivery), status_code=200) @router.get("/auto/customer/{customer_id}", status_code=200) def get_auto_delivery_by_customer(customer_id: int, current_user: Auth_User = Depends(get_current_user)): logger.info(f"GET /delivery/auto/customer/{customer_id} - User: {current_user.username}") get_auto_delivery = ( session.query(Auto_Delivery) .filter(Auto_Delivery.customer_id == customer_id) .first() ) if not get_auto_delivery: # Auto-create record from customer data customer = session.query(Customer_Customer).filter( Customer_Customer.id == customer_id ).first() if not customer: return JSONResponse(content={"error": "Customer not found"}, status_code=404) # Use division average K-factor as default div_avg = session.query(func.avg(Auto_Delivery.house_factor)).filter( Auto_Delivery.house_factor.isnot(None), Auto_Delivery.house_factor > 0 ).scalar() default_k = float(div_avg) if div_avg else 0.12 get_auto_delivery = Auto_Delivery( customer_id=customer.id, account_number=customer.account_number, customer_town=customer.customer_town, customer_state=customer.customer_state, customer_address=customer.customer_address, customer_zip=customer.customer_zip, customer_full_name=f"{customer.customer_first_name} {customer.customer_last_name}".strip(), estimated_gallons_left=Decimal('100'), estimated_gallons_left_prev_day=Decimal('100'), tank_size='275', house_factor=Decimal(str(round(default_k, 4))), auto_status=1, hot_water_summer=0, confidence_score=20, k_factor_source='default' ) session.add(get_auto_delivery) session.commit() session.refresh(get_auto_delivery) logger.info(f"Auto-created Auto_Delivery record for customer {customer_id}") avg_hdd = _get_avg_hdd() enriched = _enrich_auto(get_auto_delivery, avg_hdd) return JSONResponse(content=enriched, status_code=200) @router.put("/update_status/{auto_id}", status_code=200) def update_auto_status(auto_id: int, current_user: Auth_User = Depends(get_current_user)): logger.info(f"PUT /delivery/update_status/{auto_id} - User: {current_user.username}") update_status = ( session.query(Auto_Delivery) .filter(Auto_Delivery.id == auto_id) .first() ) if update_status: update_status.auto_status = 3 session.commit() return {"message": "Auto status updated to 3"} return {"error": "Auto delivery not found"} @router.put("/auto/customer/{customer_id}/house_factor", status_code=200) def update_house_factor(customer_id: int, body: HouseFactorUpdate, current_user: Auth_User = Depends(get_current_user)): logger.info(f"PUT /delivery/auto/customer/{customer_id}/house_factor - User: {current_user.username}") auto_delivery = ( session.query(Auto_Delivery) .filter(Auto_Delivery.customer_id == customer_id) .first() ) if not auto_delivery: return JSONResponse(content={"error": "Auto delivery record not found"}, status_code=404) auto_delivery.house_factor = Decimal(str(round(body.house_factor, 4))) auto_delivery.k_factor_source = 'manual' session.commit() session.refresh(auto_delivery) avg_hdd = _get_avg_hdd() enriched = _enrich_auto(auto_delivery, avg_hdd) return JSONResponse(content=enriched, status_code=200)