Skip to main content

housekeeping-service

Bounded Context: Housekeeping (Core) · Owner: Operations · Phase: 0 · Storage: Cloud SQL Postgres (shared schema + RLS, monthly partitioning on housekeeping_tasks) + transactional outbox · Bundle: services/housekeeping-service/

housekeeping-service owns the room-status lifecycle and the housekeeping task lifecycle for Ghasi Melmastoon — the multi-tenant hotel SaaS with an Electron offline-first backoffice and GCP-hosted cloud. It is the single authority for whether a room is clean, dirty, cleaning, inspected, ready, out_of_order, or out_of_service, for which staff member is responsible for cleaning it on a given shift, and for the operational hand-off with reservation-service (every checkout creates a turnover task) and property-service (which retains the structural definition of the room and its long-term OOO state).

The service is a Core context in the platform's domain classification (02 §3) because room-readiness is a strict gate on every check-in — a guest cannot be moved into a room that the cleaning board has not flipped to ready, and a missed flip cascades into front-desk delays, lobby congestion, and (eventually) cancellations. Hotels in our target markets (Afghanistan, Tajikistan, Iran) typically run with 2–6 housekeepers per shift, often without per-staff mobile devices, which means the shared desktop must be the source of truth during a shift and the sync engine must be forgiving about offline operation in basement laundry rooms and floor-end linen closets that lose Wi-Fi.

Purpose

  • Be the single authoritative aggregate for room status and housekeeping task lifecycle across every property a tenant operates.
  • Drive the turnover saga automatically: reservation.checked_out.v1 arrives → a turnover HousekeepingTask is created with priority derived from the next arrival's ETA → the task is auto-assigned to the housekeeper with the best score (proximity, current workload, language, skill) using the AI routing port → the task is worked on the desktop board until task.completed.v1 → an Inspection is scheduled (or skipped per tenant config) → on pass, room.status_changed.v1 flips to ready and the front-desk arrivals board lights up.
  • Keep the housekeeping board on the Electron desktop responsive to drag-and-drop reassignment, partial-task pause/resume, and mid-stay cleaning requests captured at the front desk.
  • Let cleaners report a maintenance issue mid-task (broken faucet, stained mattress) without leaving the cleaning flow — the service emits room.maintenance_required.v1 for maintenance-service to open a work order, and the task either pauses (light issue) or is converted to a requires_maintenance outcome (heavy issue).
  • Provide linen inventory light-stock tracking with low-watermark alerts so the housekeeping supervisor knows when towels and sheets are running short before a 6-room turnover hits the linen closet.

Key responsibilities

  1. Auto-create turnover tasks on melmastoon.reservation.checked_out.v1 with priority computed from next-arrival ETA, room type, and current requires_maintenance flags.
  2. Manual + AI-assisted task assignment — staff can drag-drop on the board; the AI routing port (calls ai-orchestrator-service) suggests an optimal allocation per shift considering proximity (group rooms by floor/wing), staff workload, ETA, language match, and skill (deep clean vs turnover).
  3. Task progress lifecyclepending → assigned → in_progress → (paused ↔ in_progress) → completed | failed | cancelled | requires_maintenance, with append-only audit on every transition.
  4. Room-status state machineclean → dirty → cleaning → cleaned → inspected → ready with side branches out_of_order (transient maintenance) and out_of_service (longer-term, owned by property-service but echoed here for the board).
  5. Mid-stay cleaning — staff or guest can request a stay-in clean; for stays > 1 night, daily mid-stay tasks are auto-scheduled per tenant config (default opt-in for properties marked "full-service").
  6. Inspection — optional per tenant; if enabled, a senior housekeeper or supervisor must run a checklist before the room flips to ready.
  7. Lost & found — recorded against the room (and the recently checked-out reservation if within 7 days), matched against guest claims, disposed per tenant policy after retention window.
  8. Linen inventory light tracking — issued counts per task, returned counts on completion, low-stock alerts at tenant-configured watermarks.
  9. Shift staffing-gap detection — monitors active shifts vs scheduled tasks; if a staff member calls in sick mid-shift, fires shift.staffing_gap_detected.v1 so the manager can re-route or call in a relief.
  10. Checklist templates — fully tenant-configurable (turnover, deep clean, maintenance check, post-renovation, …); versioned and bound to tasks at assignment time so a checklist update never silently changes an in-flight task.
  11. Offline-first desktop board — drag-and-drop reassignment, status flips, and task completion all work against the local SQLite store and sync back; conflict policy declared in SYNC_CONTRACT.
  12. Pashto/Dari/Persian UI — the housekeeping board ships with first-class support for staff who do not read English; locale is per user, not per tenant.

Aggregates owned

AggregateCardinalityPurposeIdentity prefix
HousekeepingTaskroot, 1 per cleaning unitLifecycle, assignment, checklist binding, linen issuance, outcomehkt_
RoomStatus1 per room (singleton per (tenant, property, room))Live status flag and last-flip audit(singleton key)
CleaningChecklist0..N per tenantVersioned template; bound at assignment timechl_
Inspection0..1 per taskPass/fail with photo evidenceins_
LinenInventory1 per linen line per propertyLight stock tracking, low-watermark alertslin_
LostAndFound0..N per room/reservationCapture, match, disposelaf_
StaffShiftAssignment0..N per shiftHousekeeping-scoped projection of staff-service shift; carries capacity and active task count(composite, ULID)
RoomBlock0..N per roomShared notion with maintenance-service — block reasons (cleaning, inspection, maintenance, oos); we own the cleaning/inspection rows, maintenance-service owns the maintenance rows(composite, ULID)

Key APIs (REST, /api/v1/housekeeping)

MethodPathPurpose
POST/tasksCreate a task (auto-called by event handlers; manual create via backoffice)
GET/tasksList tasks for a property/shift (filter by status, assignee, priority)
GET/tasks/:idRead one task
POST/tasks/:id/assignAssign or reassign to a staff member
POST/tasks/:id/startStaff starts working
POST/tasks/:id/pausePause (with reason: break, awaiting linen, awaiting maintenance)
POST/tasks/:id/resumeResume from pause
POST/tasks/:id/completeMark complete (with linen returned, time spent, photos)
POST/tasks/:id/failMark failed (with reason; triggers re-route or escalation)
POST/tasks/:id/escalateEscalate to supervisor (e.g., room not vacated)
POST/tasks/:id/maintenance-requiredConvert task or pause + emit room.maintenance_required.v1
POST/tasks/:id/inspectionsRun inspection (pass/fail + checklist results)
GET/rooms/:roomId/statusRead current room status
POST/rooms/:roomId/statusManual flip (supervisor override; audit-flagged)
POST/rooms/:roomId/blockBlock a room (cleaning / inspection / maintenance / oos)
DELETE/rooms/:roomId/block/:blockIdClear a block
POST/checklistsCreate or version a checklist template
GET/checklistsList active checklist templates
POST/lost-and-foundRecord a found item
POST/lost-and-found/:id/matchMatch against a guest claim
POST/lost-and-found/:id/disposeDispose per policy
GET/linenList linen lines + current stock
POST/linen/:lineId/issueIssue linen against a task
POST/linen/:lineId/returnReturn linen on completion
GET/boardComposite read for the desktop board (tasks + room statuses + active staff)
GET/stats/turnoverTurnover statistics for a property + date range

Direct callers are bff-backoffice-service (Electron desktop, the dominant traffic) and bff-tenant-booking-service (mid-stay cleaning request from in-stay guest). Internal event handlers are exposed under /internal/events/<topic-short> and authenticated by Pub/Sub OIDC.

Key events published

EventTrigger
melmastoon.housekeeping.task.created.v1Task created (auto from checkout, or manual)
melmastoon.housekeeping.task.assigned.v1Task assigned to staff
melmastoon.housekeeping.task.reassigned.v1Assignment changed
melmastoon.housekeeping.task.started.v1Staff pressed Start
melmastoon.housekeeping.task.paused.v1Pause with reason
melmastoon.housekeeping.task.resumed.v1Resumed after pause
melmastoon.housekeeping.task.completed.v1Task complete (cleaning + checklist done)
melmastoon.housekeeping.task.failed.v1Task failed with reason
melmastoon.housekeeping.task.cancelled.v1Cancelled (reservation modified, room reassigned, …)
melmastoon.housekeeping.task.escalated.v1Escalated to supervisor
melmastoon.housekeeping.room.status_changed.v1Room status flipped
melmastoon.housekeeping.room.maintenance_required.v1Cleaning revealed an issue → triggers maintenance-service
melmastoon.housekeeping.inspection.passed.v1Inspection passed
melmastoon.housekeeping.inspection.failed.v1Inspection failed (room re-cleaned)
melmastoon.housekeeping.checklist.template_updated.v1Tenant changed a checklist template
melmastoon.housekeeping.lost_item.recorded.v1Item logged in lost & found
melmastoon.housekeeping.lost_item.matched.v1Item matched to a guest claim
melmastoon.housekeeping.lost_item.disposed.v1Item disposed per policy
melmastoon.housekeeping.linen.low_stock_alert.v1Stock fell below the low-watermark
melmastoon.housekeeping.shift.staffing_gap_detected.v1Shift capacity insufficient for queued tasks

Key events consumed

EventEffect
melmastoon.reservation.checked_out.v1Auto-create turnover HousekeepingTask with priority by next-arrival ETA
melmastoon.reservation.early_checkout.v1Priority bump on existing turnover task; create one if not yet created
melmastoon.reservation.modification.requested.v1Mid-stay cleaning request → create mid_stay_clean task
melmastoon.maintenance.work_order.completed.v1Return room from out_of_order to cleaning queue (recreate task if needed)
melmastoon.staff.shift.started.v1Project a StaffShiftAssignment; refresh router capacity
melmastoon.staff.shift.ended.v1Tear down StaffShiftAssignment; reassign open tasks
melmastoon.ai_orchestrator.suggestion.housekeeping_routing.v1Apply (with HITL gate) the AI routing suggestion to pending tasks
melmastoon.property.room.archived.v1Cancel pending tasks; clear RoomStatus row
melmastoon.tenant.settings.changed.v1Refresh in-memory cache of inspection-required flag, mid-stay cadence, low-watermarks

Upstream / downstream

Upstream (we consume): reservation-service, property-service, staff-service, maintenance-service, tenant-service, ai-orchestrator-service.

Downstream (we publish for): maintenance-service, notification-service (low-stock alerts, escalations), analytics-service (turnover stats, staff performance), audit-service, search-aggregation-service (room-readiness facet for tenant booking surface), sync-service, bff-backoffice-service, bff-tenant-booking-service (mid-stay request acknowledgement).

Non-functional requirements

NFRTarget
Turnover task auto-create latency (event in → task visible)< 2 s p95
Board read p99 (GET /board)< 250 ms with 200 active rooms
Board write (drag-drop reassign) p99< 400 ms end-to-end including outbox publish
API availability99.9% monthly
Tenant isolationRLS-enforced; tenant-isolation.spec.ts mandatory in CI
Sync footprintTasks of last 30 days + room statuses + active checklists + linen + open lost-and-found replicated to desktop SQLite
ReplicasMin 2 Cloud Run instances (hot path); separate shift-staffing-gap worker as Cloud Run cron job (every 60 s)
Partitioninghousekeeping_tasks partitioned monthly; partition-pruning enforced on hot reads

Where to go next