Add Polaris FastAPI backend: models/schemas, pricing+scoring+order router, importer, Celery tasks, API routers, seed script
This commit is contained in:
4
backend/scripts/celery_worker.sh
Executable file
4
backend/scripts/celery_worker.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
# Celery worker + beat entrypoint (used by the OPS agent's compose service).
|
||||
# Beat schedule is defined in app/tasks.py (sync_inventory every 5min).
|
||||
exec celery -A app.tasks.celery_app worker --beat --loglevel=info
|
||||
159
backend/scripts/seed.py
Normal file
159
backend/scripts/seed.py
Normal file
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Seed the sample supplier, import its catalog, price + publish valid products.
|
||||
|
||||
Idempotent: safe to re-run — supplier/catalog are upserted, prices recalculated.
|
||||
|
||||
Usage: python3 scripts/seed.py
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
# allow running from repo root or scripts/
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from decimal import ROUND_HALF_UP, Decimal
|
||||
|
||||
from app.database import SessionLocal
|
||||
from app.engines.importer import import_feed
|
||||
from app.engines.pricing import calculate_price
|
||||
from app.engines.suppliers.base import build_adapter
|
||||
from app.models import (
|
||||
AuditLog,
|
||||
PriceHistory,
|
||||
Supplier,
|
||||
SupplierPerformance,
|
||||
SupplierProduct,
|
||||
)
|
||||
|
||||
SAMPLE_NAME = "Sample Home & Gadgets"
|
||||
|
||||
|
||||
def ensure_supplier(db):
|
||||
supplier = db.query(Supplier).filter(Supplier.name == SAMPLE_NAME).first()
|
||||
if supplier is None:
|
||||
supplier = Supplier(
|
||||
name=SAMPLE_NAME,
|
||||
adapter_type="sample",
|
||||
shipping_regions=["US", "CA"],
|
||||
reliability_score=92.0,
|
||||
status="active",
|
||||
)
|
||||
db.add(supplier)
|
||||
db.flush()
|
||||
print(f"[seed] created supplier {supplier.id}")
|
||||
else:
|
||||
supplier.status = "active"
|
||||
print(f"[seed] supplier already exists: {supplier.id}")
|
||||
return supplier
|
||||
|
||||
|
||||
def ensure_performance(db, supplier):
|
||||
if db.query(SupplierPerformance).filter(SupplierPerformance.supplier_id == supplier.id).first() is None:
|
||||
db.add(
|
||||
SupplierPerformance(
|
||||
supplier_id=supplier.id,
|
||||
fulfillment_rate=98.0,
|
||||
avg_shipping_days=4.0,
|
||||
cancellation_rate=1.0,
|
||||
stock_accuracy=97.0,
|
||||
return_rate=2.0,
|
||||
)
|
||||
)
|
||||
db.flush()
|
||||
|
||||
|
||||
def price_and_publish(db, supplier):
|
||||
"""Price every linked product via the pricing engine; publish valid ones.
|
||||
|
||||
NEVER publish invalid: products that fail rule lookup or end up with a
|
||||
non-positive retail price stay un-published.
|
||||
"""
|
||||
links = (
|
||||
db.query(SupplierProduct)
|
||||
.filter(SupplierProduct.supplier_id == supplier.id)
|
||||
.all()
|
||||
)
|
||||
published = 0
|
||||
skipped = []
|
||||
for sp in links:
|
||||
product = sp.product
|
||||
if product is None:
|
||||
continue
|
||||
quote = calculate_price(
|
||||
db,
|
||||
product.cost,
|
||||
shipping=sp.shipping_cost or 0,
|
||||
fees=0,
|
||||
min_price=product.min_price,
|
||||
)
|
||||
if quote is None:
|
||||
skipped.append({"sku": product.sku, "reason": "no active price rule"})
|
||||
continue
|
||||
if quote["retail"] <= 0:
|
||||
skipped.append({"sku": product.sku, "reason": "non-positive retail"})
|
||||
continue
|
||||
|
||||
product.retail_price = quote["retail"]
|
||||
product.min_price = (quote["retail"] * Decimal("0.90")).quantize(
|
||||
Decimal("0.01"), rounding=ROUND_HALF_UP
|
||||
)
|
||||
product.max_price = (quote["retail"] * Decimal("1.15")).quantize(
|
||||
Decimal("0.01"), rounding=ROUND_HALF_UP
|
||||
)
|
||||
product.status = "price_calculated"
|
||||
db.add(PriceHistory(product_id=product.id, price=quote["retail"], reason="auto"))
|
||||
|
||||
# published only when priced
|
||||
product.status = "published"
|
||||
published += 1
|
||||
db.add(
|
||||
AuditLog(
|
||||
actor="seed",
|
||||
action="product_published",
|
||||
entity="product",
|
||||
entity_id=str(product.id),
|
||||
detail={"sku": product.sku, "retail_price": float(quote["retail"])},
|
||||
)
|
||||
)
|
||||
|
||||
db.commit()
|
||||
print(f"[seed] priced + published {published} products; skipped: {skipped}")
|
||||
|
||||
|
||||
def main():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
supplier = ensure_supplier(db)
|
||||
db.commit()
|
||||
ensure_performance(db, supplier)
|
||||
db.commit()
|
||||
|
||||
summary = import_feed(db, supplier, feed_type="sample", data_or_url="")
|
||||
print(
|
||||
f"[seed] import: received={summary['received']} "
|
||||
f"imported={summary['imported']} updated={summary['updated']} "
|
||||
f"skipped={len(summary['skipped'])}"
|
||||
)
|
||||
if summary["skipped"]:
|
||||
print(f"[seed] skipped details: {summary['skipped']}")
|
||||
|
||||
price_and_publish(db, supplier)
|
||||
|
||||
# quick self-check on a known item ($80-ish cost band)
|
||||
probe = db.query(SupplierProduct).filter(
|
||||
SupplierProduct.supplier_id == supplier.id,
|
||||
SupplierProduct.supplier_sku == "SPL-1006",
|
||||
).first()
|
||||
if probe and probe.product:
|
||||
p = probe.product
|
||||
print(
|
||||
f"[seed] sanity: {p.sku} cost={p.cost} retail={p.retail_price} "
|
||||
f"status={p.status}"
|
||||
)
|
||||
print("[seed] done")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user