Spaces:
Running
Running
File size: 7,643 Bytes
ca81019 df692ec ca81019 df692ec ca81019 df692ec 7507549 df692ec ca81019 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | """Dev-only: seed a LOCAL dashboard with clickable batch + booking leads (overlay image, outline
geometry, pricing, prior edit) so the address popup / drag-to-edit outline / measurement history
can be browser-tested WITHOUT running the real (model-heavy) pipeline.
./.venv/Scripts/python.exe scripts/dev_seed.py
# then open http://127.0.0.1:8000/dashboard?token=devtoken (Leads screen β click an address)
Standalone β nothing here ships to prod. It runs the app in-process (so the in-memory viz cache
is seeded too) with a dev-token login (no Clerk) and an in-memory SQLite DB.
"""
from __future__ import annotations
import json
import os
import uuid
# Dev env must be set BEFORE importing the app (config is read at import).
os.environ.setdefault("WARM_MODEL_ON_STARTUP", "0") # skip the 3-model load
os.environ.setdefault("DASHBOARD_DEV_TOKEN", "devtoken")
os.environ.setdefault("DASHBOARD_DEV_TENANT", "thelawnstandard")
os.environ.setdefault("ALLOWED_API_KEYS", "dev:devkey")
os.environ.setdefault("WIDGET_API_KEYS", "thelawnstandard:pub-dev") # tenant:publishable β /widget?key=pub-dev
os.environ.setdefault("ADMIN_DEV_TOKEN", "admintoken") # /admin?token=admintoken
os.environ.setdefault("PLATFORM_ADMIN_EMAILS", "admin@localhost")
os.environ.setdefault("LAWN_DB_PATH", ":memory:")
for _k in ("DATABASE_URL", "CLERK_SECRET_KEY", "CLERK_PUBLISHABLE_KEY"):
os.environ.pop(_k, None) # force the dev-token gate, local SQLite
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from lawn_estimator import api
TENANT = os.environ["DASHBOARD_DEV_TENANT"]
TOKEN = os.environ["DASHBOARD_DEV_TOKEN"]
def _overlay_png(path, poly):
"""A faux 'lawn overlay' (aerial-ish background + the measured lawn) so the popup image +
drag editor render. The frontend draws its own editable SVG polygon on top of this."""
fig = plt.figure(figsize=(6.4, 6.4), dpi=200) # 1280x1280
ax = fig.add_axes([0, 0, 1, 1])
ax.set_xlim(0, 1280)
ax.set_ylim(1280, 0)
ax.axis("off")
ax.add_patch(plt.Rectangle((0, 0), 1280, 1280, color="#5c6b4f"))
xs = [p[0] for p in poly] + [poly[0][0]]
ys = [p[1] for p in poly] + [poly[0][1]]
ax.fill(xs, ys, color="#38c860", alpha=0.35)
ax.plot(xs, ys, color="#2e7d32", lw=3)
fig.savefig(path)
plt.close(fig)
def _seed_batch(address, zip_code, sqft, poly):
rid = uuid.uuid4().hex[:12]
pricing = {"currency": "USD", "line_items": [
{"service": "mow", "label": "Mowing", "price": round(sqft / 1000 * 10, 2)},
{"service": "fert", "label": "Fertilizer", "price": round(sqft / 1000 * 7, 2)}]}
price_total = round(sum(li["price"] for li in pricing["line_items"]), 2)
api.METER.record(TENANT, address, api.BATCH, lawn_sqft=sqft, result_id=rid, zip=zip_code,
method="lidar+rgb", confidence="high")
detail = json.dumps({"lawn_polygon_px": poly, "pixel_area_sqft": 0.35, "image_size_px": [1280, 1280],
"pricing": pricing, "original_sqft": sqft, "original_pricing": pricing})
api.LEADS.record(TENANT, "batch", address, email="ops@thelawnstandard.com", zip=zip_code,
lawn_sqft=sqft, price_total=price_total, confidence="high", result_id=rid, detail=detail)
api.OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
overlay = api.OUTPUT_DIR / f"dev_{rid}.png"
_overlay_png(overlay, poly)
api._remember_viz(rid, overlay, overlay, TENANT) # viz + overlay = the placeholder; owner=tenant
return rid
def seed():
if not api.USERS.by_clerk_id(api.DASHBOARD_DEV_CLERK_ID):
api.USERS.create(TENANT, api.DASHBOARD_DEV_CLERK_ID, "dev@localhost", "owner", status="active")
r1 = _seed_batch("1423 N 75th St, Omaha, NE 68134", "68134", 5200.0,
[[300, 320], [980, 300], [1010, 980], [280, 1000]])
# a batch lead that already carries a prior tenant edit, so its popup shows Original + Edit + Revert
r2 = _seed_batch("7863 N 144th Ave, Bennington, NE 68007", "68007", 8100.0,
[[260, 260], [1020, 300], [980, 1010], [300, 980]])
api.MEDITS.record(TENANT, r2, 8600.0, kind="tenant", original_sqft=8100.0,
address="7863 N 144th Ave, Bennington, NE 68007", user_id=1)
api._persist_measurement_edit(TENANT, r2, 8600.0,
{"currency": "USD", "line_items": [{"service": "mow", "label": "Mowing", "price": 86.0}]})
# A team member (Team screen) + a pending invite.
if not api.USERS.by_clerk_id("clerk_staff"):
api.USERS.create(TENANT, "clerk_staff", "staff@thelawnstandard.com", "staff", status="active")
if not api.USERS.pending_by_email("invitee@thelawnstandard.com"):
api.USERS.invite(TENANT, "invitee@thelawnstandard.com", "staff", invited_by=1)
# A widget-origin quote β lead β booking with long-format line items (Leads + Overview + popup).
rw = uuid.uuid4().hex[:12]
api.METER.record(TENANT, "5534 Mayberry St, Omaha, NE 68106", api.SINGLE, lawn_sqft=4200.0,
result_id=rw, zip="68106", method="lidar+rgb", confidence="high")
api.LEADS.record(TENANT, "widget", "5534 Mayberry St, Omaha, NE 68106", name="Sam Homeowner",
email="sam@example.com", phone="402-555-0100", zip="68106", result_id=rw)
api.LEADS.record(TENANT, "booking", "5534 Mayberry St, Omaha, NE 68106", name="Sam Homeowner",
email="sam@example.com", zip="68106", lawn_sqft=4200.0, price_total=84.0,
result_id=rw, detail=json.dumps({"original_sqft": 4200.0,
"bundle": {"id": "spring", "name": "Spring Package", "cadence": "seasonal"},
"pricing": {"line_items": [{"service": "mow", "label": "Mowing", "price": 42.0},
{"service": "fert", "label": "Fertilizer", "price": 42.0}]}}))
api.BOOKING_ITEMS.record_many(TENANT, rw, [
{"service": "mow", "label": "Mowing", "price": 42.0, "purchased": True},
{"service": "fert", "label": "Fertilizer", "price": 42.0, "purchased": True},
{"service": "aerate", "label": "Aeration", "price": 120.0, "purchased": False}])
# Backdated runs so the Overview volume sparkline shows a trend (raw insert β record() can't backdate).
import random
from datetime import date, timedelta
zips = ["68106", "68134", "68164", "68007", "68135"]
for i in range(1, 15):
d = (date.today() - timedelta(days=i)).isoformat() + "T12:00:00.000Z"
for _ in range(random.randint(0, 4)):
oc = random.choices(["ok", "rejected", "error"], weights=[85, 10, 5])[0]
api.METER._db.execute(
"INSERT INTO runs (tenant_id, surface, address, address_norm, zip, billable, "
"method, confidence, lawn_sqft, duration_s, outcome, created_at) "
"VALUES (?, ?, ?, ?, ?, ?, 'lidar+rgb', 'high', ?, ?, ?, ?)",
(TENANT, random.choice(["single", "batch"]), f"seed {i}", f"seed {i}",
random.choice(zips), 1, random.uniform(2500, 9000), random.uniform(18, 55), oc, d))
api.METER._db.commit()
print(f"\n Seeded β http://127.0.0.1:8000/dashboard?token={TOKEN} (widget: /widget?key=pub-dev)")
print(" Leads β click an address (batch: view+drag-edit+history/revert; booking: view).")
print(f" results: {r1} (fresh), {r2} (has a tenant edit), {rw} (widget booking)\n")
if __name__ == "__main__":
seed()
import uvicorn
uvicorn.run(api.app, host="127.0.0.1", port=8000, log_level="warning")
|