Applied Module 12 · The Nick E. Playbook

Morning Maintenance Triage Board

What you'll learn

~30 min
  • Parse PMS-style CSV exports of maintenance work orders in Node.js
  • Implement transparent priority-scoring rules (keyword matching + age + safety flags)
  • Visualize backlog by urgency, category, and building location
  • Generate a 'first 10 actions' daily queue and printable standup summary

What you’re building

It is 6:45 AM. You walk into the maintenance office at The Concord Crystal City. Overnight, 14 new work orders came in through the resident portal. Three are about a leak on the 12th floor. Two are noise complaints. One says “no hot water” with three exclamation marks. The rest are routine — a squeaky closet door, a dishwasher that smells, a request to re-caulk a bathtub. Your two maintenance techs arrive at 7:30 and need to know exactly where to go first.

Right now you open Yardi, scroll through the list, try to mentally sort by urgency, and scribble a priority list on a whiteboard. By the time you are done, it is 7:20 and you have not had coffee. What if you could export last night’s work orders as a CSV, run one command, and have a priority-scored action queue with a printable standup summary ready before your techs walk in the door?

That is what you will build in the next 25 minutes.

💬This is a daily-use tool

You will use this tool every single morning. It takes the most stressful 30 minutes of your day — the triage scramble — and compresses it into 30 seconds. Build it once, use it 250 times a year. That is not a practice exercise. That is an operational upgrade.

By the end of this lesson you will have a Node.js CLI tool that reads a CSV of maintenance work orders, applies a transparent priority-scoring algorithm (keyword detection, age-based urgency, safety flags), generates a sorted action queue, and outputs both a terminal summary and a printable HTML dashboard with Chart.js visualizations. No server. No subscription. Just a command you run before your morning standup.

Software pattern: CSV pipeline with scoring engine

Import CSV + apply rules + rank + visualize. This pattern works for helpdesk tickets, customer support queues, inspection checklists — any prioritized backlog. The techniques in this lesson transfer directly to non-property contexts.

🔍Domain Primer: Maintenance operations terms you'll see in this lesson

New to maintenance operations? Here are the key terms:

  • Work order — A formal request for maintenance work. When a resident submits a request through the portal (“my dishwasher is leaking”), the PMS creates a work order with a unique ID, timestamp, unit number, description, category, and status. Think of it like a support ticket, but for physical building issues.
  • SLA (Service Level Agreement) — The maximum response and completion time for different priority levels. At a property like The Concord, typical SLAs are: Emergency (life safety, major leak, no heat in winter) = 4-hour response, 24-hour resolution. Urgent (no hot water, broken AC in summer, elevator down) = 24-hour response, 48-hour resolution. Routine (cosmetic, minor repair, appliance issue) = 48-hour response, 5-business-day resolution. SLA breaches are tracked by regional managers and affect property performance scores.
  • Morning standup — A brief daily meeting (5-10 minutes) where the maintenance supervisor reviews the day’s priorities with the techs. Typically done at 7:30 or 8:00 AM. The standup covers: what was completed yesterday, what is on deck today, and any blockers (parts on order, vendor scheduling, access issues). A good standup board makes this meeting crisp and efficient.
  • Make-ready — The process of preparing a vacant unit for the next resident. Includes cleaning, painting, repairs, appliance checks, and final inspection. Make-ready work orders compete with resident requests for tech time, so they need to be factored into daily scheduling.
  • Vendor/tech assignment — Matching work orders to the right person. A general maintenance tech handles most issues (plumbing, electrical, HVAC basics), but specialized work (elevator repair, fire alarm panel, roof leak) requires an outside vendor. The triage board suggests assignments based on work order category.

You don’t need to memorize these — the tool will use them in context. Knowing what they mean helps you understand why the scoring rules work the way they do.

Who this is for

  • Maintenance supervisors who run the morning standup and need a prioritized queue before their team arrives.
  • Community managers who want visibility into the maintenance backlog without digging through the PMS.
  • Regional property managers who want consistent triage logic across every property in their portfolio.
Why CSV and not a direct PMS integration?

Every PMS (Yardi, RealPage, Entrata, AppFolio) has a CSV export function for work orders. Starting with CSV means this tool works with any system, today, with zero IT involvement. You do not need API access, vendor approval, or a developer to build an integration. Export the CSV, run the tool, get your board. If you want a direct integration later, that is a follow-up prompt — but the CSV approach works on day one.


The showcase

Here is what the finished tool produces:

  • Terminal output: A color-coded priority queue showing the top 10 actions, each with work order ID, unit, issue summary, priority score, SLA status, and suggested assignment
  • HTML dashboard (output/triage-board.html): A printable single-page board with:
    • Priority queue table — all work orders sorted by score, color-coded by urgency tier (red/orange/yellow/green)
    • Urgency breakdown pie chart — how many emergency, urgent, routine, and make-ready orders
    • Category bar chart — plumbing, HVAC, electrical, appliance, general, exterior
    • Building location heatmap — work orders by floor (floors 1-18), showing which floors have the most open issues
    • SLA breach warnings — highlighted rows for any work order that has exceeded or is approaching its SLA deadline
    • Standup summary — a clean, printable block with today’s date, total open orders, top priorities, and tech assignments

Everything runs locally. The HTML dashboard uses Chart.js from CDN for charts and can be printed directly or saved as PDF from the browser.


The prompt

Open your terminal, navigate to a project folder, start your AI CLI tool (e.g., by typing claude), and paste this prompt:

Build a Node.js CLI tool called maintenance-triage-board that parses
maintenance work order CSVs and generates a prioritized daily action queue
with a printable HTML dashboard. The property is The Concord Crystal City,
a 413-unit, 18-story luxury apartment community.
PROJECT STRUCTURE:
maintenance-triage-board/
package.json
src/
index.js (CLI entry point)
csv-parser.js (CSV ingestion and validation)
scorer.js (priority scoring engine)
dashboard.js (HTML dashboard generator)
templates/
board.hbs (Handlebars template for the HTML dashboard)
sample-data/
work-orders.csv (realistic sample data, 25 work orders)
output/ (generated files go here)
REQUIREMENTS:
1. CLI INTERFACE (src/index.js)
- Usage: node src/index.js <csv-file> [options]
- Options:
--top <n> Show top N priority items (default: 10)
--techs <names> Comma-separated tech names for assignment
(default: "Marco,Daniela")
--date <date> Override today's date for SLA calculations
(default: today, format YYYY-MM-DD)
--html Generate HTML dashboard (default: true)
--no-html Skip HTML generation, terminal output only
--print Open the HTML file in the default browser after
generating
- Terminal output: color-coded table with columns:
Priority | WO# | Unit | Floor | Category | Summary | Score |
SLA Status | Assigned To
- Colors: red for emergency, orange for urgent, yellow for routine,
green for make-ready/low
2. CSV PARSER (src/csv-parser.js)
- Expected CSV columns (matching common PMS exports):
work_order_id, created_date, unit_number, floor, resident_name,
category, description, status, priority_pms (the PMS-assigned
priority, which we'll re-score), assigned_to, notes
- Handle common CSV issues: quoted fields with commas, empty fields,
date format variations (MM/DD/YYYY, YYYY-MM-DD, M/D/YY)
- Validate required fields (work_order_id, created_date, unit_number,
description). Log warnings for incomplete rows but don't skip them.
- Filter to only "open" and "in-progress" status (skip "completed",
"cancelled")
3. PRIORITY SCORING ENGINE (src/scorer.js)
Score each work order on a 0-100 scale using these transparent rules:
a. KEYWORD EMERGENCY DETECTION (0-40 points)
- Immediate life-safety keywords (40 pts): "fire", "fire alarm",
"smoke", "gas leak", "gas smell", "carbon monoxide", "flood",
"flooding", "ceiling collapse", "electrical fire"
- Critical comfort keywords (30 pts): "no hot water", "no heat",
"no AC", "no air conditioning", "no electricity", "power out",
"elevator stuck", "elevator down", "sewage", "backed up"
- Urgent keywords (20 pts): "leak", "leaking", "water damage",
"mold", "pest", "roach", "mice", "broken lock", "door won't
lock", "window broken", "toilet won't flush"
- Standard keywords (10 pts): "dishwasher", "disposal", "squeaky",
"cosmetic", "paint", "caulk", "screen", "lightbulb"
- Match against description + notes fields, case-insensitive
b. AGE-BASED URGENCY (0-25 points)
- Calculate days since created_date
- 0-1 days: 5 pts
- 2-3 days: 10 pts
- 4-7 days: 15 pts
- 8-14 days: 20 pts
- 15+ days: 25 pts (approaching or past SLA breach)
c. SLA BREACH PROXIMITY (0-20 points)
- Calculate hours/days until SLA deadline based on category:
Emergency = 24 hrs, Urgent = 48 hrs, Routine = 5 business days
- Already breached: 20 pts
- Within 25% of deadline: 15 pts
- Within 50% of deadline: 10 pts
- Within 75% of deadline: 5 pts
- Plenty of time: 0 pts
d. REPEAT/ESCALATION BONUS (0-15 points)
- If the description or notes contain "again", "still", "second
time", "recurring", "called again", "not fixed": +15 pts
- If resident name appears in more than one open work order: +10 pts
(multiple issues = frustrated resident)
- Final score = sum of all components, capped at 100
- Tier assignment: 80-100 = EMERGENCY (red), 60-79 = URGENT (orange),
30-59 = ROUTINE (yellow), 0-29 = LOW (green)
- Export the scoring breakdown for each work order (not just the final
score) so the dashboard can show WHY each order is ranked where it is
4. TECH ASSIGNMENT SUGGESTIONS
- Round-robin assignment across the provided tech names
- Emergency items: assign to the first available tech (don't round-robin)
- Category-based hints: if the category is "HVAC" or "Elevator",
add a note "Consider vendor dispatch" instead of assigning a tech
- Output the suggested assignment but make it clear these are
suggestions, not dispatched orders
5. HTML DASHBOARD (src/dashboard.js + templates/board.hbs)
Generate output/triage-board.html with:
a. HEADER
- Title: "Morning Triage Board -- The Concord Crystal City"
- Date: today's date (or --date override)
- Summary stats: Total open WOs, Emergency count, Urgent count,
SLA breaches, Oldest open WO age
b. PRIORITY QUEUE TABLE
- All work orders sorted by score (highest first)
- Columns: Rank, WO#, Unit, Floor, Category, Description (first
60 chars), Score, Score Breakdown (tooltip or expandable), Tier,
SLA Status, Suggested Tech
- Row background color by tier: red #fecaca, orange #fed7aa,
yellow #fef08a, green #bbf7d0
- SLA breached rows get a red left border and bold "BREACHED" tag
c. CHARTS (Chart.js from CDN)
- Urgency breakdown: doughnut chart showing count by tier
- Category distribution: horizontal bar chart showing count by
category (Plumbing, HVAC, Electrical, Appliance, General,
Exterior, Make-Ready)
- Floor heatmap: vertical bar chart, one bar per floor (1-18),
bar height = number of open WOs on that floor, color-coded by
highest-severity WO on that floor
d. STANDUP SUMMARY (print-friendly section)
- "TOP 10 PRIORITIES" list with WO#, unit, one-line summary, tech
- "SLA ALERTS" list of any breached or near-breach items
- "PARTS/VENDOR NEEDED" list if any notes mention "parts",
"order", "vendor", "backordered", "scheduled"
- "YESTERDAY'S COMPLETIONS" placeholder (to be filled manually
or from a separate CSV)
- Formatted for printing on a single sheet of paper (A4/Letter)
e. DESIGN
- Dark theme for screen: background #09090b, cards #141414,
borders #262626, text #e5e5e5
- Print stylesheet: white background, black text, no dark theme,
Chart.js charts hidden (replaced by text summaries), single
page layout
- Font: system sans-serif stack
- Responsive: works on laptop screens and tablets
6. SAMPLE DATA (sample-data/work-orders.csv)
Include 25 realistic work orders with a mix of:
- 2 emergency items (12th floor leak affecting multiple units, fire
alarm panel fault on L3)
- 4 urgent items (no hot water in 14B, elevator intermittent on
east bank, backed-up drain in 6A, broken window lock in 9D)
- 12 routine items (dishwasher issues, garbage disposal, paint touch-
up, caulking, squeaky doors, HVAC filter changes, lightbulb
replacements)
- 4 make-ready items (units 4C, 7B, 11A, 15E -- turns happening
this month)
- 3 items with SLA breach potential (created 5+ days ago, still open)
- 2 items with "recurring" or "again" in notes
- Spread across floors 1-18 with realistic unit numbers
- Mix of categories: Plumbing, HVAC, Electrical, Appliance, General,
Exterior, Make-Ready
- Dates ranging from today back to 16 days ago
DEPENDENCIES: commander, csv-parse (or csv-parser), handlebars, chalk,
open (to open HTML in browser with --print flag)
💡Copy-paste ready

That entire block is the prompt. Paste it as-is. The scoring rules are spelled out explicitly because you need to be able to explain to your regional manager exactly why work order #4521 is ranked above #4518. Transparent, auditable logic beats a black box every time.


What you get

After the LLM finishes (typically 60-120 seconds), you will have the project:

maintenance-triage-board/
package.json
src/
index.js
csv-parser.js
scorer.js
dashboard.js
templates/
board.hbs
sample-data/
work-orders.csv

Set it up and run

Terminal window
cd maintenance-triage-board
npm install
node src/index.js sample-data/work-orders.csv

You should see terminal output like:

Morning Triage Board -- The Concord Crystal City
Date: 2026-03-06 | Open WOs: 22 | Emergency: 2 | Urgent: 4 | SLA Breaches: 3
# | WO# | Unit | Fl | Category | Summary | Score | Tier | SLA | Tech
1 | WO-4501| 12F | 12 | Plumbing | Active leak from ceiling, affe...| 92 | EMERGENCY | BREACHED | Marco
2 | WO-4503| L3 | 3 | Electrical | Fire alarm panel showing fault...| 88 | EMERGENCY | -22h left | Marco
3 | WO-4507| 14B | 14 | Plumbing | No hot water since yesterday m...| 71 | URGENT | -18h left | Daniela
4 | WO-4512| East | - | Elevator | Elevator intermittent, traps o...| 68 | URGENT | Vendor | *Vendor*
5 | WO-4509| 6A | 6 | Plumbing | Kitchen drain backed up, water...| 63 | URGENT | -36h left | Marco
...
Dashboard generated: output/triage-board.html

Open output/triage-board.html in your browser. You should see:

  1. Header with today’s date and summary statistics.
  2. Priority queue table with all 22 open work orders, color-coded by tier.
  3. Doughnut chart showing 2 emergency, 4 urgent, 12 routine, 4 make-ready.
  4. Category bar chart showing plumbing as the largest category.
  5. Floor chart with floor 12 highlighted red (the active leak).
  6. Standup summary section at the bottom, ready to print.

If something is off

ProblemFollow-up prompt
CSV parsing fails on date formatsThe date parser is failing on MM/DD/YYYY dates. Use a flexible date parsing approach: try YYYY-MM-DD first, then MM/DD/YYYY, then M/D/YY. Use new Date() with manual parsing, not Date.parse() which is inconsistent across environments.
Chart.js charts are blankThe Chart.js charts aren't rendering. Make sure Chart.js is loaded from CDN before the chart code runs. Add the script tag in the <head> with defer, and wrap chart creation in a DOMContentLoaded event listener.
Score breakdown is not visibleI can see the final score but not how it was calculated. Add a tooltip or expandable row in the HTML table that shows the scoring breakdown: keyword points, age points, SLA points, and escalation bonus. This is critical for transparency.
Print layout is brokenWhen I print the HTML dashboard, the dark background prints and the charts overlap the table. Add a @media print stylesheet that sets white background, hides Chart.js canvases, and uses the text-based standup summary instead. Make sure the priority table fits on one page.

🔧

When Things Go Wrong

Use the Symptom → Evidence → Request pattern: describe what you see, paste the error, then ask for a fix.

Symptom
The scorer gives every work order the same low score
Evidence
All 25 work orders have scores between 10-15, even the one about a fire alarm panel fault. The tier column shows 'LOW' for everything.
What to ask the AI
"The keyword matching in scorer.js is not working. Check that the keyword detection is case-insensitive (use .toLowerCase() on the description before matching). Also verify that the keywords are being matched as substrings, not exact matches -- 'leaking faucet' should match the keyword 'leak'. Use description.toLowerCase().includes(keyword) for each keyword check."
Symptom
SLA calculations show negative hours for everything
Evidence
Every work order shows 'BREACHED' in the SLA column, even ones created today. The SLA hours remaining are all large negative numbers.
What to ask the AI
"The SLA calculation is subtracting dates in the wrong direction or using the wrong units. Make sure you're calculating: sla_deadline = created_date + sla_hours. Then: remaining = sla_deadline - now. If remaining < 0, it's breached. Use Date objects and divide millisecond differences by (1000 * 60 * 60) to get hours. Check that created_date is being parsed correctly from the CSV."
Symptom
The floor chart shows all work orders on floor 0
Evidence
The floor heatmap bar chart has one giant bar at position 0 and all other floors are empty. The CSV has floor numbers 1-18.
What to ask the AI
"The floor number is being read as a string from the CSV and not converted to a number. The chart grouping is failing because '12' as a string doesn't match 12 as a number. Add parseInt(row.floor, 10) when parsing the CSV. Also handle non-numeric floor values (like 'L3' for lobby level 3 or 'B1' for basement) by mapping them to numeric positions."
Symptom
Tech assignment gives every work order to the same person
Evidence
All 22 work orders are assigned to Marco. Daniela has zero assignments. The round-robin is not working.
What to ask the AI
"The round-robin counter is being reset for each work order instead of persisting across the loop. Move the counter variable outside the scoring/assignment loop. Initialize it once (let techIndex = 0) before iterating through the sorted work orders, and increment it (techIndex = (techIndex + 1) % techs.length) after each non-emergency, non-vendor assignment."
Symptom
The HTML file is generated but opening it shows a blank page
Evidence
output/triage-board.html exists (42 KB) but opening it in Chrome shows a white page. View Source shows HTML content is there.
What to ask the AI
"The HTML is being generated but the JavaScript or CSS is malformed, causing the browser to fail silently. Check the Handlebars template for unclosed tags. Add a minimal inline style in the <head> so the page has visible content even if external CSS fails. Also check that Chart.js data is being serialized with JSON.stringify() and not containing undefined values that break the script."

How it works (the 2-minute explanation)

You do not need to understand every line of the generated code, but here is the mental model:

  1. CSV parsing reads each row into a JavaScript object. The parser handles date format variations and quoted fields. It filters out completed and cancelled work orders so you only see what needs attention.
  2. Priority scoring is a points-based system with four components: keyword detection (what is the problem?), age (how long has it been open?), SLA proximity (are we about to breach?), and escalation signals (is this a repeat issue?). Each component has a maximum, and the total is capped at 100. The breakdown is stored with each work order so you can explain any ranking.
  3. Tech assignment uses round-robin for routine items and first-available for emergencies. Specialized categories (HVAC, elevator) get flagged for vendor dispatch instead. These are suggestions, not dispatched orders — your techs know the building better than any algorithm.
  4. Chart.js renders three charts: a doughnut for urgency tiers, a horizontal bar for categories, and a vertical bar for floors. These give you a visual overview that is faster to read than a table.
  5. The print stylesheet strips the dark theme and charts for paper output, leaving a clean priority list and standup summary that fits on one page.
🔍Why transparent scoring matters

A black-box algorithm that says “this work order is priority 1” is useless if your regional manager asks why. Transparent scoring — where you can say “it scored 71 because: 30 points for ‘no hot water’ keyword, 15 points for being 4 days old, 16 points for approaching SLA breach, and 10 points because the resident has two open work orders” — is defensible. It is auditable. And when the scoring is wrong (and it will be sometimes), you can adjust the rules and re-run. You cannot do that with a gut feeling written on a whiteboard.


Customize it

The base tool handles daily triage, but here are follow-up prompts for specific operational needs:

Add vendor dispatch tracking

Add a --vendors flag that accepts a JSON file mapping categories to vendor
contacts. Example: {"HVAC": {"name": "AirTech Services", "phone":
"703-555-0188", "contract": "monthly"}, "Elevator": {"name": "ThyssenKrupp",
"phone": "800-555-0199", "contract": "on-call"}}. When a work order is
flagged for vendor dispatch, include the vendor name and phone in the
dashboard. Add a "Vendor Dispatch" section to the standup summary listing
all vendor-required items with contact info.

Add week-over-week trend tracking

Add a --history flag that appends today's triage summary to a
triage-history.json file. Track: date, total open WOs, emergency count,
average score, SLA breach count, oldest open WO age. After 7+ days of
data, add a "Trends" section to the dashboard showing a line chart of
open WO count over time and a bar chart of SLA breaches per day. This
helps identify whether the backlog is growing or shrinking.

Add resident impact scoring

Add a "resident impact" component to the scoring engine. If a single unit
has multiple open work orders, boost all of that unit's WOs by 10 points.
If multiple units on the same floor report the same category (e.g., three
plumbing WOs on floor 12), flag it as a potential building-wide issue and
add a "BUILDING ISSUE" tag. Add a "Resident Impact" section to the
dashboard showing which residents have the most open issues and which
floors have clustered problems.

Add make-ready tracking

Add a separate "Make-Ready Board" section to the dashboard. Filter work
orders with category "Make-Ready" and group them by unit. For each unit,
show: unit number, move-in date (add a move_in_date column to the CSV),
days until move-in, completion percentage (completed make-ready WOs /
total make-ready WOs for that unit), and a progress bar. Color-code by
urgency: red if move-in is within 3 days and not 100% complete, yellow
if within 7 days, green otherwise.
💡The customization loop

Start with the base triage board. Use it for a week. You will immediately notice what is missing for your specific property — maybe you need vendor tracking, maybe you need make-ready progress bars, maybe you need a different SLA schedule. Each missing feature is one follow-up prompt. The tool grows to fit your operation, not the other way around.


Try it yourself

  1. Open your CLI tool in an empty folder.
  2. Paste the main prompt from above.
  3. Run npm install and test with the sample data.
  4. Open the HTML dashboard and review the priority queue. Does the scoring make sense?
  5. Try the print view (Ctrl+P or Cmd+P in the browser). Does the standup summary fit on one page?
  6. If you have access to a real work order export from your PMS, run it through the tool. Adjust column names in the CSV parser if your PMS uses different headers.
  7. Pick one customization from the list above and add it.

Key takeaways

  • CSV is the universal export format — every PMS supports it. Building on CSV means your tool works with Yardi, RealPage, Entrata, AppFolio, or any system, today, without IT involvement.
  • Transparent scoring beats gut-feel triage — when every point is traceable to a rule (keyword match, age, SLA proximity, repeat flag), you can explain and defend your priorities to anyone. Adjust the rules when they are wrong.
  • SLA awareness prevents breaches before they happen — the breach proximity score pushes aging work orders up the queue before they actually breach, not after. Prevention is better than a breach report from your regional.
  • Print-friendly output bridges the digital-analog gap — your maintenance techs may not have tablets. A printed standup sheet taped to the office wall is the interface that actually gets used every morning.
  • The tool gets better with customization — vendor dispatch, trend tracking, and make-ready boards are all one prompt away. Start with the base, iterate to fit your property.

KNOWLEDGE CHECK

A work order for 'kitchen faucet leaking slowly' was created 6 days ago and is still open. The property's SLA for routine plumbing is 5 business days. The scoring engine gives it 62 points: 20 (keyword 'leak') + 15 (6 days old) + 20 (SLA breached) + 7 (no escalation signals). What tier does this work order land in?


What’s next

In the next lesson, you will build a tool that goes beyond daily operations into financial visibility: a Rent Roll Analyzer that parses your monthly rent roll export, calculates occupancy metrics, flags lease expirations, and produces a portfolio health dashboard. Same pattern — showcase, prompt, output, customize — but with financial data and KPI calculations instead of work order scoring.