From 0900a06bd08f1fcf48bcf446d2160ce375889b6e Mon Sep 17 00:00:00 2001 From: Elshat Date: Thu, 24 Sep 2026 07:35:39 +0000 Subject: [PATCH] feat: product search + potato/onion/carrot/beet in demo + demo-data banner + /api/products MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - overview: search field with datalist -> /product/ (matches ru label or id) - demo_data: +4 tubers (potato KZ/TJ/UZ, onion, carrot, beet) - product page: banner 'Демо-данные' when all sources are demo_* - /api/products: id->{label} for search - regenerated demo + report (10 sheets incl tubers_potato) --- data/agromarket_report.xlsx | Bin 13892 -> 18250 bytes src/dashboard/app.py | 44 +++++++++++++++++-- src/dashboard/templates/overview.html | 58 +++++++++++++++++++++++++- src/dashboard/templates/product.html | 1 + src/demo_data.py | 17 ++++++++ 5 files changed, 114 insertions(+), 6 deletions(-) diff --git a/data/agromarket_report.xlsx b/data/agromarket_report.xlsx index 7cd4e0bd9982461cf1c7be95cc1a9e0d8650305b..a1fd6f53e9de3929fe0104279918b974a50917be 100644 GIT binary patch delta 15699 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

2AZ=Bx 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 D&xDF< delta 11308 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 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"), ]