feat: 5-tier pricing, market ticker integration, and delivery stats
Major update spanning pricing, market data, and analytics: - Pricing: Replace single-price service fees with 5-tier pricing for same-day, prime, and emergency deliveries across create/edit/finalize - Market: Add Ticker_Price and CompanyPrice models with endpoints for live commodity prices (HO, CL, RB) and competitor price tracking - Stats: Add daily/weekly/monthly gallons endpoints with multi-year comparison and YoY totals for the stats dashboard - Delivery: Add map and history endpoints, fix finalize null-driver crash - Schema: Change fill_location from INTEGER to VARCHAR(250), add pre_load normalization for customer updates, fix admin auth check Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,9 +7,14 @@ from app.classes.pricing import Pricing_Oil_Oil, Pricing_Oil_Oil_schema
|
||||
from app.classes.admin import Admin_Company
|
||||
from app.classes.delivery import Delivery_Delivery
|
||||
from app.classes.service import Service_Service
|
||||
from app.classes.ticker import Ticker_Price, Ticker_Price_Schema
|
||||
from app.classes.competitor import CompanyPrice
|
||||
|
||||
from flask_login import login_required
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from datetime import datetime, timedelta
|
||||
from flask import request
|
||||
|
||||
|
||||
@info.route("/price/oil/tiers", methods=["GET"])
|
||||
@@ -84,3 +89,116 @@ def get_company():
|
||||
'name': get_data_company.company_name,
|
||||
'telephone': get_data_company.company_phone_number,
|
||||
})
|
||||
|
||||
|
||||
@info.route("/price/ticker", methods=["GET"])
|
||||
@login_required
|
||||
def get_ticker_prices():
|
||||
"""Get latest stock/commodity ticker prices."""
|
||||
logger.info("GET /info/price/ticker - Fetching ticker prices")
|
||||
|
||||
# We want the latest price for each symbol
|
||||
# HO=F, CL=F, RB=F
|
||||
target_symbols = ["HO=F", "CL=F", "RB=F"]
|
||||
results = {}
|
||||
|
||||
# 1. Fetch Market Tickers
|
||||
for symbol in target_symbols:
|
||||
latest = (db.session.query(Ticker_Price)
|
||||
.filter(Ticker_Price.symbol == symbol)
|
||||
.order_by(Ticker_Price.timestamp.desc())
|
||||
.first())
|
||||
|
||||
if latest:
|
||||
results[symbol] = latest.to_dict()
|
||||
|
||||
# 2. Fetch Competitor Prices
|
||||
# Focusing on LMT Oil and Charlton Oil as requested, but fetching others too for completeness
|
||||
competitors = [
|
||||
"LMT OIL",
|
||||
"CHARLTON OIL",
|
||||
"LEBLANC OIL",
|
||||
"ALS OIL",
|
||||
"VALUE OIL",
|
||||
"DADDY'S OIL"
|
||||
]
|
||||
|
||||
for comp_name in competitors:
|
||||
latest_comp = (db.session.query(CompanyPrice)
|
||||
.filter(CompanyPrice.company_name.ilike(f"%{comp_name}%"))
|
||||
.order_by(CompanyPrice.scrape_date.desc())
|
||||
.first())
|
||||
|
||||
if latest_comp:
|
||||
# Map to Ticker format for uniform frontend handling if possible, or distinct
|
||||
# For simplicity, we'll just add them to the dictionary.
|
||||
# Convert name to a key like 'LMT' or keep full name
|
||||
key = comp_name.replace(" OIL", "").replace("'S", "").replace(" ", "")
|
||||
results[key] = {
|
||||
"symbol": comp_name, # Use full name as symbol/label
|
||||
"price": float(latest_comp.price_decimal),
|
||||
"currency": "USD",
|
||||
"change": 0.0, # We'd need history to calc this, skipping for now
|
||||
"percent_change": 0.0,
|
||||
"timestamp": latest_comp.scrape_date.isoformat()
|
||||
}
|
||||
|
||||
return success_response({"tickers": results})
|
||||
|
||||
|
||||
@info.route("/price/ticker/history", methods=["GET"])
|
||||
@login_required
|
||||
def get_ticker_history():
|
||||
"""
|
||||
Get historical ticker prices for charting.
|
||||
|
||||
Query Params:
|
||||
days (int): Number of days to look back (default 30)
|
||||
|
||||
Returns:
|
||||
List of daily price points for each ticker.
|
||||
"""
|
||||
try:
|
||||
days = int(request.args.get('days', 30))
|
||||
except ValueError:
|
||||
days = 30
|
||||
|
||||
logger.info(f"GET /info/price/ticker/history - Fetching history for {days} days")
|
||||
|
||||
start_date = datetime.utcnow() - timedelta(days=days)
|
||||
|
||||
# Fetch all records since start_date
|
||||
# We want to group by date and symbol, taking the last price of the day
|
||||
records = (db.session.query(Ticker_Price)
|
||||
.filter(Ticker_Price.timestamp >= start_date)
|
||||
.order_by(Ticker_Price.timestamp.asc())
|
||||
.all())
|
||||
|
||||
# Organize data structure for Chart.js
|
||||
# { date: { symbol: price, symbol2: price } }
|
||||
daily_data = {}
|
||||
|
||||
target_symbols = ["HO=F", "CL=F", "RB=F"]
|
||||
|
||||
for record in records:
|
||||
if record.symbol not in target_symbols:
|
||||
continue
|
||||
|
||||
date_str = record.timestamp.strftime('%Y-%m-%d')
|
||||
|
||||
if date_str not in daily_data:
|
||||
daily_data[date_str] = {}
|
||||
|
||||
# This acts as "last value for the day" since we ordered by asc timestamp
|
||||
daily_data[date_str][record.symbol] = float(record.price_decimal)
|
||||
|
||||
# Convert to list for frontend
|
||||
# [ { date: '2023-01-01', prices: { 'HO=F': 2.5, ... } }, ... ]
|
||||
result = []
|
||||
for date_str in sorted(daily_data.keys()):
|
||||
result.append({
|
||||
"date": date_str,
|
||||
"prices": daily_data[date_str]
|
||||
})
|
||||
|
||||
return success_response({"history": result})
|
||||
|
||||
Reference in New Issue
Block a user