feat(maintenance): amenity-driven maintenance plan catalog and planner [TURNWRK-630] - #23
Draft
jcotillo wants to merge 1 commit into
Draft
feat(maintenance): amenity-driven maintenance plan catalog and planner [TURNWRK-630]#23jcotillo wants to merge 1 commit into
jcotillo wants to merge 1 commit into
Conversation
…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
marked this pull request as draft
September 4, 2026 16:09
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Turnwrk stores
supply.amenitiesandmaintenance.assetson a property but nothing reads them, andPropertyAmenityhad 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-e07d199b7f32What
PropertyAmenitywidened from 7 to 46 classes; the original seven keep their spelling.src/maintenance/(new subpath export@turnwrk/shared/maintenance):catalog.tsper 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.tskeyword classification of the free-text asset register plus the preventive work each class implies.aliases.tsthe 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.tsplanForProperty({ 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.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
npm run typecheck && npm run test:run(61 files, 582 tests, 32 of them new undertests/maintenance/).npm run build && node scripts/gen-vendored-package-json.mjs > /dev/nullconfirms the new subpath resolves for vendored consumers.packages/shared/src/maintenancein dispatch;npm run typecheckthere stays clean because nothing consumes the union as a closed set.Note:
scripts/check-vendored-email.shfails 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