mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-12 08:30:51 +01:00
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint) (push) Waiting to run
Tests / test (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
101 lines
3.7 KiB
Python
101 lines
3.7 KiB
Python
"""Walk the panel's endpoints to today's delivered dishes.
|
|
|
|
Three calls, and the shape of the walk is not what the endpoint names suggest:
|
|
|
|
1. ``company/customer/order/active-ids`` -> ``[orderId, ...]``. More than one
|
|
while a renewal overlaps the running order, **newest first** -- so the first
|
|
id is the future order and today's delivery sits in a later one.
|
|
2. ``company/customer/order/{orderId}`` -> the order, which **embeds every
|
|
delivery** with its date. No enumeration call is needed, but the embedded
|
|
``deliveryMeals`` carry ids only -- no dish names, no macros.
|
|
3. ``company/general/menus/delivery/{deliveryId}/new`` -> the day's actual
|
|
menu, with names and per-portion nutrition.
|
|
|
|
Step 3 is the important correction: the obvious-looking
|
|
``.../deliveries/{id}/details`` **404s** (and 400s when keyed by date), which
|
|
was confirmed against three separate delivery days. The menu endpoint is keyed
|
|
by the opaque ``deliveryId``, never by the date.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from diet_guard._kuchnia_errors import KuchniaError
|
|
from diet_guard._kuchnia_parse import parse_menu
|
|
|
|
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
from datetime import date
|
|
|
|
from diet_guard._kuchnia_client import PanelSession
|
|
from diet_guard._kuchnia_parse import Dish
|
|
|
|
|
|
def _active_order_ids(session: PanelSession) -> list[object]:
|
|
"""Return every active order id, empty when there is no active order."""
|
|
payload = session.get_json("company/customer/order/active-ids")
|
|
if not isinstance(payload, list):
|
|
return []
|
|
return payload
|
|
|
|
|
|
def _delivery_id_for(payload: object, wanted: str) -> object | None:
|
|
"""Return the delivery id whose date is ``wanted``.
|
|
|
|
Args:
|
|
payload: The decoded order.
|
|
wanted: An ISO ``YYYY-MM-DD`` date.
|
|
|
|
Returns:
|
|
The delivery id, or None when that day has no (undeleted) delivery.
|
|
"""
|
|
if not isinstance(payload, dict):
|
|
return None
|
|
deliveries = payload.get("deliveries")
|
|
if not isinstance(deliveries, list):
|
|
return None
|
|
for delivery in deliveries:
|
|
if not isinstance(delivery, dict):
|
|
continue
|
|
# ``deleted`` marks a cancelled day; it still appears in the list.
|
|
if delivery.get("deleted"):
|
|
continue
|
|
if str(delivery.get("date")) == wanted:
|
|
return delivery.get("deliveryId")
|
|
return None
|
|
|
|
|
|
def fetch_dishes(session: PanelSession, day: date) -> list[Dish]:
|
|
"""Return the dishes delivered on ``day``.
|
|
|
|
Args:
|
|
session: An authenticated session.
|
|
day: The delivery date to fetch.
|
|
|
|
Returns:
|
|
The day's dishes, empty when nothing is delivered that day.
|
|
|
|
Raises:
|
|
KuchniaError: When the panel cannot be read.
|
|
"""
|
|
# Never trust the list's order: on 2026-10-10 a renewal starting 10-24
|
|
# came first, and reading only it made every day look delivery-free.
|
|
delivery_id = None
|
|
for order_id in _active_order_ids(session):
|
|
order = session.get_json(f"company/customer/order/{order_id}")
|
|
delivery_id = _delivery_id_for(order, day.isoformat())
|
|
if delivery_id is not None:
|
|
break
|
|
if delivery_id is None:
|
|
return []
|
|
menu = session.get_json(f"company/general/menus/delivery/{delivery_id}/new")
|
|
dishes = parse_menu(menu)
|
|
if not dishes:
|
|
# A delivery exists but nothing survived parsing: either the menu is
|
|
# not published yet, or its units failed the consistency check. Both
|
|
# are "no data", not a crash -- but they are worth distinguishing from
|
|
# "no delivery" in the caller's message.
|
|
msg = f"catering menu for {day.isoformat()} has no usable dishes"
|
|
raise KuchniaError(msg)
|
|
return dishes
|