diff --git a/data/agromarket_report.xlsx b/data/agromarket_report.xlsx index 7cd4e0b..a1fd6f5 100644 Binary files a/data/agromarket_report.xlsx and b/data/agromarket_report.xlsx differ diff --git a/src/dashboard/app.py b/src/dashboard/app.py index 199cfd7..427bf31 100644 --- a/src/dashboard/app.py +++ b/src/dashboard/app.py @@ -310,11 +310,20 @@ def product_page(product_id: str, days: int = Query(30, ge=7, le=180)) -> HTMLRe f"{html.escape(r['source_id'])} · {html.escape(r['region'])} " f"{r['first_d']} → {r['last_d']} · {r['n']} записей" for r in src_rows) or "
  • источники ещё не собрали этот товар
  • " + # Banner: if ALL sources for this product are 'demo_*', it's synthetic data + all_demo = len(src_rows) > 0 and all(r["source_id"].startswith("demo_") for r in src_rows) + demo_banner = ( + "

    " + "Демо-данные (источник demo_*). Подключите реальный источник на " + "странице «Источники», чтобы увидеть живые цены." + "

    " if all_demo else "" + ) body = _render("product", - PRODUCT=html.escape(product_id), - PRODUCT_LABEL=html.escape(product_id.replace("_", " ")), - LATEST_VAL=f"{val:,.1f}" if val else "—", - LATEST_AS_OF=as_of or "—", + PRODUCT=html.escape(product_id), + PRODUCT_LABEL=html.escape(product_id.replace("_", " ")), + DEMO_BANNER=demo_banner, + LATEST_VAL=f"{val:,.1f}" if val else "—", + LATEST_AS_OF=as_of or "—", LATEST_REGION=html.escape(region or "—"), LATEST_SRC_URL=html.escape(src_url or ""), LATEST_FRAGMENT=html.escape(frag[:120] if frag else "—"), @@ -534,6 +543,33 @@ def api_arbitrage(product: str, from_: str = "KZ", to: str = "TJ", return a +@app.get("/api/products") +def api_products() -> dict: + """id → best label, for the overview search field. + Uses category/subcategory as the human label (ru from taxonomy).""" + import yaml + try: + with open(config.TAXONOMY_PATH, "r", encoding="utf-8") as f: + tax = yaml.safe_load(f) or {} + except Exception: + tax = {} + sub_label = {} + for c in tax.get("categories", []): + for s in c.get("subcategories", []): + sub_label[(c.get("category") or c.get("id"), s.get("id"))] = s.get("label_ru") or s.get("label") or s.get("id") + cat_label = {c.get("category") or c.get("id"): c.get("label_ru") or c.get("label") or (c.get("category") or c.get("id")) + for c in tax.get("categories", [])} + out = {} + for r in db.all_products(): + cat = r.get("category") or "" + sub = r.get("subcategory") or "" + pid = r["product"] + # best label: prefer sub label, fall back to sub id readable + lbl = sub_label.get((cat, sub), (sub.replace("_", " ").capitalize() if sub else pid)) + out[pid] = lbl + return {"products": out} + + @app.get("/healthz") def healthz() -> dict: return {"ok": True, "time": date.today().isoformat()} diff --git a/src/dashboard/templates/overview.html b/src/dashboard/templates/overview.html index 9c3305b..6cab35c 100644 --- a/src/dashboard/templates/overview.html +++ b/src/dashboard/templates/overview.html @@ -1,8 +1,62 @@ -
    +

    Обзор рынка

    -

    Демо: {{ TODAY }}. Все цены в тг/кг. Карантин = данные, прошедшие валидацию и отправленные на проверку.

    +

    Демо-набор данных от {{ TODAY }}. Все цены в тг/кг. «Карантин» — записи, отправленные на проверку.

    +
    +
    + +
    + +

    + Демо-данные (сгенерированы для примера). Чтобы увидеть живые цены, подключите реальный источник + на странице Источники или загрузите CSV/HTML оттуда же. +

    +
    + + +
    {{ KPI_PRODUCTS }}
    товаров за 7 дн.
    {{ KPI_COUNTRIES }}
    стран
    diff --git a/src/dashboard/templates/product.html b/src/dashboard/templates/product.html index 95ce17e..1b625d3 100644 --- a/src/dashboard/templates/product.html +++ b/src/dashboard/templates/product.html @@ -5,6 +5,7 @@ фрагмент: {{ LATEST_FRAGMENT }}

    {{ COUNTRY_CHIPS }}
    + {{ DEMO_BANNER }}
    diff --git a/src/demo_data.py b/src/demo_data.py index 91af815..970c0df 100644 --- a/src/demo_data.py +++ b/src/demo_data.py @@ -53,6 +53,23 @@ DEMO_SOURCES = [ "meat_mutton", "meat", "mutton", "wholesale"), ("demo_tj", "TJ", "TJ-Dushanbe", "Dushanbe wholesale", "https://agromarket.asia/demo/dushanbe", 265.0, "meat_mutton", "meat", "mutton", "wholesale"), + # --- Tubers / корнеплоды --- + ("demo_kz", "KZ", "KZ-Sharyn", "Sharyn", "https://agromarket.asia/demo/sharyn", 55.0, + "tubers_potato", "tubers", "potato", "wholesale"), + ("demo_uz", "UZ", "UZ-Tashkent", "Tashkent wholesale", "https://agromarket.asia/demo/tashkent", 72.0, + "tubers_potato", "tubers", "potato", "wholesale"), + ("demo_tj", "TJ", "TJ-Dushanbe", "Dushanbe wholesale", "https://agromarket.asia/demo/dushanbe", 90.0, + "tubers_potato", "tubers", "potato", "wholesale"), + ("demo_kz", "KZ", "KZ-Sharyn", "Sharyn", "https://agromarket.asia/demo/sharyn", 210.0, + "tubers_onion", "tubers", "onion", "wholesale"), + ("demo_uz", "UZ", "UZ-Tashkent", "Tashkent wholesale", "https://agromarket.asia/demo/tashkent", 180.0, + "tubers_onion", "tubers", "onion", "wholesale"), + ("demo_kz", "KZ", "KZ-Sharyn", "Sharyn", "https://agromarket.asia/demo/sharyn", 160.0, + "tubers_carrot", "tubers", "carrot", "wholesale"), + ("demo_uz", "UZ", "UZ-Tashkent", "Tashkent wholesale", "https://agromarket.asia/demo/tashkent", 140.0, + "tubers_carrot", "tubers", "carrot", "wholesale"), + ("demo_kz", "KZ", "KZ-Sharyn", "Sharyn", "https://agromarket.asia/demo/sharyn", 150.0, + "tubers_beet", "tubers", "beet", "wholesale"), ]