|
@@ -2,7 +2,6 @@ import asyncio
|
|
|
from datetime import date
|
|
from datetime import date
|
|
|
from fastmcp import FastMCP
|
|
from fastmcp import FastMCP
|
|
|
from room_cal._utils import services as _services, safe_json_loads, iter_dates
|
|
from room_cal._utils import services as _services, safe_json_loads, iter_dates
|
|
|
-from room_cal.store import DEFAULT_CALENDAR_NAME
|
|
|
|
|
from room_cal._health import caldav_checks, credentials_checks
|
|
from room_cal._health import caldav_checks, credentials_checks
|
|
|
from room_cal.pricing import CalPrice
|
|
from room_cal.pricing import CalPrice
|
|
|
from room_cal.pricing.calc import (
|
|
from room_cal.pricing.calc import (
|
|
@@ -27,7 +26,7 @@ _API_SOURCE = {'domain': 'mcp', 'id': ''}
|
|
|
async def health_check() -> dict:
|
|
async def health_check() -> dict:
|
|
|
"""Check CalDAV connection, room calendar reachability, and service credential validity. Returns {caldav_connection, calendar, credentials}."""
|
|
"""Check CalDAV connection, room calendar reachability, and service credential validity. Returns {caldav_connection, calendar, credentials}."""
|
|
|
(conn, cal_result), creds = await asyncio.gather(
|
|
(conn, cal_result), creds = await asyncio.gather(
|
|
|
- caldav_checks(DEFAULT_CALENDAR_NAME),
|
|
|
|
|
|
|
+ caldav_checks(),
|
|
|
credentials_checks(),
|
|
credentials_checks(),
|
|
|
)
|
|
)
|
|
|
return {
|
|
return {
|
|
@@ -58,14 +57,14 @@ async def convert_price(transformation: str, service: str, amount: int) -> dict:
|
|
|
@mcp.tool()
|
|
@mcp.tool()
|
|
|
async def get_prices(start_date: str, end_date: str) -> dict:
|
|
async def get_prices(start_date: str, end_date: str) -> dict:
|
|
|
"""Get price dicts for a date range (YYYY-MM-DD). Returns a dict keyed by date string; None for dates with no price set."""
|
|
"""Get price dicts for a date range (YYYY-MM-DD). Returns a dict keyed by date string; None for dates with no price set."""
|
|
|
- price_range = await CalPrice(DEFAULT_CALENDAR_NAME).get(date.fromisoformat(start_date), date.fromisoformat(end_date))
|
|
|
|
|
|
|
+ price_range = await CalPrice().get(date.fromisoformat(start_date), date.fromisoformat(end_date))
|
|
|
return {k: v.to_dict() if v else None for k, v in price_range.items()}
|
|
return {k: v.to_dict() if v else None for k, v in price_range.items()}
|
|
|
|
|
|
|
|
|
|
|
|
|
@mcp.tool()
|
|
@mcp.tool()
|
|
|
async def set_price(target_date: str, value: float) -> None:
|
|
async def set_price(target_date: str, value: float) -> None:
|
|
|
"""Set the net price for a date (YYYY-MM-DD)."""
|
|
"""Set the net price for a date (YYYY-MM-DD)."""
|
|
|
- await CalPrice(DEFAULT_CALENDAR_NAME).set(date.fromisoformat(target_date), value)
|
|
|
|
|
|
|
+ await CalPrice().set(date.fromisoformat(target_date), value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -74,7 +73,7 @@ async def set_price(target_date: str, value: float) -> None:
|
|
|
async def patch_reservation(uid: str, checkin_date: str, wifi_ssid: str | None = None, wifi_key: str | None = None, door_code: str | None = None) -> dict:
|
|
async def patch_reservation(uid: str, checkin_date: str, wifi_ssid: str | None = None, wifi_key: str | None = None, door_code: str | None = None) -> dict:
|
|
|
"""Update guest access fields (wifi_ssid, wifi_key, door_code) on a stored reservation. checkin_date (YYYY-MM-DD) is required to locate the event. Only supplied fields are written. Returns updated ReservationData as a dict."""
|
|
"""Update guest access fields (wifi_ssid, wifi_key, door_code) on a stored reservation. checkin_date (YYYY-MM-DD) is required to locate the event. Only supplied fields are written. Returns updated ReservationData as a dict."""
|
|
|
from dataclasses import asdict
|
|
from dataclasses import asdict
|
|
|
- result = await Reservation(DEFAULT_CALENDAR_NAME).patch(
|
|
|
|
|
|
|
+ result = await Reservation().patch(
|
|
|
uid, date.fromisoformat(checkin_date),
|
|
uid, date.fromisoformat(checkin_date),
|
|
|
wifi_ssid=wifi_ssid, wifi_key=wifi_key, door_code=door_code,
|
|
wifi_ssid=wifi_ssid, wifi_key=wifi_key, door_code=door_code,
|
|
|
)
|
|
)
|
|
@@ -87,7 +86,7 @@ async def patch_reservation(uid: str, checkin_date: str, wifi_ssid: str | None =
|
|
|
async def get_reservation(target_date: str) -> dict | None:
|
|
async def get_reservation(target_date: str) -> dict | None:
|
|
|
"""Get the reservation covering a date (checkin ≤ date < checkout), or null."""
|
|
"""Get the reservation covering a date (checkin ≤ date < checkout), or null."""
|
|
|
d = date.fromisoformat(target_date)
|
|
d = date.fromisoformat(target_date)
|
|
|
- for ev in await Reservation(DEFAULT_CALENDAR_NAME).list(d, d):
|
|
|
|
|
|
|
+ for ev in await Reservation().list(d, d):
|
|
|
if ev['start'].date() <= d < ev['end'].date():
|
|
if ev['start'].date() <= d < ev['end'].date():
|
|
|
xd = safe_json_loads(ev['x_data']) or {}
|
|
xd = safe_json_loads(ev['x_data']) or {}
|
|
|
xd['checkin_date'] = ev['start'].date().isoformat()
|
|
xd['checkin_date'] = ev['start'].date().isoformat()
|
|
@@ -101,7 +100,7 @@ async def get_reservations_range(start_date: str, end_date: str) -> dict:
|
|
|
"""Get reservations for each date in a range. Returns {date: dict or null}."""
|
|
"""Get reservations for each date in a range. Returns {date: dict or null}."""
|
|
|
start = date.fromisoformat(start_date)
|
|
start = date.fromisoformat(start_date)
|
|
|
end = date.fromisoformat(end_date)
|
|
end = date.fromisoformat(end_date)
|
|
|
- events = await Reservation(DEFAULT_CALENDAR_NAME).list(start, end)
|
|
|
|
|
|
|
+ events = await Reservation().list(start, end)
|
|
|
result: dict[str, dict | None] = {str(d): None for d in iter_dates(start, end)}
|
|
result: dict[str, dict | None] = {str(d): None for d in iter_dates(start, end)}
|
|
|
for ev in events:
|
|
for ev in events:
|
|
|
checkin = ev['start'].date()
|
|
checkin = ev['start'].date()
|
|
@@ -118,7 +117,7 @@ async def get_reservations_range(start_date: str, end_date: str) -> dict:
|
|
|
@mcp.tool()
|
|
@mcp.tool()
|
|
|
async def get_closed(target_date: str) -> list[str]:
|
|
async def get_closed(target_date: str) -> list[str]:
|
|
|
"""Get closed service keys for a date. Empty list means open."""
|
|
"""Get closed service keys for a date. Empty list means open."""
|
|
|
- return await Closed(DEFAULT_CALENDAR_NAME).get(date.fromisoformat(target_date)) or []
|
|
|
|
|
|
|
+ return await Closed().get(date.fromisoformat(target_date)) or []
|
|
|
|
|
|
|
|
|
|
|
|
|
@mcp.tool()
|
|
@mcp.tool()
|
|
@@ -126,7 +125,7 @@ async def get_closed_range(start_date: str, end_date: str) -> dict:
|
|
|
"""Get closed services for each date in a range. Returns {date: [service, ...]}."""
|
|
"""Get closed services for each date in a range. Returns {date: [service, ...]}."""
|
|
|
start = date.fromisoformat(start_date)
|
|
start = date.fromisoformat(start_date)
|
|
|
end = date.fromisoformat(end_date)
|
|
end = date.fromisoformat(end_date)
|
|
|
- events = await Closed(DEFAULT_CALENDAR_NAME).list(start, end)
|
|
|
|
|
|
|
+ events = await Closed().list(start, end)
|
|
|
by_date: dict[str, list] = {str(d): [] for d in iter_dates(start, end)}
|
|
by_date: dict[str, list] = {str(d): [] for d in iter_dates(start, end)}
|
|
|
for ev in events:
|
|
for ev in events:
|
|
|
d = ev['start'].date()
|
|
d = ev['start'].date()
|
|
@@ -139,13 +138,13 @@ async def get_closed_range(start_date: str, end_date: str) -> dict:
|
|
|
@mcp.tool()
|
|
@mcp.tool()
|
|
|
async def set_closed(target_date: str, services: list[str]) -> None:
|
|
async def set_closed(target_date: str, services: list[str]) -> None:
|
|
|
"""Block a date for the given services. Empty list unblocks the date."""
|
|
"""Block a date for the given services. Empty list unblocks the date."""
|
|
|
- await Closed(DEFAULT_CALENDAR_NAME).set(date.fromisoformat(target_date), services, source=_API_SOURCE)
|
|
|
|
|
|
|
+ await Closed().set(date.fromisoformat(target_date), services, source=_API_SOURCE)
|
|
|
|
|
|
|
|
|
|
|
|
|
@mcp.tool()
|
|
@mcp.tool()
|
|
|
async def get_discount(target_date: str) -> dict | None:
|
|
async def get_discount(target_date: str) -> dict | None:
|
|
|
"""Get discount for a date, or null if none set."""
|
|
"""Get discount for a date, or null if none set."""
|
|
|
- return await Discount(DEFAULT_CALENDAR_NAME).get(date.fromisoformat(target_date))
|
|
|
|
|
|
|
+ return await Discount().get(date.fromisoformat(target_date))
|
|
|
|
|
|
|
|
|
|
|
|
|
@mcp.tool()
|
|
@mcp.tool()
|
|
@@ -153,7 +152,7 @@ async def get_discounts_range(start_date: str, end_date: str) -> dict:
|
|
|
"""Get discounts for each date in a range. Returns {date: {type, value} or null}."""
|
|
"""Get discounts for each date in a range. Returns {date: {type, value} or null}."""
|
|
|
start = date.fromisoformat(start_date)
|
|
start = date.fromisoformat(start_date)
|
|
|
end = date.fromisoformat(end_date)
|
|
end = date.fromisoformat(end_date)
|
|
|
- events = await Discount(DEFAULT_CALENDAR_NAME).list(start, end)
|
|
|
|
|
|
|
+ events = await Discount().list(start, end)
|
|
|
by_date: dict[str, dict | None] = {str(d): None for d in iter_dates(start, end)}
|
|
by_date: dict[str, dict | None] = {str(d): None for d in iter_dates(start, end)}
|
|
|
for ev in events:
|
|
for ev in events:
|
|
|
d = ev['start'].date()
|
|
d = ev['start'].date()
|
|
@@ -167,7 +166,7 @@ async def get_discounts_range(start_date: str, end_date: str) -> dict:
|
|
|
@mcp.tool()
|
|
@mcp.tool()
|
|
|
async def set_discount(target_date: str, discount_type: str, value: int) -> None:
|
|
async def set_discount(target_date: str, discount_type: str, value: int) -> None:
|
|
|
"""Set a discount. discount_type: 'percent' or 'flat'."""
|
|
"""Set a discount. discount_type: 'percent' or 'flat'."""
|
|
|
- await Discount(DEFAULT_CALENDAR_NAME).set(
|
|
|
|
|
|
|
+ await Discount().set(
|
|
|
date.fromisoformat(target_date), discount_type, value, source=_API_SOURCE
|
|
date.fromisoformat(target_date), discount_type, value, source=_API_SOURCE
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -175,7 +174,7 @@ async def set_discount(target_date: str, discount_type: str, value: int) -> None
|
|
|
@mcp.tool()
|
|
@mcp.tool()
|
|
|
async def delete_discount(target_date: str) -> None:
|
|
async def delete_discount(target_date: str) -> None:
|
|
|
"""Remove the discount on a date."""
|
|
"""Remove the discount on a date."""
|
|
|
- await Discount(DEFAULT_CALENDAR_NAME).delete_on(date.fromisoformat(target_date))
|
|
|
|
|
|
|
+ await Discount().delete_on(date.fromisoformat(target_date))
|
|
|
|
|
|
|
|
|
|
|
|
|
@mcp.tool()
|
|
@mcp.tool()
|