major claude changes

This commit is contained in:
2026-01-28 21:54:58 -05:00
parent 6316309184
commit ac4354716b
15 changed files with 269 additions and 95 deletions

42
app/auth.py Normal file
View File

@@ -0,0 +1,42 @@
import re
import logging
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from database import get_db
from app.models.auth import Auth_User
logger = logging.getLogger(__name__)
security = HTTPBearer()
def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
) -> Auth_User:
"""
Validates the Bearer token and returns the authenticated user.
Raises HTTPException 401 if token is invalid or user not found.
"""
token = credentials.credentials
user = db.query(Auth_User).filter(Auth_User.api_key == token).first()
if not user:
logger.warning("Authentication failed: invalid API key")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication token",
headers={"WWW-Authenticate": "Bearer"},
)
if user.active != 1:
logger.warning(f"Authentication failed: user {user.username} is inactive")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User account is inactive",
headers={"WWW-Authenticate": "Bearer"},
)
return user

20
app/models/auth.py Normal file
View File

@@ -0,0 +1,20 @@
from sqlalchemy import Column, Integer, String, Text, TIMESTAMP
from database import Base
class Auth_User(Base):
__tablename__ = 'auth_users'
__table_args__ = {"schema": "public"}
id = Column(Integer, primary_key=True, autoincrement=True)
uuid = Column(String(32))
api_key = Column(Text)
username = Column(String(40))
password_hash = Column(Text)
member_since = Column(TIMESTAMP)
email = Column(String(350))
last_seen = Column(TIMESTAMP)
admin = Column(Integer)
admin_role = Column(Integer)
confirmed = Column(Integer)
active = Column(Integer, default=1)

View File

@@ -1,5 +1,8 @@
import logging
from fastapi import APIRouter, Request, HTTPException from fastapi import APIRouter, Request, HTTPException
from datetime import date from datetime import date
logger = logging.getLogger(__name__)
from database import session from database import session
from decimal import Decimal from decimal import Decimal
@@ -52,7 +55,7 @@ async def update_auto(autoid: int, request: Request):
return {"ok": True, "message": "Delivery confirmed and customer factor refined successfully."} return {"ok": True, "message": "Delivery confirmed and customer factor refined successfully."}
except Exception as e: except Exception as e:
print(str(e)) logger.error(str(e))
session.rollback() session.rollback()
raise HTTPException(status_code=500, detail=f"An internal error occurred: {str(e)}") raise HTTPException(status_code=500, detail=f"An internal error occurred: {str(e)}")

View File

@@ -1,10 +1,15 @@
from fastapi import APIRouter import logging
from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from database import session from database import session
from app.models.auto import Auto_Delivery, Tickets_Auto_Delivery from app.models.auto import Auto_Delivery, Tickets_Auto_Delivery
from app.models.delivery import Delivery from app.models.delivery import Delivery
from app.models.auth import Auth_User
from app.auth import get_current_user
logger = logging.getLogger(__name__)
@@ -17,8 +22,8 @@ router = APIRouter(
@router.get("/all/customers", status_code=200) @router.get("/all/customers", status_code=200)
def get_delivery_customers(): def get_delivery_customers(current_user: Auth_User = Depends(get_current_user)):
logger.info(f"GET /delivery/all/customers - User: {current_user.username}")
automatics = ( automatics = (
session.query(Auto_Delivery) session.query(Auto_Delivery)
.filter(Auto_Delivery.auto_status.in_([1, 3])) .filter(Auto_Delivery.auto_status.in_([1, 3]))
@@ -30,7 +35,8 @@ def get_delivery_customers():
@router.get("/driver/{driver_employee_id}", status_code=200) @router.get("/driver/{driver_employee_id}", status_code=200)
def get_delivery_for_specific_driver(driver_employee_id: int): 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 = ( automatics = (
session.query(Delivery) session.query(Delivery)
.filter(Delivery.driver_employee_id == driver_employee_id) .filter(Delivery.driver_employee_id == driver_employee_id)
@@ -42,7 +48,8 @@ def get_delivery_for_specific_driver(driver_employee_id: int):
return JSONResponse(content=jsonable_encoder(automatics), status_code=200) return JSONResponse(content=jsonable_encoder(automatics), status_code=200)
@router.get("/delivery/{ticket_id}", status_code=200) @router.get("/delivery/{ticket_id}", status_code=200)
def get_delivery_by_openticket(ticket_id): 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 = ( get_delivery = (
session.query(Auto_Delivery) session.query(Auto_Delivery)
.filter(Auto_Delivery.id == ticket_id) .filter(Auto_Delivery.id == ticket_id)
@@ -53,7 +60,8 @@ def get_delivery_by_openticket(ticket_id):
@router.get("/finddelivery/{ticket_id}", status_code=200) @router.get("/finddelivery/{ticket_id}", status_code=200)
def get_delivery_by_openticket(ticket_id): 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 = ( get_delivery = (
session.query(Auto_Delivery) session.query(Auto_Delivery)
.filter(Auto_Delivery.open_ticket_id == ticket_id) .filter(Auto_Delivery.open_ticket_id == ticket_id)
@@ -65,7 +73,8 @@ def get_delivery_by_openticket(ticket_id):
@router.get("/autoticket/{delivery_id_order}", status_code=200) @router.get("/autoticket/{delivery_id_order}", status_code=200)
def get_auto_by_ticket(delivery_id_order): 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 = ( get_delivery = (
session.query(Tickets_Auto_Delivery) session.query(Tickets_Auto_Delivery)
.filter(Tickets_Auto_Delivery.id == delivery_id_order) .filter(Tickets_Auto_Delivery.id == delivery_id_order)
@@ -76,8 +85,8 @@ def get_auto_by_ticket(delivery_id_order):
@router.get("/all/profile/{customer_id}", status_code=200) @router.get("/all/profile/{customer_id}", status_code=200)
def get_autos_customers(customer_id): 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 = ( get_delivery = (
session.query(Tickets_Auto_Delivery) session.query(Tickets_Auto_Delivery)
.filter(Tickets_Auto_Delivery.customer_id == customer_id) .filter(Tickets_Auto_Delivery.customer_id == customer_id)
@@ -89,8 +98,8 @@ def get_autos_customers(customer_id):
return JSONResponse(content=jsonable_encoder(get_delivery), status_code=200) return JSONResponse(content=jsonable_encoder(get_delivery), status_code=200)
@router.get("/all/profile/profile/{customer_id}", status_code=200) @router.get("/all/profile/profile/{customer_id}", status_code=200)
def get_autos_customers(customer_id): 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 = ( get_delivery = (
session.query(Tickets_Auto_Delivery) session.query(Tickets_Auto_Delivery)
.filter(Tickets_Auto_Delivery.customer_id == customer_id) .filter(Tickets_Auto_Delivery.customer_id == customer_id)
@@ -103,8 +112,8 @@ def get_autos_customers(customer_id):
@router.get("/auto/customer/{customer_id}", status_code=200) @router.get("/auto/customer/{customer_id}", status_code=200)
def get_auto_delivery_by_customer(customer_id): def get_auto_delivery_by_customer(customer_id, current_user: Auth_User = Depends(get_current_user)):
logger.info(f"GET /delivery/auto/customer/{customer_id} - User: {current_user.username}")
get_auto_delivery = ( get_auto_delivery = (
session.query(Auto_Delivery) session.query(Auto_Delivery)
.filter(Auto_Delivery.customer_id == customer_id) .filter(Auto_Delivery.customer_id == customer_id)
@@ -115,7 +124,8 @@ def get_auto_delivery_by_customer(customer_id):
@router.put("/update_status/{auto_id}", status_code=200) @router.put("/update_status/{auto_id}", status_code=200)
def update_auto_status(auto_id: int): 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 = ( update_status = (
session.query(Auto_Delivery) session.query(Auto_Delivery)
.filter(Auto_Delivery.id == auto_id) .filter(Auto_Delivery.id == auto_id)

View File

@@ -1,3 +1,4 @@
import logging
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
@@ -9,6 +10,8 @@ from decimal import Decimal
from app.models.auto import Auto_Delivery, Tickets_Auto_Delivery, Auto_Temp from app.models.auto import Auto_Delivery, Tickets_Auto_Delivery, Auto_Temp
from app.models.delivery import Delivery from app.models.delivery import Delivery
logger = logging.getLogger(__name__)
# Constants from fuel_estimator # Constants from fuel_estimator
HOT_WATER_DAILY_USAGE = Decimal('1.0') HOT_WATER_DAILY_USAGE = Decimal('1.0')
K_FACTOR_SMOOTHING_WEIGHT = Decimal('0.7') K_FACTOR_SMOOTHING_WEIGHT = Decimal('0.7')
@@ -33,6 +36,7 @@ def fix_customer_last_delivered():
Returns statistics and a list of changes made. Returns statistics and a list of changes made.
""" """
logger.info("GET /fixstuff/lastdelivered - Fixing customer last delivered dates")
auto_deliveries = session.query(Auto_Delivery).all() auto_deliveries = session.query(Auto_Delivery).all()
changes = [] changes = []
total_customers = len(auto_deliveries) total_customers = len(auto_deliveries)
@@ -76,6 +80,7 @@ def estimate_customer_gallons(update_db: int):
Multiple deliveries: use historical average. Includes address and scaling factor. Multiple deliveries: use historical average. Includes address and scaling factor.
When update_db=1, updates estimated_gallons_left and house_factor in database. When update_db=1, updates estimated_gallons_left and house_factor in database.
""" """
logger.info(f"GET /fixstuff/estimate_gallons/{update_db} - Estimating customer gallons (update_db={update_db})")
auto_deliveries = session.query(Auto_Delivery).all() auto_deliveries = session.query(Auto_Delivery).all()
estimates = [] estimates = []
for ad in auto_deliveries: for ad in auto_deliveries:

View File

@@ -1,3 +1,4 @@
import logging
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
@@ -10,6 +11,8 @@ from app.models.customer import Customer_Customer, Customer_estimate_gallons
from app.models.delivery import Delivery from app.models.delivery import Delivery
from app.models.auto import Auto_Temp from app.models.auto import Auto_Temp
logger = logging.getLogger(__name__)
# Constants from fuel_estimator # Constants from fuel_estimator
HOT_WATER_DAILY_USAGE = Decimal('1.0') HOT_WATER_DAILY_USAGE = Decimal('1.0')
K_FACTOR_SMOOTHING_WEIGHT = Decimal('0.7') K_FACTOR_SMOOTHING_WEIGHT = Decimal('0.7')
@@ -42,6 +45,7 @@ def fix_customer_last_delivered():
Returns statistics and a list of changes made. Returns statistics and a list of changes made.
""" """
logger.info("GET /fixstuff_customer/lastdelivered - Fixing customer last delivered dates")
session.rollback() # Reset any aborted transaction state session.rollback() # Reset any aborted transaction state
customer_estimates = session.query(Customer_estimate_gallons).all() customer_estimates = session.query(Customer_estimate_gallons).all()
changes = [] changes = []
@@ -87,6 +91,7 @@ def estimate_customer_gallons(update_db: int):
Multiple deliveries: use historical average. Includes address and scaling factor. Multiple deliveries: use historical average. Includes address and scaling factor.
When update_db=1, updates estimated_gallons_left and house_factor in database. When update_db=1, updates estimated_gallons_left and house_factor in database.
""" """
logger.info(f"GET /fixstuff_customer/estimate_gallons/{update_db} - Estimating customer gallons (update_db={update_db})")
session.rollback() # Reset any aborted transaction state session.rollback() # Reset any aborted transaction state
# Check if weather data is available # Check if weather data is available
@@ -235,6 +240,7 @@ def estimate_customer_gallons_specific(customer_id: int):
Estimates current gallons for a specific regular customer based on delivery history and weather. Estimates current gallons for a specific regular customer based on delivery history and weather.
Returns estimation data for the specified customer only. Returns estimation data for the specified customer only.
""" """
logger.info(f"GET /fixstuff_customer/estimate_gallons/customer/{customer_id} - Estimating gallons for specific customer")
session.rollback() # Reset any aborted transaction state session.rollback() # Reset any aborted transaction state
# Check if weather data is available # Check if weather data is available
@@ -386,6 +392,7 @@ def populate_customer_estimates():
Returns statistics on records created. Returns statistics on records created.
""" """
logger.info("GET /fixstuff_customer/populate_estimates - Populating customer estimates")
session.rollback() # Reset any aborted transaction state session.rollback() # Reset any aborted transaction state
# Get all regular customers (customer_automatic == 0) # Get all regular customers (customer_automatic == 0)

View File

@@ -1,6 +1,9 @@
import logging
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from database import session from database import session
logger = logging.getLogger(__name__)
from app.script.fuel_estimator import FuelEstimator from app.script.fuel_estimator import FuelEstimator
from app.script.temp_getter import fetch_and_store_daily_temp from app.script.temp_getter import fetch_and_store_daily_temp
from app.script.fuel_estimator_customer import FuelEstimatorCustomer from app.script.fuel_estimator_customer import FuelEstimatorCustomer
@@ -45,7 +48,7 @@ def update_all_customer_fuel_levels_auto():
except Exception as e: except Exception as e:
session.rollback() session.rollback()
# Log the exception e # Log the exception e
print(str(e)) logger.error(str(e))
return {"ok": False, "message": "An internal error occurred."} return {"ok": False, "message": "An internal error occurred."}
@@ -63,5 +66,5 @@ def update_all_customer_fuel_levels_normal():
except Exception as e: except Exception as e:
session.rollback() session.rollback()
# Log the exception e # Log the exception e
print(str(e)) logger.error(str(e))
return {"ok": False, "message": "An internal error occurred."} return {"ok": False, "message": "An internal error occurred."}

View File

@@ -1,8 +1,11 @@
import logging
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import func from sqlalchemy import func
from datetime import date, timedelta from datetime import date, timedelta
from decimal import Decimal from decimal import Decimal
logger = logging.getLogger(__name__)
# Import your existing database models # Import your existing database models
from app.models.auto import Auto_Delivery, Auto_Temp, Auto_Update, Tickets_Auto_Delivery from app.models.auto import Auto_Delivery, Auto_Temp, Auto_Update, Tickets_Auto_Delivery
@@ -58,7 +61,7 @@ class FuelEstimator:
if delivery_count <= 1: if delivery_count <= 1:
# Customers with 0 or 1 delivery should have house_factor = 0.12 (initial average) # Customers with 0 or 1 delivery should have house_factor = 0.12 (initial average)
if customer.house_factor != Decimal('0.12'): if customer.house_factor != Decimal('0.12'):
print(f"Correcting house_factor for customer {customer.customer_id} from {customer.house_factor} to 0.12 (1 or fewer deliveries)") logger.info(f"Correcting house_factor for customer {customer.customer_id} from {customer.house_factor} to 0.12 (1 or fewer deliveries)")
customer.house_factor = Decimal('0.12') customer.house_factor = Decimal('0.12')
corrected = True corrected = True
# For customers with 2+ deliveries, keep their calculated factor (no correction needed) # For customers with 2+ deliveries, keep their calculated factor (no correction needed)
@@ -74,13 +77,13 @@ class FuelEstimator:
# 1. Check if the update has already run today # 1. Check if the update has already run today
if self.session.query(Auto_Update).filter(Auto_Update.last_updated == today).first(): if self.session.query(Auto_Update).filter(Auto_Update.last_updated == today).first():
print(f"Daily update for {today} has already been completed.") logger.info(f"Daily update for {today} has already been completed.")
return {"ok": True, "message": "Update already run today."} return {"ok": True, "message": "Update already run today."}
# 2. Get today's weather data (specifically the Heating Degree Days) # 2. Get today's weather data (specifically the Heating Degree Days)
todays_weather = self._get_weather_for_date(today) todays_weather = self._get_weather_for_date(today)
if not todays_weather: if not todays_weather:
print(f"Error: Weather data for {today} not found. Cannot run update.") logger.info(f"Error: Weather data for {today} not found. Cannot run update.")
return {"ok": False, "message": f"Weather data for {today} not found."} return {"ok": False, "message": f"Weather data for {today} not found."}
# Degree days can't be negative for this calculation. If it's warm, HDD = 0. # Degree days can't be negative for this calculation. If it's warm, HDD = 0.
@@ -92,10 +95,10 @@ class FuelEstimator:
).all() ).all()
if not auto_customers: if not auto_customers:
print("No active automatic delivery customers found.") logger.info("No active automatic delivery customers found.")
return {"ok": True, "message": "No active customers to update."} return {"ok": True, "message": "No active customers to update."}
print(f"Staging daily fuel update for {len(auto_customers)} customers...") logger.info(f"Staging daily fuel update for {len(auto_customers)} customers...")
corrections_made = 0 corrections_made = 0
@@ -124,7 +127,7 @@ class FuelEstimator:
new_update_log = Auto_Update(last_updated=today) new_update_log = Auto_Update(last_updated=today)
self.session.add(new_update_log) self.session.add(new_update_log)
print("Daily update staged. Awaiting commit.") logger.info("Daily update staged. Awaiting commit.")
message = f"Successfully staged updates for {len(auto_customers)} customers." message = f"Successfully staged updates for {len(auto_customers)} customers."
if corrections_made > 0: if corrections_made > 0:
message += f" Corrected house factors for {corrections_made} customers." message += f" Corrected house factors for {corrections_made} customers."
@@ -141,11 +144,11 @@ class FuelEstimator:
).first() ).first()
if not customer: if not customer:
print(f"Customer {ticket.customer_id} not found.") logger.info(f"Customer {ticket.customer_id} not found.")
return return
if not customer.last_fill: if not customer.last_fill:
print(f"Setting initial K-Factor for new customer {ticket.customer_id} with only one delivery.") logger.info(f"Setting initial K-Factor for new customer {ticket.customer_id} with only one delivery.")
customer.house_factor = self._estimate_initial_house_factor(customer) customer.house_factor = self._estimate_initial_house_factor(customer)
self._update_tank_after_fill(customer, ticket) self._update_tank_after_fill(customer, ticket)
return return
@@ -154,7 +157,7 @@ class FuelEstimator:
end_date = ticket.fill_date end_date = ticket.fill_date
if start_date >= end_date: if start_date >= end_date:
print(f"Cannot refine K-Factor for customer {ticket.customer_id}: New fill date is not after the last one. Resetting tank only.") logger.info(f"Cannot refine K-Factor for customer {ticket.customer_id}: New fill date is not after the last one. Resetting tank only.")
self._update_tank_after_fill(customer, ticket) self._update_tank_after_fill(customer, ticket)
return return
@@ -172,7 +175,7 @@ class FuelEstimator:
gallons_for_heating = ticket.gallons_delivered - total_hot_water_usage gallons_for_heating = ticket.gallons_delivered - total_hot_water_usage
if gallons_for_heating <= 0 or total_hdd == 0: if gallons_for_heating <= 0 or total_hdd == 0:
print(f"Cannot calculate new K-Factor for customer {ticket.customer_id}. (HDD: {total_hdd}, Heating Gallons: {gallons_for_heating}). Resetting tank only.") logger.info(f"Cannot calculate new K-Factor for customer {ticket.customer_id}. (HDD: {total_hdd}, Heating Gallons: {gallons_for_heating}). Resetting tank only.")
self._update_tank_after_fill(customer, ticket) self._update_tank_after_fill(customer, ticket)
return return
@@ -181,13 +184,13 @@ class FuelEstimator:
current_k_factor = customer.house_factor current_k_factor = customer.house_factor
smoothed_k_factor = (current_k_factor * K_FACTOR_SMOOTHING_WEIGHT) + (new_k_factor * (Decimal('1.0') - K_FACTOR_SMOOTHING_WEIGHT)) smoothed_k_factor = (current_k_factor * K_FACTOR_SMOOTHING_WEIGHT) + (new_k_factor * (Decimal('1.0') - K_FACTOR_SMOOTHING_WEIGHT))
print(f"Refining K-Factor for Customer ID {customer.customer_id}:") logger.info(f"Refining K-Factor for Customer ID {customer.customer_id}:")
print(f" - Old K-Factor: {current_k_factor:.4f}, New Smoothed K-Factor: {smoothed_k_factor:.4f}") logger.info(f" - Old K-Factor: {current_k_factor:.4f}, New Smoothed K-Factor: {smoothed_k_factor:.4f}")
customer.house_factor = smoothed_k_factor customer.house_factor = smoothed_k_factor
self._update_tank_after_fill(customer, ticket) self._update_tank_after_fill(customer, ticket)
print(f"K-Factor and tank status for Customer {customer.customer_id} staged for update.") logger.info(f"K-Factor and tank status for Customer {customer.customer_id} staged for update.")
def _update_tank_after_fill(self, customer: Auto_Delivery, ticket: Tickets_Auto_Delivery): def _update_tank_after_fill(self, customer: Auto_Delivery, ticket: Tickets_Auto_Delivery):
"""Helper to update customer tank status after a fill-up or partial delivery.""" """Helper to update customer tank status after a fill-up or partial delivery."""

View File

@@ -1,8 +1,11 @@
import logging
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import func from sqlalchemy import func
from datetime import date, timedelta from datetime import date, timedelta
from decimal import Decimal from decimal import Decimal
logger = logging.getLogger(__name__)
# Import your existing database models # Import your existing database models
from app.models.customer import Customer_estimate_gallons, Customer_Update from app.models.customer import Customer_estimate_gallons, Customer_Update
from app.models.delivery import Delivery from app.models.delivery import Delivery
@@ -60,7 +63,7 @@ class FuelEstimatorCustomer:
if delivery_count <= 1: if delivery_count <= 1:
# Customers with 0 or 1 delivery should have house_factor = 0.12 (initial average) # Customers with 0 or 1 delivery should have house_factor = 0.12 (initial average)
if customer.house_factor != Decimal('0.12'): if customer.house_factor != Decimal('0.12'):
print(f"Correcting house_factor for customer {customer.customer_id} from {customer.house_factor} to 0.12 (1 or fewer deliveries)") logger.info(f"Correcting house_factor for customer {customer.customer_id} from {customer.house_factor} to 0.12 (1 or fewer deliveries)")
customer.house_factor = Decimal('0.12') customer.house_factor = Decimal('0.12')
corrected = True corrected = True
# For customers with 2+ deliveries, keep their calculated factor (no correction needed) # For customers with 2+ deliveries, keep their calculated factor (no correction needed)
@@ -76,13 +79,13 @@ class FuelEstimatorCustomer:
# 1. Check if the update has already run today # 1. Check if the update has already run today
if self.session.query(Customer_Update).filter(Customer_Update.last_updated == today).first(): if self.session.query(Customer_Update).filter(Customer_Update.last_updated == today).first():
print(f"Daily update for {today} has already been completed.") logger.info(f"Daily update for {today} has already been completed.")
return {"ok": True, "message": "Update already run today."} return {"ok": True, "message": "Update already run today."}
# 2. Get today's weather data (specifically the Heating Degree Days) # 2. Get today's weather data (specifically the Heating Degree Days)
todays_weather = self._get_weather_for_date(today) todays_weather = self._get_weather_for_date(today)
if not todays_weather: if not todays_weather:
print(f"Error: Weather data for {today} not found. Cannot run update.") logger.info(f"Error: Weather data for {today} not found. Cannot run update.")
return {"ok": False, "message": f"Weather data for {today} not found."} return {"ok": False, "message": f"Weather data for {today} not found."}
# Degree days can't be negative for this calculation. If it's warm, HDD = 0. # Degree days can't be negative for this calculation. If it's warm, HDD = 0.
@@ -94,10 +97,10 @@ class FuelEstimatorCustomer:
).all() ).all()
if not customer_estimates: if not customer_estimates:
print("No active regular delivery customers found.") logger.info("No active regular delivery customers found.")
return {"ok": True, "message": "No active customers to update."} return {"ok": True, "message": "No active customers to update."}
print(f"Staging daily fuel update for {len(customer_estimates)} customers...") logger.info(f"Staging daily fuel update for {len(customer_estimates)} customers...")
corrections_made = 0 corrections_made = 0
@@ -126,7 +129,7 @@ class FuelEstimatorCustomer:
new_update_log = Customer_Update(last_updated=today) new_update_log = Customer_Update(last_updated=today)
self.session.add(new_update_log) self.session.add(new_update_log)
print("Daily update staged. Awaiting commit.") logger.info("Daily update staged. Awaiting commit.")
message = f"Successfully staged updates for {len(customer_estimates)} customers." message = f"Successfully staged updates for {len(customer_estimates)} customers."
if corrections_made > 0: if corrections_made > 0:
message += f" Corrected house factors for {corrections_made} customers." message += f" Corrected house factors for {corrections_made} customers."
@@ -143,11 +146,11 @@ class FuelEstimatorCustomer:
).first() ).first()
if not customer: if not customer:
print(f"Customer {delivery.customer_id} not found.") logger.info(f"Customer {delivery.customer_id} not found.")
return return
if not customer.last_fill: if not customer.last_fill:
print(f"Setting initial K-Factor for new customer {delivery.customer_id} with only one delivery.") logger.info(f"Setting initial K-Factor for new customer {delivery.customer_id} with only one delivery.")
customer.house_factor = self._estimate_initial_house_factor(customer) customer.house_factor = self._estimate_initial_house_factor(customer)
self._update_tank_after_fill(customer, delivery) self._update_tank_after_fill(customer, delivery)
return return
@@ -156,7 +159,7 @@ class FuelEstimatorCustomer:
end_date = delivery.when_delivered end_date = delivery.when_delivered
if start_date >= end_date: if start_date >= end_date:
print(f"Cannot refine K-Factor for customer {delivery.customer_id}: New fill date is not after the last one. Resetting tank only.") logger.info(f"Cannot refine K-Factor for customer {delivery.customer_id}: New fill date is not after the last one. Resetting tank only.")
self._update_tank_after_fill(customer, delivery) self._update_tank_after_fill(customer, delivery)
return return
@@ -174,7 +177,7 @@ class FuelEstimatorCustomer:
gallons_for_heating = delivery.gallons_delivered - total_hot_water_usage gallons_for_heating = delivery.gallons_delivered - total_hot_water_usage
if gallons_for_heating <= 0 or total_hdd == 0: if gallons_for_heating <= 0 or total_hdd == 0:
print(f"Cannot calculate new K-Factor for customer {delivery.customer_id}. (HDD: {total_hdd}, Heating Gallons: {gallons_for_heating}). Resetting tank only.") logger.info(f"Cannot calculate new K-Factor for customer {delivery.customer_id}. (HDD: {total_hdd}, Heating Gallons: {gallons_for_heating}). Resetting tank only.")
self._update_tank_after_fill(customer, delivery) self._update_tank_after_fill(customer, delivery)
return return
@@ -183,13 +186,13 @@ class FuelEstimatorCustomer:
current_k_factor = customer.house_factor current_k_factor = customer.house_factor
smoothed_k_factor = (current_k_factor * K_FACTOR_SMOOTHING_WEIGHT) + (new_k_factor * (Decimal('1.0') - K_FACTOR_SMOOTHING_WEIGHT)) smoothed_k_factor = (current_k_factor * K_FACTOR_SMOOTHING_WEIGHT) + (new_k_factor * (Decimal('1.0') - K_FACTOR_SMOOTHING_WEIGHT))
print(f"Refining K-Factor for Customer ID {customer.customer_id}:") logger.info(f"Refining K-Factor for Customer ID {customer.customer_id}:")
print(f" - Old K-Factor: {current_k_factor:.4f}, New Smoothed K-Factor: {smoothed_k_factor:.4f}") logger.info(f" - Old K-Factor: {current_k_factor:.4f}, New Smoothed K-Factor: {smoothed_k_factor:.4f}")
customer.house_factor = smoothed_k_factor customer.house_factor = smoothed_k_factor
self._update_tank_after_fill(customer, delivery) self._update_tank_after_fill(customer, delivery)
print(f"K-Factor and tank status for Customer {customer.customer_id} staged for update.") logger.info(f"K-Factor and tank status for Customer {customer.customer_id} staged for update.")
def _update_tank_after_fill(self, customer: Customer_estimate_gallons, delivery: Delivery): def _update_tank_after_fill(self, customer: Customer_estimate_gallons, delivery: Delivery):
"""Helper to update customer tank status after a fill-up or partial delivery.""" """Helper to update customer tank status after a fill-up or partial delivery."""

View File

@@ -1,7 +1,10 @@
import logging
from datetime import date from datetime import date
import requests import requests
from decimal import Decimal from decimal import Decimal
logger = logging.getLogger(__name__)
# Import your database model and the session from your database configuration # Import your database model and the session from your database configuration
from app.models.auto import Auto_Temp from app.models.auto import Auto_Temp
from database import session from database import session
@@ -22,11 +25,11 @@ def fetch_and_store_daily_temp() -> bool:
# 1. Check if the temperature for today already exists in the database # 1. Check if the temperature for today already exists in the database
today = date.today() today = date.today()
if session.query(Auto_Temp).filter(Auto_Temp.todays_date == today).first(): if session.query(Auto_Temp).filter(Auto_Temp.todays_date == today).first():
print(f"Temperature for {today} already exists in the database. Skipping fetch.") logger.info(f"Temperature for {today} already exists in the database. Skipping fetch.")
return True return True
# 2. If it doesn't exist, fetch it from the API # 2. If it doesn't exist, fetch it from the API
print(f"Fetching temperature for {today} from OpenWeatherMap...") logger.info(f"Fetching temperature for {today} from OpenWeatherMap...")
try: try:
# API key and location # API key and location
api_key = '21648d8c8d1a4ae495ace0b7810b4d36' api_key = '21648d8c8d1a4ae495ace0b7810b4d36'
@@ -63,11 +66,11 @@ def fetch_and_store_daily_temp() -> bool:
# 4. Add the new record to the session (it will be committed by the calling function) # 4. Add the new record to the session (it will be committed by the calling function)
session.add(add_new_temp) session.add(add_new_temp)
print(f"Successfully fetched and staged temperature for {today}.") logger.info(f"Successfully fetched and staged temperature for {today}.")
return True return True
except Exception as e: except Exception as e:
print(f"An error occurred while fetching weather data: {e}") logger.info(f"An error occurred while fetching weather data: {e}")
# Make sure to rollback the session in case of a partial failure # Make sure to rollback the session in case of a partial failure
session.rollback() session.rollback()
return False return False

View File

@@ -3,7 +3,6 @@ import os
def load_config(mode=os.environ.get('MODE')): def load_config(mode=os.environ.get('MODE')):
try: try:
print(f"mode is {mode}")
if mode == 'PRODUCTION': if mode == 'PRODUCTION':
from settings_prod import ApplicationConfig from settings_prod import ApplicationConfig
return ApplicationConfig return ApplicationConfig

62
main.py
View File

@@ -1,13 +1,57 @@
import logging
import sys
from app.routers import fixstuff_auto from app.routers import fixstuff_auto
from fastapi import FastAPI from fastapi import FastAPI
from app.routers import main, delivery, confirm, fixstuff_customer from app.routers import main, delivery, confirm, fixstuff_customer
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
import os import os
from config import load_config from config import load_config
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
ApplicationConfig = load_config() ApplicationConfig = load_config()
# Configure logging
def setup_logging():
"""Configure structured logging for the application."""
log_level = logging.DEBUG if ApplicationConfig.CURRENT_SETTINGS != 'PRODUCTION' else logging.INFO
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
root_logger = logging.getLogger()
root_logger.setLevel(log_level)
root_logger.handlers.clear()
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(log_level)
console_handler.setFormatter(formatter)
root_logger.addHandler(console_handler)
logging.getLogger('uvicorn.access').setLevel(logging.WARNING)
return logging.getLogger('eamco_auto_api')
logger = setup_logging()
# Database setup
engine = create_engine(ApplicationConfig.SQLALCHEMY_DATABASE_URI)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def check_db_connection():
"""
Test database connectivity.
"""
try:
db = SessionLocal()
db.execute(text("SELECT 1"))
db.close()
return True
except Exception:
return False
app = FastAPI() app = FastAPI()
@@ -33,3 +77,21 @@ app.add_middleware(
@app.get("/") @app.get("/")
def read_root(): def read_root():
return {"Status": "Auto Service is online"} return {"Status": "Auto Service is online"}
@app.on_event("startup")
async def startup_event():
"""Application startup - log configuration and test DB connection."""
logger.info("🚀 eamco_auto_api STARTING")
mode = ApplicationConfig.CURRENT_SETTINGS.upper()
if mode in ['DEVELOPMENT', 'DEV']:
logger.info("🤖🤖🤖🤖🤖 Mode: Development 🤖🤖🤖🤖🤖")
elif mode in ['PRODUCTION', 'PROD']:
logger.info("💀💀💀💀💀💀💀💀💀💀 ⚠️ WARNING PRODUCTION 💀💀💀💀💀💀💀💀💀💀")
logger.info(f"DB: {ApplicationConfig.SQLALCHEMY_DATABASE_URI[:30]}...")
logger.info(f"CORS: {len(ApplicationConfig.origins)} origins configured")
# Test database connection
if check_db_connection():
logger.info("DB Connection: ✅ OK")
else:
logger.info("DB Connection: ❌ FAILED")

View File

@@ -1,5 +1,11 @@
fastapi # eamco_auto_api dependencies
uvicorn[standard] # FastAPI web framework and server
psycopg2-binary fastapi==0.115.6
sqlalchemy uvicorn[standard]==0.34.0
requests
# Database
SQLAlchemy==2.0.40
psycopg2-binary==2.9.10
# HTTP client
requests==2.32.3

View File

@@ -1,29 +1,31 @@
import os
class ApplicationConfig: class ApplicationConfig:
""" """
Basic Configuration for a generic User Local Configuration (LAN deployment)
""" """
CURRENT_SETTINGS = 'LOCAL' CURRENT_SETTINGS = 'LOCAL'
# databases info
POSTGRES_USERNAME = 'postgres'
POSTGRES_PW = 'password'
POSTGRES_SERVER = '192.168.1.204'
POSTGRES_PORT = '5432'
POSTGRES_DBNAME00 = 'auburnoil'
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME,
POSTGRES_PW,
POSTGRES_SERVER,
POSTGRES_DBNAME00
)
# Database credentials from environment variables
POSTGRES_USERNAME = os.environ.get('POSTGRES_USERNAME', 'postgres')
POSTGRES_PW = os.environ.get('POSTGRES_PW')
POSTGRES_SERVER = os.environ.get('POSTGRES_SERVER', '192.168.1.204')
POSTGRES_PORT = os.environ.get('POSTGRES_PORT', '5432')
POSTGRES_DBNAME00 = os.environ.get('POSTGRES_DBNAME', 'auburnoil')
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(
POSTGRES_USERNAME,
POSTGRES_PW,
POSTGRES_SERVER,
POSTGRES_DBNAME00
)
SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI} SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI}
origins = [ origins = [
"http://192.168.1.204:9000", "http://192.168.1.204:9000",
"http://192.168.1.204:9613", "http://192.168.1.204:9613",
"http://192.168.1.204:9614", "http://192.168.1.204:9614",
"http://192.168.1.204:9612", "http://192.168.1.204:9612",
"http://192.168.1.204:9611", "http://192.168.1.204:9611",
] ]

View File

@@ -1,22 +1,28 @@
import os
class ApplicationConfig: class ApplicationConfig:
""" """
Basic Configuration for a generic User Production Configuration
""" """
CURRENT_SETTINGS = 'PRODUCTION' CURRENT_SETTINGS = 'PRODUCTION'
# databases info
POSTGRES_USERNAME = 'postgres' # Database credentials from environment variables
POSTGRES_PW = 'password' POSTGRES_USERNAME = os.environ.get('POSTGRES_USERNAME', 'postgres')
POSTGRES_SERVER = '192.168.1.204' POSTGRES_PW = os.environ.get('POSTGRES_PW')
POSTGRES_PORT = '5432' POSTGRES_SERVER = os.environ.get('POSTGRES_SERVER', '192.168.1.204')
POSTGRES_DBNAME00 = 'auburnoil' POSTGRES_PORT = os.environ.get('POSTGRES_PORT', '5432')
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME, POSTGRES_DBNAME00 = os.environ.get('POSTGRES_DBNAME', 'auburnoil')
POSTGRES_PW,
POSTGRES_SERVER, SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(
POSTGRES_DBNAME00 POSTGRES_USERNAME,
) POSTGRES_PW,
POSTGRES_SERVER,
POSTGRES_DBNAME00
)
SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI} SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI}
origins = [ origins = [
"https://oil.edwineames.com", "https://oil.edwineames.com",
"https://apiauto.edwineames.com", "https://apiauto.edwineames.com",
] ]