CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/nanoclaw-orders

Order-email triage for NanoClaw: fetches order-related Gmail, maintains the orders SQLite table, and flags recent anomalies (cancellations/refunds, overdue deliveries, orders stuck in 'ordered' that never shipped), with a cadence companion that runs it on a schedule. NanoClaw per-chat overlay tile.

Quality

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

compute-stuck-orders.pyskills/check-orders/scripts/

#!/usr/bin/env python3
"""Compute the ids of orders stuck in `ordered` with no shipment.

Step 8 of check-orders SKILL.md. Deterministic stuck-order detection
(`jbaruch/nanoclaw-orders#55`): an `ordered` row whose `order_date` falls
in the stuck window — at least STUCK_ORDER_MIN_DAYS and at most
STUCK_ORDER_MAX_DAYS before today, both bounds inclusive — is stuck unless
its logical order has a shipment row. Logical-order identity is the
persisted `(source, order_number)` key; a row whose `order_number` is NULL
cannot be paired to a shipment, so it is stuck on age alone.

Pairing is a deterministic join on the stored `order_number` column
(populated at ingestion, `#58`), so it lives in this script — the earlier
agent-pairing step is gone. The confirmation and shipment emails of one
order carry the same order number, so an order that shipped is not counted
as stuck.

Below STUCK_ORDER_MIN_DAYS a slow ship is normal, not yet a signal; above
STUCK_ORDER_MAX_DAYS the alert ages out (channel stays signal-only, same
philosophy as the flag-anomalies cutoffs).

Stdout on success: `{"stuck_ids": ["amazon-...", ...]}` (ascending id
order). The list feeds `flag-anomalies.py`'s STUCK_IDS at Step 9.

Exit codes: 0 success, 1 IO/schema error.
"""

from __future__ import annotations

import json
import os
import sqlite3
import sys
from datetime import date

DB_PATH = os.environ.get("ORDERS_DB_PATH", "/workspace/store/messages.db")

STUCK_ORDER_MIN_DAYS = 7
STUCK_ORDER_MAX_DAYS = 90

_SHIPPED_STATUSES = ("shipped", "delivered", "assumed_delivered")


def _aged_candidate(order_date) -> bool:
    """True iff order_date parses as ISO date AND falls in the stuck window.

    Type-guards against SQLite's permissiveness (same rationale as
    flag-anomalies `_within_days`): a non-string or malformed value is
    ineligible rather than a crash.
    """
    if not isinstance(order_date, str) or not order_date.strip():
        return False
    try:
        parsed = date.fromisoformat(order_date.strip()[:10])
    except ValueError:
        return False
    delta = (date.today() - parsed).days
    return STUCK_ORDER_MIN_DAYS <= delta <= STUCK_ORDER_MAX_DAYS


def _order_key(source, order_number):
    """(source, order_number) logical-order key, or None when the row has
    no usable order number (NULL/blank) and so cannot be paired."""
    if isinstance(order_number, str) and order_number.strip():
        return (source, order_number.strip())
    return None


def main() -> int:
    conn = None
    try:
        conn = sqlite3.connect(DB_PATH)
        conn.row_factory = sqlite3.Row
        rows = conn.execute(
            "SELECT id, status, source, order_date, order_number FROM orders"
        ).fetchall()

        # Logical-order keys proven shipped. Built over all rows so an
        # excluded shipment still proves its `ordered` sibling shipped.
        shipped_keys = set()
        for row in rows:
            if row["status"] in _SHIPPED_STATUSES:
                key = _order_key(row["source"], row["order_number"])
                if key is not None:
                    shipped_keys.add(key)

        stuck_ids = []
        for row in rows:
            if row["status"] != "ordered" or not _aged_candidate(row["order_date"]):
                continue
            key = _order_key(row["source"], row["order_number"])
            if key is None or key not in shipped_keys:
                stuck_ids.append(row["id"])

        json.dump({"stuck_ids": sorted(stuck_ids)}, sys.stdout)
        sys.stdout.write("\n")
        return 0
    except sqlite3.Error as exc:
        sys.stderr.write(
            f"compute-stuck-orders: SQLite error against {DB_PATH}: {exc}. "
            f"Verify the database file exists, is readable, and the orders "
            f"table is present (created by the orchestrator's state-001 "
            f"migration, with order_number added by state-017).\n"
        )
        return 1
    finally:
        if conn is not None:
            conn.close()


if __name__ == "__main__":
    sys.exit(main())

skills

check-orders

SKILL.md

state-schema.md

CHANGELOG.md

pyrightconfig.json

README.md

tile.json