bookly/mock_data.py
Cody Borders 30cdea2aac Build Bookly customer support agent
A FastAPI + vanilla JS chat app fronting an Anthropic Claude agent for
order status, returns, and policy questions.

Architecture:
- agent.py: system prompt, runtime reminder injection, output validation,
  agentic tool-use loop with prompt caching on the system prompt block
- tools.py: four tools (lookup_order, check_return_eligibility,
  initiate_return, lookup_policy) with per-session SessionGuardState
  enforcing protocol ordering on the tool side
- mock_data.py: orders, return policy, and FAQ entries used as the single
  source of truth by both the prompt and the tools
- server.py: FastAPI app exposing /api/chat, /health, and the static UI
- static/: vanilla HTML/CSS/JS chat UI, no build step
- tests/: 30 tests covering tool-side enforcement, the privacy boundary,
  output validation, and the agent loop with a mocked Anthropic client
- deploy/: systemd unit and nginx site config for production
2026-04-14 22:17:59 -07:00

110 lines
4.1 KiB
Python

"""In-memory data fixtures for orders, returns, and FAQ policies.
`ORDERS` and `RETURN_POLICY` are read by both the system prompt (so the prompt
quotes policy verbatim instead of paraphrasing) and the tool handlers (so the
two never drift apart). `RETURNS` is mutated by `initiate_return` at runtime.
"""
from datetime import date, timedelta
# A frozen "today" so the four-order fixture stays deterministic across runs.
TODAY = date(2026, 4, 14)
def _days_ago(n: int) -> str:
return (TODAY - timedelta(days=n)).isoformat()
RETURN_POLICY: dict = {
"window_days": 30,
"condition_requirements": "Items must be unread, undamaged, and in their original packaging.",
"refund_method": "Refunds are issued to the original payment method.",
"refund_timeline_days": 7,
"non_returnable_categories": ["ebooks", "audiobooks", "gift cards", "personalized items"],
}
# Four orders covering the interesting scenarios. Sarah Chen has two orders so
# the agent must disambiguate when she says "my order".
ORDERS: dict = {
"BK-10042": {
"order_id": "BK-10042",
"customer_name": "Sarah Chen",
"email": "sarah.chen@example.com",
"status": "delivered",
"order_date": _days_ago(20),
"delivered_date": _days_ago(15),
"tracking_number": "1Z999AA10123456784",
"items": [
{"title": "The Goldfinch", "author": "Donna Tartt", "price": 16.99, "category": "fiction"},
{"title": "Sapiens", "author": "Yuval Noah Harari", "price": 19.99, "category": "nonfiction"},
],
"total": 36.98,
},
"BK-10089": {
"order_id": "BK-10089",
"customer_name": "James Murphy",
"email": "james.murphy@example.com",
"status": "shipped",
"order_date": _days_ago(4),
"delivered_date": None,
"tracking_number": "1Z999AA10987654321",
"items": [
{"title": "Project Hail Mary", "author": "Andy Weir", "price": 18.99, "category": "fiction"},
],
"total": 18.99,
},
"BK-10103": {
"order_id": "BK-10103",
"customer_name": "Sarah Chen",
"email": "sarah.chen@example.com",
"status": "processing",
"order_date": _days_ago(1),
"delivered_date": None,
"tracking_number": None,
"items": [
{"title": "Tomorrow, and Tomorrow, and Tomorrow", "author": "Gabrielle Zevin", "price": 17.99, "category": "fiction"},
],
"total": 17.99,
},
"BK-9871": {
"order_id": "BK-9871",
"customer_name": "Maria Gonzalez",
"email": "maria.gonzalez@example.com",
"status": "delivered",
"order_date": _days_ago(60),
"delivered_date": _days_ago(55),
"tracking_number": "1Z999AA10555555555",
"items": [
{"title": "The Midnight Library", "author": "Matt Haig", "price": 15.99, "category": "fiction"},
],
"total": 15.99,
},
}
# Verbatim FAQ entries returned by `lookup_policy`. The agent quotes these
# without paraphrasing.
POLICIES: dict[str, str] = {
"shipping": (
"Standard shipping is free on orders over $25 and takes 3-5 business days. "
"Expedited shipping (1-2 business days) is $9.99. We ship to all 50 US states. "
"International shipping is not currently available."
),
"password_reset": (
"To reset your password, go to bookly.com/account/login and click \"Forgot password.\" "
"Enter the email on your account and we will send you a reset link. "
"The link expires after 24 hours. If you do not receive the email, check your spam folder."
),
"returns_overview": (
"You can return most items within 30 days of delivery for a full refund to your original "
"payment method. Items must be unread, undamaged, and in their original packaging. "
"Ebooks, audiobooks, gift cards, and personalized items are not returnable. "
"Refunds typically post within 7 business days of us receiving the return."
),
}
# Mutated at runtime by `initiate_return`. Keyed by return_id.
RETURNS: dict[str, dict] = {}