"""JustVitamin — PostgreSQL data layer. Creates tables, seeds from jv_data.json on first boot, provides fast filtered queries for the dashboard API.""" import os, json, time, logging from pathlib import Path import psycopg2 from psycopg2.extras import execute_values, RealDictCursor log = logging.getLogger(__name__) DB_URL = os.environ.get( "DATABASE_URL", "postgresql://jv:jvpass@tasks.db:5432/justvitamin" ) # ── Connection ─────────────────────────────────────────────── def get_conn(): return psycopg2.connect(DB_URL) def wait_for_db(retries=30, delay=2): """Block until Postgres is ready.""" for i in range(retries): try: conn = get_conn() conn.close() log.info("✓ Database ready") return True except Exception: if i < retries - 1: log.info(f"Waiting for DB... ({i+1}/{retries})") time.sleep(delay) raise RuntimeError("Database not available after retries") # ── Schema ─────────────────────────────────────────────────── SCHEMA = """ -- Core monthly aggregates CREATE TABLE IF NOT EXISTS monthly ( year_month TEXT PRIMARY KEY, revenue NUMERIC, orders INT, aov NUMERIC, customers INT, new_customers INT, avg_shipping NUMERIC, free_ship_pct NUMERIC, avg_margin NUMERIC, discount_pct NUMERIC, avg_items NUMERIC, total_shipping NUMERIC, free_ship_orders INT, discount_orders INT, total_items INT ); -- Channel breakdown by month CREATE TABLE IF NOT EXISTS channel_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, referrer_source TEXT, orders INT, revenue NUMERIC, avg_aov NUMERIC, avg_items NUMERIC, new_pct NUMERIC, free_ship_pct NUMERIC, discount_pct NUMERIC ); CREATE INDEX IF NOT EXISTS idx_channel_ym ON channel_monthly(year_month); -- New vs returning by month CREATE TABLE IF NOT EXISTS new_returning_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, is_new_customer TEXT, orders INT, revenue NUMERIC ); CREATE INDEX IF NOT EXISTS idx_nr_ym ON new_returning_monthly(year_month); -- Day of week by month CREATE TABLE IF NOT EXISTS dow_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, day_of_week TEXT, orders INT, revenue NUMERIC ); CREATE INDEX IF NOT EXISTS idx_dow_ym ON dow_monthly(year_month); -- Payment processor by month CREATE TABLE IF NOT EXISTS payment_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, payment_processor TEXT, orders INT, revenue NUMERIC ); CREATE INDEX IF NOT EXISTS idx_pay_ym ON payment_monthly(year_month); -- Country by month CREATE TABLE IF NOT EXISTS country_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, customer_country TEXT, orders INT, revenue NUMERIC ); CREATE INDEX IF NOT EXISTS idx_country_ym ON country_monthly(year_month); -- Delivery by month CREATE TABLE IF NOT EXISTS delivery_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, delivery_method TEXT, orders INT, revenue NUMERIC, avg_shipping NUMERIC, avg_aov NUMERIC ); CREATE INDEX IF NOT EXISTS idx_delivery_ym ON delivery_monthly(year_month); -- AOV buckets by month CREATE TABLE IF NOT EXISTS aov_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, aov_bucket TEXT, count INT, revenue NUMERIC, avg_shipping NUMERIC, pct_free_ship NUMERIC ); CREATE INDEX IF NOT EXISTS idx_aov_ym ON aov_monthly(year_month); -- Discount by month CREATE TABLE IF NOT EXISTS discount_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, has_discount TEXT, count INT, avg_aov NUMERIC, avg_items NUMERIC, avg_margin NUMERIC, avg_shipping NUMERIC, revenue NUMERIC ); CREATE INDEX IF NOT EXISTS idx_disc_ym ON discount_monthly(year_month); -- Discount codes by month CREATE TABLE IF NOT EXISTS discount_codes_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, discount_code TEXT, uses INT, revenue NUMERIC, avg_aov NUMERIC, avg_discount_pct NUMERIC ); CREATE INDEX IF NOT EXISTS idx_dcode_ym ON discount_codes_monthly(year_month); -- Free ship comparison by month CREATE TABLE IF NOT EXISTS shipping_comp_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, is_free_shipping TEXT, count INT, avg_aov NUMERIC, avg_items NUMERIC, avg_margin NUMERIC, new_pct NUMERIC, discount_pct NUMERIC, avg_shipping NUMERIC ); CREATE INDEX IF NOT EXISTS idx_shipcomp_ym ON shipping_comp_monthly(year_month); -- Category by month CREATE TABLE IF NOT EXISTS category_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, category TEXT, total_revenue NUMERIC, total_revenue_ex_vat NUMERIC, total_cost NUMERIC, total_qty INT, order_count INT, unique_products INT, margin NUMERIC, margin_pct NUMERIC ); CREATE INDEX IF NOT EXISTS idx_cat_ym ON category_monthly(year_month); -- Product by month CREATE TABLE IF NOT EXISTS product_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, description TEXT, sku_description TEXT, category TEXT, total_revenue NUMERIC, total_revenue_ex_vat NUMERIC, total_cost NUMERIC, total_qty INT, order_count INT, margin NUMERIC, margin_pct NUMERIC ); CREATE INDEX IF NOT EXISTS idx_prod_ym ON product_monthly(year_month); -- Cross-sell pairs (no month dimension) CREATE TABLE IF NOT EXISTS cross_sell ( id SERIAL PRIMARY KEY, product1 TEXT, product2 TEXT, count INT ); -- Cohort retention CREATE TABLE IF NOT EXISTS cohort_retention ( id SERIAL PRIMARY KEY, cohort TEXT, size INT, m0 NUMERIC, m1 NUMERIC, m2 NUMERIC, m3 NUMERIC, m4 NUMERIC, m5 NUMERIC, m6 NUMERIC, m7 NUMERIC, m8 NUMERIC, m9 NUMERIC, m10 NUMERIC, m11 NUMERIC, m12 NUMERIC ); -- Free shipping threshold by month CREATE TABLE IF NOT EXISTS threshold_monthly ( id SERIAL PRIMARY KEY, year_month TEXT, total_orders INT, total_shipping_rev NUMERIC, t15_eligible NUMERIC, t15_ship_absorbed NUMERIC, t15_near NUMERIC, t15_near_gap NUMERIC, t20_eligible NUMERIC, t20_ship_absorbed NUMERIC, t20_near NUMERIC, t20_near_gap NUMERIC, t25_eligible NUMERIC, t25_ship_absorbed NUMERIC, t25_near NUMERIC, t25_near_gap NUMERIC, t30_eligible NUMERIC, t30_ship_absorbed NUMERIC, t30_near NUMERIC, t30_near_gap NUMERIC, t35_eligible NUMERIC, t35_ship_absorbed NUMERIC, t35_near NUMERIC, t35_near_gap NUMERIC, t40_eligible NUMERIC, t40_ship_absorbed NUMERIC, t40_near NUMERIC, t40_near_gap NUMERIC, t50_eligible NUMERIC, t50_ship_absorbed NUMERIC, t50_near NUMERIC, t50_near_gap NUMERIC ); CREATE INDEX IF NOT EXISTS idx_thresh_ym ON threshold_monthly(year_month); -- Metadata CREATE TABLE IF NOT EXISTS meta ( key TEXT PRIMARY KEY, value TEXT ); """ def init_schema(): conn = get_conn() cur = conn.cursor() try: cur.execute(SCHEMA) conn.commit() log.info("✓ Schema created") except Exception as e: conn.rollback() if "already exists" in str(e): log.info("✓ Schema already exists") else: raise finally: cur.close() conn.close() # ── Seed ───────────────────────────────────────────────────── def is_seeded(): conn = get_conn() cur = conn.cursor() cur.execute("SELECT COUNT(*) FROM monthly") n = cur.fetchone()[0] cur.close() conn.close() return n > 0 def seed_from_json(path=None): """Load jv_data.json into Postgres. Idempotent — skips if data exists.""" if is_seeded(): log.info("✓ Database already seeded — skipping") return if path is None: path = Path(__file__).parent / "static" / "dashboard" / "jv_data.json" log.info(f"Seeding from {path} ...") t0 = time.time() with open(path) as f: data = json.load(f) conn = get_conn() cur = conn.cursor() # 1. monthly rows = [(r['YearMonth'], r.get('revenue'), r.get('orders'), r.get('aov'), r.get('customers'), r.get('newCustomers'), r.get('avgShipping'), r.get('freeShipPct'), r.get('avgMargin'), r.get('discountPct'), r.get('avgItems'), r.get('totalShipping'), r.get('freeShipOrders'), r.get('discountOrders'), r.get('totalItems')) for r in data['monthly']] execute_values(cur, "INSERT INTO monthly VALUES %s ON CONFLICT DO NOTHING", rows) # 2. channelMonthly rows = [(r['YearMonth'], r.get('ReferrerSource'), r.get('orders'), r.get('revenue'), r.get('avgAOV'), r.get('avgItems'), r.get('newPct'), r.get('freeShipPct'), r.get('discountPct')) for r in data['channelMonthly']] execute_values(cur, "INSERT INTO channel_monthly (year_month,referrer_source,orders,revenue,avg_aov,avg_items,new_pct,free_ship_pct,discount_pct) VALUES %s", rows) # 3. newReturningMonthly rows = [(r['YearMonth'], r.get('IsNewCustomer'), r.get('orders'), r.get('revenue')) for r in data['newReturningMonthly']] execute_values(cur, "INSERT INTO new_returning_monthly (year_month,is_new_customer,orders,revenue) VALUES %s", rows) # 4. dowMonthly rows = [(r['YearMonth'], r.get('DayOfWeek'), r.get('orders'), r.get('revenue')) for r in data['dowMonthly']] execute_values(cur, "INSERT INTO dow_monthly (year_month,day_of_week,orders,revenue) VALUES %s", rows) # 5. paymentMonthly rows = [(r['YearMonth'], r.get('PaymentProcessor'), r.get('orders'), r.get('revenue')) for r in data['paymentMonthly']] execute_values(cur, "INSERT INTO payment_monthly (year_month,payment_processor,orders,revenue) VALUES %s", rows) # 6. countryMonthly rows = [(r['YearMonth'], r.get('CustomerCountry'), r.get('orders'), r.get('revenue')) for r in data['countryMonthly']] execute_values(cur, "INSERT INTO country_monthly (year_month,customer_country,orders,revenue) VALUES %s", rows) # 7. deliveryMonthly rows = [(r['YearMonth'], r.get('DeliveryMethod'), r.get('orders'), r.get('revenue'), r.get('avgShipping'), r.get('avgAOV')) for r in data['deliveryMonthly']] execute_values(cur, "INSERT INTO delivery_monthly (year_month,delivery_method,orders,revenue,avg_shipping,avg_aov) VALUES %s", rows) # 8. aovMonthly rows = [(r['YearMonth'], r.get('AOVBucket'), r.get('count'), r.get('revenue'), r.get('avgShipping'), r.get('pctFreeShip')) for r in data['aovMonthly']] execute_values(cur, "INSERT INTO aov_monthly (year_month,aov_bucket,count,revenue,avg_shipping,pct_free_ship) VALUES %s", rows) # 9. discountMonthly rows = [(r['YearMonth'], r.get('HasDiscount'), r.get('count'), r.get('avgAOV'), r.get('avgItems'), r.get('avgMargin'), r.get('avgShipping'), r.get('revenue')) for r in data['discountMonthly']] execute_values(cur, "INSERT INTO discount_monthly (year_month,has_discount,count,avg_aov,avg_items,avg_margin,avg_shipping,revenue) VALUES %s", rows) # 10. discountCodesMonthly rows = [(r['YearMonth'], r.get('DiscountCode'), r.get('uses'), r.get('revenue'), r.get('avgAOV'), r.get('avgDiscountPct')) for r in data['discountCodesMonthly']] execute_values(cur, "INSERT INTO discount_codes_monthly (year_month,discount_code,uses,revenue,avg_aov,avg_discount_pct) VALUES %s", rows) # 11. shippingCompMonthly rows = [(r['YearMonth'], r.get('IsFreeShipping'), r.get('count'), r.get('avgAOV'), r.get('avgItems'), r.get('avgMargin'), r.get('newPct'), r.get('discountPct'), r.get('avgShipping')) for r in data['shippingCompMonthly']] execute_values(cur, "INSERT INTO shipping_comp_monthly (year_month,is_free_shipping,count,avg_aov,avg_items,avg_margin,new_pct,discount_pct,avg_shipping) VALUES %s", rows) # 12. categoryMonthly rows = [(r['YearMonth'], r.get('Category'), r.get('totalRevenue'), r.get('totalRevenueExVAT'), r.get('totalCost'), r.get('totalQty'), r.get('orderCount'), r.get('uniqueProducts'), r.get('margin'), r.get('marginPct')) for r in data['categoryMonthly']] execute_values(cur, "INSERT INTO category_monthly (year_month,category,total_revenue,total_revenue_ex_vat,total_cost,total_qty,order_count,unique_products,margin,margin_pct) VALUES %s", rows) # 13. productMonthly rows = [(r['YearMonth'], r.get('Description'), r.get('SKUDescription'), r.get('Category'), r.get('totalRevenue'), r.get('totalRevenueExVAT'), r.get('totalCost'), r.get('totalQty'), r.get('orderCount'), r.get('margin'), r.get('marginPct')) for r in data['productMonthly']] execute_values(cur, "INSERT INTO product_monthly (year_month,description,sku_description,category,total_revenue,total_revenue_ex_vat,total_cost,total_qty,order_count,margin,margin_pct) VALUES %s", rows) # 14. crossSell rows = [(r.get('product1'), r.get('product2'), r.get('count')) for r in data['crossSell']] execute_values(cur, "INSERT INTO cross_sell (product1,product2,count) VALUES %s", rows) # 15. cohortRetention rows = [(r.get('cohort'), r.get('size'), r.get('m0'), r.get('m1'), r.get('m2'), r.get('m3'), r.get('m4'), r.get('m5'), r.get('m6'), r.get('m7'), r.get('m8'), r.get('m9'), r.get('m10'), r.get('m11'), r.get('m12')) for r in data['cohortRetention']] execute_values(cur, "INSERT INTO cohort_retention (cohort,size,m0,m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11,m12) VALUES %s", rows) # 16. thresholdMonthly rows = [(r['YearMonth'], r.get('totalOrders'), r.get('totalShippingRev'), r.get('t15_eligible'), r.get('t15_shipAbsorbed'), r.get('t15_near'), r.get('t15_nearGap'), r.get('t20_eligible'), r.get('t20_shipAbsorbed'), r.get('t20_near'), r.get('t20_nearGap'), r.get('t25_eligible'), r.get('t25_shipAbsorbed'), r.get('t25_near'), r.get('t25_nearGap'), r.get('t30_eligible'), r.get('t30_shipAbsorbed'), r.get('t30_near'), r.get('t30_nearGap'), r.get('t35_eligible'), r.get('t35_shipAbsorbed'), r.get('t35_near'), r.get('t35_nearGap'), r.get('t40_eligible'), r.get('t40_shipAbsorbed'), r.get('t40_near'), r.get('t40_nearGap'), r.get('t50_eligible'), r.get('t50_shipAbsorbed'), r.get('t50_near'), r.get('t50_nearGap')) for r in data['thresholdMonthly']] execute_values(cur, "INSERT INTO threshold_monthly (year_month,total_orders,total_shipping_rev,t15_eligible,t15_ship_absorbed,t15_near,t15_near_gap,t20_eligible,t20_ship_absorbed,t20_near,t20_near_gap,t25_eligible,t25_ship_absorbed,t25_near,t25_near_gap,t30_eligible,t30_ship_absorbed,t30_near,t30_near_gap,t35_eligible,t35_ship_absorbed,t35_near,t35_near_gap,t40_eligible,t40_ship_absorbed,t40_near,t40_near_gap,t50_eligible,t50_ship_absorbed,t50_near,t50_near_gap) VALUES %s", rows) # 17. meta meta = data.get('meta', {}) for k, v in meta.items(): cur.execute( "INSERT INTO meta (key, value) VALUES (%s, %s) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value", (k, json.dumps(v) if not isinstance(v, str) else v) ) conn.commit() cur.close() conn.close() elapsed = time.time() - t0 log.info(f"✓ Seeded {sum(len(data[k]) for k in data if isinstance(data[k], list))} rows in {elapsed:.1f}s") # ── Queries ────────────────────────────────────────────────── def _ym_clause(start, end, col="year_month"): """Build WHERE clause for date range.""" parts = [] params = [] if start: parts.append(f"{col} >= %s") params.append(start) if end: parts.append(f"{col} <= %s") params.append(end) where = " AND ".join(parts) if parts else "TRUE" return where, params def _query(sql, params=()): conn = get_conn() cur = conn.cursor(cursor_factory=RealDictCursor) cur.execute(sql, params) rows = cur.fetchall() cur.close() conn.close() # Convert Decimal to float for JSON serialisation return [_dec_to_float(r) for r in rows] def _dec_to_float(d): from decimal import Decimal return {k: (float(v) if isinstance(v, Decimal) else v) for k, v in d.items()} def get_meta(): rows = _query("SELECT key, value FROM meta") meta = {} for r in rows: v = r['value'] try: v = json.loads(v) except (json.JSONDecodeError, TypeError): pass meta[r['key']] = v return meta def _remap(rows, mapping): """Rename DB snake_case keys back to the camelCase the dashboard JS expects.""" return [{mapping.get(k, k): v for k, v in r.items()} for r in rows] # Column mappings: DB name → JS name _M_MONTHLY = { 'year_month':'YearMonth','revenue':'revenue','orders':'orders','aov':'aov', 'customers':'customers','new_customers':'newCustomers','avg_shipping':'avgShipping', 'free_ship_pct':'freeShipPct','avg_margin':'avgMargin','discount_pct':'discountPct', 'avg_items':'avgItems','total_shipping':'totalShipping','free_ship_orders':'freeShipOrders', 'discount_orders':'discountOrders','total_items':'totalItems' } _M_CHANNEL = { 'year_month':'YearMonth','referrer_source':'ReferrerSource','orders':'orders', 'revenue':'revenue','avg_aov':'avgAOV','avg_items':'avgItems', 'new_pct':'newPct','free_ship_pct':'freeShipPct','discount_pct':'discountPct' } _M_NR = {'year_month':'YearMonth','is_new_customer':'IsNewCustomer','orders':'orders','revenue':'revenue'} _M_DOW = {'year_month':'YearMonth','day_of_week':'DayOfWeek','orders':'orders','revenue':'revenue'} _M_PAY = {'year_month':'YearMonth','payment_processor':'PaymentProcessor','orders':'orders','revenue':'revenue'} _M_COUNTRY = {'year_month':'YearMonth','customer_country':'CustomerCountry','orders':'orders','revenue':'revenue'} _M_DELIVERY = { 'year_month':'YearMonth','delivery_method':'DeliveryMethod','orders':'orders', 'revenue':'revenue','avg_shipping':'avgShipping','avg_aov':'avgAOV' } _M_AOV = { 'year_month':'YearMonth','aov_bucket':'AOVBucket','count':'count', 'revenue':'revenue','avg_shipping':'avgShipping','pct_free_ship':'pctFreeShip' } _M_DISC = { 'year_month':'YearMonth','has_discount':'HasDiscount','count':'count', 'avg_aov':'avgAOV','avg_items':'avgItems','avg_margin':'avgMargin', 'avg_shipping':'avgShipping','revenue':'revenue' } _M_DCODE = { 'year_month':'YearMonth','discount_code':'DiscountCode','uses':'uses', 'revenue':'revenue','avg_aov':'avgAOV','avg_discount_pct':'avgDiscountPct' } _M_SHIPCOMP = { 'year_month':'YearMonth','is_free_shipping':'IsFreeShipping','count':'count', 'avg_aov':'avgAOV','avg_items':'avgItems','avg_margin':'avgMargin', 'new_pct':'newPct','discount_pct':'discountPct','avg_shipping':'avgShipping' } _M_CAT = { 'year_month':'YearMonth','category':'Category','total_revenue':'totalRevenue', 'total_revenue_ex_vat':'totalRevenueExVAT','total_cost':'totalCost', 'total_qty':'totalQty','order_count':'orderCount','unique_products':'uniqueProducts', 'margin':'margin','margin_pct':'marginPct' } _M_PROD = { 'year_month':'YearMonth','description':'Description','sku_description':'SKUDescription', 'category':'Category','total_revenue':'totalRevenue','total_revenue_ex_vat':'totalRevenueExVAT', 'total_cost':'totalCost','total_qty':'totalQty','order_count':'orderCount', 'margin':'margin','margin_pct':'marginPct' } _M_CROSS = {'product1':'product1','product2':'product2','count':'count'} _M_COHORT = { 'cohort':'cohort','size':'size', 'm0':'m0','m1':'m1','m2':'m2','m3':'m3','m4':'m4','m5':'m5', 'm6':'m6','m7':'m7','m8':'m8','m9':'m9','m10':'m10','m11':'m11','m12':'m12' } _M_THRESH = { 'year_month':'YearMonth','total_orders':'totalOrders','total_shipping_rev':'totalShippingRev', 't15_eligible':'t15_eligible','t15_ship_absorbed':'t15_shipAbsorbed','t15_near':'t15_near','t15_near_gap':'t15_nearGap', 't20_eligible':'t20_eligible','t20_ship_absorbed':'t20_shipAbsorbed','t20_near':'t20_near','t20_near_gap':'t20_nearGap', 't25_eligible':'t25_eligible','t25_ship_absorbed':'t25_shipAbsorbed','t25_near':'t25_near','t25_near_gap':'t25_nearGap', 't30_eligible':'t30_eligible','t30_ship_absorbed':'t30_shipAbsorbed','t30_near':'t30_near','t30_near_gap':'t30_nearGap', 't35_eligible':'t35_eligible','t35_ship_absorbed':'t35_shipAbsorbed','t35_near':'t35_near','t35_near_gap':'t35_nearGap', 't40_eligible':'t40_eligible','t40_ship_absorbed':'t40_shipAbsorbed','t40_near':'t40_near','t40_near_gap':'t40_nearGap', 't50_eligible':'t50_eligible','t50_ship_absorbed':'t50_shipAbsorbed','t50_near':'t50_near','t50_near_gap':'t50_nearGap', } def get_dashboard_data(start=None, end=None): """Return the full dashboard payload, filtered by date range. Returns the same JSON shape the frontend RAW variable expects.""" w, p = _ym_clause(start, end) result = { 'monthly': _remap(_query(f"SELECT * FROM monthly WHERE {w} ORDER BY year_month", p), _M_MONTHLY), 'channelMonthly': _remap(_query(f"SELECT year_month,referrer_source,orders,revenue,avg_aov,avg_items,new_pct,free_ship_pct,discount_pct FROM channel_monthly WHERE {w} ORDER BY year_month", p), _M_CHANNEL), 'newReturningMonthly': _remap(_query(f"SELECT year_month,CAST(is_new_customer AS INTEGER) as is_new_customer,orders,revenue FROM new_returning_monthly WHERE {w} ORDER BY year_month", p), _M_NR), 'dowMonthly': _remap(_query(f"SELECT year_month,day_of_week,orders,revenue FROM dow_monthly WHERE {w} ORDER BY year_month", p), _M_DOW), 'paymentMonthly': _remap(_query(f"SELECT year_month,payment_processor,orders,revenue FROM payment_monthly WHERE {w} ORDER BY year_month", p), _M_PAY), 'countryMonthly': _remap(_query(f"SELECT year_month,customer_country,orders,revenue FROM country_monthly WHERE {w} ORDER BY year_month", p), _M_COUNTRY), 'deliveryMonthly': _remap(_query(f"SELECT year_month,delivery_method,orders,revenue,avg_shipping,avg_aov FROM delivery_monthly WHERE {w} ORDER BY year_month", p), _M_DELIVERY), 'aovMonthly': _remap(_query(f"SELECT year_month,aov_bucket,count,revenue,avg_shipping,pct_free_ship FROM aov_monthly WHERE {w} ORDER BY year_month", p), _M_AOV), 'discountMonthly': _remap(_query(f"SELECT year_month,CAST(has_discount AS INTEGER) as has_discount,count,avg_aov,avg_items,avg_margin,avg_shipping,revenue FROM discount_monthly WHERE {w} ORDER BY year_month", p), _M_DISC), 'discountCodesMonthly': _remap(_query(f"SELECT year_month,discount_code,uses,revenue,avg_aov,avg_discount_pct FROM discount_codes_monthly WHERE {w} ORDER BY year_month", p), _M_DCODE), 'shippingCompMonthly': _remap(_query(f"SELECT year_month,CAST(is_free_shipping AS INTEGER) as is_free_shipping,count,avg_aov,avg_items,avg_margin,new_pct,discount_pct,avg_shipping FROM shipping_comp_monthly WHERE {w} ORDER BY year_month", p), _M_SHIPCOMP), 'categoryMonthly': _remap(_query(f"SELECT year_month,category,total_revenue,total_revenue_ex_vat,total_cost,total_qty,order_count,unique_products,margin,margin_pct FROM category_monthly WHERE {w} ORDER BY year_month", p), _M_CAT), 'productMonthly': _remap(_query(f"SELECT year_month,description,sku_description,category,total_revenue,total_revenue_ex_vat,total_cost,total_qty,order_count,margin,margin_pct FROM product_monthly WHERE {w} ORDER BY year_month", p), _M_PROD), # Non-date-filtered 'crossSell': _remap(_query("SELECT product1,product2,count FROM cross_sell ORDER BY count DESC"), _M_CROSS), 'cohortRetention': _remap(_query("SELECT cohort,size,m0,m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11,m12 FROM cohort_retention ORDER BY cohort"), _M_COHORT), 'thresholdMonthly': _remap(_query(f"SELECT * FROM threshold_monthly WHERE {w} ORDER BY year_month", p), _M_THRESH), 'meta': get_meta(), } # Strip 'id' key from all rows (serial PKs not needed by frontend) for k, v in result.items(): if isinstance(v, list): result[k] = [{kk: vv for kk, vv in row.items() if kk != 'id'} for row in v] return result # ── Init ───────────────────────────────────────────────────── def init_db(): """Wait for DB, create schema, seed if needed. Uses advisory lock to prevent races.""" wait_for_db() conn = get_conn() cur = conn.cursor() try: # Advisory lock so only one worker seeds cur.execute("SELECT pg_advisory_lock(42)") init_schema() seed_from_json() finally: cur.execute("SELECT pg_advisory_unlock(42)") cur.close() conn.close()