#!/usr/bin/env python3 """Polaris mock API — serves /api endpoints for FRONTEND VERIFICATION ONLY. Run: python3 mock_api.py [port] (default 8000) Not part of the frontend Docker image (.dockerignore excludes mock/). Mimics the contract in CONTRACT.md against db/schema.sql shapes. """ import json import random import time import uuid from datetime import datetime, timedelta, timezone from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.parse import parse_qs, urlparse ADMIN_EMAIL = "admin@polaris.shop" ADMIN_PASSWORD = "polaris-admin" TOKEN = "mock-jwt-payload-not-for-production" CATS = ["Home", "Gadgets", "Office", "Audio"] PRODUCT_SEEDS = [ ("Aurora Desk Lamp", "Home", "AuroraLight", 12.50, 38.50), ("Nimbus Smart Scale", "Home", "NimbusLab", 18.20, 52.00), ("Polaris Pro Keyboard", "Gadgets", "Keystroke", 24.90, 79.00), ("Stellar Mouse MX", "Gadgets", "Keystroke", 11.30, 44.00), ("Comet USB-C Hub", "Gadgets", "PortWorks", 15.60, 59.00), ("Galaxy Standing Desk", "Office", "WorkSpan", 145.00, 389.00), ("Nova Ergonomic Chair", "Office", "WorkSpan", 98.00, 279.00), ("Lunar Notebook Set", "Office", "PaperNest", 4.80, 16.50), ("Eclipse Headphones", "Audio", "SoundOrbit", 32.00, 129.00), ("Pulsar Bluetooth Speaker", "Audio", "SoundOrbit", 21.50, 74.00), ("Quasar Webcam 4K", "Gadgets", "ClearSight", 39.00, 119.00), ("Vega Monitor Arm", "Office", "WorkSpan", 27.00, 89.00), ] LIFECYCLE = [ "discovered", "analyzing", "approved", "imported", "content_generated", "price_calculated", "quality_check", "published", "published", "published", "monitored", "paused", ] STATUS_FLOW = ["new", "paid", "fraud_check", "supplier_order", "confirmed", "shipped", "delivered"] def now_iso(days_ago=0, hours_ago=0): return (datetime.now(timezone.utc) - timedelta(days=days_ago, hours=hours_ago)).isoformat() def build_products(): products = [] for i, (title, cat, brand, cost, retail) in enumerate(PRODUCT_SEEDS, start=1): products.append({ "id": f"prd-{i:03d}", "sku": f"POL-{i:04d}", "title": title, "description": f"{title} — sourced, vetted and dynamically priced by the Polaris pipeline. Ships from a top-scoring supplier with live tracking.", "category": cat, "brand": brand, "manufacturer": brand, "upc_ean_gtin": f"8{i:013d}", "images": [], "dimensions": {"l_cm": 20, "w_cm": 14, "h_cm": 8}, "weight_grams": 300 + i * 40, "cost": cost, "retail_price": retail, "min_price": round(cost * 1.8, 2), "max_price": round(retail * 1.35, 2), "status": LIFECYCLE[i % len(LIFECYCLE)], "lifecycle": {"steps_completed": i % 8}, "created_at": now_iso(days_ago=20 - i % 14), "updated_at": now_iso(hours_ago=i), }) return products PRODUCTS = build_products() SUPPLIERS = [ {"id": "sup-001", "name": "Starline Wholesale", "adapter_type": "csv_http", "api_endpoint": "https://feeds.starline.example/products.csv", "reliability_score": 0.94, "status": "active", "created_at": now_iso(days_ago=40)}, {"id": "sup-002", "name": "NorthBridge Supply", "adapter_type": "xml_ftp", "api_endpoint": "ftp://nb.example/feed.xml", "reliability_score": 0.87, "status": "active", "created_at": now_iso(days_ago=31)}, {"id": "sup-003", "name": "Meridian Goods", "adapter_type": "json_api", "api_endpoint": "https://api.meridian.example/v2", "reliability_score": 0.71, "status": "paused", "created_at": now_iso(days_ago=12)}, ] SUPPLIER_PERF = [ {"supplier_id": "sup-001", "supplier_name": "Starline Wholesale", "fulfillment_rate": 0.96, "avg_shipping_days": 2.1, "cancellation_rate": 0.02, "stock_accuracy": 0.93, "return_rate": 0.03, "updated_at": now_iso()}, {"supplier_id": "sup-002", "supplier_name": "NorthBridge Supply", "fulfillment_rate": 0.89, "avg_shipping_days": 3.4, "cancellation_rate": 0.07, "stock_accuracy": 0.84, "return_rate": 0.05, "updated_at": now_iso()}, {"supplier_id": "sup-003", "supplier_name": "Meridian Goods", "fulfillment_rate": 0.72, "avg_shipping_days": 5.2, "cancellation_rate": 0.16, "stock_accuracy": 0.66, "return_rate": 0.09, "updated_at": now_iso()}, ] ORDERS = [ { "id": "ord-1001", "order_number": "POL-1001", "customer_id": "cus-1", "customer_email": "ada@example.com", "items": [{"product_id": "prd-001", "qty": 2, "unit_price": 38.50, "title": "Aurora Desk Lamp"}], "retail_total": 77.00, "supplier_id": "sup-001", "supplier_cost": 25.00, "shipping_cost": 6.99, "fees": 1.5, "profit": 43.51, "status": "delivered", "tracking": "1Z999AA10123456784", "carrier": "UPS", "created_at": now_iso(days_ago=6), "updated_at": now_iso(days_ago=3), }, { "id": "ord-1002", "order_number": "POL-1002", "customer_id": "cus-2", "customer_email": "grace@example.com", "items": [{"product_id": "prd-003", "qty": 1, "unit_price": 79.00, "title": "Polaris Pro Keyboard"}, {"product_id": "prd-004", "qty": 1, "unit_price": 44.00, "title": "Stellar Mouse MX"}], "retail_total": 123.00, "supplier_id": "sup-002", "supplier_cost": 36.20, "shipping_cost": 0.0, "fees": 2.4, "profit": 84.40, "status": "shipped", "tracking": "9400100000000000000001", "carrier": "USPS", "created_at": now_iso(days_ago=1, hours_ago=5), "updated_at": now_iso(hours_ago=2), }, { "id": "ord-1003", "order_number": "POL-1003", "customer_id": "cus-3", "customer_email": "linus@example.com", "items": [{"product_id": "prd-009", "qty": 1, "unit_price": 129.00, "title": "Eclipse Headphones"}], "retail_total": 129.00, "supplier_id": "sup-001", "supplier_cost": 32.00, "shipping_cost": 0.0, "fees": 2.5, "profit": 94.50, "status": "confirmed", "tracking": None, "carrier": None, "created_at": now_iso(hours_ago=9), "updated_at": now_iso(hours_ago=4), }, { "id": "ord-1004", "order_number": "POL-1004", "customer_id": "cus-4", "customer_email": "katherine@example.com", "items": [{"product_id": "prd-010", "qty": 2, "unit_price": 74.00, "title": "Pulsar Bluetooth Speaker"}], "retail_total": 148.00, "supplier_id": "sup-002", "supplier_cost": 43.00, "shipping_cost": 7.99, "fees": 2.9, "profit": 94.11, "status": "fraud_check", "tracking": None, "carrier": None, "created_at": now_iso(hours_ago=1), "updated_at": now_iso(hours_ago=1), }, ] def summary_payload(): return { "revenue": 4210.44, "orders": 17, "gross_profit": 987.12, "net_profit": 812.90, "margin": 19.3, "aov": 247.67, "conversion": 3.1, "refunds": 2, "refunded_orders": 2, } def top_products_payload(): rows = [ ("prd-009", "Eclipse Headphones", "POL-0009", 6, 774.00, 582.00), ("prd-003", "Polaris Pro Keyboard", "POL-0003", 5, 395.00, 270.50), ("prd-001", "Aurora Desk Lamp", "POL-0001", 8, 308.00, 208.00), ("prd-006", "Galaxy Standing Desk", "POL-0006", 1, 389.00, 244.00), ("prd-010", "Pulsar Bluetooth Speaker", "POL-0010", 4, 296.00, 210.00), ] return [ {"product_id": pid, "title": t, "sku": sku, "units_sold": u, "revenue": r, "profit": p} for pid, t, sku, u, r, p in rows ] class Handler(BaseHTTPRequestHandler): server_version = "PolarisMock/0.1" def log_message(self, fmt, *args): # quieter logs pass # ── helpers ── def send_json(self, obj, status=200): body = json.dumps(obj).encode() self.send_response(status) self.send_header("Content-Type", "application/json") self.send_header("Cache-Control", "no-store") self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def read_body(self): length = int(self.headers.get("Content-Length") or 0) if not length: return {} try: return json.loads(self.rfile.read(length)) except Exception: return {} def not_found(self): self.send_json({"detail": "Not found"}, 404) # ── routing ── def do_OPTIONS(self): self.send_response(204) self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") self.send_header("Access-Control-Allow-Headers", "Authorization, Content-Type, Cache-Control") self.end_headers() def do_GET(self): u = urlparse(self.path) path = u.path.rstrip("/") or "/" q = parse_qs(u.query) if path == "/api/health": return self.send_json({"status": "ok", "service": "polaris-api-mock", "version": "0.1.0"}) if path == "/api/products": status = (q.get("status") or [None])[0] cat = (q.get("category") or [None])[0] search = (q.get("search") or [None])[0] try: limit = int((q.get("limit") or ["100"])[0]) except ValueError: limit = 100 rows = [p for p in PRODUCTS if not status or p["status"] == status] if cat: rows = [p for p in rows if p["category"] == cat] if search: s = search.lower() rows = [p for p in rows if s in p["title"].lower() or s in p["sku"].lower()] return self.send_json(rows[:limit]) if path.startswith("/api/products/") and path.count("/") == 3 and path.endswith("/price-history"): pid = path.split("/")[3] prod = next((p for p in PRODUCTS if p["id"] == pid), None) if not prod: return self.not_found() retail = prod["retail_price"] or 50 history = [] for i in range(8): history.append({ "price": round(retail * (0.86 + 0.14 * (i / 7)) + random.uniform(-1.5, 1.5), 2), "reason": "market reprice" if i % 2 else "initial pricing", "created_at": now_iso(days_ago=7 - i), }) return self.send_json(history) if path.startswith("/api/products/"): pid = path.split("/")[3] prod = next((p for p in PRODUCTS if p["id"] == pid), None) return self.send_json(prod) if prod else self.not_found() if path == "/api/suppliers": return self.send_json(SUPPLIERS) if path == "/api/orders": return self.send_json(ORDERS) if path.startswith("/api/orders/"): oid = path.split("/")[3] order = next((o for o in ORDERS if o["id"] == oid or o["order_number"] == oid), None) return self.send_json(order) if order else self.not_found() if path == "/api/analytics/summary": return self.send_json(summary_payload()) if path == "/api/analytics/top-products": return self.send_json(top_products_payload()) if path == "/api/analytics/supplier-performance": return self.send_json(SUPPLIER_PERF) return self.not_found() def do_POST(self): u = urlparse(self.path) path = u.path.rstrip("/") or "/" body = self.read_body() if path == "/api/auth/login": email = (body.get("email") or "").strip().lower() password = body.get("password") or "" if email == ADMIN_EMAIL and password == ADMIN_PASSWORD: return self.send_json({"access_token": TOKEN, "token_type": "bearer", "email": email}) return self.send_json({"detail": "Invalid email or password"}, 401) if path == "/api/auth/register": return self.send_json({"access_token": TOKEN, "token_type": "bearer", "email": body.get("email")}) if path == "/api/orders": email = body.get("customer_email") or "guest@example.com" items = [] total = 0.0 for it in body.get("items") or []: prod = next((p for p in PRODUCTS if p["id"] == it.get("product_id")), None) qty = max(1, int(it.get("qty") or 1)) price = (prod["retail_price"] if prod else 29.99) total += price * qty items.append({"product_id": it.get("product_id"), "qty": qty, "unit_price": price, "title": prod["title"] if prod else None}) order = { "id": f"ord-{random.randint(2000, 9999)}", "order_number": f"POL-{random.randint(100000, 999999)}", "customer_id": None, "customer_email": email, "items": items, "retail_total": round(total, 2), "supplier_id": "sup-001", "supplier_cost": None, "shipping_cost": 0.0, "fees": 0.0, "profit": None, "status": "new", "tracking": None, "carrier": None, "created_at": now_iso(), "updated_at": now_iso(), } ORDERS.insert(0, order) return self.send_json(order, 201) if path.startswith("/api/orders/") and path.endswith("/tracking"): oid = path.split("/")[3] order = next((o for o in ORDERS if o["id"] == oid or o["order_number"] == oid), None) if not order: return self.not_found() idx = STATUS_FLOW.index(order["status"]) if order["status"] in STATUS_FLOW else 0 order["status"] = STATUS_FLOW[min(idx + 1, len(STATUS_FLOW) - 1)] if order["status"] in ("shipped", "delivered") and not order["tracking"]: order["tracking"] = f"1Z{random.randint(10**15, 10**16 - 1)}" order["carrier"] = "UPS" order["updated_at"] = now_iso() return self.send_json(order) if path.startswith("/api/products/") and path.count("/") == 3 and ( path.endswith("/publish") or path.endswith("/pause") ): pid = path.split("/")[3] prod = next((p for p in PRODUCTS if p["id"] == pid), None) if not prod: return self.not_found() prod["status"] = "published" if path.endswith("/publish") else "paused" prod["updated_at"] = now_iso() return self.send_json(prod) if path == "/api/products/import": imported = random.randint(3, 9) for i in range(imported): title = f"Imported Feed Item {len(PRODUCTS) + 1}" PRODUCTS.append({ "id": f"prd-{100 + len(PRODUCTS)}", "sku": f"IMP-{len(PRODUCTS):04d}", "title": title, "description": "Imported via feed — awaiting pipeline analysis.", "category": random.choice(CATS), "brand": body.get("supplier_id") or "FeedImport", "manufacturer": None, "upc_ean_gtin": None, "images": [], "dimensions": None, "weight_grams": None, "cost": 0.0, "retail_price": None, "min_price": None, "max_price": None, "status": "discovered", "lifecycle": {}, "created_at": now_iso(), "updated_at": now_iso(), }) return self.send_json({"message": f"Feed accepted — {imported} products staged for the pipeline.", "imported": imported}, 201) return self.not_found() if __name__ == "__main__": port = int(__import__("sys").argv[1]) if len(__import__("sys").argv) > 1 else 8000 print(f"Polaris mock API on :{port} (admin: {ADMIN_EMAIL} / {ADMIN_PASSWORD})") HTTPServer(("0.0.0.0", port), Handler).serve_forever()