$ uv run tools/flow.py push reference/lab03-example1-budget.json
! unpublished '75 IT helpdesk + long question cap (answer)' (w3d4ba9195f92a57): it used the same webhook path
✔ pushed '75 IT helpdesk + daily budget (reference)' (id wd38606e31f611a9)
   POST http://localhost:5678/webhook/my-cost

$ uv run tools/flow.py test cases/lab03.json -v
POST http://localhost:5678/webhook/my-cost · 3 case(s) × 1
✔ before: POST http://localhost:8800/usage-clear · HTTP 200 · 0.0s · {"cleared": 0}
    → request  POST http://localhost:8800/usage-clear
      {}
    ← response HTTP 200
      {
        "cleared": 0
      }
✔ before: POST http://localhost:8800/budget · HTTP 200 · 0.0s · {"daily_usd": 0.001, "user_limit_per_min": 20, "max_question_chars": 500, "used_usd": 0, "remaining_usd": 0.001, "over":
    → request  POST http://localhost:8800/budget
      {
        "daily_usd": 0.001
      }
    ← response HTTP 200
      {
        "daily_usd": 0.001,
        "user_limit_per_min": 20,
        "max_question_chars": 500,
        "used_usd": 0,
        "remaining_usd": 0.001,
        "over": false
      }
✔ before: POST http://localhost:8800/usage · HTTP 200 · 0.0s · {"stored": 1, "total": 1}
    → request  POST http://localhost:8800/usage
      {
        "request_id": "seed",
        "user": "seed",
        "step": "answer",
        "model": "gpt-4.1-mini",
        "cost_usd": 0.002
      }
    ← response HTTP 200
      {
        "stored": 1,
        "total": 1
      }
✔ 한도를 넘으면 · FAQ 원문 · 1.0s · 한도 초과 · 0 calls · $0.000000
    → request  POST /webhook/my-cost
      {
        "question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
        "user": "이준호"
      }
    ← response HTTP 200
      {
        "route": "한도 초과",
        "answer": "VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속합니다. (오늘 자동 답변 한도를 넘어 FAQ 원문으로 안내합니다.)",
        "sources": [
          "FAQ-003"
        ],
        "llm_calls": 0,
        "usage": {
          "prompt_tokens": 0,
          "completion_tokens": 0,
          "by_step": {}
        },
        "cost_usd": 0
      }
✔ 한도를 넘으면 · 접수 원문 · 0.2s · 한도 초과 · 0 calls · $0.000000
    → request  POST /webhook/my-cost
      {
        "question": "T-1002 처리 상태 알려 주세요",
        "user": "박서연"
      }
    ← response HTTP 200
      {
        "route": "한도 초과",
        "answer": "T-1002 접수 건은 현재 '처리 중' 상태입니다. 인증서 발급 승인 대기 중이며 10월 20일 완료 예정입니다. (오늘 자동 답변 한도를 넘어 원문으로 안내합니다.)",
        "sources": [],
        "llm_calls": 0,
        "usage": {
          "prompt_tokens": 0,
          "completion_tokens": 0,
          "by_step": {}
        },
        "cost_usd": 0
      }
✔ before: POST http://localhost:8800/budget/reset · HTTP 200 · 0.0s · {"daily_usd": 1.0, "user_limit_per_min": 20, "max_question_chars": 500, "used_usd": 0.002, "remaining_usd": 0.998, "over
    → request  POST http://localhost:8800/budget/reset
      {}
    ← response HTTP 200
      {
        "daily_usd": 1.0,
        "user_limit_per_min": 20,
        "max_question_chars": 500,
        "used_usd": 0.002,
        "remaining_usd": 0.998,
        "over": false
      }
✔ before: POST http://localhost:8800/usage-clear · HTTP 200 · 0.0s · {"cleared": 1}
    → request  POST http://localhost:8800/usage-clear
      {}
    ← response HTTP 200
      {
        "cleared": 1
      }
✔ 한도 안이면 평소대로 · 1.6s · 자동 답변 · 1 call · $0.000156
    → request  POST /webhook/my-cost
      {
        "question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
        "user": "이준호"
      }
    ← response HTTP 200
      {
        "route": "자동 답변",
        "answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 티켓은 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
        "sources": [
          "FAQ-003"
        ],
        "llm_calls": 1,
        "usage": {
          "prompt_tokens": 165,
          "completion_tokens": 56,
          "by_step": {
            "answer": {
              "model": "gpt-4.1-mini",
              "prompt_tokens": 165,
              "completion_tokens": 56,
              "cost_usd": 0.0001556
            }
          }
        },
        "cost_usd": 0.0001556,
        "trimmed": false,
        "question_chars": 24,
        "recorded": true
      }
Passed 3/3
report: runs/lab03-20261028-210722.json
✔ after: POST http://localhost:8800/budget/reset · HTTP 200 · 0.0s · {"daily_usd": 1.0, "user_limit_per_min": 20, "max_question_chars": 500, "used_usd": 0.0001556, "remaining_usd": 0.999844
    → request  POST http://localhost:8800/budget/reset
      {}
    ← response HTTP 200
      {
        "daily_usd": 1.0,
        "user_limit_per_min": 20,
        "max_question_chars": 500,
        "used_usd": 0.0001556,
        "remaining_usd": 0.9998444,
        "over": false
      }
✔ after: POST http://localhost:8800/usage-clear · HTTP 200 · 0.0s · {"cleared": 1}
    → request  POST http://localhost:8800/usage-clear
      {}
    ← response HTTP 200
      {
        "cleared": 1
      }
