Skip to content

feat(maintenance): amenity-driven maintenance plan catalog and planner [TURNWRK-630] - #23

Draft
jcotillo wants to merge 1 commit into
mainfrom
turnwrk-630-maintenance-plan-catalog
Draft

feat(maintenance): amenity-driven maintenance plan catalog and planner [TURNWRK-630]#23
jcotillo wants to merge 1 commit into
mainfrom
turnwrk-630-maintenance-plan-catalog

Conversation

@jcotillo

@jcotillo jcotillo commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Why

Turnwrk stores supply.amenities and maintenance.assets on a property but nothing reads them, and PropertyAmenity had seven values. A property's maintenance load could not be derived, so preventive plans and field partner quotes were built by hand. Ticket: https://plane.practical.works/practical-works-core/projects/08b7ecad-9b14-407c-bb58-8b7281199f16/issues/ff467abd-bb6f-40cd-812f-e07d199b7f32

What

  • PropertyAmenity widened from 7 to 46 classes; the original seven keep their spelling.
  • src/maintenance/ (new subpath export @turnwrk/shared/maintenance):
    • catalog.ts per amenity class: per-turn inspection items and minutes, preventive tasks (cadence, trade, minutes, priority, checklist sections), corrective load (calls per year, trade, minutes). Data in the vertical pack style; every number is a tunable default.
    • assets.ts keyword classification of the free-text asset register plus the preventive work each class implies.
    • aliases.ts the single label to class table (Airbnb and operator labels), three outcomes: matched, ignored (supply and policy labels), unmapped. Unknown labels are surfaced, never dropped. Unavailable: rows are absences.
    • plan.ts planForProperty({ amenities, assets, turnsPerMonth, inHouseTrades }) returns schedules to create, the composed per-turn inspection sections, and a monthly minutes load by trade split in-house versus specialty. Pure, deterministic, integers out.
  • No consumer changes in this PR. Dispatch's plan route (TURNWRK-631) and quote (TURNWRK-632) build on it after the sync.

Palmshine Hideaway (the first fixture, read from the live Airbnb modal)

62 labelled rows plus 6 description-only features: 29 classes matched, 21 supply and policy labels ignored, 0 unmapped. Walk 85 min across 27 sections, 29 preventive schedules, about 18.9 h per month at 5 turns: handyman 13.3 h in-house, pool 2.7 h, then landscaping, arcade, hvac, appliance, plumbing, court surface, gas, locksmith.

How to verify

  1. npm run typecheck && npm run test:run (61 files, 582 tests, 32 of them new under tests/maintenance/).
  2. npm run build && node scripts/gen-vendored-package-json.mjs > /dev/null confirms the new subpath resolves for vendored consumers.
  3. After merge, the sync-consumers PR lands packages/shared/src/maintenance in dispatch; npm run typecheck there stays clean because nothing consumes the union as a closed set.

Note: scripts/check-vendored-email.sh fails locally on macOS sed (\1 not defined in the RE) before and after this change; CI runs it on ubuntu. Not touched here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01S8xWFH8zVEkciKR59Tq3sm

…r (TURNWRK-630)

Turnwrk stored amenities and assets on a property but nothing read them,
so a property's maintenance load could not be derived. This adds the
catalog and the pure planner behind it:

- PropertyAmenity widened from seven classes to the real STR set (heated
  pool, hot tub, pickleball court, putting green, arcade, outdoor bar,
  fire pit, safety equipment, access hardware, ...) without changing the
  original seven.
- src/maintenance/catalog.ts: per amenity class, what a tech checks on
  every turn, which preventive tasks recur (cadence, trade, minutes,
  checklist sections) and how often it breaks and who answers. Data, in
  the vertical pack style; every number is a tunable default.
- src/maintenance/assets.ts: keyword classification of the free-text
  asset register (water heater, HVAC, pool pump, smart lock, ...) with
  the preventive work each implies.
- src/maintenance/aliases.ts: the one table that turns Airbnb and
  operator labels into classes, with three outcomes (matched, ignored
  supply or policy labels, unmapped). Unknown labels are surfaced, never
  dropped; "Unavailable:" rows are absences.
- src/maintenance/plan.ts: planForProperty folds amenities and assets
  into schedules to create, a composed per-turn inspection, and a monthly
  minutes load split by trade and by in-house versus specialty.
  Deterministic, no I/O, integers out.

Tests: alias normalization, catalog invariants (unique keys, PM-expressible
cadences, namespaced item ids), planner math and edges, and the Palmshine
Hideaway fixture read from the live Airbnb amenities modal (all 62 labelled
rows place; zero unmapped).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S8xWFH8zVEkciKR59Tq3sm
@jcotillo
jcotillo marked this pull request as draft September 4, 2026 16:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant