lib/policies.jsPOST /api/maintenance
10// --- /api/maintenance: a tenant texts the building ---------------------------
11// "smell gas in the hallway" at 3am and "paint chipping in bathroom" must not
12// land in the same queue.
13
14const issue = choice('issue', 'What is `message` reporting?', {
15 'water-leak': 'Water leaking, dripping, flooding, or a burst pipe',
16 'no-heat': 'No heat or no hot water',
17 'no-power': 'Power out in the unit, or a dead breaker',
18 lockout: 'Locked out, lost keys, or a broken lock',
19 pests: 'Mice, roaches, bedbugs, or other pests',
20 cosmetic: 'Paint, scuffs, squeaks, and other things that can wait',
21 other: 'Something else',
22});
23const danger = noul('danger?', 'Could anyone be in physical danger right now?', {
24 criteria: { true: 'Gas smell, smoke, sparks, carbon monoxide alarm, water near electrics, structural damage', false: 'Nothing suggests risk to people' },
25});
26
27export const maintenance = definePolicy({
28 name: 'building-maintenance', version: '1', owner: 'property-ops',
29 questions: [issue, danger],
30 state: {
31 message: { path: ['message'], default: '', maxChars: 1500 },
32 hour: { path: ['hour'], local: true },
33 'outside-temp-c': { path: ['outsideTempC'], default: 15, local: true },
34 },
35 stateOptions: { redact: ['emails', 'phones', 'cards', 'keys'] },
36 gates: [gate(issue, 0.75, escalate('property-manager', { reason: 'not sure what the tenant is reporting' }), { onRead: true })],
37 route: {
38 clauses: [
39 // Deliberately low bar: a false alarm costs a phone call, a miss costs a life.
40 rule(danger.yes(0.3), page('emergency-oncall', { reason: 'possible danger: tell the tenant to get out and call 911' })),
41 rule(all(issue.is('no-heat'), compare('lt', fact('outside-temp-c'), 5)), page('hvac-oncall', { reason: 'no heat in freezing weather' })),
42 rule(all(issue.is('water-leak'), any(compare('gte', fact('hour'), 20), compare('lt', fact('hour'), 7))), page('plumber-oncall', { reason: 'after-hours leak' })),
43 rule(issue.is('water-leak'), assign('plumber-today')),
44 rule(issue.is('no-power'), assign('electrician-today')),
45 rule(issue.is('no-heat'), assign('hvac-next-slot')),
46 rule(issue.is('lockout'), assign('locksmith')),
47 rule(issue.is('pests'), assign('pest-control-route')),
48 rule(issue.is('cosmetic'), assign('maintenance-backlog')),
49 ],
50 otherwise: escalate('property-manager', { reason: 'no standing rule for this' }),
51 },
52});