[object Object]

← back to Lifestyle Asset Intel

initial scaffold — package, env, pm2, gitignore, blueprint, roadmap

18d04fefa00e0899689bf2973b90d593b0be4b72 · 2026-05-09 21:36:26 -0700 · Steve Abrams

v0 of lifestyle-asset-intel — enterprise valuation/liquidity/ownership-cost
platform per the pasted blueprint. This commit lays in the project shell:
package.json + lockfile, .env.example with the macOS-socket PG pattern, pm2
ecosystem on port 9820, .gitignore per Steve's standing rule, full
BLUEPRINT.md verbatim, and ROADMAP.md mapping the path from this skeleton
to the v2 multi-vertical (handbags → watches → sneakers → jewelry) target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 18d04fefa00e0899689bf2973b90d593b0be4b72
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat May 9 21:36:26 2026 -0700

    initial scaffold — package, env, pm2, gitignore, blueprint, roadmap
    
    v0 of lifestyle-asset-intel — enterprise valuation/liquidity/ownership-cost
    platform per the pasted blueprint. This commit lays in the project shell:
    package.json + lockfile, .env.example with the macOS-socket PG pattern, pm2
    ecosystem on port 9820, .gitignore per Steve's standing rule, full
    BLUEPRINT.md verbatim, and ROADMAP.md mapping the path from this skeleton
    to the v2 multi-vertical (handbags → watches → sneakers → jewelry) target.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 .env.example        |   20 +
 .gitignore          |   14 +
 BLUEPRINT.md        |  284 ++++++++++++
 README.md           |   78 ++++
 ROADMAP.md          |  121 ++++++
 ecosystem.config.js |   21 +
 package-lock.json   | 1201 +++++++++++++++++++++++++++++++++++++++++++++++++++
 package.json        |   30 ++
 8 files changed, 1769 insertions(+)

diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..40125e3
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,20 @@
+PORT=9820
+NODE_ENV=development
+PUBLIC_URL=http://localhost:9820
+
+# Postgres — standalone db for this project, NOT dw_unified
+# On macOS Homebrew PG, /tmp is the unix-socket dir → peer auth works without a password.
+# On Linux/Kamatera override PGHOST to the actual socket dir or 127.0.0.1 + PGPASSWORD.
+PGHOST=/tmp
+PGPORT=5432
+PGDATABASE=lifestyle_asset_intel
+PGUSER=stevestudio2
+
+# Sessions (only used once auth lands; v0 is internal-only)
+SESSION_SECRET=dev-secret-change-me
+ADMIN_EMAIL=steve@designerwallcoverings.com
+
+# TODO (v0.1): once a brand domain is chosen, register info@<domain> at Purelymail
+# and set CONTACT_EMAIL accordingly. See ROADMAP.md.
+CONTACT_EMAIL=
+BRAND_DOMAIN=
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e703288
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+node_modules/
+.env
+.env.*
+!.env.example
+*.log
+.DS_Store
+.vscode/
+.idea/
+dist/
+build/
+.next/
+tmp/
+uploads/
+coverage/
diff --git a/BLUEPRINT.md b/BLUEPRINT.md
new file mode 100644
index 0000000..6c5f2b4
--- /dev/null
+++ b/BLUEPRINT.md
@@ -0,0 +1,284 @@
+# Enterprise Blueprint for a Lifestyle Asset Intelligence Platform
+
+## Executive summary
+
+An enterprise-grade Lifestyle Asset Intelligence platform should launch as a transaction-normalized valuation, liquidity, and ownership-cost system for handbags first, then expand into watches, jewelry, and only later broader apparel. The reason is simple: high-end handbags already have a relatively rich public market structure consisting of official auction-result pages, curated marketplace inventory, quote-generation systems, and durable configuration families such as Birkin size, leather, hardware, and color. For Birkin specifically, urlSotheby’shttps://www.sothebys.com/en/ says it has sold nearly $160 million in Birkin and Kelly bags since 2021; its classic Birkin 30 in Togo averaged about $22,300 in 2025, up 6% from 2024 and 9% from 2023, while pristine Togo Birkin 25/30 bags continued to trade roughly in the $25,000 to $30,000 band and at about 2.4x retail in 2024. That combination of comparables, premiums, and persistent demand is exactly what a valuation platform needs for its first enterprise wedge. citeturn43view0turn44view1turn43view1turn43view3
+
+The right product is not “what can I list this for?” but “what is the most defensible current net realizable value, how liquid is it, how much confidence do we have, and what will ownership likely cost over a defined hold period?” That means every estimate should be output as a bundle: gross market value, expected net seller proceeds, confidence interval, liquidity score, expected days-to-sell, authenticity risk, and a time-series curve that can be viewed like a stock chart. The platform should also support configuration-level indices such as “Birkin 25 neutrals,” “Birkin 30 Togo gold/black,” “Sellier premium index,” and “recent-date-stamp premium,” because auction evidence shows that smaller sizes, neutrals/pastels, Sellier construction, Togo/Epsom leathers, and recent date stamps outperform broader averages. citeturn43view0turn43view2
+
+From a systems perspective, the best fit is an event-driven lakehouse and search architecture: raw capture and provenance storage; canonical asset resolution; transaction normalization; vector-plus-keyword search; low-latency feature serving; quantile valuation models; multimodal matching; and enterprise APIs secured with OIDC/OAuth plus SAML and SCIM for provisioning. The supporting platform technologies are well suited to this pattern: Kafka streams for exactly-once event handling, Iceberg for schema evolution and time-travel friendly analytical storage, OpenSearch for vector retrieval, Feast for online/offline feature serving, Temporal for durable workflow execution, Kubernetes for autoscaling, OpenTelemetry and Prometheus Alertmanager for observability, and MLflow for model lifecycle governance. citeturn38view1turn38view0turn37view1turn37view2turn37view3turn37view4turn38view2turn37view5turn37view6turn37view7turn38view5turn39view0turn38view6turn38view7
+
+## Product scope and market-data strategy
+
+The platform should ship four product surfaces from the same core data model: an enterprise valuation API, an internal market-ops console, a portfolio and benchmark dashboard, and periodic research products such as indices and market reports. The first release should be configuration-specific and operational rather than editorial: quote an item from images and metadata, explain the comps, show the curve, surface liquidity, and compute exit proceeds after marketplace-specific fees, shipping, and expected frictional costs. A second release should add portfolio monitoring and benchmark indices. A third should add white-labeled widgets and API packaging for lenders, insurers, resellers, and marketplaces. This sequencing mirrors where public source quality is strongest and where enterprise willingness to pay is highest. citeturn43view0turn35view2turn45view1turn32view0
+
+The source hierarchy should be explicit and opinionated. Tier 1 should be official closed-lot auction data from urlSotheby’shttps://www.sothebys.com/en/ and urlChristie’shttps://www.christies.com/, because those venues publish realized results, sale totals, and category-level performance. Tier 2 should be verified resale-market signals from urlStockXhttps://stockx.com/, urlChrono24https://www.chrono24.com/, urlFASHIONPHILEhttps://www.fashionphile.com/, urlRebaghttps://www.rebag.com/, and urlThe RealRealhttps://www.therealreal.com/, because they either expose sales history, sold-item archives, pricing algorithms based on similar sold items, or quote-generation systems based on large internal datasets. Tier 3 should be broad-market discovery and long-tail inventory from urleBayhttps://www.ebay.com/. Tier 4 should be direct dealer quotes and enterprise partner feeds, which are valuable but should be treated as quote or floor signals unless they are linked to realized transactions. citeturn24view0turn24search4turn45view0turn45view1turn35view2turn35view1turn20search0turn32view0turn41view1turn32view1turn32view2turn32view3turn36view0turn36view1
+
+One of the most important practical facts is that urlHermèshttps://www.hermes.com/us/en/ sells the Kelly, Birkin, and Constance “exclusively” in stores; that means the platform cannot depend on a normal retail e-commerce feed for the most important benchmark products. MSRP history for those lines therefore has to be backfilled from boutique receipts, specialist price trackers, client uploads, and monitored market commentary from high-trust experts such as auction houses. The same rule applies to any luxury product family where primary-market access is intentionally opaque. citeturn12search12turn44view3
+
+The source caveats should materially affect valuation weights. urlStockXhttps://stockx.com/ is powerful precisely because it is new-condition only and requires rigorous packaging and accessory completeness for Hermès Birkin and Kelly bags; that makes it a cleaner source for “store-fresh” market value but a poor source for used-condition curves. urleBayhttps://www.ebay.com/ Browse search returns active fixed-price listings by default, while sales history lives in Marketplace Insights, which eBay says is a limited-release API and “not open to new users at this time.” urlChrono24https://www.chrono24.com/, by contrast, is especially useful for watches because its public ChronoPulse index is based on more than 600,000 transactions since January 2019 and its Watch Collection updates estimated values daily. Those source differences are exactly why a single unweighted “market price” is not sufficient. citeturn29view3turn36view2turn36view1turn36view0turn35view2turn35view1
+
+### Recommended source-priority model
+
+| Priority | Source class | Primary use | Weight in price engine |
+|---|---|---|---|
+| Highest | Closed auction results | Ground-truth realized pricing and premium behavior | Highest |
+| High | Verified sold-item archives and market-history panels | Current clean comps, listing-to-sale transitions, spread analysis | High |
+| Medium | Instant quotes, buyout offers, consignment recommendations | Liquidity floor, dealer appetite, seller-proceeds modeling | Medium |
+| Medium | Active listings | Inventory pressure, ask curves, assortment depth | Medium-low |
+| Lower | User-entered receipts and boutique observations | MSRP history, provenance, accessory completeness | Contextual |
+| Lower | Opportunistic web discovery | Recall and coverage expansion | Low until verified |
+
+This weighting is supported by the way auction houses publish realized outcomes, resale platforms publish condition-specific inventory and pricing mechanics, and quote platforms explicitly distinguish offers from final realized value. citeturn24view0turn32view0turn32view1turn32view2turn32view3turn45view0
+
+## Canonical data model and normalization
+
+The core object in the system should be a **canonical asset configuration**, not a marketplace listing. A canonical asset record should include brand, model family, sub-line, size, material, finish, color, hardware, edition, construction, reference/SKU when applicable, year or date stamp, accessories included, and region. For handbags, that means resolving something like “Birkin 30 / Togo / Gold / GHW / Retourne / 2024 / full set / U.S.” into a stable asset key. For watches, it means reference number, dial, bracelet, box/papers, service history, and market region. For sneakers, it means SKU, size, release date, and condition. The valuation engine then estimates value for the canonical asset and applies condition, completeness, and marketability adjustments at the instance level. citeturn43view2turn35view2turn45view0turn36view2
+
+### Canonical field set
+
+| Field group | Example fields | Why it matters |
+|---|---|---|
+| Identity | brand, family, model, sub-line, reference/SKU | Prevents cross-listing alias collisions |
+| Physical spec | size, material, finish, color, hardware | Drives most of the price variance |
+| Time | production year, date stamp, acquisition date | Recent stamps often command premiums |
+| Condition | standardized grade, wear facets, repair/restoration | Biggest determinant in secondary pricing |
+| Completeness | box, dust bag, receipt, lock/key, raincoat, papers | Affects authenticity confidence and saleability |
+| Geography | listing region, ship-from, currency, duties context | Needed for all-in landed cost and net proceeds |
+| Source lineage | source name, source listing ID, scrape/API timestamp | Needed for auditability and confidence |
+| Commercials | hammer, buyer’s premium, taxes, seller fees, shipping | Converts gross signals to comparable net values |
+| Trust/risk | authenticity risk, seller quality, image-match score | Needed to down-weight noisy signals |
+
+Condition normalization is non-negotiable. urlFASHIONPHILEhttps://www.fashionphile.com/ explicitly distinguishes Giftable, New, Excellent, Shows Wear, Worn, Fair, and Flawed; urlThe RealRealhttps://www.therealreal.com/ uses Pristine, Excellent, Very Good, Good, Fair, and As Is; urlStockXhttps://stockx.com/ accepts bags only in new condition and, for Birkin/Kelly, requires the original box, tissue, dust bags, felt protector, lock-package dust bag, and raincoat. The platform should therefore store both an **ordinal normalized grade** and **facet-level condition attributes** such as corner wear, hardware scratching, handle darkening, odor, lining wear, shape loss, and repair history. That lets the model price “Very Good with clean interior and moderate feet wear” differently from “Very Good with odor and handle glazing issues,” even if a source collapses both into one grade. citeturn29view2turn29view1turn29view3
+
+Commercial normalization should separate gross buyer prices from seller-realizable value. Auction houses often have hammer plus buyer’s premium; marketplaces often show asks, “last sale,” or recommended listing prices; quote systems show what a platform is willing to pay now; and cross-border channels introduce duties and shipping. A defensible enterprise record therefore needs at least four derived price measures for each observation: `gross_transaction_price`, `all_in_buyer_price`, `expected_net_seller_proceeds`, and `normalized_market_value`. Quote signals should enter the system as **instant-liquidity bids**, not as final settled comps. That distinction is supported by the fact that urlRebaghttps://www.rebag.com/ offers instant Clair quotes and separate Consign/Trade/Buyout modes, while urlThe RealRealhttps://www.therealreal.com/ says its pricing algorithm uses market trends, demand signals, search data, and similar items sold.
+
+A practical confidence score should be compositional rather than cosmetic. A useful first-pass formulation is:
+
+```text
+confidence =
+  source_quality_weight
++ comparable_similarity_weight
++ sample_depth_weight
++ recency_weight
++ image_match_weight
+- authenticity_risk_penalty
+- condition_uncertainty_penalty
+- region_gap_penalty
+- fee_model_uncertainty_penalty
+```
+
+That score should be visible everywhere the platform shows a price. A lender or insurer should be able to say, “This value is lower confidence because there were only three high-similarity comps in-region, the condition came from images only, and authenticity risk is elevated.”
+
+## System architecture and machine learning stack
+
+The architecture should be built around four layers: **collection**, **canonicalization**, **modeling**, and **serving**. Collection captures listings, quote responses, auction results, images, and metadata. Canonicalization resolves the source record into a stable asset configuration and normalized transaction schema. Modeling creates features, value curves, liquidity signals, and risk scores. Serving exposes these through APIs, dashboards, and report pipelines. The architecture below is the cleanest enterprise-grade path because it preserves source provenance while still enabling low-latency product experiences.
+
+```mermaid
+flowchart LR
+    A[Auction connectors] --> R[Raw capture store]
+    B[Marketplace connectors] --> R
+    C[Quote and dealer feeds] --> R
+    D[Retail/MSRP monitors] --> R
+    E[Image uploads] --> R
+
+    R --> N[Normalization and asset resolver]
+    N --> T[Canonical transactions]
+    N --> M[Canonical master assets]
+    N --> V[Vector/image embeddings]
+
+    T --> F[Feature store]
+    M --> F
+    V --> S[Search index]
+
+    F --> P[Price and liquidity models]
+    F --> Q[Authenticity risk models]
+    V --> U[Image SKU matcher]
+
+    P --> G[Valuation API]
+    Q --> G
+    U --> G
+    S --> G
+
+    G --> H[Portfolio and dashboard UI]
+    G --> I[Index and report publisher]
+    G --> J[Enterprise API and webhooks]
+```
+
+For the data plane, Kafka is a strong fit because exactly-once semantics are valuable when duplicate events would corrupt pricing, benchmark curves, or customer-visible valuations. Kafka’s own documentation explicitly highlights exactly-once guarantees and notes that production exactly-once defaults expect a three-broker cluster. For analytical storage, Iceberg is attractive because it supports add/drop/rename/reorder/update schema evolution without file rewrites and tracks columns by unique IDs, which is ideal when marketplace schemas change. For search and similarity, OpenSearch `knn_vector` fields enable vector retrieval and can be tuned for in-memory or on-disk workloads. For feature consistency, Feast gives the usual enterprise split between historical offline features and low-latency online serving. Temporal is a strong orchestration choice for source polling, enrichment, and revaluation jobs because it provides crash-proof, resumable workflow execution. Kubernetes then provides autoscaled runtime isolation for collectors, APIs, and model services, while OpenTelemetry plus Prometheus Alertmanager provides the traces, logs, metrics, grouping, routing, and alerting needed for enterprise operations. MLflow closes the loop by providing lineage, versioning, aliases, and promotion paths for models.
+
+### Recommended service map
+
+| Service | Responsibility | Notes |
+|---|---|---|
+| Source connector service | API clients, browser agents, parsers, delta polling | Separate by source family |
+| Asset resolver | Alias mapping, schema normalization, canonical asset IDs | Central to all downstream accuracy |
+| Pricing engine | Comparable retrieval, adjustments, quantile valuation | Must return explanation payload |
+| Liquidity engine | sell-through, time-to-sell, spread, bid/ask depth | Separate from price |
+| Image intelligence service | embedding generation, candidate retrieval, SKU match | Feeds resolver and UX |
+| Authenticity risk service | multimodal fraud and anomaly scoring | Never replace human review for edge cases |
+| Portfolio service | holdings, P&L, ownership-cost and exposure analytics | Enterprise dashboard core |
+| Index publisher | benchmark curves, family indices, cohort analytics | Report and API monetization |
+| Auth gateway | tenant isolation, RBAC, OIDC/SAML, audit logs, SCIM sync | Needed early for enterprise sales |
+
+The modeling stack should be modular. For value estimation, start with gradient-boosted tabular models because residual structure in luxury resale is dominated by high-cardinality categorical and interaction features like size × material × color × region × condition. XGBoost remains a strong baseline for that kind of sparse, mixed tabular problem. For image-based candidate retrieval, CLIP-style multimodal embeddings are the obvious launch choice because they provide strong image-text representations and support zero-shot transfer; combined with vector search, they make a practical image-first intake experience possible. A good production pattern is: CLIP embedding retrieval for top-`k` candidates, then a second-stage tabular ranker or cross-encoder for final candidate selection, then a valuation model over standardized features. Authenticity risk should be framed as **risk scoring**, not binary truth: visual anomaly detection, metadata inconsistency scoring, provenance gaps, accessory mismatch, and seller-pattern outliers should route items to human inspection when thresholds are breached.
+
+Update cadence should be designed around source type, not a universal cron.
+
+| Artifact | Recommended cadence |
+|---|---|
+| Active listings and ask curves | 15–60 minutes |
+| Closed auction results | event-driven plus hourly reconciliation |
+| Quote feeds | on-demand and nightly aggregate refresh |
+| MSRP and boutique price changes | daily monitor, event-triggered on detected change |
+| Canonical curves and benchmark indices | daily recompute, weekly frozen snapshots |
+| Champion/challenger models | weekly incremental training, monthly review |
+| Portfolio revaluations | nightly default, on-demand anytime |
+
+That cadence matches how the public sources themselves behave: auction results arrive in batches, quote platforms are interactive, watch-estimate systems can refresh daily, and market-history views on trading-style marketplaces are useful at sub-daily intervals.
+
+## Experience design and visual analytics
+
+The most important UI decision is to treat each asset like a tradable position rather than a static product page. The user should always be able to answer five questions on one screen: **What is it? What is it worth now? What would I actually net if I sold it? How liquid is it? Why does the system believe this?** That implies a layered detail page: hero image or uploaded scan, canonical configuration, confidence badge, current value band, expected net exit proceeds, liquidity score, comparable transactions, valuation explanation, and a stock-like chart with toggles for MSRP, secondary average, expected net proceeds, list/ask spread, and source-weighted demand. For enterprise buyers, the chart should also expose cohort toggles such as neutral colors only, store-fresh only, recent date stamps only, or a specific region.
+
+A portfolio surface should look more like a lightweight private-markets terminal than an e-commerce dashboard. It should show acquisition basis, current gross value, current exit value, unrealized P&L, annualized ownership cost, concentration by brand/model/material/color, and a liquidity ladder. Alerts should include “premium compression,” “price support broken,” “quote floor moved,” “new auction comp above index mean,” and “valuation confidence changed materially.” A research surface should publish benchmark pages that look like sector indices: Birkin 25, Birkin 30, Kelly 25, Sellier premium, neutral color index, exotic index, and new-vintage spread.
+
+### Sample Birkin dataset for product scaffolding
+
+The table below is a **normalized illustrative seed dataset** for a Birkin 30 in Togo leather in the U.S. It is suitable for UI prototyping, data-contract design, and explaining methodology. The 2023 and 2024 secondary averages are back-solved from Sotheby’s statement that the 2025 average of $22,300 was up 6% from 2024 and 9% from 2023. The 2026 secondary figure is shown separately as a current pristine-market range, because a directly comparable full-year average was not identified in the public sources reviewed.
+
+| Year | U.S. MSRP | Secondary-market metric |
+|---|---:|---:|
+| 2022 | 11,300 | 19,000 average |
+| 2023 | 11,600 | 20,459 normalized average |
+| 2024 | 12,500 | 21,038 normalized average |
+| 2025 | 13,900 | 22,300 average |
+| 2026 | 14,900 | 27,000–31,000 current pristine range |
+
+```mermaid
+xychart-beta
+    title "Birkin 30 Togo U.S. MSRP vs normalized secondary average"
+    x-axis [2022, 2023, 2024, 2025]
+    y-axis "USD" 0 --> 25000
+    line "MSRP" [11300, 11600, 12500, 13900]
+    line "Secondary avg" [19000, 20459, 21038, 22300]
+```
+
+On this normalized series, secondary average value compounds at roughly 5.5% annually from 2022 to 2025, while MSRP compounds at about 7.2% annually from 2022 to 2026. The deeper insight, however, is that “average Birkin” is the wrong lens for buyers and enterprises. The consistently stronger configuration families are smaller sizes, recent stamps, Sellier construction, neutral and pastel colors, and Togo/Epsom leathers. Sotheby’s specifically calls out Birkin 25, Mini Kelly 20 II, Black, Gold, Nata, Rose Sakura, recent 2020s date stamps, and Sellier premiums; it also notes that store-fresh Birkins with original packaging and plastic intact command materially higher pricing.
+
+For motion design, the product should standardize a small asset system rather than ad hoc marketing videos.
+
+| Asset | Format | Duration | Use |
+|---|---|---:|---|
+| Hero market pulse loop | WebM/MP4, 16:9 and 1:1 | 8–12 sec | Homepage and category reports |
+| Scan-to-match animation | Lottie + MP4 fallback | 3–5 sec | Item intake flow |
+| Confidence badge microinteraction | Lottie/SVG | 1–2 sec | Valuation result |
+| Portfolio delta sparkline pack | SVG sprites | static + hover | Holdings list |
+| Benchmark explainer video | MP4, 1080p, captions-first | 20–40 sec | Sales and onboarding |
+| Report-cover motion graphic | MP4/GIF teaser | 5–8 sec | Research launches |
+
+## Autonomous agents and engineering operations
+
+A Claude-code style operating model makes sense here because the platform is naturally agentic: collectors watch sources, resolvers canonicalize assets, labelers create training data, trainers refresh models, QA agents validate outputs, and a governance layer decides whether anything is publishable. The key is to make every agent narrow, tool-bounded, and auditable. No agent should be allowed to publish a market value directly without passing data-quality, comparables, and confidence gates.
+
+### Suggested autonomous agent roles
+
+| Agent | Inputs | Outputs | Required skills |
+|---|---|---|---|
+| Source collector agent | source config, login/session, crawl window | raw listing/lot/quote records | browser automation, parsing, resilient retry logic |
+| Auction normalizer agent | closed lots, images, catalogs | normalized transaction rows | market-schema mapping, FX, buyer-premium handling |
+| Catalog resolver agent | source record, embeddings, metadata | canonical asset ID + match score | entity resolution, ranking, image-text retrieval |
+| Condition labeling agent | images, source condition text, defects | facet labels and normalized condition grade | CV labeling, luxury-specific heuristics |
+| Authenticity risk agent | images, provenance, serial/date metadata | risk score and escalation route | multimodal fraud detection, rules + ML |
+| Valuation trainer agent | curated comps, feature views | candidate model artifacts | tabular ML, backtesting, calibration |
+| Drift and QA agent | model outputs, fresh comps, golden sets | hold/publish decision, alerts | statistical QA, anomaly detection |
+| Index publisher agent | approved valuation snapshots | benchmark pages, CSV/API payloads | time-series assembly, publication governance |
+| API gateway agent | client requests, tenant policy | responses, authz decisions, audit log | API policy, RBAC, enterprise auth |
+| Enterprise identity agent | IdP metadata, user/group changes | tenant mappings, provisioning updates | OIDC, OAuth, SAML, SCIM |
+
+Enterprise auth is worth building early, not late. OpenID Connect provides the identity layer on top of OAuth 2.0, OAuth provides standard delegated authorization, SCIM standardizes provisioning, and SAML remains relevant for enterprise SSO. That means the platform should expose tenant-aware RBAC, just-in-time or SCIM-based provisioning, API tokens with scoped claims, and immutable audit logs from day one if it expects to sell into marketplaces, lenders, or insurers.
+
+### Claude-style orchestration scaffold
+
+```yaml
+agent_system:
+  name: lifestyle-asset-intelligence
+  policies:
+    - no_publication_without_data_quality_green
+    - no_publication_if_confidence < 0.70
+    - escalate_if_authenticity_risk > 0.35
+    - require_human_review_for_rare_or_low-sample_assets
+
+workflows:
+  collect_and_publish_daily:
+    steps:
+      - collector.fetch_deltas
+      - normalizer.standardize_records
+      - resolver.assign_canonical_asset_ids
+      - labeler.refresh_condition_facets
+      - modeler.compute_features
+      - valuator.score_prices_and_liquidity
+      - qa.run_golden_tests_and_drift_checks
+      - publisher.publish_if_approved
+```
+
+```python
+def publish_valuation(asset_id: str):
+    comps = retrieve_comparables(asset_id)
+    features = build_feature_view(asset_id, comps)
+    valuation = price_model.predict_quantiles(features)
+    liquidity = liquidity_model.predict(features)
+    auth_risk = authenticity_model.score(asset_id)
+    confidence = combine_confidence(comps, features, auth_risk)
+
+    if auth_risk > 0.35:
+        route_to_human_review(asset_id, reason="authenticity_risk")
+        return "held"
+
+    if confidence < 0.70 or len(comps.high_similarity) < 3:
+        publish_with_holdout_badge(asset_id, valuation, liquidity, confidence)
+        return "limited_publish"
+
+    publish_full(asset_id, valuation, liquidity, confidence, comps)
+    return "published"
+```
+
+CI/CD should treat data and models as first-class deployables. Source connectors need contract tests and replay fixtures. Curated tables need dbt data tests and Great Expectations assertions. Valuation models need backtests by brand, line, condition band, and geography. API responses need snapshot tests. New models should ship in champion/challenger mode and only be promoted once residuals, calibration, and coverage are acceptable. MLflow gives the registry, aliases, lineage, and version management needed for that promotion path.
+
+## Roadmap, economics, and go-to-market
+
+A pragmatic roadmap is a handbags-first build over roughly 12 months, followed by a sector-expansion year. The first three months should focus on source connectors, raw/curated schemas, canonical asset IDs, and an internal market-ops console. Months four through eight should add image-first intake, valuation and liquidity models, and the first Birkin/Kelly benchmark pages. Months nine through twelve should add portfolio functionality, enterprise auth, API packaging, confidence reports, and research report generation. The second year should bring watches first, then sneakers, then jewelry, because watches have the cleanest public market-index structure after handbags.
+
+### Suggested milestone plan
+
+| Phase | Time window | Core deliverables |
+|---|---|---|
+| Foundation | Months 0–3 | collectors, raw store, canonical schema, internal console |
+| Handbag MVP | Months 4–6 | Birkin/Kelly resolver, price engine, condition normalization, explanation UI |
+| Enterprise beta | Months 7–9 | portfolio, confidence models, auth gateway, benchmark pages, webhooks |
+| Commercial v1 | Months 10–12 | SLA hardening, report exporter, tenant admin, billing hooks, API docs |
+| Category expansion | Year 2 | watches, then sneakers, then jewelry |
+
+A realistic delivery team is roughly 8–12 core FTE for the first year: one product lead, one design lead, two backend/platform engineers, two data engineers, two ML engineers, one computer-vision engineer, one frontend engineer, and shared QA/SRE coverage. If the business wants white-glove enterprise onboarding, faster category expansion, or private partner feeds in year one, that usually pushes the team into the 12–18 FTE range. These are planning estimates rather than sourced market benchmarks.
+
+For cost, the practical planning envelope is roughly **$2.5M–$5M** for a disciplined 12-month handbag-first build, then **$6M–$12M cumulative** by the time the platform supports handbags plus watches plus a sellable enterprise API and research product. The wider end of the range reflects higher labor quality, enterprise-security hardening, data partnerships, and heavier image/ML inference volume. Infrastructure alone does not dominate this program; the biggest cost drivers are source maintenance, luxury-domain labeling, and QA around rare-item edge cases.
+
+Go-to-market should not depend on becoming a consumer marketplace. The stronger enterprise packaging is:
+
+| Package | Buyer | What they buy |
+|---|---|---|
+| Valuation API | resellers, lenders, insurers, marketplaces | point-in-time values, proceeds, liquidity, confidence |
+| Benchmark indices | research desks, media, HNW advisors | configuration-level indices and category reports |
+| White-label portfolio UI | luxury services, private shopping, concierge, lending | client dashboards and alerts |
+| Market-intel reports | brands, resale operators, investors | quarterly trend decks, premium compression, source-of-demand analysis |
+| Data-ops console | internal use or enterprise pro tier | comp review, source QA, escalation workflow |
+
+The most defensible commercial wedge is the **enterprise valuation API plus benchmark indices**. That product is hard to commoditize because its value comes from canonicalization, provenance, and normalization discipline, not from a single scraped feed.
+
+## Open questions and limitations
+
+This report deliberately did not analyze brand-policy, scraping-permissions, privacy, lending-law, insurance-regulatory, securities, or resale-tax issues; those matters will materially affect connector strategy and should be scoped separately.
+
+The Birkin sample data above is suitable for product and modeling scaffolding, but it is not a formal investable index. The 2023 and 2024 secondary averages are back-solved from Sotheby’s published percentage deltas, and the 2026 secondary market figure is a current pristine-market range rather than a directly comparable full-year average. For production benchmarks, the platform should freeze a formal methodology document that defines source eligibility, outlier filtering, fee handling, region treatment, and rebasing rules.
+
+The largest unresolved product question is whether the first paying enterprise customer wants **valuation accuracy**, **quote conversion**, **portfolio intelligence**, or **white-labeled consumer UX** most. The architecture above can support all four, but the first one chosen should drive the initial KPI tree, source contracts, and staffing mix.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f70b20a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,78 @@
+# lifestyle-asset-intel
+
+v0 scaffold for an enterprise Lifestyle Asset Intelligence platform — valuation,
+liquidity, and ownership-cost intelligence for luxury secondary-market assets,
+starting with Hermès Birkin/Kelly handbags.
+
+The full enterprise design (12-month, 8–12 FTE, ~$2.5–5M build) lives in
+[`BLUEPRINT.md`](./BLUEPRINT.md). The phased path from this skeleton to that
+endpoint lives in [`ROADMAP.md`](./ROADMAP.md). Read those two before extending
+the codebase.
+
+## What v0 actually is
+
+- Express + Postgres skeleton in Steve's canonical NPH pattern
+- Canonical schema (sources / brands / model_families / canonical_assets /
+  raw_observations / transactions / valuation_snapshots / indices / index_points /
+  portfolio_holdings) — the **public contract** the rest of the platform plugs into
+- Stub valuation engine (`lib/valuation.js`) that returns blueprint-shaped
+  responses from the seeded transactions table — no ML
+- Internal market-ops console at `/` with sort, density slider, grid-search,
+  dark/light toggle, hamburger nav, schema.org JSON-LD (per Steve's standing rules)
+- JSON API at `/api/health`, `/api/sources`, `/api/assets`, `/api/valuation/:slug`,
+  `/api/index/:slug`, `/api/portfolio?owner=`
+- Sample data: Birkin 30 Togo Gold GHW (US), with the blueprint's normalized
+  2022–2025 secondary-market averages plus one Pristine FASHIONPHILE comp at
+  $28,500
+- Smoke tests via `node --test`
+- pm2 ecosystem on port **9820**
+
+## What v0 is NOT
+
+No live source connectors, no ML, no auth, no multi-tenant, no Kafka/Iceberg/
+OpenSearch/Feast/Temporal/MLflow. All deferred to later phases (see ROADMAP.md).
+No domain registered yet — `info@<domain>` provisioning is deferred to v0.1.
+
+## Run it
+
+```bash
+cd ~/Projects/lifestyle-asset-intel
+cp .env.example .env
+npm install
+createdb lifestyle_asset_intel
+PGDATABASE=lifestyle_asset_intel psql -f db/schema.sql
+PGDATABASE=lifestyle_asset_intel psql -f db/seed.sql
+npm test
+npm run dev          # or: npm run pm2:start
+```
+
+Then open <http://localhost:9820>.
+
+## Health check
+
+```bash
+curl -s http://127.0.0.1:9820/api/health | jq
+curl -s http://127.0.0.1:9820/api/valuation/birkin-30-togo-gold-ghw-us | jq
+curl -s http://127.0.0.1:9820/api/index/birkin-30-togo-neutral | jq
+```
+
+## Layout
+
+```
+.
+├── server.js                 Express bootstrap, NPH-style middleware order
+├── lib/db.js                 pg Pool wrapper
+├── lib/valuation.js          v0 stub — replace contents only when ML lands
+├── routes/api.js             /api/* JSON
+├── routes/console.js         /, /asset/:slug, /indices/:slug
+├── views/                    EJS templates (head/foot partials, index, asset, index_detail, error)
+├── public/css/app.css        dark/light vars, grid system
+├── public/js/app.js          sort + density + search + theme toggle (localStorage)
+├── db/schema.sql             includes all migrations in order
+├── db/seed.sql               blueprint sample dataset
+├── db/migrations/0001_*.sql  canonical schema
+├── scripts/migrate.js        idempotent migration runner
+├── tests/smoke.test.js       boots server + hits all endpoints
+├── BLUEPRINT.md              full enterprise design (source of truth)
+└── ROADMAP.md                phased plan to get from v0 → v2
+```
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 0000000..af78397
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,121 @@
+# ROADMAP — lifestyle-asset-intel
+
+Derived from `BLUEPRINT.md`. Each phase is a concrete next-session task list.
+Standing rules from `~/.claude/CLAUDE.md` and Steve's MEMORY apply at every phase.
+
+## v0 — this scaffold (DONE)
+
+- [x] Express + PG skeleton in Steve's canonical pattern
+- [x] Canonical schema (10 tables) under `db/migrations/0001_canonical_schema.sql`
+- [x] Stub valuation engine returning blueprint-shaped contract
+- [x] Console with sort + density + grid-search + dark/light + schema.org
+- [x] Smoke tests via `node --test`
+- [x] pm2 ecosystem on port 9820
+- [x] Gitified from start, author `steve@designerwallcoverings.com`
+
+## v0.1 — domain + email (small, do this first when picking up)
+
+- [ ] Pick a brand domain (TLD defaults to `.com` per Steve's standing rule)
+- [ ] Use the **`domain-name-agent`** subagent to:
+  - check availability (Verisign WHOIS, not just GoDaddy FAST)
+  - register at GoDaddy/Cloudflare/Namecheap/Porkbun
+  - create CF zone, swap NS, clean stale records, provision LE SSL
+- [ ] Use the **`onboard-domain-agent`** subagent if a public-facing site is desired
+- [ ] Provision `info@<domain>` at Purelymail (Steve's hard build-time rule);
+  set MX/SPF/DKIM/DMARC. Set `CONTACT_EMAIL=info@<domain>` and `BRAND_DOMAIN=<domain>` in `.env`
+- [ ] Update `views/partials/foot.ejs` so the footer renders the live email
+  (the current TODO indicator disappears automatically)
+- [ ] Add ad-signals tracker entry per Steve's portfolio convention (only if site goes live)
+
+## v0.2 — first source connector: Sotheby's (Tier 1)
+
+- [ ] **Run `/best-practices`** before starting — pre-flight against Steve's standing rules
+  (CAN-SPAM, TOS, robots, image source rules)
+- [ ] Use the **`browserbase`** skill (Steve's default) for headless work — NOT local
+  Playwright/Puppeteer
+- [ ] Build `lib/connectors/sothebys.js` — captures closed-lot results into `raw_observations`
+- [ ] Build `lib/normalizers/sothebys.js` — maps lot → `transactions` row with
+  `gross_transaction_price`, `all_in_buyer_price` (hammer + buyer's premium),
+  `expected_net_seller_proceeds`, currency, region
+- [ ] Add migration `0002_connector_state.sql` — connector cursor / dedupe table
+- [ ] Add launchd job (Mac2 default per Steve's rule, NOT cloud) for hourly polling
+- [ ] Replace stub aggregations in `lib/valuation.js` with live comp retrieval
+
+## v0.3 — image-intake stub (CLIP, local)
+
+- [ ] Stand up CLIP embedding service — local model, no API costs
+- [ ] Use **Mac Studio 1** (`192.168.1.133:11434`) per Steve's default-Ollama rule
+  if any LLM call is involved (e.g., caption generation)
+- [ ] Add migration `0003_image_assets.sql` — `image_assets` + `image_embeddings`
+- [ ] Wire upload → embed → top-k canonical_assets candidate retrieval
+- [ ] Console gets a "Match by photo" entry point
+
+## v0.4 — condition normalization (vision-based)
+
+- [ ] Adopt the `enrich-ai-tags` skill pattern (Gemini vision)
+- [ ] Migration `0004_condition_facets.sql` — facet table (corner wear, hardware,
+  handle, lining, odor, repairs)
+- [ ] Replace seed `condition_facets` jsonb with structured facet rows
+- [ ] Recalibrate `condition_grade` ordinal from facets
+
+## v0.5 — confidence model (replaces stub)
+
+- [ ] Train calibrated regressor over comp count, source weight, recency,
+  similarity, image-match score
+- [ ] Replace `confidence_breakdown` stub jsonb with model output
+- [ ] Add MLflow registry; champion/challenger promotion path
+
+## v0.6 — liquidity model
+
+- [ ] Per-config sell-through ratio, time-to-sell, ask-curve depth
+- [ ] Migration `0005_liquidity_features.sql`
+- [ ] Surface in console + `/api/valuation/:slug.latest_snapshot.liquidity_score`
+
+## v1.0 — enterprise auth gateway
+
+- [ ] OIDC + OAuth + SAML + SCIM (per blueprint)
+- [ ] Tenant-aware RBAC, scoped API tokens, immutable audit logs
+- [ ] Use **`next-auth-gate`** skill for the console pre-public exposure
+- [ ] Use the central `admin.agentabrams.com` SSO if going internal-DW only
+
+## v1.1 — index publisher
+
+- [ ] Indices: `birkin-30-togo-neutral`, `birkin-25-pastels`, `sellier-premium`,
+  `recent-stamp-premium`
+- [ ] Daily recompute job; weekly frozen snapshots
+- [ ] Public benchmark pages (gated behind paid tier)
+
+## v2.0 — category expansion
+
+- [ ] Watches first — Chrono24 connector, ChronoPulse parity (>600k txns since 2019)
+- [ ] Sneakers — StockX (new-condition only, sized SKU)
+- [ ] Jewelry — auction-house weighted, smaller sample sizes; needs higher
+  authenticity-risk threshold and human-in-the-loop on rare lots
+
+## Cross-cutting standing rules (apply at every phase)
+
+- **Default Ollama target = Mac Studio 1** (`http://192.168.1.133:11434`)
+- **Default voice = ElevenLabs** (never piper/say/coqui as primary)
+- **Default browser = Browserbase** (Playwright/Puppeteer only as fallback)
+- **NEVER use the Anthropic API key** — Claude inference via `claude` CLI only
+- **No stock images** — public-domain only on any public-facing surface
+- **info@<domain>** required at build time; block launch if missing
+- **No DNS edits** to `designerwallcoverings.com` or `studentdebtcrisis(center).org`
+- **CAN-SPAM / §17529.5 / TCPA / CCPA** review before any outbound email/SMS/push
+  via the **`comms-compliance`** subagent
+- **Audit batches >10% failure rate** — alert via George
+- **Every grid view** must keep sort + density + dark/light + grid-search
+- **Gitified** — commit per logical change, never `--force` to main
+
+## Open product question (driver of v0.1+ packaging)
+
+The biggest unresolved decision per the blueprint is which buyer drives the first
+paid contract:
+
+1. **Enterprise valuation API + benchmark indices** ← Steve picked this for v0
+2. Quote-conversion partnerships (lender/insurer/concierge)
+3. Portfolio intelligence (HNW client dashboards)
+4. White-labeled consumer UX
+
+v0 wedge is locked to (1). v1.0 packaging will need a re-vote with whichever
+prospect is closest to signing.
diff --git a/ecosystem.config.js b/ecosystem.config.js
new file mode 100644
index 0000000..d7fe0e5
--- /dev/null
+++ b/ecosystem.config.js
@@ -0,0 +1,21 @@
+module.exports = {
+  apps: [
+    {
+      name: 'lai-server',
+      script: 'server.js',
+      cwd: __dirname,
+      instances: 1,
+      exec_mode: 'fork',
+      autorestart: true,
+      max_memory_restart: '512M',
+      env: {
+        NODE_ENV: 'development',
+        PORT: 9820
+      },
+      env_production: {
+        NODE_ENV: 'production',
+        PORT: 9820
+      }
+    }
+  ]
+};
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..d2d1965
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1201 @@
+{
+  "name": "lifestyle-asset-intel",
+  "version": "0.0.1",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "lifestyle-asset-intel",
+      "version": "0.0.1",
+      "dependencies": {
+        "compression": "^1.7.5",
+        "dotenv": "^16.4.5",
+        "ejs": "^3.1.10",
+        "express": "^4.21.0",
+        "helmet": "^8.1.0",
+        "morgan": "^1.10.0",
+        "pg": "^8.13.1"
+      },
+      "engines": {
+        "node": ">=20"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/accepts/node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+      "license": "MIT"
+    },
+    "node_modules/async": {
+      "version": "3.2.6",
+      "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+      "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+      "license": "MIT"
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "license": "MIT"
+    },
+    "node_modules/basic-auth": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+      "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "5.1.2"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/basic-auth/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "license": "MIT"
+    },
+    "node_modules/body-parser": {
+      "version": "1.20.5",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
+      "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "~3.1.2",
+        "content-type": "~1.0.5",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "~1.2.0",
+        "http-errors": "~2.0.1",
+        "iconv-lite": "~0.4.24",
+        "on-finished": "~2.4.1",
+        "qs": "~6.15.1",
+        "raw-body": "~2.5.3",
+        "type-is": "~1.6.18",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/body-parser/node_modules/qs": {
+      "version": "6.15.1",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
+      "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+      "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/compressible": {
+      "version": "2.0.18",
+      "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+      "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": ">= 1.43.0 < 2"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/compression": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
+      "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "compressible": "~2.0.18",
+        "debug": "2.6.9",
+        "negotiator": "~0.6.4",
+        "on-headers": "~1.1.0",
+        "safe-buffer": "5.2.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+      "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+      "license": "MIT"
+    },
+    "node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/dotenv": {
+      "version": "16.6.1",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+      "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "license": "MIT"
+    },
+    "node_modules/ejs": {
+      "version": "3.1.10",
+      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+      "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "jake": "^10.8.5"
+      },
+      "bin": {
+        "ejs": "bin/cli.js"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/express": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+      "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "~1.20.3",
+        "content-disposition": "~0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "~0.7.1",
+        "cookie-signature": "~1.0.6",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.3.1",
+        "fresh": "~0.5.2",
+        "http-errors": "~2.0.0",
+        "merge-descriptors": "1.0.3",
+        "methods": "~1.1.2",
+        "on-finished": "~2.4.1",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "~0.1.12",
+        "proxy-addr": "~2.0.7",
+        "qs": "~6.14.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "~0.19.0",
+        "serve-static": "~1.16.2",
+        "setprototypeof": "1.2.0",
+        "statuses": "~2.0.1",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/filelist": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
+      "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "minimatch": "^5.0.1"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+      "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "~2.0.2",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+      "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/helmet": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
+      "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+      "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "~2.0.0",
+        "inherits": "~2.0.4",
+        "setprototypeof": "~1.2.0",
+        "statuses": "~2.0.2",
+        "toidentifier": "~1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/jake": {
+      "version": "10.9.4",
+      "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
+      "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "async": "^3.2.6",
+        "filelist": "^1.0.4",
+        "picocolors": "^1.1.1"
+      },
+      "bin": {
+        "jake": "bin/cli.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "license": "MIT",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.54.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+      "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types/node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "5.1.9",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+      "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/morgan": {
+      "version": "1.10.1",
+      "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
+      "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
+      "license": "MIT",
+      "dependencies": {
+        "basic-auth": "~2.0.1",
+        "debug": "2.6.9",
+        "depd": "~2.0.0",
+        "on-finished": "~2.3.0",
+        "on-headers": "~1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/morgan/node_modules/on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "license": "MIT"
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.4",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
+      "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.4",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/on-headers": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+      "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-to-regexp": {
+      "version": "0.1.13",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
+      "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
+      "license": "MIT"
+    },
+    "node_modules/pg": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
+      "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
+      "license": "MIT",
+      "dependencies": {
+        "pg-connection-string": "^2.12.0",
+        "pg-pool": "^3.13.0",
+        "pg-protocol": "^1.13.0",
+        "pg-types": "2.2.0",
+        "pgpass": "1.0.5"
+      },
+      "engines": {
+        "node": ">= 16.0.0"
+      },
+      "optionalDependencies": {
+        "pg-cloudflare": "^1.3.0"
+      },
+      "peerDependencies": {
+        "pg-native": ">=3.0.1"
+      },
+      "peerDependenciesMeta": {
+        "pg-native": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pg-cloudflare": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
+      "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/pg-connection-string": {
+      "version": "2.12.0",
+      "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz",
+      "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==",
+      "license": "MIT"
+    },
+    "node_modules/pg-int8": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+      "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/pg-pool": {
+      "version": "3.13.0",
+      "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz",
+      "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==",
+      "license": "MIT",
+      "peerDependencies": {
+        "pg": ">=8.0"
+      }
+    },
+    "node_modules/pg-protocol": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz",
+      "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==",
+      "license": "MIT"
+    },
+    "node_modules/pg-types": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+      "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+      "license": "MIT",
+      "dependencies": {
+        "pg-int8": "1.0.1",
+        "postgres-array": "~2.0.0",
+        "postgres-bytea": "~1.0.0",
+        "postgres-date": "~1.0.4",
+        "postgres-interval": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/pgpass": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+      "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+      "license": "MIT",
+      "dependencies": {
+        "split2": "^4.1.0"
+      }
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/postgres-array": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+      "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/postgres-bytea": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
+      "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postgres-date": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+      "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postgres-interval": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+      "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+      "license": "MIT",
+      "dependencies": {
+        "xtend": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "license": "MIT",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.14.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+      "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+      "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "~3.1.2",
+        "http-errors": "~2.0.1",
+        "iconv-lite": "~0.4.24",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/send": {
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+      "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "~0.5.2",
+        "http-errors": "~2.0.1",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "~2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "~2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/send/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/serve-static": {
+      "version": "1.16.3",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+      "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+      "license": "MIT",
+      "dependencies": {
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "~0.19.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
+    },
+    "node_modules/side-channel": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+      "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3",
+        "side-channel-list": "^1.0.0",
+        "side-channel-map": "^1.0.1",
+        "side-channel-weakmap": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-list": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+      "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-map": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-weakmap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3",
+        "side-channel-map": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/split2": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+      "license": "ISC",
+      "engines": {
+        "node": ">= 10.x"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+      "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "license": "MIT",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4"
+      }
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8072d05
--- /dev/null
+++ b/package.json
@@ -0,0 +1,30 @@
+{
+  "name": "lifestyle-asset-intel",
+  "version": "0.0.1",
+  "private": true,
+  "description": "Lifestyle Asset Intelligence — valuation, liquidity, and ownership-cost platform (handbags first)",
+  "main": "server.js",
+  "scripts": {
+    "start": "node server.js",
+    "dev": "node --watch server.js",
+    "schema": "psql $PGDATABASE < db/schema.sql",
+    "seed": "psql $PGDATABASE < db/seed.sql",
+    "migrate": "node scripts/migrate.js",
+    "test": "node --test tests/smoke.test.js",
+    "pm2:start": "pm2 start ecosystem.config.js",
+    "pm2:stop": "pm2 stop ecosystem.config.js",
+    "pm2:restart": "pm2 restart ecosystem.config.js"
+  },
+  "dependencies": {
+    "compression": "^1.7.5",
+    "dotenv": "^16.4.5",
+    "ejs": "^3.1.10",
+    "express": "^4.21.0",
+    "helmet": "^8.1.0",
+    "morgan": "^1.10.0",
+    "pg": "^8.13.1"
+  },
+  "engines": {
+    "node": ">=20"
+  }
+}

(oldest)  ·  back to Lifestyle Asset Intel  ·  canonical schema + Birkin sample seed + pg pool wrapper 5afcf7c →