shopico

shopico

Provides Shopify demand forecasting, reorder recommendations, and dollar-impact reports as MCP tools.

Category
访问服务器

README

Shopify Demand & Inventory Copilot

A production-grade demand forecasting and inventory-risk system for Shopify merchants: a live GraphQL Admin API integration (OAuth install, webhooks, bulk operations), per-SKU forecasting with an eval-gated promotion process, and every forecast translated into an estimated dollar impact versus a naive baseline -- not just an accuracy score.

This is built as an installable, multi-tenant Shopify app (any merchant can install it via OAuth), not a tool wired to one personal store.

Status: Phase 4 of 4 (complete)

Phase Scope Status
0 Repo scaffold, schema, local Postgres Done
1 Seasonality-aware data generation, Shopify OAuth install flow, webhook ingestion (HMAC-verified), bulk-operation reconciliation Done
2 dbt aggregation (daily_sku_sales), naive baseline, backtest harness, $-impact methodology Done
3 Per-SKU LightGBM model, MLflow tracking, promotion gate Done -- see REPORTS.md
4 FastAPI serving (/forecast, /reorder, /impact-report), MCP tools, Streamlit dashboard, Evidently monitoring, full Docker Compose stack Done (this pass)

Headline result (see REPORTS.md for the full, real MLflow-logged history): the promoted forecasting model has worse raw accuracy than the naive baseline (6.69 vs. 5.39 units MAE) but delivers +$11,593.21 over the baseline across a 12-week/24-SKU backtest, because it was trained toward the newsvendor-optimal quantile instead of the conditional mean. Three earlier, accuracy-competitive candidates were correctly rejected by the promotion gate before this fix, along with a deliberately weak model on every run -- real evidence the gate evaluates dollars, not accuracy.

What's real vs. what needs live Shopify credentials

There's no Shopify Partner account or dev store yet, so everything is built in two tracks:

  • Runs today, no Shopify credentials needed: local Postgres, the seasonality/promo/catalog generators, data_generator/seed_local.py (writes synthetic order history directly into Postgres), the webhook listener + HMAC verification (tested with locally-crafted signed payloads).
  • Implemented against the real Shopify API contract, but requires a live Partner dev store (or a real merchant install) to actually run: shopify_client/oauth.py (app install flow), data_generator/shopify_seed.py (seeds history via real orderCreate mutations), ingestion/bulk_backfill.py (weekly reconciliation via bulkOperationRunQuery).

Creating a Shopify Partner account and a free development store (no credit card, ~10 minutes) is what unlocks live end-to-end testing of the second track. Until then, the first track proves the ingestion/storage contract works correctly on its own.

Repo layout

data_generator/    seasonality + promo + catalog models, local and live seeding
shopify_client/    Admin GraphQL client, OAuth install flow, bulk operations
ingestion/         FastAPI app: webhook listener (HMAC-verified) + OAuth routes, bulk reconciliation job
dbt/               staging models + daily_sku_sales mart
forecasting/       naive baseline, $-value dollarization, backtest harness, LightGBM training, MLflow promotion gate
serving/           GET /forecast, /reorder, /impact-report business logic, mounted into ingestion's FastAPI app
mcp_server/        MCP tool wrappers over serving/, for use from Claude Desktop/Code or any MCP client
dashboard/         Streamlit dashboard leading with the live $-impact headline number
monitoring/        Evidently feature-drift report + a custom $-impact-degradation trend check
db/                schema.sql, tiny psycopg2 connection helper
tests/             pytest suite
.github/workflows/ CI (tests on push/PR) + scheduled weekly retrain (+ drift check)
Dockerfile         image shared by the api/dashboard/mlflow-ui docker-compose services
docker-compose.yml postgres + api + dashboard + mlflow-ui for local dev
REPORTS.md         Phase 3 baseline-vs-model comparison, accuracy and dollar terms

Running Phase 1 locally

  1. Start Postgres

    docker compose up -d postgres
    

    This applies db/schema.sql automatically on first start.

  2. Set up a virtualenv and install deps

    python -m venv .venv
    .venv/Scripts/activate   # or source .venv/bin/activate on macOS/Linux
    pip install -r requirements.txt
    cp .env.example .env
    

    Generate a TOKEN_ENCRYPTION_KEY:

    python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
    
  3. Seed 18-24 months of synthetic order history

    python -m data_generator.seed_local --months 20
    

    Re-run with --reset to regenerate. This writes a dev shop, a 24-SKU catalog with Pareto-ish popularity, and daily orders driven by a trend + weekday seasonality + quarterly/BFCM promo-spike curve.

  4. Run the ingestion API

    uvicorn ingestion.main:app --reload
    

    GET /health, GET /auth?shop=... (install flow), POST /webhooks (HMAC-verified orders/create / orders/updated / inventory_levels/update).

  5. Run the tests

    pytest tests/
    

    Covers: HMAC accepts a correctly-signed payload and rejects a forged or missing signature (both as pure-function checks and as full webhook-endpoint integration checks that assert nothing is written to the DB on rejection).

  6. Build the daily_sku_sales mart with dbt

    dbt run --project-dir dbt --profiles-dir dbt
    

    Aggregates orders + order_line_items into daily per-SKU unit sales. The dbt profile defaults to the docker-compose Postgres credentials, so no extra config is needed for local dev.

  7. Run the backtest harness

    python -m forecasting.backtest
    

    Replays the last 12 weeks for every product, using a trailing 4-week moving average as the naive baseline (forecasting/baseline.py), and writes each SKU-week's actual/predicted/baseline/dollar-impact into backtest_results. Phase 2 has no trained model yet, so predicted_units is deliberately set equal to the baseline -- the headline dollar impact should print as $0.00, which is the expected, correct result: it proves the harness and the dollarization math (forecasting/backtest.py::dollarize_forecast_error) are wired correctly without a real model muddying the signal. Phase 3's trained model plugs into this same harness as a genuinely different predicted_units and is expected to produce a non-zero (ideally positive) number.

  8. Train the model, run the promotion gate, and see the real comparison

    python -m forecasting.train
    

    Trains a global LightGBM model (product_id as a categorical feature) plus a deliberately weak DummyRegressor "control" candidate, backtests both through the exact same harness as the baseline, logs everything to MLflow (MLFLOW_TRACKING_URI, defaults to a local sqlite:///mlflow.db so Model Registry stage transitions work without a real server), and only promotes a candidate to the Production stage if it beats both the baseline and whatever's currently deployed, in dollars (forecasting/registry.py). Prints a comparison table at the end; see REPORTS.md for the full real run history from this project's own development, including multiple rejections.

    mlflow ui --backend-store-uri sqlite:///mlflow.db
    

    to browse runs/versions/stage transitions in the MLflow UI.

Running Phase 4 locally

  1. Serve live forecasts (after step 8 has promoted a model -- otherwise these fall back to the naive baseline)

    uvicorn ingestion.main:app --reload
    

    GET /forecast?shop_domain=...&sku=..., GET /reorder?shop_domain=...&sku=..., GET /impact-report?shop_domain=....

  2. Run the dashboard

    streamlit run dashboard/app.py
    

    Leads with the live $-impact-vs-baseline headline number (serving/impact_report_service.py), then a per-SKU breakdown and the reorder-recommendations table.

  3. Register the MCP server (wraps the same serving/ logic as three MCP tools: get_forecast_tool, get_reorder_recommendation_tool, get_impact_report_tool)

    claude mcp add shopico -- python -m mcp_server.server
    

    Or run directly with python -m mcp_server.server (stdio transport) to register with any other MCP client.

  4. Check for degrading $-impact or feature drift

    python -m monitoring.drift_monitor
    

    Prints an early-half-vs-recent-half $-impact trend for the active model and writes an Evidently feature-drift HTML report to monitoring/reports/. Wired into the weekly retrain workflow as a post-training step.

  5. Or run the whole stack in Docker

    docker compose up -d --build
    

    api (port 8000, ingestion + serving), dashboard (port 8501), mlflow-ui (port 5000) all bind-mount the host's ./mlflow.db, so they see whatever step 8 already promoted rather than starting from an empty registry.

Once a Partner dev store exists

python -m data_generator.shopify_seed --months 20   # seeds via real orderCreate mutations
python -m ingestion.bulk_backfill --shop-domain your-store.myshopify.com --access-token <token> --since-days 7

And point the app's /auth?shop=your-store.myshopify.com at a running ingestion.main:app (with SHOPIFY_APP_URL set to a publicly reachable URL, e.g. via ngrok for local dev) to exercise the full install flow.

Design notes worth knowing

  • products.shopify_inventory_item_id is an addition beyond the original spec's schema: inventory_levels/update webhooks key off inventory_item_id, not SKU, so there's no way to route that event to a product row without it.
  • Webhook ingestion and bulk-operation reconciliation intentionally write through the same handle_order_webhook function (ingestion/webhook_listener.py), so both paths agree by construction on overlapping records.
  • Everything uses a single psycopg2 (sync) driver, no ORM -- matches the spec's "right-sized, not cloud-scale" philosophy (see original spec §7).
  • dollarize_forecast_error's sign convention was corrected vs. the original spec pseudocode. The pseudocode's cost_of_error returns a positive cost for the stockout branch but a negative value for the overstock branch. Walk through a case where the model forecasts perfectly and the baseline over-forecasts: baseline_cost - model_cost should be positive (model saved money by avoiding the baseline's excess inventory), but the pseudocode's sign makes it negative -- and more negative the worse the baseline overstocks, inverting the stated "positive = value created" meaning. Fixed by having cost_of_error always return a positive cost magnitude in both branches. See forecasting/backtest.py's module docstring and tests/test_dollar_impact_calc.py::test_overstock_scenario_* for the hand-verified case that pins this down.
  • The naive baseline (forecasting/baseline.py) is a trailing 4-week moving average of weekly units sold, computed strictly from weeks before the target week (no leakage) -- the standard, easy-to-hand-verify "seasonal naive" baseline for weekly-bucketed retail demand.
  • A single global LightGBM model, not per-SKU models. The spec allows "per-SKU (or per-SKU-cluster, for low-volume SKUs)"; with ~600-900 days of history per SKU, one shared model with product_id as a categorical feature borrows statistical strength across SKUs far better than fitting 24 independent models in isolation, especially for the long-tail ones.
  • Trained toward a cost-optimal quantile, not the conditional mean. dollarize_forecast_error's cost is asymmetric (stockouts cost a full unit's margin, overstock only a small holding fee), so a plain L2 regression model has no way to know that slight over-forecasting is usually cheaper than under-forecasting. This is the classic newsvendor problem; forecasting/train.py::newsvendor_critical_fractile derives the optimal LightGBM quantile objective alpha from each product's real unit economics rather than guessing. See REPORTS.md for what this fix actually changed (rejected → promoted, despite worse MAE).
  • forecasting/backtest.py::write_backtest_results deletes existing rows for a model_version before inserting -- discovered this needed to be idempotent while iterating during Phase 3 development, when re-running training for the same day's model label was silently accumulating duplicate rows and corrupting total_dollar_impact's aggregation.
  • The promotion gate uses MLflow's Model Registry Production stage (via MlflowClient.transition_model_version_stage) as the single source of truth for "what's currently deployed," rather than a custom state table. That API is deprecated as of MLflow 2.9 in favor of model aliases, but still functional as of 2.19 (used here) -- worth revisiting if this project upgrades MLflow significantly later.
  • serving/model_loader.py reuses forecasting/train.py::make_predict_fn directly (loading the model via mlflow.lightgbm.load_model, not mlflow.pyfunc) so live inference goes through the exact same feature-building code path already validated in backtest -- no separate, potentially-drifted serving-time implementation. It caches the loaded model by MLflow registry version number (immutable once registered, unlike "Production" which changes meaning on every promotion), since callers that loop over many SKUs for one shop (dashboard/app.py, monitoring/drift_monitor.py) would otherwise reload the same model from disk once per SKU.
  • forecasting/train.py never persisted the naive baseline's own backtest_results rows (only trained candidates go through write_backtest_results) -- a real gap this pass's serving/impact_report_service.py had to work around: if the baseline has no rows for a shop, it's recomputed live via forecasting.backtest.run_backtest instead of silently reporting a zero MAE.
  • The reorder policy (serving/reorder_service.py) is a documented assumption, not real Shopify data: LEAD_TIME_WEEKS/SAFETY_STOCK_WEEKS/ OVERSTOCK_WEEKS_OF_COVER are a standard weeks-of-cover policy, overridable via env vars, until a live merchant's actual lead times exist.
  • fastapi was bumped from 0.115.6 to 0.139.0 (and pydantic 2.10.5 to 2.11.0) in this pass: fastapi 0.115.6 pinned starlette<0.42, which can't coexist in one virtualenv with the starlette version streamlit 1.59.2 requires, and mcp 1.28.1 requires pydantic>=2.11. Existing tests (tests/test_webhook_hmac.py, the OAuth routes) still pass unchanged.
  • The Docker image needs libgomp1 (apt-get install) -- python:3.11-slim doesn't include it, and LightGBM's native library won't load without it (OSError: libgomp.so.1: cannot open shared object file).
  • Evidently alone only compares feature distributions, which wouldn't catch a model that's still "in-distribution" but has quietly stopped beating the baseline in dollars -- the metric this whole project is built around. So monitoring/drift_monitor.py runs a custom early-half-vs-recent-half $-impact trend check (flags >30% relative degradation, or a positive→negative flip) alongside Evidently's feature-drift report, rather than relying on feature drift as a proxy for the thing that actually matters.

All four planned phases are now complete. Natural next steps beyond the original scope: replacing MLflow's deprecated stage-based registry with aliases, real Shopify Partner dev-store credentials to exercise the OAuth/bulk paths end-to-end, and per-SKU (rather than catalog-average) lead times once real merchant inventory data exists.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选