v3: PostgreSQL backend — 20K rows seeded, server-side SQL filtering
- Added PostgreSQL 16 Alpine service to Docker Swarm stack - db.py: schema for 17 tables, auto-seed from jv_data.json on first boot - /api/dashboard/data?start=&end= — server-side SQL filtering All Time: 0.43s (was 4MB JSON download stuck loading) Filtered (12mo): 0.20s with ~90% less data transferred - Dashboard HTML patched: calls API instead of static JSON - Integer casting for IsNewCustomer/HasDiscount/IsFreeShipping - Advisory lock prevents race condition during parallel worker startup - Returning Revenue now shows correctly: £14.5M (75% of total)
This commit is contained in:
60
app.py
60
app.py
@@ -1,7 +1,8 @@
|
||||
"""JustVitamin × QuikCue — AI Content Engine
|
||||
Live proposal site with real Gemini-powered demos."""
|
||||
Live proposal site with real Gemini-powered demos.
|
||||
PostgreSQL-backed data dashboard."""
|
||||
|
||||
import os, json, time, traceback
|
||||
import os, json, time, traceback, logging
|
||||
from pathlib import Path
|
||||
from flask import (Flask, render_template, jsonify, request,
|
||||
send_from_directory, redirect)
|
||||
@@ -9,6 +10,9 @@ from flask import (Flask, render_template, jsonify, request,
|
||||
from scraper import scrape_product, scrape_competitor
|
||||
from ai_engine import (generate_asset_pack, competitor_xray, pdp_surgeon,
|
||||
optimise_pdp_copy, generate_product_images)
|
||||
from db import init_db, get_dashboard_data, get_meta
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
|
||||
|
||||
app = Flask(__name__,
|
||||
static_folder="static",
|
||||
@@ -17,6 +21,13 @@ app = Flask(__name__,
|
||||
GEN_DIR = Path(__file__).parent / "generated"
|
||||
GEN_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# ── Init DB on startup ───────────────────────────────────────
|
||||
with app.app_context():
|
||||
try:
|
||||
init_db()
|
||||
except Exception as e:
|
||||
logging.error(f"DB init failed: {e}")
|
||||
|
||||
# ── Page routes ──────────────────────────────────────────────
|
||||
|
||||
@app.route("/")
|
||||
@@ -32,6 +43,37 @@ def dashboard():
|
||||
def dashboard_files(filename):
|
||||
return send_from_directory("static/dashboard", filename)
|
||||
|
||||
|
||||
# ── Dashboard API (Postgres-backed) ─────────────────────────
|
||||
|
||||
@app.route("/api/dashboard/data")
|
||||
def api_dashboard_data():
|
||||
"""Return filtered dashboard data from PostgreSQL.
|
||||
Query params: ?start=YYYY-MM&end=YYYY-MM
|
||||
Replaces the 4MB static JSON with fast server-side SQL filtering."""
|
||||
start = request.args.get("start") or None
|
||||
end = request.args.get("end") or None
|
||||
try:
|
||||
t0 = time.time()
|
||||
data = get_dashboard_data(start, end)
|
||||
data["_query_time"] = f"{time.time()-t0:.3f}s"
|
||||
data["_source"] = "postgresql"
|
||||
resp = jsonify(data)
|
||||
resp.headers['Cache-Control'] = 'public, max-age=60'
|
||||
return resp
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route("/api/dashboard/meta")
|
||||
def api_dashboard_meta():
|
||||
"""Return just the meta info (fast, tiny)."""
|
||||
try:
|
||||
return jsonify(get_meta())
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/proposal")
|
||||
def proposal():
|
||||
return send_from_directory("static/proposal", "index.html")
|
||||
@@ -185,9 +227,23 @@ def api_generate_images():
|
||||
|
||||
@app.route("/api/health")
|
||||
def health():
|
||||
db_ok = False
|
||||
try:
|
||||
from db import get_conn
|
||||
conn = get_conn()
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT COUNT(*) FROM monthly")
|
||||
count = cur.fetchone()[0]
|
||||
cur.close()
|
||||
conn.close()
|
||||
db_ok = count > 0
|
||||
except Exception:
|
||||
pass
|
||||
return jsonify({
|
||||
"status": "ok",
|
||||
"gemini_key_set": bool(os.environ.get("GEMINI_API_KEY")),
|
||||
"db_connected": db_ok,
|
||||
"db_rows": count if db_ok else 0,
|
||||
})
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user