Property Maintenance

FixFlow

Faster Fixes, Happier Tenants

FixFlow is a lightweight web dashboard that centralizes maintenance requests, auto-schedules vendors with a shared calendar and SMS confirmations, and streamlines tenant communication for independent landlords and small property managers managing 1–200 units—cutting response times, preventing double-bookings, and eliminating chaotic email-and-phone workflows.

Subscribe to get amazing product ideas like this one delivered daily to your inbox!

FixFlow

Product Details

Explore this AI-generated product idea in detail. Each aspect has been thoughtfully created to inspire your next venture.

Vision & Mission

Vision
Empower independent landlords and small managers to resolve maintenance faster, transparently, and reliably, preserving property value and tenant satisfaction.
Long Term Goal
Within 3 years, onboard 10,000 independent landlords managing 50,000 units, resolve 500,000 maintenance tickets annually, and cut average resolution time platform-wide by 40%
Impact
Reduces maintenance response time by 50% for independent landlords and small property managers, cuts vendor scheduling conflicts by 80% and no-shows by 60%, saving managers 3–6 hours weekly and improving timely issue resolution and tenant retention.

Problem & Solution

Problem Statement
Independent landlords and small property managers (1–200 units) face scattered maintenance requests, vendor double-bookings, and tenant no-shows due to reliance on spreadsheets, email, and phone; existing tools are costly, complex, and lack automated vendor scheduling and SMS confirmations.
Solution Overview
FixFlow centralizes tenant requests in a lightweight web dashboard and auto-schedules vendors via a shared calendar with automated SMS confirmations, eliminating double-bookings and no-shows so landlords resolve maintenance faster and ditch chaotic email-and-phone coordination.

Details & Audience

Description
FixFlow centralizes maintenance requests, vendor scheduling, and tenant communication in a lightweight web dashboard. It serves independent landlords and small property managers overseeing 1–200 units. FixFlow cuts response times, prevents scheduling conflicts, and replaces chaotic email-and-phone workflows so issues resolve faster and tenants stay satisfied. A shared automated vendor calendar with SMS confirmations eliminates double-bookings and no-shows.
Target Audience
Independent rental landlords and small property managers (30–60) needing faster maintenance, who rely on spreadsheets.
Inspiration
At 2 a.m., a tenant texted frantic photos of a burst pipe and I spent hours juggling calls, emails, and a vendor who’d already been booked elsewhere; spreadsheets and voicemails offered no help. That chaotic, costly night—water damage, a furious tenant, my exhausted midnight coordination—sparked the idea: a simple shared scheduler with automated SMS confirmations so vendors and tenants always know exactly when someone’s coming.

User Personas

Detailed profiles of the target users who would benefit most from this product.

R

Remote Owner Riley

- Age 34–48; lives 500+ miles from properties - Owns 6–20 single-family rentals in two metros - Former tech professional; BS Business; comfortable with apps - Mid–six-figure household income; budgets for contractors - Mobile-first; travels frequently for work

Background

Bought first rental while relocating, then scaled remotely via local vendors. A double-booked plumber and missed access window cost two days’ rent, forcing a search for ironclad scheduling and proof-of-work. Now insists on confirmations and photo receipts.

Needs & Pain Points

Needs

1) Instant SMS confirmations with time-stamped updates. 2) Remote access coordination and lockbox instructions. 3) Vendor performance history and on-time metrics.

Pain Points

1) Missed appointments when not onsite. 2) Conflicting schedules across time zones. 3) Unverified completion without photo proof.

Psychographics

- Craves control without constant micromanaging - Trusts timestamps and photos over promises - Hates phone-tag and vague updates - Prioritizes vendor reliability over lowest bid

Channels

1) SMS — primary alerts 2) Gmail — work hub 3) Facebook Groups — landlord forums 4) YouTube — how-to maintenance 5) LinkedIn — vendor sourcing

T

Turnover Tactician Tessa

- Age 26–40; property management coordinator at 80–150 units - Oversees 8–20 turnovers monthly across two neighborhoods - NAA/IREM coursework; checklist-driven; desktop by day, phone onsite - Works 8–6 weekdays; peak crunch at month-ends

Background

Started as a leasing agent chasing keys and contractors. A 10-day delay from a double-booked cleaner pushed her to standardize scheduling. Now tracks every make-ready step against target timelines.

Needs & Pain Points

Needs

1) Batch scheduling for cleaners, painters, locksmiths. 2) Real-time vendor availability across calendars. 3) Automatic move-in/out reminders to tenants.

Pain Points

1) Double-booked vendors derail make-ready timelines. 2) Keys or access codes not ready. 3) Vendors miss emailed schedules.

Psychographics

- Obsessed with zero lost rent days - Color-codes tasks to tame chaos - Prefers clear SLAs and deadlines - Thrives on tight, predictable routines

Channels

1) Google Calendar — daily planning 2) Gmail — scheduling threads 3) SMS — last-minute changes 4) LinkedIn — local vendor sourcing 5) Facebook Groups — property management

C

Compliance Keeper Casey

- Age 35–55; compliance/risk manager at nonprofit or PM - Oversees 120–180 regulated units across two sites - Fair housing certified; detail-heavy; Excel power user - Office desktop primary; monthly report deadlines

Background

After a citation for late responses risked funding, overhauled maintenance tracking. Needs defensible logs of every tenant request and vendor step to breeze through audits.

Needs & Pain Points

Needs

1) Tamper-proof audit trail with timestamps. 2) SLA breach alerts and escalations. 3) One-click agency-ready reports.

Pain Points

1) Missing timestamps during inspections. 2) Unlogged phone or lobby requests. 3) Proof of vendor insurance and compliance.

Psychographics

- Documentation-first, risk-averse, process faithful - Values transparency and immutable timestamps - Motivated by passing audits effortlessly - Prefers policies to improvisation

Channels

1) Google Search — compliance queries 2) LinkedIn — compliance groups 3) Email — policy newsletters 4) Zoom — training webinars 5) IREM Connect — peer forums

D

Data-Driven Operator Dana

- Age 30–45; owns/operates 20–80 units in two submarkets - MBA Finance; former analyst; loves dashboards - Uses MacBook and iPhone; early adopter mindset - Budgets quarterly; seeks ROI within 90 days

Background

Bought value-add buildings and watched maintenance costs swallow gains. Built rough spreadsheets, but inconsistent categories broke analysis. Now wants clean, structured data from the source.

Needs & Pain Points

Needs

1) Accurate maintenance cost and time analytics. 2) Vendor scorecards with on-time rates. 3) Easy exports to Sheets/BI tools.

Pain Points

1) Inconsistent work-order categorization. 2) Hidden costs from reschedules and returns. 3) No technician utilization visibility.

Psychographics

- Numbers-first; decisions demand trendlines - Seeks continuous process optimization - Impatient with vague or late updates - Values measurable ROI over features

Channels

1) Google Search — KPI tooling 2) LinkedIn — investor operators 3) YouTube — analytics tutorials 4) X/Twitter — RE finance threads 5) Podcasts — BiggerPockets Business

S

Short-Stay Steward Skylar

- Age 25–40; manages 5–30 STRs across one metro - Platforms: Airbnb/VRBO; iPhone-first, on the move - Weekend-heavy workload; rapid response expectations - Income seasonal; contractor-heavy operations

Background

Started as a superhost, scaled via co-hosting. Group texts with cleaners failed during peak season, causing refunds. Now standardizes scheduling and proof-of-clean.

Needs & Pain Points

Needs

1) Auto-schedule cleaners from booking calendar. 2) SMS door codes and arrival windows. 3) Photo proof after each turnover.

Pain Points

1) Last-minute cleaner cancellations. 2) Missed turnovers trigger guest refunds. 3) Unclear damage documentation.

Psychographics

- Guest-experience obsessed, review-driven - Automation-minded, mobile-only workflows - Comfortable with rapid-fire decisions - Values photo proof over promises

Channels

1) Facebook Groups — STR hosts 2) Airbnb Community — host forums 3) Instagram — vendor DMs 4) YouTube — hosting tips 5) Google Search — turnover automation

H

HOA Liaison Liam

- Age 35–60; condo/HOA board liaison or part-time manager - 50–150 units in one community - Evenings/weekends availability; moderate tech comfort - Works from personal laptop and phone

Background

Inherited a chaotic spreadsheet-and-email system after board turnover. Duplicate bookings and miscommunication sparked resident complaints. Seeks one source of truth.

Needs & Pain Points

Needs

1) Central intake for owner/resident requests. 2) Shared vendor calendar for common areas. 3) Broadcast SMS for outages and notices.

Pain Points

1) Confusion over approvals and responsibilities. 2) Duplicate bookings by multiple board members. 3) Residents left without timely updates.

Psychographics

- Consensus-driven, transparency-focused communicator - Avoids risk; prefers clear approval trails - Low-effort, low-training tool preference - Values resident satisfaction metrics

Channels

1) Nextdoor — community groups 2) Facebook Groups — HOA communities 3) Email — board communications 4) Google Search — HOA software 5) LinkedIn — vendor sourcing

Product Features

Key capabilities that make this product valuable to its target users.

Vision AutoFill

AI analyzes uploaded photos or short videos to identify the fixture, detect likely faults (e.g., leaking supply line vs. condensation), and assess hazards (pooling water, scorch marks). It auto-fills category, severity, and preferred vendor while extracting visible model/serial numbers—so tenants type less and managers get cleaner, routable tickets that drive first-visit fixes.

Requirements

Multi-Modal Media Ingestion
"As a tenant, I want to quickly upload clear photos or a short video of the issue so that the system can analyze it and I don’t have to type a long description."
Description

Enable tenants to upload photos and short video clips directly from web or mobile (camera capture and drag-and-drop), with support for JPEG/PNG/HEIC images and MP4/H.264 video up to defined limits. Provide inline guidance for capturing clear evidence (e.g., include full fixture and a close-up), automatic orientation correction, client-side compression, and multiple-file support (max 6 assets, 200 MB total, 1080p video cap, 10–20s each). Implement resumable, chunked uploads with progress, retry, and offline queueing; server-side antivirus scanning; EXIF sanitation (strip GPS); and storage in object storage with signed URL access. Associate media with a draft maintenance ticket and capture minimal metadata (timestamp, device type). Ensure accessibility, low-bandwidth fallbacks, and graceful error handling.

Acceptance Criteria
Upload Mixed Media with Format and Limit Enforcement
Given a tenant opens the upload UI on web or mobile When they add images in JPEG, PNG, or HEIC formats or videos in MP4 (H.264) Then the system accepts the files and displays them in the queue with thumbnails and duration for videos Given a tenant attempts to add an unsupported format When the file is dropped or selected Then the system rejects it and displays a message naming the allowed formats Given a tenant attempts to add more than 6 assets or the cumulative size exceeds 200 MB When the selection is made Then the system prevents adding the excess and shows a message indicating the current count/size and the limits Given a tenant adds a video longer than 20 seconds or with resolution above 1080p When validation runs client-side Then the system blocks the upload and displays guidance to record 10–20s at 1080p or lower
User Guidance, Accessibility, and Low-Bandwidth Fallbacks
Given the upload UI is opened with no assets added When the component renders Then inline guidance is visible describing how to capture a full fixture shot and a close-up, and remains accessible via a Help link thereafter Given a user navigates via keyboard only When interacting with all upload actions (open file picker, drag-and-drop target, camera capture, remove asset) Then each control is reachable in a logical tab order and operable via Enter/Space Given a screen reader is active When the upload UI is used Then all controls have meaningful accessible names/roles and progress updates are announced via an ARIA live region without interrupting input Given the client detects a constrained network (Save-Data header present or a built-in bandwidth check indicates slow connection) When the user begins an upload Then the UI offers a low-bandwidth mode that defers video capture and reduces preview quality, and informs the user that uploads will queue for background transfer Given an error occurs (unsupported format, limit exceeded, network failure) When it is detected Then a human-readable message is shown inline next to the affected file with a clear action (remove, retry, or learn more)
Automatic Orientation Correction and Client-Side Compression
Given an image with EXIF orientation is uploaded When the preview renders and the file is stored Then the image is automatically rotated to correct orientation in both preview and stored object Given a video above 1080p is selected When client-side processing runs Then the client offers to downscale to 1080p and H.264 before uploading; if the user proceeds, the uploaded video is 1080p Given the total selected media would exceed 200 MB When client-side compression completes Then the total size is reduced to 200 MB or less or the user is prompted to remove items before upload proceeds
Resumable Chunked Uploads with Progress, Retry, and Offline Queue
Given uploads are in progress and the network drops When connectivity is restored Then each file resumes from the last confirmed chunk without re-uploading completed chunks Given a chunk upload fails transiently When retry policy applies Then the client retries the chunk automatically up to 3 times with exponential backoff before surfacing a retry button to the user Given files are queued while offline When the app regains connectivity or the app is relaunched Then queued uploads begin automatically and progress is displayed per file and in total Given uploads are in progress When the user views the queue Then each file shows an individual progress indicator with percentage and an overall aggregated progress indicator
Security and Privacy: Antivirus Scan and EXIF GPS Sanitation
Given a file upload completes to the server When server-side processing runs Then an antivirus scan is performed and only files that pass are marked as clean; infected files are quarantined and the user is notified to remove and re-upload Given an image contains EXIF GPS or other location metadata When sanitation runs Then GPS coordinates and other location-identifying EXIF fields are stripped before storage and are not present in any subsequently served file Given a file has not yet passed antivirus and sanitation When a request for access is made Then the system does not issue a signed URL and the file is not retrievable
Storage and Access via Time-Limited Signed URLs
Given a file passes scanning and sanitation When it is persisted Then it is stored in object storage under a tenant-safe namespace and is not publicly readable Given an authorized viewer requests to see the media When the backend generates access Then a time-limited signed URL is issued that expires by default within 15 minutes and is scoped to read-only access for that object Given an expired signed URL is used When the request is made Then access is denied with a 403 and no object data is returned
Draft Ticket Association and Minimal Metadata Capture
Given a tenant adds the first asset When the upload begins Then a draft maintenance ticket is created (if one does not exist) and all subsequently added media are associated with that draft Given media are associated with a draft When the user returns to the draft on the same account and device Then previously uploaded media are displayed in their original order Given minimal metadata requirements When each asset is stored Then the system records upload timestamp (UTC) and device type (web or mobile, with available OS) and links them to the draft ticket Given a draft is deleted by the tenant When media were associated Then the media are no longer accessible via signed URLs and are flagged for backend cleanup
Vision Inference Service
"As a property manager, I want the system to automatically identify the fixture and probable fault from photos or video so that tickets are accurate and actionable without manual triage."
Description

Provide a scalable inference service that analyzes uploaded media to identify fixture type (e.g., sink, toilet, HVAC condenser, breaker panel), detect likely faults (e.g., leaking supply line vs. condensation, tripped breaker, burned element), and assess visible hazards (e.g., pooling water, scorch marks, exposed wiring). Output structured JSON including labels, bounding boxes, per-label confidence scores, hazard flags, and a severity hint. Support batching, asynchronous processing via a job queue, idempotency by media hash, model versioning, and feature flags. Target P95 latency of ≤6 seconds per image and ≤12 seconds per short video on typical loads. Emit domain events for downstream autofill and logging, and integrate with FixFlow’s back end via authenticated APIs.

Acceptance Criteria
Single Image Fixture Classification within Latency SLO
Given an authenticated POST to /inference/images with a single JPEG or PNG ≤8MB containing a clear fixture When the request is accepted Then the API responds 202 Accepted with job_id and media_hash Given the job_id When the client polls GET /inference/jobs/{job_id} until status=completed Then the result JSON contains fixtures[0].label as a supported fixture type, fixtures[0].confidence ≥ 0.60, and fixtures[0].bbox with normalized coordinates in [0,1] Given ≥500 valid image requests under typical load When measuring from 202 response to result availability Then the P95 latency is ≤ 6 seconds
Short Video Fault Detection within Latency SLO
Given an authenticated POST to /inference/videos with an MP4 (H.264) ≤20MB and ≤15s duration showing a relevant fixture with a likely fault When the request is accepted Then the API responds 202 Accepted with job_id and media_hash Given the completed job When retrieving the result Then the JSON includes a top fixture label, detected_faults[0].label with confidence ≥ 0.60 when a fault is present, and at least one bounding box for the relevant object(s) Given ≥200 valid short videos under typical load When measuring from 202 response to result availability Then the P95 latency is ≤ 12 seconds
Hazard Assessment and Severity Hint Accuracy
Given a held-out labeled validation set of ≥1000 media items with ground-truth hazards and severity levels When the deployed model_version is evaluated end-to-end Then hazard flag precision ≥ 0.90 and recall ≥ 0.80 macro-averaged across hazard types Given the same evaluation When computing severity_hint against three-level ground truth (low, medium, high) Then macro F1 ≥ 0.80 and confidence calibration ECE ≤ 0.10
Structured JSON Output Schema Compliance
Given any completed job result When validating the payload against the published schema v1 Then it contains top-level fields: job_id, media_hash, model_version, created_at, feature_flags_applied, and result Given the result section When validating detections Then fixtures, faults, and hazards arrays contain items with label, confidence in [0,1], and bbox as {x,y,w,h} normalized to [0,1] Given feature flags disable certain detectors When a detector is disabled Then the corresponding array is present and empty and feature_flags_applied lists the disabled flag Given strict output control When producing the result Then no undocumented fields are included in the JSON
Asynchronous Processing and Batching via Job Queue
Given an authenticated batch POST to /inference/images with N images where 1 ≤ N ≤ 20 When the request is accepted Then the API responds 202 with N distinct job_ids and media_hashes Given the batch jobs When polling each job Then each job independently reports status in {queued, processing, completed, failed} and partial failures do not block other jobs Given a worker restart during processing When the system recovers Then previously accepted jobs persist and eventually reach a terminal status without loss or duplication
Idempotency by Media Hash and Safe Replays
Given two submissions whose byte-level media_hash is identical within 24 hours When the second request is received Then the API returns 200 with the original job_id and does not enqueue a duplicate job Given concurrent identical submissions When both requests arrive within 1 second Then only one inference is executed and both responses reference the same job_id Given a completed job for a media_hash When the same media is resubmitted without an explicit bypass flag Then the cached result is returned with identical content and model_version
Domain Events Emission and Authenticated Integration
Given a job reaches completed or failed When the result is finalized Then an InferenceCompleted or InferenceFailed domain event is published within 500 ms with fields {job_id, media_hash, tenant_id, model_version, summary, severity_hint, correlation_id} Given a subscriber with valid credentials When it consumes the event Then the event schema validates and can be correlated to the original request via correlation_id and job_id Given an unauthenticated API call to any inference endpoint When no Bearer token is provided Then the API returns 401 Unauthorized Given an authenticated API call with insufficient scope When accessing a restricted endpoint Then the API returns 403 Forbidden
OCR Label Extraction
"As a technician, I want model and serial numbers auto-extracted so that I can bring the right parts on the first visit."
Description

Detect and extract textual labels from media, focusing on manufacturer nameplates, model numbers, serial numbers, and part identifiers. Use robust text detection and recognition tolerant to skew, glare, low light, and curved surfaces. Normalize and validate outputs against known manufacturer patterns and vendor catalogs; standardize casing and delimiters; and flag low-confidence characters. Provide cropped label thumbnails for verification and store extracted fields on the ticket. Mask any incidental PII and prompt tenants for a close-up retake when confidence falls below a configurable threshold.

Acceptance Criteria
High-Confidence Extraction from Clear Nameplate
Given a single 1080p+ photo containing a sharp, unobstructed manufacturer nameplate When OCR runs Then the system extracts manufacturer, model, and serial as separate fields And each extracted field has confidence >= 0.95 And fields are written to the ticket with createdBy=OCR and include per-field confidence and source bounding box coordinates And the extracted text exactly matches the label contents after normalization rules are applied And a label thumbnail is generated and attached to the ticket
Robust Extraction in Challenging Conditions
Given a curated test set of at least 300 images containing labels with skew (<= 30°), glare, low light (>= 10 lux), and curved surfaces When OCR runs on the set Then label region detection rate >= 95% And exact-match model number extraction accuracy >= 90% And exact-match serial number extraction accuracy >= 88% And no more than 5% of images in the set trigger the retake prompt due to low confidence
Pattern Validation and Catalog Cross-Check
Given extracted manufacturer, model, serial, and part identifiers When validated against known manufacturer regex patterns and the vendor catalog Then values matching patterns are marked validated with reasonCode=PatternMatch And catalog matches add vendorId and partFamily with matchScore >= 0.80 And conflicting or non-matching values are flagged status=Unvalidated and are not used for auto-routing And all validation outcomes (patternId, matchScore, timestamp) are logged to the ticket audit trail
Normalization and Standardization
Rule: Manufacturer names are mapped to a canonical list (e.g., Whirlpool Corp. -> WHIRLPOOL) Rule: Model and serial values are returned uppercase; internal hyphens and slashes preserved; spaces removed; leading/trailing punctuation trimmed Rule: Delimiters standardized to single hyphen between alphanumeric groups; multiple delimiters collapsed Rule: Both raw and normalized values are stored on the ticket; normalized values are used for downstream matching and display to managers
Cropped Label Thumbnails for Verification
Given one or more detected label regions When generating verification assets Then produce a crop per region at 512–1024 px on longest side, JPEG/WEBP, file size < 300 KB And include a 4 px bounding outline overlay in the crop And attach the crops to the ticket and make them viewable in tenant and manager UIs And clicking a crop highlights the corresponding region on the original image
PII Masking in OCR Outputs and Thumbnails
Given detected PII (faces, phone numbers, emails, street addresses) within the crop or immediate surroundings When preparing thumbnails and OCR text for display Then PII is redacted in displayed thumbnails via box blur radius >= 12 px and in text via token replacement (e.g., (555) 123-4567 -> (###) ###-####) And unredacted originals are not displayed to tenants And PII masking achieves precision >= 0.95 and recall >= 0.90 on the PII QA set And a redaction log (piiType, boundingBoxes, timestamp) is stored with the ticket
Low-Confidence Handling and Retake Workflow
Given any required field (model or serial) has confidence < threshold (configurable, default 0.85) When the tenant attempts to submit Then the UI prompts for a close-up retake with guidance and an example image And per-character low-confidence positions are indicated by bracketing characters (e.g., A[7]B) And after up to 2 retake attempts the tenant may proceed with status=Unverified OCR And the ticket stores the threshold used, attempt count, provisional values, and lowConfidence=true flags for affected fields
Auto-Classification & Vendor Routing
"As a small property manager, I want tickets to auto-fill category, severity, and vendor so that I can dispatch faster and avoid double-bookings."
Description

Translate AI outputs into FixFlow’s taxonomy to auto-fill ticket fields: category (plumbing, electrical, HVAC, appliance), subcategory, severity level (Low/Medium/High/Emergency), and recommended preferred vendor. Combine ML confidence with configurable business rules (per landlord/property, hours of operation, coverage area, capability tags, warranties) to select the vendor and set scheduling constraints. Record the rationale (rules hit, confidences) and pre-populate hazard flags and notes. Support deterministic tie-breakers, override hooks, and a simulation mode to test routing changes before production.

Acceptance Criteria
Map AI Output to FixFlow Taxonomy
Given an AI output with fixture type, suspected fault, and confidence scores When the system processes the request Then it maps to a category ∈ {Plumbing, Electrical, HVAC, Appliance}, a subcategory from the configured taxonomy, and a severity ∈ {Low, Medium, High, Emergency} And if category confidence < landlord-configured threshold, Then category is set to "Unknown" and the ticket is flagged "Needs Review" And if subcategory mapping is ambiguous, Then subcategory is set to "Other" And all auto-filled fields are stored on the ticket with a "source=auto" tag
Preferred Vendor Selection via Rules and Confidence
Given the ticket has category, subcategory, severity, property, and landlord context And vendor records include coverage areas, hours, capability tags, warranty affiliations, and status When selecting a recommended vendor Then only vendors matching coverage area, capability tags for the (category, subcategory), and warranty requirements are eligible And if severity = Emergency, Then eligible vendors must have 24/7 availability; otherwise, after-hours vendors are excluded And if AI confidence for category or subcategory < threshold, Then skip auto-selection and place the ticket in "Manual Routing" queue And if at least one eligible vendor exists, Then exactly one vendor is selected deterministically And scheduling constraints on the ticket are set from the selected vendor’s hours and blackout rules
Deterministic Tie-Breakers
Given multiple eligible vendors remain after filtering When applying tie-breakers Then the system evaluates, in order: landlord-defined vendor priority, warranty preference, shortest SLA response time, fewest open FixFlow jobs, lowest historical no-show rate, lexicographically smallest vendor ID And the first vendor that leads to a unique winner is selected And the tie-breaker factors considered and the winning factor are recorded in the decision rationale
Decision Rationale Capture and Auditability
Given any auto-classification or vendor routing decision is made When the ticket is saved Then a rationale record is written containing: input features summary, ML confidences, thresholds used, rules evaluated with pass/fail, eligible and ineligible vendors with reasons, selected vendor, tie-breaker path, configuration version, and timestamp And the rationale record is immutable; edits create a new version with a reference to the prior version And the rationale is retrievable via API and UI for the ticket within 2 seconds
Manual Override Hooks
Given a manager with permission edits auto-filled category, subcategory, severity, hazard flags, notes, or recommended vendor When the override is submitted Then the system records actor, timestamp, changed fields, and a required override reason in the rationale And auto-routing is disabled for that ticket until the manager re-enables it explicitly And subsequent AI updates do not overwrite overridden fields unless re-enabled
Simulation Mode for Rule Changes
Given a proposed routing ruleset change scoped to a landlord or property And a selected dataset of historical or sample tickets When simulation is executed Then the system computes outcomes using the new rules without sending notifications or altering tickets And it produces a diff report per ticket (before vs. after classification, vendor, severity, scheduling constraints) and aggregate metrics (% changed, tie-breaker usage, newly unroutable count) And the full report is downloadable as CSV and JSON
Hazard Flags and Notes Pre-Population
Given AI outputs include detected hazards and extracted model/serial numbers When the ticket is auto-classified Then hazard flags are populated per configurable mapping (e.g., pooling water -> WaterLeak, scorch marks -> FireRisk) And severity is escalated to Emergency when mapped hazards meet escalation rules And notes include a concise hazard summary and any extracted model/serial numbers
Confidence Thresholds & Human Review UI
"As a manager, I want to review and adjust AI-suggested fields when needed so that accuracy stays high and liability is minimized."
Description

Provide a review interface that surfaces AI-suggested fields with confidence indicators, visual annotations (e.g., bounding boxes over hazards), and clear controls to accept all, accept per-field, or override. Allow account-level and field-level minimum confidence thresholds; require human confirmation when below thresholds or when hazards are detected. Capture lightweight feedback (correct labels, reasons) to improve models and rules. Ensure mobile-responsive design, keyboard shortcuts, and an audit trail of edits linking user, timestamp, and model version.

Acceptance Criteria
Account-Level Confidence Threshold Enforcement
Given an account-level minimum confidence threshold T is configured for AI-suggested fields When Vision AutoFill returns predictions with confidence scores Then any field with confidence < T is marked Requires Review and not auto-accepted And attempting to submit without confirming such fields is blocked with an inline message that lists the fields below threshold And accepting those fields counts as human confirmation and unblocks submission And the threshold value T and decision outcome are stored alongside each prediction
Field-Level Threshold Overrides
Given a field-level threshold F is set for a specific field (e.g., Severity) that differs from the account-level threshold T When predictions are returned with confidences S (Severity) and C (Category) Then Severity is evaluated against F and Category against T And if S < F then Severity requires review even if S >= T And if C >= T then Category is auto-accepted (unless otherwise blocked) And the per-field threshold applied and comparison results are recorded for audit
Mandatory Review When Hazards Detected
Given the model flags any hazard (e.g., pooling water, scorch marks) in the uploaded media When the review screen opens Then a hazard banner is displayed summarizing hazard type(s) and affected region(s) And Accept All requires explicit human confirmation acknowledging the hazard And submission is blocked until at least one human action acknowledges the hazard And a hazard_acknowledged flag with user ID and timestamp is stored with the ticket
Review UI Controls, Keyboard Shortcuts, and Mobile Usability
Given AI-suggested fields are present When the review UI loads on desktop Then controls are available to Accept All, Accept per-field, Override per-field, and Clear a field And keyboard shortcuts are available: Accept All Ctrl/Cmd+Enter, Accept field A, Override field O, Next field Tab, Previous field Shift+Tab And the shortcuts are discoverable via a help tooltip and perform equivalent actions without mouse interaction Given the review UI loads on a mobile viewport width between 375px and 414px When viewed on iOS/Android browsers Then there is no horizontal scrolling, touch targets are at least 44x44 px, and all controls remain reachable without overlap Given no AI predictions exist When the review UI loads Then Accept All is disabled and per-field Accept controls are hidden Given fields remain in Requires Review When the user chooses Accept All Then all remaining predictions are accepted and marked as human-confirmed in a single action
Confidence Indicators and Visual Annotations
Given AI-suggested fields are displayed When the review UI renders Then each field shows a confidence percentage rounded to 1 decimal (0.0–100.0%) and color-coding relative to its threshold (red < threshold, amber within 5% of threshold, green ≥ threshold) Given the model outputs annotation regions (e.g., hazard or object bounding boxes) When the media viewer renders Then labeled boxes are overlaid and can be toggled on/off And selecting a field highlights the corresponding annotation and scrolls/zooms it into view And for videos the frame with the highest-scoring annotation is shown first Given a screen reader is used When navigating the review UI Then annotations and confidence values have descriptive labels announced to the user
Feedback Capture on Overrides and Corrections
Given a user overrides or corrects an AI-suggested field When they submit the change Then they must select a required reason code and may add an optional comment up to 280 characters And the feedback payload includes ticket ID, field name, original value, new value, original confidence, model name and version, reason code, user ID, and timestamp And the payload is queued asynchronously and retried up to 3 times on failure without blocking ticket submission Given a hazard label is overridden When the reason code indicates false positive or false negative Then the feedback also includes the annotation ID associated with the hazard
Immutable Audit Trail with Model Versioning
Given any field is accepted, overridden, or cleared When the action occurs Then an audit record is created capturing user ID, action type, field name, before value, after value, confidence, thresholds applied, model name and version, hazard flags, and timestamp (UTC ISO-8601) And audit records are immutable (append-only) and cannot be edited or deleted Given a ticket has prior actions When a user opens the History tab Then the audit timeline renders within 2 seconds for up to 200 events and supports filtering by field and action type Given a user requests export of the audit trail When they choose JSON or CSV Then the exported file downloads successfully and includes all captured audit fields
Privacy, Security & Audit Logging
"As an admin, I want media and AI outputs to be secure and auditable so that we comply with privacy laws and protect tenant data."
Description

Protect tenant media and AI outputs with encryption in transit and at rest, short-lived signed URLs, and role-based access controls. Automatically redact faces and license plates where detected. Provide configurable retention periods for raw media and derived data, consent notices for tenants, and a GDPR/CCPA-compliant deletion workflow. Maintain a complete audit log capturing media lineage, inference results, confidences, user overrides, model versions, and routing decisions to support compliance and incident investigations.

Acceptance Criteria
Secure Media Delivery: Encryption in Transit and Short‑Lived Signed URLs
Given a client attempts to upload media via web or API When the connection is established Then TLS 1.2 or higher is enforced and non-TLS requests are rejected with an error and no data is accepted And HSTS is enabled for all web endpoints Given media is stored after upload When the object is written to storage Then it is encrypted at rest using server-side encryption with customer-managed keys and a unique object key is recorded in the audit log Given an authorized user requests to view/download a media asset When the system issues an access link Then a pre-signed URL is generated with a maximum validity of 10 minutes and single-resource scope And the URL expires on schedule and returns 403 after expiry And no public, unauthenticated endpoints expose media And pre-signed URLs are only generated after an RBAC authorization check passes
Role-Based Access Control on Media and AI Outputs
Given a tenant user is authenticated When they attempt to access media or AI outputs from another tenant or property Then access is denied with 403 and the event is logged with user, role, resource, and reason Given a vendor user is authenticated When they attempt to access media or AI outputs for tickets not assigned to them Then access is denied with 403 and logged Given a property manager is authenticated When they access media or AI outputs for properties they manage Then access is granted and logged; attempts outside their portfolio are denied Given an auditor role is authenticated When they access audit logs Then read-only access to logs is granted but raw media is hidden unless explicitly permitted by policy Given a role or assignment change occurs When a permission update is made Then effective access decisions reflect the change within 60 seconds And all access attempts (allow/deny) are captured in the audit log
Automatic Redaction of Faces and License Plates
Given an image or video is uploaded by a tenant When the redaction service processes the media Then detected human faces and vehicle license plates are blurred or masked prior to general access And the redaction status and detector confidence scores are recorded in the audit log Given the redaction service cannot confidently detect a face or plate When detection confidence is below the configured threshold Then the media is flagged for manual review and is not exposed to non-privileged roles Given a curated test dataset of labeled faces and plates When the redaction pipeline is evaluated Then at least 95% of labeled faces and plates are redacted (recall) with no more than 5% false positives on non-sensitive regions Given an authorized manager role requests unredacted media When policy allows unredacted access Then unredacted viewing is allowed and explicitly logged; all other roles see only redacted versions
Configurable Retention and Auto-Deletion Policies
Given an administrator configures retention policies When they set retention for raw media and derived data separately (e.g., media=30 days, derived=365 days) Then the system stores the policy version and applies it to existing and new items Given stored media and derived records reach their retention thresholds When the scheduled job runs Then items are permanently deleted and the deletion events are logged with item ID, type, timestamp, and actor=system And references in application metadata are removed within 24 hours Given backups contain deleted items When the retention threshold has passed Then backups containing those items expire and are unrecoverable within 30 days per policy Given a legal hold is applied to a ticket or property When retention would otherwise delete items Then deletion is suspended until hold removal and the hold reason and expiry are recorded Given a user views an item When the item is displayed Then the UI shows the scheduled deletion date based on current policy
Tenant Consent Notice Prior to Upload
Given a tenant begins a new maintenance request with media upload When the upload step is reached Then a consent notice is displayed summarizing data usage, retention, redaction, and rights with region-specific language (GDPR/CCPA as applicable) And a required checkbox must be selected before enabling the upload action Given a tenant accepts the consent notice When they proceed with upload Then a consent record is stored with user ID, timestamp, IP, locale, property region, and notice version And the consent record is linked to the ticket and visible to authorized managers Given a tenant declines consent When they attempt to proceed Then the upload is blocked and the request flow offers contact alternatives without storing media
GDPR/CCPA Deletion (Right to Erasure) Workflow
Given a data subject requests deletion of their personal data related to a ticket When identity is verified via OTP to registered email or phone Then a deletion case is created with a due date no later than 30 days and status is trackable by requester Given a deletion case is in progress When processors and integrated vendors hold related data Then deletion or anonymization requests are propagated to all downstream processors and acknowledgments are recorded Given the deletion case reaches completion When all in-scope data (raw media, derived AI outputs, consent records, access logs containing PII) is deleted or anonymized Then the requester receives confirmation and attempting to access the data returns 404 or equivalent Given a legal hold or statutory requirement exists When evaluating scope Then items under hold are exempted with documented justification visible in the case record
Comprehensive Audit Logging and Immutability
Given any media item is uploaded, processed, accessed, or routed When an event occurs Then an append-only audit log entry is created capturing event type, actor ID and role, timestamp, resource ID, checksum, storage location, encryption status, processing pipeline ID, model names and versions, inference results with confidence scores, user overrides, RBAC decision (allow/deny with reason), and routing decisions Given audit logs are stored When tamper protection is applied Then logs are written to immutable, hash-chained storage with periodic anchors and access controlled by RBAC Given an authorized auditor queries logs When filtering by ticket ID, user ID, property ID, or time range Then results are returned within 5 seconds for ranges up to 7 days and can be exported to CSV or JSON with PII minimization options Given privacy constraints When storing logs Then raw media bytes are never included in logs; only references and checksums are stored Given log retention policies are configured (e.g., 2 years) When the retention threshold is reached Then logs are expired or archived per policy with deletions recorded in an immutable ledger
Performance, Reliability & Fallbacks
"As a tenant, I want fast and reliable autofill feedback so that I know my issue is being handled without delays."
Description

Establish SLOs for the end-to-end Vision AutoFill pipeline (e.g., P95 autofill completion ≤10s for one image, ≤20s for a short video; ≥99.5% daily job success). Implement autoscaling based on queue depth, circuit breakers for downstream dependencies, and retry with exponential backoff. Provide graceful degradation to manual ticket creation that preserves uploaded media and inserts a templated prompt when AI is unavailable. Expose observability dashboards (latency, error rates, queue metrics), alerting, and periodic chaos testing to validate resilience.

Acceptance Criteria
P95 Autofill Latency SLO (Image/Video)
- Given a rolling 24-hour window of successful single-image autofill jobs, When measuring end-to-end time from upload accepted to autofill result persisted, Then P95 completion time ≤ 10s and P99 ≤ 15s. - Given a rolling 24-hour window of successful short-video autofill jobs (video length ≤ 15s, size ≤ 50MB), When measuring end-to-end time from upload accepted to autofill result persisted, Then P95 completion time ≤ 20s and P99 ≤ 30s. - Given measurement instrumentation is enabled, When computing these percentiles, Then sample size ≥ 200 jobs per modality or the full day’s volume if lower, and the method excludes canceled user-initiated aborts.
Daily Job Success SLO
- Given all Vision AutoFill jobs in a UTC day, When computing terminal outcomes, Then ≥ 99.5% of jobs reach a terminal state without operator intervention: either AutofillCompleted or GracefulFallbackCreated. - Given any terminal state, When verifying persistence, Then 100% of jobs have original uploaded media stored and linked to the resulting ticket/job record. - Given hourly rollups, When evaluating intra-day health, Then no single UTC hour has terminal success < 98.5%.
Autoscaling Based on Queue Depth
- Given the processing queue backlog exceeds 2× the number of active workers for ≥ 60s, When the autoscaler evaluates (every ≤ 10s), Then it increases workers such that backlog/worker ≤ 50 within 5 minutes, not exceeding MaxWorkers. - Given backlog remains < 0.5× active workers for ≥ 10 minutes, When the autoscaler evaluates, Then it scales down by ≤ 20% per 5 minutes, never below MinWorkers, and without causing queue wait P95 > 2s. - Given a step-load burst of 1,000 image jobs in 60s, When autoscaling responds, Then P95 queue wait time recovers to ≤ 2s within 5 minutes.
Circuit Breakers and Retry with Backoff
- Given a dependency (ModelInference, OCR, VendorDirectory) shows ≥ 20% error rate or p95 latency ≥ 2s over 60s, When issuing calls, Then the circuit breaker opens for 60s and subsequent calls fail fast. - Given a dependency call fails, When retrying, Then perform up to 3 retries with exponential backoff (1s, 2s, 4s) and ±20% jitter, respecting an overall per-call budget ≤ 6s. - Given retries or duplicate deliveries, When persisting outputs, Then idempotency keys prevent duplicate job results (0% duplicate records in audit logs over 30 days). - Given a circuit is open and no alternate path exists, When the final attempt fails, Then the job transitions to GracefulFallback within 1s.
Graceful Degradation to Manual Ticket
- Given AI services are unavailable or a job exceeds its end-to-end time budget, When degradation triggers, Then the tenant sees the manual ticket form within 1s, with all originally uploaded media attached and persisted. - Given the manual form is shown, When pre-filling content, Then a templated prompt is inserted including property, unit, timestamp, media type, and a placeholder description, and it is fully editable by the tenant/manager. - Given a fallback ticket is created, When viewed by a manager, Then it is labeled "Needs Review - Vision Unavailable" and preferred vendor is unset to avoid misrouting. - Given any fallback path, When auditing, Then 100% of fallback tickets are linked to the originating job ID and show the cause code for degradation.
Observability Dashboards and Alerting
- Given the system is operating, When accessing dashboards, Then they show (≤ 30s lag) queue depth, enqueue/dequeue rates, worker utilization, per-step latencies (p50/p95/p99), error rates per dependency, retry counts, circuit states, and SLO compliance for the last 1h/24h/7d. - Given SLO burn-rate exceeds 2× target for ≥ 10 minutes for either Autofill Latency or Daily Success, When alert rules evaluate, Then a high-priority alert is sent to on-call via Slack and PagerDuty within 5 minutes, with a link to the runbook and the top contributing dependency. - Given end-to-end tracing is enabled, When sampling production traffic, Then ≥ 95% of jobs have a single trace ID propagated across services and are queryable by job ID within 30 days.
Periodic Chaos Testing for Resilience
- Given a scheduled monthly chaos window, When injecting failures (dependency 500s, +3s latency, process restarts, 1% message drops), Then circuit breakers activate within 60s, retries follow policy, and no uploaded media is lost (0% data loss). - Given the chaos experiment runs for 30 minutes, When monitoring outcomes, Then ≥ 98% of jobs still reach a terminal state (AutofillCompleted or GracefulFallback) and P95 queue wait ≤ 5s after 10 minutes from injection start. - Given experiment completion, When generating reports, Then a pass/fail report with findings and action items is produced within 24 hours and stored in the shared knowledge base.

Adaptive Prompts

Dynamic, issue-specific questions appear based on the image and selected symptom—collecting must-have details like shutoff access, noise patterns, error codes, entry permissions, pet notes, and preferred time windows. Tap-to-answer chips speed completion and cut follow-up calls, enabling faster scheduling and fewer return trips.

Requirements

Symptom-Driven Prompt Engine
"As a tenant submitting a maintenance request, I want the app to ask only the relevant questions for my issue so that I can provide the right details quickly without back-and-forth calls."
Description

A configurable rules engine that maps selected symptoms and detected image tags to dynamic question sets, ensuring capture of must-have details (e.g., shutoff access, noise pattern, error codes, entry permissions, pet notes, preferred time windows, severity). Supports conditional logic, branching, required/optional flags, input types (tap-to-answer chips, text, photo, dropdown, time picker), validation, and auto-advance. Ships with default templates for top maintenance categories (plumbing, HVAC, electrical, appliances) and a JSON schema for question packs with versioning and localization keys. Auto-prefills known unit data (e.g., pet presence, entry permissions) and writes normalized outputs into the work order payload for scheduling and vendor briefing.

Acceptance Criteria
Symptom+Image Tag Mapping Selects Correct Question Pack
Given a tenant selects the symptom "Leaking under sink" and an image tag "P-trap" with confidence ≥ 0.80 is present When the prompt engine initializes Then it selects the configured question pack "plumbing.leak_under_sink" with the latest active pack_version Given multiple mapping rules match When selecting a pack Then the engine chooses the rule with the highest priority; if priorities tie, the rule with the most conditions wins Given no mapping rule matches When the engine initializes Then it falls back to the category default pack for "plumbing" and records reason = "fallback_no_rule"
Conditional Branching and Required Flags Enforced
Given the question "Shutoff access available?" is marked required in the selected pack When the tenant attempts to proceed without answering Then the Next action is disabled and an inline validation message is shown Given the tenant answers "No" to "Shutoff access available?" When branching rules evaluate Then the follow-up group "Building shutoff location and contact" is inserted next and questions specific to "Yes" are hidden Given a question is hidden due to branching When generating the payload Then that question is omitted from the payload
Input Types Supported, Auto-Advance, and Inline Validations
Given a chip-based single-select question When the tenant taps a chip Then the selection is saved and the flow auto-advances to the next visible question within ≤ 300 ms Given a dropdown question with ≥ 10 options When the tenant types in the control Then options filter in real time and the control supports keyboard navigation and screen reader labels Given a time picker for preferred windows When the tenant selects a range Then the value is stored as ISO 8601 start and end with timezone; if the range is in the past, an inline error is shown and advance is blocked Given a photo prompt When the tenant uploads an image Then images below 800x600 px or above 10 MB are rejected with an inline message; accepted images show a thumbnail and allow retake before continuing Given a text field expecting an error code matching ^[A-Z0-9-]{2,10}$ When the tenant enters an invalid value Then an inline error appears and advancing is blocked until corrected
Auto-Prefill of Unit Data with Tenant Confirmation
Given the unit profile has pets.present = true and access.entry_permission = "management_with_notice" When the pack renders those questions Then the fields are prefilled with those values and visually labeled as Prefilled Given the tenant leaves a prefilled field unchanged When submitting the pack Then the payload marks source = "unit_profile" for that field Given the tenant edits a prefilled field When submitting the pack Then the payload marks source = "tenant_input" and tenant_updated = true for that field
Default Templates Available with Localization and Fallback
Given the system ships default packs for plumbing, HVAC, electrical, and appliances When a matching symptom is selected Then the appropriate default pack is available without admin configuration Given the user's locale is fr-CA and a localized string key exists When rendering questions Then localized text is displayed; if a key is missing for the locale, the text falls back to en-US without showing raw i18n keys Given a pack is updated and activated as pack_version N+1 When a new session starts Then it uses pack_version N+1; in-progress sessions continue with the version they started
Question Pack JSON Schema and Versioning Compliance
Given a question pack JSON is created or edited When it is validated Then it conforms to the published schema_version and is rejected on any schema violation with explicit error messages Given a pack is marked deprecated When attempting to activate it Then activation is blocked with an error Given a new schema_version introduces breaking changes When publishing packs Then only packs matching that schema_version can be activated; mixed-schema activation is prevented Given display text must use localization keys per schema When validating a pack Then all display strings reference i18n keys and not hardcoded text
Normalized Output Persisted in Work Order Payload
Given the tenant completes the question pack When creating the work order Then the payload includes a details object with normalized keys: severity, shutoff_access, noise_pattern, error_code, access.entry_permission, pets.present, scheduling.preferred_windows[], entry_notes, pet_notes, and photos[] as applicable Given optional questions were not answered When persisting the work order Then optional fields are omitted and required fields are present and non-empty Given preferred_windows[] are present in the payload When the auto-scheduler runs Then the shared calendar receives those windows as availability constraints
Tap-to-Answer Chips UI
"As a tenant on my phone, I want to tap quick answers instead of typing so that I can finish the form fast even when I’m busy."
Description

A responsive UI component that presents common answers as selectable chips to minimize typing and speed completion. Supports single- and multi-select, accessibility (WCAG 2.1 AA), keyboard navigation, ARIA labeling, haptics, and clear states (selected, disabled, error). Includes progress indicator, back/next controls, and optional skip for non-required prompts. Auto-advances on selection when appropriate and persists choices immediately. Provides lightweight theming to match FixFlow’s design system and works across web and mobile web, including SMS deep-link resume.

Acceptance Criteria
Single-Select: Auto-Advance and Immediate Persist
- Given a single-select prompt with no supplemental input required, When the user selects an option via click/tap or presses Enter/Space on a focused chip, Then the option is marked selected, the view auto-advances to the next prompt within 300 ms, and a persist request containing promptId and answerId is initiated within 300 ms. - Given a single-select selection has been made, When the user navigates back to that prompt or reloads within the same session, Then the previously selected option is restored and exposed to assistive technologies as selected. - Given a single-select prompt has an option configured as disabled, When the user attempts to select it via pointer or keyboard, Then no selection change occurs and the disabled state remains intact.
Multi-Select: Toggle Behavior and Persist on Change
- Given a multi-select prompt, When the user taps/clicks a chip or presses Enter/Space on a focused chip, Then that chip toggles its selected state without affecting other selected chips, and a persist request reflecting the new selection set is initiated within 300 ms. - Given a multi-select prompt, When at least one selection is required, Then the Next control remains disabled until the minimum requirement is met; otherwise Next remains enabled. - Given selections have been made on a multi-select prompt, When the user navigates away and returns within the same session, Then all previously selected chips are restored as selected and announced appropriately.
Keyboard Navigation: WCAG 2.1 AA Focus and Operation
- Given the chips are rendered, When the user presses Tab/Shift+Tab, Then focus moves in a logical order: prompt heading → first chip → remaining chips → Skip (if present) → Back → Next → progress indicator. - Given a chip has focus, When the user presses ArrowRight/ArrowLeft (or ArrowDown/ArrowUp), Then focus moves to the next/previous chip without toggling selection. - Given a chip has focus, When the user presses Enter or Space, Then it toggles selection (or selects exclusively for single-select) and triggers auto-advance only if the single-select auto-advance conditions are met. - Given any focusable element is focused, Then a visible focus indicator with sufficient contrast is present, and all interactions are operable with keyboard only (no pointer required).
ARIA Semantics and Screen Reader Announcements
- Given a single-select prompt, Then the chip group is exposed as role="radiogroup" with an accessible name equal to the prompt text, and each chip is role="radio" with aria-checked reflecting selection state. - Given a multi-select prompt, Then the chip group is exposed as a group with an accessible name equal to the prompt text, and each chip is role="checkbox" with aria-checked reflecting selection state. - Given helper or error text is present, Then it is programmatically associated to the group via aria-describedby and announced when it appears. - Given a selection change occurs, Then the change is conveyed to assistive technologies via native control state change or a polite live region, and step changes announce updated progress.
Visual States and Theming Compliance
- Given FixFlow design tokens are available, When the component renders, Then it uses tokens for color, spacing, typography, and radius, and supports runtime theme overrides without code changes. - Given a chip is in selected, disabled, or error state, Then each state is visually distinct and meets WCAG 2.1 AA contrast requirements (text ≥ 4.5:1; essential non-text indicators ≥ 3:1). - Given a chip is disabled, Then it is not focusable, not clickable, and exposed with aria-disabled="true". - Given an error is present for the prompt, Then an inline error message is displayed adjacent to the group, linked via aria-describedby, and no auto-advance occurs until the error is resolved.
Progress Indicator, Back/Next, and Optional Skip
- Given the flow has N steps, When the user navigates, Then the progress indicator displays the current step number and total (e.g., 3/7) and updates within 200 ms of the change. - Given a required prompt is incomplete, Then the Next control is disabled and an attempt to proceed surfaces an inline error; when complete, Next becomes enabled. - Given a prompt is optional, Then a Skip control is visible; activating Skip persists a null/empty answer for that prompt and advances within 300 ms. - Given the user navigates Back and then forward again, Then all previously persisted selections are restored without additional input.
Mobile Web Haptics and SMS Deep-Link Resume
- Given a supported mobile device/browser, When the user selects a chip, Then a light haptic feedback is produced once per selection action and is suppressed when the device/browser does not support haptics or when reduced-motion is requested. - Given the user opens the flow via an SMS deep link with a valid resume token, Then the app loads to the last in-progress prompt for that request and pre-populates any previously persisted selections for that step. - Given the flow is opened via deep link, Then initial focus is set to the prompt heading and the current step is announced to assistive technologies. - Given an invalid or expired deep-link token, Then no user data is exposed and the user is shown an error with a path to start or re-authenticate.
Image-Assisted Issue Detection
"As a tenant unsure of the exact terminology, I want the app to recognize what I photographed and guide me with the right questions so that I don’t miss important details."
Description

On photo upload, analyze images to suggest likely fixtures/appliances and common conditions (e.g., sink leak, toilet clog, HVAC error panel) to pre-select symptom categories and attach tags that drive prompt selection. Includes manual override, confidence thresholds, and latency budget under 1.5 seconds. Supports multiple photos, basic annotation (mark the problem area), and privacy controls (store derived tags; store images only with consent; redact identifiable info where possible). Exposes results to the prompt engine and embeds derived tags in the work order for vendor context.

Acceptance Criteria
Single Photo Detection and Symptom Pre-Selection
- Given a tenant uploads one photo containing a single recognizable fixture, When image analysis completes, Then the system returns a fixture label and at least one condition tag with confidence scores between 0 and 1. - Given the highest-confidence suggestion meets or exceeds the configured threshold (default 0.75), When the issue form renders, Then the corresponding symptom category is pre-selected and the derived tags are attached to the draft work order. - Given the highest-confidence suggestion is below the configured threshold, When the issue form renders, Then no category is auto-selected and the suggestions are displayed as tappable chips.
Latency Budget Compliance (p95 ≤ 1.5s)
- Given image upload has completed, When server-side analysis starts, Then the time from upload-complete to suggestions-available is ≤ 1.5 seconds at the 95th percentile measured over 200 consecutive requests in staging. - Given analysis exceeds 300 ms, When the UI waits for results, Then a progress indicator is displayed until suggestions arrive or 2 seconds elapse, after which a "Continue without suggestions" option is shown. - Given suggestions are not available within 5 seconds, When the user proceeds, Then the form functions normally without pre-selection and an error is logged with a correlation ID.
Multiple Photos Aggregation and Conflict Resolution
- Given a tenant uploads 3 photos for the same issue, When analysis completes, Then tags from all photos are merged and de-duplicated and include per-photo provenance. - Given conflicting fixture suggestions across photos, When computing the final pre-selection, Then the system selects the fixture with the highest confidence across all photos; if a tie occurs, Then no auto pre-selection occurs. - Given per-photo tags are generated, When the vendor views the work order, Then all photo thumbnails display their respective tag chips and any annotations.
Manual Override of Suggested Category and Tags
- Given a pre-selected category was set by image analysis, When the tenant changes the category manually, Then the pre-selection is replaced by the user’s choice and the derived tags are updated to align with the new category while preserving the original ML suggestions in an audit trail. - Given the user manually edits or removes a suggested tag, When the prompt engine runs, Then it uses the user-edited tag set and does not re-apply the original suggestions in that session. - Given a manual override was performed, When the work order is created, Then it records that a manual override occurred with timestamp and user ID.
Problem Area Annotation Persistence
- Given analysis results are shown, When the tenant taps "Mark problem area" and draws an annotation on a photo, Then the annotation coordinates are saved with the photo and are visible on re-open. - Given multiple photos are present, When annotations are added to different photos, Then each photo retains its own annotation(s) without affecting others. - Given a work order is generated, When the vendor opens the order, Then the annotated overlays render on the corresponding photos in the vendor view.
Privacy Controls, Consent, and Redaction
- Given no explicit consent is provided, When image analysis completes, Then only derived tags are persisted; the original images and thumbnails are deleted within 60 seconds and are not accessible via the UI or API. - Given consent is provided via a clear toggle at submission, When the request is saved, Then the original images are stored and linked to the work order with consent=true metadata. - Given an image contains detectable faces or license plates, When storage or transmission occurs, Then those regions are blurred (Gaussian blur with radius ≥ 10 px on 1080p) and EXIF GPS data is stripped; success is verified on a standard test set with 100% of detected instances redacted.
Expose Detection Results to Prompt Engine and Work Orders
- Given derived tags are available, When the prompt engine evaluates the issue, Then it receives the tags as a key-value payload and renders the corresponding dynamic questions within 300 ms of form render. - Given a work order is created, When the vendor opens it, Then the embedded derived tags are displayed under "Detected Context" and included in the order’s JSON payload. - Given tags are updated due to manual override, When the prompt engine re-evaluates, Then the new tags are used and the prior ML-only tags are not re-applied.
Scheduling Readiness Gate
"As a property manager, I want scheduling to proceed only after essential details are captured so that vendors arrive prepared and we avoid return trips and double-bookings."
Description

A validation layer that blocks vendor scheduling until all required prompts are completed and critical safety steps are acknowledged (e.g., water shutoff). Computes a readiness score, displays a checklist of missing items, and deep-links users back into specific prompts. On completion, compiles a vendor-ready job brief including answers, photos, error codes, entry permissions, pet notes, and preferred time windows, then passes it to the shared calendar and SMS confirmation flows. Supports high-priority issue overrides with emergency instructions and automatic escalation rules.

Acceptance Criteria
Block Scheduling Until Required Prompts and Safety Acknowledgements Are Complete
Given a maintenance request with any required prompts unanswered or required safety acknowledgements not confirmed When the user attempts to schedule a vendor (tap Schedule or via API) Then the scheduling action is blocked and the Schedule control is disabled And a modal/toast explains that required information is missing And a checklist is displayed listing each missing required item with counts And scheduling becomes enabled only when readinessScore = 100 and all required safetyAcknowledgements = true And an analytics event "readiness_gate_blocked" is logged with requestId and missingItemIds
Real-Time Readiness Score Calculation and UX State
Given totalRequiredPrompts = N and answeredRequiredPrompts = M When any required answer is added, edited, or cleared Then readinessScore = floor((M/N)*100) updates within 200 ms of the change And the primary CTA state reflects: Enabled iff readinessScore = 100 and all required safetyAcknowledgements = true; otherwise Disabled And the score indicator displays "Ready" when readinessScore = 100, else displays the numeric percentage (e.g., "80%")
Checklist Display of Missing Items with Deep-Link Navigation
Given one or more required items are missing When the user taps a checklist row for a missing item Then the app navigates directly to the corresponding prompt group and focuses the first unanswered control And upon providing an answer that satisfies the requirement, the checklist item is marked complete within 300 ms And when all items in a group are complete, that group collapses or disappears from the checklist And using Back returns to the checklist preserving scroll position and filter state
Vendor-Ready Job Brief Compilation and Handoff to Calendar/SMS
Given readinessScore = 100 and all required safetyAcknowledgements = true and the user confirms scheduling When the job is scheduled Then a jobBrief object is assembled including: answers(required+optional), photos, errorCodes, entryPermissions, petNotes, preferredTimeWindows, safetyAcknowledgements, reporterContact, timestamps And the calendar event payload contains: jobBriefId, requestId, propertyId, vendorId, startWindow, endWindow, accessInstructions, and a link to the jobBrief And an SMS confirmation payload is generated within 300 ms including: issue summary, time window, entry permissions, contact reply instructions, and a secure link to photos/jobBrief And both calendar API and SMS service return HTTP 2xx; otherwise the schedule is rolled back and the user sees an actionable error with retry
High-Priority Emergency Override with Escalation
Given the issue is flagged High Priority (e.g., active leak, no heat in winter) When the user selects Emergency Override Then the readiness gate is bypassed after the user acknowledges emergency instructions (e.g., shutoff guidance) with a required confirmation checkbox And an escalation is triggered: notify on-call vendor(s) via SMS/push within 60 seconds and create a high-priority calendar hold And the request is tagged readinessStatus = "Override" and outstanding checklist items remain available for later completion And an audit log entry records actor, timestamp, reason, and recipients
Progress Persistence and Resume Across Sessions
Given the user partially completes prompts and/or uploads photos When the user closes the app or switches devices and later returns to the request Then answers, photos, and readiness progress are autosaved at least every 5 seconds or on each change and restored within 1 second on reopen And deep links from checklist or notifications open directly to the targeted missing prompt And up to 10 minutes of offline work is retained locally and syncs on reconnect with last-write-wins and visible conflict indicators if applicable
Prompt Admin & Templates
"As a small property manager, I want to tailor the questions to my buildings and vendors so that tenants provide exactly what our team needs to schedule quickly."
Description

An admin console for landlords/property managers to customize and publish prompt packs per property, unit type, and vendor category. Allows creation/editing of questions, required vs optional flags, branching rules, and default answer sets. Supports import/export of JSON schemas, preview-as-tenant, version history with rollback, staged rollout, and localization. Enforces guardrails to preserve critical compliance/safety prompts while enabling flexible tailoring to local procedures.

Acceptance Criteria
Create and Publish Prompt Pack by Property/Unit Type/Vendor Category
Given I am an authorized admin, When I create a prompt pack with name, description, and at least one target scope (property and/or unit type and vendor category), Then the pack is saved as Draft with a unique ID and timestamps. Given a Draft prompt pack with no validation errors, When I click Publish Now, Then it is versioned to 1.0 and becomes Active for the selected scopes. Given a Draft prompt pack, When I schedule a future publish date/time, Then the pack transitions to Active at that time without manual intervention and an audit entry is recorded. Given multiple Active packs match a tenant context, When resolving which pack to serve, Then the most specific scope wins (property+unit type+vendor category > property+vendor category > vendor category only), and ties are broken by highest version. Given I unpublish or archive an Active pack, When a tenant session starts thereafter, Then the system falls back to the next eligible pack per the same priority rules.
Question Editor: Required Flags, Branching Rules, Default Answers, and Guardrails
Given I add a question of type {single-select|multi-select|yes/no|text|number|date|time|photo}, When I mark it Required, Then runtime enforces an answer and publish is blocked if prompt text is missing in required locales. Given I define branching rules on prior answers, When a rule creates a cycle or unreachable question, Then save is blocked and the error lists offending question IDs and JSON Pointer paths. Given I configure default answer chips for a select-type question, When I save, Then up to 25 choices are stored in the specified order and are localizable per locale key. Given a compliance/safety prompt is locked by guardrails, When I attempt to delete it, change its Required flag, or alter restricted wording beyond allowed placeholders, Then the change is blocked with a guardrail error referencing the policy ID. Given I duplicate a question within a pack, When I save, Then all branch rule references are updated to new IDs and validation passes.
Import/Export JSON Schema
Given I upload a JSON file conforming to the Prompt Pack schema version X.Y, When I import, Then a Draft pack is created/updated with 0 errors and 0 warnings, and the report lists counts for questions, branches, locales, and guardrailed items. Given I export any pack, When I download the JSON, Then it is emitted in canonical order and re-importing it yields a pack that is deep-equal after canonicalization (no diffs in a structural comparison). Given the import JSON contains unknown fields, invalid enums, or schema violations, When I import, Then the operation fails with an error list using JSON Pointer paths and messages, and no partial changes are applied. Given the import references a removed or restricted compliance prompt, When I import, Then the import is rejected and the report lists guardrail violations by rule and question key.
Preview as Tenant
Given a pack is Draft or Active, When I click Preview as Tenant and select property, unit type, vendor category, locale, symptom, and optional sample image, Then the preview renders exactly the questions resolved by the branching engine for that context in the selected language. Given required questions exist, When I attempt to submit the preview without answering them, Then the UI blocks submission and highlights each required field. Given questions have default answer chips, When the preview loads, Then chips appear in the configured order and selecting them records the corresponding value. Given I generate a preview link, When it is opened, Then it displays the same resolved flow and expires automatically 24 hours after generation or immediately upon pack deletion.
Version History and Rollback
Given I save changes to a Draft, When the save succeeds, Then the system increments the minor version and records an audit log with user, timestamp, and change summary. Given I publish a pack, When publish completes, Then the system increments the major version and snapshots the content for diffs. Given I view history, When I select two versions, Then a structured diff shows added/removed/changed questions, branching rules, and translations. Given I initiate a rollback to a prior version, When confirmed, Then a new Draft is created from that snapshot as the next minor version, preserving current guardrails; publishing it makes it the latest major without mutating historical records.
Localization and Translation Coverage
Given a pack targets properties with required locales configured, When I attempt to publish with missing translations in any required locale, Then publish is blocked and a report lists missing keys per locale. Given a non-required locale is missing a translation, When a tenant views prompts in that locale, Then the system falls back to the default locale and logs a missing-translation telemetry event. Given I export a pack with translations included, When I re-import it, Then all localized strings and locale mappings are preserved. Given I enter RTL text for an RTL locale, When previewing or exporting, Then directionality and formatting render correctly and are preserved.
Staged Rollout and Targeting Controls
Given an Active pack with a newer version available, When I configure a staged rollout by percentage or by property/unit subset, Then eligible tenants are bucketed deterministically by stable hash of tenant or unit ID and remain consistent across sessions. Given a staged rollout is in progress, When I click Pause or Roll back, Then traffic returns to the baseline version within 5 minutes and the action is recorded in the audit log. Given multiple staged rollouts would overlap for the same scope, When I attempt to enable a conflicting plan, Then the system blocks the change and identifies the conflicting scopes and versions. Given rollout metrics are enabled, When the plan runs, Then the system captures and displays success rate, drop-off rate, and error count per version and locale.
Data Persistence & Resume Flow
"As a tenant who may get interrupted, I want my progress saved and a link to resume so that I can finish later without starting over."
Description

Autosave all prompt responses in real time and allow users to resume via secure, expiring deep link from SMS or email. Pre-fill known tenant and unit data (e.g., pets on file, lockbox code, entry permissions) and allow review/edit before submission. Handle offline mode with local caching and retry queues, ensure idempotent submission, and store data encrypted at rest. Integrates with FixFlow identity and work order records to prevent duplicate requests and maintain a single source of truth.

Acceptance Criteria
Real-Time Autosave of Prompt Responses
Given the user is online and editing Adaptive Prompts When any response value changes (text input, selection, tap-to-answer chip) Then the draft is written to local cache within 300 ms and sent to the server within 1 s And the server responds 2xx with updated version and lastSavedAt timestamp Given the user reloads or navigates back to the draft When the form initializes Then previously saved responses are restored exactly as of lastSavedAt Given two browser tabs edit the same draft When a stale version is posted Then the server returns 409 with current version And the client refreshes to the latest draft without data loss
Resume via Secure, Expiring Deep Link
Given the tenant requests a resume link via SMS or Email When the request is made Then the system generates and sends a single-use, signed deep link bound to the draft and tenant identity that expires in 24 hours Given the tenant opens the deep link before expiry When the app loads Then the tenant is resumed into the saved draft at the last step with all responses restored Given the deep link is opened after expiry or re-used When the link is accessed Then access is denied (401/410) and no draft data is returned Given a different authenticated user attempts to use the link When access is attempted Then the request is rejected with 403 and the event is logged
Pre-Fill Tenant and Unit Data with Review and Edit
Given the draft is associated with a known tenant and unit When the Adaptive Prompts form loads Then pets on file, lockbox code, entry permissions, and preferred time windows are pre-populated from the profile Given any pre-filled value is edited by the tenant When the review step is shown Then the edited values are displayed and will be submitted instead of the original pre-fill Given a required pre-filled field is empty in the profile When the user proceeds to submit Then submission is blocked with a validation error until the field is completed Given the user submits When the payload is created Then the payload includes the final values for all pre-filled fields
Offline Mode with Local Caching and Retry Queue
Given the tenant is offline while completing Adaptive Prompts When a response value changes Then the change is saved to encrypted local storage and added to a retry queue Given connectivity is restored When the client detects online status Then queued saves are sent within 5 seconds in original order and the server's latest version is applied Given the browser/tab is closed while offline When the tenant returns to the draft Then the form restores from the most recent local autosave with no loss of entered data Given transient server/network errors occur during sync When retries are attempted Then the client retries up to 5 times with exponential backoff (max 60 seconds) without creating duplicate records
Idempotent Submission and Duplicate Tap Protection
Given a client-generated idempotency key is attached to the draft When the tenant taps Submit multiple times within 60 seconds Then exactly one work order is created and subsequent submits return 200 with the same workOrderId Given submission fails due to a transient error and the client retries with the same key When the server later receives the retry Then at most one work order exists and the response includes the original workOrderId Given a second submission attempts to use the same key with a materially different payload When the server validates the request Then the request is rejected with 409 Idempotency Conflict
Encryption at Rest for Drafts and Submissions
Given draft data is cached locally in the browser When storage is inspected (IndexedDB/Cache Storage) Then values are stored as ciphertext and cannot be read without the encryption key And no PII fields (names, phone, lockbox code, entry permissions) appear in plaintext Given submissions and drafts are stored server-side When data-at-rest configuration is audited Then database and object storage encryption at rest is enabled and key rotation follows policy
Identity Linkage and Duplicate Work Order Prevention
Given the draft is linked to a tenant identity and unit record in FixFlow When the tenant submits the request Then the submission is associated to that tenant and unit in the work order system Given an open work order exists for the same unit and issue category within the last 14 days When the submission is received Then no new work order is created; the submission is linked to the existing work order and the response returns that workOrderId Given no matching open work order exists When the submission is received Then a new work order is created and the draft transitions to Submitted state

Severity Guard

A real-time risk score flags true emergencies and surfaces clear safety steps (e.g., water shutoff, breaker switch) with visual guides. After-hours logic escalates urgent cases to on-call vendors while deferring non-critical issues to business hours—reducing false emergencies, protecting property, and improving SLA compliance.

Requirements

Real-Time Risk Scoring Engine
"As a property manager, I want incidents automatically risk-scored in real time so that true emergencies are escalated immediately and non-urgent issues are queued appropriately."
Description

A low-latency scoring service that evaluates incoming maintenance requests across channels (web, SMS, phone notes) using structured fields, keyword cues, attachment metadata, and property context to compute a 0–100 risk score and severity tier (Emergency/High/Normal/Low) in under 500 ms. Outputs score, tier, rationale, and recommended next action via an internal API and event stream. Supports multilingual inputs, configurable weighting, and fallbacks to deterministic rules to minimize false positives/negatives. Integrates with intake forms, ticket creation, and routing services, and logs each scoring decision for auditability.

Acceptance Criteria
P95 Latency  500 ms Under Representative Load
Given a sustained mixed workload of 50 requests/second for 5 minutes with representative payloads across channels When the scoring service processes requests end-to-end via the internal API Then P95 latency is <= 500 ms, P99 latency is <= 700 ms, and error rate (HTTP 5xx/timeouts) is < 0.1% Given single-request execution on production-parity staging hardware When the same valid payload is scored 100 times Then average response time (P50) is <= 200 ms and no run exceeds 500 ms
API Response and Event Stream Schema Completeness
Given a valid scoring request When processed synchronously via the internal API Then the JSON response includes fields with valid values and types: score (integer 0–100), tier (one of Emergency|High|Normal|Low), rationale (non-empty string >= 20 chars), recommended_next_action (one of escalate_on_call|schedule_business_hours|request_more_info|show_safety_steps) Given a valid scoring request When processing completes Then an event is published to the internal event stream within 200 ms of the API response containing the same core fields plus request_id, property_id, timestamp (ISO-8601 UTC), and model_version, and the message passes JSON Schema validation with 0 errors Given a processed request When checking the audit store Then an immutable audit record exists with request_id, input fingerprint (hash), score, tier, rationale, recommended_next_action, property_id, timestamp, and model_version, and is retrievable by request_id within 2 seconds (write success rate >= 99.9%)
Deterministic Severity Tier Mapping from Score
Given any produced score in the inclusive range [0, 100] When mapping to a severity tier Then tiers are assigned by thresholds: Emergency [85–100], High [65–84], Normal [35–64], Low [0–34], and boundary values map deterministically (85=>Emergency, 65=>High, 35=>Normal, 34=>Low) Given identical inputs submitted multiple times When scores are produced Then the computed tier is identical across 3 consecutive runs
Multichannel Inputs and Attachment Metadata Utilization
Given inputs originating from web form, SMS (including multi-part), and phone notes When each is submitted for scoring Then all are normalized into a canonical request and return a score, tier, rationale, and recommended_next_action without channel-specific failures Given an SMS message exceeding 160 characters split into multiple parts When received by the system Then parts are reassembled losslessly prior to scoring and the full text is used in features Given requests with image/video attachments When processed Then attachment metadata (MIME type, count, size, EXIF timestamp if available) is extracted and made available to the scoring features; requests without attachments are processed without error
Configurable Weighting with Safe Runtime Reload and Rollback
Given a new versioned weighting configuration (config_id) When applied via the configuration interface Then the engine reloads weights within 60 seconds without dropping requests and includes config_id and model_version in responses/events Given a rollback command to the previous configuration When executed Then the engine restores the prior weights within 60 seconds, logs the change (actor, timestamp, reason), and responses/events reflect the restored config_id Given a dry-run mode is enabled for a candidate configuration When scoring a sampled traffic subset (>= 5%) Then the engine records shadow scores without affecting live decisions and emits comparison metrics for review
Deterministic Rule Fallback Ensures SLA and Accuracy
Given the primary model is unavailable, exceeds an internal timeout of 300 ms, or returns confidence < 0.60 When a request is scored Then deterministic fallback rules produce a score and tier such that the overall response meets the 500 ms SLA and the rationale includes fallback_rules_applied=true with the top contributing rules Given a labeled validation set of 500 requests (balanced Emergency/Non-Emergency) When processed entirely via fallback rules Then Emergency vs Non-Emergency precision is >= 0.85 and recall is >= 0.80
Multilingual Detection and Cross-Language Consistency
Given inputs in English, Spanish, Portuguese, French, and Simplified Chinese (>= 200 labeled samples per language) When scored Then language detection accuracy is >= 0.95, and no encoding errors (mojibake) occur in rationale or logs Given a parallel corpus of 100 real-world issues translated across the five languages When scored Then the assigned tier is identical across languages for >= 90% of cases and the absolute score difference is <= 10 points for >= 95% of cases
Contextual Safety Playbooks with Visual Guides
"As a tenant, I want clear, step-by-step safety instructions with visuals tailored to my unit so that I can safely mitigate damage while I wait for help."
Description

Tenant-facing, step-by-step safety instructions triggered by incident category and severity (e.g., shut off water, trip breaker) with annotated images and short clips optimized for mobile. Content is tailored to property fixtures through per-property notes (e.g., shutoff valve location) and localized into supported languages. Delivered inline in the tenant portal and via secure SMS links, with progress tracking (checkboxes, confirmations) and telemetry on completion rates. Versioned content with approval workflow and accessibility conformance (WCAG AA).

Acceptance Criteria
Emergency Water Leak Playbook Triggered and Rendered on Mobile
Given an incident is classified as category "Water Leak" with severity "Emergency" for a tenant at Property P with stored shutoff notes and media When the tenant opens the tenant portal on a mobile device or the playbook via secure SMS link Then the "Water Leak - Emergency" playbook renders inline within 2s on a 4G connection and within 4s on a 3G (1.5 Mbps) connection And the first steps include actionable safety tasks (e.g., locate main water shutoff, turn valve clockwise, confirm water stops) with annotated images and a short clip And annotations are crisp on small screens and support pinch-to-zoom; videos buffer without auto‑playing audio And only steps relevant to the "Water Leak - Emergency" scenario are displayed; unrelated category steps are excluded And total initial above‑the‑fold payload (HTML+CSS+JS+media) is ≤ 2 MB
Property-Specific Fixture Notes Injected into Playbook Steps
Given Property P has per‑property notes "Main water shutoff behind washer" and a property‑specific image configured for the water shutoff step When the playbook is rendered for an incident at Property P Then the step text includes the note inline and displays the property‑specific image in place of the default And a "Property‑specific guidance" label is shown on tailored steps And if a required note or asset is missing, the system falls back to default content, records a warning event with property_id and missing_field, and displays a non‑blocking helper link to contact support And updates to per‑property notes propagate to new playbook renders within 5 minutes
Localization and Language Switching for Tenant-Facing Playbooks
Given the tenant’s preferred language is Spanish (es) and a Spanish translation exists for the selected playbook When the tenant opens the playbook via portal or SMS link Then all step text, UI labels, alt text, and video captions render in Spanish by default And a language switcher allows changing to any supported language without page reload; the selection persists for that incident And if a translation key is missing in Spanish, the system falls back to English for that key and logs a missing_translation event with key and language And localized content load time does not exceed English by more than 10% And SMS message body and preview text are localized to the tenant’s language
Secure SMS Deep Link Delivery with Expiry and Scope
Given a playbook is generated for Incident I for Tenant T When the system sends an SMS deep link Then the link contains a signed, single‑scope token bound to Incident I and Tenant T with a TTL of 24 hours and ≥128‑bit entropy And clicking the link opens the playbook directly (no login) only for Incident I and records an access event (timestamp, incident_id, masked phone, user_agent) And after expiry or if the token is tampered, the link shows an expired/invalid page with an option to request a new link via verified phone And if progress exists, the deep link opens to the last incomplete step
Step Completion Tracking with Persistent Progress and Telemetry
Given a tenant is viewing a multi‑step playbook with checkboxes and required confirmations When the tenant marks a step as done or confirms a prompt Then the step state persists within 1s and is restored on refresh or device change And when all required steps are complete, the playbook displays a Completed banner and emits a playbook_completed event And telemetry events are emitted on step_viewed, step_completed, and playbook_completed including incident_id, property_id, playbook_version, step_id, language, timestamp, and device_type And an admin dashboard can report completion rate by incident category and property with data latency ≤ 5 minutes And no PII beyond incident_id/property_id and masked identifiers is stored in telemetry payloads
Versioning and Approval Workflow Governing Playbook Visibility
Given Playbook X has Approved v1 (live) and Draft v2 (pending approval) When a tenant opens Playbook X for a new incident Then only Approved v1 is served until v2 is approved by a user with the Approver role And upon approval, new incidents receive v2 while existing incidents continue to see their originally pinned version unless explicitly re‑pinned And editors cannot publish directly; valid transitions are Draft → In Review → Approved → Archived with audit trail (who, when, version, change summary) And an administrator can roll back the default to a prior Approved version; rollback takes effect for new incidents within 5 minutes
WCAG AA Accessibility for Visual Guides and Interactive Steps
Given tenants using screen readers (NVDA/VoiceOver) or keyboard‑only navigation access the playbook When navigating all steps, controls, images, and videos Then all interactive elements are reachable in a logical focus order with visible focus indicators and proper ARIA roles/labels And images have meaningful alt text; videos have captions and transcripts; motion/animation respects prefers‑reduced‑motion And color contrast meets ≥ 4.5:1 for text/icons; touch targets are ≥ 44×44 px And automated scans (axe‑core) report 0 critical and ≤ 3 moderate issues per playbook page; any issues are tracked with IDs And the entire playbook is usable via keyboard without timing‑dependent gestures
After-Hours Escalation and Deferral Routing
"As a property manager, I want urgent after-hours incidents routed to the on-call vendor and non-critical ones deferred to business hours so that we protect property while controlling costs."
Description

Time-aware routing that applies account business hours, time zones, and holidays to decide action paths. Emergencies and high-severity incidents outside business hours auto-notify on-call vendors via SMS/voice/email with accept/decline, attach incident details and safety status, and place a provisional calendar hold. Non-critical incidents receive an automatic receipt, safety guidance, and are scheduled to the next business window. Includes vendor response SLAs, retry/escalation ladder, override controls, and full decision logging.

Acceptance Criteria
After-Hours Emergency Escalation — Vendor Accepts
Given an incident with Severity = Emergency is created outside configured business hours in the property's local time When the incident is saved Then the system notifies the primary on-call vendor via SMS, voice, and email within 60 seconds including incident ID, property address, tenant callback, current safety-step status, and a unique Accept/Decline link And when the vendor taps Accept Then the system records the acceptance timestamp, sets incident status to Dispatched, and places a provisional calendar hold on the vendor's shared calendar for the earliest available slot within 2 hours of acceptance with a 60-minute duration, attaching incident details And then the tenant and landlord receive confirmation messages with vendor name and ETA within 30 seconds of acceptance And delivery receipts for all outbound notifications are stored per channel with timestamp and success/failure
After-Hours Emergency Escalation — No Response, Retry, and Secondary Escalation
Given an after-hours Emergency incident is awaiting vendor response When notifications are sent to the primary on-call vendor Then a response SLA timer of 5 minutes starts and is visible on the incident And if no Accept or Decline is recorded within 5 minutes Then the system retries the primary vendor up to 2 additional times at 2-minute intervals across all channels And if the vendor Declines at any time Then the system immediately escalates to the next vendor in the on-call ladder And if still no response after all retries Then the system escalates to the next vendor in the ladder, repeating the same SLA and retry pattern, up to 3 vendors total And if all vendors fail to respond or decline Then the system notifies the on-call manager via SMS and email, creates a voicemail task, flags the incident as Escalation Breached, and removes any provisional holds And each attempt and escalation step is logged with vendor, attempt number, channels used, delivery outcomes, and timestamps
After-Hours Non-Critical Deferral to Next Business Window
Given an incident with Severity = Low or Medium is created outside configured business hours or on a configured holiday When the incident is saved Then the system sends the tenant an automatic receipt within 30 seconds including category-specific safety guidance and a reference number And the system determines the next business window using account business hours, the property's time zone, and holiday exclusions Then the system proposes the earliest vendor slot available within that window, creates a provisional calendar hold for that slot, and sets incident status to Scheduled - Deferred And the landlord receives a summary email of the deferral action; the tenant receives a confirmation with the proposed slot and a reschedule link And no on-call vendors are notified during deferral
Time Zone, DST, and Holiday-Aware Routing
Given a property is located in a time zone that may differ from the account default When an incident is created Then business-hours determination uses the property's time zone derived from its address And when daylight saving time transitions occur Then business-hour boundaries shift correctly without overlapping or missing windows And when a day is marked as a holiday in the account calendar Then the entire day is treated as after-hours for routing decisions And the audit log records the time zone used, DST status, holiday flags, and the resulting routing decision
Vendor Response SLA Tracking and Breach Alerts
Given an after-hours incident with Severity = Emergency or High When any notification is sent to a vendor Then a response SLA timer starts (Emergency: 5 minutes; High: 10 minutes) and is visible on the incident And when the vendor Accepts within SLA Then the system marks SLA Met and records the response time in metrics And if the SLA elapses without Accept Then the incident is marked SLA Breached, the escalation ladder proceeds, and the landlord receives an immediate alert via SMS and email And SLA outcomes (Met/Breached) and average response times appear in weekly reports
Manual Override to Force Dispatch or Deferral
Given a user with role = Admin or Dispatcher is viewing an incident When they choose Override and select Force Dispatch or Force Deferral and provide a required reason Then the system executes the chosen route immediately, bypassing automatic logic And the override records user, timestamp, reason, and prior decision state in the audit log And notifications sent due to the override include an Override flag and the reason in vendor and landlord messages And override actions are reversible within 10 minutes by the same or higher-privileged user, and both the override and reversal are logged
Comprehensive Decision Logging and Export
Given any routing decision is made for an incident When the incident is processed Then an immutable decision log entry is created containing input parameters (severity, created timestamp, property's time zone, business-hours set, holiday flag, on-call roster snapshot), actions taken (notifications by channel with delivery status, SLA timers, escalations, holds created), and outcomes (assigned vendor, scheduled time, status changes) And decision logs are viewable in the UI within 5 seconds of creation and exportable as CSV and JSON And sensitive tenant contact data in logs is masked (phone shows last 4 digits; email shows first 3 letters and domain) And decision logs are retained for at least 24 months
Two-Way SMS Triage and Confirmation
"As a tenant, I want to answer quick questions and share photos by text so that my issue is triaged accurately without needing a phone call."
Description

Interactive messaging flow that asks clarifying questions, requests photos/videos, and confirms completion of safety steps to refine the risk score and reduce false emergencies. Supports branching logic based on prior answers and current severity, opt-in/out management, delivery status tracking, rate limiting, and multilingual templates. Integrates with the scoring engine, safety playbooks, and ticket updates so that new data immediately re-scores and adjusts routing as needed.

Acceptance Criteria
Real-Time Re-Scoring and Routing Adjustment
Given an active maintenance ticket with SMS triage enabled and tenant consented And the triage flow collects a new answer or media When the answer/media is received Then FixFlow sends the update to the scoring engine within 3 seconds (p95) and 8 seconds (p99) And the ticket's risk score updates atomically with a timestamp and source "SMS-Triage" And if the score crosses the emergency threshold, the ticket status changes to "Emergency" and routing updates within 5 seconds to the correct queue/on-call vendor And if the score drops below the emergency threshold, any pending emergency escalation is canceled and the ticket is deferred to business hours with an audit entry
Branching Logic Drives Dynamic Questions
Given a tenant reports "water leak" or selects category "Water" When the current severity is Medium or higher Then the next prompt includes the water shutoff steps link and asks "Can you locate and turn off the main water valve? Reply YES/NO" Given a tenant reports "no power" or selects category "Electrical" When the current severity is Medium or higher Then the next prompt includes a breaker reset guide and asks for room(s) affected Given a tenant responds with "NO" to a safety step When the time is after-hours Then the flow branches to escalate rather than scheduling for business hours
Safety Steps Instruction and Confirmation
Given a safety playbook step is sent via SMS When the tenant replies YES within 3 minutes Then the step is marked completed, a confirmation message is sent, and the risk score reduces by the configured step weight When the tenant replies NO or times out after 3 minutes Then the step is marked failed, the risk score increases by the configured step weight, and escalation logic evaluates next action And every step completion/failure is recorded to the ticket timeline with the playbook step ID and timestamp
Photo/Video Intake and Validation
Given the triage requests media When the tenant sends MMS or uploads via secure link Then the system accepts images (jpeg, png, heic) up to 10 MB and videos (mp4, mov) up to 30 MB, virus-scans them, and attaches them to the ticket And a thumbnail/preview is visible in the dashboard within 10 seconds of receipt And if an unsupported type or oversize media is received, the tenant is prompted to retry with supported formats or to use the upload link And each media artifact is tagged with the prompting question ID and sender phone
SMS Consent and Keyword Management
Given a tenant has not previously opted in When the first triage message is sent Then it includes an explicit opt-in request and STOP/HELP instructions and the flow halts until the tenant replies YES When the tenant sends STOP at any time Then all non-compliance messages cease immediately, the number is marked opted-out, and an opt-out confirmation is sent When the tenant sends START after STOP Then messaging resumes, consent timestamp and language are logged, and the ticket continues where it left off And HELP returns a configurable help message without altering consent status
Messaging Delivery Tracking, Retries, and Throttling
Given an SMS is sent via the gateway When a delivery receipt is returned Then the per-message status (queued, sent, delivered, failed with cause) is stored with timestamps and visible on the ticket When a message fails with a temporary error Then the system retries up to 3 times with exponential backoff (10s, 30s, 90s) When a message fails with a permanent error Then retries stop, an agent task is created to follow up via an alternate channel, and the tenant is not re-messaged Given more than 6 outbound messages would be sent to the same number within 10 minutes and the ticket is not Emergency Then additional prompts are queued and coalesced to stay within the limit, and a cooldown banner appears for agents
Multilingual Triage Templates and Fallback
Given a tenant’s preferred language is stored as es-ES or es-MX When triage prompts are sent Then Spanish templates are used, with variables correctly localized (dates, times), and messages remain under 160 GSM characters per segment Given a template translation is missing When the message would be sent Then the system falls back to English with a one-time apology line in the tenant’s language and logs a translation gap Given the tenant replies in a different language detected by confidence > 0.8 When continuing the flow Then the system offers to switch language and updates the preference upon confirmation
SLA and Audit Timeline
"As a property owner or manager, I want a complete audit trail and SLA metrics so that I can prove compliance and improve response performance."
Description

Immutable timeline capturing every event in the incident lifecycle—risk scores, playbook views and confirmations, messages, vendor notifications, accepts/declines, calendar holds, and on-call escalations—with timestamps and actors. Generates SLA metrics such as time-to-triage, time-to-vendor-contact, and time-to-mitigation, with alerts when thresholds are at risk. Provides export to CSV/PDF and a dashboard with filters by property, vendor, and severity for compliance reviews.

Acceptance Criteria
Immutable Event Timeline Capture
Given an incident exists, When any of the following occur: risk score generated/updated, safety playbook viewed, safety step confirmed, message sent/received, vendor notified, vendor accepts/declines, calendar hold created/updated, on-call escalation triggered, Then a new append-only event is created with a unique event_id, event_type, actor_type, actor_id, and event_timestamp (UTC). Given an event is recorded, Then it is visible in the incident timeline within 2 seconds of action completion and ordered by event_timestamp ascending with event_id as tie-breaker. Given any API or UI attempt to modify or delete an existing event, Then the operation is rejected with 403 and no existing event is changed; When a correction is submitted, Then a new event of type correction is appended referencing the prior event_id. Given network retries, When a duplicate action_id is received, Then no duplicate event is appended (idempotent write). Given an incident is closed, Then the full timeline remains readable and uneditable to all authorized roles.
SLA Metric Computation
Given an incident timeline contains incident.created and either risk_score.confirmed or triage_decision.saved, When metrics are generated, Then time_to_triage = (triage_end timestamp) − (incident.created timestamp), where triage_end is the earliest of risk_score.confirmed or triage_decision.saved. Given triage_end and vendor.notification.sent exist, Then time_to_vendor_contact = (first vendor.notification.sent timestamp) − (triage_end timestamp). Given any of safety_step.confirmed or mitigation_action.recorded exist, Then time_to_mitigation = (earliest mitigation event timestamp) − (incident.created timestamp). Given anchor events for a metric are missing, Then that metric displays N/A and is excluded from breach evaluation for that metric without blocking others. Then all metric durations are computed in minutes with two decimal places and recomputed in real time as new events arrive.
SLA Breach and At-Risk Alerts
Given per-severity SLA thresholds are configured for time_to_triage, time_to_vendor_contact, and time_to_mitigation and a lead_time_minutes (default 15) is set, When elapsed time reaches (threshold − lead_time_minutes) without completion, Then an at-risk alert is sent to the incident assignee and property manager via in-app and email/SMS (if enabled). When a threshold is exceeded without completion, Then a breach alert is sent and, if outside business hours, escalated to on-call per policy with an on_call.escalation.triggered event appended. Then each alert includes incident_id, property, severity, metric_name, elapsed_time, threshold, and a deep link to the incident. Given alert suppression is enabled, When the condition persists, Then repeat alerts for the same metric are suppressed for 30 minutes.
Timeline Export to CSV and PDF
Given a user selects a date range and optional filters (property, vendor, severity) and clicks Export CSV, Then a CSV downloads within 10 seconds containing one row per event with columns: incident_id, property_id, vendor_id, severity, incident_created_at_utc, event_id, event_timestamp_utc, event_type, actor_type, actor_id, event_summary. Given a user selects Export PDF for a single incident, Then a PDF generates within 10 seconds summarizing SLA metrics and rendering the full ordered timeline with timestamps in the user's timezone and a UTC legend. Then export files use the naming pattern fixflow_audit_{range_or_incident}_{timestampUTC}.{csv|pdf}. Then exported event counts and metric values match those shown in the dashboard and incident view exactly.
Dashboard Filtering and Compliance Review
Given a user opens the SLA dashboard, When filters for property (multi-select), vendor (multi-select), severity (multi-select), and date range are applied in any combination, Then the list and aggregate metrics refresh within 2 seconds for up to 10,000 incidents. Then aggregates display average, median, 90th percentile, and breach rate for each metric grouped by severity. When a row is clicked, Then the user navigates to the incident timeline view for drilldown. When filters are cleared, Then the dashboard reverts to unfiltered totals.
After-Hours Escalation Logging
Given business hours are configured for a property and an incident is created outside those hours, When Severity Guard flags Emergency or an assignee marks the incident urgent, Then an on_call.escalation.triggered event is appended and the first on-call vendor is notified, recording vendor.notification.sent. When the on-call vendor accepts or declines, Then vendor.accepted or vendor.declined events are appended with timestamps and actor_id; if declined, Then the next on-call vendor notification is recorded with preserved sequence. When an incident is deemed non-critical after-hours, Then a defer_to_business_hours event is appended and vendor notifications are scheduled for the next business window.
Timestamp and Ordering Consistency
Then all event timestamps are stored in UTC using ISO 8601 (Z) and displayed in the viewer's local timezone with a timezone toggle available. Given two events share the same second-level timestamp, Then ordering is deterministic by event_id ascending. Given client clock skew, When events arrive with future client timestamps, Then the server assigns authoritative received_at for event_timestamp and stores client_timestamp in metadata; ordering uses event_timestamp. Then the incident timeline supports pagination and infinite scroll without re-ordering artifacts across page boundaries.
Admin Policy and Threshold Configuration
"As an admin, I want to configure thresholds, on-call rotations, and routing rules so that Severity Guard aligns with our policies across different properties."
Description

Administrative UI to configure severity thresholds, scoring weights, auto-escalation criteria, on-call rotations, quiet hours, holiday calendars, and property-specific safety notes. Includes a test harness to simulate scores and routing using sample incidents, preview outcomes, and compare against historical cases. Enforces role-based access control and records change history with rollbacks to ensure safe operations.

Acceptance Criteria
Configure Severity Thresholds and Scoring Weights
Given a user with role Owner or Admin is on the Severity Settings form When they define severity bands with numeric bounds between 0 and 100 and no overlaps or gaps Then the form validates ranges inline and enables Save only when valid Given scoring weights are edited When the total weight equals 100% (±0.0%) Then Save is permitted; otherwise an error message identifies fields causing imbalance Given valid bands and weights When Save is clicked Then the system persists a new version (versionId increments), records userId and UTC timestamp, and shows a success confirmation within 1 second Given a sample incident is entered in the inline simulator When Recalculate is clicked Then a deterministic risk score (0–100), severity band, and per-factor contribution breakdown render within 2 seconds p95 using the current in-edit (unsaved) values Given a successful save When the decision engine is polled for active policy Then the new version becomes active within 60 seconds and is reported as the active versionId
Define Auto‑Escalation, Quiet Hours, and Holiday Calendar
Given an Owner or Admin configures quiet hours with a specific timezone When they add intervals Then overlapping or invalid intervals are rejected with inline errors and all stored times are normalized to the selected timezone Given a holiday calendar is configured When a date is added or removed Then the change persists and is reflected in the simulator immediately after save Given non‑critical incidents are set to defer outside business hours When the simulator runs an incident timestamped during quiet hours or a holiday for the property's timezone Then the outcome shows Deferred with the next business window start time Given critical incidents are set to escalate after‑hours When the simulator runs an incident timestamped during quiet hours Then the outcome shows Escalate and displays the selected on‑call rotation and escalation delay Given settings are saved When the page is reloaded Then timezone, quiet hours, and holiday entries display exactly as configured
Manage On‑Call Rotations and Vendor Escalation Paths
Given an Owner or Admin creates an on‑call rotation When they add at least two participants with verified SMS numbers and define order and active flags Then Save is allowed only if at least one participant is active and no duplicate positions exist Given a rotation with weekly shifts and a start date When Preview is clicked Then the next 8 weeks display the primary assignee per day in the selected timezone Given an escalation rule of "notify next after X minutes" exists When a critical incident is simulated Then the simulator shows the notification sequence (primary → secondary → fallback) with computed timestamps per step Given the rotation is saved When another admin opens Change History Then the creation event records participants, schedule parameters, userId, UTC timestamp, and versionId
Maintain Property‑Specific Safety Notes and Visual Guides
Given an Owner or Admin is on a property's Safety Notes tab When they add a note with a title, at least one ordered step, and optional images ≤ 5 MB each with required alt text Then the note saves successfully and is associated to the property and selected hazard tags Given portfolio‑level default notes and property‑level notes both exist for the same hazard When viewing the property's notes Then the property note overrides the default and an Overridden badge with a Reset to Default action is visible Given the simulator runs for that property When an incident is tagged with a hazard that has notes Then the simulator displays the steps and images inline before routing within 1 second Given a safety note is edited When Save is clicked Then a new version is recorded and previous versions remain available for rollback in Change History
Simulate Incident Scoring and Routing with Historical Comparison
Given an Owner or Admin is on the Test Harness When they enter incident attributes and click Simulate Then the system returns risk score (0–100), severity band, routing decision (on‑call/vendor/deferred), and an explanation table within 2 seconds p95 Given up to 5 historical incidents are selected for comparison When Simulate is clicked Then side‑by‑side results show scores, routes, and a delta summary highlighting changed outcomes from current policy versus historical outcome Given the policy has unsaved edits When Simulate is clicked Then the run is labeled Using Unsaved Draft and uses the draft parameters for computation Given a historical incident is selected When Use Historical Inputs is clicked Then the input form pre‑populates from the historical record and fields remain editable prior to simulation
RBAC, Change History, and Safe Rollback
Given a user with role Owner or Admin When they access Admin Policy screens and APIs Then they can view and edit settings successfully (HTTP 200 on write operations) Given a user without Owner/Admin role When they attempt to edit any policy via UI or API Then the UI blocks edits and APIs return HTTP 403 with no side effects Given any change is saved When viewing Change History Then each entry shows userId, UTC timestamp, versionId, and field‑level diffs for all modified settings Given a prior version is selected for rollback When the user confirms Rollback Then a new version is created restoring those values, the rollback event is logged, and the active policy updates within 60 seconds Given two admins edit concurrently When the second admin attempts to save an outdated version Then the save is rejected with a conflict message and an option to reload latest changes
Per‑Property Overrides and Policy Inheritance
Given portfolio‑level defaults exist When a new property is created Then it inherits all default policy values with zero overrides Given a property has overridden a setting When Reset to Default is clicked for that field Then the field value reverts to the portfolio default and an audit entry is recorded Given multiple properties (up to 200) are selected When Bulk Apply Defaults is confirmed Then defaults are applied to all selected within 10 seconds p95 and any failures are reported per property Given a property with overrides is opened When the settings screen loads Then overridden fields are clearly labeled and the simulator reflects the effective (post‑inheritance) values
Calendar Hold and Double-Booking Guard
"As a dispatcher, I want emergency escalations to reserve vendor time without conflicts so that we avoid double-bookings during critical incidents."
Description

Atomic reservation of vendor time slots during emergency escalation with conflict detection, automatic hold expiration if not accepted within SLA, and one-click reassignment. Syncs final accepts to the shared calendar, respects vendor availability buffers, and prevents double-bookings across teams and channels. Provides visibility into holds and reasons within the incident ticket.

Acceptance Criteria
Atomic Hold Placement During Emergency Escalation
- Given an incident marked "Emergency" and a vendor with an available slot at time T, When escalation is triggered, Then the system creates an atomic hold on the selected slot within 1 second and records hold ID, vendor, start/end, and SLA expiry in the incident ticket. - Given concurrent escalation attempts targeting the same vendor and time window from different channels or users, When holds are attempted, Then only one hold succeeds and all other attempts receive a conflict error with no partial reservations created. - Given a hold is created, When querying availability across all scheduling surfaces (dashboard, SMS link, API), Then the held slot is excluded from availability and shown as "On Hold — FixFlow" in the shared calendar feed.
Double-Booking Guard Across Teams and Channels
- Given any attempt to create a hold or booking, When an existing booking or hold (internal or external) overlaps the requested window (including buffer windows), Then the system blocks the action and returns conflict details (source, event ID, overlap minutes). - Given holds/bookings created by any team or channel (dashboard, SMS, API), When a new hold is attempted on the same resource and time, Then conflict detection executes before hold creation and prevents double-booking in all calendars. - Given simultaneous requests across services, When processed, Then resource-time locking prevents overlapping holds/bookings and guarantees consistency under race conditions.
SLA-Based Hold Expiration and Auto-Release
- Given a hold with an SLA response window (e.g., 10 minutes), When the SLA expires without vendor acceptance, Then the hold auto-expires and the slot is released across calendars within 5 seconds, with notifications sent to the incident and vendor as configured. - Given a hold expires, When viewing the incident ticket, Then the hold is labeled "Expired" with reason "SLA timeout" and includes start/end, created/expired timestamps, and actor/system identifiers in the audit log. - Given a service restart occurs during an active hold, When services resume, Then expiration timers are recovered and applied without extending the SLA by more than 5 seconds.
One-Click Reassignment from Incident Ticket
- Given an active hold awaiting acceptance, When the user clicks "Reassign" and selects an alternative vendor and slot, Then the original hold is released and the new hold is created atomically with no overlap where both holds are active, and all changes are logged. - Given reassignment succeeds, When notifications are sent, Then the new vendor receives an acceptance prompt (SMS/app/API) and the prior vendor receives a release notice; the incident timeline captures both events. - Given reassignment fails due to a conflict on the new selection, When the operation completes, Then the original hold remains active, the user sees a clear error with next-available options, and no partial changes persist.
Final Acceptance and Calendar Sync
- Given a vendor accepts a hold within the SLA via SMS, app, or API, When acceptance is recorded, Then the provisional hold converts to a confirmed booking with a unique event ID, and the shared calendar reflects the confirmed event while removing any provisional markers. - Given intermittent calendar API failures, When syncing the confirmed booking, Then the system retries with exponential backoff up to 3 times using an idempotency key so no duplicate events are created; booking remains in "Pending Sync" until success. - Given a confirmed booking exists, When any availability view is requested, Then the booked slot is unavailable and pre/post buffers are enforced consistently across all channels.
Hold Visibility and Reason Codes in Incident Ticket
- Given an incident has one or more holds, When viewing the incident ticket, Then the Holds panel lists each hold with status (Active, Released, Expired, Confirmed), reason code (Escalation, Reassignment, Conflict, Timeout), vendor, start/end, SLA expiry, and hold ID. - Given any hold state change occurs, When it happens, Then an immutable audit entry is added to the incident timeline with actor, action, before/after states, timestamps, and correlation IDs. - Given an API client with incident.read scope, When requesting the incident holds endpoint, Then the API returns the same fields and statuses as the UI within 200 ms at the 95th percentile.
Vendor Availability Buffers and Duration Enforcement
- Given vendor buffer rules (e.g., 15 minutes before/after) and a job duration estimate, When calculating eligible slots for a hold, Then only windows satisfying duration plus buffers are offered; attempts to hold shorter windows are rejected with a specific buffer violation message. - Given an existing booking ending at time T, When attempting to hold a new job starting at T minus the vendor's buffer, Then the system blocks the hold due to buffer overlap and surfaces the earliest valid start time. - Given buffer rules are updated for a vendor, When new holds are created, Then they must comply with the updated buffers; existing holds/bookings are not auto-cancelled, and admins receive an alert if any current holds would violate the new rules were they created now.

Model Match

OCR reads appliance and equipment labels to auto-capture make/model/serial, attach relevant manual snippets, and suggest likely parts or tools. Vendors arrive prepared, first-time fix rates climb, and managers avoid costly second visits due to missing information.

Requirements

Label OCR Capture
"As a tenant or vendor, I want to snap a photo of the equipment label and have make, model, and serial captured automatically so that the work order has accurate details without manual typing."
Description

Enable users to capture or upload equipment label photos and automatically extract make, model, and serial numbers using OCR. Support common label layouts for appliances and HVAC systems with multi-image capture, auto-cropping, and perspective correction. Provide real-time image quality feedback (blur, glare, cutoff) and prompt retakes. Persist original images and parsed fields on the linked FixFlow work order, expose field-level confidence scores, and allow quick manual correction with validation. Ensure data flows to downstream steps (manual snippets, parts suggestions, prep pack) and respect privacy by redacting sensitive PII where present.

Acceptance Criteria
Real-Time Photo Quality Feedback on Capture
Given I am capturing an equipment label photo in FixFlow When the camera shutter fires or an image is selected Then the app analyzes blur (variance of Laplacian >= 150), glare (specular highlight area <= 5% of label region), and cutoff (label fully inside frame with >= 5% margin) within 500 ms And shows specific inline warnings for any failing check And disables Continue until at least one image in the set passes all quality checks And provides a one-tap Retake option
Multi-Image Capture with Auto-Crop and Perspective Correction
Given some equipment labels require multiple shots When I add 1–5 images for the same label Then the system auto-detects the label region, auto-crops to it, and applies perspective correction (<= 2° residual skew, <= 3% aspect distortion) And allows me to reorder or delete images prior to submission And retains both original and corrected images linked to the work order upon submission
OCR Field Extraction for Make, Model, and Serial
Given supported appliance and HVAC label layouts When OCR runs on the captured images Then the system outputs fields: make, model, serial with per-field confidence scores in [0,1] And normalizes model and serial (trim, uppercase A–Z0–9, collapse hyphen/space variants) And on a curated QA set of 50 labels, achieves >= 95% precision for make, >= 92% for model, >= 90% for serial; processing time <= 7 s per image at p95
Manual Review and Correction with Validation
Given OCR results are displayed to the user When I edit any field Then inline validation enforces: make 2–50 chars (A–Z0–9 and spaces), model 2–50 chars (A–Z0–9, -, /), serial 4–30 chars (A–Z0–9) And invalid input shows an error and disables Save And on Save, changes persist to the work order with editedBy and editedAt metadata, flagged as user_edited, with the original OCR value retained in the audit log
Persistence on Work Order and API Exposure
Given OCR has completed or manual corrections are saved When I open the linked work order or retrieve it via API Then I can view original images (immutable), corrected crops, and fields (make, model, serial, per-field confidence, user_edited flags) And an audit log records create/update events for images and fields with timestamp and actor And data persists across refresh and is available to other FixFlow modules
Downstream Integration to Model Match Suggestions
Given a work order has extracted or corrected fields When the Model Match pipeline runs Then relevant manual snippets are attached based on make/model with a link to source And parts and tool suggestions are generated; if any field is missing or confidence < 0.7, suggestions are marked low confidence and still shown And the Prep Pack view displays the suggestions within 5 seconds of OCR completion
Sensitive PII Redaction
Given images or OCR text may include sensitive PII unrelated to equipment identification (phone numbers, emails, street addresses) When images are stored and displayed Then all derived images and text shown in UI/API have detected PII redacted (mask or blur) with >= 95% detection recall on a QA set of 100 mixed samples And unredacted originals are stored securely and accessible only to Admin via time-limited access; downstream modules receive only redacted versions And redaction never removes or alters equipment make, model, or serial fields
Model Normalization & Verification
"As a property manager, I want normalized and verified equipment details with a simple review step so that I can trust the data and avoid sending vendors with the wrong information."
Description

Normalize extracted brand, model, and serial into canonical formats and verify against reference datasets to reduce ambiguity and duplicates. Implement brand aliasing, model pattern rules, and serial format checks with a confidence threshold that triggers a lightweight review step. Provide a "Confirm model" action in the work order to accept suggestions, pick alternates, or correct fields. Maintain a mapping table for learned aliases and store provenance (image, rule, manual edit) for auditability. Expose a clean, API-ready model object for use by manual retrieval and parts suggestion services.

Acceptance Criteria
Canonical Brand and Model Normalization
Given an OCR extraction with brand "Whirlpool Corp." and model " wrx735sdbm 02 " When normalization runs Then brand equals "Whirlpool" per alias table and model equals "WRX735SDBM02" per model rules Given mixed-case or punctuation in brand/model When normalization runs Then output uses uppercase model, trimmed whitespace, and removes unsupported characters per brand rules Given no alias match exists for extracted brand When normalization runs Then brand remains as extracted and normalization confidence is <= 0.60 and review flag is set
Reference Verification and Confidence Threshold
Given normalized brand, model, and serial When verified against reference datasets Then a confidence score in [0.0,1.0] is produced and the top 3 matches are available Given score >= 0.85 When verification completes Then the work order shows status "Verified" and fields are auto-filled without review flag Given 0.50 <= score < 0.85 When verification completes Then the work order is flagged "Needs Review" and alternates are displayed Given score < 0.50 When verification completes Then fields are not auto-filled and user confirmation is required before save
Work Order Confirm Model Action
Given a work order with model suggestions When the user clicks "Confirm model" Then selected brand/model/serial are saved and locked and provenance is "manual-confirm" Given the user picks an alternate suggestion When saved Then provenance is "manual-select" and audit trail records before/after values with user and timestamp Given the user edits any field manually When saved Then provenance is "manual-edit", validation rules re-run, and confidence is set to 1.00 Given an invalid value is entered When saving Then the save is blocked and field-level errors indicate the violated rule
Learned Alias Mapping and Application
Given a user changes brand from "GE Appliances" to "GE" and selects "Save as alias" When saved Then an alias record is created with case-insensitive key "ge appliances" mapped to canonical "GE" with createdBy and createdAt Given a future OCR returns "G.E. Appliances" When normalization runs Then the alias is applied and brand becomes "GE" with provenance "alias:manual" Given a new alias conflicts with an existing alias mapped to a different canonical brand When saving Then the system blocks the save and prompts to merge or cancel
Serial Format Validation and Misclassification Prevention
Given brand "GE" with serial regex "^[A-Z]{2}[0-9]{6}$" and extracted serial "AB123456" When validation runs Then serial is marked valid Given brand "GE" and serial "A1234567" When validation runs Then serial is marked invalid, confidence reduced below 0.85, and the work order is flagged for review Given a string matches a model pattern but not the serial pattern When classification runs Then the value is assigned to model and the serial field remains blank
API-Ready Model Object Exposure and Versioning
Given a verified or confirmed model exists When GET /api/models/{id} is called Then response is 200 with JSON including brand, model, serial, confidence, provenance, sourceImageId, normalizedAt, and version per OpenAPI schema Given concurrent edits When PUT /api/models/{id} is called with a stale If-Match ETag Then response is 412 Precondition Failed Given 1000 consecutive GET requests When measured Then p95 latency <= 200 ms and error rate < 0.1%
Duplicate Detection and De-duplication
Given a unit has an existing model object with brand "Whirlpool", model "WRX735SDBM02", serial "KX123456" within 30 days When a new extraction normalizes to the same triple Then no new model object is created and the work order links to the existing object Given the new extraction differs only by alias or formatting When normalization runs Then the same canonical triple is computed and de-dup links to the existing object Given a different serial for the same brand/model When processed Then a new model object is created and a potential duplicate banner suggests review
Manual Snippet Attachment
"As a vendor, I want the key manual pages attached to the ticket so that I can quickly understand the unit and troubleshoot on-site without wasting time searching."
Description

Automatically find and attach the most relevant manual pages for the identified make/model, prioritizing troubleshooting guides, wiring diagrams, and parts diagrams. Use licensed sources and cached repositories to ensure fast retrieval and compliance. Highlight relevant sections/pages, support full-text search within manuals, and display them inline on the work order and in the vendor mobile view. Fallback gracefully when manuals are unavailable by attaching generic guidance or requesting user upload. Cache results per model to minimize repeated lookups and ensure offline availability in the field.

Acceptance Criteria
Licensed Source Retrieval and Compliance Logging
Given a make and model have been identified When manuals are searched Then only manuals from whitelisted licensed sources or the organization’s licensed cache are attached And then each attached manual records source metadata including publisher, license type, and retrieval timestamp And then if only unlicensed sources are found, no manual is attached and the fallback flow is triggered And then an audit log entry is created for every retrieval attempt with source validation outcome
Relevance Ranking and Page Selection
Given a valid make and model When manual content is retrieved Then the system attaches 3–10 pages prioritized in this order: troubleshooting guide pages, wiring diagrams, parts diagrams And then if a category is unavailable for that model, it is skipped without blocking other categories And then for a validation set of representative models, at least 90% include at least one page from each available category and the first page is troubleshooting when available And then duplicate or near-duplicate pages (≥95% similarity) are not attached
Highlighting of Relevant Sections
Given manual pages are attached When pages are displayed Then matched terms such as the exact model number, common error codes, and component names are highlighted with non-destructive overlays And then at least one highlight is present on 95% of attached pages that contain a textual match And then users can toggle highlights on or off and view a legend of highlight types And then selecting a highlight scrolls or zooms to that region on desktop and mobile
Inline Manual Viewer in Work Order and Vendor Mobile
Given a work order with attached manual pages When opened on desktop Then an inline viewer renders pages with pagination, zoom, and rotate controls And then when opened in the vendor mobile view (iOS Safari and Android Chrome) Then the viewer renders responsively with touch gestures and no horizontal scrolling at 375px width And then the first page loads within 2 seconds at P95 from cache and within 5 seconds at P95 from network on a 4G connection And then the viewer provides a Download full manual action when a complete PDF is available
Full-Text Search Within Manuals
Given attached manual content is indexed When a user submits a query in the Search manuals field Then results show page numbers and text snippets with hit highlighting And then phrase queries in quotes are supported and logical AND is the default operator And then selecting a result navigates directly to the corresponding page and highlight And then P95 query latency is ≤ 500 ms for cached content on desktop and mobile
Fallback When Manual Unavailable
Given no licensed manual is found within 10 seconds or none exist for the model When attachment is attempted Then the system attaches a generic guidance document for the equipment category (e.g., Gas furnace basics, Refrigerator troubleshooting) And then the user can trigger a Request upload action that sends an email/SMS with a secure upload link And then the work order displays a banner Manual unavailable—using generic guidance and a Retry lookup button And then the event is logged with a reason code (not_found, unlicensed, timeout)
Per-Model Caching and Offline Availability
Given manual pages have been attached for a model When another work order with the same make, model, and revision is opened Then cached pages and the search index are reused without re-downloading And then cache entries are available offline in the vendor mobile app And then a background sync refreshes stale cache entries at most once every 7 days or when the user taps Refresh And then if storage exceeds 250 MB, least-recently-used model caches are pruned while preserving assets for the current work order
Parts & Tools Suggestions
"As a vendor, I want a ranked checklist of likely parts and tools for this model and symptom so that I can bring what I need and maximize first-time fix success."
Description

Suggest likely replacement parts and required tools based on the confirmed model and the issue description in the work order. Combine a rules engine (common failure modes by model) with optional ML ranking to output a prioritized checklist with confidence levels. Cross-reference suggested parts with preferred suppliers, lead times, and cost, and surface quick-add links to purchase or reserve. Allow vendors to accept, adjust, or dismiss suggestions, and persist selections on the work order. Integrate with inventory lists or saved vendor kits to flag what is already on-hand.

Acceptance Criteria
Prioritized Parts & Tools Checklist
Given a work order with a confirmed make/model/serial and a free-text issue description When the system generates suggestions Then it produces a single prioritized checklist combining parts and tools, sorted by descending confidence score And each checklist item includes: itemType (part|tool), displayName, optional SKU/partNumber, confidenceScore (0–100), rationale (<=200 chars), and source (rules|ml|hybrid) And at least 3 suggestions are returned when the knowledge base contains model-specific matches; otherwise a "No suggestions found" state is shown And the top suggestion has a confidenceScore >= the second item's score, with no ties unless scores are equal And the checklist displays a lastUpdated timestamp in the user's timezone
Rules + ML Ranking with Fallback
Given rules engine and ML ranking are both available When generating suggestions Then ranking uses ML probabilities to order items and rules-derived priority as a tie-breaker Given the ML service is unavailable or times out (>800 ms per request) When generating suggestions Then the system falls back to rules-only ranking, logs the incident, and still renders a checklist Given identical inputs (model, issue, catalog) within a 24-hour window When generating suggestions twice Then the ordered results are deterministic and identical Given an administrator has disabled ML ranking in settings When generating suggestions Then rules-only ranking is used
Supplier Cross-Reference and Quick-Add
Given preferred suppliers are configured with credentials and catalogs When suggestions include a part with known SKU or normalized name Then each part shows up to 3 preferred supplier offers with price, leadTimeDays, stockStatus (in-stock|backorder), and totalCost (price + shipping if available) And a default supplier is selected based on lowest totalCost then shortest leadTimeDays And each offer includes a Quick Add action that opens a prefilled purchase or reservation flow and records the association to the work order Given no preferred supplier match is found When rendering the part Then a "No preferred supplier" message and "Request quote" CTA are displayed All supplier lookups complete within 5 seconds P95 end-to-end; if exceeded, partial results render with a timeout notice
Vendor Acceptance, Adjustment, Dismissal, and Persistence
Given the assigned vendor or a manager is viewing the work order When they Accept a suggested item Then the item is marked Accepted with quantity (default 1), user, timestamp, and pinned to the work order When they Adjust quantity or edit notes on an item Then changes are saved immediately and persist after page reload When they Dismiss an item Then the item is hidden from the default checklist view and moved to a Dismissed list with undo available for 10 minutes Only assigned vendors and managers can modify selections; other roles are read-only An audit log entry is created for each Accept, Adjust, or Dismiss action
Inventory and Vendor Kits On-Hand Flagging
Given organization inventory and vendor kit lists are connected and synced within the last 24 hours When rendering suggestions Then items already on-hand are flagged On-hand with location and availableQuantity And if availableQuantity >= selectedQuantity Then the Purchase/Reserve quick-add is disabled by default and a "Use On-hand" action is highlighted And if availableQuantity < selectedQuantity Then the Quick Add action defaults to purchasing the shortfall quantity When the work order status changes to In Progress and the item is Accepted Then inventory is soft-allocated immediately and decremented on status Done Concurrent edits on the same item result in the second update failing with a clear conflict message and a retry option
Performance, UX, and Access Control
Given a work order with confirmed model and issue description When opening the work order Suggestions tab Then initial suggestions render within 3 seconds at P95 and 1 second at P50; a loading skeleton is shown until render If suggestions cannot be produced within 5 seconds Then a retry control is shown without blocking the rest of the page The UI provides filters for All, Parts, Tools, and On-hand Only, and a search field that filters results in under 200 ms at P95 All actionable controls have accessible names and are operable via keyboard (Tab/Enter/Space) and meet WCAG 2.1 AA color contrast Tenants never see pricing, supplier links, or inventory locations; vendors see pricing but not organization-wide inventory counts beyond their kits; managers see all fields
Vendor Prep Pack Delivery
"As a vendor, I want a single prep pack link sent before the appointment so that I have all critical info in one place and can arrive fully prepared."
Description

Assemble a concise prep pack that includes extracted details (make/model/serial), appointment time and location, manual snippets, and the accepted parts/tools checklist. Deliver via secure, tokenized mobile-friendly link through SMS and email aligned with FixFlow’s scheduling notifications. Track opens and clicks, support one-tap acknowledgment, and allow managers to resend or update if the appointment changes. Ensure access control, link expiry, and offline viewing of cached content in the field.

Acceptance Criteria
Prep Pack Assembly Completeness
Given a scheduled work order with OCR-captured make/model/serial and a manager-approved parts/tools checklist When a prep pack is generated Then the pack includes make, model, serial, appointment date/time, location, relevant manual snippet(s) for the captured model, and the accepted parts/tools checklist And each included field matches the source record values exactly; missing OCR fields are labeled "Not Captured" And manual snippet references include source document title and page/section number
Notification Delivery Alignment and Secure Access
Given an appointment is created, confirmed, or rescheduled for an assigned vendor When scheduling notifications are sent Then both SMS and email to the vendor include a single-use, tokenized HTTPS link to the prep pack And the token authorizes only the assigned vendor and is bound to the appointment and prep pack version And attempts with a missing/invalid/expired token return HTTP 403 with no prep content And delivery events (message ID, channel, timestamp) are logged for both SMS and email
Mobile-Friendly Rendering and Performance
Given the vendor opens the prep pack link on a mobile device When the viewport width is between 320px and 480px on a simulated 4G network Then the page renders without horizontal scrolling, text is readable at 16px+ base font, and primary actions have 48px+ touch targets And First Contentful Paint occurs within 2 seconds for a 1 MB manual snippet payload And content meets WCAG 2.1 AA contrast requirements
Open and Click Tracking Analytics
Given the vendor receives the SMS and email with the prep pack link When the link is opened, the acknowledgment is tapped, or a manual snippet is expanded/downloaded Then an event is recorded with vendor ID, appointment ID, channel, device type, timestamp, and prep pack version And manager-facing analytics show open/click/ack status within 60 seconds of the event And duplicate events from the same device within 5 minutes are de-duplicated in reporting
One-Tap Vendor Acknowledgment
Given the vendor has accessed the prep pack When the vendor taps the Acknowledge button Then the system records acknowledgment with timestamp and user agent and associates it to the appointment and vendor And the button state changes to Acknowledged and prevents repeat submissions And the manager receives a notification and the work order activity log is updated
Resend and Update on Appointment Changes
Given the prep pack was already delivered When the appointment time, location, or parts/tools checklist is changed and saved Then a new prep pack version is generated with an updated tokenized link and is sent via SMS and email And the prior link is invalidated with a message directing the vendor to the newest link And managers can trigger a manual resend from the work order details and see a timestamped send history
Offline Viewing of Cached Content
Given the vendor has opened the prep pack at least once while online When the vendor opens the link without connectivity Then the last-synced prep pack (details, manual snippets, checklist) is available for offline viewing And an offline banner shows the last sync time and a Retry action And any acknowledgment made offline is queued and auto-submitted when connectivity is restored
Model Match Analytics & Audit
"As a property manager, I want clear metrics and an audit trail for Model Match so that I can quantify impact, identify gaps, and justify continued use."
Description

Provide an analytics dashboard and export that attribute outcomes to Model Match, including OCR success rate, average confidence, manual edit rate, prep pack open rate, first-time fix rate, second-visit reduction, and time-to-resolution improvements. Maintain an audit trail of OCR outputs, normalization steps, user edits, and suggestion accept/dismiss actions for each work order. Enable filtering by property, vendor, model family, and timeframe to demonstrate ROI and guide continuous improvement.

Acceptance Criteria
Core Metrics Visibility and Accuracy
Given a user with Analytics permission and a selected timeframe When the Model Match analytics dashboard loads Then the following metrics are displayed with numeric values: OCR success rate, average OCR confidence, manual edit rate, prep pack open rate, first-time fix rate, second-visit rate and reduction vs baseline, average time-to-resolution and improvement vs baseline And each metric shows its numerator and denominator on hover or detail view And values are rounded to 1 decimal place for percentages and to whole minutes for time metrics Given a seeded dataset with known outcomes When metrics are computed Then each metric matches the expected value within 0.1 percentage points or 1 minute for time metrics Given there are zero qualifying work orders in the selection When the dashboard renders Then all affected metrics display a clear No data state without errors or divide-by-zero artifacts
Filter by Property, Vendor, Model Family, and Timeframe
Given filters for property, vendor, model family, and timeframe are available When a user selects multiple values within a single filter type Then the filter applies as an OR within that type Given multiple different filter types are selected simultaneously When results are computed Then the filters apply as an AND across types Given no filters are set When the dashboard first loads Then the timeframe defaults to Last 30 days and properties default to All within the user's access scope Given a filter state is applied When the user refreshes the page or shares the URL Then the identical filter state is restored from URL parameters Given a new filter is applied When the server responds Then all visible metrics update within 500 ms of the response and show the active filter chips
Analytics Export Reflects Dashboard and Filters
Given a current filter state on the analytics dashboard When the user triggers Export CSV Then a CSV file is generated within 20 seconds for up to 10,000 rows And the file name includes the date/time and a hash of the filter state And the first row contains headers Given the exported CSV When inspected Then it contains per-work-order rows with at least the following columns: work_order_id, property_id, property_name, vendor_id, vendor_name, model_make, model_model, model_family, model_match_used (boolean), ocr_success (boolean), ocr_avg_confidence (0–100), manual_edit (boolean), prep_pack_opened (boolean), first_time_fix (boolean), second_visit (boolean), time_to_resolution_minutes (integer), created_at (UTC ISO-8601), closed_at (UTC ISO-8601) Given the same filters used for export When aggregating the CSV data to compute dashboard metrics Then the results match the dashboard values exactly Given the user’s timezone setting When the export is generated Then date/time columns are in UTC with offset noted in the file metadata section or header comment, and the UI indicates this
Per-Work-Order Audit Trail Completeness and Integrity
Given a work order that used Model Match When viewing its audit log Then entries exist for each of the following event types with timestamps (UTC), actor (system/user id), before_value, and after_value: OCR raw output captured, normalization steps applied, user edits to make/model/serial, suggestion accepted, suggestion dismissed Given the audit log endpoint When attempting to modify or delete an existing audit entry Then the system rejects the operation and returns 403 Forbidden, preserving immutability Given the audit log UI When filtering by event type or time range Then only matching entries are displayed and the total count updates accordingly Given API access with the work_order_id When requesting the audit log Then results are paginated, ordered by timestamp ascending, and include a stable cursor or page token
Attribution of Outcomes to Model Match Usage
Given the current filter state When the dashboard renders attribution Then it displays side-by-side metrics segmented by Model Match Used = Yes vs No for first-time fix rate, second-visit rate, and median time-to-resolution And it displays deltas: uplift in first-time fix (percentage points), reduction in second-visit rate (percentage points), and improvement in median time-to-resolution (minutes) And it shows sample sizes (n) for each segment Given segment sample size n < 30 for either Yes or No When computing deltas Then the delta is replaced with Insufficient data and no uplift/reduction is shown Given canceled or duplicate work orders in the timeframe When computing denominators Then those orders are excluded consistently from both segments
Prep Pack Open Rate Tracking and Accuracy
Given a prep pack link is sent via SMS for a work order When the vendor opens the link Then a unique open event is recorded with timestamp (UTC), vendor_id, work_order_id, and device fingerprint Given multiple opens by the same vendor for the same work order When computing open rate Then only one unique open is counted per vendor per work order Given prep pack messages fail delivery or SMS consent is missing When computing open rate Then undelivered or non-consented messages are excluded from the denominator Given the analytics dashboard When displaying prep pack open rate by vendor and property Then values match aggregates derived from underlying open and delivery events
Data Freshness and Performance SLAs
Given new qualifying events occur (OCR result saved, user edit made, work order closed) When observing the analytics dashboard Then metrics reflect these events within 15 minutes Given a portfolio with up to 1,000 work orders in the selected timeframe When loading the analytics dashboard Then the summary view renders in under 2 seconds at the 95th percentile Given a portfolio with up to 10,000 work orders in the selected timeframe When loading the analytics dashboard Then the summary view renders in under 4 seconds at the 95th percentile Given an export request producing up to 10,000 rows When the export runs Then the file is delivered in under 20 seconds at the 95th percentile

Duplicate Shield

Image and text similarity checks detect duplicate or near-duplicate submissions from the same unit or building, merging them into one work order and notifying tenants with the current status. This cuts noise, prevents double-bookings, and keeps calendars clean.

Requirements

Unit-aware Similarity Detection Engine
"As a property manager, I want the system to automatically detect duplicate requests from the same unit so that I avoid noise and handle each issue once."
Description

Implement a real-time service that calculates text and image similarity for incoming maintenance requests to detect duplicate or near-duplicate submissions originating from the same unit or building. Use normalized text preprocessing, semantic embeddings for multilingual support, fuzzy matching on categories and location, time-window constraints, and perceptual image hashing with lightweight OCR on photos to correlate visuals. Support configurable similarity thresholds and exclusion rules by property, adjustable time windows (e.g., 30 days), and performance targets under 200 ms per check at P95. Provide deterministic IDs for duplicate clusters and structured match explanations for auditability. Integrate with the FixFlow ingestion pipeline with idempotent processing and retry policies.

Acceptance Criteria
Real-time Unit-Scoped Text Similarity Detection
Given a new maintenance request for unit U with text T, category C, and location L And an existing open work order in unit U within the configured time window W When the engine normalizes T, computes semantic embedding similarity, and applies fuzzy matching on C and L Then the combined similarity score is calculated using the property's configured weights and thresholds And if combined_score >= property.threshold_duplicate, the request is classified as Duplicate of the existing work order And if property.threshold_near_duplicate <= combined_score < property.threshold_duplicate, the request is classified as Near-Duplicate And if any configured exclusion rule matches (e.g., excluded categories or keyword patterns), the request is classified as Unique regardless of score And the decision includes the scope = unit
Multilingual Semantic Matching Within Same Unit or Building
Given an existing open work order described in language L1 for unit U (and building B) And a new maintenance request describing the same issue in language L2 for unit U or building B When the engine computes multilingual embedding similarity on normalized text Then if unit matches and cosine_similarity >= unit_scope_threshold, the new request is classified as Duplicate of the existing work order And if unit is missing but building matches and cosine_similarity >= building_scope_threshold with supporting category/location similarity >= min_fuzzy_threshold, classify as Duplicate at building scope And the match explanation includes detected languages L1, L2 and the embedding similarity score used in the decision
Perceptual Image Hash and OCR Correlation
Given a new request containing photo(s) P and optional text T for unit U (or building B) And an existing open work order in the same scope within time window W with photo(s) Q When the engine computes perceptual hashes for P and Q Then if min_hamming_distance(P, Q) <= property.image_hash_threshold, classify as Duplicate And if min_hamming_distance(P, Q) > property.image_hash_threshold but OCR(P) ∪ T has text similarity >= property.text_image_combo_threshold, classify as Near-Duplicate And the explanation records pHash values (redacted), Hamming distances, OCR snippets, and contributing scores
Configurable Time Window Enforcement
Given property P with duplicate_detection_window_days = 30 And an existing work order for unit U closed 45 days ago with similar content When a new similar request for unit U arrives Then the engine does not classify it as Duplicate due to window expiration And when duplicate_detection_window_days is updated to 60 and the same new request is evaluated Then the engine classifies it according to similarity thresholds (Duplicate or Near-Duplicate) within the 60-day window And the explanation includes the effective time window and relevant timestamps
Performance P95 <= 200 ms per Check
Given a load test with at least 10,000 requests representing production text+image mix on standard production hardware When the engine processes incoming requests from ingestion to classification decision Then the end-to-end duplicate check latency per request has P95 <= 200 ms And the latency measurement report includes sample size, timestamp, and hardware profile
Deterministic Cluster IDs and Idempotent Processing with Retries
Given two identical detection invocations for the same request_id due to transient failures and retries When the engine processes the invocations Then the same deterministic cluster_id is produced for the same duplicate set, and no additional clusters are created And all side effects (cluster creation, linkages) occur at most once (idempotent) And given the same unordered set of member requests, the cluster_id remains stable across runs And on transient errors, the ingestion pipeline retries up to R times with exponential backoff without changing the eventual outcome
Structured Match Explanation and Audit Trail
Given a classification outcome of Duplicate, Near-Duplicate, or Unique When the result is returned to the ingestion pipeline Then the response includes a structured explanation object with fields: matched_scope (unit|building), scores {text, category, location, image, ocr, combined}, thresholds_used, time_window, rules_triggered, matched_request_ids, cluster_id, decision And the explanation is persisted to the audit log with a deterministic correlation_id and timestamp And explanations are retrievable by cluster_id and request_id via internal API
Automatic Merge and Consolidation
"As a landlord, I want duplicates to be merged into one work order so that scheduling and follow-ups stay clean and accurate."
Description

When a duplicate is detected, automatically merge submissions into a single canonical work order while preserving all original content and authorship. Combine descriptions, categories, timestamps, and attachments; deduplicate identical photos; tag merged items with source request IDs; and maintain an immutable audit trail of merge actions with actor, time, and confidence score. Apply safe merging rules, with the earliest valid request as canonical, and support unmerge/reassign to recover from mistakes. Ensure all downstream references (links, notifications, billing) point to the canonical work order with exactly-once merge execution under concurrency.

Acceptance Criteria
Canonical Selection for Detected Duplicate Requests
Given multiple maintenance requests are detected as duplicates for the same unit/building When the merge is executed Then the earliest valid request by created_at is set as the canonical work order And all other requests are marked as merged_into the canonical work order with their request IDs recorded on the canonical And single-valued fields with conflicts retain the canonical value per safe merge rules And original created_at, submitter identities, and content of all source requests remain unchanged
Content and Attachment Consolidation with Deduplication
Given duplicate requests each contain descriptions, categories, timestamps, and attachments When the merge is executed Then the canonical work order description contains all unique text segments appended in chronological order with author attribution And the category list is the union of unique categories with the canonical’s original primary category preserved And a merged timeline includes timestamps of all submissions and updates from sources And attachments are merged while byte-identical photos (same SHA-256 hash) are stored once; each unique attachment preserves original uploader and timestamp And the total attachment count equals the number of unique attachments across sources
Immutable Audit Trail for Merge Actions
Given a merge operation completes Then an audit record is appended capturing: canonical_work_order_id, merged_source_request_ids, actor (user_id or system), timestamp, similarity confidence score, and merge rules applied And audit records are immutable and cannot be edited or deleted via UI or API (attempts return 403 and no data change) And audit records are viewable via audit endpoints and UI for authorized roles And the audit record references pre- and post-merge state identifiers sufficient to reconstruct history
Source Request ID Tagging and Retrieval
Given requests A and B are merged into canonical C When viewing C Then C displays A.id and B.id as source_request_ids And searching by A.id or B.id returns C as the single authoritative result And export/report endpoints include source_request_ids for C
Downstream References and Notifications Point to Canonical
Given a merge has created canonical work order C from sources A and B When new notifications, links, or billing artifacts are generated Then all references (URLs, SMS deep links, email links, billing line items) point to C And previously sent links to A or B redirect or resolve to C without error And billing aggregates charges under C without duplication And tenants associated with A and B receive a merge notification containing a link to C and its current status
Exactly-Once Merge Under Concurrency
Given two or more duplicate-detection events for the same request set occur concurrently When the merge operation is triggered by multiple workers or requests Then exactly one merge is committed and subsequent attempts return an idempotent success referencing the same canonical work order ID And A and B each have exactly one merged_into reference to the same canonical ID with no cycles And only one audit record is created for the merge And no partial or duplicate merges are observed under stress testing
Unmerge and Reassign Recovery
Given requests A and B were merged into canonical C and an admin initiates unmerge for B When the unmerge is executed Then B is restored as an independent work order with its original content, authorship, attachments, and timestamps intact And all downstream references that were repointed from B to C are updated to reference B again where applicable, while existing references to C remain valid And an audit record is appended for the unmerge with actor, timestamp, and reason And notifications inform affected tenants/vendors of the change with updated links And if unmerge is unsafe due to completed downstream actions (e.g., billed/closed), the system blocks unmerge and offers a reassignment flow to move items safely with audit
Tenant Status Notifications on Merge
"As a tenant, I want to be informed when my request is merged with an existing one so that I know the issue is being handled and don't submit again."
Description

Notify all tenants who submitted a request that was merged into an existing work order via SMS and email with current status, scheduled appointment details, and a link to follow updates. Support message templates per property, localization, quiet hours, and opt-out preferences. Include clear guidance that no further action is required and how to add additional details or photos to the canonical work order. Track delivery and read receipts and surface failures for operational follow-up.

Acceptance Criteria
Notify All Submitters on Merge
Given one or more maintenance requests from the same unit or building are merged into an existing canonical work order And each merged request has a tenant submitter with at least one contact method on file When the merge action is saved Then the system sends a notification within 2 minutes per tenant submitter via all eligible channels And the message includes: property name, work order ID, current status, scheduled appointment date, time window, vendor name (if available), and a unique link to follow updates And the message body contains explicit text that no further action is required and instructions for adding additional details/photos via the link And duplicate messages are not sent to the same tenant even if they submitted multiple merged requests And an audit log entry is created per tenant with timestamp and channels used
Channel Selection, Opt-Out, and Fallback
Given tenants have channel preferences and opt-out flags for email and SMS And contact information is validated (email not hard-bounced within 90 days; phone is E.164-formatted and passes provider validation) When preparing notifications Then do not send on any channel the tenant has opted out of; log the suppression reason And if both channels are eligible, send on both And if exactly one channel is eligible, send on that channel And if no channels are eligible, record "suppressed: no eligible channels" and surface in the dashboard And SMS includes required STOP/help footer per locale; receiving STOP sets SMS opt-out within 1 minute and blocks further SMS And email includes an unsubscribe link for this notification category that functions correctly
Property Templates and Localization
Given the property has message templates per locale with variables: {{property_name}}, {{work_order_id}}, {{status}}, {{appointment_date}}, {{time_window}}, {{vendor_name}}, {{update_link}} And the tenant has a preferred language and the property has a default language and timezone When rendering a notification Then the system selects the template in the tenant's language; if unavailable, falls back to the property's default; if unavailable, falls back to the en-US default And all variables are populated; any missing variable causes a render failure that is logged and triggers fallback to the default template And dates and times are formatted in the property's timezone and localized to the selected locale And unit tests verify correct token substitution and localization for at least one template per supported locale
Quiet Hours Scheduling
Given the property has quiet hours defined as a daily time window in the property's timezone And a merge occurs during quiet hours When the notification job is queued Then do not send during quiet hours; schedule delivery for the first minute after quiet hours end And the scheduled send time is stored on the notification record with reason "quiet_hours" And if the scheduled appointment begins before quiet hours end, flag "potential urgency" in the dashboard and allow a manual override send And the audit log records whether a notification was delayed due to quiet hours
Delivery and Read Receipts with Failure Surfacing
Given notifications are sent via email (ESP) and SMS (carrier) When provider webhooks return delivery/open events Then per-recipient, per-channel statuses are updated to one of: queued, sent, delivered, opened (email), failed, bounced (email), undeliverable (SMS) And the work order's notifications panel displays the latest status and timestamps for each recipient/channel And on failure (hard bounce, spam block, carrier error), create an operational follow-up item with reason, channel, tenant, and a retry option And apply automatic retry for transient errors up to 2 attempts with 15-minute intervals; do not retry on hard bounces or opt-outs And webhook events are verified for signature and processed idempotently
Secure Update Link and Tenant Add-Details Path
Given the notification includes a link to follow updates for the canonical work order When a tenant opens the link Then the tenant sees the work order status and scheduled appointment details without login via a signed, single-work-order token valid for 14 days And the page provides a clear call-to-action to add additional details or photos that attach to the canonical work order And submissions are attributed to the tenant and appear in the work order timeline within 2 minutes And expired or tampered tokens produce a friendly error with no data exposure; all access attempts are logged
Calendar and Vendor Booking Guard
"As a vendor coordinator, I want merged duplicates to result in only one appointment so that schedules remain accurate and we avoid overbooking."
Description

Ensure that merged requests translate into a single calendar event and single vendor assignment to prevent double-bookings. On merge, consolidate any pre-existing tentative bookings, de-duplicate calendar entries, and notify assigned vendors of the consolidated job. Enforce constraints so that duplicate clusters cannot create multiple concurrent bookings. Update the shared calendar and SMS confirmations atomically with the canonical work order, handling rescheduling cascades and vendor capacity checks.

Acceptance Criteria
Merge Produces Single Canonical Booking
Given multiple requests grouped as duplicates for the same unit/building When a merge is confirmed Then exactly one canonical work order is retained with a single work-order ID And then exactly one calendar event exists for that work order in the shared calendar And then any pre-existing tentative bookings from the duplicates are consolidated into the canonical event and the duplicates are cancelled And then exactly one vendor assignment is associated with the canonical work order And then each tenant in the duplicate cluster is linked as a reporter on the canonical work order without generating additional bookings
Duplicate Cluster Cannot Create Concurrent Bookings
Given a duplicate cluster exists When any user or automated process attempts to create an additional booking that overlaps the canonical event’s time window Then the system rejects the creation with a duplication/overlap error and no new event is added And when two concurrent requests try to book simultaneously within the cluster Then only one booking is persisted and the other request receives a deterministic conflict response And then the database and calendar feeds remain with exactly one event for the cluster after concurrency tests
Vendor Notification Consolidation on Merge
Given duplicates with one or more tentative vendor assignments When the requests are merged Then only one consolidated vendor notification (SMS/email) is sent referencing the canonical work-order ID and scheduled time And then any prior tentative notifications from duplicate requests are automatically cancelled with a single cancellation message (where sent) And then the vendor portal/availability view shows only one job for the time slot after synchronization
Calendar Entry De-duplication and Clean State
Given duplicate requests with existing calendar entries When merged Then all duplicate calendar entries are removed or marked cancelled, and only the canonical event remains visible in the shared calendar and ICS feed And then the canonical event description notes "Merged from N requests" with links/IDs to source requests And then the calendar shows no overlapping events for the same unit/building and vendor in the merged time window
Atomic Update of Calendar and SMS Confirmations
Given a merge that changes booking details When the system updates the calendar event and sends tenant/vendor SMS confirmations Then the operations execute atomically: either both succeed or both are rolled back And when the SMS gateway returns a failure Then no calendar change is persisted and the user sees a surfaced error with a retry option And then idempotency keys prevent duplicate SMS or duplicate calendar updates on retries
Rescheduling Cascade with Capacity Check
Given the merged booking conflicts with the vendor’s capacity When the system reschedules Then the next available slot respecting vendor capacity and service windows is proposed and applied to the canonical event And then all impacted stakeholders (tenants, vendor, manager) receive a single updated confirmation each reflecting the new time And then previously held tentative slots are released and no double-bookings remain in the calendar
Pre-Commit Capacity Validation on Consolidation
Given a merge action will finalize a time slot When validating Then vendor capacity for the time window is checked before commit And if capacity is insufficient Then the merge is blocked from finalizing the booking and an alternative slot list is returned And then no calendar event is created/updated until a capacity-compliant slot is confirmed
Review Queue with Confidence Thresholds
"As a property manager, I want to review borderline duplicate matches so that I stay in control and prevent incorrect merges."
Description

Provide a moderation UI and workflow for low-confidence matches that require human review. Support adjustable thresholds per property and category, queue sorting by confidence and recency, side-by-side comparison of text, photos, unit, and timestamps, plus an explanation of similarity signals. Offer one-click actions—Merge, Link Only, or Dismiss—with keyboard shortcuts and bulk actions. Record reviewer decisions to continuously improve thresholds and models and to create an auditable decision history.

Acceptance Criteria
Configure Confidence Thresholds per Property and Category
Given I am a Property Admin on FixFlow and open Duplicate Shield settings for Property "P" and Category "C" When I set Review Lower Threshold to 0.35 and Review Upper Threshold to 0.75 and save Then the settings persist and apply immediately to new detections for P/C And matches with score s where 0.35 <= s <= 0.75 are queued for human review And matches with s > 0.75 follow the configured auto-action and are not queued And matches with s < 0.35 follow the configured auto-action and are not queued And existing queued items are re-evaluated within 60 seconds of a threshold change And all threshold changes are recorded in audit logs with admin, timestamp, and old/new values
Sort Review Queue by Confidence and Recency
Given I am on the Review Queue for Property "P" When I view the queue without changing sort Then items are sorted by Most Recent first (descending created-at) When I switch sort to Confidence Then items are sorted by Confidence (descending score) And my sort preference persists across sessions for my user And new detections appear in the correct sorted position within 5 seconds And sorting is stable for items with equal keys (secondary sort by created-at descending)
Side-by-Side Comparison with Signal Explanations
Given I open a queued pair in the Review Queue When the comparison view loads Then I see left/right panels with: Request ID, Property, Building, Unit, Category, Reporter name/phone, Created-at, Title, Description (with highlighted overlaps), and Photos (thumbnails with zoom to full-size) And I see the aggregate similarity score s and the top 3 signal contributions (e.g., text, image, time proximity) with labels and percentages And all displayed scores match the detection engine output within ±0.001 And missing fields are shown as "Not provided" rather than blank And image thumbnails load within 1.5s p95 and open full-size within 2.5s p95
Single-Item Actions with Keyboard Shortcuts
Given a queued pair is focused in the Review Queue When I press "M" or click Merge Then the two requests are merged into a single work order with the older request as primary by default and union of text and attachments (de-duplicated), and the pair is removed from the queue When I press "L" or click Link Only Then both requests remain separate but are bi-directionally linked as related, and the pair is removed from the queue When I press "D" or click Dismiss Then the pair is marked Not Duplicate, is removed from the queue, and will not be re-queued unless underlying content changes And each action completes within 1s p95 and shows a confirmation toast And keyboard shortcut hints are visible on hover for buttons And only users with Reviewer permission can execute these actions
Bulk Actions and Conflict Handling
Given I multi-select N queued items across the Review Queue When I choose Bulk Merge Then items are grouped into duplicate clusters and I can choose a primary per cluster; if not chosen, the oldest by created-at is used as primary And a preflight summary shows counts by action and any conflicts before execution When I execute the bulk action Then a result summary shows successes and failures; successes are removed from the queue; failures remain with an inline error reason And partial failures do not roll back successful operations And all bulk decisions are individually recorded in the audit log
Decision Logging and Audit Trail
Given any reviewer decision (Merge, Link Only, Dismiss) is taken on a queued pair Then an immutable audit record is created with: pair IDs, decision, reviewer ID, UTC timestamp, property, category, pre-action score, thresholds at decision time, optional reviewer note, and resulting work order/link IDs When I open the History tab and filter by property, category, decision, reviewer, and date range Then the filtered audit records are displayed within 2s p95 and can be exported to CSV And audit records are retained for at least 24 months
Feedback Loop for Thresholds and Model Improvement
Given audited decisions have accumulated for Property "P" and Category "C" When the nightly training job runs Then labeled examples are appended from the audit log and metrics (precision, recall, F1) at current thresholds are computed per P/C And a recommended threshold adjustment is generated if it improves F1 by ≥2% with at least 100 labeled examples When an admin views the Recommendations screen and accepts a suggested change Then the thresholds for P/C are updated, applied immediately to new detections, existing queue is re-evaluated within 60 seconds, and the change is audited
Duplicate Analytics and Tuning
"As an operations lead, I want visibility into duplicate trends and effectiveness so that I can tune settings and quantify ROI."
Description

Deliver dashboards and exports reporting duplicate detection rates, false positive/negative trends, time saved, and impact on booking conflicts by property and time range. Provide per-signal contribution insights and threshold adjustment tools with a safe preview mode to simulate historical reclassification effects before applying. Expose metrics via API and integrate with existing FixFlow analytics for portfolio-level views.

Acceptance Criteria
Property and Time-Filtered Duplicate Analytics Dashboard
Given a user with Analytics access selects a property (or All Properties) and a valid time range, When the dashboard loads, Then it displays cards for Duplicate Detection Rate, False Positive Rate, False Negative Estimate, Time Saved (hours), and Booking Conflict Impact (# avoided) computed for the selection. Given the dashboard is rendered, When a user hovers any metric, Then a tooltip shows the metric definition and the last_refreshed_at timestamp accurate to the minute. Given a valid selection, When the user drills down by day or property from any chart, Then the view updates within 2 seconds p95 and reflects the same filters. Given the selected data includes the last 24 hours, Then no metric is older than 24 hours and a staleness banner appears if data exceeds this threshold. Given an invalid filter (e.g., end_date before start_date), When Apply is clicked, Then an inline validation error is shown and no data request is sent. Given All Properties is selected, When viewing the dashboard, Then totals are portfolio-wide and a breakdown table by property is available via a single click.
Analytics Exports for Duplicate Metrics
Given a user selects Export > CSV and chooses properties and a time range, When the export is requested, Then the generated file contains one row per work order (or aggregated row if group_by applied) with columns: date, property_id, unit_id, work_order_id, duplicate_flag, detection_score, classification (original|merged), false_positive_reviewed, false_negative_reviewed, time_saved_minutes, conflict_avoided_flag, signal_contributions (JSON), and a footer totals row when applicable. Given an export is requested, Then timestamps in the file are in the user's selected timezone and the file name encodes the time range and property scope. Given a large export (>200k rows), When the export is requested, Then it runs as a background job, sends an email and in-app notification with a secure download link, and completes within 10 minutes for up to 2M rows. Given a completed export, When a user compares totals with the on-screen dashboard for the same filters, Then totals match exactly. Given a user requests JSON via API (format=json), Then the same fields and aggregations are returned with identical values. Given a scheduled export is created (daily/weekly), Then it executes at the configured time with last 24h/7d window and logs success/failure status.
Per-Signal Contribution Insights
Given a user opens Signal Contributions for a selected property/time range, When the view loads, Then it displays a ranked list of signals (e.g., image_similarity, text_similarity, unit_history, tenant_frequency, building_hotspot) with mean contribution (%) to duplicate classification and 95% CI. Given the contributions list is shown, When the user clicks a signal, Then a detail panel shows distribution, top 10 influencing examples, and definition text. Given a merged work order is opened in drilldown, When the Contributions tab is selected, Then per-request signal contributions sum to the model score within ±0.01 and show the model version used. Given the model version changes, When viewing historical data, Then contributions are labeled with the model version that produced them and a banner indicates cross-version comparisons may differ. Given access control is enforced, Then only users with Analytics access can view contributions and property-scoped users see only permitted properties.
Threshold Tuning with Safe Preview Mode
Given an Admin navigates to Tuning > Preview, When they adjust the duplicate_threshold and merge_policy settings, Then a preview simulation runs on the last N days (configurable up to 90) and returns: projected duplicates merged (delta), estimated false positives (delta), estimated false negatives (delta), time saved (delta), and conflicts avoided (delta) without applying changes. Given a preview is started on data up to 10k requests and 90 days, Then results return within 30 seconds p95; otherwise a progress indicator appears and completion is notified via email/in-app. Given preview results are shown, When the Admin clicks Apply, Then a confirmation modal lists the settings and projected impact and requires re-authentication before applying. Given changes are applied, Then an audit log entry is recorded with user, timestamp, previous and new thresholds, preview parameters, and summary deltas; a rollback to previous settings is available with one click. Given role-based access control, Then Admins can apply changes, Editors can run previews only, and Viewers cannot access Tuning. Given safety guardrails, Then thresholds cannot be set outside [0.1, 0.99], previews cannot run during an active incident flag, and Apply is disabled until a preview has completed within the last 24 hours.
Duplicate Analytics Metrics API
Given an authenticated client with scope analytics:read requests GET /api/v1/duplicate-analytics with params properties[], start_date, end_date, group_by (date|property|signal), metrics[], page, per_page, Then the API responds 200 with JSON containing the requested metrics, pagination (total, page, per_page), and last_refreshed_at matching the UI for the same filters. Given an API request exceeds 60 requests/min per token, Then the API responds 429 with a Retry-After header indicating when to retry. Given invalid parameters (e.g., end_date < start_date, unknown metric), Then the API responds 400 with field-level error messages; 401 for invalid token; 403 for missing scope. Given group_by=signal and metrics include contributions, Then the response includes per-signal contribution values consistent with the Signal Contributions UI within ±0.5%. Given Accept: application/vnd.fixflow.v1+json is provided, Then the response conforms to v1; when v1 is deprecated, a Deprecation header and sunset date are returned.
Portfolio Integration in FixFlow Analytics
Given Duplicate Shield is enabled for the tenant, When a user opens the Portfolio dashboard, Then Duplicate metrics widgets (detection rate, FP/FN trend, time saved, conflicts avoided) appear and adopt the global date picker and filters (property, vendor, source channel, status). Given cross-filtering is used on the dashboard, When a user filters by vendor or source channel, Then Duplicate widgets update in sync within 2 seconds p95. Given role-based property permissions, Then users only see metrics for properties they are allowed to view; All Properties aggregates only across permitted properties. Given metric definitions, When a user opens the widget info, Then the definition text matches the dashboard and export definitions verbatim. Given portfolio size up to 10k units and 24 months of data, Then the Duplicate widgets render above-the-fold within 2 seconds p95 and all interactions remain under 300ms p95 after initial load.
Booking Conflict Impact Measurement Validity
Given a selected time range and property set, When computing Conflicts Avoided, Then the value equals the count of scheduling overlaps prevented by merges compared to a counterfactual baseline that re-schedules as-if duplicates were not merged for the same period. Given the baseline computation, Then the baseline algorithm is documented in UI tooltips and API docs and is versioned with an identifier in outputs. Given a synthetic test dataset with known overlaps is run through the calculator, Then reported Conflicts Avoided matches the expected value within 1% and the unit test suite coverage for the module is ≥90%. Given sampling is applied for large datasets, Then a 95% confidence interval is displayed next to the metric and included in API responses with methodology noted. Given the metric is shown in dashboard, exports, and API for the same filters, Then all three surfaces report identical point estimates for Conflicts Avoided.

Privacy Blur

On-device redaction auto-blurs faces, family photos, addresses, and sensitive documents in uploaded images before they leave the tenant’s phone. Tenants feel safer reporting issues, and managers reduce privacy risk without losing diagnostic detail.

Requirements

On-Device Sensitive Content Detection
"As a tenant, I want my photos analyzed on my phone for sensitive content so that nothing private is uploaded to FixFlow."
Description

Run computer vision and OCR models entirely on the tenant’s device to identify faces, street addresses, license plates, ID documents, and family photos in captured or uploaded images. Processing occurs offline; unredacted pixels never leave the device. Provide a unified client SDK for FixFlow’s tenant capture flow (mobile web/PWA and native wrappers) that returns bounding boxes and categories for redaction and supports batch processing. If the device lacks required capabilities, block upload of unredacted media and offer a built-in manual redaction tool; server-side pre-redaction processing is disabled. Integrates with maintenance request creation so only redacted assets proceed to scheduling and vendor notifications.

Acceptance Criteria
On-Device Detection Outputs and Categories
Given a supported device and an image containing faces, addresses, license plates, ID documents, or family photos, When detectSensitiveContent runs offline, Then it returns detections with category in {face,address,license_plate,id_document,family_photo}, bbox normalized to [0..1], and confidence in [0..1]. Given a labeled reference test set and confidence threshold 0.5, When evaluated, Then per-category recall >= 0.90 and precision >= 0.90 with IoU >= 0.50 for true positives. Given rotated or mirrored images, When processed, Then detections are orientation-correct and bboxes align with the displayed orientation. Given JPEG/PNG/HEIC up to 20 MB and resolution up to 12 MP, When processed, Then outputs are produced without error.
Offline-Only Processing and Data Leakage Prevention
Given network connectivity is available, When detection runs, Then no network requests containing unredacted pixels or derived embeddings are made prior to redaction (verified via HTTP proxy capture = 0 bytes image payload). Given airplane mode is enabled, When detection runs, Then it completes successfully. Given temporary storage is used, When redaction is confirmed, Then unredacted intermediates remain in app sandbox and are deleted within 5 seconds of export. Given server configuration, When attempting pre-redaction endpoints, Then they are disabled and return 403/disabled feature.
Unified Client SDK APIs and Platform Support
Given PWA (mobile Safari 16+/Chrome 120+), iOS 15+, and Android 10+, When integrating, Then the SDK exposes detectSensitiveContent(images[], options) and returns {runId, detections:[{imageId,category,bbox:{x,y,w,h},confidence}], processingStats}. Given identical inputs across platforms, When executed, Then detections are functionally equivalent with confidence variance ≤ 5% and IoU variance < 0.05. Given an unsupported platform or missing capability, When called, Then the SDK returns error code E_UNSUPPORTED_ON_DEVICE with remediation guidance. Given TypeScript integration, When building sample apps, Then type definitions compile without errors and API signatures are consistent across platforms.
Batch Processing Performance and Resource Limits
Given a batch of up to 20 images (≤12 MP) on reference devices (iPhone 12, Pixel 5), When processed, Then average per-image latency ≤ 3000 ms and 95th percentile ≤ 5000 ms. Given low-end devices (iPhone XR, Pixel 3), When processed, Then average per-image latency ≤ 6000 ms and 95th percentile ≤ 9000 ms. Given batch processing, When running, Then peak memory ≤ 350 MB, no crashes, and all heavy work occurs off the main/UI thread. Given a user cancels processing, When cancel() is invoked, Then all tasks abort within 500 ms and resources are released.
Capability Check and Manual Redaction Fallback
Given device capability tests fail (e.g., missing WASM/WebGPU/NN hardware or low memory), When user attempts to upload, Then unredacted upload controls are disabled and a modal offers the Manual Redaction Tool. Given a user attempts to bypass detection, When submitting, Then upload is blocked until all sensitive regions are redacted or user manually confirms there are none. Given fallback mode is active, When processing, Then no server-side detection/pre-redaction endpoints are invoked. Given manual redaction is completed, When exported, Then upload is enabled and only the redacted asset is queued.
Manual Redaction Tool UX and Efficacy
Given the tool opens, When initialized, Then suggested blur boxes from the last detection are displayed and users can add/move/resize/delete boxes. Given blur strength is adjusted, When exporting, Then minimum blur sigma ≥ 12 (or pixelation block size ≥ 16 px) is enforced to ensure irreversible redaction. Given zoom/pan interactions, When editing at 3x zoom, Then placement accuracy is within ±2 px. Given export, When saving, Then all redactions are baked into pixels (no layers), GPS/personal EXIF are removed, and all processing occurs on-device.
Redacted-Only Integration with Maintenance Workflow
Given a maintenance request creation flow, When images are attached, Then only redacted assets proceed to submission, scheduling, and vendor notifications. Given storage and previews, When viewing thumbnails/CDN links, Then only redacted versions are accessible and rendered. Given the server receives an asset flagged as unredacted, When validated, Then it is rejected with HTTP 422 and an audit log entry is recorded; no vendor-facing channel renders unredacted media. Given SMS/email notifications, When sent, Then all attachments and links reference redacted assets only.
Configurable Redaction Policies
"As a property manager, I want to set default redaction rules so that tenant images are protected consistently across my portfolio."
Description

Provide admin-level policy controls to define which categories are blurred by default (faces, children/family photos, addresses, license plates, government/financial documents) with preset templates and per-property overrides. Allow tenants to override or unblur specific detections for a given upload with clear warnings to preserve diagnostic details when needed. Expose sensitivity thresholds per category and store the applied policy ID with each upload. Display concise legal and privacy disclosures aligned with GDPR/CCPA and FixFlow privacy terms.

Acceptance Criteria
Admin Configures Default Redaction Categories
Given I am an admin on FixFlow and open Privacy Blur settings When I toggle default categories for faces, children/family photos, addresses, license plates, and government/financial documents Then the system saves the selected categories as the account default policy And the saved state persists after page refresh and new login And non-admin users cannot access or modify these settings And new properties created inherit these defaults unless overridden
Per-Property Policy Override Takes Precedence
Given an account default policy exists And a property has an override policy configured When a tenant from that property uploads an image Then the override policy categories and sensitivity thresholds are applied And the default policy is not applied for that upload And the upload screen clearly displays the applied policy name/ID
Preset Policy Templates Available and Applicable
Given I am an admin in Privacy Blur settings When I open the policy templates gallery Then I can view at least three templates: Strict, Balanced, Minimal And selecting a template previews its category toggles and thresholds And applying a template updates the current policy values And I can set the template as the account default or apply it to a specific property override
Tenant Unblurs Specific Detections With Warnings
Given a tenant is uploading images and the redaction preview shows blurred regions with labels per detection When the tenant taps Unblur on a specific detection region Then the app displays a clear privacy warning and requires explicit confirmation And upon confirmation, only that detection region is unblurred for this upload And the upload is marked as having a tenant override applied in metadata And if the tenant cancels, no regions change state
Sensitivity Thresholds Per Category Are Adjustable
Given an admin opens sensitivity settings for the policy When the admin adjusts thresholds independently for faces, children/family photos, addresses, license plates, and government/financial documents Then each category's numeric/slider value is saved And the preview re-runs detection and updates within 2 seconds to reflect the new thresholds And subsequent uploads use the saved thresholds for the applied policy
Applied Policy ID Stored With Each Upload
Given a tenant completes an upload When the upload metadata is persisted on the server Then the record includes applied_policy_id matching the policy used at upload time And applied_policy_id is immutable after creation And the applied_policy_id is retrievable via the admin API and the dashboard upload details view
Legal and Privacy Disclosures Displayed and Captured
Given a tenant initiates the first upload in a session When the redaction preview screen loads Then GDPR/CCPA-aligned disclosures and a link to FixFlow privacy terms are displayed concisely And the tenant must acknowledge via a checkbox before submitting the upload And the disclosure version and acknowledgment timestamp are stored with the upload metadata
Real-time Preview and Manual Controls
"As a tenant, I want to see and adjust what gets blurred before I send my photos so that vendors can still diagnose the issue without exposing my private details."
Description

Offer a live preview overlay that shows detected regions before submission and enables tenants to adjust: move/resize boxes, add/remove regions, select blur style (pixelate/Gaussian) and intensity, and apply edits across a batch. Provide pinch-zoom for precision, undo/redo, and accessible controls (screen reader labels and large touch targets). Operates offline and within low-end device constraints. Edits are applied on-device prior to upload.

Acceptance Criteria
Live Preview Overlay Displays Detected Regions Pre-Submission
Given a tenant opens the Privacy Blur editor with an image selected When automated detection runs Then detected regions are outlined with visible bounding boxes and type icons (face, photo, address, document) before upload And the initial overlay renders within 500 ms on devices with ≥2 GB RAM And the live preview maintains ≥12 fps while panning or zooming And bounding boxes remain aligned to the image with ≤1 px drift during preview
Manual Region Editing With Pinch-Zoom Precision
Given an image with detected regions visible When the tenant drags a region Then the region moves with finger input latency ≤60 ms and remains within image bounds When the tenant drags a corner or edge handle Then the region resizes with a minimum size of 24×24 px and snaps to stay fully within the image When the tenant performs a two-finger pinch Then the canvas zooms between 0.5× and 8× and can be panned, with overlay and image remaining pixel-aligned (≤1 px deviation) And corner/edge handles have touch targets ≥44×44 dp
Add And Remove Redaction Regions
Given the editor is open When the tenant taps Add Region Then a new adjustable rectangular region is created at the center and becomes selected When the tenant confirms Remove on a selected region (tap delete or accessibility action) Then the region is deleted from the image And the editor supports at least 50 total regions per image without degraded interaction responsiveness (>12 fps and ≤100 ms touch latency)
Blur Style And Intensity Selection With Real-Time Feedback
Given a redaction region is selected When the tenant selects Pixelate Then pixelation is applied and updates in the preview within 150 ms, with block size adjustable from 4–64 px in steps of 4 When the tenant selects Gaussian Then blur is applied and updates in the preview within 150 ms, with sigma adjustable from 2–20 in steps of 1 And style and intensity choices are stored per region and are preserved in the exported images
Batch Apply Edits Across Multiple Images
Given 2–50 images are loaded in the editor and the tenant has edited one source image When the tenant taps Apply to All Then all regions, styles, and intensities from the source image are copied to the selected target images with proportional scaling to each image’s dimensions And the UI confirms the number of images updated And the operation completes at an average processing time ≤2 s per 12 MP image without app crash or freeze And the tenant can exclude specific images from the batch and re-run Apply to All
Undo And Redo History
Given the tenant is editing images When the tenant performs actions (add, remove, move, resize, change style, change intensity, apply-to-all) Then each action is recorded as a discrete undoable step (apply-to-all recorded as one grouped step) And the editor supports at least 50 undo levels per image and corresponding redo And undo/redo state persists when switching between images within the same editing session
Offline, On-Device Processing And Privacy Guarantees On Low-End Devices
Given the device is in airplane mode When the tenant performs detection, editing, batch apply, and export Then all functions operate without network connectivity Given the device is online and the tenant taps Upload When network traffic is inspected Then only redacted (blurred) images are transmitted; no unredacted image bytes are sent And for a 12 MP image with 5 regions, final export completes in ≤1.5 s on a device with ≥2 GB RAM And peak memory usage during batch processing of 10 images stays ≤300 MB and the app remains responsive (touch latency ≤100 ms)
Metadata Sanitization and Quality Preservation
"As a tenant, I want location and device metadata removed while keeping image clarity so that my privacy is protected without hurting maintenance diagnosis."
Description

Strip sensitive EXIF metadata (GPS coordinates, device serials, user names) by default while preserving orientation and visual quality. Maintain full resolution for non-redacted regions and constrain output size according to FixFlow’s media pipeline limits. Provide an option to embed a subtle “redacted” marker in file metadata for downstream systems. Ensure redaction does not degrade diagnostically important areas outside the masked regions.

Acceptance Criteria
Default EXIF Sanitization on Upload
Given a tenant uploads an image that contains EXIF/XMP/IPTC fields including GPS*, Location*, SerialNumber*, OwnerName, Artist, UserComment, MakerNote*, and LensSerialNumber* When the Privacy Blur pipeline processes the image with default settings Then the output file shall not contain any of the above sensitive fields in any metadata block And the output shall not contain device identifiers (e.g., device serials) or user-identifying strings And only non-sensitive technical tags required for correct rendering (e.g., ColorSpace, PixelXDimension, PixelYDimension, Orientation if normalized) may remain
Orientation Preservation for Rotated Images
Given a set of test images that cover all 8 EXIF Orientation values When each image is processed Then the resulting image displays upright in standard web and mobile viewers without requiring client-side rotation And EXIF Orientation in the output is normalized to 1 (Top-left) And no unintended cropping, padding, or aspect ratio change is introduced
Visual Quality Outside Redacted Regions
Given an image with one or more redaction masks M and a baseline rendition produced by the same pipeline settings but without redaction When the redacted image is produced Then for all pixels outside M with a 4px buffer, SSIM against the baseline shall be ≥ 0.98 and PSNR ≥ 40 dB And no blur or pixelation extends beyond the 4px buffer around redaction boundaries And diagnostically important details (e.g., texture, text, edges) outside M remain legible to the same degree as the baseline
Output Size Constraints with Resolution Integrity
Given pipeline configuration values maxLongEdgePx and maxBytes And a tenant uploads an image of any dimension and size When the image is processed Then if the original long edge ≤ maxLongEdgePx and estimated encoded size ≤ maxBytes, the output pixel dimensions equal the original and file size ≤ maxBytes And if limits are exceeded, the output is uniformly downscaled to meet maxLongEdgePx while preserving aspect ratio and without upscaling And the final encoded file size ≤ maxBytes And no additional downsampling is applied to non-redacted regions beyond the uniform resize needed to meet limits
Optional Embedded “Redacted” Marker in Metadata
Given the processing option embedRedactedMarker=true When an image is processed Then the output metadata shall include an XMP property fixflow:privacyRedacted=true in the namespace http://fixflow.app/ns/1.0/ And no sensitive EXIF/XMP/IPTC fields are reintroduced by adding this marker And given embedRedactedMarker=false, the fixflow:privacyRedacted property is absent
Cross-Format Metadata Sanitization (JPEG/PNG/HEIC)
Given input images in JPEG, PNG, and HEIC formats that contain sensitive metadata (e.g., GPS, OwnerName, SerialNumber) When each image is processed Then the resulting files have sensitive metadata removed according to the default sanitization rules for their respective container formats And the images display with correct orientation post-processing in standard viewers And output size and aspect-ratio constraints are satisfied per pipeline configuration
Redaction Manifest and Verification
"As a property manager, I want an auditable record of what was redacted so that I can demonstrate compliance and reduce privacy risk."
Description

Generate a tamper-evident manifest alongside each redacted image containing redaction categories, bounding boxes, blur parameters, applied policy ID, model version, timestamp, and cryptographic hashes of inputs/outputs. Sign the manifest with the app key and store it with the maintenance request so managers can view verification details in the FixFlow dashboard and auditors can confirm that only redacted media were stored and shared with vendors.

Acceptance Criteria
Manifest Generation Completeness
Given a tenant uploads an image and on-device redaction is applied When the app generates the redaction manifest Then the manifest includes: redaction_categories[], bounding_boxes[{x,y,width,height} in pixels], blur_parameters{kernel_size, sigma}, applied_policy_id, model_version, manifest_schema_version, timestamp (ISO-8601 UTC), input_hash (SHA-256 of original bytes), output_hash (SHA-256 of redacted bytes), media_asset_id, request_id And the manifest validates against the JSON Schema v1.0 without errors And redaction_categories count equals the number of bounding_boxes And blur_parameters.kernel_size is an odd integer between 3 and 99 inclusive; sigma > 0 And output_hash computed server-side over the stored redacted asset matches the manifest output_hash
Manifest Signature and Tamper Detection
Given a manifest has been generated When the app computes a signature over the canonicalized manifest payload using Ed25519 and includes {key_id, alg, signature} in the manifest Then the server verifies the signature using the corresponding app public key (key_id) and records signature_status=Verified And any modification to any manifest field or the redacted image file results in signature_status=Invalid on verification And manifests signed with unknown or revoked key_id are rejected and not linked to the request And canonicalization follows JSON Canonicalization Scheme (JCS, RFC 8785) to ensure deterministic signatures
Secure Storage and Linkage to Maintenance Request
Given a maintenance request contains a redacted image and its manifest When the upload completes Then only the redacted image and manifest are stored server-side; original (unredacted) media is not accepted nor persisted And the manifest is linked to {request_id, media_asset_id} and retrievable via API and dashboard And access to manifests is restricted to roles Manager and Auditor; Vendors have no read access; Tenants can view their own request’s manifest metadata And duplicate submissions with the same input_hash and request_id are deduplicated idempotently
Dashboard Verification View
Given a manager opens the request details in the FixFlow dashboard When the media panel loads Then a Verification section displays: signature_status (Verified/Invalid/Unknown), input_hash (first 10 chars), output_hash (first 10 chars), model_version, applied_policy_id, timestamp, number_of_redactions, and a toggle to overlay bounding boxes on the redacted image And a Download Manifest (JSON) action is available And the Verification section renders within 1.5 seconds at P95 under a dataset of 5,000 requests and 200 concurrent users in staging And if signature_status≠Verified or hash mismatch is detected, the UI shows a blocking warning and disables Share with Vendor for that asset
Vendor Share Gate Enforces Redacted Media Only
Given a manager initiates Share with Vendor for a request When selecting media to share Then only assets with an attached manifest where signature_status=Verified and server-computed output_hash matches are selectable And assets without a valid manifest are auto-excluded with a tooltip: “Redaction verification required” And the share API rejects attempts to include non-verified assets with HTTP 422 and reason MANIFEST_REQUIRED And an audit log entry records share_id, request_id, asset_id, manifest_hash (SHA-256 over canonicalized manifest), vendor_id, timestamp
Audit Export for External Auditors
Given an auditor requests an export for a specified date range When the export job completes Then the system produces a ZIP containing: all manifests (JSON), a JWK set of app public keys, and a verification report (CSV) with per-asset fields: request_id, asset_id, signature_status, hash_match, model_version, applied_policy_id, timestamp And the ZIP includes SHA-256 checksums for each file and a top-level export manifest signed by the FixFlow server And exports of up to 10,000 assets complete within 5 minutes in staging And the export can be re-verified offline using the included JWKs without network access
Offline and Clock Skew Handling
Given the tenant device is offline or has incorrect system time When a redacted image and manifest are produced on-device Then both are persisted to encrypted local storage and queued for upload with exponential backoff until success or 24 hours elapse And upon upload, if timestamp skew > 5 minutes versus server time, the server records time_status=Skewed and surfaces a yellow warning in the dashboard but does not reject the manifest And uploads are atomic per {asset, manifest}; partial uploads do not create orphaned records And the tenant is notified in-app if retries exhaust without successful upload
Performance, Battery, and Reliability SLAs
"As a tenant, I want the blurring to be fast and not drain my battery so that I can submit maintenance requests quickly and reliably."
Description

Meet user-centric performance targets: live detection overlay under 500 ms per 1080p frame and under 2 seconds to process a single photo on mid-tier devices, with CPU/GPU usage capped to avoid thermal throttling. Implement progressive passes (low-res first, refine if time allows), cancellation, and resume for batches. Provide graceful degradation on older devices and capture client telemetry (crash/perf only, privacy-safe) post-redaction and with consent. Ensure the upload pipeline blocks until redaction completes successfully or the user opts for manual masking.

Acceptance Criteria
Live Overlay Latency ≤500 ms per 1080p Frame on Mid‑Tier Devices
- Given a mid‑tier reference device, when the 1080p live camera feed is active with detection overlay enabled, then median end‑to‑end per‑frame latency (capture → render) is ≤500 ms over a 30‑second session. - 95th percentile per‑frame latency is ≤650 ms; no single frame exceeds 800 ms. - Dropped frames attributable to processing are ≤5% over the session. - Enabling/disabling the overlay toggles within 200 ms with no app freeze longer than 100 ms.
Single Photo Redaction ≤2.0 s on Mid‑Tier Devices
- Given a set of 50 diverse 8–12 MP images on a mid‑tier reference device, when redaction is initiated on a single photo, then 95th percentile time from invoke to redacted image availability is ≤2.0 s; max ≤2.3 s. - Cold start penalty (first run after app launch) adds ≤300 ms beyond the above. - Result is written atomically to local storage with no partial files; I/O time ≤200 ms of total budget.
Resource Usage Caps Prevent Thermal Throttling
- During a 10‑minute stress session (live overlay + intermittent photo redactions), average app CPU utilization ≤60% and GPU utilization ≤50% (device profiler). - No OS‑reported thermal throttling or sustained frequency cap >10% vs nominal occurs (system APIs/logs). - Battery drain during the session ≤9% on a 3000–4000 mAh class device; variance ±2% allowed across runs. - Frame latency and photo SLA from related criteria remain within targets throughout the session.
Progressive Passes and Graceful Degradation Across Device Tiers
- When redaction starts, a low‑res pass (≤720p) completes first and masks sensitive regions before any high‑res refinement begins. - If remaining time budget would be exceeded (≥450 ms/frame live; ≥1.8 s/photo), the system returns low‑res masks without blocking and schedules refinement opportunistically. - On constrained/older devices, the system auto‑selects a lightweight model and/or reduced resolution to meet adjusted budgets: live ≤700 ms/frame; single photo ≤3.0 s. - A non‑blocking UI notice indicates "Reduced quality mode" when degradation is active; toggling back occurs automatically when capacity returns.
Batch Cancellation and Resume with Idempotency
- Given a batch of N photos, when the user taps Cancel, processing halts within 300 ms; no further CPU/GPU work occurs after 500 ms. - Successfully redacted photos remain available; partial outputs from in‑flight items are discarded. - When the user resumes within 7 days (or on next app launch after a crash), remaining items resume from the next unprocessed photo without reprocessing completed items. - Uploads use idempotency keys so duplicates are not sent if resume occurs; verified via server logs that per‑photo key is used exactly once. - Progress UI reflects accurate count remaining (±1 error not permitted).
Privacy‑Safe Telemetry Opt‑In Post‑Redaction
- Before any telemetry is sent, the user is presented an opt‑in consent; default is opt‑out. No telemetry network calls occur until consent is granted. - With consent granted, telemetry events are limited to crash and performance metrics (e.g., processing times, CPU/GPU utilization, model/version, pass type); payload contains no image bytes, pixel histograms, PII, GPS, contacts, or file paths. - Telemetry related to a photo is emitted only after the redacted result is produced (post‑redaction) and never includes the original image. - Revoking consent stops telemetry within 60 seconds and clears unsent buffers; verified via proxy inspection and local logs. - Session identifiers are non‑persistent and rotate at least every 24 hours; no stable cross‑session user identifier is included.
Upload Blocks Until Redaction Completes or Manual Masking
- When a photo or batch is queued, uploads do not begin until automatic redaction completes successfully. - If redaction fails or exceeds time budgets twice, the user is prompted to retry, switch to manual masking, or cancel; default is to block upload. - Selecting manual masking enables user edits; upon completion, only the redacted result is uploaded. - No network requests containing original, unredacted image bytes occur before redaction/manual masking completion (verified via proxy capture). - An audit record (non‑content) notes the path taken: auto redacted, manual masked, or aborted.

Offline Snap

Works in low-signal areas by capturing photos and guided answers offline with timestamps and optional GPS. Submissions auto-sync when connectivity returns, and critical safety guidance is shown immediately—ensuring reliable intake from basements, parking garages, or rural properties.

Requirements

Offline Media Capture & Local Queue
"As a tenant, I want to take and save photos of a maintenance issue without internet so that I can file a complete request even in basements or rural areas."
Description

Enable tenants and field staff to capture one or more photos of an issue while offline and store them locally with stable client-generated IDs, timestamps, and basic metadata until connectivity returns. Support camera invocation and gallery upload, image compression and orientation correction, and per-photo notes. Queue submissions atomically with their associated form answers, property/unit reference, and contact details in encrypted IndexedDB or equivalent. Provide safeguards for low-storage conditions, duplicate detection, and graceful handling of app refreshes or device restarts so no data is lost. Integrate with FixFlow’s maintenance request model so that queued items post to the existing intake endpoint without schema divergence when sync occurs.

Acceptance Criteria
Offline Photo Capture and Local Storage
Given the device has no connectivity (airplane mode) When the user taps Add Photos and uses the Camera Then the app captures one or more photos and persists each in encrypted IndexedDB with a client-generated UUIDv4 mediaId and an ISO 8601 timestamp with timezone Given the device is offline When the user selects one or more photos from the device gallery Then each photo is persisted with the same metadata and a source=gallery attribute in encrypted IndexedDB Given offline photos exist in local storage When the user navigates away, refreshes, or relaunches the app Then photo previews load from local storage and no photo entries are missing
Atomic Queueing with Form Answers and References
Given the user is offline and has captured photos and filled required form fields (issue details, propertyId, unitId, contact info) When the user taps Submit Then a single submission object with a client-generated UUIDv4 submissionId is written atomically to the local queue including all photos, per-photo notes, and form answers Given a queued submission with N photos When connectivity returns and sync begins Then either all N photos and form data are posted and the submission is marked synced, or none are posted (no partial server record is created) Given a queued submission begins syncing and the app crashes during upload When the app restarts Then the sync resumes without duplicating previously uploaded items and completes atomically
Image Compression and Orientation Correction
Given a photo larger than 1600 px on the longest edge or larger than 1.5 MB When it is added to the queue Then it is downscaled to ≤ 1600 px longest edge and compressed to ≤ 1.5 MB while remaining legible for diagnostics Given a photo with EXIF orientation not equal to 1 When previewed in the app and after sync to the server Then the image displays upright and the stored/sent bytes are normalized so rendering does not rely on EXIF orientation Given a HEIC/HEIF image is selected on iOS When it is added Then it is converted to JPEG (quality approximately 0.8) while meeting the size and dimension constraints
Per-Photo Notes, Timestamps, and Optional GPS
Given the user attaches photos while offline When the user adds a note to a specific photo Then the note (max 500 characters) is stored with that photo’s mediaId, persists across refresh/restart, and is included in the sync payload Given camera permissions include location and the user opts in to attach location When a photo is captured Then GPS coordinates (latitude, longitude, accuracy in meters) and capture timestamp are stored with the photo; when permission is denied, no location is stored and capture is not blocked Given a photo has GPS data stored When the user removes location Then GPS metadata is cleared locally and is not sent during sync Given a photo is captured in any timezone When the timestamp is recorded Then it is stored as ISO 8601 with timezone offset
Low-Storage Safeguards and User Guidance
Given available storage is below 50 MB or a quota/write error occurs When the user attempts to add a photo Then the app warns the user, prevents data loss, and offers options to retry, remove selected photos, or continue with higher compression Given low-storage mode is accepted by the user When new photos are captured Then they are compressed to ≤ 900 px longest edge and ≤ 500 KB Given low-storage conditions are resolved When the user retries the add/save Then the photo is saved successfully without errors
Resilience to App Refreshes and Device Restarts
Given one or more submissions are queued When the user refreshes the page, closes and reopens the tab, or the device restarts Then the queue contents (submissions, photos, notes, metadata) are intact and visible within 5 seconds of app load Given a sync is in progress When the app is terminated mid-sync Then on next launch the sync resumes without creating duplicates and with exponential backoff Given the app is updated to a new version with a storage schema migration When launching after the update Then existing queued items migrate without loss and remain syncable
Schema-Compatible Sync to Intake Endpoint with De-duplication
Given connectivity returns or the user triggers Sync Now When a queued submission exists Then the client POSTs to the existing FixFlow intake endpoint using the current schema (submissionId, propertyId, unitId, contact details, form answers, photo metadata and binaries) and receives a 2xx response Given the same submissionId was already processed by the server When the client retries due to timeout or restart Then the server responds idempotently without creating a new maintenance request and the client marks the item as synced Given two photos in a submission have identical content hashes When preparing the upload Then the client does not upload the identical binary twice and maintains both references in the submission payload Given sync succeeds for a submission When acknowledgment containing the server request ID is received Then the client removes the submission from the local queue and records the server ID for traceability
Offline Guided Questionnaire
"As a tenant, I want clear step-by-step questions available offline so that I can provide the right details to get faster help."
Description

Package a lightweight, versioned set of guided intake questions (with required fields, conditional logic, and validation rules) for offline use so users can answer step-by-step without connectivity. Cache property lists, unit identifiers, and common issue categories for quick selection; allow free-text details and photo-to-question linking. Support localization and accessibility (screen readers, large text). When back online, submit responses with their version metadata so the server can validate and evolve forms without breaking older cached flows. Ensure parity with the online intake to keep reporting and downstream workflows consistent.

Acceptance Criteria
Offline Intake Form Loads with Cached Reference Data
Given the device has no internet connectivity and the user selects "Report Issue" When the Offline Guided Questionnaire is opened Then the most recently cached questionnaire version is loaded within 2 seconds on a mid-tier device And the property list, unit identifiers, and common issue categories are available and searchable without network access And the last successful data sync timestamp is displayed And if no cached questionnaire exists, the user sees an "Offline unavailable" message with options to Retry or Exit
Offline Conditional Logic and Validation Enforcement
Given a questionnaire with required fields, conditional branching, and validation rules is opened offline When the user progresses through questions Then required questions block progression until valid input is provided and an inline, localized error is shown And conditional questions appear only when their conditions are satisfied and are excluded from the payload when hidden And client-side validations (e.g., numeric ranges, allowed values, formats) are enforced offline with messages consistent with the online flow
Photo-to-Question Linking with Timestamp and Optional GPS Offline
Given the user is offline and on a question that allows photo attachments When the user captures or selects a photo Then the photo is stored locally and linked to the specific question by a stable question identifier And a capture timestamp is recorded using device time and timezone offset And if location permission is granted and a fix is available, GPS coordinates are saved; otherwise submission proceeds with "GPS unavailable" noted And the UI enforces the configured maximum number of photos per question and shows the remaining count
Localization and Accessibility Support While Offline
Given the device is offline with language set to a supported locale and accessibility features enabled When the Offline Guided Questionnaire is used Then all labels, prompts, and validation messages are presented in the selected locale with fallback to English for missing strings And all interactive elements have programmatic labels announced correctly by screen readers, and dynamic validation errors are announced when they appear And text scales up to 200% without truncating essential content, with scrolling enabled where needed And focus order is logical and visible focus indicators meet WCAG 2.1 AA contrast requirements
Auto-Sync of Offline Responses with Version Metadata
Given the user has completed a questionnaire offline and has at least one unsent submission When network connectivity is restored Then unsent submissions automatically attempt to sync within 15 seconds without user action And each submission includes questionnaire version, locale, device timestamp, timezone offset, and per-answer metadata required by the server And on server acceptance, the submission is marked Sent, the local draft is deleted, and a confirmation with server receipt ID is shown And on server validation failure, the user is prompted to resolve specific fields and can retry; unsuccessful attempts back off exponentially up to a maximum interval of 30 minutes And if the server indicates the cached questionnaire version is incompatible, the client fetches the latest version and preserves the original draft for user-assisted migration
Parity of Offline Intake with Online Output and Workflows
Given the same answers are provided to the same questionnaire version offline and online When submissions are generated Then the payload structure, field keys, codes, and normalization match the online schema exactly, excluding offline-only metadata (e.g., local draft IDs) And downstream routing (e.g., category mapping, assignment rules) triggered by the submission is identical And analytics counters and reporting dimensions reflect the same values as an online submission for the same inputs
Draft Persistence and Resilience Across App Restarts
Given a user starts an offline questionnaire but has not submitted When the app is backgrounded, force-closed, or the device restarts Then the in-progress draft is retained locally and is recoverable to the last completed step with all answers and attachments intact And multiple in-progress drafts are listed with property/unit, category, and last-edited timestamp for resumption or discard And if local storage becomes insufficient during photo capture, the user is warned and prevented from proceeding until space is freed, without data loss to existing answers
Auto-sync & Conflict Resolution
"As a tenant, I want my offline submissions to send automatically when I’m back online so that I don’t have to remember to resend or worry about duplicates."
Description

Implement background sync that detects restored connectivity and automatically uploads queued submissions and photos with resumable uploads. Use idempotent, retry-safe APIs with client-generated UUIDs to prevent duplicates, exponential backoff on failures, and per-item progress tracking. Handle partial successes (e.g., form posted, some images pending), and ensure atomic linkage between forms and media. On successful server acceptance, trigger the standard FixFlow flow (ticket creation, vendor scheduling, and SMS confirmations) so downstream automations behave identically to online submissions. Provide deterministic rules for merge/overwrite if a user edits a queued item before sync completes.

Acceptance Criteria
Auto-sync on Connectivity Restoration
Given the device has at least one queued submission with photos and is offline When connectivity is restored to any network with internet access Then background sync starts within 10 seconds and begins uploading all queued items without user action Given the app was previously closed with queued items When the app launches after connectivity is restored Then previously queued items persist and auto-sync resumes within 10 seconds Given no connectivity is present When the user opens the app Then queued items remain unsent and display a waiting for connection state
Resumable Media Uploads
Given a photo upload begins and the connection drops mid-transfer When connectivity returns Then the upload resumes from the last confirmed byte or chunk and does not restart from 0 percent Given a photo of size S MB is uploaded with two interruptions When the upload completes Then total bytes transmitted do not exceed S times 1.2 and the server stores exactly one copy Given multiple photos are queued When one fails mid-transfer Then other photos continue uploading independently
Idempotent Ticket and Media Creation
Given the client submits a form with client-generated submissionUUID X and photoUUIDs A and B When the request is retried 1 to 5 times due to transient errors Then the server persists exactly one ticket for X and at most one copy of each photo A and B Given the same payload X is received after a prior success When processed Then the server responds success without creating new records and returns the existing identifiers Given parallel retries happen within 500 milliseconds of each other When processed Then no duplicate tickets or duplicate photos are created
Partial Success With Atomic Linkage and Standard Flow
Given a form payload is accepted by the server while some photos are still uploading When the ticket is created Then vendor scheduling and SMS confirmations are triggered as in the online flow and reference the same ticket Given photos finish uploading after the ticket exists When they are accepted Then they attach to the same ticket created from the form and appear in its media section with correct timestamps Given a photo ultimately fails after maximum retries When the user chooses to keep the ticket Then the ticket remains active, the failed photo is flagged as pending retry with a retry action, and no orphan ticket or media records exist
Deterministic Conflict Resolution on Pre-Sync Edits
Given a user edits fields such as description or priority of a queued submission before sync completes When sync occurs Then the latest local edit timestamp prevails per field and is applied to the single server ticket and no duplicate tickets are created Given a user deletes or replaces photos in the queued item before sync When sync occurs Then only the final photo set at sync time is uploaded, deleted photos are not uploaded, and replaced photos upload once with new photoUUIDs Given the form has already been created on the server but images are pending When the user edits form fields locally before image upload finishes Then the server updates the existing ticket with the new field values deterministically and preserves linkage to pending photos
Exponential Backoff and Retry Rules
Given a retriable failure such as network error or HTTP 408, 429, or 5xx occurs When retrying Then the client uses exponential backoff starting at 2 seconds doubling each attempt with plus or minus 20 percent jitter and capped at 5 minutes between attempts Given 7 consecutive retriable failures for an item When the 7th attempt fails Then the item status becomes paused with auto resume on connectivity change and the next retry is scheduled only on connectivity change or manual retry Given a non-retriable error such as HTTP 4xx excluding 408 and 429 When received Then the item is marked failed with action needed and the user can edit or discard to proceed and automatic retries stop
Per-Item Progress Tracking and Persistence
Given multiple items and photos are queued When uploads are in progress Then each item shows per-item state such as waiting, uploading with percentage, retrying in N seconds with attempt count, paused, and synced, and photo-level progress where applicable Given the app is force quit or the device restarts When the app is reopened Then the queue, per-item states, and progress resume without loss and in-flight uploads continue or resume within 10 seconds Given an item completes syncing When viewing its details Then the UI shows synced with server ticket ID and timestamps for form accepted and each media accepted
Timestamp & Optional GPS Tagging
"As a property manager, I want timestamped and optionally geotagged submissions so that I can verify when and where an issue was recorded."
Description

Automatically attach a device-side timestamp to each photo and submission captured offline. Offer optional GPS tagging with explicit consent, clearly indicating accuracy and letting users disable or override location. Validate GPS data against known property geofences when available, and fall back to unit selection if location is unavailable. Store privacy preferences per user and organization with admin controls to disable GPS collection entirely. Display captured metadata to users for transparency and include it in the synced payload for auditability.

Acceptance Criteria
Per-Photo and Submission-Level Device Timestamping Offline
Given the device is offline and a user captures one or more photos for a maintenance submission When each photo is taken Then the app attaches a device-side ISO 8601 timestamp with timezone offset to each photo's metadata Given a submission is created When the first photo is taken and when the submission is finalized Then the app records submission-level created_at and submitted_at timestamps in ISO 8601 with timezone offset Given the user views the photo or submission summary before sync When the details screen is opened Then the timestamps are visible and read-only to the user Given the submission syncs successfully When the payload is generated Then all photo-level and submission-level timestamps are included in the synced payload
Explicit Consent and Optional GPS Tagging with Accuracy Display
Given a user first attempts to enable location tagging When the capture screen requests location for the first time Then a consent dialog explains purpose, use, and controls and requires explicit opt-in to proceed Given the user opted in to GPS tagging When a photo or submission is captured Then latitude, longitude, and accuracy in meters are attached to the metadata and shown to the user Given the user declined or later disables GPS tagging in settings When capturing photos or submitting offline Then the app does not request or collect location and a clear 'Location off' indicator is shown Given the user disables location for a single capture or edits the location When saving the submission Then the user's choice is honored and an override flag is recorded in metadata and included in the payload
Geofence Validation and Property Association
Given the organization has property geofences configured When a GPS fix is captured for a submission Then the app evaluates whether the coordinates fall within a single property's geofence Given the coordinates fall within exactly one geofence When property association is determined Then the property is auto-selected and a geofence_match=true flag is recorded in metadata Given the coordinates fall outside all geofences or intersect multiple When the user proceeds Then the user is prompted to confirm or select the correct property/unit and geofence_match=false with reason is recorded Given the user overrides an auto-selected property When saving the submission Then the override, user_id, and timestamp are recorded and included in the payload
Fallback to Unit Selection When Location Unavailable or Disabled
Given GPS is disabled by user or organization or a fix cannot be obtained within the app's location timeout When the user proceeds with the submission Then the app requires property/unit selection before submission and allows photo capture without delay Given location is unavailable or disabled for a capture When metadata is prepared Then location_status is set to 'unavailable' or 'disabled' with a reason code and included in the payload Given the user wants to retry location When permitted by settings Then the UI provides an explicit Retry Location action without blocking capture
Org-Level Admin Control to Disable GPS Collection
Given an admin enables 'Disable GPS collection' at the organization level When any user in the organization uses the capture flow Then the app does not request location, location UI is hidden or marked 'Disabled by admin', and no coordinates are stored Given the org-level disable is enabled When a submission is created and later synced Then the payload contains no location coordinates and includes a policy indicator showing GPS disabled by org Given the org-level disable is off When a user has their personal GPS preference off Then the personal preference takes precedence for that user unless overridden per-capture by the user
Persistent Privacy Preferences per User and Organization
Given a user sets their GPS tagging preference in settings When they log out and back in or use another device Then the preference persists and is applied on capture screens Given an org admin updates the organization policy for GPS collection When the app refreshes configuration or restarts Then the effective preference for each user reflects org policy precedence Given a capture is made When the payload is generated Then the payload includes the effective org policy and user preference values at time of capture
Metadata Transparency and Sync Auditability
Given a user reviews a photo or submission before sync When the details screen is shown Then the app displays metadata: photo timestamp, submission timestamp, coordinates (if any), accuracy, geofence result, and effective privacy settings Given the submission syncs When the payload is transmitted Then all displayed metadata fields for each photo and the submission are included in the payload Given location is disabled by user and/or admin When the submission is synced Then the payload contains no coordinates and includes indicators that location was disabled by user and/or admin
Immediate Offline Safety Guidance
"As a tenant, I want immediate safety tips while offline so that I can reduce damage or danger before help arrives."
Description

Bundle a small, offline-available library of critical safety guidance (e.g., gas leak, electrical hazard, active water shutoff) that can be displayed instantly based on user inputs or keywords, with high-contrast visuals and multi-language support. Allow admins to update guidance content through versioned content packs that refresh when the app is online. Ensure guidance is visible before submission and without connectivity, with clear calls to action (e.g., call 911, shut off valve) and disclaimers. Log which guidance was shown for later audit without blocking the intake flow.

Acceptance Criteria
Instant Guidance Offline—No Connectivity
Given the device has no connectivity (Airplane Mode enabled) and the user’s intake description includes the keyword "gas leak" When the user advances past the description field or pauses typing for 800 ms Then a Gas Leak Safety Guidance screen sourced from the local content pack is presented within 1 second, before submission, without making any network requests And the guidance remains accessible if the user navigates back and forth within the intake flow And all text and iconography meet WCAG 2.1 AA contrast (text ≥ 4.5:1; large text/icons ≥ 3:1) And the display event is timestamped locally for audit
Keyword and Input Trigger Mapping
Given the content pack defines trigger mappings for hazards (e.g., GAS_LEAK: ["gas leak","smell gas","rotten egg"], ELECTRICAL_HAZARD: [...]) and guided-answer codes When the user’s free text matches any configured keyword (case- and accent-insensitive) or selects a mapped guided answer while offline Then the corresponding safety guidance appears exactly once per intake session unless explicitly re-opened by the user And if multiple hazards are detected, guidance for the highest severity is shown first with a visible option to view others (priority: GAS_LEAK > ELECTRICAL_HAZARD > ACTIVE_WATER_SHUTOFF) And detection operates fully offline and completes within 200 ms on a mid-tier device And on a curated regression set of 50 phrases, precision and recall for each mapped hazard are ≥ 95%
Multi-language Offline Guidance Selection
Given the device locale is Spanish (es) and the local content pack includes Spanish strings for safety guidance When guidance is displayed while offline Then all static guidance text renders in Spanish with correct diacritics And any missing Spanish string falls back to English, with missing rate ≤ 5% of total strings in the pack And the user can switch language between English and Spanish from the guidance screen, and the content updates within 500 ms without connectivity And screen-reader labels follow the selected language, and RTL layouts (if enabled for that language) preserve readability and touch targets
Versioned Content Pack Update and Integrity
Given the device is online and the installed content pack is v1.2 When a newer signed pack v1.3 is available Then the app downloads the update (delta or full), verifies its signature and checksum, and swaps it atomically without interrupting offline availability And if verification fails or the download is incomplete, v1.2 remains active and v1.3 is discarded And the pack’s metadata (version, createdAt, locales, hash) is persisted and visible in diagnostics And update checks run at app launch and upon connectivity regain, with a minimum interval of 24 hours between automatic checks And the app logs update success/failure with reason codes for audit
CTAs and Disclaimers Visibility and Actionability
Given hazard-specific guidance is shown When the hazard is GAS_LEAK Then a primary CTA labeled "Call 911" is displayed and activates the device dialer via tel:911 (or shows region-appropriate emergency instructions if calling is not available) And secondary CTAs/instructions (e.g., "Evacuate immediately") are visible above the fold And for ACTIVE_WATER_SHUTOFF, a primary CTA "Shut off main valve" presents clear step-by-step visuals And all CTA touch targets are ≥ 44x44 pt and keyboard-focusable with visible focus indicators And a legal disclaimer is displayed on-screen without scrolling and is announced to screen readers And all actions are available offline
Guidance Exposure Logging and Sync
Given any safety guidance is displayed When the screen is first shown Then a local log entry is queued with fields: requestId, guidanceId, hazardCode, severity, contentPackVersion, language, triggeredBy (keyword|guidedAnswer|manual), shownAt (UTC), dismissedAt (UTC optional) And logging is non-blocking (≤ 50 ms on UI thread) and resilient to app restarts (persisted to local storage) And when connectivity is restored, queued logs are sent to the server with retries (exponential backoff up to 24 hours) and are idempotent via a stable eventId And no sensitive free-text is transmitted; only codes/metadata per schema
Non-blocking Intake Flow with Re-entry
Given safety guidance appeared before submission When the user dismisses the guidance Then the intake flow resumes at the previous step with photo capture and form inputs fully functional offline And a persistent banner "Safety Guidance available" remains visible and re-opens the last guidance within 300 ms when tapped And guidance never prevents submission; the user can submit the request without being forced to re-open guidance And the presence of guidance does not increase the median time-to-first-photo by more than 200 ms compared to baseline
Connectivity & Sync Status UI
"As a tenant, I want to see what’s queued and when it’s sent so that I know my request hasn’t been lost."
Description

Provide clear UI indicators for offline mode, queued items, and sync progress, including an Outbox view listing each pending submission with per-item states (Queued, Uploading, Waiting Retry, Needs Attention, Synced). Allow manual retry, cancel, or edit before sync, and show last successful sync time. Use unobtrusive banners and toasts to reassure users that their data is saved locally. Reflect final server status with standard FixFlow confirmation screens and request IDs once syncing completes.

Acceptance Criteria
Offline Indicator and Local Save Feedback
Given the device loses internet connectivity When the user is on any capture or form screen Then an unobtrusive banner with text "Offline — saving locally" appears within 500 ms and remains until connectivity is restored Given the user submits a photo or answer while offline When the submission is saved locally Then a toast "Saved offline to Outbox" displays within 1 s and the Outbox badge increments by 1 Given connectivity is restored When the app detects online status Then the banner changes to "Online — syncing" within 1 s and auto-dismisses after syncing completes Rule: Banner and toasts meet WCAG AA contrast (>=4.5:1) and are announced by screen readers
Outbox Listing with Per-Item States
Rule: Outbox lists each pending submission as a row with title, timestamp, optional GPS indicator, and state in {Queued, Uploading, Waiting Retry, Needs Attention, Synced} Given there are N unsynced items When the Outbox is opened Then exactly N rows are displayed with states limited to {Queued, Uploading, Waiting Retry, Needs Attention} Given an item finishes syncing When the server acknowledges success Then its state becomes "Synced" and the row is removed from Outbox within 2 s Given a row is tapped When the details view opens Then it shows captured fields, photos, current state, any error summary, and payload size (MB)
Manual Retry, Cancel, and Edit Before Sync
Given an item is in "Waiting Retry" or "Needs Attention" When the user taps Retry Then upload starts immediately and the state becomes "Uploading" Given an item is in "Queued" or "Waiting Retry" When the user taps Edit Then the form opens prefilled; on Save the item returns to "Queued" with updated content and timestamp; on Cancel no changes are saved Given an item is in "Queued" or "Waiting Retry" When the user taps Cancel and confirms Then the item is removed from Outbox within 500 ms and the badge decrements; no network call is made Given an item is in "Uploading" When the user taps Cancel Then upload is aborted within 2 s, state returns to "Queued", and any partial upload is discarded
Sync Progress and Connectivity Transitions
Given connectivity changes from offline to online When there is at least one queued item Then auto-sync starts within 3 s without user action Rule: While syncing, a global progress indicator shows X/Y items completed and updates at least every 1 s Given connectivity drops during an upload When the upload fails due to network loss Then the in-flight item transitions to "Waiting Retry" with a displayed next retry time and the global progress pauses Given all items have uploaded successfully When the queue is empty Then the global progress indicator hides within 1 s
Last Successful Sync Time
Rule: UI displays "Last sync: <relative> (<absolute>)" using local timezone; absolute uses ISO 8601 (YYYY-MM-DD HH:mm) Given at least one item uploads successfully When the server response is received Then "Last sync" updates within 1 s to the server-acknowledged time Given the app restarts When the dashboard loads Then the last sync time persists and is shown; if none exists, display "Last sync: Never" Rule: Relative time is accurate within 60 s of the absolute timestamp
Final Server Confirmation and Request IDs
Given an item upload completes successfully When the server assigns a Request ID Then the standard FixFlow confirmation view is shown with the Request ID, the item is removed from Outbox, and it appears in the Requests list within 5 s Given the server returns validation errors When the upload attempt completes Then the item enters "Needs Attention" with a concise error summary and an Edit action to resolve and resubmit Given the server detects a duplicate submission When the response is received Then the confirmation indicates the existing Request ID, the local item is marked Synced, and it is removed from Outbox
Error States and Retry Backoff Visibility
Given a transient error (HTTP 5xx or timeout) occurs When an upload attempt fails Then the item moves to "Waiting Retry" and the UI shows "Retry in <n> min" with exponential backoff between 1–15 minutes Given 5 consecutive failed attempts for the same item When the next retry would be scheduled Then the state changes to "Needs Attention", automatic retries stop, and actions shown are Retry Now, Edit, and Cancel Given the user taps View error on an item When the error details drawer opens Then it shows the last error code, message, and timestamp without sensitive stack traces Rule: All error messages are localized and fit within two lines on a 320px-wide device
Encrypted Local Storage & Retention Controls
"As a property manager, I want offline data to be encrypted and purged promptly so that tenant information stays secure and compliant."
Description

Encrypt all offline data at rest using Web Crypto with per-user keys where feasible, minimizing exposure of PII and photos. Implement configurable retention: auto-purge items after successful sync or after an admin-defined time limit; securely wipe media files and form data on logout or device change. Monitor storage quotas, provide user prompts when nearing limits, and fail gracefully with guidance to free space. Ensure compliance with organization data policies and align with FixFlow’s authentication/session lifecycle to prevent unauthorized access to cached data.

Acceptance Criteria
Encrypt Offline Data with Per-User Keys
Given the user is authenticated and the device is offline When the user saves photos and form data via Offline Snap Then the data is stored encrypted at rest using Web Crypto (AES-GCM, 256-bit) And the encryption key is unique per user and organization tenant And encryption keys are never persisted unencrypted in localStorage/sessionStorage or IndexedDB And only ciphertext blobs are present in storage; plaintext PII or media are not readable via DevTools And logging in as a different user on the same device cannot decrypt the cached data And after app restart, decryption requires successful re-authentication to re-establish keys
Auto-Purge After Successful Sync
Given one or more offline items exist locally When connectivity is restored and the server responds 2xx for an item Then all local copies of that item’s media and form data are securely deleted within 10 seconds And the item no longer appears in any offline queue or index And no recoverable blob data remains in IndexedDB/Cache Storage And an audit entry records the purge timestamp and item identifier (non-PII)
Retention Time Limit for Unsynced Items
Given an admin configures offline retention to N hours in org settings When an unsynced offline item reaches age >= N hours Then the item is automatically purged on next app foreground or maintenance tick And the purge securely deletes media blobs and form fields And the user receives an in-app notice summarizing the count of expired items removed (no content exposure) And the configured retention cannot exceed the organization’s policy maximum
Secure Wipe on Logout or Account Switch
Given the user initiates logout or switches to a different account/tenant on the same device When logout completes or account switch is confirmed Then all locally cached offline data, media blobs, indexes, and encryption material are wiped immediately And reopening the app shows zero offline items and no active decryption keys in memory And attempting to read storage externally finds no recoverable plaintext or keys
Storage Quota Monitoring and Graceful Degradation
Given the app stores offline media and forms in browser storage When estimated storage usage reaches 80% of effective quota Then the user is warned with remaining capacity and guidance to free space And when usage reaches 95% or a write fails with QuotaExceededError Then new captures are blocked with a clear error and an option to delete older drafts And previously saved data remains intact; partial writes are rolled back And telemetry emits anonymized quota warning/failure events
Session Lifecycle Protection of Cached Data
Given the user’s authentication session expires, is revoked, or the device is locked When the app is resumed or a background sync would run Then decryption keys are unavailable and cached data remains inaccessible until re-authentication And background sync does not process items while unauthenticated And after re-authentication, only the re-authenticated user’s data is decryptable
Organization Data Policy Compliance Controls
Given an organization policy defines encryption requirements, GPS storage allowance, and maximum retention When the policy is fetched or updated Then the app enforces the stricter of organization policy and app defaults And GPS coordinates are not stored offline if disallowed; otherwise included only with user opt-in And retention cannot be set above the policy maximum; attempts are rejected with an admin-facing message And an audit trail records the policy version applied to each offline item (no PII)

SmartCaps

Set not-to-exceed limits by property, issue type, or vendor. Requests under the cap auto-approve or send a one-tap confirm; over-cap estimates route to the right approver with suggested alternates. Reduces decision fatigue, speeds common fixes, and keeps spend aligned to budget without manual review.

Requirements

Cap Rule Engine & Precedence Hierarchy
"As a property manager, I want to define NTE limits by property, issue type, and vendor with clear precedence so that routine fixes can be auto-approved while keeping spend within policy."
Description

Provide a configurable rule engine to set not-to-exceed (NTE) limits by property, portfolio, unit, issue type, and vendor with clear precedence (e.g., vendor-specific overrides issue-type, which overrides property default). Support effective dates, exceptions, and rule versioning. Handle taxes, fees, and overtime multipliers in cap evaluations. Allow cloning and bulk-edit of rules, inline validation, and conflict detection. Integrate with FixFlow’s property/issue taxonomy, vendor directory, and request intake so that every new or updated request is evaluated against the correct active rule in real time.

Acceptance Criteria
Rule Precedence Resolution Across Vendor, Issue Type, Unit, Property, and Portfolio
Given portfolio default cap $500, property A cap $400, issue type Plumbing cap $350 for property A, and vendor PipePros cap $300 for Plumbing at property A When the rule engine evaluates a request at unit 12B in property A for Plumbing assigned to PipePros Then the applied cap is $300 (vendor-specific) and all lower-precedence caps are ignored Given the same setup but no vendor-specific rule exists When the rule engine evaluates the request Then the applied cap is $350 (issue-type) and lower-precedence caps (property $400, portfolio $500) are ignored Given only a property cap exists for property A and no issue-type or vendor rule exists When the rule engine evaluates a request for any vendor and issue type in property A Then the applied cap is $400 (property) Given no specific caps exist at unit/property/issue/vendor levels When the rule engine evaluates any request in the portfolio Then the applied cap is the portfolio default $500
Effective Dates, Exceptions, and Rule Versioning Selection
Given two versions of a Plumbing rule for property A: v1 effective 2025-01-01 to 2025-06-30 cap $300; v2 effective 2025-07-01 onward cap $350 When a request created on 2025-06-15 is evaluated Then v1 is selected and cap $300 applies Given the same request updates its estimate on 2025-07-10 When the request is re-evaluated Then v2 is selected and cap $350 applies Given an exception "Emergency override cap $600" active 2025-06-10 to 2025-06-20 for property A Plumbing When a request on 2025-06-15 is evaluated Then the exception cap $600 overrides the normal rule for that period Given a rule with an end date in the past When evaluating any request on a date after the end date Then the expired rule is not considered in selection
Cap Evaluation Includes Taxes, Fees, and Overtime Multipliers
Given a rule cap $500 and a request estimate with labor 2.0 hours at $100/hour, overtime multiplier 1.5, parts $120, fees $20, and tax 8% applied to labor+parts+fees When the rule engine evaluates the request Then total cost considered for cap = ((2.0 * 100 * 1.5) + 120 + 20) * 1.08 = $475.20 and the request is under-cap Given the same estimate but overtime multiplier 2.0 When the rule engine evaluates the request Then total cost considered for cap = ((2.0 * 100 * 2.0) + 120 + 20) * 1.08 = $583.20 and the request is over-cap Given a rule configured to exclude taxes from NTE evaluation When the rule engine evaluates the same estimate Then taxes are excluded from the total considered and the calculation is reflected in the evaluation record
Inline Validation and Conflict Detection During Rule Create/Edit
Given a user creates a new rule with missing required fields (scope, cap amount, effective dates) When attempting to save Then inline errors are shown per field and the save is blocked Given a user sets an effective date range that overlaps an existing active rule for the same scope and precedence level When attempting to save Then the system blocks the save and displays a conflict list including rule IDs, scopes, and overlapping ranges Given the user resolves all validation errors and conflicts When saving the rule Then the rule saves successfully and appears in the active rules list within 2 seconds
Rule Cloning Preserves Logic and Requires Target-Specific Inputs
Given an existing rule R scoped to property A, issue type Plumbing, cap $350, effective 2025-07-01 onward When the user selects Clone and chooses target property B Then a new draft rule is created for property B with identical cap, issue type, precedence, and conditions, but with no effective dates set And then the clone references R in 'clonedFrom' metadata and starts at version 1 And then the user must set effective dates before save; otherwise inline validation blocks save
Bulk Edit Applies Atomically with Preview and Rollback
Given a user selects 25 rules across multiple properties And chooses Bulk Edit to update cap amount by +10% and set a unified start date 2025-10-01 When applying changes Then a preview lists all target rules with before/after values and any conflicts And then if any conflict exists, no changes are committed and the user is prompted to resolve conflicts And then if no conflicts, all 25 rules are updated in a single transaction and version numbers increment appropriately
Real-Time Evaluation on Intake/Update with Routing
Given a new maintenance request is submitted via intake with property A, unit 12B, issue 'Plumbing', vendor 'PipePros', and an initial estimate When the request is created Then the rule engine evaluates within 200 ms P95 and stamps the request with appliedRuleId, ruleVersion, precedencePath, capAmount, calculatedCost, and decision ('under-cap' or 'over-cap') Given the decision is 'under-cap' and the rule has autoApprove=true When evaluation completes Then the request status transitions to 'Auto-Approved' and an SMS confirmation is queued to the vendor Given the decision is 'over-cap' When evaluation completes Then the request status transitions to 'Needs Approval' and is routed to the configured approver group with reason 'Over NTE'
Auto-Approval & One-Tap Confirm
"As an owner-operator, I want sub-cap work to auto-approve or be confirmed with one tap so that I can keep things moving without reviewing every small ticket."
Description

Automatically approve and schedule requests that meet the applicable cap using the shared calendar, honoring vendor availability and preventing double bookings. For borderline cases or when policy requires human confirmation, send a one-tap confirm link via in-app notification, email, or SMS with a concise summary (issue, property, vendor, estimated cost, cap remaining). Support policy-based timeouts (auto-approve, escalate, or cancel) and working-hour windows. Log all actions and sync confirmations back to the request timeline and vendor schedule.

Acceptance Criteria
Auto-approve under applicable cap and schedule using shared calendar
Given a maintenance request with an estimated cost at or below the applicable SmartCaps policy cap (by property, issue type, or vendor) and an eligible vendor with availability within configured working hours When the request is submitted Then the system auto-approves the request And schedules the earliest non-conflicting time slot on the shared calendar honoring vendor availability and working-hour windows And updates the request status to Approved and Scheduled And records the applied cap and estimated cost in the request metadata
Prevent double bookings during auto-scheduling
Given an existing vendor booking on the shared calendar for a time slot When auto-scheduling a qualifying request Then the system must not book the vendor in any conflicting time slot And must select the next available non-conflicting slot within policy constraints And if no qualifying slot exists, the request is not auto-approved and is routed per policy for manual scheduling or escalation
One-tap confirm notification for borderline or policy-required approvals
Given a request that meets cap criteria requiring human confirmation per policy (e.g., within configurable threshold of cap or flagged category) When the request is submitted Then the system sends a one-tap confirm action via in-app notification, email, and SMS (based on recipient delivery preferences) And the message includes: issue summary, property, vendor, estimated cost, cap amount, and cap remaining And tapping the link confirms approval and schedules the earliest compliant slot on the shared calendar And the confirmation result is acknowledged to the user with success or failure feedback
Timeout policies for pending one-tap confirmations
Given a pending one-tap confirmation with a configured timeout and outcome policy (auto-approve, escalate, or cancel) When the timeout elapses without a confirmation Then the system executes the configured outcome And notifies the relevant parties of the action taken And logs the timeout and resulting action on the request timeline
Respect working-hour windows for scheduling and notifications
Given configured working-hour windows for properties and vendors When auto-scheduling or sending one-tap notifications Then scheduling occurs only within overlapping working-hour windows of the vendor and property And notifications sent outside a recipient’s working hours are queued and delivered at the start of the next window unless policy explicitly allows immediate delivery
Comprehensive action logging and audit trail
Given any approval, scheduling, notification, confirmation, timeout, escalation, or cancellation event When the event occurs Then the system logs: timestamp, actor (system/user), channel (in-app/email/SMS), prior and new statuses, relevant identifiers (request, property, vendor), and decision basis (cap policy reference) And the log entry is visible on the request timeline and exportable via audit view
Sync confirmations and schedules to request timeline and vendor schedule
Given an approval or confirmation that results in a scheduled appointment When the action completes Then the request timeline shows the approval/confirmation entry and the scheduled appointment details And the vendor’s shared calendar is updated with the appointment, including property location and contact notes, without creating duplicates And subsequent changes via one-tap or policy outcomes keep both the timeline and calendar in sync
Over-Cap Routing with Smart Alternates
"As a maintenance coordinator, I want over-cap requests to route to the right approver with lower-cost vendor alternatives so that approvals are fast and cost-effective."
Description

Detect when a request exceeds its cap and route it to the correct approver based on property, role, and amount thresholds. Present a clear variance breakdown versus cap and provide suggested alternate vendors who can meet the cap based on rate cards, proximity, availability, and performance rating. Enable one-click reassignment to alternates or submission for revised estimates. Capture approver decisions with reason codes and update the schedule and notifications accordingly.

Acceptance Criteria
Auto-Detect Over-Cap and Route by Thresholds
Given a request with an estimated total that exceeds the applicable cap (property/issue type/vendor) and approval thresholds configured by role and amount When the request is submitted Then the system flags the request as over-cap, identifies the correct approver based on property, role hierarchy, and amount thresholds, and assigns the approval task within 5 seconds And the system records the cap source and threshold values used in routing Given multiple approvers qualify by thresholds When routing is determined Then the highest-authority approver per configured hierarchy is selected Given no approver matches the thresholds When routing is determined Then the request is routed to the default escalation approver and an alert is created
Variance Breakdown Versus Cap
Given an over-cap request When the approver opens the request details Then the UI displays: cap amount, estimate total, variance amount and percentage, cap source (property/issue type/vendor), and the date/time the cap was last updated And the line-item comparison vs vendor rate card including unit rates and quantities And values are formatted in the property currency with two decimal places Given rate card data is missing for any line item When variance is displayed Then the UI indicates "rate card missing" for that line and excludes it from rate-card variance while still showing total variance
Suggest Alternates Meeting Cap
Given an over-cap request with vendor rate cards, vendor locations, calendars, and performance ratings available When alternates are computed Then the system returns a ranked list of up to 5 vendors projected to complete under the cap, scored by weighted factors (rate 50%, proximity 20%, availability 20%, rating 10%) Given fewer than 3 vendors meet the cap When alternates are computed Then the list includes all qualifying vendors and displays "Only N match" message Given any vendor has a calendar conflict or blackout for the requested time window When alternates are computed Then that vendor is excluded For each suggested vendor Then show score, estimated cost, earliest available slot, distance, rating, and reason summary
One-Click Reassign or Request Revised Estimate
Given alternates are displayed for an over-cap request When the approver clicks "Reassign" on an alternate Then the work order vendor is switched, the earliest available slot within the tenant's availability window is booked, and tenant and selected vendor receive notifications via SMS and email within 2 minutes And the previous vendor is notified of cancellation with reason Given an over-cap request When the approver clicks "Request Revised Estimate" Then the current vendor is sent an estimate revision request containing target not-to-exceed amount, variance breakdown link, and due-by date/time; the request status moves to "Awaiting Revised Estimate" and an SLA timer (default 24 hours) starts Given a reassign or revision action completes Then the activity timeline shows the action, actor, timestamp, and before/after vendor and schedule details
Capture Decision and Reason Codes
Given an approver is taking an action on an over-cap request (approve as-is, reassign, request revision, reject) When they submit the action Then a reason code from the configurable list is mandatory and free-text notes up to 500 characters are allowed Given an action is submitted Then an immutable audit log entry is created storing actor, role, timestamp (UTC), action type, reason code, notes, cap source, cap value, estimate value, variance amount/percent, and routing path Given the audit log exists When exported as CSV Then all captured fields are present and values are correctly formatted
Fallback When No Alternates Meet Cap
Given an over-cap request where no vendors can meet the cap When alternates are computed Then the system offers options: request revised estimate from current vendor; expand cap by up to X% (configurable) for alternates; or escalate to higher approver And the default selection is "Request Revised Estimate" Given the approver chooses expand cap by Y% When alternates are recomputed Then only vendors under the expanded cap are shown and the expansion is recorded in the audit Given none of the fallback options produce a vendor Then the request remains in "Needs Action" with an escalation timer per SLA
Approver Escalation on SLA Breach
Given an over-cap request is routed to an approver with an approval SLA (e.g., 8 business hours) When the SLA elapses without action Then the request auto-escalates to the next approver in the hierarchy, and both the original and escalated approvers receive notifications via email/SMS; tenant receives an informational delay notice Given an escalation occurs Then the approval task reflects escalated status, and a calendar hold on the original vendor is either extended or released per configuration, with the action logged Given the escalated approver takes action Then the SLA timer resets and the audit trail includes the full escalation chain
Vendor Rate Catalog & Cost Estimator
"As an approver, I want reliable estimated costs based on vendor rates and typical effort so that caps are evaluated accurately before I approve."
Description

Maintain a structured catalog of vendor rates (call-out fees, hourly tiers, minimums, overtime/holiday multipliers, trip charges, and parts markup) by service category. Map issue types to typical labor/material profiles and compute an estimated total cost used by SmartCaps evaluations. Support regional tax rules, surcharges, and unit-based adjustments. Provide APIs and UI for rate updates with validation and audit. Surface estimate components on requests and approvals to improve transparency and decision quality.

Acceptance Criteria
Vendor Rate Catalog Creation & Validation
- System allows create/update of rate cards per vendor + service category + region with fields: currency, callOutFee, hourlyTiers, minimumHours, overtimeMultiplier, holidayMultiplier, tripCharge, partsMarkup, effectiveStart, effectiveEnd - Field validations: required-by-category; numeric fields >= 0; percentages 0–100; currency is ISO 4217; hourlyTiers have ascending, non-overlapping ranges; minimumHours >= 0 - Uniqueness: no overlapping effective periods for the same vendor + category + region; conflicts return 409 with conflicting period details - Versioning: each accepted change increments a monotonic version; versions are retrievable via history - Audit: create/update/delete writes audit entry with actor, action, timestamp, before/after values, and reason - Deletion is soft: in-use rates become inactive and remain visible in history; inactive rates excluded from new estimates
Issue-Type Mapping to Labor/Material Profiles
- Admins can define mappings: issueType -> {laborHours profile with tier applicability, materialsEstimate, tripCount, skill category} - Mappings support unit-based adjustments (e.g., buildingType=high-rise adds +10% labor, +1 trip); adjustments are rule-based and effective-dated - Validation enforces positive hours/costs, integer tripCount >= 0, and resolvable property attributes referenced by rules - Estimator selects the most specific matching mapping for a request (issueType + property attributes); if none found, returns 422 with missing mapping detail - Estimates embed the mappingId and mappingVersion used for traceability
Estimated Cost Computation & Breakdown
- Estimator computes: total = callOutFee + max(minimumHours, laborHoursByTier) × baseRate × applicable multipliers (overtime/holiday) + (tripCount × tripCharge) + (materials × (1 + partsMarkup)) + regional surcharges + taxes - LaborHoursByTier applies tiered pricing correctly across time blocks (split hours across tiers when thresholds are crossed) - Taxability is applied per region config (labor vs materials); rounding is round-half-up to 2 decimals per line and on totals - Output includes a structured breakdown (JSON) with components: callOut, labor detail per tier, multipliers, trips, materials, markup, surcharges, taxes by code, subtotal, total; includes rateVersion, taxConfigVersion, mappingVersion - If any required rate/tax configuration is missing, estimator returns 422 with a machine-readable list of missing configs; no partial estimate is persisted
Regional Tax & Surcharge Rules
- System supports defining regional tax rules with fields: code, appliesTo (labor/materials/both), rate%, compounding order, exemptions, effectiveStart/End - Multiple taxes (e.g., state + city) stack in configured order; exemptions on vendor or property zero-out applicable taxes - Regional surcharges can be flat or percent and may apply per trip or per job; order of application is configurable and honored in the breakdown - Tax and surcharge configurations are versioned and effective-dated; historical estimates reference the versions used at computation time
Rate Management APIs with Validation & Audit
- POST /vendors/{vendorId}/rates creates a rate card; authZ required; on success returns 201 with rateId and version; supports Idempotency-Key to ensure safe retries (duplicates return 200 with same resource) - PUT /vendors/{vendorId}/rates/{rateId} updates mutable fields and returns 200 with incremented version; identity fields (vendorId, category, region) are immutable; attempts return 422 - GET endpoints support filtering by vendor, category, region, effective date; results are paginated and include totalCount - Validation errors return 422 with field-level error codes; overlapping effective periods return 409; unauthenticated/unauthorized requests return 401/403 - All mutations write audit entries including correlationId; audits are retrievable by resourceId and time range
UI Rate Editor & Estimate Preview
- UI form supports create/edit of rate cards with inline validation and disabled Save until valid - Hourly tiers are managed interactively; UI prevents gaps/overlaps and displays computed minimum charge preview - A preview panel allows selecting issueType, property, vendor, and schedule window to render a real-time estimate breakdown matching server results within $0.01 - On Save, a confirmation summarizes changes and effective dates; success shows new version in history; server errors display inline with field associations - Form controls meet keyboard accessibility and contrast requirements; critical actions are accessible without a mouse
Estimate Surfacing & SmartCaps Integration
- Maintenance request detail and approval views display the estimate breakdown: call-out, labor by tier, multipliers, trips, materials, markup, surcharges, taxes, subtotal, total; includes IDs for rate, tax, and mapping versions - The estimated total is provided to SmartCaps; if total <= cap, system auto-approves or triggers one-tap confirm per property setting; if total > cap, request routes to the configured approver with the breakdown visible - Suggested alternates present up to 3 vendors with lower estimated totals for the same request context, sorted by total with savings deltas; selecting an alternate refreshes the estimate and SmartCaps decision within 2 seconds - SmartCaps decision logs include estimate inputs and decision outcome for auditability
Budget Alignment & Spend Reporting
"As a portfolio owner, I want to monitor spend against budgets and adjust caps accordingly so that maintenance costs stay on target."
Description

Allow setting monthly/quarterly budgets per property and category, linking SmartCaps to budget thresholds. Provide alerts when spend nears limits and visualize auto-approved vs. over-cap spend, trends, and vendor-level breakdowns. Offer filters by date range, property, issue type, and vendor, with CSV export and sharable links. Support what-if analysis to propose updated caps based on historical outcomes and seasonality.

Acceptance Criteria
Budget Setup per Property and Category
Given a property and a maintenance category exist When I create a budget with a period (monthly or quarterly), a start date, and an amount Then the budget is saved with a unique key of property+category+period+start And overlapping periods for the same key are rejected with a validation error And I can edit or archive the budget entry, with all changes recorded in an audit log (user, timestamp, old/new values) And amounts are stored in the organization currency with 2‑decimal precision
SmartCaps Budget-Aware Approval Routing
Given a SmartCap is configured for a property/category (or issue type/vendor) and a linked budget exists for the same scope and period When a new request is submitted with an estimated cost Then the system computes remaining budget for the scope and period at submission time And if estimated cost ≤ SmartCap and remaining budget − estimated cost ≥ 0, the request is auto‑approved or one‑tap confirmed per cap settings And if estimated cost ≤ SmartCap and remaining budget − estimated cost < 0, the request is routed to the designated approver as over‑budget And if estimated cost > SmartCap, the request is routed to the designated approver with suggested alternate vendors/caps And the decision record stores the applied rule (cap vs budget) and reason code
Budget Threshold Alerts and Auto-Approval Safeguards
Given budgets are active for properties and categories When cumulative spend reaches 75% or 90% of a budget within its period Then an in‑app alert is displayed and an email notification is sent to configured recipients within 5 minutes And when 100% is reached, auto‑approvals for that scope are blocked unless an override is enabled by a role with permission And alerts are de‑duplicated per threshold per scope for 24 hours and include current spend, remaining amount, and projected overrun
Spend Dashboard: Auto-Approved vs Over-Cap, Trends, and Vendor Breakdown
Given spend and budget data exist for a selected period When I open the Spend Dashboard Then I see KPIs for Total Spend, Auto‑Approved Spend, Over‑Cap Spend, and Remaining Budget for the selected scope And a trend chart can toggle daily/weekly/monthly granularity and reflects the selected filters And a vendor breakdown lists vendors ranked by spend with metrics: total spend, work order count, average ticket, auto‑approved %, over‑cap % And clicking any chart/segment filters the detailed table below to the same scope And all totals reconcile to underlying transactions within ±0.1%
Multi-Filter Spend View (Date, Property, Issue Type, Vendor)
Given transactions exist across multiple properties, issue types, and vendors When I apply any combination of date range, property, issue type, and vendor filters Then the dashboard, charts, and tables update within 2 seconds for datasets up to 50,000 transactions And the URL reflects the active filters for shareability and is restorable on reload And clearing filters restores default global scope And an empty‑state message appears if no results match, with a one‑click reset
CSV Export and Shareable Links for Filtered Reports
Given I have applied filters to the spend view When I export to CSV Then the file includes one row per transaction with columns: date, property, category/issue type, vendor, approval path (auto/over‑cap), amount, currency, work order ID, status And values are UTF‑8 encoded, timestamps in ISO‑8601 UTC, amounts to 2 decimals, and fields safely quoted And exports up to 100,000 rows complete within 10 seconds and match on‑screen totals within ±0.1% When I create a shareable link Then the link preserves current filters and dashboard state, supports optional expiration (7/30 days/custom), and can be revoked And access to the link is tokenized and logged (viewer, timestamp)
What‑If Analysis and Suggested Cap Updates
Given at least 6 months of historical requests exist for a scope When I open What‑If Analysis and select a scope (property+category or vendor), a lookback window, and seasonality option Then the system simulates outcomes under proposed cap levels using historical approval rates and seasonal multipliers and returns recommended caps that keep projected spend within budget ±5% And the output includes: recommended cap, projected auto‑approval rate, projected spend, and confidence band And I can preview applying the recommended caps to selected scopes, with a diff of current vs proposed values, and save changes with an audit entry
Decision Audit Trail & Policy Compliance
"As a controller, I want a complete audit trail of SmartCaps decisions so that I can verify compliance and resolve disputes."
Description

Record an immutable audit trail for all SmartCaps-related events, including rule creations/edits, evaluations, auto-approvals, overrides, routing, confirmations, and notifications. Store who performed the action, timestamps, the matched rule version, inputs (estimates, rates), and outputs (decision, next steps). Provide search, filters, and export to support compliance reviews and dispute resolution. Display a concise history on each request’s timeline.

Acceptance Criteria
Immutable Audit Log for SmartCaps Events
Given any SmartCaps event (rule create/edit, evaluation, auto-approval, manual override, routing, confirmation, notification) When the event is processed Then an audit entry is appended within 500 ms containing: event_type, timestamp (ISO 8601 UTC), actor_id, actor_role, actor_source (user/api/system), request_id, property_id, vendor_id (if applicable), issue_type, rule_id, rule_version, inputs (e.g., estimate_amount, vendor_rate), outputs (e.g., decision, next_steps), and correlation_id And the entry has a unique immutable id And the entry cannot be edited or deleted via UI or API And any attempt to modify or delete an entry returns 403 and creates a tamper_attempt event with actor and reason And an integrity verification endpoint for a selectable date or id range returns status OK when no tampering has occurred
Complete Decision Context Captured for Evaluations and Overrides
Given a SmartCaps evaluation is triggered for a maintenance request When the rule engine evaluates the request Then the audit entry records the matched rule_id and rule_version, thresholds/limits used, estimate_amount, comparison result (under_cap/over_cap/by how much), and decision (auto_approve/route_for_approval) And suggested alternates (if generated) are captured with rationale (e.g., lower rate, availability) Given a manual override occurs after an evaluation When the approver submits the override Then the audit entry records approver_id, approver_role, override_reason, final_decision, variance_from_rule_outcome, and time_to_decision And the correlation_id ties the evaluation, override, and subsequent notifications for the same request
Search and Filter Audit Logs for Compliance Review
Given a user with audit_view permissions is on the Audit Log screen When they apply filters for date range, property, vendor, request_id, event_type, decision_outcome, rule_id, rule_version, and actor Then the result set matches all selected filters and displays a total count And the first page of results returns within 2 seconds for datasets up to 100,000 records And results can be sorted by timestamp ascending/descending with stable ordering And selecting a row opens the related request or rule details in a new tab And when no results match, an empty state is shown with no data returned
Export Audit Logs with Fidelity
Given filters are applied to the Audit Log When the user exports to CSV or JSON Then the export contains exactly the filtered rows (no extras, no omissions) up to 100,000 rows per file; if more, a paginated export with a continuation token is provided And the export includes all standard fields (event_type, timestamp UTC ISO 8601, actor_id, actor_role, actor_source, request_id, property_id, vendor_id, issue_type, rule_id, rule_version, inputs, outputs, correlation_id, entry_id) And data types are preserved (timestamps as ISO strings, numbers as numeric, booleans as true/false) And a SHA-256 checksum for the file is generated and presented to the user And exports complete within 60 seconds for 100,000 rows or show progress and deliver via email link if longer
Per-Request Timeline Shows Concise History
Given a user views a maintenance request When the timeline panel loads Then it displays a concise sequence of SmartCaps events (evaluation, decision, override, routing, confirmation, notification) with human-readable summaries, timestamps, actor display names, and rule_version badges And each item can be expanded to reveal full audit details matching the underlying audit entries (by entry_id) And the collapsed view is limited to two summary lines per item with a Show Details control And the initial render completes within 500 ms on a typical request with up to 50 SmartCaps events
Notification and Routing Outcomes Recorded
Given a decision results in routing for approval or assignment When the routing occurs Then the audit entry records destination (queue/user/team), SLA target (if set), and next_steps Given an SMS or email confirmation is sent to a vendor or tenant When delivery status updates are received from the provider Then audit entries record channel, recipient identifier, provider_message_id, status (queued/sent/delivered/failed), timestamp per status, and error_code/message on failure And delivery status updates are reflected in the audit log within 30 seconds of provider webhook receipt And retries and final disposition (success/exhausted) are recorded as separate events with attempt counts
Roles & Permissions for SmartCaps
"As an admin, I want granular permissions for managing caps and approvals so that policies are enforced across teams and portfolios."
Description

Introduce granular, role-based permissions to control who can view, create, modify, or delete cap rules; override decisions; edit rate cards; and adjust budgets. Support portfolio scoping, approval delegation, and backup approvers for leave coverage. Integrate with SSO groups and apply least-privilege defaults. Log all permission changes for audit and quickly revoke access when needed.

Acceptance Criteria
RBAC: Manage SmartCap Rules
Given a user with permission smartcaps.rules.create scoped to Portfolio A, when they submit a valid rule for a property in Portfolio A, then the rule is created and visible with status Active and a 201 Created response. Given a user without smartcaps.rules.create, when they attempt to create a rule, then access is denied with 403 or the UI action is disabled with an explanatory message. Given a user with smartcaps.rules.update scoped to Property X, when they edit an existing rule on Property X, then the change is saved, versioned, and appears in the audit log with actor, timestamp, and diff. Given a user with smartcaps.rules.delete scoped to Property X, when they delete a rule on Property X, then the rule is soft-deleted, excluded from active evaluations within 60 seconds, and the deletion is logged. Given a user with smartcaps.rules.view scoped to Portfolio A, when they list rules, then only rules within Portfolio A are returned; direct access to rules outside scope returns 403 without leaking metadata.
Scoped Visibility by Portfolio and Property
Given a user scoped only to Portfolio A, when they search, filter, or export SmartCaps rules and budgets, then results include only entities from Portfolio A and counts do not include out-of-scope items. Given a user attempts to access a rule by ID belonging to Portfolio B, then the API/UI returns 403 and the error does not reveal the target's name or property. Given an admin adds Property X (Portfolio B) to the user's scope, when the user refreshes, then access to Property X's SmartCaps data becomes available within 60 seconds. Given an admin removes Property X from scope, when the user refreshes or performs any SmartCaps action, then access is denied within 60 seconds and active views are cleared.
Permission to Override SmartCaps Decisions
Given a user with smartcaps.override scoped to Property P, when an under-cap request auto-approves on Property P, then the user can rescind or place the request on hold within 30 minutes and must enter a reason; the override is logged. Given a user without smartcaps.override, when they attempt to override an approval or force-approve an over-cap estimate, then the action is blocked with 403 and an audit entry records the denied attempt. Given a user with smartcaps.override, when they force-approve an over-cap estimate, then routing to approvers is bypassed, notifications are sent to the original approver and requestor, and audit captures before/after states and reason. Given a user with smartcaps.override scoped to Portfolio A only, when they attempt to override a decision on Property Q outside Portfolio A, then the action is denied with 403.
Rate Cards and Budget Adjustments Permissions
Given a user with smartcaps.ratecards.edit scoped to Vendor V in Portfolio A, when they update Vendor V's rate card for properties in Portfolio A, then changes save with an effective timestamp, do not retroactively alter closed requests, and are applied to new evaluations within 60 seconds. Given a user without smartcaps.ratecards.edit, when they view rate cards, then fields are read-only and API updates return 403. Given a user with smartcaps.budgets.adjust scoped to Portfolio A, when they adjust a budget cap, then a required comment is enforced, the new cap takes effect within 60 seconds, and an audit entry records old and new values. Given a user with smartcaps.budgets.view only, when they access budgets, then they can view but cannot edit; edit controls are hidden/disabled and write attempts return 403.
Approval Delegation and Backup Approvers
Given an approver configures a delegate from 2025-09-10T00:00Z to 2025-09-20T23:59Z for Portfolio A, when an over-cap estimate requires approval during that window, then the delegate receives the task and can act per delegated permissions; actions are recorded as "on behalf of" the primary. Given multiple backup approvers are configured with priority order, when the primary is unavailable, then approval requests fan out in order and the first to act claims the task; subsequent attempts by others are prevented and notified as already-claimed. Given the delegation window ends, when a new approval arrives, then routing returns to the primary approver and any unclaimed delegated tasks re-route within 60 seconds. Given an admin revokes a delegate mid-window, then new approvals stop routing to the delegate within 60 seconds and the change is audited.
SSO Group Mapping and Least-Privilege Defaults
Given an IdP group "SmartCaps-Editors:PortfolioA" is mapped to smartcaps.rules.update for Portfolio A, when a user logs in with that group claim, then the permission is granted for Portfolio A within the session; without any mapped groups, the user has no SmartCaps permissions by default. Given a user's IdP group membership changes, then their SmartCaps permissions update on next login or within 5 minutes for active sessions, whichever is sooner. Given an IdP deactivates a user, then access to FixFlow is blocked on next token validation or within 5 minutes. Given conflicting group mappings grant and deny the same permission, then the deny rule takes precedence and is reflected in the user's effective permissions.
Audit Logging and Rapid Revocation
Given any permission change (grant, revoke, scope change), then an immutable audit record is created within 5 seconds capturing actor, target user, action, old/new values, scope, timestamp (UTC), IP/user-agent, and reason/comment. Given an auditor with smartcaps.audit.view, when they query by user or date range up to 10,000 records, then the results return within 2 seconds and can be exported to CSV with a checksum for integrity verification. Given an admin revokes a user's SmartCaps access, then the user loses the ability to view or act within SmartCaps within 60 seconds across all active sessions; subsequent attempts return 403 and are logged as denied. Given outstanding approval tasks are assigned to a revoked user, then tasks reassign to the configured backup approver or escalate per policy within 60 seconds and the reassignment is audited.

Cascade Chain

Configure sequential or parallel approver paths (owner → ops → HOA) with timeouts and fallbacks. If someone doesn’t respond, FixFlow auto-nudges, re-routes to backups, or triggers emergency policies—ensuring decisions don’t stall while preserving a clean approval trail.

Requirements

Chain Builder (Sequential & Parallel)
"As an operations manager, I want to design reusable approval chains with sequential and parallel steps so that requests follow the right path without manual coordination."
Description

Provide a drag‑and‑drop chain designer to configure sequential and parallel approver steps, with conditional routing by property, issue type, spend threshold, and HOA rules. Support reusable templates, versioning, and pre-flight validation to prevent dead ends or orphaned branches. Persist chains in a portable JSON schema with backwards compatibility and migration support. Integrate with FixFlow roles and directory to map steps to users, groups, or dynamic assignees (e.g., “property owner of Unit X”). Offer a live preview that simulates routing and shows expected SLAs per step. Ensure changes can be scheduled and safely rolled out with audit of who modified what and when.

Acceptance Criteria
Drag-and-Drop Mixed Sequential/Parallel Chain Design
Given a blank canvas, when an admin drags approver steps onto the canvas and connects them sequentially, then the system saves the order and displays the sequence correctly upon reload. Given a node configured with two or more outgoing connectors marked as parallel, when the chain is saved, then the resulting model executes those branches concurrently in simulation and marks them as parallel in the persisted JSON. Given a chain containing nested parallel groups up to 3 levels deep, when the chain is saved and reloaded, then the nested structure is preserved without loss or reordering. Given a chain with at least 30 steps, when the admin saves, then the save completes successfully and reloads without truncation or performance warnings. Given a step is repositioned via drag-and-drop, when dropped, then inbound/outbound connections remain intact and the logical execution order is updated accordingly.
Conditional Routing by Property, Issue Type, Spend Threshold, HOA Rules
Given a decision node with conditions on property, issue type, spend threshold, and HOA rule flags, when simulation inputs match a single branch, then only that branch is followed. Given multiple branches match and the node is set to Parallel mode, when evaluated, then all matching branches are spawned concurrently. Given multiple branches match and the node is set to Sequential mode with priorities, when evaluated, then only the highest-priority matching branch is chosen. Given no conditions match, when validating or simulating, then a default branch must be configured and taken; otherwise publishing is blocked with a clear error identifying the node. Given a spend threshold condition using ">=", when the spend equals the threshold, then the branch matches as expected. Given a property that requires HOA approval per rules, when evaluated, then the HOA branch is selected or included according to node mode.
Pre-Flight Validation Blocks Dead Ends and Orphaned Branches
Given a chain with any node that has no inbound path from Start, when Validate is run, then an error "Orphaned node" is reported with the node ID and publish is blocked. Given a non-terminal node with no outbound connectors, when Validate is run, then an error "Dead end" is reported with the node ID and publish is blocked. Given the graph contains a cycle without an explicit loop exit, when Validate is run, then an error "Cyclic path detected" is reported and publish is blocked. Given a decision node without a default branch and with conditions that may not match, when Validate is run, then a blocking error is reported. Given validation passes with no errors, when Publish is clicked, then the chain is publishable and a validation summary is recorded in the audit log.
Portable JSON Schema Persistence with Backward-Compatible Migration
Given a chain is saved, when Export is clicked, then a JSON document is produced conforming to the current schema version (e.g., schemaVersion: "1.x") including nodes, edges, conditions, assignee bindings, SLAs, metadata, and version info. Given a JSON file in a prior supported schema version is imported, when Import is completed, then a migration is applied and the resulting chain simulates equivalently to the source under identical inputs. Given a chain is exported and then immediately re-imported without edits, when compared, then the model round-trips without loss of information (allowing for metadata such as timestamps). Given an import requires migration, when completed, then a migration report is stored with details of transformed fields and any warnings. Given the schema version is incremented for a non-breaking change, when older chains are loaded, then they continue to load without requiring edits.
Role/Directory Mapping to Users, Groups, and Dynamic Assignees
Given an approver step, when the admin searches in the assignee field, then user and group suggestions from the FixFlow directory are returned and selectable. Given a step is mapped to a user, when simulated, then the assignee resolves to that user ID. Given a step is mapped to a group, when simulated, then the assignee resolves to the group entity and runtime expansion rules are recorded in the model. Given a step is mapped to a dynamic assignee (e.g., "property owner of Unit X"), when simulated with a property context, then the assignee resolves correctly using directory data. Given a mapped user is deactivated or a dynamic assignee cannot be resolved for the provided context, when Validate is run, then a warning is shown and publish is blocked unless a backup assignee is configured.
Live Preview Simulation with Path and Step SLAs
Given property, issue type, and spend inputs are provided, when Simulate is clicked, then the preview highlights the chosen path(s) and displays an SLA for each step in hours or days. Given a sequence of steps with SLAs 2h and 3h, when simulated, then the cumulative sequential SLA is shown as 5h. Given a parallel group with step SLAs 2h and 4h, when simulated, then the group completion SLA is shown as 4h (max of the parallel branches) and contributes accordingly to the overall SLA. Given a step SLA is edited in the properties panel, when simulated again, then the preview updates immediately to reflect the change. Given no route matches the inputs, when Simulate is clicked, then the preview shows a clear warning and points to the decision node requiring a default or adjusted conditions.
Scheduled Rollouts, Templates, Versioning, and Audit Trail
Given a configured chain, when saved as a template with name and tags, then it appears in the template library and can be instantiated to new chains. Given an existing chain, when a new version is published, then the version number increments and prior versions remain read-only and recoverable. Given a draft version, when scheduled for activation at a specific date/time with a defined timezone, then the version becomes active at that time without manual intervention and the change is recorded. Given a scheduled activation exists, when the admin cancels or reschedules before activation, then the schedule updates accordingly and the audit log reflects the change. Given two versions are compared, when Diff is requested, then the UI lists changes to nodes, edges, conditions, assignees, SLAs, and metadata. Given any create, edit, publish, import, migration, or schedule action occurs, when completed, then an immutable audit entry is written capturing who, what, when, and before/after summaries.
Status
To Do
Step Timeouts & Escalations
"As a property manager, I want timeouts and automatic escalations per approver step so that approvals keep moving when someone is unavailable."
Description

Allow per-step timeouts defined in business hours with calendar/holiday awareness, after which the system automatically executes a configured action (nudge, escalate to backup, auto-advance, or trigger emergency policy). Support absolute and relative timers, grace periods, and step-level overrides. Provide snooze/resume for approvers with full visibility into remaining time. Log all timeout and escalation events in the audit trail and expose them via webhook/API for integrations. Ensure deterministic resolution when multiple escalation rules could apply, with a clear precedence model.

Acceptance Criteria
Business-Hour Timeout with Holiday Calendar Awareness
Given an approval step configured with a timeout of 8 business hours And organization business hours are Mon–Fri 09:00–17:00 in America/Chicago And the organization holiday calendar contains 2025-09-01 as a full-day holiday And the step is activated on 2025-08-29 (Friday) at 15:00 local time When the system calculates the timeout deadline Then the deadline is set to 2025-09-02 (Tuesday) at 12:00 local time And no timeout action is executed before the deadline When the deadline elapses without a decision Then the configured timeout action executes exactly once within 60 seconds And the audit trail records the computed deadline, action taken, and timestamps
Absolute vs Relative Timers with Step-Level Override
Given a workflow with a default relative timeout of 12 business hours And Step B overrides with an absolute deadline of 2025-09-12 17:00 local time And Step C overrides with a relative timeout of 4 business hours When Step B activates on 2025-09-12 09:00 local time Then Step B's deadline is 2025-09-12 17:00 local time When Step C activates on 2025-09-10 16:00 local time Then Step C's deadline is 2025-09-11 12:00 local time And for each step the audit trail stores timer_type (absolute|relative), configured value, and computed deadline
Grace Period and Post-Timeout Action Execution
Given a step timeout configured as 2 business hours with a 30-minute grace period And the timeout action is "Escalate to backup approver" When the computed deadline passes without decision Then the system delays action until the grace period expires And if no decision is recorded during the grace period, the system escalates to the backup approver And exactly one escalation notification is sent to the backup via the configured channels And if a decision is recorded during the grace period, no escalation occurs and the grace period is canceled And the audit trail logs grace_period_started_at, grace_period_ended_at, and final action outcome
Snooze and Resume with Remaining Time Visibility
Given an approver viewing an active step with 3h 20m remaining business time When the approver snoozes the step for 1 business hour Then the countdown pauses and remaining time is unchanged while snoozed And the UI displays "Snoozed" and the time at which the snooze ends in local business time When the snooze expires or the approver resumes manually Then the countdown resumes with 3h 20m remaining, recalculated in business hours And the audit trail records SNOOZE and RESUME events with actor, timestamps, and remaining_time_before/after And the remaining time is visible to all authorized viewers within 1 second of change
Deterministic Precedence for Conflicting Escalation Rules
Given a step where multiple escalation rules are eligible at timeout And the precedence order is defined as Emergency Policy > Escalate to Backup > Auto-Advance > Nudge When the step times out and all four rules would otherwise apply Then only the Emergency Policy action executes And a single outcome is recorded with the chosen rule_id and precedence_reason And the audit trail includes the evaluated rules list with their ranks and conditions And no lower-precedence actions are executed or queued
Webhook and API Exposure of Timeout and Escalation Events
Given a subscribed webhook endpoint that responds 200 OK When a timeout, nudge, escalation, auto-advance, snooze, or resume event occurs Then the system sends a webhook within 5 seconds containing event_id, event_type, step_id, workflow_id, rule_id (if any), timestamps (scheduled_at, fired_at), actor (if any), and computed_deadline And each event_id is unique and idempotent for retries And if the endpoint responds non-2xx, the system retries up to 6 times with exponential backoff And the same events are retrievable via the API within 10 seconds with filters for workflow_id, step_id, event_type, and date range
Auto-Nudge, Escalate, Auto-Advance, and Emergency Action Behavior
Given four steps configured respectively to Nudge, Escalate to Backup, Auto-Advance, and Trigger Emergency Policy on timeout When each step times out without a decision Then the Nudge step sends an SMS and email to the assigned approver containing the step link and any remaining grace information And the Escalate step reassigns to the configured backup and notifies the new assignee via the configured channels And the Auto-Advance step records an approval per configured policy and moves the workflow to the next step And the Emergency Policy step invokes the configured emergency handlers and flags the workflow as emergency And each action is executed exactly once and within 60 seconds of timeout And all actions record structured audit entries with action_type, recipients (if any), and outcomes
Auto‑Nudge Notifications
"As an approver, I want timely reminders with quick action links so that I can respond fast without logging into the dashboard."
Description

Send configurable reminder cadences via SMS, email, and in-app notifications with unique, expiring one-click approve/deny links and optional reason capture. Stop reminders immediately upon response and throttle to prevent spam; include quiet hours and localization. Provide delivery status, read receipts where supported, retry with channel failover, and rate limiting. Surface nudge history in the request timeline and expose templates manageable by non-technical users. Secure links with signed tokens and role checks to prevent unauthorized actions.

Acceptance Criteria
Multi-Channel Reminder Cadence with Quiet Hours and Throttling
Given an approver has not responded for 30 minutes after assignment and their preferred channel is SMS When the cadence is active Then send a reminder via SMS and schedule the next attempt for 30 minutes later Given no response after the second attempt When the third attempt is due Then send via the next configured channel (Email) and mark the channel switch in logs Given the recipient’s local time is within quiet hours (21:00–08:00) When a nudge becomes due Then defer delivery to 08:00 local time plus a random jitter of 0–5 minutes and record the deferral Given throttling is enabled at max 3 nudges per approver per request per rolling 24 hours When the limit is reached Then suppress additional nudges and log a throttled event Given the cadence completes its max 3 attempts without response When the window closes Then stop further nudges for this step and expose a "cadence exhausted" status in logs and timeline
One-Click Approve/Deny Links with Expiration and Reason Capture
Given a nudge is sent When the recipient clicks the Approve one-click link within 24 hours Then the approval for the current step is recorded, the link is invalidated, and the user sees a success confirmation screen within 2 seconds Given the Deny one-click link is clicked within 24 hours and reason capture is required When the reason modal is submitted Then the denial and the provided reason (minimum 5 characters) are stored and audited Given a link is clicked after 24 hours or after an action was already taken When the URL is visited Then no state changes and a "Link expired" page is shown with a safe re-auth option Given links contain signed tokens scoped to tenant, request, step, and recipient When a token with invalid signature, wrong role, or mismatched step is presented Then return HTTP 403 with no side effects and record a security event Given multiple links from separate nudges exist When any one link is used to take action Then all other outstanding links for the same step are invalidated within 5 seconds
Delivery Status, Read Receipts, Retry, and Channel Failover
Given SMS is the primary channel When the provider returns a hard failure or no delivery confirmation within 5 minutes Then trigger an email nudge within 60 seconds and tag it as "failover" Given email supports read tracking When the recipient opens the email Then capture a read receipt and display "Read" in the timeline within 30 seconds; where unsupported, omit read status without error Given an outbound rate limit of 60 messages per tenant per minute When nudges exceed this rate Then queue the overflow with exponential backoff (2x) up to 3 retries and surface "rate-limited" events in logs and timeline Given an in-app notification channel is available When both SMS and email fail Then deliver an in-app nudge and show an inbox badge within 10 seconds of the last failure Given a provider outage is detected When retries are scheduled Then switch to the next healthy provider/channel automatically and annotate the failover path in logs
Immediate Cancellation of Reminders Upon Response Across Chain
Given an approver responds via any channel When the system records the action Then cancel all pending and scheduled nudges for that approver and step within 10 seconds and prevent any further sends Given the step escalates to a backup approver due to timeout When escalation occurs Then immediately cancel remaining nudges to the original approver and start the backup’s cadence per configuration Given the chain advances to the next step When the next step becomes active Then ensure no nudges for prior steps are sent and any queued ones are purged with a "step closed" reason logged Given duplicate responses arrive from one-click links due to latency When the second response is processed Then the system is idempotent and records no duplicate state changes, logging the duplicate attempt
Localized, Non-Technical Template Management
Given a Template Manager edits the "Approval Reminder" template for en-US and es-ES in the WYSIWYG editor When saving Then validation ensures required placeholders {request_id}, {due_at}, and {action_links} are present and rejects if missing with clear errors Given a user previews the template When previewing Then sample data renders correctly and action links display the correct localized labels Given a recipient’s preferred locale is es-ES When the nudge is sent Then the es-ES template is used; if not found, fallback to en-US and record the fallback event Given inputs include rich text When saving a template Then strip or reject disallowed tags and scripts and store only allowed formatting Given a test send is requested from the editor When sent Then the message is delivered through all enabled channels within 60 seconds and marked as "test" in the timeline
Nudge History Visible in Request Timeline
Given a request has generated nudges When viewing the request timeline Then each nudge entry shows timestamp, recipient, channel, template name, attempt number, delivery status, read status (where supported), action taken, link clicked, and any captured reason Given a user filters by channel=SMS and recipient=Approver A When filters are applied Then only matching nudge entries are shown within 2 seconds Given provider metadata (message ID, error code) exists When expanding a nudge entry Then raw metadata is viewable to users with Support or Admin roles only Given auditability requirements When timeline entries are displayed Then entries are immutable (read-only) and ordered newest-first
Fallback Routing & Backup Approvers
"As an owner, I want backups and fallback routing when primary approvers don’t respond so that urgent maintenance isn’t delayed."
Description

Enable configuration of backup approvers per step with quorum rules (any-of, all-of, N-of-M) and round-robin options to distribute load. Detect out-of-office and calendar conflicts to preemptively route to backups and prevent stalls. Allow safe reassignment of in-flight approvals while preserving decision context and preventing duplicate approvals. Notify all affected parties of reroutes and ensure permissions are enforced at each hop. Provide guardrails such as maximum hops and loop detection.

Acceptance Criteria
Configure Backup Approvers with Quorum and Routing Options
Given I am an Org Admin editing Step X in a Cascade Chain When I add three backup approvers (B1, B2, B3) and select quorum rule "N-of-M" with N=2 and routing mode "Round-robin" Then the system validates that 1 ≤ N ≤ M, prevents save on invalid values with inline errors, and persists the configuration with a timestamped audit entry referencing Step X Given the step configuration is saved When I reopen Step X or fetch via API Then the response includes backups=[B1,B2,B3] in configured order, quorum={type:"N-of-M", N:2, M:3}, routingMode:"Round-robin"
Quorum Decision Processing
Given Step X is configured with quorum rule Any-of over backups {B1,B2,B3} When any one authorized backup (e.g., B2) submits an approve action for the current request Then the step is marked Approved, further approvals on the same step are rejected with HTTP 409 and idempotent messaging, and the audit trail records the deciding approver and quorum rule satisfied Given Step Y is configured with quorum rule All-of over backups {C1,C2} When only C1 approves Then the step remains Pending and clearly indicates remaining approver(s); when C2 also approves, the step transitions to Approved Given Step Z is configured with quorum rule N-of-M where N=2 over {D1,D2,D3} When approvals are received from any two distinct authorized users among D1..D3 Then the step transitions to Approved and any subsequent approvals are blocked with a "quorum met" reason
Round-Robin Backup Distribution at Runtime
Given Step X has backups {B1,B2,B3} and routing mode Round-robin with pointer initially at B1 When three new approval requests enter Step X sequentially Then the assignees are B1, then B2, then B3 respectively, and the rotation pointer advances after each assignment Given B2 is temporarily unavailable (OOO flag true) When the next request is assigned Then the assignee skips B2 and goes to B3, and the pointer reflects the skip without breaking rotational fairness Given 10 requests are processed with B2 intermittently unavailable When distribution is analyzed Then each available backup’s assignments differ by no more than 1 under identical availability windows
Preemptive Reroute on Out-of-Office and Calendar Conflicts
Given the primary approver P has an active OOO event covering the next 24 hours When a new approval for Step X is created Then the system assigns the request directly to the next eligible backup per routing rules, records the OOO bypass in the audit trail, and notifies P and the new assignee Given the primary approver P has a calendar conflict overlapping ≥ 75% of the SLA response window for Step X When the approval is generated Then the request is preemptively routed to backup, with a notice citing the conflict window and SLA threshold Given a reroute occurs due to OOO or conflict When the reroute is executed Then only users with approval permission for Step X can act on the approval, and all affected parties (original assignee, new assignee, requester) receive notifications via configured channels
Timeout-Based Fallback with Auto-Nudges
Given Step X has an approval timeout T=4 hours and nudge cadence every 1 hour When an approval remains pending Then nudge notifications are sent at 1h, 2h, and 3h to the current assignee and watchers, and events are logged Given the approval reaches timeout (≥4h) without decision When the timeout elapses Then the system reroutes to the next eligible backup based on the configured routing mode, resets the timeout clock, records the hop count increment, and notifies all affected parties of the reroute and new SLA Given an approval has been rerouted due to timeout When the previous assignee attempts to act Then the action is rejected with a 403 Unauthorized or disabled UI state indicating the approval has been reassigned
Safe Reassignment of In-Flight Approvals
Given an approval is pending with assignee A and includes comments, attachments, and activity history When a user with role Owner or Ops Admin triggers a manual reassignment to user B Then the system transfers assignee to B, preserves all decision context (comments, attachments, audit), revokes A’s ability to act, and appends a reassignment audit entry with reason and initiator Given a reassignment occurs while A submits an action concurrently When the system processes both events Then exactly one decision is accepted using idempotency tokens, the losing action returns a conflict response with a "reassigned" reason, and no duplicate approvals are recorded Given B lacks permission to approve Step X When a reassignment to B is attempted Then the system blocks the reassignment with a validation error and suggests eligible approvers
Guardrails: Maximum Hops and Loop Detection
Given Step X has max_hops set to 3 When an approval is rerouted due to consecutive timeouts/availability beyond three hops Then the system halts further rerouting, escalates per policy (e.g., assign to Emergency Approver or mark Needs Attention), and records the terminal state in the audit trail Given a backup configuration creates a potential cycle (e.g., A→B→C→A) When the chain is validated or a reroute is attempted Then the system detects the loop, prevents applying a cyclic path, and prompts the admin to resolve the configuration with a descriptive error Given hop count increases on each reroute When reporting is generated Then hop_count and loop_detection events are available for analytics and monitoring
Approval Audit Trail & Visualization
"As a compliance stakeholder, I want a clear, exportable approval trail so that I can verify decisions and resolve disputes."
Description

Record an immutable, tamper-evident event log for each request capturing step transitions, approver identity, decision, timestamps, channel, comments, nudges, timeouts, and escalations. Present a visual timeline and current-chain status within the request detail view, with filters and search. Support export to CSV/PDF and an API endpoint for audits and HOA compliance reviews, with PII redaction and role-based visibility controls. Hash events for integrity verification and include chain definition version used at decision time. Ensure storage retention policies are configurable.

Acceptance Criteria
Audit Log Event Capture Completeness
Given a maintenance request using Cascade Chain with multiple approvers and actions across web, SMS, and system channels When approvals, rejections, step transitions, comments, nudges, timeouts, and escalations occur Then the audit log records exactly one event per action with fields: event_id, request_id, step_id, step_name, approver_id, approver_role, decision (approve|reject|n/a), channel (web|sms|api|system), comment_text, timestamp (ISO-8601 UTC, ms precision), actor_type (user|system), nudge_type, timeout_seconds, escalation_target, chain_definition_version And timestamps are monotonic non-decreasing within the request And the current-chain status computed from the log matches the Cascade Chain engine state
Tamper-Evident Hash Chain Integrity
Given persisted audit events for a request When each event stores content_hash = SHA-256(canonicalized event payload) and prev_hash referencing the prior event (or null for genesis) Then recomputing hashes over the sequence yields the stored content_hash values for all events And modifying any persisted field in any event causes verification to fail and marks integrity_status = "failed" with first_bad_event_id recorded And the UI, export, and API expose an integrity verification result (pass|failed) for the request
Visual Timeline and Current Step Accuracy in Request Detail
Given a request with at least ten audit events including transitions, nudges, timeouts, and escalations When a user opens the request detail view Then the timeline displays events in chronological order with type icons, actor, channel, decision, and exact timestamp And the current approver and step are highlighted and match the Cascade Chain engine state And relative durations between consecutive events are shown And PII fields are masked for users without PII_VIEW permission And the timeline renders within 1 second for requests with up to 2,000 events
Timeline Filters and Search
Given a request timeline with a variety of event types and actors When the user filters by event_type (decision|nudge|timeout|escalation|comment), actor, channel, and date range Then only matching events are displayed and the visible count updates accordingly And searching by keyword returns events whose comment_text or actor display name contains the term (case-insensitive) And clearing all filters restores the full event list And for >2,000 events the UI paginates server-side in pages of 100 and applies filters server-side with updates within 1 second
CSV and PDF Export with Role-Based PII Redaction
Given a user with AUDIT_EXPORT permission requests an export from the request detail view When exporting to CSV or PDF with optional filters applied Then the file includes events with columns: event_id, timestamp, step_name, approver_role, decision, channel, comment_text (redacted), nudge_type, timeout_seconds, escalation_target, chain_definition_version, integrity_verification_status And PII fields (e.g., emails, phone numbers, tenant names) are masked unless the user has PII_VIEW permission And the export completes within 5 seconds for up to 10,000 events and reflects the applied filters And the generated file name includes request_id and generated_at timestamp
Audit API Endpoint Security, Filtering, and Redaction
Given an API client with a valid token scoped to audit:read for the tenant When it calls GET /api/v1/requests/{id}/audit with event_types, actor, channel, start, end, page, and page_size parameters Then a 200 response returns JSON with events, pagination (page, page_size, total), and integrity_verification_status And PII fields are redacted unless the token includes pii:view And invalid or missing token returns 401, insufficient scope returns 403, not-found or cross-tenant request returns 404 And responses include ETag and support If-None-Match with 304 when unchanged And the 95th percentile response time is ≤800 ms for requests with up to 5,000 events and simple filters
Retention Policy Configuration and Purge Auditing
Given an organization sets audit_log_retention to 7 years and places specific requests on legal hold When the scheduled retention job runs Then audit events older than 7 years that are not on legal hold are purged And a purge summary audit entry is recorded with org_id, time_window, count_purged, and actor_type=system And purged events no longer appear in UI, exports, or API results And changes to the retention setting are audited with who, when, old_value, new_value and validated to allowed values [1,2,3,5,7,10,Forever]
SLA Metrics & Bottleneck Analytics
"As an ops lead, I want analytics on approval chains so that I can spot bottlenecks and improve response times."
Description

Capture per-step and end-to-end metrics such as time to first response, average decision time, nudge count, escalation rate, and timeout frequency. Provide dashboards segmented by property, vendor, owner, HOA, and chain template, with trend lines and percentile views. Generate alerts for SLA breaches and recommendations to adjust timeouts, backups, or nudge cadence where bottlenecks are detected. Support A/B testing of notification cadence and comparative reporting across versions of a chain. Respect data retention and access controls.

Acceptance Criteria
Per-Step and End-to-End SLA Metric Capture
Given a Cascade Chain instance with multiple approver steps is started When each step receives events (first view, decision, nudge sent, escalation routed, timeout reached) Then the system records for each step: first_response_at, decision_time_ms, nudge_count, escalated_flag, escalated_at, timed_out_flag, timed_out_at using ISO 8601 UTC timestamps And computes chain-level metrics: time_to_first_response, end_to_end_time_ms, average_decision_time_ms across completed steps And persists/updates these metrics within 120 seconds of the triggering event And metrics are idempotent when events are replayed and survive service restarts
Dashboard Segmentation, Trends, and Percentiles
Given a user opens the SLA Analytics dashboard and selects a date range When the user applies filters by property, vendor, owner, HOA, and chain template individually and in combination Then the dashboard returns counts and metrics scoped to the selected segments with 100% filter accuracy And renders time-series trend lines for 7, 30, and 90 days And renders percentile views for P50, P90, and P95 per metric And 90th percentile dashboard loads within 3 seconds for segments with up to 10k chain instances And displayed values match backend aggregates within ±1% or ±1 second, whichever is larger
SLA Breach Alerts and De-duplication
Given SLA thresholds are configured for time_to_first_response and step_timeout When a chain instance exceeds a threshold Then an alert is generated within 5 minutes containing property, chain_id, step (if applicable), metric name, current value, threshold, and a deep link to the instance And the alert is delivered via the recipient’s configured channels (in-app, email, SMS) And no more than one active alert exists per chain_id+metric within a 24-hour window unless severity increases by ≥10% And acknowledging the alert stops further notifications; lack of acknowledgment triggers escalation to the backup route per policy And all alert lifecycle events are audit-logged with timestamp and actor
Bottleneck Detection and Actionable Recommendations
Given 90 days of SLA data with at least 30 instances for a step or segment When the step’s P90 decision_time exceeds its SLA by >20% or its timeout_frequency exceeds 5% Then the system flags the step as a bottleneck and generates at least one recommendation (e.g., reduce timeout, add backup approver, increase nudge cadence) And each recommendation includes estimated impact, underlying evidence (metrics and sample size), and a confidence score (0–1) And users can Apply or Dismiss a recommendation; applied changes update the chain template and are logged with before/after values And the dashboard tracks post-change impact with a before/after comparison over a configurable window (default 14 days)
A/B Testing Notification Cadence and Comparative Reporting
Given an experiment is created on a chain template with two notification cadence variants (A and B) and a 50/50 allocation When new chain instances start under that template Then instances are randomly assigned to A or B with sample ratio within ±5% after 200 assignments And emergency policy routes are excluded from the experiment And the experiment report shows time_to_first_response, nudge_count, escalation_rate, and decision_time percentiles by variant with uplift and p-values And the system prevents declaring a winner until minimum sample size and significance threshold are met And the user can stop the experiment and roll out the winning variant to 100% with a single action; all assignments and outcomes are audit-logged
Data Retention, Privacy, and Access Controls
Given data retention is configured to 24 months When retention is reached or a property-level purge is requested Then all associated SLA metrics and raw events are deleted within 24 hours and excluded from dashboards and exports And segments with fewer than 5 instances in the selected date range are suppressed to prevent re-identification And access is enforced: Admins can view all analytics; Property Managers/Owners see only their properties; HOA reps see only HOA-step metrics; Vendors see only their own aggregated metrics; others receive 403 with no data leakage And every analytics query and export is audit-logged with requester, scope, timestamp, and record count
Comparative Reporting Across Chain Template Versions
Given a chain template has multiple published versions When a user selects two versions and a date range with overlapping properties/vendors Then the dashboard displays side-by-side metrics (time_to_first_response, average_decision_time, nudge_count, escalation_rate, timeout_frequency, end_to_end_time_ms) with absolute and percent deltas And allows normalization by property and vendor to control for mix shift And flags statistically significant differences when p < 0.05 with per-version sample size ≥ 100 And provides links to underlying instances with access controls enforced
Emergency Policy Triggers
"As a property manager, I want automatic emergency handling when approvals stall or severity is high so that critical work starts immediately without risking safety or damage."
Description

Offer a rules engine to classify and act on emergencies based on issue type, keywords, severity flags, photo evidence, or breached timeouts. When triggered, automatically approve predefined spend caps, dispatch preferred vendors, reserve calendar slots, and issue SMS confirmations to tenants and stakeholders. Create a retroactive approval path for record-keeping and accountability, with clear guardrails (max spend, required documentation, and notification acknowledgments). Allow configuration per property/HOA and provide a manual override/pause. Log all automated actions in the audit trail and expose real-time alerts.

Acceptance Criteria
Rule-Based Emergency Classification and Triggering
Given a property/HOA with emergency rules configured (issue types, keyword list, severity flag, photo requirement, timeout thresholds) When a maintenance request is created or updated Then the rules engine evaluates within 5 seconds And if any rule matches (issue type matches configured list OR at least one configured keyword appears in the title/description OR severity flag = true OR SLA timeout is breached OR photos present when a photo-based rule is configured), the request is marked Emergency and the emergency workflow is triggered And the matched rule identifier, matched fields, and timestamp are recorded on the request And if the request is already marked Emergency, no duplicate trigger occurs (idempotent)
Auto-Approval with Spend Cap Enforcement
Given a triggered emergency and a configured emergency spend cap for the property/HOA When the system generates an approval Then an approval record is created within 10 seconds with approved_amount <= configured spend cap And if the estimated cost exceeds the cap, the system approves up to the cap and flags the overage for manual approval in the Cascade Chain And the approval record links to the incident and planned vendor dispatch And the system prevents any automated commitment that would exceed the cap without explicit manual override
Preferred Vendor Dispatch with Calendar Reservation and Fallbacks
Given an Emergency incident with a derived service category When the system dispatches a vendor Then the top-ranked preferred vendor for the property/category is selected And a job offer is sent via SMS (and email fallback) containing incident details, spend cap, and proposed time And a tentative calendar hold is placed for the earliest emergency window without double-booking the vendor or unit And if the vendor does not accept within 5 minutes, the system auto-nudges once and then routes to the next backup vendor And if all preferred vendors are exhausted or reject, the system marks status as Unassigned - Emergency and alerts ops for manual intervention
SMS Confirmations and Acknowledgments to Tenants and Stakeholders
Given an Emergency incident with dispatch initiated When notifications are sent Then SMS messages are delivered to the tenant and configured stakeholders (owner, ops, HOA as applicable) within 60 seconds including vendor name (or Unassigned), ETA/slot, spend cap, and support link And delivery status and timestamps are recorded for each recipient And owner/ops are prompted for acknowledgment; any ACK/YES reply is captured and associated to the incident And if SMS fails after 2 retries or is undeliverable, an email fallback is sent and a dashboard alert is raised
Retroactive Approval Path with Guardrails and Required Documentation
Given automated spend approval and dispatch have occurred for an Emergency When creating the retroactive approval path Then a Cascade Chain is instantiated within 2 minutes using the configured approver path (sequential/parallel) And the chain includes auto-approval details (amount, cap, reason, matched rules) and links to evidence And incident closure is blocked until required documentation is attached (vendor invoice, before/after photos, vendor report) and at least one stakeholder acknowledgment is recorded And if cumulative costs exceed the configured cap, the overage is routed for manual approval before closure And all approver decisions and timestamps are recorded in the incident history
Per-Property/HOA Configuration and Manual Override/Pause
Given an admin with permissions When configuring Emergency policies for a property/HOA Then they can set rules (issue types, keywords, severity flag use, photo requirement, timeout thresholds), spend caps, preferred vendor lists/ranking, notification recipients, and escalation order And changes are versioned with effective dates and are audit logged with actor and timestamp And in-flight incidents are unaffected by new configurations unless Apply Now is explicitly selected And for any active Emergency, an authorized user can Pause automation, Resume, or Override specific automated decisions with a required reason And while Paused, no new automated actions execute; upon Resume, only pending actions run
Audit Trail Logging and Real-Time Alerts
Given any automated Emergency action (classification, approval, dispatch, notification, pause/resume) When the action executes Then an audit entry is written with actor=System, action type, payload snapshot, related IDs (incident, request, vendor), and UTC timestamp to the second And audit entries are immutable and filterable by incident, property, action type, and date range And a real-time alert updates the dashboard within 10 seconds reflecting the current incident status and next step And users can subscribe/unsubscribe to alert channels per property And exporting the incident timeline to PDF/CSV includes all automated actions, notifications, acknowledgments, and approvals

Estimate Compare

See side-by-side estimates with line-item diffs, annotated photos, historical price benchmarks, and vendor performance scores. Highlights what really changed (labor hours, parts costs, trip fees) so you can pick the best value with a single tap—cutting overpay and second-guessing.

Requirements

Estimate Normalization Engine
"As a property manager, I want estimates normalized into consistent line items so that I can compare bids fairly regardless of vendor format."
Description

Automatically ingest and standardize vendor estimates from PDFs, emails, images, or manual entry into a unified schema with consistent line-item structure, units, quantities, labor, parts, fees, taxes, and currency handling. Detect and align equivalent items across estimates using fuzzy matching and categorization, flagging missing or ambiguous data for user confirmation. Support attachments, vendor metadata, and versioning to track revisions. Provide validation rules and error reporting to ensure data quality and readiness for side-by-side comparison within FixFlow.

Acceptance Criteria
Multi-Source Estimate Ingestion to Unified Schema
- Given a vendor estimate PDF <= 10 pages at >= 300 dpi, when uploaded via the web UI, then parsing completes within 15 seconds and a normalized estimate record is produced with zero blocking validation errors. - Given an email with an attached PDF or inline HTML estimate forwarded to the ingest address, when received, then a normalized estimate is created within 60 seconds and linked to email metadata (from, subject, received_at). - Given an image estimate (JPG/PNG/HEIC) at >= 200 dpi, when uploaded, then OCR extracts totals and tax numeric fields with >= 98% accuracy on the reference test set. - Given a manual entry with at least one line item, when saved, then the system produces the same normalized schema and flags the source as user-entered.
Line-Item Normalization and Financial Consistency
- Given an ingested estimate with mixed unit notations (e.g., ea/each, hr/hour), when normalized, then units are mapped to the canonical unit set and quantities are numeric with up to 3 decimal places. - When normalization completes, then labor, parts, fees, discounts, and taxes are separated into explicit fields per line item. - Then computed subtotal + tax - discounts equals vendor total within the greater of $0.01 or 0.1%; otherwise a validation error is raised with field-level details. - Then every line item has category, description, unit, quantity, and unit_price populated or explicitly flagged as missing for review.
Currency Detection and Normalization
- Given an estimate in USD, CAD, or EUR, when ingested, then currency_code is detected from symbols/content and stored with all monetary fields in original currency. - When normalization completes, then monetary fields are also stored in the account default currency using an exchange rate timestamped to retrieval time; rounding uses banker's rounding to 2 decimals. - Given FX service is unavailable, when normalization runs, then a cached rate not older than 24 hours is used and a non-blocking warning is recorded; if no cached rate exists, a blocking validation error is raised. - Given line items contain mixed currencies, when detected, then ingestion fails with an error prompting the user to select a single currency or split the estimate.
Fuzzy Matching to Align Equivalent Line Items Across Estimates
- Given two or more normalized estimates linked to the same work order, when alignment runs, then equivalent line items are matched with F1 >= 0.90 on the curated test set using description, category, unit, and quantity features. - Then aligned items produce a diff that isolates changes by component (labor_hours, labor_rate, parts_cost, fees, tax) with per-field deltas. - Then items with no confident match (score < 0.70) are marked as unmatched and surfaced for user review. - Then duplicate line items within a single estimate (same normalized key) are merged or flagged according to the deduplication rule and recorded in the audit trail.
Missing or Ambiguous Data Flagging and User Confirmation
- Given any required field is missing or ambiguous after normalization, when validation runs, then the estimate status is set to Needs Review and each issue is listed with field path and reason. - When a user supplies confirmations or edits, then a new version is created, the issues are cleared, and the estimate transitions to Ready for Compare if no blocking validations remain. - Given a user dismisses a non-blocking warning, when saved, then the waiver is recorded with user, timestamp, and reason and does not reappear for that version.
Attachments, Vendor Metadata, and Versioning
- Given a source file (PDF/IMG/Email), when ingested, then the original file is stored as an immutable attachment with type, size, and checksum. - Given vendor details are present (name, contact, license number if provided), when parsed, then they populate vendor metadata and link to an existing vendor record or prompt selection/creation if multiple candidates score within 0.1 of each other. - When a revised estimate for the same vendor and work order is uploaded, then a new version is created with an incremented version number, timestamps, and an auto-generated change summary between versions.
Validation Rules, Error Reporting, and Compare Readiness
- When normalization completes, then validation rules run and return a machine-readable result including pass/fail, error codes, messages, and JSON field paths within 2 seconds for estimates with <= 100 line items. - Given all blocking validations pass or are explicitly waived, when marked ready, then the estimate is flagged Ready for Compare and is selectable in the Estimate Compare UI/API. - Given blocking validations exist, when user attempts to compare, then the system prevents progression and displays the blocking issues with direct links to the affected fields. - When requested via API, then the normalized estimate exports as JSON conforming to the published schema version with a successful 200 response.
Side-by-Side Diff View
"As a landlord, I want a clear side-by-side view of estimate differences so that I can quickly see what drives price changes and choose confidently."
Description

Present selected estimates for the same work order in a responsive, columnar view that aligns comparable line items and highlights differences in labor hours, parts costs, trip fees, taxes, and totals. Provide visual diff cues, per-line variance percentages, and a summary of key cost drivers to clarify what changed and why pricing differs. Allow expand/collapse of item groups, filter by category, and handle missing items gracefully with placeholders. Persist user selections and integrate with approval actions from the comparison screen.

Acceptance Criteria
Responsive Columnar Alignment of Selected Estimates
Given a user selects two or three estimates for the same work order When the user opens the Side-by-Side Diff View on a desktop (>=1024px) Then each estimate renders as a separate column with a sticky header showing vendor name, total, and taxes, and comparable line items align row-by-row by normalized item key (case-insensitive name trimmed + SKU if present; else category + description fingerprint) And unique items in any estimate display a neutral placeholder in other columns without breaking row alignment And resizing the browser maintains column alignment and sticky headers without requiring page reload And on tablet (768–1023px) at least two columns are visible with horizontal scroll to access additional columns And on mobile (<768px) one column is visible at a time with swipe or tab controls to switch columns, preserving row alignment and placeholders
Visual Diff Cues and Per-Line Variance Calculations
Given aligned line items across the selected estimates When values differ for labor hours, parts costs, trip fees, taxes, or line totals Then the lowest value for that line is marked as Lowest with a green cue, and higher values are highlighted in red with an up-arrow And each non-lowest column displays an absolute variance (currency or hours) and a percentage variance relative to the lowest value: (value − lowest) / lowest, rounded to 1 decimal place And if the lowest value is 0 or the line is missing in a column, the percentage variance shows N/A while the absolute variance shows the full difference And equal values across columns render with a neutral style and no variance badge And tooltips on variance badges explain the baseline and calculation used
Cost Drivers Summary Across Estimates
Given two or three estimates are displayed in the diff view When the summary panel is opened Then it displays per-estimate totals broken down by Labor, Parts, Trip Fees, Taxes, and Grand Total And it identifies the top 3 cost drivers contributing to the total difference between the cheapest and the most expensive estimate, listing the line item name, category, and absolute difference amount And tapping a listed driver scrolls to and highlights the corresponding line item rows in all columns And if multiple items tie for contribution, ties are resolved by higher absolute difference first, then alphanumeric by item name And the summary clearly indicates whether differences are due to quantity/hours, unit price, added/missing items, or fees/taxes
Expand/Collapse Item Groups with State Retention
Given line items are grouped by category (e.g., Labor, Parts, Trip Fees, Taxes) and any vendor-provided group labels When the user toggles a group’s expand/collapse control Then all aligned rows for that group across columns expand or collapse in unison without losing row alignment And an Expand All / Collapse All control applies to all groups at once And keyboard users can toggle groups via Enter/Space on the group header, with ARIA attributes reflecting expanded state And the expand/collapse state remains unchanged when filters are applied or cleared within the same session
Category Filtering in Diff View
Given the diff view is open When the user applies a category filter (e.g., Labor only, Parts only, Trip Fees only, Taxes only, or multi-select) Then only matching line items (and their placeholders in other columns) are shown, while non-matching groups are hidden And per-column subtotals and grand totals adjust to reflect only the visible (filtered) items, with a clear Filtered indicator And clearing the filter restores all items and totals to their unfiltered state And the active filters are visually indicated and keyboard accessible
Placeholder Handling for Missing Line Items
Given a line item exists in one estimate but not in another When the diff view renders aligned rows Then the missing entry displays a neutral placeholder (e.g., “Not quoted”) with a tooltip explaining it was not included in that estimate And the absolute variance for present vs. missing is calculated against zero and shown, while percentage variance displays as N/A And missing items are included as potential cost drivers in the summary when they materially contribute to total differences And placeholders do not alter other columns’ totals or alignment
Persisted Selections and Approve From Compare
Given a user selects specific estimates, applies filters, and adjusts group expand/collapse When the user leaves and later returns to the same work order within 30 days Then the diff view restores the previously selected estimates, active filters, and group expansion states from local storage for that work order And when the user clicks Approve on a column Then a confirmation dialog shows vendor, total, taxes, and key cost drivers, and requires explicit confirmation And on confirmation, the system records the approval for that estimate and triggers the same downstream workflow as approving from the estimate detail (including notifications/SMS where configured) And on any API failure, no approval is recorded and a retryable error message is shown
Line-Item Photo Annotations
"As a property manager, I want annotated photos attached to line items so that I can verify scope and understand why a cost is necessary."
Description

Enable uploading and viewing of annotated photos tied to specific line items, allowing users and vendors to draw callouts, add notes, and timestamped marks that justify scope and pricing. Display thumbnails inline with each item and provide a full-screen viewer with before/after toggles. Support mobile capture, file-type constraints, and automatic association to the relevant work order. Store annotations as non-destructive overlays to preserve original images and maintain an audit trail.

Acceptance Criteria
Upload Annotated Photo to a Line Item
Given I am a landlord or vendor viewing a specific estimate line item within a work order When I upload a supported image and add at least one annotation (arrow or text) and save Then the original image file is stored unchanged and the annotations are stored as an overlay linked to that photo And a thumbnail of the photo appears inline under the correct line item without requiring a page refresh And the photo is automatically associated to the line item and its parent work order And an audit record is created capturing user, role, timestamp, line item ID, work order ID, and annotation count
Full-Screen Viewer with Before/After Toggle
Given a line item has at least one photo marked Before and one marked After When I open the full-screen viewer from any thumbnail Then I can toggle between Before and After states while retaining current zoom/pan And I can toggle Annotations On/Off to view the original vs. annotated overlay And the viewer displays photo timestamp, uploader, and annotation author(s) And I can navigate to next/previous photos within the same line item
Mobile Camera Capture and Auto-Association
Given I am on a mobile device viewing a specific line item When I tap Add Photo, choose Camera, capture a photo, and confirm upload Then the photo uploads successfully and is associated to the correct line item and work order And EXIF capture time (if present) is stored and orientation is corrected for display And I see an upload progress indicator and a success state on completion And if camera permission is denied, I am prompted to choose an existing photo from the device library instead
File-Type and Size Constraints
Given I select one or more files to upload to a line item When a file is not JPEG, PNG, or HEIC, or exceeds the 25 MB size limit Then that file is rejected with an inline error message stating allowed types and max size And any valid files in the same selection continue to upload And no partial or empty photo records are created for rejected files Given I upload a HEIC file When the system processes it Then the original HEIC is preserved and a web-viewable derivative is generated for display without altering the original
Annotation Tools as Non-Destructive Overlays
Given I open the annotation editor for a photo tied to a line item When I add or edit callouts (arrow, rectangle, freehand, and text with selectable color) and save Then the annotations are stored as a structured overlay (not baked into the image) and linked to the photo version And the original image remains unchanged and retrievable And I can download/export the Original image and an Annotated composite separately Given I undo/redo or delete an annotation and save When I view the photo's history Then a new annotation version is created and prior versions remain accessible with timestamps
Audit Trail and Role-Based Access
Given a photo with annotations exists on a work order line item When I view its details Then the audit log lists who created/edited each annotation, with timestamps and version numbers Given I am a vendor or landlord assigned to the work order When I open the line item Then I can view photos and create/edit annotations Given I am not assigned to the work order When I attempt to access the photo or annotation via URL or UI Then I receive a 403 Access Denied response
Inline Thumbnails with Counts and Indicators
Given a line item has associated photos When I view the estimate compare screen Then thumbnails appear inline beneath the line item with a numeric badge showing total photo count And each thumbnail displays a Before or After tag if set, and an annotation indicator if overlays exist And clicking a thumbnail opens the full-screen viewer at that photo Given a line item has no photos When I view the line item Then I see an Add Photo call-to-action in place of thumbnails
Historical Benchmarking Service
"As a small property manager, I want benchmark ranges for similar jobs so that I can spot outliers and avoid overpaying."
Description

Surface price benchmarks for common repair categories by leveraging historical FixFlow jobs, regional factors, and seasonality to show typical ranges and median costs per unit, hour, or item. Display how each line item and total compares to benchmarks with percent deviation and confidence indicators. Cache benchmark results per category and geography and refresh them on a scheduled cadence. Respect data privacy by aggregating and anonymizing sources while exposing enough context for actionable decisions.

Acceptance Criteria
Benchmark Retrieval by Category and Geography
Given a repair category and geographic key, when the benchmarking service is queried, then it returns median, p25 and p75 values, unit_of_measure, and sample_size for that category+geo within 2,000 ms p95 when uncached and within 400 ms p95 when served from cache. Given a category+geo with no cache entry, when queried, then the result is computed from historical jobs within the last 18 months filtered to the geo scope and persisted to cache with a 24-hour TTL. Given multiple supported unit types for a category (per_hour, per_item, per_unit), when queried, then the response includes a benchmark object per available unit type and sets unit_not_available=true for any unsupported unit requested.
Regional and Seasonal Adjustments Applied
Given a category, month, and geo, when benchmarks are computed, then month-of-year seasonal factors are applied only if the per-month sample_size>=20; otherwise no seasonal adjustment is applied and confidence is downgraded one level. Given a requested geo with sample_size<20, when computing benchmarks, then the service falls back in order metro→state→country and sets fallback_level to the level used. Given regional and seasonal adjustments are applied, when returning results, then adjusted=true is set and both adjusted_median and unadjusted_median are included in the payload.
Line-Item and Total Comparison Output
Given an estimate line item with amount and unit type, when compared to the benchmark, then percent_deviation is computed as round(((amount - median)/median)*100,1) and classification is Below if <= -5.0, At if > -5.0 and < 5.0, and Above if >= 5.0. Given an estimate with multiple comparable line items, when computing a total comparison, then percent_deviation_total is computed against the sum of median benchmarks for matched categories and unit types and excludes any items without a benchmark. Given a line item has no available benchmark, when comparing, then the item is marked no_benchmark=true and is excluded from aggregate comparisons while still returning item-level context.
Confidence Indicator and Context Metadata
Given computed benchmarks, when returning the payload, then confidence is High if sample_size>=100 and coefficient_of_variation<=0.30, Medium if sample_size between 40 and 99 or coefficient_of_variation<=0.50, and Low otherwise. Given a benchmark payload, when returned, then it includes context fields: sample_size, data_window_months, geo_scope, fallback_level (if any), last_refreshed_at (UTC ISO8601), unit_of_measure, and adjusted flag. Given confidence=Low, when returning, then a recommendation field is included with value broaden_geo_scope or manual_review to guide user action.
Caching and Scheduled Refresh
Given a category+geo cache entry, when its TTL expires or a scheduled refresh occurs, then benchmarks are recomputed and the cache is updated atomically with no partial responses and last_refreshed_at is set to the recompute completion time. Given the daily refresh task, when it runs at 03:00 local time for each geo, then all category+geo entries are refreshed with P95 end-to-end refresh latency under 5 minutes per geo. Given a cache rebuild failure, when it occurs, then the system retries up to 3 times with exponential backoff, retains the last known good cache until success, and emits an error event with category and geo identifiers.
Privacy and Anonymization Guarantees
Given historical job data, when aggregating, then no raw job identifiers, tenant or vendor PII, or individual job amounts are exposed; only aggregated statistics are returned. Given a category+geo cohort with sample_size<20, when requested, then no benchmark values are returned for that scope and the service either falls back to a broader scope or returns insufficient_data when country-level sample_size<20. Given any aggregate with sample_size between 20 and 39, when returning results, then calibrated Laplace noise is applied (epsilon>=1.0) and dp_applied=true is included in the payload.
Unit Normalization and Mapping
Given input line items labeled labor, materials, trip fee, or other, when normalized, then they are mapped to standard categories and unit types using a maintained mapping table achieving at least 95% mapping coverage on a held-out validation set. Given a labor line item with hours and rate, when benchmarking, then comparisons use cost per hour; materials use cost per item; per-unit uses cost per unit, and unit conversions are applied when inputs differ from benchmark units. Given ambiguous or unknown units, when normalization fails, then the item is flagged unit_ambiguous=true, excluded from comparisons, and a remediation_hint field is provided indicating the missing or ambiguous unit.
Vendor Performance Scoring
"As a landlord, I want vendor performance scores next to each bid so that I can weigh reliability and quality in addition to cost."
Description

Compute and display vendor performance scores alongside each estimate using weighted metrics such as on-time arrival, first-fix rate, callback rate, tenant satisfaction, response time, and warranty claims. Show trend indicators and sample sizes, handle insufficient-data cases, and link to detailed vendor history. Integrate scores into sorting and tie-breaker rules within the comparison view to inform selection beyond price alone.

Acceptance Criteria
Weighted Score Calculation
Given configured metric weights and transforms exist in the system When a vendor’s performance score is calculated Then the system normalizes each metric to a higher-is-better 0–100 scale per configuration and applies the configured weight to each metric And the overall score equals the weighted average of the normalized metrics on a 0–100 scale And the score is rounded to one decimal place using standard rounding and is deterministic across runs for the same inputs
Score, Trend, and Sample Size Display in Compare View
Given the Estimate Compare view shows two or more estimates When the view renders Then each estimate card displays the vendor performance score (0.0–100.0), a trend indicator (up/down/flat), and the overall sample size formatted as "n={count}" And hovering/tapping the score reveals a breakdown tooltip with each metric’s normalized value and weight And the score badge is keyboard-focusable and announced with an accessible label including the score and sample size
Trend Indicator Calculation
Given a configured trend window (default: last 90 days) and a previous equal-length window When the system compares the current average score to the previous period Then show an up arrow if the delta is ≥ +2.0 points, a down arrow if the delta is ≤ −2.0 points, otherwise show a flat indicator And the tooltip includes the delta in points and both periods’ sample sizes And if the previous period’s sample size is below threshold, display "Trend unavailable" and no arrow
Insufficient Data Handling and Weight Normalization
Given one or more metrics do not meet the minimum sample threshold for inclusion When computing the vendor performance score Then those metrics are excluded and remaining metric weights are renormalized to sum to 100% And the UI displays a "Limited data" badge and the included overall sample size And if no metrics meet the threshold, the UI displays "Not enough data" instead of a score and the vendor is excluded from score-based tie-breakers
Sorting and Tie-Breaker Integration
Given the user sorts by Price or Best Value in the Estimate Compare view When two estimates have equal price within a 1% tolerance Then prefer the estimate with the higher vendor performance score And if scores are within 0.5 points, prefer the vendor with lower average response time And if still tied, order by vendor name ascending deterministically And when sorting by Vendor Score, order by score descending, placing "Not enough data" vendors after scored vendors
Vendor History Deep Link
Given a vendor score is displayed on an estimate card When the user clicks the score badge or selects the "View vendor history" link from the tooltip Then the Vendor History view opens in-place within 1 second and focuses the history panel And the history view shows the underlying metrics, definitions, past jobs contributing to the score, and the time window used And a back/close control returns the user to the comparison view at the same scroll position
API/Data Freshness and Error States
Given the Estimate Compare view requests vendor scores for all visible estimates When data is fetched Then scores reflect calculations using data updated within the last 24 hours or display a "Stale" badge if older And transient API errors show a non-blocking retry state without breaking the estimates list And successful retries replace placeholders with scores without requiring a full page reload
One-Tap Award & Scheduling
"As a property manager, I want to award the best estimate and schedule the job in one tap so that I can move from decision to action without extra steps."
Description

Allow users to select a winning estimate from the comparison view and trigger automated notifications via SMS/email with job details, proposed slots, and required confirmations. Upon acceptance, auto-create the calendar event in the shared scheduler, send confirmations to tenants, and update the work order status while logging the decision rationale and locking the chosen estimate version. Optionally notify non-selected vendors with a courteous decline and store the audit trail for compliance and reporting.

Acceptance Criteria
One-Tap Award from Compare View
- Given a user views the Estimate Compare screen with at least two estimates for an open work order, When the user taps "Award & Schedule" on a selected estimate, Then the system records the estimate ID, work order ID, and user ID and displays a confirmation modal summarizing job details and vendor availability. - Given the confirmation modal is displayed, When the user confirms, Then the award action completes within 2 seconds server time and responds with a success state, or returns a specific error code and message without changing any persistence (idempotent on retry with the same request ID). - Given an award is in progress for a work order, When another user attempts to award a different estimate for the same work order, Then the system prevents double-award with a clear message and keeps the original winning estimate.
Automated Vendor Notification With Proposed Slots
- Given an estimate has been awarded, When notifications are triggered, Then the system sends both SMS and email to the winning vendor with: work order ID, property address, scope summary, price, links to annotated photos, and three proposed time slots derived from the vendor’s calendar in the tenant’s timezone. - Given notifications are sent, Then each message includes actionable Accept, Decline, and Propose New Time links/buttons bound to a unique, expiring token (min 24 hours) and logged with provider message IDs, timestamps, and delivery status. - Given vendor communication preferences exist, When sending, Then the system respects opt-outs and fallbacks (e.g., if SMS disabled, send email only) and records the channel selection rationale.
Vendor Acceptance Creates Calendar Event
- Given the vendor taps Accept and selects a proposed slot, When the system processes the response, Then a shared calendar event is created within 3 seconds with correct start/end, timezone, location, access instructions, and attendees (vendor, tenant, landlord/PM). - Given the event is created, Then the system performs a conflict check to prevent double-booking; if a conflict is detected, the vendor is prompted to choose an alternative slot, and no overlapping event is created. - Then the work order status updates to "Scheduled", tenant and landlord receive confirmations via preferred channels, and the vendor sees the event on their shared calendar.
Decision Rationale and Estimate Locking
- Given a user confirms the award, When the action is saved, Then the system requires a decision rationale (dropdown: price, availability, quality, other) and optional note and stores these with user ID, timestamp, and estimate version hash. - Then the winning estimate version is snapshotted and locked from further edits; non-winning estimates are marked as declined and hidden from award actions for this work order. - Given API or UI attempts to modify the locked estimate, Then the system rejects with a 409 Conflict and preserves the snapshot.
Optional Declines to Non-Selected Vendors
- Given the "Notify other vendors" option is enabled at award time, When the award is confirmed, Then courteous decline messages are sent to all non-selected vendors within 30 seconds without revealing the winning vendor or price. - Then each decline includes the work order reference, a thank-you, and a link to manage notification preferences; all messages are logged with provider IDs and delivery status. - Given a vendor has a global do-not-contact flag, Then the system suppresses the decline and logs the suppression reason.
Audit Trail and Reporting Compliance
- Given any award-related event occurs, When it is committed, Then an immutable audit log entry is written with: event type, actor, timestamp (UTC), request ID, old/new states, and related entity IDs. - Then the work order timeline displays the award, notifications, responses, and scheduling events in chronological order with filters by actor and date. - Given an admin exports a compliance report for a date range, Then the export includes all award decisions and notifications with delivery outcomes in CSV format and completes within 30 seconds for up to 10,000 records.
Notification Failure Handling and Idempotency
- Given a notification send attempt fails (temporary), When retries are enabled, Then the system retries up to 3 times with exponential backoff and marks the final status; a banner prompts the user to resend if all retries fail. - Given a duplicate vendor response link is clicked multiple times, Then the processing is idempotent using the token/request ID and does not create duplicate calendar events or duplicate status updates. - Given the notification provider returns a permanent failure (e.g., 400 invalid number), Then the system stops retries, records the error code, and switches to an alternative channel if available.

ChangeGuard

Automatically flags change orders and variance from the approved scope or price thresholds. Presents a one-tap re-approve with highlighted deltas, or enforces caps and requires notes. Prevents scope creep, protects budgets, and preserves a tidy audit record.

Requirements

Delta Detection Engine
"As a property manager, I want automatic detection of variances from the approved scope and price so that I am instantly alerted to changes that need my attention."
Description

Automatically compares incoming change orders against the approved work scope and baseline pricing for each maintenance ticket. Computes line-item and total variance, classifies by type (scope, price, and time), and highlights impacted fields. Supports percentage and absolute comparisons, property- and vendor-specific baselines, and per-ticket overrides. Exposes a flag event for downstream UI, notifications, and policy enforcement. Persists detected deltas on the ticket for reporting and audit.

Acceptance Criteria
Price Variance Detection with Percentage and Absolute Thresholds
Given an approved ticket with baseline line item L1 price 100 and configured thresholds percent=10% and absolute=15 When a change order updates L1 price to 112 Then the engine computes L1 varianceAbs=12 and variancePct=12% and totalPriceVarianceAbs=12 And classifies the delta type as price And flags the ticket because variancePct exceeds threshold while varianceAbs does not And records thresholdTriggered="percent" and impactedFields=["lineItems.L1.price","totals.price"]
Scope Change Detection for Added/Removed/Quantity-Adjusted Line Items
Given an approved ticket with scope lineItems=[{id:L1, qty:2},{id:L2, qty:1}] and baseline prices When a change order adds {id:L3, qty:1}, removes {id:L2}, and updates {id:L1, qty:3} Then the engine identifies scopeAdds=[L3], scopeRemovals=[L2], scopeQtyChanges=[{id:L1, from:2, to:3}] And computes price impact from these scope changes and updates total variance accordingly And classifies the delta types to include scope and price And highlights impactedFields including lineItems.L1.qty, lineItems.L2 (removed), lineItems.L3 (added), and totals.price
Time Variance Detection for Schedule Changes
Given an approved ticket with scheduledStart=2025-09-10T10:00:00-07:00 and scheduledEnd=2025-09-10T12:00:00-07:00 When a change order proposes scheduledStart=2025-09-12T14:00:00-07:00 and scheduledEnd=2025-09-12T16:00:00-07:00 Then the engine computes timeVarianceHours=52 and classifies the delta type as time And highlights impactedFields=["scheduledStart","scheduledEnd"] And stores all timestamps normalized in UTC with ISO-8601 format
Delta Classification and Impact Highlighting
Given an approved ticket and a change order that includes a new line item (scope), a 5% price tweak on L1, and a +24h schedule shift When the engine evaluates the change order with thresholds percent=10% and absolute=20 Then the engine classifies deltaTypes=["scope","time"] and excludes "price" because the price variance is below both thresholds And it attaches a delta summary listing each impacted field with before/after values and variance metrics where applicable And no unrelated fields are marked as impacted
Baseline Resolution with Property/Vendor Context and Ticket Overrides
Given propertyId=P1 has baseline price for L1=120, vendorId=V1 has baseline price for L1=110, and ticket T has an override for L1=105 When a change order proposes L1 price=115 Then the engine resolves the comparison baseline as 105 (ticket override precedence over vendor over property) And computes varianceAbs=10 and variancePct=9.52% And does not flag price variance when thresholds are percent=10% and absolute=15 And records baselineSource="ticketOverride" in the persisted delta
Flag Event Emission for Downstream Consumers
Given a change order on ticket T produces deltas that meet flagging criteria When the engine completes comparison Then it emits a single flag event to the event bus within 500ms containing {ticketId, deltaTypes, lineItemDeltas[{id, field, before, after, varianceAbs, variancePct}], totalsVariance, thresholdsApplied, baselineSource, occurredAt, correlationId} And the event payload passes JSON schema validation and includes occurredAt in ISO-8601 UTC And duplicate processing of the same change order idempotently emits no additional events (same correlationId)
Persistence of Detected Deltas for Audit and Reporting
Given a flagged change order on ticket T When the engine persists detected deltas Then T stores a delta record with {version, createdAt, createdBy="system", classifications, impactedFields, before/after snapshots, variance metrics, thresholdsApplied, baselineSource} And the record is immutable and queryable by date range, propertyId, vendorId, deltaType, and amount ranges And a subsequent change order creates a new version without altering prior records And persisted deltas remain available after service restart
Threshold & Policy Configuration
"As an owner, I want to configure caps and rules for change orders so that the system enforces our budget discipline consistently."
Description

Provides an admin console to define approval thresholds and policies by portfolio, property, unit, category, and vendor. Supports absolute and percentage caps, soft versus hard limits, auto-approval rules under a threshold, required-note policies, and attachment requirements. Policies can inherit defaults and be overridden per ticket. Integrates with user roles to target approvers and with budget categories for contextual limits.

Acceptance Criteria
Admin Console: Create and Save Policy With Validation
Given I am an Admin user on the ChangeGuard > Policies page When I create a new policy and select scope (portfolio/property/unit/category/vendor/budget category) Then I can set absolute cap (currency, >= 0, up to 2 decimals) and percentage cap (0–100%, up to 2 decimals), choose soft or hard limit, toggle auto-approval under threshold, and toggle required note/attachment And invalid inputs disable Save and show inline errors specifying the field and rule violated When I click Save with valid inputs Then the policy is persisted, appears in the policy list with scope, caps, limit type, and toggles summarized, and an audit entry records user, timestamp, and before/after values When I click Test Policy and enter a sample approved amount and context (scope selections) Then the tool returns the effective caps, whether auto-approval would apply, and the targeted approver roles according to the saved configuration
Hierarchy Inheritance and Resolution With Per-Ticket Override
Given a portfolio default policy (abs cap $500, % cap 10%) and a property override (abs cap $400) and a category override for Plumbing (abs cap $350) and a vendor override for Vendor ABC (abs cap $300) and a unit override (abs cap $375) When a ticket is created for that property, unit, category Plumbing, using Vendor ABC Then the effective cap is $375 per the precedence rule: Ticket Override > Unit > Property > Category > Vendor > Portfolio Given an authorized approver applies a ticket-level policy override (abs cap $250) with a required reason When the override is saved Then the ticket shows cap $250 and the audit log records the override reason, before/after values, user, and timestamp Given no lower-scope overrides exist for a ticket When the ticket is evaluated Then the portfolio default applies
Absolute and Percentage Caps Calculation and Auto-Approval
Given a ticket with an approved base amount of $1,000 and a policy with absolute cap $200, percentage cap 15%, and auto-approval under threshold enabled When a change order increases cost by $140 Then the change is auto-approved and recorded as such in the audit log When a change order increases cost by $180 Then approval is required because it exceeds the 15% cap ($150.00) even though it is under the $200 absolute cap When a change order increases cost by $210 Then approval is required because it exceeds both caps And percentage thresholds are calculated against the approved base amount with currency rounded to the nearest cent
Soft vs Hard Limit Enforcement
Given a policy with a soft limit and caps that would be exceeded by a proposed change When the user submits the change Then the system allows submission, displays a warning banner indicating the exceeded cap and amount over, and triggers the approval workflow Given a policy with a hard limit and caps that would be exceeded by a proposed change When the user attempts to submit the change Then the system blocks submission, displays an error stating the hard cap is exceeded, and offers a Request Re-Approval action for targeted approvers And all events create audit entries including user, timestamp, and limit type enforced
Required Notes and Attachments Enforcement
Given a policy requires a note when limits are exceeded and requires an attachment for category Plumbing When a user submits a change that exceeds a cap under a soft limit Then a note field is required (minimum 10 characters) and Save/Submit remains disabled until provided When the ticket category is Plumbing Then at least one attachment (PDF/PNG/JPG, max 10 MB per file) is required and Save/Submit remains disabled until provided And invalid file types or oversized files are rejected with specific error messages
Role-Targeted Approver Routing and Escalation
Given a policy targets approver roles Property Manager (PM) first and Owner second for hard-cap exceed events with a 24-hour SLA before escalation When a change requires approval Then approval requests are sent to PMs assigned to the property via in-app, email, and SMS, and only users with the PM role at that property can approve When no PM approves within 24 hours Then the request auto-escalates to Owners assigned to the property and they are notified via in-app, email, and SMS And non-targeted roles cannot see Approve/Reject actions for the request
Budget Category Contextual Limits Application
Given a budget category policy for HVAC - Repairs at Property A sets abs cap $600 and % cap 20% When a change order for Property A is tagged with budget category HVAC - Repairs Then the HVAC - Repairs caps apply per the precedence rule and are displayed in the approval panel When the same change order is re-tagged to a category without a specific policy Then the effective caps recalculate to the next applicable scope per the precedence rule
One-tap Re-approval UI
"As a landlord on the go, I want a clear, one-tap review of changes with the differences highlighted so that I can quickly approve or reject without digging through emails."
Description

Presents flagged change orders in a compact review screen with before/after values, cost impact, timeline impact, and highlighted deltas. Offers one-tap actions to approve, decline, or request changes, with optional inline note entry. Mobile-optimized, accessible, and embedded in the ticket view. Surfaces compliance indicators (within cap, exceeds cap) and shows the required approver.

Acceptance Criteria
Approve Within Cap from Ticket View (One-Tap)
Given a maintenance ticket containing a flagged change order that is within the configured cap and the signed-in user is the required approver When the user opens the ticket view Then a compact Re-approval card is displayed within the ticket view showing before/after values for changed fields, cost impact (absolute amount and %), timeline impact (in days), and the compliance indicator "Within Cap" Given the Re-approval card is visible When the user taps Approve Then the change order status updates to Approved, the card collapses to an Approved summary with approver name and timestamp, and a success confirmation is shown Then an audit record is persisted containing user ID, timestamp, action=Approve, note (if provided), before/after snapshot, cost and timeline deltas, and compliance indicator
Decline with Required Note on Exceed Cap
Given a flagged change order that exceeds the configured cap and the signed-in user is the required approver When the user selects Decline without entering a note Then the action is blocked and a validation message "Note required to decline over-cap changes" is displayed When the user enters a note of at least 5 characters and taps Decline Then the change order status updates to Declined, the decline note is saved, and an audit record is created with action=Decline, note, and compliance indicator "Exceeds Cap"
Approve Disabled on Exceeds Cap without Override
Given a flagged change order that exceeds the configured cap and the signed-in user lacks override-approval permission When the user views the Re-approval card Then the Approve action is disabled with an explanatory tooltip "Exceeds cap—approval not permitted" and only Decline and Request Changes are enabled When the user attempts to submit an approval via any client interaction or API call Then the request is rejected and the UI displays a non-blocking error "Approval blocked by cap policy" without changing the change order state
Request Changes Triggers Notifications and Audit
Given a flagged change order and the signed-in user is the required approver When the user taps Request Changes and optionally enters a note Then the change order status updates to Needs Changes, the note (if entered) is saved, and the card displays a Requested Changes summary Then a system comment is added to the ticket with the request details and the configured notifications to the assigned vendor and ticket followers are sent Then an audit record is persisted with action=Request Changes, user, timestamp, note (if any), and before/after snapshot
Mobile and Accessibility Compliance for One-Tap Card
Given a mobile viewport width between 320px and 414px When the Re-approval card is rendered Then there is no horizontal scrolling, text reflows within the viewport, and tap targets for Approve/Decline/Request Changes are at least 44x44px Then the card loads within 2 seconds on a 3G Fast profile and remains responsive to input Given keyboard or screen reader navigation When navigating the card Then all interactive controls are reachable via keyboard, have visible focus, include accessible names/labels, follow logical focus order, and text and delta highlights meet a contrast ratio of at least 4.5:1
Delta Highlighting and Impact Calculations Accurate
Given a flagged change order with modified fields When the card is displayed Then only changed fields are highlighted, each shows before and after values side by side, and cost impact displays currency in the property's locale with absolute delta and percentage to two decimal places Then timeline impact is shown as a signed whole number of days (e.g., +3 days or −2 days), and increases are indicated with red and decreases with green supplemented by icons or text so meaning is not conveyed by color alone Then the compliance indicator reflects the cap evaluation result for the total change (Within Cap or Exceeds Cap) and matches backend calculation
Required Approver Visibility and Action Gating
Given a flagged change order with a designated required approver When the current user is not the required approver and lacks delegate permission Then the card displays "Required approver: {Name/Role}" and Approve/Decline/Request Changes actions are disabled with an explanation "Only {Name/Role} may act" When the current user is the required approver Then the actions are enabled and the card header shows "You are the required approver" Then any attempt by a non-authorized user to act is rejected server-side and logged without changing state
Enforcement & Blockers
"As an operations lead, I want the system to automatically block unapproved overages and require explanations so that work cannot proceed without proper control."
Description

Applies configured policies at execution time to prevent scope creep. Blocks vendor scheduling that exceeds hard caps until approval is granted, and requires notes or attachments when soft caps are exceeded. Provides contextual error messages and guidance to vendors and staff. Supports temporary overrides with justification and tracks exceptions for review.

Acceptance Criteria
Block Scheduling When Hard Cap Exceeded
Given a work order with a configured hard budget cap of $X and a current committed total of $Y And a vendor enters a new scheduling request with an estimated cost of $Z where Y + Z > X When the vendor submits the scheduling request Then the system rejects the request with no calendar event created and no SMS/notifications sent And the UI/API returns an error indicating hard cap exceeded showing X, Y, Z, and the overage amount And an approval request is generated and linked to the work order And the attempt is recorded in the audit log with timestamp, user, vendor, work order ID, and delta amount
Require Notes or Attachment on Soft Cap Exceedance
Given a work order with a soft cap of $X and a current committed total of $Y And a vendor enters an estimated cost $Z where Y + Z > X but Y + Z ≤ hard cap When the vendor submits without a note or attachment Then the system prevents submission and displays validation requiring either a note (minimum 20 characters) or a file attachment (≤10 MB; pdf,jpg,png) When the vendor provides the required note or attachment and resubmits Then the submission succeeds and the note/attachment is stored and linked to the change event in the audit log
Temporary Soft-Cap Override with Justification and Expiry
Given the user has the Override Soft Cap permission And a work order is subject to soft-cap enforcement When the user creates a temporary override with justification text (≥20 characters) and an expiry time within 72 hours Then soft-cap checks for that work order are bypassed until expiry And hard-cap enforcement remains in effect And an override banner is visible on the work order showing actor, reason, and expiry countdown When the expiry time is reached or the override is revoked Then soft-cap enforcement is automatically reinstated and the event is logged
Contextual Error Messaging to Vendors During Booking
Given a vendor triggers a block due to a policy (hard cap exceeded or missing note/attachment for soft cap) When the error is presented Then the primary message states the rule and next step in 120 characters or fewer And details include threshold amount, current total, and overage amount And a single-click call to action is provided to Request approval (hard cap) or Add note/attachment (soft cap) And the same information is returned in the API error payload with a stable error code and machine-readable fields
Approval Gating for Hard-Cap Exceptions
Given an approval request exists for a hard-cap exceedance on a work order When an authorized approver approves it with optional note and attachment Then the work order state reflects Cap exception approved with the approved amount and validity window (if configured) And subsequent vendor scheduling within the approved amount succeeds without block When the request is denied or expires Then scheduling remains blocked and vendor/staff see updated guidance reflecting the decision
Exception Logging and Audit Trail for Policy Breaches
Given any block, soft-cap override, approval, denial, or successful scheduling post-approval occurs When the event is saved Then an immutable audit record is created including: timestamp, actor, role, action type, work order ID, old total, new total, cap values, delta, justification text (if any), attachment references, and IP/user agent And the record is visible on the work order timeline and in the ChangeGuard exceptions report And records are exportable as CSV and filterable by date range, action type, actor, and property
Immutable Audit Trail
"As a portfolio manager, I want a complete, immutable log of change orders and approvals so that I can audit decisions and resolve disputes confidently."
Description

Records every change order event, detected variance, decision, note, attachment, and policy evaluation with timestamp, actor, and before/after values. Stores versioned snapshots linked to the maintenance ticket, vendor, and property. Supports export to CSV/PDF, filterable logs, and retention aligned with compliance needs. Ensures records are tamper-evident for audits and dispute resolution.

Acceptance Criteria
Comprehensive Event Capture
Given a user creates a change order on a maintenance ticket, When the change order is saved, Then an audit entry is recorded with fields: event_type=create_change_order, ticket_id, change_order_id, property_id, vendor_id, actor_id, timestamp_utc (ISO 8601), before_values=null, after_values={...} Given a change order is updated (price, scope, schedule), When the update is saved, Then an audit entry is recorded capturing before_values and after_values for all changed fields with field-level granularity Given ChangeGuard detects a variance beyond the configured threshold, When the variance is flagged, Then an audit entry is recorded with event_type=variance_detected, policy_id, threshold_value, actual_variance, evaluation_result, actor_id=system, and timestamp_utc Given an approver takes action, When the re-approve or reject decision is submitted, Then an audit entry is recorded with event_type=decision, decision_value=approve|reject, approver_id, notes (if any), and timestamp_utc Given a note or attachment is added to the change order, When it is saved, Then audit entries are recorded with event_type=note_added or attachment_added including note_id or attachment metadata (filename, size_bytes, content_hash)
Immutability and Tamper Evidence
Given any user attempts to edit or delete an existing audit entry via UI or API, When the request is processed, Then the operation is blocked with 403 Forbidden and no audit data is changed Given a superadmin attempts to modify audit content, When the request is processed, Then modification is disallowed; only new append-only entries can be created to annotate prior entries Given an audit entry is written, When it is stored, Then it includes content_hash and prev_hash to form a verifiable hash chain and the daily chain anchor is stored with a signed digest Given the integrity verification job runs for a selected range, When any entry within the range has been altered, Then verification fails and returns the first invalid entry_id and range boundary affected Given the integrity verification job runs for a selected range, When no entries have been altered, Then the job returns status=intact and the anchor digest matches
Versioned Snapshots and Linkage
Given any change to a change order, When the change is saved, Then a new immutable snapshot version is created with version_number incremented by 1 and includes the full payload at that time Given a snapshot is created, When it is stored, Then it is linked to maintenance_ticket_id, vendor_id, and property_id and is retrievable via any of those contexts Given a user views the change order history, When they select a version, Then the system displays the exact snapshot content and a computed delta from the previous version Given an API client requests snapshots by change_order_id, When results are returned, Then versions are ordered ascending by version_number with pagination and total_count
Filterable Log Retrieval
Given a permitted user opens the audit log, When they apply filters (date range, event_type, actor_id, ticket_id, property_id, vendor_id, decision, variance_threshold), Then only matching entries are returned and the applied filters are reflected in the query state Given the result set exceeds the page size, When the user paginates, Then subsequent pages return the correct continuation with no duplicates or gaps and include a next_page cursor Given up to 5,000 matching entries, When filters are applied, Then the first page loads within 2 seconds at p95 latency in the target environment Given the user clears all filters, When the log reloads, Then the default view returns entries for the last 30 days sorted by timestamp_utc descending
Export to CSV and PDF
Given a filtered audit view, When the user exports CSV, Then the file contains one row per entry with columns [timestamp_utc, event_type, ticket_id, change_order_id, property_id, vendor_id, actor_id, decision, variance, before_values_json, after_values_json, content_hash, prev_hash] and preserves sort order Given the same filtered view, When the user exports PDF, Then a paginated PDF is generated summarizing each entry with human-readable fields and includes a batch_verification_digest on the cover page Given N entries match the current filter, When CSV and PDF exports complete, Then both files contain exactly N entries and indicate the filter criteria, requesting_user, and generated_at (UTC) Given an export of up to 10,000 entries is requested, When processing completes, Then the downloadable files are available within 30 seconds at p95 latency and the user is notified on completion
Retention Policy and Legal Hold
Given an account retention policy of X years is configured, When entries exceed X years and are not on legal hold, Then they are purged or archived per policy and a retention_action audit meta-entry records the action, counts, and timestamp_utc Given a legal hold is placed on a ticket, vendor, or property, When retention processing runs, Then all related entries are retained until the hold is removed and holds are auditable Given entries are purged, When chain verification runs, Then the integrity of retained segments is provable via persisted boundary anchor hashes and verification passes for retained ranges Given a compliance export is requested for a range with purged entries, When the export is generated, Then the output clearly lists unavailable ranges due to retention with entry counts omitted
Role-Based Access to Audit Logs
Given RBAC is configured, When a landlord admin accesses audit logs, Then they can view all entries for properties they own and no others Given a property manager accesses audit logs, When their assigned properties are evaluated, Then only entries for those properties are visible Given a vendor user accesses audit logs, When visibility rules are applied, Then only entries with their vendor_id are visible and entries marked private_internal are excluded Given a tenant user accesses the system, When they attempt to access change order audit logs, Then access is denied by default unless an explicit permission grant exists Given an API token without read:audit scope calls the audit endpoint, When the request is processed, Then a 403 Forbidden is returned and no metadata about entries is leaked
Smart Notifications & Calendar Sync
"As a property manager, I want timely notifications and automatic schedule updates when changes are approved so that everyone stays aligned and we avoid double-bookings."
Description

Sends targeted notifications to approvers, vendors, and tenants when a change is flagged, approved, or declined, using email and SMS per FixFlow preferences. Includes highlighted deltas and clear next steps. Upon approval, updates the shared calendar and SMS confirmations to reflect schedule changes while preventing double-bookings. Supports quiet hours and escalation if approvals stall.

Acceptance Criteria
Notification Routing by Role and Channel Preferences
Given a change order is flagged on a work order with approver A, assigned vendor V, and tenant T and each has saved FixFlow notification preferences When the change is flagged Then notifications are sent only to A, V, and T via their preferred channels (email, SMS, or both) within 30 seconds And each message includes the change ID, work order ID, required action, and a link to view the change And no other users receive the notification
Delta Highlighting and Next Steps in Messages
Given a proposed change includes scope, price, or schedule differences from the approved baseline When notifications are sent for a flag, approval, or decline event Then the message shows a clear diff with old value, new value, and net variance for each changed field And approver messages include clear Approve and Decline actions And vendor and tenant messages include explicit next steps or “no action required”
Quiet Hours Suppression and Deferral
Given a recipient has quiet hours configured (e.g., 21:00–07:00) in their local timezone When a notification would be sent during those quiet hours Then the system suppresses real-time sending and queues a single notification per recipient per change And the queued notification is delivered at the quiet-hours end time with the original event timestamp And no duplicate notifications for the same event are sent during the quiet period
Escalation on Approval Stall
Given a change requires approval by approver A and has an escalation policy of 24-hour reminder and 48-hour escalation to approver B When A takes no action for 24 hours after the first notification Then A receives a reminder via all enabled channels and the event is logged And if no action is taken by 48 hours Then B is notified with escalation context and A is CC’d And escalation ceases immediately once any required approver acts
Calendar Update and Double-Booking Prevention on Approval
Given an approved change modifies an appointment’s date or time for unit U with vendor V When the change is approved Then the shared calendar event is updated within 60 seconds to reflect the new schedule And any conflicting events for U or V at the new time are detected and the update is blocked with a conflict alert until a free slot is selected And approver, vendor, and tenant receive reschedule notifications with the updated details
SMS Confirmation Synchronization After Schedule Change
Given a prior SMS confirmation exists for an appointment that is rescheduled by an approved change When the appointment is updated Then tenant and vendor receive a new SMS confirmation with updated date/time and a unique confirmation code within 2 minutes And the prior confirmation is marked superseded and cannot be used to check in or confirm And replies (CONFIRM/RESCHEDULE/DECLINE) apply only to the latest appointment version
Preference Fallbacks and Unreachable Handling
Given a recipient’s primary channel is SMS and email is configured as a fallback When SMS delivery fails (e.g., carrier error, unreachable) Then the notification is sent via email within 2 minutes And the failure reason and retry attempts are recorded in the audit log and delivery status is visible on the activity timeline And if all channels fail, the event appears in the Delivery Failures report for follow-up

Context Card

In-approval snapshot shows severity, habitability impact, unit access notes, warranty coverage, model/serial data, and prior work history. Approvers get the ‘need-to-know’ in one screen, enabling confident, faster approvals without chasing details.

Requirements

Unified Context Data Aggregation
"As an approver, I want all relevant request, unit, asset, warranty, and history data compiled into one reliable snapshot so that I can review and decide quickly without chasing details."
Description

Aggregate and normalize all approval-critical data for a maintenance request into a single context payload: severity rating, habitability impact, unit access notes, asset metadata (model/serial), warranty coverage, and prior work history. Data is sourced from existing FixFlow objects (request, unit profile, asset registry, vendor calendar, work orders) and external integrations (warranty documents/providers) via background jobs that precompute a snapshot when a ticket enters the approval state and on subsequent updates. Provide a stable schema with versioning, null-safe fields, and fallback derivations (e.g., infer severity from category and SLA). Expose the payload via an internal API for the UI and for audit storage. Ensure sub-500ms read latency and graceful degradation when sources are unavailable.

Acceptance Criteria
Snapshot Precompute on Approval Entry
Given a maintenance request transitions into the "Approval" state When the aggregator service is triggered for that request Then a context snapshot is persisted and available via the internal API And the snapshot contains fields: schemaVersion, requestId, severity, habitabilityImpact, unitAccessNotes, assetMetadata.model, assetMetadata.serial, warrantyCoverage, priorWorkHistory And the snapshot is created within 15 seconds of the status transition And repeated identical triggers do not create duplicate records for the snapshot (idempotent)
Snapshot Refresh on Source Update
Given a request in "Approval" state with an existing context snapshot And a source datum used by the snapshot changes (e.g., unit access notes updated, new warranty doc ingested, asset serial corrected) When the change event is processed Then the context snapshot reflects the new values within 20 seconds And no previously updated fields regress to stale values And the internal API returns the refreshed snapshot
Stable Schema, Versioning, and Null-Safe Contract
Given any retrieval of a context snapshot via the internal API Then the response always includes the full stable schema keys And unresolved or unavailable values are returned as null (not missing or placeholders) And schemaVersion is present and follows SemVer (MAJOR.MINOR.PATCH) And within a given MAJOR version, keys are neither removed nor have their types changed
Severity Fallback Derivation from Category and SLA
Given a request where severity is not explicitly set on the request record And the category and SLA are present When the snapshot is computed Then severity is populated using the configured mapping from category and SLA And the resulting severity is non-null and within the allowed set defined by configuration And if category or SLA are missing, severity remains null and no error is thrown
Internal API Read Performance and Concurrency
Given precomputed snapshots exist for a set of 200 requests When the internal API is called concurrently at 50 requests per second to retrieve snapshots Then the P95 response latency is <= 500 ms and P99 <= 800 ms And the 5xx error rate is <= 0.1% during the test window
Graceful Degradation When Sources Are Unavailable
Given the external warranty provider or vendor calendar API is unreachable or times out When the snapshot is computed or refreshed Then the API still returns HTTP 200 with the snapshot And warrantyCoverage (or vendor-derived fields) are null while other available fields are populated And the snapshot is produced within 15 seconds And an internal warning is logged for the degraded source condition
Audit Storage of Context Snapshots
Given a snapshot is created or refreshed When an auditor retrieves the historical record for the request Then the exact snapshot content at that time is retrievable from audit storage And the audit record includes timestamp and the associated requestId And audit retrieval returns within 1 second P95 And the audit record matches the snapshot served at the time it was created
Approval Snapshot UI
"As a property manager approving work, I want a clear one-screen snapshot with the critical facts and quick actions so that I can approve confidently in seconds."
Description

Deliver a single-screen, responsive context card within the approval workflow that presents severity and habitability badges, access notes, warranty status, asset model/serial, and a concise prior work summary. Include visual hierarchy and iconography for quick scanning, expandable sections for details, and inline quick actions (Approve, Request Info, Decline) with keyboard shortcuts. Integrate real-time updates from the context payload, show loading and partial data states, and provide links to full records when needed. Optimize for desktop and mobile, meeting accessibility standards (WCAG AA) and sub-200ms render after data is available.

Acceptance Criteria
Approver sees complete snapshot data
Given an approval request with a populated context payload When the context card renders Then it displays the Severity badge with the correct label and color per severity mapping (e.g., Critical, High, Medium, Low) And it displays a Habitability badge only when habitabilityImpact = true, with the correct label; otherwise the badge is hidden And it shows Unit Access Notes; if absent or blank, it displays the placeholder text "No access notes" And it shows Warranty status as "In warranty" or "Out of warranty" based on current date within warranty coverage; if unknown, shows "Warranty info unavailable" And it shows Asset Model and Serial with labels; if serial is missing, shows "Serial: —" And it shows a Prior Work Summary including count of prior work orders and the most recent work date; if none, shows "No prior work on record" And each data point is accompanied by the prescribed iconography from the design system And all displayed values match the latest payload values without stale data
Approver quickly scans via visual hierarchy
Given a desktop viewport width ≥ 1024px When the context card renders Then Severity and Habitability badges appear in the header area with Severity leftmost And key facts (severity, habitability, warranty, access notes) are visible without scrolling And section headers use consistent typography hierarchy (H levels) per design tokens And long text in access notes truncates after 2 lines with an overflow tooltip on hover/focus showing the full text And icons align left of labels with an 8px gap and consistent baseline alignment And the visual order matches the tab order (top-to-bottom, left-to-right)
Approver expands details for warranty and history
Given the context card is visible When the user activates the "Warranty Details" expander via click or keyboard (Enter/Space) Then the section toggles expanded/collapsed state and reveals warranty coverage dates, provider, and policy number when available And the expand/collapse animation completes within 200ms And the expanded/collapsed state persists per request during the session Given the user activates the "Prior Work" expander When expanded Then it shows up to the last 5 entries with date, brief summary, and status, and a "View all" link to the full history And if no prior work exists, the section displays "No prior work on record" and remains collapsible
Approver uses quick actions and hotkeys
Given the context card has loaded When the user clicks Approve or presses the A hotkey with the card focused Then a single approval request is submitted, the Approve button shows a loading state, all action buttons are disabled until a response, and on success a confirmation banner appears And on network or server error, an inline error message is shown, controls are re-enabled, and no duplicate action is recorded When the user selects Request Info or presses R Then a modal or inline form appears requiring a message of at least 5 characters before submission; on submit, the request is sent and confirmation is shown on success When the user selects Decline or presses D Then a reason is required (dropdown or free text ≥ 5 characters) before submission; on success, a decline confirmation is shown And pressing ? opens a shortcut help overlay/list; all shortcuts are disabled while a submission is in-flight
Context card updates in real time and handles partial data
Given the context card is open and a live update for the request payload is received When only some fields change Then only the affected fields update within 500ms without a full re-render, preserving focus and any unsent user input And an "Updated just now" indicator with timestamp appears for 3 seconds without stealing focus Given the payload arrives in parts When a section has not yet loaded Then a skeleton or loading placeholder is shown; if a field resolves to null/undefined, display a defined empty state (e.g., "Not available") And updates are debounced to at most once per second to prevent flicker And if the live connection drops, an unobtrusive warning is shown with auto-retry
Approver navigates to full records when needed
Given IDs for related entities exist in the payload (Work Order, Asset, Unit, Tenant) When the user activates a corresponding link or icon Then the full record opens (desktop: new tab; mobile: in-app route) without breaking the approval workflow state And links are only rendered when the related ID exists; otherwise the link control is hidden or disabled with an explanatory tooltip And link targets include descriptive accessible names (e.g., "Open full Asset record") And if the destination returns 403/404, a non-blocking error toast is shown and the user remains on the approval screen
Context card meets performance, responsive, and accessibility standards
Given data availability is signaled by a "data_ready" event When rendering the context card Then time from data_ready to first meaningful paint of the card is ≤ 200ms in the 95th percentile on target devices (desktop and mid-tier mobile), measured via performance marks And interaction latency for action button click-to-handler is ≤ 100ms under normal load Given viewport widths 320–768px When viewed on mobile Then layout adapts to a single column with no horizontal scrolling, tap targets are ≥ 44x44px, and primary actions remain visible without overlap Given viewport width ≥ 1024px When viewed on desktop Then layout uses a two-column arrangement per design and remains readable at 200% zoom without loss of content or functionality And the card meets WCAG 2.1 AA: color contrast ≥ 4.5:1 for text, fully keyboard operable (including expanders and actions), visible focus indicators, accurate semantics/roles, announced dynamic updates via aria-live, and no keyboard traps; automated axe scan reports 0 serious/critical issues
Warranty Intelligence
"As an approver, I want to know whether the issue is covered by warranty so that I can route the work correctly and avoid unnecessary costs."
Description

Determine and display warranty coverage for the implicated asset using model/serial, install/purchase date, and attached warranty documents. Parse uploaded PDFs/images to extract terms and dates; cross-check against provider APIs or internal rules to compute coverage status (in warranty, limited, expired) and next-step guidance (e.g., call manufacturer, approved vendor list). Indicate potential cost impact and route recommendations. Cache results in the context payload with provenance and refresh logic to avoid stale or misleading data.

Acceptance Criteria
OCR and Parsing of Warranty Documents
Given a maintenance request with at least one attached warranty PDF or image, When the Warranty Intelligence service processes the request, Then it extracts model_number, serial_number, provider_name, purchase_date or installation_date, and warranty_term with field-level confidence >= 0.90 and returns them in a normalized schema. Given multiple attached documents, When conflicting values are detected, Then the most recent document by embedded date or file timestamp is preferred and lower-confidence fields (<0.90) are ignored for that attribute. Given processing starts, When 10 seconds elapse, Then OCR/parsing completes or returns a timeout error with parse_status='timeout' and no partial fields saved.
Coverage Computation and Status Determination
Given normalized asset identifiers (model and serial) and dates, When a provider API is available and responds within 5 seconds, Then coverage_status is computed from the API response and set to one of [in_warranty, limited_warranty, expired]. Given the provider API is unavailable or times out, When internal rules are present for the provider, Then coverage_status is computed from rules using purchase/installation date and warranty_term. Given conflicting API and document-derived results, When both have freshness timestamps, Then the fresher result (<24h age) prevails; otherwise the API result prevails if present. Given insufficient data to compute coverage, When required fields are missing or below confidence threshold, Then coverage_status='unknown' and reason_codes include the missing fields. Then coverage_until date is populated when status is in_warranty or limited_warranty; otherwise omitted.
Next-Step Guidance and Routing
Given coverage_status='in_warranty' or 'limited_warranty', When guidance is generated, Then the context card shows a primary CTA labeled 'Contact Manufacturer' with provider phone or support URL and a secondary CTA 'View Warranty Terms'. Given coverage_status='expired' or 'unknown', When guidance is generated, Then the context card shows CTA 'Schedule Preferred Vendor' with the approved vendor list filtered by trade. Given any coverage_status, When guidance is generated, Then route_recommendation is set to one of [Manufacturer, ApprovedVendor, DiagnoseFirst] with rationale text <= 180 characters. Given a computed coverage_status, When cost impact is estimated, Then estimated_owner_cost_range is populated with min/max currency values and tenant_chargeable is set true/false per policy mapping.
Context Card Display and Provenance
Given cached warranty results exist, When the approver opens the Context Card, Then coverage_status badge, coverage_until (if applicable), provider_name, and route_recommendation render within 1 second. Given no cached results exist, When the approver opens the Context Card, Then a calculating indicator appears immediately and results render within 5 seconds or an error state with actionable text appears. Given any computed result, When displayed, Then provenance includes source_type [API|Document|Manual], source_reference (URL or file name), last_checked_at timestamp (UTC ISO 8601), and computation_version, all visible in an expandable details section.
Caching and Refresh Logic
Given a computed warranty result, When stored, Then it is cached with a TTL of 7 days and a last_checked_at timestamp. Given any of the following events: a new document upload, an edit to model/serial/purchase/installation date, or TTL expiry, When detected, Then the service triggers a refresh within 60 seconds. Given multiple refreshes produce different results, When confidence scores differ, Then the higher-confidence result supersedes; ties are resolved by newest last_checked_at. Given provider APIs support ETag or Last-Modified, When refreshing, Then conditional requests are used and on 304 Not Modified last_checked_at is updated to now and TTL is extended; no content fields change.
Error Handling and Edge Cases
Given model or serial is missing from both structured data and documents, When computation runs, Then coverage_status='unknown', reason_codes include 'missing_model' and/or 'missing_serial', and the UI displays a prompt to add the missing fields. Given fuzzy matching is attempted against the internal asset registry, When a candidate match score >= 0.85 is found, Then use the matched identifiers and include reason_codes=['fuzzy_match'] with match_score. Given a provider API returns 4xx or 5xx, When retries are exhausted (max 2 retries with exponential backoff up to 8 seconds), Then the computation falls back to document rules and logs an error event with provider_error_code. Given the request references multiple assets, When computation runs, Then warranty results are computed per asset and displayed in distinct sections with correct asset IDs.
Model/Serial Capture and Verification
"As a field tech or approver, I want accurate model and serial data captured once and verified so that future approvals and warranty checks are faster and more reliable."
Description

Capture asset model and serial numbers from tenant or technician input and optionally extract via OCR from photos. Validate format against manufacturer catalogs or internal patterns and associate the identifiers with the unit’s asset record. Provide confidence scoring and user prompts to correct ambiguous captures. Feed verified identifiers to warranty checks and history lookups, and store them for future requests to minimize re-entry.

Acceptance Criteria
Tenant Manual Entry Validation
Given a tenant is submitting a maintenance request for a known unit asset When the tenant enters model and serial numbers and clicks Save Then the system validates both fields against the selected manufacturer catalog (if available) or internal format patterns within 300 ms per field And invalid entries display field-level error messages specifying the failed rule and block Save And if both fields uniquely match a catalog entry, the identifiers are marked Verified with confidence 1.00 And if only pattern rules pass (no unique catalog match), the identifiers are marked Needs Confirmation with confidence between 0.90 and 0.99 and a prompt to confirm or edit And on successful confirmation or verification, the identifiers are persisted to the asset record and shown on the Context Card
Technician Photo OCR and Confirmation
Given a technician uploads one or more photos (≤10 MB each) of the asset plate When OCR processing completes Then candidate model and serial values are extracted with per-field confidence scores (0.00–1.00) and displayed to the user And if confidence ≥ 0.85 for a field, it is auto-populated; if < 0.85, the field requires manual confirmation or edit before Save And OCR processing completes within 10 seconds for images ≤ 5 MB and within 15 seconds for images > 5 MB and ≤ 10 MB (95th percentile) And if no candidates are found, the UI displays “No identifiers found” and allows manual entry without errors And upon user confirmation, the selected values are marked Verified if they uniquely match a catalog entry; otherwise Needs Confirmation with recorded confidence
Catalog/Pattern Verification Logic
Given captured model and serial values and (optionally) a selected manufacturer When verification runs Then model is validated against the manufacturer catalog (exact SKU/PN match) when catalog exists And serial is validated against the manufacturer’s serial format rules (length/charset/checksum/prefix) when available And if manufacturer is unknown or catalog unavailable, internal format patterns (regex + length constraints) are applied and results flagged as Pattern-Only And ambiguous catalog matches (≥2 possible models) prompt the user to choose one before Save And verification outcome sets status to Verified (unique catalog+serial format match), Pattern-Only (rules pass, no catalog), or Invalid (rule failure) with machine-readable reason codes
Association to Asset Record and Context Card Display
Given identifiers are Verified or user-confirmed When the request is saved Then model and serial are written to the unit’s asset record with fields: model, serial, manufacturer (if known), verificationStatus, confidence, source (manual/OCR), verifiedBy, verifiedAt, and version And the Context Card displays the stored identifiers, verification status badge, and last verified timestamp within 1 second of opening And the identifiers remain available for future requests for the same unit (no re-entry required) via API and UI retrieval
Verified IDs Trigger Warranty and History Lookups
Given an asset record transitions to Verified identifiers When the Context Card loads during approval Then the system triggers warranty check using the stored model/serial and displays status (In Warranty/Out of Warranty/Unknown) with coverage dates if available within 5 seconds (95th percentile) And prior work history is retrieved and summarized (count of past work orders for the asset and last service date) within 3 seconds And if external services fail or time out, the UI displays a non-blocking “Unavailable” status with a retry option and logs the failure with correlation ID
Pre-Fill, Conflict Resolution, and Audit Trail
Given a new maintenance request is created for a unit with stored identifiers When the request form initializes Then model and serial fields are pre-filled from the asset record and require no user input unless changed And if newly captured identifiers differ from stored ones, the system shows a side-by-side diff and requires the user to choose: Keep Existing, Replace, or Add Alternate And choosing Replace or Add Alternate requires a reason; the decision is recorded in an audit log with before/after values, source, user, timestamp, and confidence And the Context Card reflects the final chosen identifiers and indicates when a conflict resolution occurred
Prior Work History Timeline
"As an approver, I want a quick view of prior work on this unit/asset so that I can spot repeat issues and approve the right scope and vendor."
Description

Retrieve and summarize relevant past work for the unit and asset, including dates, vendors, costs, root cause notes, and outcomes. Detect and flag repeat issues within a configurable window and highlight recent vendor performance or callbacks. Present a compressed timeline in the context card with drill-through to detailed tickets. Include filters (by asset, issue type) and compute a brief insight (e.g., ‘3rd leak in 6 months’) to guide approval decisions.

Acceptance Criteria
Display Prior Work Summary on Context Card
Given a maintenance ticket for a specific unit and asset exists with at least one prior closed work order When the approver opens the Context Card Then a compressed timeline is displayed with up to the 5 most recent relevant entries And each entry includes date (ISO or locale-formatted), vendor name, cost (with currency), root cause notes (first 140 chars with ellipsis if longer), and outcome status And the timeline data loads within 800 ms at the 95th percentile (excluding network latency) And entries are sorted by most recent date descending
Repeat Issue Detection within Configurable Window
Given the repeat-issue detection window is configured to 6 months at the account level And there are two or more prior closed tickets matching the current ticket’s issue type for the same unit or same asset When the Context Card loads Then a repeat-issue badge is displayed with the text pattern "{Nth} {issueType} in {window}" And the count accurately reflects the number of occurrences within the window (including the current approval request) And changing the window to 12 months updates the badge count accordingly on next load
Vendor Performance and Callback Highlight
Given the most recent prior ticket for the asset/unit had a vendor callback within 14 days or a vendor rating <= 3/5 When the Context Card loads Then a vendor performance highlight is shown with one of: "Callback within 14 days", "Low vendor rating", or both if applicable And the highlight links to the referenced prior ticket And if neither condition is present, no vendor highlight is shown
Drill-through to Detailed Ticket from Timeline
Given the compressed timeline is visible on the Context Card When the approver clicks a timeline entry Then the detailed ticket view opens in a modal or new tab as per app standard And returning from the detailed view restores the Context Card state (scroll position, applied filters) And the drill-through action is logged with ticket ID and timestamp
Filter Timeline by Asset and Issue Type
Given the Context Card timeline is displayed with filter controls for Asset and Issue Type When the approver selects a specific asset from the filter Then only timeline entries for that asset are shown And when the approver selects one or more issue types Then only entries matching any selected issue type are shown And clearing filters restores the unfiltered timeline And applying or clearing a filter updates the list within 300 ms client-side (after data is cached)
No History and Partial Data Handling
Given there are no prior work orders for the unit and asset When the Context Card loads Then the timeline area displays "No prior work history" with a link to create filters disabled And no errors or placeholders flicker in the UI And if a prior entry lacks vendor or cost, the timeline shows "Unknown" for the missing field without blocking display of other fields
Asset Matching by Model/Serial and Unit Context
Given the asset on the ticket includes model and serial numbers And prior work orders exist for matching model/serial in the same unit When the Context Card loads Then only entries matching the same unit and the same asset (by asset ID or model+serial if ID differs) are included And entries from other units with the same model are excluded And an inline note "Matched by model/serial" is shown when ID fallback matching is used
Access and Scheduling Readiness
"As an approver, I want to see access constraints and earliest viable time slots so that I can approve work knowing it can be scheduled smoothly."
Description

Consolidate tenant access notes, preferred windows, and unit entry requirements with vendor availability from the shared calendar to show earliest viable scheduling options before approval. Warn on conflicts (e.g., restricted hours, key required) and suggest compatible vendors/time slots. Provide a readiness indicator and inline prompts to collect missing access details. Do not schedule yet; the goal is to remove uncertainty at approval time and prevent double-bookings later.

Acceptance Criteria
Earliest Viable Scheduling Suggestions
Given a maintenance request with tenant access notes, preferred time windows, unit entry requirements, and a shared vendor calendar with availability When an approver opens the Context Card before approval Then the system calculates and displays the first 3 earliest conflict-free vendor/time slot suggestions sorted by soonest date/time And all suggestions respect tenant preferred windows, property restricted hours, and vendor working hours And suggestions are time-zone aware for the property's locale And results render within 2 seconds for requests with up to 10 candidate vendors And no suggestion overlaps an existing vendor booking on the shared calendar
Conflict Detection and Warning Badges
Given any constraint that blocks scheduling (e.g., tenant restricted hours, key required with no key on file, tenant must be present with no availability provided, building blackout dates) When computing options Then the system surfaces a visible warning badge for each active conflict on the Context Card And no suggested time slot violates an active conflict And each warning lists a clear, human-readable reason and the field(s) to resolve it
Compatible Vendor Filtering
Given a request with trade/category, warranty coverage, and location When generating suggestions Then only vendors matching the trade and serving the property's location are considered And if warranty_covered = true, only warranty-authorized vendors are considered And vendors marked "Do Not Use" for the owner/portfolio are excluded And vendors with overlapping holds/appointments are excluded And for each suggestion the vendor name and reasoning tags (e.g., "authorized", "in-area") are displayed
Readiness Indicator Logic
Given the computed constraints and suggestions When the Context Card loads Then a readiness indicator displays one of: Ready, Needs Access Info, Needs Vendor, Conflicts And "Ready" is shown only if at least one conflict-free suggestion exists and all mandatory access fields are complete (permission to enter, key/lockbox info or tenant presence plan, pet/alarm notes if applicable) And "Needs Access Info" is shown if any mandatory access field is missing And "Needs Vendor" is shown if no compatible vendor remains after filtering And "Conflicts" is shown if constraints block all times despite complete access info And the indicator includes a tooltip listing the specific blockers or a checkmark summary when Ready
Inline Access Detail Collection
Given missing required access details When the approver clicks an inline prompt on the Context Card Then an in-place editor opens to capture the specific field(s) without page navigation And client-side validation enforces required format (e.g., 4–8 digit alarm code mask, free-text notes max 500 chars) And upon save, the details persist to the request and recomputation of suggestions occurs within 1 second And the readiness indicator and warnings update in real time
No Pre-Approval Scheduling or Notifications
Given the feature operates pre-approval When an approver views suggestions or updates access details Then no calendar events are created, modified, or deleted And no vendor or tenant notifications (SMS/email) are sent And the UI labels suggestions as "Not Scheduled" And an audit log records the viewing and data edits with timestamp and user ID
Role-Based Visibility and Audit Snapshot
"As an operations lead, I want sensitive details restricted by role and an auditable record of the approval snapshot so that we stay compliant and resolve disputes confidently."
Description

Enforce role-based access on context card fields (e.g., redact tenant PII for vendors) and capture an immutable audit snapshot of what the approver saw at decision time, including data version, timestamps, and actions taken. Store snapshots for compliance and dispute resolution and expose them in an audit log. Provide admin configuration for field-level visibility and retention policies.

Acceptance Criteria
Vendor View Redacts Tenant PII on Context Card
Given a user with role "Vendor" accesses a Context Card via UI or API When the Context Card data is retrieved and rendered Then all fields marked "hidden for Vendor" in Admin Visibility Settings are redacted in UI and omitted or null-masked in API responses And tenant PII (as defined in settings) is not displayed or returned And permitted fields render fully and accurately And any direct request for a redacted field returns HTTP 403 with a field-level error code And the render completes within 1 second at p95
Approver Decision Triggers Immutable Audit Snapshot
Given a user with role "Approver" has the Context Card open And field visibility is applied per the user's role When the user takes a decision action (Approve or Reject) Then the system creates an immutable audit snapshot capturing: request ID, action type, actor user ID and role, UTC timestamp (ms precision), data version identifiers for each visible entity/record, the exact values of fields as displayed (including redaction markers), and attachments metadata (filename, size, checksum) And the snapshot ID is linked to the decision record and visible in the Audit Log And subsequent edits to the Context Card do not alter the stored snapshot
Snapshot Immutability and Tamper-Evidence Enforcement
Given an existing audit snapshot When any user attempts to modify or delete the snapshot via UI or API Then the operation is rejected with HTTP 403 and no data is changed And snapshots are stored append-only with a stored cryptographic hash And any integrity check failure is logged as a critical alert and the snapshot is quarantined from display And permitted deletions occur only via automated retention purge and are logged
Admin Configures Field-Level Visibility by Role
Given an Admin opens Field Visibility Settings When the Admin changes visibility for one or more fields per role and saves Then the new rules apply to subsequent UI and API requests within 60 seconds And a "Preview as Role" function shows the Context Card exactly as it will appear for each role before saving And the change is recorded in the Audit Log with actor, timestamp, and before/after diff And invalid configurations (e.g., hiding all required approver fields) are blocked with specific validation errors
Audit Log Surfaces Snapshots with Search, View, and Export
Given a user with "AuditViewer" permission opens the Audit Log When they filter by date range, request ID, actor, action type, and role, and sort by timestamp Then results are returned within 2 seconds p95 and include snapshot ID, request ID, actor, action, role, timestamp, and snapshot hash And clicking a result opens a read-only view of the snapshot exactly as captured And exporting CSV or JSON includes the visible metadata only and excludes redacted PII values And users without permission receive HTTP 403 and see no entries
Retention Policy Enforcement and Legal Hold
Given retention is configured to retain audit snapshots for N months When a snapshot exceeds N months and there is no active legal hold Then the snapshot data is purged by an automated job and the Audit Log retains a tombstone entry with snapshot ID, purge timestamp, and reason And snapshots under legal hold are not purged until the hold is removed And each purge action is recorded in the system audit trail with actor "system" and UTC timestamp And manual purge requests are not allowed

SLA Escalator

Ties approvals to response/resolution SLAs with live timers. Auto-sends reminder nudges, escalates to alternates as deadlines near, and can auto-approve under defined emergency rules. Keeps work moving, avoids SLA misses, and documents every step for compliance.

Requirements

SLA Policy Builder
"As a property manager admin, I want to configure SLAs tailored to each property and issue type so that approvals and resolutions are consistently enforced and measurable."
Description

Allow admins to define response and resolution SLAs per property, request category, priority, and approver role, including business hours, holidays, time zones, and vendor calendars. Support separate timers for first response, approval decision, dispatch, and resolution, with pause/resume rules for waiting-on-tenant or vendor-unavailable states. Provide versioned policies with effective dates and safe defaults, and validate policies to prevent gaps or conflicts. Expose policies to other modules through a stable internal API.

Acceptance Criteria
Define SLA by Property/Category/Priority/Approver Role
Given an admin opens the SLA Policy Builder to create a new policy version When they select Property=Oak Court (ID 101), Category=Plumbing, Priority=High, Approver Role=Property Manager and set First Response=1 business hour, Approval Decision=2 business hours, Dispatch=4 business hours, Resolution=24 business hours Then the policy saves successfully with a unique Policy Version ID and the rule appears in the matrix for the selected dimensions And when a maintenance request matches Property=101, Category=Plumbing, Priority=High, Approver Role=Property Manager, the engine resolves the applicable timers from this policy version
Apply Business Hours, Holidays, and Time Zones
Given Property=Lakeview (TZ=America/Chicago) has business hours Mon–Fri 09:00–17:00 and holidays=[2025-11-27] When a First Response SLA of 4 business hours starts at 2025-11-26 16:00 CST Then the due time is calculated as 2025-11-28 12:00 CST (1 hour on 11/26, skip 11/27 holiday, remaining 3 hours on 11/28) And when the same SLA starts at 2025-11-30 03:00 CST (outside business hours), the countdown begins at 2025-12-01 09:00 CST and is due at 2025-12-01 13:00 CST And all SLA calculations use the property's time zone regardless of the admin or tenant's local time zone
Independent Timers for Response/Approval/Dispatch/Resolution
Given a request is created and the applicable policy defines timers for First Response, Approval Decision, Dispatch, and Resolution When the request is created at 10:00 Then the First Response timer starts at 10:00 and stops at the first staff reply event timestamp When an approval is requested at 11:15 Then the Approval Decision timer starts at 11:15 and stops when approved or denied When the work order is approved at 12:00 and assigned to a vendor at 12:05 Then the Dispatch timer runs from 12:00 to 12:05 and stops on assignment When the vendor is dispatched at 12:05 and marks work completed at 16:30 Then the Resolution timer runs from 12:05 to 16:30 in business time as defined by the policy And each timer's start/stop timestamps and elapsed business time are recorded as auditable events
Pause/Resume on Waiting-on-Tenant and Vendor-Unavailable
Given a request's First Response timer has 90 minutes remaining when the status is changed to Waiting on Tenant at 10:15 When the tenant replies at 11:00 the next business day Then the First Response timer resumes with 90 minutes remaining and its due time is recalculated using business hours Given a vendor calendar marks the assigned vendor unavailable on 2025-09-10 from 09:00 to 13:00 local time When the Dispatch and Resolution timers would otherwise run during that window Then those timers pause for the duration of the unavailability and resume at 13:00 And paused durations are excluded from elapsed business time and are shown in the audit trail with pause/resume reasons
Versioned Policies with Effective Dates and Safe Defaults
Given an organization has Policy Version v1 effective 2025-09-01 to 2025-09-30 and v2 effective from 2025-10-01 onward When a matching request is created on 2025-09-15 Then v1 is applied; when created on 2025-10-02, v2 is applied And published policy versions are immutable; edits create a new version with a new effective date range And when no specific rule matches a request, the configured Safe Default policy for the organization/property is applied without error
Policy Validation Prevents Gaps and Conflicts
Given an admin attempts to publish policies with overlapping rules for the same Property/Category/Priority/Approver Role and effective date ranges When they click Publish Then the builder blocks publication and displays specific errors identifying the conflicting rules Given an admin attempts to publish with gaps where no rule or default would apply for an active property Then the builder blocks publication and lists the uncovered dimensions that must be addressed And publishing succeeds only when the rule set has no conflicts and every active property has coverage via specific rules or a safe default
Internal API Exposes Effective SLA Policy and Timers
Given other modules request an effective SLA via GET /internal/slas/v1/effective?propertyId=...&category=...&priority=...&approverRole=... When the inputs correspond to a valid context on 2025-10-02 Then the API returns 200 with policyVersionId, timers={firstResponse, approvalDecision, dispatch, resolution}, businessHours, timeZone, holidaySet, and pauseRules And when no specific match exists, the API returns the resolved Safe Default policy instead of 404 And the response schema remains backward compatible across minor releases and is documented with version=v1 in the payload
Live SLA Timers and Visual Indicators
"As a coordinator, I want to see live SLA timers and risk indicators on each request so that I can prioritize actions before deadlines are missed."
Description

Real-time countdown timers displayed on request cards and detail views for each SLA, updating live and reflecting pause/resume states and time zone adjustments. Color-coded stages and badges indicate on-track, at-risk, or breached thresholds, with hover tooltips explaining remaining time and the rule source. Timers persist server-side to ensure accuracy across sessions and devices and are resilient to client clock drift. Provide accessibility-compliant visual and text cues.

Acceptance Criteria
Real-time SLA Timer Display with Pause/Resume
Given a request with an active Response or Resolution SLA When the request is viewed on both the list card and the detail view simultaneously Then both views show a live countdown that updates at least once per second and display values within ±1 second of each other Given the SLA is paused due to a status or reason that pauses timing When the pause event occurs Then the timer stops within 2 seconds, shows a Paused state, and freezes the remaining time without decreasing Given the SLA is resumed When the resume event occurs Then the timer resumes within 2 seconds from the frozen remaining time and continues decreasing correctly Given the user refreshes the page or reopens the app When the view loads Then the timer rehydrates from server state within 1 second and shows the correct remaining time
Color and Badge States for SLA Thresholds
Given an SLA rule defines an at-risk threshold (e.g., N minutes before breach) and breach at remaining time ≤ 0 When remaining time > N minutes Then the timer shows state On Track with a green badge and the text label On Track Given the same configuration When 0 < remaining time ≤ N minutes Then the timer shows state At Risk with an amber/orange badge and the text label At Risk Given the same configuration When remaining time ≤ 0 Then the timer shows state Breached with a red badge and the text label Breached Given a request appears on both the list card and detail view When its state changes between On Track, At Risk, and Breached Then both locations reflect the same state within 2 seconds
Hover Tooltip: Remaining Time and Rule Source
Given a user hovers the timer or focuses it via keyboard When the tooltip is triggered Then it appears within 300 ms and shows: remaining time (hh:mm:ss), SLA type (Response/Resolution), rule name/source, start timestamp, breach deadline timestamp, and the timezone abbreviation Given the SLA is paused When the tooltip is opened Then it additionally shows Paused with the pause reason if available Given the tooltip is open When the user moves the pointer away, blurs focus, or presses Escape Then the tooltip closes within 200 ms
Server-Side Persistence and Cross-Device Consistency
Given a user opens the same request on two different devices or browsers When both views are loaded Then the remaining time matches within ±1 second and counts down in sync using server time Given a user closes the app for 10 minutes When they reopen the same request Then the timer reflects the correct remaining time based on server state within 1 second of load Given the browser tab is reloaded When the page renders Then the timer value is derived from server state, not client clock, with no visible jump after initial render
Client Clock Drift and Offline Handling Resilience
Given the client device clock is skewed ±10 minutes from actual time When the timer displays Then the remaining time is accurate to server time within ±1 second and does not depend on client clock Given the network connection drops for up to 60 seconds When connectivity is restored Then the timer reconciles to server time within 2 seconds with at most one visible correction and never increases remaining time unless resuming from a paused state Given the app is backgrounded for 10 minutes When the user returns focus Then the timer corrects to the server value within 1 second
Accessibility Compliance: Contrast, Text, and Focus
Given the timer and badges are rendered When evaluated for contrast Then text and icon/badge foreground-to-background contrast ratios are ≥ 4.5:1 (WCAG AA) Given users who cannot rely on color perception When viewing the SLA state Then a non-color cue is present (text labels On Track, At Risk, Breached) alongside color indicators Given a screen reader user focuses the timer When the control is announced Then it includes: SLA type, remaining time in human-readable units, current state, and paused/resumed status if applicable Given live countdown updates When using assistive technology Then announcements are throttled (no per-second announcements) and are triggered only on state changes, minute boundaries, pause/resume, or breach events using aria-live="polite" Given keyboard navigation When tabbing Then the timer and its info/tooltip trigger are focusable, show a visible focus indicator, and the tooltip can be dismissed with Escape
Time Zone and Daylight Saving Adjustments
Given a property is in Time Zone A and a user views the request from Time Zone B When the timer and tooltip display Then remaining time is independent of timezone and absolute timestamps in the tooltip reflect the viewer's local timezone with abbreviation Given an SLA window spans a Daylight Saving Time change in the property's timezone When computing the breach deadline Then the deadline accounts for the DST shift per IANA rules and the remaining time is correct across the transition Given a user changes their profile timezone and reloads When viewing the same request Then absolute timestamps update to the new timezone while remaining time stays unchanged
Automated Reminder Nudges
"As an approver, I want timely reminders with direct action links so that I can respond quickly without hunting for the request."
Description

Configurable reminder schedules that send email, SMS, and in-app notifications to the current approver and stakeholders as SLA deadlines approach, with escalating frequency and content templates. Respect user notification preferences, quiet hours, and locale, and include deep links to approve/deny or reassign. Capture delivery status and read receipts where available to feed the audit trail and trigger follow-up rules.

Acceptance Criteria
Escalating Reminder Schedule Before SLA Deadline
Given a pending approval with an SLA due at 2025-09-10 17:00 in the approver’s time zone and a reminder schedule configured at 24h, 6h, 1h, and 15m with templates R1–R4 When the clock reaches each threshold prior to the due time Then the system sends a reminder via the configured channels (email, SMS, in-app) to the current approver and subscribed stakeholders And each send uses the template mapped to that threshold And the 24h, 6h, 1h, and 15m reminders are each sent no more than once And no reminder is sent after the item is approved, denied, or reassigned And the schedule recalculates if the SLA due time changes, sending only future-due reminders
Respect User Notification Preferences and Quiet Hours
Given the approver has channel preferences (email:on, SMS:off, in-app:on) and quiet hours 22:00–07:00 in their local time zone When a reminder becomes due during quiet hours Then the system defers the reminder to 07:00 local time And only uses the enabled channels for each recipient And stakeholders receive reminders only if they are opted-in per role and channel And no reminder is sent during quiet hours
Localized Content and Time Zone Rendering
Given the approver's locale is fr-FR and time zone Europe/Paris When generating a reminder for a due time of 2025-09-10 17:00 UTC Then the message subject and body are rendered in French And dates/times are formatted in Europe/Paris local time using fr-FR conventions And all placeholders in the template (e.g., {ticket_id}, {due_time_local}, {unit_address}, {deep_link}) are resolved with correct values And SMS content exceeding a single segment is handled per configuration (split or link-shortened) without truncation
Deep Links to Approve/Deny/Reassign with Secure Tokens
Given a reminder message contains deep links for Approve, Deny, and Reassign actions with signed, single-use tokens valid for 24 hours When the recipient clicks a link while authenticated Then the corresponding action screen opens preloaded with the request context and can be completed in ≤2 clicks When the recipient clicks a link while not authenticated Then they are prompted to authenticate and then taken to the action screen When the token is expired or already used Then the user is redirected to the request detail page with a notice to take action And all links use HTTPS and record click events with timestamp and channel
Delivery Status and Read Receipts Logged to Audit Trail
Given providers support delivery and open/read status When a reminder is sent via email, SMS, and in-app Then the system stores for each channel: message ID, provider, send timestamp, delivery status (queued, sent, failed, bounced), and read/open timestamp where supported And creates an immutable audit entry linked to the request and recipient And the audit trail is searchable by request ID and recipient, and exportable within 5 seconds for compliance
Failure Handling, Fallback, and De-duplication
Given a reminder send attempt fails or bounces on a channel When the configured fallback rule is enabled Then the system retries per backoff policy and/or switches to an alternate channel within 5 minutes And records the failure and fallback in the audit trail And if the approver takes action (approve, deny, or reassign) or an alternate sends a manual notification, all pending reminders for that item are cancelled And cross-channel de-duplication ensures that within a 2-minute window only one reminder per recipient is delivered
Escalation Routing Rules
"As an operations lead, I want overdue approvals to automatically escalate to available alternates so that work continues without bottlenecks."
Description

Rule-based escalation engine that reassigns approvals to alternates or supervisory roles as timers cross configurable thresholds, supporting round-robin, skill-based, and property-based routing. Provide fallback chains, maximum hop limits, and conflict checks to avoid loops or dead ends. Integrate with the shared calendar to select alternates who are available and to place temporary holds on time slots for urgent dispatches.

Acceptance Criteria
Threshold-Based Escalation at SLA T-15 Minutes
Given an approval request with a response SLA and an escalation threshold set to 15 minutes before breach And the request is still unacknowledged by the primary approver When the live SLA timer crosses T-15 minutes Then the system reassigns the approval to the next eligible alternate per the active routing rule And sends notifications to the primary and the new assignee via SMS and in-app within 5 seconds And records an immutable escalation event with timestamp, previous assignee, new assignee, threshold "T-15", and hop number = 1
Skill-Filtered Round-Robin Alternate Selection
Given an escalation pool with users tagged with skills and a persistent round-robin index per property and skill And a request requiring skill "HVAC" When an escalation occurs Then the engine selects the next available alternate who has skill "HVAC" and is not in conflict (e.g., OOO, max-load, prior assignee) And advances and persists the round-robin index atomically And given three alternates A, B, and C all available, after six consecutive escalations the assignment sequence cycles evenly (e.g., A, B, C, A, B, C)
Property-Based Fallback Chain with Max-Hop Enforcement
Given Property P has a fallback chain [Ops Team, Property Supervisor, Regional Manager] and a maximum hop limit of 3 And no eligible assignee is found in Ops Team due to unavailability or conflicts When escalation is evaluated Then the engine attempts assignment to Property Supervisor, then Regional Manager if needed And never exceeds 3 hops And if no assignment succeeds by hop 3, the request is flagged "Escalation Stalled" and an alert is sent to the compliance channel within 10 seconds
Loop and Conflict Prevention on Rules and Runtime
Given an admin configures escalation rules that produce a cycle or dead-end graph When they attempt to save the rules Then the system rejects the save with a validation error naming the cycle path or dead-end node Given an in-flight escalation chain where user X has already been assigned in a prior hop When evaluating candidates for the next hop Then the engine excludes user X and any already-tried assignees from consideration
Calendar Availability Filter and Urgent Dispatch Holds
Given the shared calendar integration is connected And an urgent dispatch window W is defined for the request When escalation assigns to an alternate Then only alternates with free time overlapping W are considered eligible And the system places a temporary calendar hold titled "FixFlow Hold" for W that expires after 15 minutes if not confirmed And if the assignee declines or times out, the hold is released automatically and the next escalation hop is triggered And no double-bookings occur; if a confirmed event exists that overlaps W, the alternate is skipped
No Eligible Alternate Terminal Handling
Given no eligible alternate exists across all configured pools and fallback levels for the request When escalation evaluation completes Then the engine assigns the request to the terminal role "Duty Supervisor" as configured Or, if the emergency flag and rule "Auto-Approve on Emergency" are enabled, the request is auto-approved And the terminal action, reason code, and decision path are logged and notifications are sent to the compliance channel within 10 seconds
Emergency Auto-Approval Safeguards
"As a landlord, I want emergencies to be auto-approved within safe limits so that critical issues are addressed immediately without waiting for human approval."
Description

Emergency rules that can auto-approve requests when specific criteria are met, such as life-safety categories or after a grace period, with guardrails like spend caps, vendor whitelists, and mandatory post-approval review. Require dual confirmation for rule activation and log justification fields on each auto-approval event. Provide toggles to suspend auto-approval during known outages or vendor blackouts.

Acceptance Criteria
Life-Safety Auto-Approval Trigger
Given an Emergency Auto-Approval rule is enabled with life-safety categories including "Gas Leak" and a spend cap of 1000 USD and a vendor whitelist that includes VendorA for Property P When a maintenance request is submitted for Property P categorized as "Gas Leak" with estimatedCost=800 USD and preferredVendor=VendorA and all outage/blackout toggles are OFF Then the system auto-approves the request within 60 seconds And the request status is set to "Approved" And the selected vendor is VendorA And an AutoApproval event is recorded with non-null fields: eventId, ruleId, triggerType="life-safety", matchedCategory="Gas Leak", estimatedCost=800, spendCap=1000, vendorId="VendorA", togglesState="OFF", timestamp
Grace-Period Auto-Approval After No Response
Given an Emergency Auto-Approval rule is enabled with a gracePeriod of 2 hours for approval step "Manager Approval" and all guardrails (spend cap, vendor whitelist, toggles OFF) are configured When a request awaiting "Manager Approval" reaches the gracePeriod threshold Then the system auto-approves the request within 60 seconds And an AutoApproval event is recorded with triggerType="grace-period" and fields: ruleId, gracePeriodReachedAt, approvedAt, guardrailsVerified=true And the request status is set to "Approved" And if any guardrail fails, the system does not auto-approve, records guardrailsVerified=false with reason (e.g., "SPEND_CAP_EXCEEDED" or "NO_WHITELISTED_VENDOR"), and leaves the request pending
Spend Cap Guardrail Enforcement
Given an Emergency Auto-Approval rule with spendCap=1000 USD is active When an auto-approval trigger fires for a request with totalEstimatedCost (including known fees)=950 USD Then auto-approval is allowed to proceed with guardrailsVerified=true and the event logs spendCap=1000 and totalEstimatedCost=950 When an auto-approval trigger fires for a request with totalEstimatedCost=1200 USD Then auto-approval is blocked, the request remains pending, and the event logs guardrailsVerified=false with reason="SPEND_CAP_EXCEEDED" and includes spendCap=1000 and totalEstimatedCost=1200
Vendor Whitelist Guardrail Enforcement
Given an Emergency Auto-Approval rule is active with an Emergency Vendor Whitelist for Property P containing VendorA and VendorB When an auto-approval trigger fires for a request at Property P with preferredVendor=VendorA Then auto-approval may proceed using VendorA and the event logs vendorId="VendorA" and guardrailsVerified=true When an auto-approval trigger fires for a request at Property P with preferredVendor=VendorX (not whitelisted) Then auto-approval is blocked, the request remains pending, and the event logs guardrailsVerified=false with reason="NO_WHITELISTED_VENDOR"
Dual Confirmation Required to Enable or Modify Emergency Rules
Given a user with role "Rule Admin" submits a change to enable or modify an Emergency Auto-Approval rule When the first approver confirms the change Then the rule change remains in a "Pending Confirmation" state and is not active When a second distinct user with role "Rule Approver" confirms the same change within 24 hours Then the rule change becomes active immediately and an audit log entry records approver1, approver2, timestamps, and a before/after diff of the rule And if the second confirmation is not received within 24 hours, the pending change is discarded, the rule remains unchanged, and a notification is sent to the initiator
Mandatory Post-Approval Review and Justification Logging
Given an auto-approval event occurs under an Emergency Auto-Approval rule When the event is recorded Then the system creates a Post-Approval Review task assigned to the configured reviewer role And the work order cannot transition to "Closed" or "Payment Approved" until the review task is completed And the review form requires non-empty fields: justificationText, reviewerId, reviewOutcome (Approve/Reject), finalAmount And the auto-approval event record stores: ruleId, triggerType, guardrailsVerified, reviewerId, reviewCompletedAt, and justificationText
Global Outage and Vendor Blackout Toggles Suspend Auto-Approvals
Given a global "Auto-Approval Suspension" toggle is ON When any auto-approval trigger fires Then no auto-approval is executed, the request remains pending, and an event is recorded with reason="SUSPENDED_BY_GLOBAL_TOGGLE" Given a vendor-specific blackout toggle is ON for VendorA When an auto-approval trigger fires for a request that would select VendorA under whitelist rules Then no auto-approval is executed, the request remains pending, and an event is recorded with reason="SUSPENDED_BY_VENDOR_BLACKOUT" and vendorId="VendorA" And when the relevant toggle is turned OFF, subsequent triggers follow normal auto-approval behavior subject to guardrails
Compliance Audit Trail and Reporting
"As a compliance auditor, I want a complete, exportable history of SLA decisions and notifications so that I can verify adherence and investigate breaches."
Description

Immutable event log capturing policy versions, timer start/pause/resume/stop events, reminder deliveries, escalations, approvals, denials, and auto-approvals, with actor identity, timestamps, and channel metadata. Provide searchable history on each request, export to CSV/PDF, and an API endpoint for compliance audits. Include SLA attainment metrics and breach reasons for reporting dashboards.

Acceptance Criteria
Immutable Event Log for SLA Actions
Given an SLA-managed request When any of the following occurs: timer_started, timer_paused, timer_resumed, timer_stopped, reminder_delivered, escalation_sent, approval_granted, approval_denied, auto_approval_applied Then an append-only event is written with fields: event_id (UUIDv4), request_id, event_type, actor_type (user/system/vendor/tenant), actor_id (nullable for system), channel (web/api/sms/email), correlation_id, occurred_at (UTC ISO-8601 with ms), received_at (UTC ISO-8601 with ms) And the event is visible in the request’s audit history within 2 seconds of the action And attempts to modify or delete the event via any API or UI endpoint return 403 and produce no changes And events for a single request are returned sorted by received_at ascending with a stable tie-breaker on event_id And for reminder_delivered and escalation_sent events, channel_metadata includes provider_message_id, delivery_status, and destination
Policy Version Snapshot on Events
Given a policy version X is in effect when an event is generated Then the event stores policy_version_id = X and policy_snapshot (immutable JSON) When the active policy is later changed to version Y Then existing events continue to show X and new events show Y And each event exposes policy_name, version_number, and approver user_id And events without an applicable policy record policy_version_id = null and policy_applied = false
Searchable Per-Request Audit History
Given a user with Audit:Read permission When they open a request’s Audit tab Then they can filter by date range, event_type, actor, and channel And the first 100 events load in ≤ 2 seconds for requests with up to 1,000 events, with cursor-based pagination available And keyword search matches event metadata and message previews for reminder and escalation events And timezone display respects the user’s profile timezone setting with a toggle to UTC And copying an event_id places the exact UUID on the clipboard
CSV and PDF Export of Audit Trail
Given a user with Audit:Export permission When they export a request’s audit trail to CSV Then the file contains a header row and one row per event with fields: event_id, request_id, event_type, actor_type, actor_id, channel, correlation_id, occurred_at_utc, received_at_utc, policy_version_id, policy_applied, metadata_json And rows are ordered by received_at ascending And the CSV is UTF-8 encoded with CRLF line endings and proper escaping per RFC 4180 When exporting to PDF Then the document includes a chronological timeline with the same fields plus a page-level summary And exports of up to 5,000 events complete in ≤ 10 seconds; larger exports stream in chunks And the export includes an SLA summary section with attainment status and breach reasons for the request
Compliance Audit API Endpoint
Given a valid OAuth2 bearer token with scope audit.read When a client calls GET /api/v1/audit-events with filters (request_id, date_range, event_type[], actor_id, channel) Then the API returns 200 with a JSON array of events and a cursor for pagination And per_page supports up to 500; larger values return 400 And rate limiting enforces 60 requests per minute per token; excess returns 429 with Retry-After And the response schema includes: event_id, request_id, event_type, actor, channel, correlation_id, occurred_at, received_at, policy, metadata, integrity fields (hash, prev_hash) And unauthorized returns 401; insufficient scope returns 403; invalid filter returns 400; server error returns 500 with a request correlation_id in headers
Tamper-Evident Storage and Access Controls
Given any new audit event When it is persisted Then the event includes content_hash = SHA-256(payload) and prev_hash referencing the previous event in the same request, forming a hash chain And a daily job computes and stores a Merkle root over all events created that day And the Verify Integrity action recomputes and reports any chain breakage or hash mismatch, returning Pass when none are found And write operations are append-only; DELETE is disabled for audit tables; direct DB access is blocked for non-admins And audit records are retained for ≥ 7 years in WORM-capable storage configured at the environment level
SLA Attainment Metrics and Breach Reasons
Given completed requests within a selected date range When generating the SLA report Then the dashboard shows for each SLA type: total_requests, attained_count, breached_count, attainment_rate%, avg_time_to_approve, avg_time_to_resolve, p95_times And each breach stores and displays a breach_reason code from an enumerated list (e.g., waiting_on_tenant, vendor_no_show, approver_unavailable, system_outage) with optional free-text notes And metrics are filterable by property, portfolio, approver, vendor, and SLA policy version And counts and aggregates reconcile with underlying audit events within ±0.1% And exporting the report to CSV/PDF preserves the metrics and includes generation timestamp and applied filters

ProofHold Escrow

Automatically escrows payout funds at work order approval and releases them when required proof items are submitted (completion photos, tenant sign-off, permit number). Configure partial holdbacks by line item or percentage; FixFlow prompts vendors for missing artifacts and shows real-time release status. Managers get clean audit trails and fewer follow-ups; vendors see exactly what’s needed to get paid faster.

Requirements

Payment Gateway Abstraction & Escrow Ledger
"As a property manager, I want FixFlow to securely hold and release funds through a trusted payment processor so that vendors are paid only when agreed conditions are met without manual money movement."
Description

Implement a payment integration layer that supports creating, holding, adjusting, and releasing payout funds through one or more third‑party processors while maintaining an internal escrow ledger for authoritative state. Use tokenized payment methods to minimize PCI scope, handle idempotent operations, reconcile with gateway webhooks, and automatically retry transient failures. Track funding sources (owner/property accounts) and payout destinations (vendor accounts) with KYC/verification status, enforce limits and availability checks, and support sandbox/testing modes. Provide clear error surfaces and recovery paths to ensure funds and ledger never drift out of sync.

Acceptance Criteria
Idempotent Escrow Creation on Work Order Approval
Given a work order approval event with a hold amount, verified funding source, verified vendor destination, and idempotency key IK1 And the funding source has available balance and is within configured limits And no ledger entry exists with idempotency key IK1 When CreateEscrow is invoked with a tokenized payment method and IK1 Then a single escrow ledger entry is written with state=HoldInitiated and amount equal to the hold amount And a hold/transfer is submitted to the configured payment processor using the token (no raw PAN) And on processor 2xx acknowledgement, the ledger is updated to state=Held with gatewayRef and correlationId And any duplicate call with IK1 returns the same escrowId and state without creating additional holds And if the processor returns a retryable error (e.g., timeout, 5xx), the operation is queued for retry and the ledger state is RetryPending without duplicate financial movements
Escrow Adjustment (Increase/Decrease) with Idempotency
Given an existing escrow ledger entry in Held state with amount A1 and an adjustment idempotency key IK2 And an authorized request to adjust by delta D that complies with business rules and limits When AdjustEscrow is invoked with IK2 and delta D Then the ledger records an immutable adjustment entry with delta D and new balance A2=A1+D And the processor is called to apply the corresponding change (additional hold or partial release) linked to the adjustment entry And repeated calls with IK2 do not duplicate adjustments and return the same resulting balance and gateway reference And if the adjustment would violate limits or result in a negative balance, the request is rejected with error code ADJ_LIMIT and no gateway call is made
Conditional Release on Proof Submission and Manager Approval
Given an escrow in Held state with a defined proof checklist for the work order And all required proof artifacts are present and validated And the vendor payout destination is KYC-verified and within payout limits When ReleaseEscrow is invoked by an authorized manager with idempotency key IK3 Then the ledger transitions to ReleaseInitiated and a payout is submitted to the processor for the remaining held amount And upon webhook confirmation of settlement, the ledger state becomes Settled with settlement timestamp and gatewayRef And if the processor declines, the ledger state becomes ReleaseFailed with error code and recovery guidance And duplicate calls with IK3 do not create duplicate payouts and return the same payout reference and state
Gateway Webhook Reconciliation and Drift Guard
Given the payment processor sends signed webhook events for holds, adjustments, payouts, and settlements When a webhook is received Then the event signature and freshness are validated and the event is processed idempotently And the event is mapped to the correct escrow/payout by gatewayRef and updates the ledger atomically to the correct state And out-of-order or missing events trigger a reconciliation job that queries the processor and repairs ledger state without creating new financial movements And any detected drift between the internal ledger and processor authoritative state is flagged, alerted, and requires explicit operator acknowledgment to resolve
KYC, Limits, and Availability Enforcement
Given funding source and payout destination accounts have KYC status, limits, and available balances tracked When initiating a hold, adjustment, or release Then the operation proceeds only if both accounts meet KYC policy, limit checks, and funds availability And if checks fail, the operation is blocked with specific error codes (e.g., KYC_UNVERIFIED, LIMIT_EXCEEDED, INSUFFICIENT_FUNDS) and a ledger non-financial event is recorded And no gateway call is attempted for blocked operations
Automatic Retry and Operator Recovery Paths
Given a transient failure occurs during hold, adjust, or release (e.g., network timeout, 5xx, rate limit) When the operation fails Then the system schedules retries with exponential backoff and jitter up to N attempts and marks ledger state RetryPending And each retry is idempotent with the same idempotency key and correlationId And on final failure, an alert is emitted and operators can trigger Retry Now or Cancel/Mark Resolved actions, which are also idempotent and auditable
Sandbox and Testing Modes
Given the environment or account is configured for sandbox mode When performing hold, adjust, release, or processing webhooks with test tokens Then calls are routed to sandbox endpoints and ledger entries are marked test=true with no movement of real funds And simulated webhook events are emitted to drive state transitions And all logs and audit trails include a Sandbox tag and correlation IDs and test records are isolated from production ledgers
Escrow Initiation at Work Order Approval
"As a property manager, I want escrow to be created automatically when I approve a work order so that funding is secured before the vendor begins work."
Description

On work order approval, automatically calculate the escrow amount based on total, taxes/fees, and configured holdbacks, then place a funding hold against the selected source. Create escrow and line‑item ledger entries linked to the work order, record gateway IDs, and display immediate status. Support approval-time checks (available balance, KYC, payout method on file), fallback paths (defer hold, retry, alternate source), and automatic adjustments when change orders modify line items or totals. Prevent unintended scheduling conflicts by surfacing funding status to all stakeholders.

Acceptance Criteria
Escrow Calculation on Approval
Given a work order with a total, taxes/fees, and configured holdbacks (percentage and/or per line item) When the approver approves the work order Then the system calculates the escrow amount including taxes/fees and applying the configured holdbacks And the calculation uses currency rounding to 2 decimal places (half-up) And a breakdown (base, taxes/fees, holdbacks by rule and by line item) is persisted with the work order And the calculated escrow amount is displayed in the Approval success panel and on the work order header within 5 seconds
Funding Hold Placement and Gateway Recording
Given a calculated escrow amount and a selected funding source When approval is processed Then an authorization/hold for the exact escrow amount is placed on the selected source And the gateway authorization ID and source/customer identifiers are recorded on the escrow record And the work order funding status updates to Held within 5 seconds if the hold succeeds And duplicate approval attempts within 60 seconds do not create additional holds or ledger entries and reuse the existing hold (idempotent)
Approval-Time Eligibility Checks
Given an approver initiates approval for a work order When pre-approval checks run Then the system validates available balance on the selected source, account KYC status, and vendor payout method on file And if all checks pass, the hold attempt proceeds And if any check fails, the approval is paused before attempting a hold and a blocking alert lists the failing checks
Fallback Paths on Hold Failure or Check Failures
Given a hold attempt fails or pre-approval checks fail When the approver selects Defer hold Then the work order is approved with funding status Funding Deferred and the failure reason is recorded When the approver selects Retry Then the system retries the hold once immediately and once more after a 30-second delay before marking status Failed When the approver selects Alternate source and chooses a verified source Then the system attempts the hold against the alternate source, records the new gateway IDs, and associates the successful hold to the escrow record And all outcomes and reasons are logged to the audit trail
Ledger Entries and Audit Trail
Given an approval results in a successful hold When the escrow is created Then an escrow ledger entry linked to the work order is created with amount, tax/fee components, and holdback details And line-item sub-ledger entries are created reflecting each line item's held and held-back amounts And gateway IDs, timestamps, and actor IDs are stored And an audit trail view shows a single coherent transaction with these entries And re-approvals do not create duplicate ledger entries
Change Order Auto-Adjustment of Escrow
Given a work order with an existing escrow hold receives an approved change order modifying line items or totals When the system recalculates the escrow Then the delta is computed and applied by increasing the hold for positive deltas or partially releasing for negative deltas And corresponding adjustment ledger entries are created and linked to the original escrow And the funding status reflects the new state within 5 seconds and stakeholders are notified of the change And if the adjustment fails, the previous hold remains intact, the status is Adjustment Failed, and fallback options are offered
Funding Status Visibility and Scheduling Safeguards
Given stakeholders view the work order details, shared calendar, or vendor portal When funding status is not Held Then auto-scheduling is disabled and manual scheduling requires explicit confirmation with a funding warning And the current funding status (Held, Funding Deferred, Failed, Adjustment Pending/Failed) is displayed prominently in each surface and included in vendor SMS/email confirmations When funding status becomes Held Then scheduling proceeds normally and the status display updates within 5 seconds
Holdback Configuration by Line Item or Percentage
"As an operations lead, I want to define holdback rules by line item or percentage so that payouts align with our risk and quality controls without manual spreadsheet tracking."
Description

Provide configurable holdback rules at organization, property, and work‑order levels with overrides. Allow holdbacks by line item, percentage of line item, or percentage of total, with caps, minimums, and conditional rules (e.g., permit-required trades). Validate that rules are coherent, preview payout timelines, and store configurations as reusable templates. Ensure calculations are transparent to vendors and managers with a breakdown of immediate pay vs. on‑proof release.

Acceptance Criteria
Organization-Level Default Holdback Setup and Preview
Given I am an organization admin on FixFlow with ProofHold Escrow enabled When I create a default holdback rule of 25% of total with a minimum of $50 and a cap of $500 and save Then the rule is persisted as the organization's default and is available for inheritance by properties and work orders And the preview for a $2,000 work order shows Immediate Pay $1,500 and On-Proof Release $500 with the cap applied And values outside 0–100% or negative min/cap are rejected with inline errors before save
Property and Work-Order Override Precedence
Given the organization has a default 25% total holdback rule And a property sets an override of 10% total with a $100 cap And a work order on that property sets an override of line-item 15% with a $50 minimum per line When I view the effective rule on the work order Then the work-order override is effective and the UI indicates precedence: Work Order > Property > Organization And removing the work-order override reverts to the property override And removing the property override reverts to the organization default
Line-Item Percentage Holdbacks with Min/Cap Per Line
Given a work order has two line items: Plumbing $800 and Electrical $1,200 And the effective rule is 15% holdback per line item with $50 minimum and $150 cap per line When the system calculates holdback amounts Then Plumbing holdback is $120 and immediate pay is $680 And Electrical holdback is $150 (capped) and immediate pay is $1,050 And the total holdback is $270 and total immediate pay is $1,730
Conditional Holdback for Permit-Required Trades
Given a rule adds an additional $200 holdback per permit-required line item in addition to the base percentage And a work order has one Electrical line item flagged Permit Required and one Painting line item not flagged When the system calculates holdbacks Then the additional $200 is applied only to the Electrical line And when a valid permit number is submitted and verified, the $200 conditional holdback for that line is marked releasable And if no permit is required, the conditional holdback is not applied
Rule Coherence Validation and Error Messaging
Given I configure a total holdback of 90% with a per-line minimum of $200 and a cap of $150 When I attempt to save the rule Then the save is blocked and I see an error stating "Minimum cannot exceed cap" on the min and cap fields And a summary banner lists all conflicts including "Combined holdbacks cannot exceed 100% of any line or total" And fixing the values to min $50 and cap $150 allows the rule to save successfully
Reusable Templates for Holdback Configurations
Given I have an effective rule set on a work order When I save it as a template named "HVAC With Permit Hold 20%" Then the template is available to apply at organization, property, and work-order scopes And applying the template to a new property copies all parameters verbatim without altering existing work orders And updating the template later does not retroactively change previously applied configurations; versions are recorded in the audit trail
Transparent Breakdown for Vendors and Managers
Given a vendor or manager views the work order payout breakdown When immediate and on-proof amounts are displayed per line and in total Then amounts sum exactly to the work order total with currency rounding to two decimals And required proof items for each holdback are listed with status (Pending, Submitted, Approved) And managers see rule details and calculations; vendors see required proofs and amounts without internal template names
Proof Artifact Collection & Validation
"As a vendor, I want a clear checklist to submit photos, tenant sign‑off, and permits per line item so that I know exactly what is needed to trigger my payment."
Description

Enable per‑line‑item definitions of required proof types (completion photos, tenant e‑signature, permit number, invoices). Provide vendor/mobile flows via secure links to upload media, capture timestamps, and enter structured data, plus tenant sign‑off with PIN or SMS verification. Enforce file type/size, require minimum photo count, and perform basic automated checks (completeness, EXIF timestamps, required fields). Route submissions for optional manager review and mark artifacts as accepted or rejected with reasons.

Acceptance Criteria
Per-Line-Item Proof Configuration
Given a work order with multiple line items and manager permissions When the manager configures required proof types for a specific line item (e.g., Completion Photos: min 3; Tenant E‑Signature: required; Permit Number: optional) Then the configuration is saved and scoped only to that line item And unrelated line items retain their own proof requirements And the UI displays the configured requirements per line item to both manager and vendor And the API returns the configured schema for that line item on GET /work-orders/{id}/line-items/{lid}/proof-schema
Vendor Mobile Secure Upload Flow
Given a vendor is assigned to a work order with pending proofs and a secure one-time link is generated When the vendor opens the link on a mobile device within the validity window Then the page loads over HTTPS and requires no login, bound to vendor and work order And the UI lists each line item with its required proofs and current completion status When the vendor uploads photos and enters required fields Then server-side timestamps are captured for each artifact and EXIF metadata is stored when available And on link expiration or after successful submission, the link cannot be reused and returns HTTP 410 with guidance to request a new link
File Type, Size, and Minimum Photo Count Enforcement
Given a line item requires Completion Photos (min 3), file types JPG/PNG, max size 10 MB each When the vendor attempts to upload a non-allowed type (e.g., HEIC, PDF) or a file over 10 MB Then the upload is rejected client- and server-side with a clear error message naming the violation When the vendor attempts to submit with fewer than 3 photos Then the Submit button is disabled and an error indicates the remaining photo count required When the vendor meets all constraints Then submission succeeds with HTTP 201 and the UI shows the artifacts as Submitted
Automated Validation: Completeness and EXIF/Field Checks
Given a line item requires Completion Photos (min 2) and Permit Number (alphanumeric 6–12 chars) When the vendor submits artifacts Then the system validates presence of all required artifact types and minimum counts And validates Permit Number format and rejects if invalid with inline errors And checks photos for EXIF capture date; if missing, flags the artifact as Needs Review with reason "Missing EXIF timestamp" And if all automated checks pass and manager review is disabled, the artifact set is auto-marked Accepted And if any automated check fails, overall status is Rejected (Auto) with specific reasons returned in the response
Tenant Sign-Off via PIN or SMS Verification
Given a tenant is requested to sign off for a specific line item When the tenant chooses SMS verification Then a one-time 6-digit code is sent to the tenant's verified phone and entry of the correct code within 10 minutes records a signed timestamp and signer identity When the tenant enters an incorrect code 3 times Then the session is locked for 5 minutes and an audit entry is recorded When the tenant chooses PIN verification with a pre-shared PIN Then entry of the correct PIN records the sign-off; incorrect PIN shows an error without revealing the PIN length And in all cases, the manager view updates to show Sign-Off: Completed with timestamp and method (SMS or PIN)
Manager Review: Accept/Reject with Reasons
Given manager review is enabled for a work order When a vendor submission arrives Then its status is Pending Review and the manager receives an in-app notification When the manager clicks Accept Then the artifact(s) move to Accepted with reviewer ID, timestamp, and optional note captured When the manager clicks Reject Then a mandatory rejection reason is required, status becomes Rejected (Manual), and the vendor is notified with the reason and a resubmission link And subsequent vendor resubmissions supersede prior rejected artifacts while retaining full history
Audit Trail and Status Visibility
Given any artifact action occurs (upload, submit, auto-validate result, accept, reject, tenant sign-off) When the action is completed Then an immutable audit record is stored including actor type, actor ID, timestamp (UTC), action, target (work order/line item/artifact), previous status, new status, and reason (if any) And managers can export the audit trail for a work order to CSV via UI and GET /work-orders/{id}/audit with a 10,000-row limit and pagination And vendors and managers can see real-time per-line-item artifact status badges (Not Started, In Progress, Submitted, Needs Review, Accepted, Rejected) reflected within 2 seconds of change
Automated Partial Release & Manager Override
"As a property manager, I want funds to release automatically when required proofs are satisfied, with the ability to override in exceptions, so that payments are timely yet controlled."
Description

Implement a rules engine that evaluates proof completion per line item and triggers partial fund releases accordingly, aggregating releases when multiple items complete. Support scheduled releases, proration for percentage holdbacks, and calculation of fees/taxes. Provide manager controls to block, approve, or override releases with mandatory reason codes and optional two‑factor confirmation. Handle edge cases such as expired holds, failed disbursements, or disputed artifacts with safe rollback and requeue logic.

Acceptance Criteria
Per-Line-Item Proof Completion Triggers Partial Release
Given a work order with multiple line items and escrowed amounts per line item And each line item has configured required proof artifacts and validation rules When all mandatory artifacts for a specific line item are uploaded and pass validation Then the rules engine marks that line item as Eligible within 30 seconds And calculates Gross Release = Escrowed Amount for the line item minus applicable fees and taxes And rounds monetary values to 2 decimals using bankers' rounding And creates a pending disbursement for the net amount for that line item only And leaves funds for non-eligible line items in Held status And writes an audit record capturing line_item_id(s), validated_artifacts, rule_version, calculation_inputs/outputs, actor=system, timestamp (ISO 8601)
Scheduled Batch Aggregation of Multiple Releases
Given scheduled releases are enabled (e.g., hourly at :00) and multiple line items for the same vendor become Eligible before the next schedule When the schedule executes Then the system aggregates all Eligible releases per payee and currency into a single disbursement with an itemized breakdown (line_item_id, amount, fees, taxes) And uses a deterministic idempotency key (work_order_id + payee_id + schedule_window) to prevent duplicate disbursements on retries And updates each aggregated line item status to Disbursed on successful processor acknowledgment, or leaves them Pending if the processor is asynchronous And posts one audit record for the batch and child records for each line item And completes the aggregation within the schedule window without skipping eligible items
Percentage Holdback Proration and Accurate Calculations
Given a line item with Subtotal = unit_price × quantity and a configured Holdback% and Tax Rate (optional) And a rule that releases X% of the holdback upon a specific event (e.g., inspection_passed) When the triggering event is satisfied and validated Then the system computes: Tax = round(Subtotal × tax_rate, 2) if configured; Holdback Base = Subtotal + Tax; Holdback Total = round(Holdback Base × Holdback%, 2); Release Amount (gross) = round(Holdback Total × X%, 2); Fees = processor_fee_model(Release Amount) And sets Net Release = Release Amount − Fees And ensures cumulative releases for the line item never exceed Holdback Total And supports currency precision per currency (default 2 decimals) and bankers' rounding And displays the full calculation breakdown to manager and vendor in the release detail view
Manager Block/Approve/Override with Reason Codes and 2FA
Given manager permissions to control releases and an organization setting 2FA_on_override (true/false) When a manager Blocks a release Then the UI enforces selection of a reason code from a configured list or "Other" with a minimum 10-character free-text explanation And the release becomes Ineligible until Unblocked And an audit entry records action=Block, actor, reason_code, comment, previous_state, new_state, timestamp When a manager Approves a pending release Then status transitions to Approved within 30 seconds and is eligible for next disbursement cycle And an audit entry records action=Approve with details When a manager Overrides missing/failed artifacts to force eligibility Then 2FA is required if 2FA_on_override=true (TOTP/SMS/email supported); failure cancels the action; success proceeds And the audit entry records action=Override, 2FA method, reason_code, rule_bypass=true, and impacted artifact list
Failed Disbursement Rollback and Requeue
Given a pending or in-flight disbursement submitted to the payment processor When the processor returns a failure or a timeout is detected Then the system reverts the ledger state for the affected releases to Held without double-counting or losing funds And records the failure_code, failure_reason, and processor_trace_id And schedules automatic retries using exponential backoff with jitter up to 5 attempts by default And sets status to Disbursement Failed after max attempts and notifies the manager; if failure is due to payee details, the vendor is prompted to update payout info And enforces idempotency so that successful retries cannot create duplicate payouts And maintains an immutable audit trail of each attempt and outcome
Expired Holds and Disputed Artifact Handling
Given a hold has an expiration date and/or a submitted artifact can be disputed by tenant/manager When the hold expiration is reached with required artifacts still missing Then the system does not auto-release by default; it sets status to Hold Expired - Review and notifies the manager When an artifact is disputed before settlement Then any pending release for the affected line items is paused (state=On Hold - Dispute), no disbursement is sent, and the dispute must be resolved via manager action (Approve/Reject) When a dispute occurs after a partial release Then only the remaining holdback is frozen and schedules are recalculated; already-settled amounts are not clawed back automatically And all state changes and reasons are captured in the audit log
Real‑Time Release Status for Managers & Vendors
"As a vendor, I want a real‑time view of what’s still needed and what’s been released so that I can plan my crew and cash flow."
Description

Offer dashboards and in‑context views that show escrow totals, held amounts, pending releases, and completed disbursements at the work order and line‑item level. Provide progress indicators, timelines of events (holds, submissions, approvals, releases), filters by property/vendor/date, and mobile‑friendly views. Respect role‑based access to financial details and ensure time zone and currency are displayed consistently. Support quick export of the current view for sharing.

Acceptance Criteria
Manager Dashboard Aggregates and Line-Item Detail
Given I am a Manager user with access to Property X and Vendor Y And there exist work orders with escrowed funds and line-item holdbacks within the selected filters When I open the Release Status dashboard and filter by Property X and Vendor Y Then I see four rollup metrics for the result set: Escrowed Total, Held Amount, Pending Release, Completed Disbursements And each metric equals the sum of the underlying records in the visible table And each work order row displays per-line-item held amount, released amount, and holdback type (fixed amount or percentage) And clicking a work order row reveals an in-context panel with line-item level release statuses And at viewport width 375px, the four rollup metrics and the work order totals are visible without horizontal scrolling
Vendor Work Order Release Status and Checklist
Given I am a Vendor user assigned to Work Order WO-123 with an escrow holdback When I open the work order details view Then I see the held amount, pending release amount, and released amount for this work order And I see a progress indicator showing N of M required proof items submitted And I see a checklist enumerating required proof items with status (Missing, Submitted, Approved) And required items include completion photos, tenant sign-off, and permit number when configured And at viewport width 375px, the progress indicator and amounts are visible above the fold
Event Timeline Completeness and Audit Trail
Given a work order with escrow events When I view the Timeline tab for the work order Then I see a chronological list of events including: funds escrowed, holdbacks set/updated, proof submissions, approvals, rejections, and release disbursements And each event shows actor, timestamp with account time zone abbreviation, affected line items, and amounts And the timeline can be filtered by event type and line item And event ordering is strictly by event timestamp descending; equal timestamps are secondarily ordered by event ID
Real-Time Update SLA
Given I am viewing the Release Status dashboard or a work order page When a proof item is submitted, approved, or a release is executed for a record in my current view Then the affected row’s status, amounts, progress indicator, and timeline update without full page reload within 10 seconds And the rollup metrics recompute within 10 seconds to reflect the change And a Last updated timestamp reflects the refresh moment in the account time zone
Filters by Property, Vendor, and Date Range
Given I have access to multiple properties and vendors When I apply filters: Property = X, Vendor = Y, Date Range = Last 30 Days Then only matching work orders and line items are shown and all rollup metrics reflect the filter And the filter state persists in the URL and is restored on page reload and when exporting And date filtering uses the account time zone for start/end boundaries And clearing filters resets the view to all accessible records
Role-Based Access to Financial Details
Given I am a Manager user When I view any property in my portfolio Then I can see all financial metrics and line-item details for those properties Given I am a Vendor user When I attempt to view properties or work orders not assigned to my organization Then I am prevented with a 403 in API calls and the UI hides totals and details for unauthorized records And exports for Vendor role exclude other vendors’ data and redact fields the role is not permitted to view
Quick Export of Current View
Given I have a filtered dashboard view with up to 10,000 rows When I click Export Then a CSV download starts within 2 seconds and completes within 10 seconds And the file contains only the visible columns and rows in my current view, in the current sort order And monetary values include the ISO currency code and timestamps include the account time zone abbreviation And the first row contains an export header with the applied filters and generation timestamp And for Vendor role, the export excludes unauthorized financial fields
Notifications & Reminders for Missing Proof
"As a coordinator, I want automated reminders for missing items so that I spend less time chasing vendors and tenants and releases happen faster."
Description

Send automated, configurable reminders via SMS and email to vendors and tenants for missing artifacts, upcoming deadlines, and release confirmations. Provide message templates, throttling, escalation to managers after inactivity, and localized content. Include deep links that take users directly to the required action and update the notification schedule based on new submissions or approvals. Log all communications for auditability.

Acceptance Criteria
Missing Proof Reminders Delivery (SMS & Email)
Given a work order with ProofHold escrow active and at least one required proof item missing for a recipient (vendor or tenant) with enabled and verified contact channels When the reminder scheduler runs at the configured cadence for that work order Then the system sends a reminder via each enabled channel (SMS and/or Email) using the selected template populated with {work_order_id}, {property_address}, {missing_items}, {due_date}, and {deep_link} And the reminder lists only the currently missing items and the current due date And the notification is addressed to the correct recipient role (vendor or tenant) with role-appropriate template variant
Notification Throttling and De-duplication
Given multiple reminder triggers for the same recipient and work order within the throttle window When composing outbound notifications Then the system sends no more than the configured maximum per channel (default: 1 SMS and 1 Email per 24 hours per recipient per work order) And identical reminders (no change in missing items or due date) within the throttle window are suppressed And multiple missing items are consolidated into a single message per channel per send event
Escalation to Manager After Inactivity
Given a work order with missing proof items and at least one reminder has been sent And no proof submissions or approvals have occurred for the configured inactivity period (e.g., 48 hours) When the inactivity threshold is reached Then the system sends an escalation notification to the assigned manager(s) via Email (and SMS if enabled) including work order ID, recipient, aging since last activity, and remaining required items And escalation stops or resets if any qualifying activity (submission or approval) occurs And the number of escalations per work order respects the configured maximum (default: 3)
Localized Content and Timezone-Aware Send Windows
Given a recipient with a stored locale and timezone When generating and scheduling a notification Then the message content uses the locale-specific template and formatting for dates/times and numbers And if a translation is missing, the default locale template is used with a clear fallback And send times respect the configured quiet hours for the recipient's timezone (default: do not send between 20:00–08:00) And due dates shown reflect the recipient's timezone
Deep Links Direct to Required Action with Security
Given a notification that includes a deep link to complete a required proof action When the recipient opens the link Then they are routed directly to the specific work order and the exact proof item upload or sign-off screen And if not authenticated, they are prompted to authenticate and then returned to the target screen And the link includes a signed, time-limited token (default expiry: 7 days) tied to the recipient and work order And expired or revoked links present an informative message and offer a one-click resend request if allowed
Dynamic Rescheduling on Submissions and Approvals
Given pending reminders for a work order with missing proof items When a proof item is submitted, approved, or rejected Then the system recalculates the reminder schedule and content to reflect only the remaining missing items And cancels pending reminders for items that are satisfied or no longer required And when all required proof items are approved and escrow is released, the system sends a release confirmation to the vendor (and tenant if configured) and cancels all remaining reminders
Comprehensive Communication Audit Log
Given any notification event (queued, sent, delivered, failed, bounced, clicked, unsubscribed) When the event occurs Then the system records an audit entry with timestamp, work order ID, recipient ID and role, channel, template identifier and version, key personalization fields used, provider message ID, and event outcome And audit entries are immutable and filterable by work order, recipient, channel, and date range And deep link click events are associated back to the originating notification

Instant PushPay

Offer vendors real-time debit push or same-day ACH the moment a job is marked complete and checks pass. Transparent fees, payout speed options, and instant receipts build trust and reduce inbound "payment status" calls. Small operators keep vendors loyal with quick cash flow; larger teams hit SLA goals without manual payout runs.

Requirements

Payout Method Selector & Transparent Fees
"As an operator, I want to choose a payout method with clear fees and delivery times so that I can balance cost and speed while keeping vendors informed."
Description

Provide a checkout-style UI and API to select payout rails (instant debit push vs same-day ACH) at the moment a work order is marked complete, with real-time fee and arrival-time disclosure before confirmation. The selector defaults based on vendor preference and job size, supports operator overrides, and clearly displays gross, fees, and net amounts. Integrates with FixFlow’s job detail view and webhook-driven payment status updates to reflect "Pending", "Processing", "Sent", or "Failed" on the work order timeline. Enforces cut-off times for same-day ACH, disables unavailable rails per vendor eligibility, and logs the operator’s choice for audit and SLA reporting.

Acceptance Criteria
Default Selection Honors Vendor Preference and Job Amount Rules
Given a vendor with a saved payout preference and a configured defaulting rule that considers job amount When the operator marks a work order as complete and the payout selector opens Then the payout method is preselected according to the vendor’s preference and job amount rule (e.g., instant if within configured instant limit; otherwise ACH if eligible) And a visible hint explains why it was defaulted (e.g., "Defaulted by vendor preference" or "Defaulted by job amount rule") And the operator can override by selecting a different available method And the fee/net summary updates immediately to match the newly selected method And the system records whether the final selection was defaulted or overridden
Real-Time Fee and Arrival-Time Disclosure Prior to Confirmation
Given both instant debit push and same-day ACH are available for a vendor When the operator toggles between methods in the selector Then the UI displays gross amount, itemized fees (with method-specific labels), currency, estimated arrival date/time, and net amount And the values update within 1 second of selection and reflect current pricing and configuration And amounts are rounded to two decimal places and shown consistently in the tenant/property currency And the Confirm action remains disabled until the fee and arrival summary is visible When the operator confirms the payout Then the confirmation step shows the same breakdown and selected method prior to submission
Same-Day ACH Cut-Off Enforcement and Messaging
Given a configurable same-day ACH cut-off time and a property/vendor timezone When the selector is opened after the cut-off for that day Then the Same-Day ACH option is disabled for that calendar day And an inline message explains the reason (includes the words "cut-off") and shows the next estimated arrival (e.g., next business day) When the selector is opened before the cut-off Then the Same-Day ACH option is enabled and shows an estimated arrival of the same day And the estimated arrival timestamp is calculated using the configured timezone
Rail Availability Respects Vendor Eligibility
Given a vendor who is not eligible for instant debit push When the selector is opened Then the Instant Debit Push option is disabled with a reason tooltip or subtext (e.g., "Vendor not enabled for instant payouts") And attempting to confirm instant via API returns HTTP 422 with error code rail_not_eligible Given a vendor who is not eligible for same-day ACH When the selector is opened Then the Same-Day ACH option is disabled with a reason message And attempting to confirm same-day ACH via API returns HTTP 422 with error code rail_not_eligible
API Support for Quote, Selection, Confirmation, and Idempotency
Given a public API for payout selection When the client requests a payout quote with method, vendor, and amount Then the API returns method, gross, fee, currency, net, estimated_arrival, eligibility flags, and a quote_id And the quote expires after a configurable TTL (e.g., 60 seconds) and the API rejects confirmation with HTTP 409 if the quote is expired When the client confirms a payout with quote_id and an Idempotency-Key header Then the API creates the payout, returns 201 with payout_id and initial status Pending And a repeat request with the same Idempotency-Key returns the same payout_id without creating a duplicate And invalid parameters return 400 with machine-readable error codes
Webhook Statuses Drive Timeline Updates
Given webhook events will be sent for payout status changes (Pending, Processing, Sent, Failed) When a webhook is received for a work order payout Then the work order timeline displays a new entry with the status, timestamp, and method And the UI updates within 5 seconds of webhook receipt And a Failed status includes a visible failure reason and a unique error code And status transitions are ordered chronologically; duplicate webhooks do not create duplicate entries
Audit Trail Captures Operator Choice and Context
Given the operator views and confirms a payout method in the job detail view When the payout is submitted Then the system records an immutable audit log entry including operator ID, timestamp, selected rail, gross, fees, net, displayed estimated arrival, whether the selection was defaulted or overridden, and any cut-off or eligibility flags And the audit entry is linked to the work order and payout_id and is available to SLA reporting queries
Vendor Onboarding: KYC, W-9, and Bank Verification
"As a vendor, I want to quickly verify my identity and link my bank or debit card so that I can receive instant payments securely without back-and-forth."
Description

Embed a vendor onboarding flow that collects W-9 details, performs KYC/KYB checks, and verifies payout destinations through tokenized bank account linking (e.g., open banking) or debit card verification. Store verification status on the vendor profile, restrict payouts until verified, and auto-expire or re-verify when regulatory thresholds are hit. Mask sensitive data, maintain least-privilege access, and rely on the payment processor for PCI-sensitive storage. Provide self-serve updates for vendors and admin overrides with audit trails. Surfaces eligibility flags to the payout UI so only compliant vendors are selectable for Instant PushPay.

Acceptance Criteria
W-9 Collection and Validation During Vendor Onboarding
- Given a vendor starts onboarding, When they reach the Tax Information step, Then they must provide legal name, tax classification, TIN (SSN or EIN), and mailing address before proceeding. - Given the vendor submits a TIN, When client-side and server-side validation run, Then SSN/EIN must match accepted US formats and fail with actionable errors if invalid. - Given the vendor submits the form, When IRS/TIN match service responds, Then the match status (Match/No Match/Pending) is stored and exposed on the vendor profile. - Given a W-9 is collected, When the record is saved, Then a versioned W-9 artifact is created with timestamp and only the TIN last4 is available for non-Compliance viewing. - Given the W-9 status is No Match, When the vendor attempts to continue onboarding, Then they are blocked with guidance to correct data and payouts remain disabled.
KYC/KYB Identity Verification and Status Management
- Given a vendor selects Individual or Business, When KYC (individual) or KYB (business) is initiated via the provider, Then status values are tracked as Not Started, Pending, Verified, Manual Review, or Failed with reason codes. - Given KYC/KYB completes, When results return, Then the vendor profile displays component statuses (W-9, KYC/KYB, Payout Method) and a composite Verification Status that is Verified only if all components are Verified. - Given KYC/KYB is Failed or Manual Review, When the vendor tries to access payout features, Then payouts are blocked and the UI shows the specific reason/status. - Given any verification status changes, When the system updates the record, Then the change is timestamped, attributed (system/user), and appended to the immutable audit log.
Tokenized Bank Account/Debit Card Linking and Verification
- Given a vendor chooses Bank Account, When they link via the hosted open-banking widget from the processor, Then our system stores only the processor token, bank name, account type, and last4; no raw account/routing numbers are stored in our DB or logs. - Given a vendor chooses Debit Card, When tokenization completes via the processor’s hosted form, Then our system stores only the network token, brand, last4, and expiration; no PAN is stored in our DB or logs. - Given a payout destination is linked, When the processor returns verification results, Then payout method status must be Verified before enabling payouts; otherwise the UI shows the failure reason and payouts remain disabled. - Given telemetry and logs are produced, When redaction rules run, Then sensitive fields are always redacted; automated tests detect any unredacted PAN/account data.
Payout Eligibility Gating in Instant PushPay UI and API
- Given a job is marked complete, When the assigned vendor’s composite Verification Status is not Verified, Then the Instant PushPay button is disabled with a tooltip explaining the unmet requirements and a link to resolve. - Given a user opens the payout vendor selector, When vendors are listed, Then only Verified vendors are selectable; non-Verified vendors appear disabled with their blocking reason. - Given a client calls the payouts API for an unverified vendor, When the request is processed, Then the API returns HTTP 403 with code VENDOR_NOT_VERIFIED and no payout is created. - Given a vendor transitions to Verified, When the payout UI is refreshed or receives live updates, Then the vendor becomes selectable and the Instant PushPay action is enabled.
Auto Re-Verification on Regulatory Thresholds and Changes
- Given cumulative payouts to a vendor reach $600 in the current tax year and no W-9 is on file, When another payout is attempted, Then the system blocks the payout until a W-9 is collected and matched. - Given 12 months have elapsed since the last KYC/KYB verification or any verification document is expired, When the vendor next becomes eligible for payment, Then the status changes to Re-Verification Required and payouts are restricted. - Given cumulative payouts exceed $10,000 in a rolling 12-month window, When thresholds are evaluated nightly, Then enhanced verification is triggered and eligibility is set to Pending until completion. - Given a vendor changes legal name, tax classification, or payout destination, When the change is saved, Then re-verification is triggered automatically and payouts are blocked until Verified.
Data Masking and Least-Privilege Access Controls
- Given a non-Compliance user views a vendor profile, When sensitive data is displayed, Then TIN and bank details are masked to last4 and full values are not retrievable. - Given a Compliance Admin needs full TIN, When they click Reveal, Then MFA re-auth and justification are required, access is time-limited, and the event (who, when, why) is logged. - Given database snapshots and application logs, When nightly DLP scans run, Then no full PAN/account numbers or unmasked TINs are present; failures alert Security and block deployments. - Given RBAC is configured, When permissions are evaluated, Then only users with vendor.compliance.read can view verification statuses and only users with vendor.compliance.override can approve overrides.
Self-Serve Updates and Admin Overrides with Audit Trails
- Given a vendor updates W-9 or payout method via self-serve, When the change is submitted, Then a new versioned record is created, verification status moves to Pending, and the vendor receives a confirmation notice. - Given Compliance performs an override to set a vendor to Verified, When saving the override, Then reason, approver identity, and evidence attachments are mandatory and captured in the audit trail. - Given an override is in effect, When an Instant PushPay payout is created, Then the payout record stores the override ID and approver in its audit metadata. - Given any change to compliance-related data, When the record is updated, Then an immutable audit entry is written including actor, timestamp, before/after values, and source (UI/API).
Payout Eligibility Gate & Compliance Rules
"As a property manager, I want payouts to trigger only after all completion and compliance checks pass so that we avoid paying for incomplete or noncompliant work."
Description

Implement a rules engine that authorizes payouts only when completion criteria and compliance checks are met: work order marked complete with required evidence (photos, notes), invoice attached and approved, budget availability confirmed, duplicate payment prevention, vendor verification status green, and optional lien waiver acknowledgment. Rules are configurable by portfolio and can hold, auto-approve, or require secondary approval based on amount thresholds and vendor risk. The gate provides deterministic pass/fail reasons to the UI and APIs, enabling operators to resolve blockers without support.

Acceptance Criteria
Pass Gate: Complete Work Order with Evidence
Given portfolio rules require at least 3 completion photos and a completion note And a work order is marked Complete with 3 or more photos and a completion note And an invoice is attached and marked Approved And the invoice total is within the portfolio auto-approve threshold And the vendor verification status is Green and risk score is within portfolio limits And budget availability for the associated property/GL is sufficient And no existing payout exists for the same work_order_id + invoice_number + invoice_total + vendor_id And lien waiver is not required or is already acknowledged/attached per portfolio rules When the Payout Eligibility Gate evaluates the request Then the decision is Eligible with action Auto-Approve And the API/UI response includes reason_codes ["EVIDENCE_OK","INVOICE_APPROVED","THRESHOLD_OK","VENDOR_VERIFIED","BUDGET_OK","NO_DUPLICATE","LIEN_WAIVER_OK"] And an immutable audit record is written with decision_id and rule versions applied
Fail Gate: Missing Required Evidence
Given portfolio rules require at least 3 completion photos and a completion note And the work order is marked Complete with fewer than 3 photos or the completion note is missing When the Payout Eligibility Gate evaluates the request Then the decision is Ineligible with action Block And reason_codes include "MISSING_EVIDENCE" and missing_fields (e.g., ["photos","completion_note"]) as applicable And no payout is initiated and no funds are reserved And the UI presents a fix-it checklist derived from reason_codes And an immutable audit record is written with decision_id and the failing checks
Secondary Approval on Amount Threshold Exceeded
Given the portfolio auto-approve threshold is $1,000 And the attached approved invoice total is $1,250 And a user role authorized for secondary approval (e.g., Finance Manager) exists When the Payout Eligibility Gate evaluates the request Then the decision is On Hold with action Require Secondary Approval And reason_codes include "THRESHOLD_APPROVAL_REQUIRED" with threshold_value 1000 and invoice_total 1250 And after a Finance Manager records secondary approval and the gate re-evaluates with no other blockers, the decision becomes Eligible with action Release Payout And the audit log contains both the initial hold decision and the subsequent approval decision with user, timestamp, and rule versions
Vendor Verification and Risk-Based Hold
Given portfolio rules require vendor verification status Green and risk score <= configured threshold And the vendor is Unverified or has status Yellow/Red or risk score above threshold When the Payout Eligibility Gate evaluates the request Then the decision is On Hold with action Vendor Compliance Review And reason_codes include "VENDOR_NOT_VERIFIED" and/or "VENDOR_RISK_HOLD" with current verification status and risk score And if the vendor status becomes Green and risk score within threshold and the gate re-evaluates with no other blockers, the decision becomes Eligible And all evaluations are captured in the audit log with before/after vendor compliance attributes
Duplicate Payment Prevention
Given a payout exists in Processing or Paid state for the same tuple (work_order_id, invoice_number, invoice_total, vendor_id) When a new payout request for the same tuple is evaluated by the Payout Eligibility Gate Then the decision is Ineligible with action Block And reason_codes include "DUPLICATE_PAYMENT_DETECTED" with reference_payout_id And the API response is idempotent for repeated evaluations, returning the same decision and reference_payout_id And the audit log records the duplicate detection with hash/lookup keys used
Budget Availability and Reservation
Given budget enforcement is enabled for the portfolio and a cost center/GL is associated to the work order And the remaining budget is less than the invoice_total When the Payout Eligibility Gate evaluates the request Then the decision aligns to portfolio config over_budget_behavior (Hold or Ineligible) with action Require Budget Adjustment if Hold And reason_codes include "BUDGET_EXCEEDED" with remaining_budget and invoice_total And if remaining budget is sufficient, the decision is Eligible and the system creates a funds reservation with reservation_id to prevent race conditions until payout is executed or the decision expires And all reservation create/release events are written to the audit log
Lien Waiver Acknowledgment Requirement
Given the portfolio requires lien waiver acknowledgment for invoices at or above a configured amount And the invoice_total meets the threshold and no lien waiver acknowledgment or attachment exists When the Payout Eligibility Gate evaluates the request Then the decision is On Hold with action Collect Lien Waiver And reason_codes include "LIEN_WAIVER_REQUIRED" with threshold and current status And once the vendor acknowledges or uploads a signed lien waiver and the gate re-evaluates with no other blockers, the decision becomes Eligible And the audit log records the waiver requirement, upload/acknowledgment, and final decision
Real-time Disbursement Orchestrator (Debit Push & Same-day ACH)
"As an operator, I want payments sent reliably in real time with smart fallbacks so that vendors get paid fast even when a preferred rail is unavailable."
Description

Create a payment orchestration service that submits payouts to supported rails (debit push/card, RTP if available, and same-day ACH), manages idempotency, retries, and fallbacks, and updates state via processor webhooks. Implement a payout state machine (Created → Authorized → Submitted → Settled/Failed/Returned) with durable persistence and exactly-once semantics. Respect ACH windows and display ETA, automatically downgrade to next-best rail when instant rails are unavailable, and surface actionable failure reasons (e.g., account closed, name mismatch). Capture fees per rail, reconcile net amounts, and expose REST/GraphQL endpoints for internal UI and automation.

Acceptance Criteria
Instant Debit Push Success Path
Given a job is marked complete and payout eligibility checks pass When a payout request is created with railPreference = "instant_debit" Then a payout record is persisted with state = Created, unique payoutId, and idempotencyKey And the orchestrator authorizes the amount and transitions state to Authorized And the payout is submitted to the debit push rail and state transitions to Submitted within 2 seconds And upon processor confirmation, state transitions to Settled within 60 seconds with settlementTimestamp recorded And the vendor receives an instant receipt via SMS/email containing payoutId, rail, gross amount, fee, net, and trace/reference id And the API response returns 200 with payoutId, current state, rail, fee, net, and ETA And no duplicate submissions are made to the debit rail for this payoutId
Automatic Fallback to Next-Best Rail
Given a payout is requested with railPreference = "instant" and the vendor has multiple supported rails configured When the preferred instant rail is unavailable (e.g., rail outage, instrument not supported, risk block, or timeout > 5 seconds) Then the orchestrator selects the next-best available rail in priority order: RTP (if enabled) else Same-day ACH And a downgradeReasonCode is recorded and exposed via API/events And exactly one rail submission occurs for the payout (no parallel or duplicate submissions) And the ETA and fee reflect the selected fallback rail and are returned to the client and emitted to the UI And the payout state transitions follow the selected rail’s lifecycle without gaps
Idempotency and Exactly-Once Submission
Given clients send a Create Payout request with Idempotency-Key header When duplicate requests with the same Idempotency-Key are received within 24 hours or before the payout reaches a terminal state Then the service returns the original payoutId and current state without creating a new payout record And no additional submissions to any rail are performed for the same payoutId And concurrent duplicate requests (>=3 concurrent) across multiple nodes still result in exactly one payout record and one rail submission And the response includes idempotencyKeyMatched = true
Processor Webhooks: Valid State Transitions and Deduplication
Given the payment processor sends webhooks for authorization, submission, settlement, failure, or return events with signatures When a webhook is received Then the signature is verified and the event is deduplicated using the processor event id or hash And only valid transitions are applied: Created→Authorized→Submitted→(Settled|Failed|Returned), Submitted→(Settled|Failed|Returned) And out-of-order events are buffered or ignored without regressing state; no invalid transitions are persisted And webhook handling is idempotent; re-delivered events do not change state after first successful application And the raw webhook payload, timestamps, and transition audit are durably persisted before responding 200 to the processor
ACH Cutoff Windows and ETA Display
Given a payout must be sent via Same-day ACH When the request is received before the defined cutoff (e.g., 2:45 PM US/Eastern) on a business day Then the payout is scheduled for the same-day window and ETA displays Today by the appropriate settlement time with timezone When the request is after cutoff or on weekends/holidays Then the payout is scheduled for the next available window and ETA reflects the correct next-business-day settlement per NACHA calendar And any change in ETA due to holidays or missed windows updates the persisted ETA and emits an event to the UI/API
Actionable Failure Reasons and Returns Handling
Given a payout transitions to Failed or Returned at the processor with a machine code (e.g., R01, R03, R20, ACCOUNT_CLOSED, NAME_MISMATCH) When the webhook is processed Then the orchestrator maps the processor code to a standardized reasonCode and userMessage And the payout enters a terminal state (Failed or Returned) with reasonCode, userMessage, and processorRef recorded And retry eligibility is determined based on code (e.g., soft vs hard) and exposed via API as retryAllowed plus suggestedNextRail when applicable And a notification event is emitted for UI/ops including reasonCode and remediation guidance
Fee Capture, Net Reconciliation, and API Exposure
Given each rail has a configured fee schedule and payer When a payout is submitted and later reaches a terminal state Then the feeAmount, feePayer, feeRate/effectiveFee, and rail are recorded and netAmount = gross - feeAmount is persisted And the sum of net settlements per day matches processor reports (tolerance = 0); discrepancies create reconciliationException records And REST and GraphQL endpoints expose payout fields [payoutId, state, rail, gross, fee, net, ETA, reasonCode, createdAt, updatedAt] And an endpoint provides a downloadable receipt (PDF or JSON) containing trace/reference ids and fee breakdown And all endpoints enforce authentication and role-based access; unauthorized access returns 401/403
Instant Receipts & Multi-channel Notifications
"As a vendor, I want instant receipts and status updates via SMS/email so that I have proof of payment and don’t need to contact support."
Description

Generate itemized receipts at payout time, including job ID, invoice reference, gross, fees, net, rail used, destination last4, and expected arrival. Send notifications via SMS and email to vendors and in-app alerts to operators; provide a receipt link accessible from the job timeline and vendor portal. Include delivery status tracking, automatic resend on failures, and vendor preferences for channels. Reduce support load by embedding a self-serve "Where is my payment?" tracker with real-time status pulled from the disbursement engine.

Acceptance Criteria
Receipt Generation and Content Completeness
Given a job is marked complete and payout risk/compliance checks pass When the disbursement engine confirms payout initiation Then the system generates a receipt exactly once for that payout And the receipt includes: jobId, invoiceReference, grossAmount, feesAmount, netAmount, paymentRail, destinationLast4, expectedArrivalDateTime And monetary amounts are currency-formatted to 2 decimals using the job’s currency And expectedArrivalDateTime reflects the selected rail’s SLA and includes timezone And if payout checks fail or payout is not initiated, no receipt is generated and an operator warning is logged
Multi-Channel Vendor Notifications Trigger
Given a vendor has verified SMS and email contacts And a payout receipt is generated When notification dispatch is triggered Then send an SMS with payout summary (netAmount, rail, expectedArrivalDateTime) and a secure receipt link within 5 seconds p95 And send an email with itemized details and the secure receipt link within 60 seconds p95 And record send events with timestamps and a correlationId linking to the payout And do not dispatch notifications if vendor has opted out of all channels
Operator In-App Alerts
Given an operator assigned to the property has notification permissions When a payout receipt is generated Then create an in-app alert visible in the job timeline and notifications center And the alert shows vendor name, netAmount, rail, expectedArrivalDateTime, and a receipt link And the alert remains unread until viewed or dismissed and is audit-logged with timestamp and actor
Delivery Tracking and Auto-Resend
Given SMS and email notifications were dispatched When delivery providers return status updates Then store per-channel status (queued, sent, delivered, failed, bounced) with timestamps And surface the latest status in the job timeline (operator) and vendor portal (vendor) And if status is failed or bounced, retry up to 3 times over 15 minutes with exponential backoff And after final failure, mark as Failed, notify the operator in-app, and attempt an alternate opted-in channel if available
Vendor Channel Preferences and Opt-Outs
Given vendor notification preferences exist for Payment Receipts When a payout receipt is generated Then send notifications only to channels the vendor has opted into And suppress notifications for channels the vendor has opted out of or that are unverified And if no channels are opted in, display the receipt in the vendor portal and notify the operator of suppressed delivery And changes to preferences are audit-logged with user, timestamp, and previous/new values
Receipt Link Availability and Access Control
Given a receipt has been generated When viewing the job timeline as an operator with permissions Then a Payment Receipt link is visible and opens the receipt When viewing the vendor portal as the payout recipient Then the receipt is listed under Payments with a view link And direct links use signed tokens that expire in 7 days, enforce least-privilege access, and reveal only destinationLast4 for payout details And requests with invalid/expired tokens are denied and logged
Self-Serve Where Is My Payment Tracker
Given a vendor opens the Where is my payment? tracker for a payout When the system queries the disbursement engine in real time Then display status (Initiated, In Transit, Settled, Failed), lastUpdated timestamp, rail, destinationLast4, netAmount, and expectedArrivalDateTime And if status is Delayed or Failed, present reason if available and next steps And provide a Resend notification action that respects channel preferences and logs the attempt And tracker response time is under 2 seconds p95
Payout Ledger, Reconciliation, and Exports
"As a bookkeeper, I want a clear payout ledger with reconciliations and exports so that our accounting stays accurate and month-end close is fast."
Description

Maintain a double-entry ledger of payouts, fees, and adjustments tied to vendors and work orders. Provide daily reconciliation against processor reports, handle ACH returns and reversals with automatic status updates and recovery workflows, and expose filters for date, vendor, property, and rail. Support CSV exports and integrations with accounting systems (e.g., QuickBooks Online) with mapping for chart of accounts and classes. Ensure every payout has a unique reference, audit trail, and attachment links to invoices and receipts for month-end close and audits.

Acceptance Criteria
Double-Entry Ledger Posting on Payout Completion
Given a work order is marked complete and payout checks pass When Instant PushPay triggers a payout Then the system records a balanced double-entry in the ledger with separate lines for gross payout, fees, and adjustments (sum of debits equals sum of credits) And a unique payout reference is generated and stored on all related ledger lines And the ledger entry is linked to the vendor, work order, property, and payment rail and speed And attachments include the vendor invoice (if provided) and an auto-generated payout receipt And posting is idempotent: retries using the same payout reference do not create duplicate ledger entries
Daily Processor Reconciliation and Exceptions
Given processor settlement reports for date D are available When the system ingests reconciliation files by 06:00 local time Then each report transaction is matched to a ledger payout by unique reference and exact amount And unmatched or mismatched items are placed in an Exceptions queue with a categorized reason (Missing in Ledger, Missing in Report, Amount Mismatch, Duplicate) And a daily reconciliation report displays matched count, exception count, and variance totals that net to $0 And the reconciliation job completes within 5 minutes for up to 50,000 transactions And all reconciliation actions are logged in the audit trail
ACH Returns Handling and Automatic Reversals
Given an ACH return file (e.g., R01, R03, R29) is received from the processor When the file is ingested Then the associated payout status updates to Returned And equal-and-opposite reversal ledger entries are posted with a link to the original payout reference And fees associated with the return are posted as separate ledger lines, if present And a recovery workflow item is created with options to update payment details and reattempt And vendor and work order stakeholders receive a status notification And duplicate ingestion of the same return file or entry does not create multiple reversals
Ledger Filtering by Date, Vendor, Property, and Rail
Given the ledger contains at least 10,000 entries When filters for date range, vendor, property, and payment rail are applied in any combination Then the resulting list includes only entries satisfying all selected filters (logical AND) And aggregate totals (gross, fees, net) reflect only the filtered entries And applying or clearing filters returns results within 2 seconds for up to 10,000 entries And filter state persists when navigating between ledger and detail views during the session
CSV Export of Filtered Ledger
Given a user has applied ledger filters and has export permission When Export CSV is requested Then a UTF-8 CSV with a header row is generated containing: Payout Reference, Work Order ID, Vendor ID, Vendor Name, Property, Payment Rail, Speed, Posting Date, Created Date-Time (ISO 8601), Status, Gross Amount, Fees, Adjustments, Net Amount, Currency And numeric fields use two decimal places and a dot as decimal separator And the export includes only the currently filtered rows, up to 100,000 rows And file generation completes within 30 seconds for 100,000 rows And exported totals for gross, fees, and net match the on-screen totals for the same filter
QuickBooks Online Sync with Accounts and Classes Mapping
Given QuickBooks Online is connected and mapping for chart of accounts and classes is configured When a sync is initiated Then a journal entry (or vendor bill/payment, per configuration) is created in QBO for each eligible ledger payout with mapped accounts and classes And vendors are matched by configured mapping (external ID or name) without creating duplicates And duplicate prevention uses the payout reference to ensure idempotency across sync runs And sync failures due to missing mappings or QBO validation errors are reported with actionable messages and do not block other records And a sync log shows per-record status (Created, Updated, Skipped, Failed) and totals And subsequent changes in FixFlow post a correcting entry on the next sync rather than mutating posted entries
Unique Reference, Audit Trail, and Attachment Integrity
Given payouts are created through any rail or speed When a payout is posted Then a system-wide unique reference (max 32 characters, alphanumeric with dashes) is assigned and searchable And all state changes (Created, Posted, Reconciled, Returned, Reversed, Exported, Synced) are captured in an immutable audit trail including actor, timestamp, and changed fields And invoice and payout receipt attachments are stored, linked to the payout, and retrievable from the payout detail view And attempting to create a duplicate reference is rejected with a clear error And audit and attachment records are visible to users with appropriate permissions
Role-based Approvals, Limits, and Audit Trail
"As an admin, I want role-based controls and approval limits so that we manage payout risk and meet compliance without slowing routine payments."
Description

Introduce granular permissions for initiating payouts, selecting rails, and approving amounts above thresholds. Support two-person approval for high-value payments, per-portfolio and per-user limits, and temporary holds on vendors. Record an immutable audit log capturing who approved what, when, and why, including rule overrides with justification. Surface SLA dashboards for payout times and approval bottlenecks to help teams meet internal targets and vendor expectations.

Acceptance Criteria
Initiator Permission Enforcement for Payout Creation
Given a user without "Initiate Payouts" permission, when they attempt to create a payout via UI or API, then the system responds 403 and shows "You do not have permission to initiate payouts" and no payout record is created. Given a user with "Initiate Payouts" permission scoped to Portfolio A, when they create a payout in Portfolio A, then a payout record is created in Pending Approval status and assigned to Portfolio A. Given a user with "Initiate Payouts" permission scoped to Portfolio A, when they attempt to create a payout in Portfolio B, then the action is blocked with 403 and the attempt is written to the audit log with user ID and timestamp. Given any permission check, when evaluated, then the decision is enforced server-side and the allow/deny outcome is recorded in the audit log.
Rail Selection Restricted by Role
Given a role configured with allowed rails = {ACH}, when a user with that role opens the payout form, then Instant Debit Push options are hidden or disabled. Given the same user submits an API request with rail = debit_push, when processed, then the request is rejected with 400 "rail_not_permitted" and no payout is created. Given a user with allowed rails = {ACH, debit_push}, when they select a rail, then the fee and expected payout speed are displayed and the selected rail is persisted on the payout record. Given any payout is approved, when executed, then the selected rail is included in the audit log with the approver ID.
Two-Person Approval Workflow for High-Value Payouts
Given a portfolio HighValueThreshold = $X, when a payout amount >= $X is created, then two distinct approvers with "Approve High-Value" permission are required. Given the initiator has approval permission, when the payout is high-value, then the initiator cannot count as one of the required approvers. Given the first approver approves, when the same user attempts the second approval, then the system blocks with "second approval must be a different approver" and no duplicate approval is recorded. Given a high-value payout is awaiting approvals, when 48 hours elapse without two approvals, then the payout status changes to Approval Expired and it cannot be executed without re-submission. Given the second approval is recorded, when both approvals are present, then the system releases the payout to the selected rail immediately. Given any approver rejects, then the payout status becomes Rejected and no funds are sent.
Per-User and Per-Portfolio Payout Limits
Given a user ApprovalLimit = $Y, when they attempt to approve a payout with amount > $Y, then the action is blocked with "approval exceeds your limit" and no approval is recorded. Given a per-portfolio SinglePayoutLimit = $Z, when a payout is created with amount > $Z, then creation is blocked unless a user with "Override Limits" permission provides a justification and confirms. Given a per-portfolio DailyPayoutCap = $D, when cumulative executed payouts for the day reach $D, then further payouts are blocked from execution and remain Pending Execution until the next day or an authorized override with justification is applied. Given any limit check, when an override is used, then justification text (minimum 10 characters) is required and captured in the audit log.
Temporary Vendor Hold Blocks Payout Initiation
Given a vendor has HoldStatus = Active, when any user attempts to initiate or approve a payout to that vendor, then the action is blocked with "vendor on hold" and no new approvals are recorded. Given a user with "Override Vendor Hold" permission, when they choose to override, then they must enter a justification (minimum 10 characters) and the override is logged and time-bounded to the single payout. Given a vendor hold has an ExpiryDate, when current time exceeds ExpiryDate, then the hold auto-lifts and payouts can proceed without override. Given a vendor hold is active, when viewing the vendor profile or payout form, then the UI displays the hold reason, placed-by user, and expiry date.
Immutable Audit Log with Rule Overrides and Justification
Given any payout lifecycle event (create, edit, approve, reject, override, execute, cancel), when it occurs, then an audit entry is written with event type, user ID, role, portfolio, timestamp (UTC), source IP, and before/after values. Given any override is performed, when submitted, then the system requires a non-empty justification (minimum 10 characters) and stores it in the audit entry linked to the event. Given audit entries exist, when queried, then entries are append-only, ordered by timestamp, and expose a content hash; no user role can edit or delete an existing entry. Given an admin requests export for a date range and portfolio, when executed, then a CSV export is generated within 60 seconds containing all matching audit entries with their hashes.
SLA Dashboard for Payout Times and Approval Bottlenecks
Given SLATargets configured per portfolio (e.g., approval within 4 hours, payout release within 24 hours), when viewing the SLA dashboard, then metrics show current-period median and P90 versus targets with red/amber/green indicators. Given payouts exist, when calculating metrics, then time-to-first-approval, time-to-final-approval, and time-to-release are computed from audit timestamps and refresh at least every 5 minutes. Given approvals are pending beyond target, when viewing the dashboard, then a bottleneck list displays the count and average aging by approver and by stage, with drill-down links to affected payouts. Given filters for Portfolio, Vendor, Rail, and Date Range, when applied, then all metrics and lists update within 2 seconds to reflect the selection. Given an export is requested, when the user clicks Export CSV, then the dashboard exports the current filtered metrics and list within 30 seconds.

Milestone Releases

Break larger or multi-visit jobs into milestones with predefined amounts and evidence requirements. Each milestone triggers an automatic partial payout on approval, keeping vendors funded while protecting the budget until final sign-off. Reduces risk of overpay, eliminates spreadsheet tracking, and accelerates complex turns and rehabs.

Requirements

Milestone Template Library
"As a property manager, I want to apply standardized milestone templates to complex jobs so that I can create consistent scopes, payment schedules, and evidence requirements in minutes."
Description

Provide a reusable library of milestone templates for common jobs (e.g., make-ready turns, bathroom remodels, HVAC installs) that predefine milestone names, payout amounts or percentages, required evidence types (photos, videos, receipts, tenant sign-off), expected visit counts, and SLAs. Templates can be applied at job creation or retrofitted to existing jobs, then customized per job. Includes versioning, cloning, and guardrails so totals equal the job budget. Integrates with FixFlow’s job creation, budgeting, and vendor assignment flows to speed setup, enforce consistency, and eliminate manual spreadsheets.

Acceptance Criteria
Create Template with Payout Validation
Given I am creating a new milestone template with payout amounts or percentages When I click Save Then the system validates that the sum of payouts equals 100% for percentage-based templates or equals the template total amount for fixed-amount templates And prevents saving with a clear error if validation fails Given each milestone requires at least one evidence type When any milestone is missing an evidence type (photo, video, receipt, tenant sign-off) Then the system blocks save and highlights the missing field(s) Given SLA and expected visit count fields are required per milestone When any required field is blank or invalid Then the template cannot be saved and field-level errors are shown
Apply Template During Job Creation
Given I am creating a new job and select a milestone template When I apply the template Then milestones, payout schedule, evidence types, expected visit counts, and SLAs are auto-populated on the job Given the job has a total budget amount When I apply a percentage-based template Then calculated milestone payouts sum exactly to the job budget, with rounding applied per currency rules and no overage Given I customize auto-populated milestones When I attempt to save the job Then guardrails prevent exceeding the job budget and require all mandatory fields to remain complete
Retrofit Template to Existing Job
Given an existing job with no milestones When I apply a milestone template Then the template’s milestones are added without altering other job fields Given an existing job with existing milestones When I apply a template Then I am prompted to Merge or Replace And if Merge is chosen, only non-duplicate milestones are added And if Replace is chosen, existing milestones are archived and new milestones added, with an audit log entry recorded Given the resulting payouts would exceed the job’s remaining budget When I attempt to save Then the system blocks save and displays a specific over-budget error message
Template Versioning and Change History
Given a published template (v1) exists When I edit the template Then a new version (v2) is created and v1 becomes read-only Given jobs created with v1 When v2 is published Then existing jobs remain on v1 by default and are not auto-updated And users can optionally upgrade a job to v2 after reviewing a diff of changes Given multiple template versions exist When I open Template History Then I see a timestamped change log including author, fields changed, and version notes
Template Cloning and Permissions
Given I have template management permissions When I clone an existing template Then a new draft template is created named "Copy of <original name>" with all milestones, payouts, evidence types, SLAs, and visit counts duplicated (excluding unique IDs) Given I save a cloned template When I enter a name that already exists in my organization Then the system prevents save and prompts for a unique name Given I lack template management permissions When I attempt to create, edit, or clone a template Then access is denied and the action is not performed
Guardrails and Data Integrity on Publish
Given a template is in Draft status When I click Publish Then the system validates that payouts sum correctly, each milestone has at least one supported evidence type (photo, video, receipt, tenant sign-off), SLAs and visit counts are populated, and the template name is unique within the organization Given validation fails on publish When errors are present Then the publish action is blocked and field-level error messages are displayed Given a published template is archived When I archive it Then it can no longer be selected for new jobs but remains linked to existing jobs, and an audit log is recorded
Evidence Submission & Approval Gates
"As a landlord, I want vendors to submit proof for each milestone and for me to approve it quickly so that payouts only occur when work meets expectations."
Description

Enable vendors to submit required evidence for each milestone via web or SMS/MMS upload, with enforceable checklists, timestamps, optional geotagging, and annotations. Provide approvers a side-by-side review screen with the milestone scope, before/after media, and comments. Support approve, reject with reasons, and resubmission. Tie submission threads to the existing FixFlow conversation stream, and trigger notifications to vendors and managers. Upon approval, automatically unlock the related payout trigger. Enforce file type/size limits, retention policies, and accessibility (alt text).

Acceptance Criteria
Web Evidence Upload for Milestone
- Given an authenticated vendor is on the Milestone Evidence page for an assigned job - And the milestone has a defined evidence checklist - When the vendor uploads media that meet allowed types (Images: JPG, JPEG, PNG, HEIC; Video: MP4, MOV; Docs: PDF) - And the uploads meet size limits (Images ≤ 25 MB, Video ≤ 200 MB, Docs ≤ 10 MB) - And total submission size ≤ 2 GB and ≤ 100 files - And the vendor selects required checklist items and tags each media item as Before or After (where required) - And provides alt text per media item (min 5 chars) - Then the system captures a server-side timestamp per file, associates files to the milestone, stores originals and non-destructive annotation layers, and confirms successful submission - And the submission status becomes Submitted and is locked for review - And an audit entry is recorded (vendor ID, job ID, milestone ID, IP, user agent, timestamps) - And evidence is stored encrypted at rest with a default retention of 24 months (admin configurable 6–60 months); purge occurs within 7 days post-expiry, respects legal-hold flags, and creates a purge audit log
SMS/MMS Evidence Submission Thread
- Given a vendor receives a FixFlow SMS with a secure, expiring link to submit milestone evidence - When the vendor replies with MMS media ≤ 3.5 MB per message and allowed types (JPG, JPEG, PNG, MP4, MOV) - Then the media is ingested into the correct job and milestone via the tokenized thread and server-side timestamped - And if any file exceeds carrier limits or requires alt text/checklist tagging, the vendor is sent a one-tap link to complete upload and required fields on the web - And the system prompts (via SMS or web) to complete all required checklist items before marking the submission as Submitted - And optional EXIF geotag is captured only when consent is provided; otherwise location is not stored - And the vendor receives an SMS confirmation with the submission reference
Enforceable Milestone Checklist and Optional Metadata
- Given a milestone has a configured evidence checklist with required and optional items - When a vendor attempts to submit without all required items satisfied - Then submission is blocked with a clear list of missing items and links to fulfill them - And optional metadata fields include geotag (toggle), notes, and per-item annotations (draw/text); originals are retained and annotations stored non-destructively - And if the milestone requires both Before and After evidence, at least one item of each is required; otherwise a validation error is shown
Approver Side-by-Side Review and Annotations
- Given an approver opens the milestone review screen - Then the screen shows the milestone scope, checklist with completion status, and a side-by-side Before/After viewer for matched items - And the approver can zoom, view timestamps and geotag (if present), read alt text and vendor annotations, and add approver comments per item - And the approver can save comments without making a decision - And initial load time is ≤ 2 seconds for up to 50 images on a 25 Mbps connection, with lazy loading for additional media
Approve, Reject with Reasons, and Resubmission
- Given the approver has review permissions for the job - When the approver selects Approve - Then the milestone status updates to Approved, a payout trigger event is emitted, and notifications are sent to vendor and manager - When the approver selects Reject and provides at least one reason (coded or free-text ≥ 10 chars) - Then the status updates to Revisions Requested, the submission unlocks for vendor edits, and reasons are sent to the vendor - When the vendor resubmits - Then prior submissions remain in history, differences are highlighted, and the approver can re-review - And all actions are captured in an immutable audit trail with user IDs and timestamps
Conversation Stream Linkage and Notifications
- Given any evidence submission, approval, or rejection occurs - Then a message is appended to the existing job conversation stream with the event type, milestone name, and a deep link to the review - And notifications are sent according to role defaults: vendors via SMS, managers via in-app and email; tenants only notified if the milestone is marked shareable - And notification payloads include job address, milestone, status change, and next steps; no media is inlined in SMS - And duplicate notifications are de-duplicated within 2 minutes for the same event
Payout Unlock and Idempotency on Approval
- Given a milestone is Approved by an authorized approver - Then the related partial payout trigger is fired exactly once (idempotent on retries) with milestone ID, approved amount, and payee ID - And on success from the payments service, the milestone payout status becomes Paid; on failure, it becomes Payment Pending and an alert is sent to the manager with the error code - And approvals cannot be reversed after payout is Paid without an administrative reversal workflow (out of scope); attempts are blocked and logged
Partial Payout Automation & Escrow
"As a vendor, I want automatic partial payouts to release when my milestone is approved so that I have reliable cash flow without waiting for the entire job to finish."
Description

Allocate the job budget into milestone-specific holds and manage funds in an escrow-like ledger. On approval, automatically disburse the milestone amount to the vendor via ACH/Stripe with retries, cutoffs, and status tracking. Handle failed payouts, reversals, and partial holds/holdbacks. Capture all ledger movements (authorized, held, released, refunded) and sync to job and vendor balances. Support configurable payout delays, business day rules, and tax/document tags for year-end reporting. Provide notifications to all parties and a clear payout timeline view.

Acceptance Criteria
Automatic Partial Payout on Milestone Approval
Given a job has an escrow balance greater than or equal to the milestone amount and the milestone has no holdback And the vendor is payments-enabled (ACH/Stripe) and not on payout hold When a user with Approve Milestone permission marks the milestone Approved Then a disbursement equal to the milestone amount is created to the vendor with status Scheduled and an expected settlement date And the job escrow ledger records Release and DisbursementPending entries with unique ids and an idempotency key And job and vendor balances update accordingly within 1 second of approval And repeating the approval or resubmitting with the same idempotency key does not create a duplicate disbursement
Escrow Ledger Audit Trail and Balance Integrity
Given any funds movement (Authorize, Hold, Release, Refund, DisbursementPending, Disbursed, Reversal) When the event is committed Then the ledger writes an immutable entry with timestamp (UTC), actor, source, reason, amount, currency, before/after balances, job id, milestone id, vendor id, payout id, and idempotency key And the sum of all ledger deltas for the job equals the job escrow balance, and for the vendor equals the vendor payable balance And entries are append-only; corrections are posted as new compensating entries And an export API returns entries with pagination and deterministic ordering by time and id
Failed Payout Retry and Status Tracking
Given a payout is in Scheduled or Processing status When the payment processor returns a retryable failure (e.g., ACH R01, R03) Then the payout status changes to Failed-Retryable with the processor code and message And the system schedules up to 3 retries at 1h, 24h, and 72h intervals unless canceled by a user with proper permission And each retry attempt is logged as DisbursementRetry linked to the payout id And if a retry succeeds, status updates to Disbursed with settlement date and the ledger records Disbursed And if the failure is terminal (e.g., account closed), retries are skipped, a Reversal returns funds to escrow, and status is Failed-Terminal And the vendor and job owner receive failure notifications with next-step guidance
Payout Delays, Cutoffs, and Business Day Rules
Given organization settings define payoutDelayDays = 2, processing cutoff = 5:00 PM in org time zone, and a business-day calendar When a milestone is Approved at or before 5:00 PM on a business day Then the execution date is set to 2 business days after the approval date When a milestone is Approved after 5:00 PM or on a non-business day Then the execution date is set to 2 business days after the next business day And weekends and configured holidays are excluded per the calendar And changing these settings affects only approvals after the change and does not modify already scheduled payouts
Partial Holds and Holdbacks on Milestones
Given a milestone amount of 2,000 USD with a 10% holdback until Final Sign-Off or 14 days after approval, whichever occurs first When the milestone is Approved Then a disbursement of 1,800 USD is scheduled per payout rules and 200 USD remains held in escrow with holdback reason and release conditions And the ledger records a Release of 1,800 USD and a Holdback of 200 USD linked to the milestone When the job receives Final Sign-Off with no open disputes before day 14 Then the 200 USD holdback is released and disbursed per payout rules When day 14 arrives with no open disputes Then the holdback is released automatically And if a dispute is opened, the holdback remains until resolved, and all actions are logged
Notifications and Payout Timeline View
Given a milestone approval triggers a scheduled payout Then the vendor receives SMS and email within 60 seconds including amount, expected date, and a secure status link And the job owner receives an in-app notification and email within 60 seconds with the same details And the Payout Timeline view shows events (Approved, Scheduled, Processing, Disbursed, Failed, Reversed, Retry) in reverse chronological order with timestamps, amounts, and status codes And the timeline updates within 5 seconds of status changes and loads in under 2 seconds for up to 500 events And each event includes a reference id and links to corresponding ledger entries
Tax and Document Tagging for Year-End Reporting
Given vendor tax classification and W-9 status are stored When a payout is scheduled and when it is disbursed Then the transaction is tagged with tax year, 1099-NEC eligibility, and vendor tax id where applicable And a Year-End Tax Export for a selected tax year produces a CSV of vendor totals that reconciles to ledger entries within ±0.01 USD And payouts to vendors marked Exempt are excluded from 1099 totals and tagged Exempt And when W-9 is missing, the payout proceeds but is tagged Missing W-9 and administrators see a persistent alert until the document is collected
Milestone-Aware Scheduling
"As a coordinator, I want each milestone to have its own scheduled visits and dependencies so that work is sequenced correctly and double-bookings are prevented."
Description

Attach one or more scheduled visits to each milestone with dependencies (e.g., Milestone 2 cannot start until Milestone 1 is approved). Use FixFlow’s shared calendar and availability to auto-schedule vendors and coordinate tenant access, preventing double-bookings. Support rescheduling with dependency checks, SMS confirmations, and reminders. Provide a visual timeline/Gantt view of milestones and visits, with SLA timers and blockers called out. Respect time zones and export to calendar formats while keeping the milestone linkage intact.

Acceptance Criteria
Auto-schedule first milestone visit without conflicts
- Given a job with Milestone 1 requiring a defined visit duration, a selected vendor, tenant access windows, and a property time zone - When the user clicks Auto-schedule for Milestone 1 - Then the system selects the earliest available slot matching the duration within tenant access windows and vendor working hours in the property’s time zone - And the slot does not overlap any existing vendor or tenant bookings on the shared calendar - And the visit is created with status "Scheduled", with start/end timestamps stored in UTC and localized to the property’s time zone - And SMS confirmations are sent to vendor and tenant including date/time, address, and confirmation links - And SMS reminders are sent at configured intervals (e.g., 24h and 2h before start) unless the visit is canceled or completed - And the shared calendar displays the visit within 5 seconds - And an audit log entry records scheduler identity, selected slot, and conflict checks performed
Enforce milestone dependencies on scheduling and start
- Given Milestone 2 depends on Milestone 1 approval - When a user attempts to schedule a visit for Milestone 2 before Milestone 1 is approved - Then the system saves the visit as "Tentative (Blocked by Milestone 1)" without sending SMS or calendar invitations - And the visit is visibly marked as Blocked in the timeline with the blocker reason - When Milestone 1 is approved - Then any Tentative (Blocked) visits for Milestone 2 are auto-confirmed if still conflict-free and within configured lead time - And SMS confirmations are sent upon auto-confirmation; if conflicts exist, the system prompts the user to re-schedule instead of confirming - And Milestone 2 visits cannot be set to "In Progress" until the dependency is satisfied
Reschedule visit with conflict and dependency checks
- Given a scheduled visit for Milestone 1 and a dependent Tentative visit for Milestone 2 - When the user reschedules the Milestone 1 visit to a new time - Then the system checks for conflicts against vendor and tenant calendars and blocks the change if conflicts exist, presenting at least three alternative slots when available - And if the new time pushes Milestone 1 beyond the start of the Tentative Milestone 2 visit, the dependent visit remains Blocked and is re-evaluated upon Milestone 1 approval - And SMS update messages are sent to vendor and tenant with the new time; if the move is within 24 hours, explicit acknowledgment is required to confirm - And the Gantt timeline updates within 5 seconds; ICS feeds issue an update for the same event UID
Visual timeline with SLA timers and blockers
- Given a job with multiple milestones, some pending approval, some scheduled, and SLA targets configured per milestone - When the job timeline is viewed - Then each milestone is rendered as a bar spanning planned duration with nested visit markers and state badges (Scheduled, Tentative, In Progress, Approved) - And any unmet dependency renders a red Blocker label with the blocking milestone referenced - And an SLA countdown timer is shown for each milestone indicating time remaining to SLA breach in the property’s time zone - And milestones breaching SLA are highlighted and appear in an Overdue filter - And selecting a bar opens details with visit times, dependency list, and action buttons (Approve, Reschedule) - And timeline visuals reflect server-side changes within 5 seconds
Time zone–aware scheduling and notifications
- Given landlord, vendor, and tenant are in different time zones and the property time zone is set - When a visit is scheduled for 10:00–12:00 property local time - Then the visit is stored in UTC, displayed as 10:00–12:00 in the property context, and rendered in each user’s local time in notifications and calendars - And SMS messages include both the recipient’s local time and the property local time - And DST transitions are handled so a 2-hour visit remains 2 hours across DST changes - And ICS exports include VTIMEZONE components and correct TZID for accurate rendering
Export calendar with milestone linkage preserved
- Given a job with milestones and visits - When the user exports calendar events as an ICS file or subscribes to a calendar feed - Then each event title includes Job Name, Milestone Name, and Visit Number - And each event UID contains a stable identifier referencing the job and milestone - And the event description includes a deep link back to the milestone in FixFlow and the current dependency status - And updates to visits publish ICS updates with the same UID; cancellations send a corresponding cancel message - And subscribing calendars reflect changes within 5 minutes - And exporting/importing preserves the milestone linkage when re-imported into FixFlow by reading UID metadata
Change Orders & Budget Re-baselining
"As a property manager, I want to adjust milestones and budgets via change orders so that unexpected scope changes are tracked and approved without spreadsheets."
Description

Allow authorized users to propose change orders that add, remove, or reorder milestones; adjust amounts, evidence requirements, and due dates; and update visit plans. Require approval per org rules, then automatically re-baseline the job budget, escrow allocations, and schedule. Preserve an immutable history with rationale, attachments, and impacted payouts. Notify vendors and tenants of approved changes and reconcile any in-flight submissions or pending payouts against the new baseline.

Acceptance Criteria
Authorized Users Propose Comprehensive Change Orders
Given a user with Change Order permission on a job When they open the Change Order wizard Then they can add, remove, and reorder milestones and edit amounts, evidence requirements, due dates, and visit plans in a single draft Given required fields are missing or invalid (e.g., negative amount, blank rationale) When the user attempts to submit the draft for approval Then the system blocks submission and displays field-level validation messages Given an unauthorized user without required permissions When they attempt to create or edit a change order Then access is denied and no draft is created Given a user is in the wizard with an unsaved draft When they navigate away or their session ends Then the draft is auto-saved and can be resumed from the job’s Change Orders list
Approval Workflow Enforces Organization Rules
Given an organization approval policy exists (e.g., cost increase above a configurable threshold requires two approvers) When a submitted change order meets the policy conditions Then the system routes it to the required approvers (sequentially or in parallel per policy) and blocks baseline changes until all approvals are completed Given an approver rejects the change order with a reason When the rejection is recorded Then the change order status becomes Rejected, the proposer is notified, and no baseline updates occur Given all required approvals are completed When the final approval is recorded Then the change order status becomes Approved and the system immediately initiates re-baselining
Automatic Budget, Escrow, and Schedule Re-baselining on Approval
Given an approved change order modifies milestone amounts When re-baselining runs Then the job total, milestone budgets, and escrow allocations recalculate to match the new totals without negative balances and with rounding rules applied consistently Given re-baselining detects an escrow shortfall When funds are insufficient for upcoming payouts Then a funding task is created for the delta and payouts exceeding available escrow are blocked until funds are received Given re-baselining results in surplus escrow When ledger updates are posted Then surplus is released per org policy with auditable ledger entries Given approved changes include due dates or visit plan updates When the schedule re-baselines Then milestone due dates and visit plans update on the shared calendar without creating double-bookings
Immutable Audit Trail of Change Orders
Given any create, submit, approve, or reject action on a change order When the action occurs Then an immutable audit entry is recorded with actor, UTC timestamp, rationale text, attachments, and before/after diffs for milestones, amounts, evidence requirements, due dates, and visit plans Given an approved change order When a user views the job history Then the full change set and attachments are visible and cannot be edited or deleted; superseding is only possible via a new change order
Reconciliation of In-Flight Submissions and Pending Payouts
Given vendor submissions or pending payouts exist when a change order is approved When milestones are unchanged Then submissions remain linked and payout calculations use the new baseline amounts Given a milestone is removed, split, or merged by the change order When reconciliation runs Then associated submissions are flagged for reassignment or review, all attachments are preserved, and vendor and job owner are notified Given a pending payout exceeds the new milestone amount When re-baselining completes Then the payout is reduced to the new cap and the excess is cancelled with a recorded reason Given a pending payout belongs to a removed milestone When reconciliation runs Then the payout is cancelled with reason "Milestone removed by Change Order" and moved to a review queue
Stakeholder Notifications on Approved Changes
Given a change order is approved and re-baselining completes When affected milestones involve assigned vendors Then those vendors receive a notification summarizing changed amounts, due dates, and evidence requirements via their preferred channel (SMS/email), with a link to details Given a tenant has an impacted visit plan When the schedule updates Then the tenant receives an updated appointment notification and confirmation request honoring their communication preferences (SMS/email) Given a notification fails delivery When retries are attempted Then failures are logged with reason and surfaced for follow-up
Concurrency and Conflict Handling for Change Orders
Given two users submit change orders concurrently on the same job When one change order is approved Then the other is revalidated against the new baseline and any conflicts (e.g., editing the same milestone) are highlighted and must be resolved before approval Given a pending change order is awaiting approval When a newer approved change order alters overlapping milestones Then the pending change order is marked as Outdated by the newer change order and cannot be approved without revision Given a system error occurs during re-baselining When the operation fails Then all changes are rolled back atomically, no partial updates persist, and the user sees an error with a correlation ID for support
Milestone Reporting & Audit Trail
"As an owner, I want clear reporting and an audit trail for milestones and payouts so that I can verify work, track spend, and satisfy accounting and compliance needs."
Description

Provide dashboards and exports that show milestone status across all jobs (awaiting evidence, awaiting approval, approved, paid, overdue) with filters for property, vendor, and date. For each milestone, maintain a complete audit trail: who submitted what evidence, who approved/rejected, timestamps, comments, payout references, and ledger entries. Include aging reports, average approval times, and exception views to identify bottlenecks. Enable CSV/PDF export and secure sharing links for stakeholders and accounting.

Acceptance Criteria
Portfolio Milestone Status Dashboard & Filters
Given no filters are applied, when the Milestone Status dashboard loads, then total counts and the list include all milestones and each milestone is assigned exactly one of: Awaiting Evidence, Awaiting Approval, Approved, Paid, Overdue. Given a property filter is applied (single or multi-select), when the filter is applied, then counts and the list include only milestones for the selected properties. Given a vendor filter is applied, when the filter is applied, then counts and the list include only milestones for the selected vendor(s). Given a date range filter (Due Date) is applied, when the filter is applied, then counts and the list include only milestones with Due Date within the range (inclusive). Given a status pill is clicked (e.g., Awaiting Approval), when applied, then the list is filtered to that status and the active pill is visually indicated. Given any combination of filters, when filters are cleared, then the dashboard returns to the unfiltered state and counts match portfolio totals. Given milestones with past-due due dates that are not marked Paid, when the dashboard renders, then those milestones appear in Overdue and not in any other status bucket. Given pagination is enabled, when the user navigates pages, then totals and counts remain consistent with the active filters.
Milestone Audit Trail Completeness & Immutability
Given a milestone with evidence submissions, approvals/rejections, comments, payouts, and ledger entries, when viewing its Audit Trail, then entries are displayed in strict chronological order newest-last with event type, actor identity, timestamp, and relevant metadata (comments, amounts, references). Given an evidence submission event, when recorded, then the audit entry includes submitter, timestamp, evidence filenames/IDs, and any notes. Given an approval or rejection event, when recorded, then the audit entry includes approver, decision (approved/rejected), timestamp, and approver comment if provided. Given approval triggers a partial payout, when recorded, then the audit trail includes payout reference ID and a link/reference to the accounting ledger entry and the amounts match the milestone payout amount. Given any audit entry is edited or a record is corrected, when changes are saved, then a new audit entry is appended describing the change and the original entry remains unchanged (append-only behavior). Given a user exports the audit trail for a milestone, when CSV or PDF is generated, then the export contains the same set of entries, in the same order, with the same fields as displayed on-screen.
Aging Report & Average Approval Time
Given the Aging Report is opened with current filters and an as-of date of today, when calculated, then open milestones are bucketed by days in current status into 0–3, 4–7, 8–14, and 15+ day buckets and bucket counts sum to the total open milestones under the filters. Given milestones that have been approved, when computing Average Approval Time, then the system averages (final approval timestamp − latest evidence submission timestamp prior to approval) and displays the result to one decimal day. Given a milestone has been rejected and later approved after new evidence, when computing Average Approval Time, then the interval is measured from the latest evidence submission before the final approval. Given the user changes the date range filter, when the report recalculates, then the Average Approval Time and bucket counts reflect only milestones included by the filters. Given the user exports the Aging Report, when CSV is generated, then it contains per-status bucket counts and the Average Approval Time for the filtered set.
Exception Views for Bottlenecks
Given the Exceptions view is opened, when rendered, then it shows lists for: Overdue milestones, Awaiting Approval > 3 days, Awaiting Evidence > 3 days, and Top Vendors/Approvers by overdue count and by longest average approval time. Given an exception list is displayed, when the user sorts by Aging (descending), then the list orders milestones by longest time-in-status first. Given the user clicks a vendor/approver in the Exceptions view, when applied, then the dashboard and lists are filtered to that entity. Given an exception item is clicked, when navigating, then the milestone detail opens to the Audit Trail tab with context preserved. Given the user exports the Exceptions view, when CSV is generated, then it contains the visible exception rows under current filters and sort order.
CSV/PDF Export & Secure Sharing Links
Given any dashboard/list/report is in view with active filters, when the user exports to CSV, then the file includes one row per milestone in the view and columns: Milestone ID, Job ID, Property, Unit, Vendor, Status, Due Date, Evidence Submitted At, Approved At, Paid At, Amount, Aging (days), Approver, Payout Ref, Ledger Ref, and values match the on-screen data. Given the user exports to PDF, when generated, then the PDF mirrors the visible report layout with the same totals and filter context. Given an export is generated, when the file is created, then its filename contains the report type, portfolio identifier, and an ISO 8601 timestamp. Given the user creates a secure sharing link, when the link is generated, then it is a read-only URL scoped to the current filters and view, with an expiration between 1 and 30 days selected by the user. Given a secure sharing link is accessed, when a stakeholder opens it, then they can view and export but cannot modify data, and the access is recorded with timestamp and IP. Given a secure sharing link is revoked, when a revoked URL is visited, then the system returns an access denied response and logs the attempt.
Role-Based Approvals & Thresholds
"As an organization admin, I want to set approval rules and thresholds so that high-value milestones receive proper oversight and compliance is enforced."
Description

Configure approval rules by role and amount thresholds (e.g., any milestone over $1,000 requires owner approval). Support single- and multi-step approvals, delegates, and escalation SLAs for pending approvals. Enforce permissions on who can create/edit milestones, submit evidence, approve payouts, and issue change orders. Provide mobile-friendly quick approvals with contextual summaries and guardrails to prevent conflicts of interest (e.g., vendors cannot self-approve). All decisions are logged to the audit trail.

Acceptance Criteria
Owner Approval Threshold Enforcement (> $1,000)
Given a milestone total exceeds the configured owner-approval threshold And a rule exists requiring Owner approval for amounts over the threshold When the milestone is submitted for approval Then the approval request is routed to the Owner And payout and milestone completion are blocked until Owner approval is recorded And users without the Owner role cannot finalize approval And the UI displays required approvers and the pending state And rule evaluation uses a configuration snapshot stored with the milestone to prevent retroactive changes
Two-Step Approval Sequence (Manager then Owner)
Given a milestone policy requires sequential approvals: Manager then Owner When the Manager approves Then the request advances to the Owner and is not payable yet When the Owner approves Then the automatic partial payout for the milestone is executed And the milestone status updates to Approved And notifications are sent to requester and vendor And if the Manager rejects at step 1, the flow ends and payout is not executed
Delegated Approval with Proper Attribution
Given Approver A has an active delegate B for the approval period And an approval request is assigned to Approver A When delegate B approves the request Then the approval is recorded as approved by B on behalf of A And A can no longer act on the same request And the action respects A’s amount thresholds and permissions And all notifications and the audit trail reflect the delegation relationship
Escalation SLA for Pending Approvals
Given an escalation SLA is configured to escalate after 24 hours of no action And an approval request has been pending for at least 24 hours When the SLA time elapses Then the request escalates to the next configured approver And all involved parties are notified of the escalation And the original approver is marked as escalated in the approval history And the audit trail records the escalation timestamp and target approver And no payout is released until an authorized approver completes approval
Permissions Enforcement for Milestones and Change Orders
Given role-based permissions define who can create/edit milestones, submit evidence, approve payouts, and issue change orders When a user attempts an action outside their permissions Then the system blocks the action with a clear error message And restricted controls are hidden or disabled in the UI And only permitted fields are editable based on the user’s role And server-side authorization enforces all actions regardless of client behavior And an audit entry records the denied attempt including user, role, action, and target
Conflict of Interest Prevention (Vendor Self-Approval Block)
Given the assigned vendor is the requester of a milestone approval or change order When the vendor attempts to approve or self-approve Then the system denies the attempt with a conflict-of-interest message And no approval state is changed and no payout is triggered And the audit trail records the blocked attempt and user identity And the approval remains assigned to the correct non-vendor approver
Mobile Quick Approval with Context and Evidence Guardrails
Given an approver opens the mobile quick-approval view for a milestone And required evidence items exist for that milestone When the approver attempts to approve Then all mandatory evidence items must be viewed before the Approve action is enabled And a contextual summary displays vendor, amount, delta vs budget, prior approvals, and change orders And the approver can Approve or Reject with an optional comment And the decision is recorded with device, OS, and IP metadata And the system responds within 2 seconds on a typical mobile network And the approval state updates in real time and notifies the requester and vendor

Smart Splits

Split a single work order payout across parties and purposes—lead contractor, helper techs, and materials reimbursement—with rules based on line items or percentages. FixFlow handles separate receipts, notes, and 1099 tracking automatically, so managers avoid double-entry and vendors get exactly what they’re owed without back-office friction.

Requirements

Split Configuration & Allocation Engine
"As a property manager, I want to configure how a work order payout is split among parties so that each vendor receives the correct share without manual spreadsheets."
Description

Enable managers to define payout splits for a single work order by percentage, fixed amount, or line-item rules, with support for multiple payees (lead contractor, helper techs, materials reimbursement). Provide reusable templates by vendor and job type, rounding rules to the cent, and validations that ensure totals reconcile to the payable amount. Automatically distinguish labor vs. reimbursable materials, allow default allocations, and support quick edits without duplicate data entry. The engine persists rules with the work order, updates in real time as line items change, and integrates with FixFlow’s billing and scheduling so payouts reflect the latest approved scope.

Acceptance Criteria
Create and Apply Vendor Job‑Type Split Template
Given I have Manager role and select Vendor V and Job Type JT When I create a split template with multiple payees using percentage, fixed amount, and line‑item rules Then the template is saved with a unique name and is associated with Vendor V and Job Type JT And the template can be searched and previewed by vendor and job type Given a new work order for Vendor V and Job Type JT When I apply the template Then default allocations populate with correct payees, rule types, and default values And overriding a value updates the work order only without changing the saved template
Configure Mixed Split Rules on a Work Order
Given a work order with Labor and Materials line items totaling a payable amount When I assign a combination of rules (e.g., 60% of Labor to Lead, $100 fixed Materials reimbursement to Reimbursement payee, 20% of Labor to Helper) Then the engine calculates amounts per rule, aggregates per payee, and displays a per-rule breakdown And overlapping or conflicting rules on the same base are prevented with inline error messages before save And required payee info (e.g., tax classification) is validated before finalizing payouts
Rounding and Reconciliation to Payable Amount
Given calculated split results include fractional cents When I save the allocations Then rounding to the cent is applied using round‑half‑up and any remainder is allocated to the primary payee unless a different remainder rule is configured And the sum of all payee amounts equals the exact payable amount before finalization is allowed And a reconciliation note records rounding adjustments per payee
Automatic Labor vs. Materials Allocation
Given line items are flagged as Labor or Materials When I apply rules that target Labor or Materials Then only the targeted line items are included in each rule’s calculation base And Materials reimbursements are excluded from 1099‑eligible totals while Labor payouts are included And Materials reimbursement lines require an attached receipt before finalization
Real‑Time Recalculation and Persistence
Given a work order with saved split rules When I add, edit, or delete line items or change amounts Then split totals recalculate in real time and update within 1 second of the change And previously defined rules and overrides persist and are restored on page reload or reopen of the work order And an audit entry records line‑item changes and resulting payout recalculations with timestamp and user
Integration with Billing and Scheduling
Given the scope is updated and re‑approved or the appointment is rescheduled When payouts are generated Then payout amounts reflect the latest approved scope and effective dates And the billing export produces separate payables per payee with unique references, notes, and attached receipts And voids or modifications produce delta entries on re‑export to keep accounting in sync
Quick Edits Without Duplicate Data Entry
Given default allocations are populated on a work order When I adjust a payee’s percentage or fixed amount Then dependent totals update immediately without re‑entering other rules Given I change the designated primary payee When I confirm the change Then the engine updates rule references to the new payee without requiring duplicate data entry Given I remove a payee and a reallocation policy is configured (e.g., proportional redistribution) When I save Then the engine applies the policy and shows a before/after diff of allocations
Line-Item Mapping and Materials Reimbursement
"As an accounts coordinator, I want to attach receipts and map line items to the right payee so that materials are reimbursed exactly and transparently."
Description

Allow granular mapping of each line item to specific payees and purposes, including tagging items as reimbursable materials. Support receipt uploads per reimbursable item, enforce that reimbursements do not exceed documented amounts, and permit notes for context. Provide taxability flags, cost-code/category tagging, and automatic roll-ups per payee. Changes to line items recalculate allocations instantly while preserving attachments and notes. This reduces reconciliation friction and ensures materials are reimbursed accurately and traceably.

Acceptance Criteria
Multi-Payee Line-Item Mapping by Percentage and Amount
Given a work order line item with a subtotal, When the user assigns allocations across two or more payees using percentages that sum to 100%, Then the system calculates each payee’s amount to the nearest cent and allows save. Given allocations by percentage do not total 100%, When the user attempts to save, Then the system blocks save and displays "Allocations must total 100% for percentage-based splits." Given the user mixes fixed amounts and percentages for the same line item, When saving, Then the system calculates percentage-based amounts from the remaining subtotal after fixed amounts, ensuring the total allocated equals the line item subtotal. Given rounding introduces a one-cent difference, When saving, Then the system assigns the residual cent to the first allocation entry in display order to ensure sums match the subtotal.
Tag Line Item as Reimbursable Materials with Receipt Uploads
Given a line item is marked Reimbursable Material, When the user uploads receipts (PDF/JPG/PNG; max 10 MB each; up to 10 files), Then the receipts are attached to that line item and listed with filename and upload timestamp. Given a reimbursable materials item, When a receipt is uploaded, Then the user is required to enter a Documented Amount for that receipt to two decimal places before saving. Given a reimbursable materials item with N receipts, When saving, Then the system computes the total documented amount as the sum of receipt Documented Amount values.
Enforce Reimbursement Amount Cap from Documented Receipts
Given a reimbursable materials item with total documented receipts of $X, When the user sets the reimbursement allocation total to greater than $X, Then the system blocks save and displays "Reimbursement cannot exceed documented receipts ($X)." Given a reimbursable materials item with total documented receipts of $X, When the reimbursement allocation total is less than or equal to $X, Then the system allows save. Given currency rounding causes a one-cent discrepancy, When saving, Then the system adjusts by up to $0.01 so the reimbursement total does not exceed $X and the line item subtotal is maintained.
Per-Item Notes Persist Through Recalculation
Given a line item, When the user adds notes up to 1000 characters and saves, Then the notes are persisted with author and timestamp and displayed on the line item. Given a line item with notes, When quantities, unit prices, tax flags, cost codes, or allocations are changed and recalculated, Then the notes remain attached and unchanged. Given a line item with notes, When allocations are re-assigned between payees, Then the notes continue to be associated with the line item (not any payee) and appear unchanged after save.
Taxability and Cost-Code/Category Tagging with Roll-Up Impact
Given a line item, When the user toggles the Taxable flag and selects a Cost Code/Category from the maintained list, Then those selections are saved on the line item. Given payee roll-ups, When viewed after allocations are saved, Then totals per payee are grouped by Taxable vs Non-Taxable and by Cost Code/Category, displaying subtotals for each grouping. Given a line item is marked Reimbursable Material, When computing 1099-eligible totals per payee, Then its reimbursable amount is excluded from 1099 totals and included under Reimbursement category.
Real-Time Payee Roll-Ups Reflect Allocation Changes
Given the Payee Summary is visible, When the user edits a line item’s quantity, unit price, taxability, cost code, or allocation percentages/amounts, Then the per-payee roll-up totals update within 1000 ms without a page reload. Given multiple line items across payees, When an item is deleted or added, Then the per-payee roll-ups recalculate immediately to reflect the change. Given a recalculation occurs, When updates complete, Then an "Updated just now" timestamp appears on the roll-up component.
Preserve Attachments on Edits and Reallocation
Given a line item with attached receipts and notes, When the user edits the line item fields or reallocates to different payees and saves, Then all existing receipts and notes remain attached to that line item and are still accessible. Given a line item with receipts and notes, When recalculation of allocations occurs due to a change in price or quantity, Then no attachments or notes are removed, duplicated, or reassigned to a payee. Given a line item is re-assigned fully from one payee to another, When saving, Then receipts and notes remain with the line item and an audit entry records the reassignment event with timestamp and actor.
Multi-Payout Disbursement Orchestration
"As a finance manager, I want FixFlow to send each payee their portion automatically so that I don’t have to issue multiple payments manually."
Description

Generate separate payouts per payee from a single work order and route them via preferred payment methods (e.g., ACH). Combine a vendor’s multiple splits into a single transfer when possible, while preserving split-level metadata and receipts. Provide disbursement scheduling, payment status tracking (pending, sent, settled, failed), automatic retries, and notifications via SMS/email to confirm amounts and timing. Integrate with FixFlow’s vendor profiles for payment preferences and with the shared calendar to align disbursement timing with job completion and approvals.

Acceptance Criteria
Separate Payouts per Payee from a Single Work Order
Given a work order with multiple payees and split rules by percentage and line items, When the manager marks the work order Approved for Payout, Then the system generates one payout instruction per unique payee with amounts calculated per split rules and the sum equals the total payable. Given each payee has a preferred payment method stored in their vendor profile, When payout instructions are generated, Then each instruction is routed via the payee's preferred method (e.g., ACH) and target account. Given split calculations result in fractional cents, When amounts are computed, Then rounding is applied using banker's rounding and no rounding drift occurs (total equals original). Given the approval action is performed multiple times or retried, When idempotency keys are reused, Then duplicate payout instructions are not created.
Consolidate Multiple Splits to Same Vendor into a Single Transfer
Given two or more splits in the same work order are payable to the same vendor with the same payment method and destination and the same settlement date, When disbursement is executed, Then the system consolidates these splits into a single transfer for that vendor. Given consolidation occurs, When the transfer is created, Then the transfer amount equals the exact sum of the consolidated splits and the vendor receives one bank credit. Given consolidation occurs, When viewing payout details, Then split-level metadata (line item references, notes, tax categories) and individual receipts remain available and linked to the consolidated transfer. Given any split differs in payment method, destination account, currency, or settlement date, When disbursing, Then that split is not consolidated and is sent as its own transfer. Given a consolidated transfer is shown in the dashboard, When the user expands details, Then the UI displays both the consolidated transfer and each underlying split with amounts and references.
Disbursement Scheduling Aligned with Completion and Approval
Given a work order has a calendar completion time and requires manager approval, When the job is marked Completed and the manager approves payouts, Then the earliest disbursement date is scheduled for the next business day at 17:00 in the property timezone unless a later date is configured. Given the manager sets Hold Payout Until to a future date, When scheduling runs, Then the payout is scheduled no earlier than that date. Given required approvals (e.g., photo proof or tenant sign-off) are missing, When evaluating readiness, Then payouts remain in Pending Approval and no schedule is created. Given the scheduled date falls on a weekend or bank holiday, When determining settlement, Then the schedule automatically moves to the next business day and records the adjustment in the audit log. Given any schedule change is made by a user, When saved, Then the system records actor, timestamp, old value, and new value in the audit log.
Payment Status Lifecycle and Auditability
Given a payout is scheduled, When it awaits transmission, Then its status is Pending. Given a payout is transmitted to the payment processor, When confirmation of submission is received, Then its status changes to Sent with a processor transfer ID. Given the processor confirms funds have settled, When settlement notification is received, Then status changes to Settled with settlement timestamp. Given the processor returns an error, When the error is received, Then status changes to Failed with a machine-readable error code and human-readable reason. Given a consolidated transfer exists, When statuses change, Then both the consolidated transfer and each underlying split reflect consistent statuses and timestamps. Given the dashboard payouts view is loaded, When filtering by status, Then only payouts matching Pending, Sent, Settled, or Failed are returned.
Automatic Retry on Retryable Payment Failures
Given a payout fails with a retryable error (e.g., network timeout or ACH R01), When the failure is recorded, Then the system schedules up to 3 automatic retries over 72 hours using exponential backoff and marks status Pending Retry between attempts. Given a payout fails with a permanent error (e.g., ACH R03 invalid account), When the failure is recorded, Then no automatic retries are scheduled and the payout remains Failed (Permanent) with guidance to update payment details. Given automatic retries are scheduled, When a retry is executed, Then the retry uses the latest payment destination from the vendor profile at the time of retry and logs the attempt result. Given a manager cancels retries or triggers a manual retry, When the action is taken, Then the system updates the retry plan accordingly and records the user, timestamp, and reason. Given a retry ultimately succeeds, When settlement occurs, Then the final status is Settled and prior failure/attempt history remains in the audit log.
Vendor Notifications for Disbursement Amounts and Timing
Given a payout is scheduled, When the schedule is created, Then the payee receives an SMS/email including amount, currency, expected arrival window, masked payment method (e.g., ACH ••••1234), and a secure link to the receipt. Given multiple splits are consolidated, When the notification is sent, Then the message includes the consolidated total and a breakdown of each split amount and purpose. Given a notification preference is disabled for SMS or email in the vendor profile, When sending notifications, Then the system honors preferences and only sends via enabled channels; if SMS fails delivery, it falls back to email and records the fallback. Given a payout fails, When the failure occurs, Then the payee and manager receive a notification with the failure reason and the next retry time (if retryable) or instructions to update payment details (if permanent). Given notifications are sent, When delivery status is received (delivered/undeliverable), Then the system records the status and surfaces undeliverables in the dashboard alerts.
Split-Level Metadata and Receipts Preservation
Given each split in a work order has type, notes, and line item references, When payouts are generated, Then a unique, immutable receipt is created per split with work order ID, split type, gross amount, fees (if any), net amount, payee, timestamp, and tax category. Given splits are consolidated into a single transfer, When viewing receipts, Then the system provides both a summary receipt for the bank transfer and the individual split receipts, all linked by a shared correlation ID. Given API or dashboard access is requested, When retrieving receipts, Then each receipt can be downloaded as PDF/JSON and includes a checksum or version ID for audit integrity. Given any post-creation edits to notes or line-item mappings are permitted, When saved, Then a new receipt version is created with an incremented version number and prior versions remain accessible with an audit trail. Given year-end export is requested, When generating 1099 support data, Then each split receipt's tax category and amounts are included in the export without duplication.
1099 Eligibility Tracking and Year-End Export
"As a business owner, I want 1099-eligible amounts to be tracked automatically so that year-end reporting is accurate without manual spreadsheets."
Description

Track 1099-reportable amounts per vendor across all work orders, distinguishing labor from non-reportable reimbursements according to configured rules. Capture and validate W‑9/TIN status, surface threshold alerts (e.g., approaching $600), and maintain year-to-date tallies. Provide an export of 1099-eligible totals with supporting detail (work order IDs, dates, notes) for year-end filing, and flag any data gaps requiring remediation. This eliminates manual tallying and ensures compliance-ready records.

Acceptance Criteria
W-9/TIN Capture and Validation
- Given a vendor enters W-9 data (legal name, address, TIN), When they submit, Then the system validates TIN format (SSN: 9 digits; EIN: XX-XXXXXXX), stores W-9 received date, and sets status to Pending or Verified. - Given a payout is created for a vendor with Missing/Invalid W-9 status, When the payout is saved, Then the system displays a prominent warning, flags the payout as Requires Remediation, and excludes it from the Ready-for-1099 export until resolved. - Given a vendor updates a valid W-9/TIN, When the update is saved, Then the vendor status becomes Verified and any open remediation flags for that vendor’s payouts are cleared within 1 minute.
Smart Splits Eligibility by Line Item and Share
- Given a work order with line items tagged Eligible (e.g., Labor/Service) and Non-Eligible (e.g., Materials/Reimbursement) and Smart Splits across vendors, When the payout is finalized, Then each vendor’s 1099-eligible amount equals the sum of Eligible line items multiplied by that vendor’s split share, excluding Non-Eligible items. - Given split-based calculations produce fractional cents, When amounts are allocated, Then the system uses round half up to 2 decimals and reconciles any 1-cent remainder to the last vendor to ensure the sum of vendor allocations equals the total eligible amount. - Given receipts/notes attached to Non-Eligible items, When totals are computed, Then they do not contribute to 1099 tallies for any vendor.
Per-Vendor YTD Tally and Threshold Alerts
- Given YTD 1099-eligible totals are tracked per vendor for the selected tax year, When a new eligible payout is saved, Then the vendor’s YTD total updates within 5 seconds. - Given the default IRS threshold is $600, When a vendor’s YTD total reaches $500, Then the system surfaces an Approaching Threshold alert in the vendor profile and dashboard and sends an email notification to managers. - Given a vendor’s YTD total reaches or exceeds $600, When viewing the vendor profile or dashboard, Then a Threshold Met indicator is shown and remains until year-end or reset to a new tax year.
Retroactive Reclassification and Split Changes
- Given a previously recorded payout is edited to reclassify one or more line items between Eligible and Non-Eligible, When the edit is saved, Then affected vendors’ YTD totals and any threshold alerts recalculate immediately. - Given Smart Splits are adjusted (percentages changed or participants added/removed), When the change is saved, Then the system recalculates each impacted vendor’s eligible amounts and records an audit entry with timestamp, user, work order ID, and before/after amounts. - Given historical corrections are applied, When the year-end export is generated for the relevant date range, Then the exported totals reflect the corrections exactly once (no duplicate rows).
Year-End 1099 Export with Supporting Detail
- Given a user selects a tax year and clicks Export 1099 Data, When processing completes, Then a UTF-8 CSV file is generated within 60 seconds and made available for download for at least 24 hours. - Given the export is generated, Then the Vendor Summary section includes: Vendor Legal Name, Masked TIN (last 4), W-9 Status, Vendor Address, Total 1099-Eligible Amount, Count of Eligible Payouts. - Given the export is generated, Then the Supporting Detail section includes per payout row: Work Order ID, Payout Date (ISO 8601), Eligible Amount, Non-Eligible Amount, Line Item Category, Notes. - Given timezone differences, When dates are exported, Then all dates/times are normalized to the account’s configured timezone.
Export Filters and Inclusion Rules
- Given export options are presented, When the user selects Include only vendors at/above threshold, Then the summary includes only vendors with YTD eligible totals >= $600 for the selected year. - Given export options are presented, When the user selects Include all vendors with eligible amount > $0, Then all such vendors are included regardless of threshold. - Given export options are presented, When the user filters by W-9 Status (Verified/Pending/Missing), Then the export includes only vendors matching the selected statuses. - Given a specific date range within a tax year is chosen, When the export runs, Then totals reconcile to the UI YTD totals for the same range within $0.01 per vendor.
Data Gap Detection and Remediation
- Given compliance-required fields (W-9 on file, valid TIN format, vendor address) are missing or invalid, When a payout is created or an export is run, Then the system creates remediation items listing vendor, issue type, and affected payout/work order. - Given remediation items exist, When the year-end export is generated, Then two CSVs are produced: 1099_Ready.csv (vendors/payouts with all requirements met) and 1099_Exceptions.csv (issues and impacted records) with counts per vendor. - Given a remediation issue is resolved (e.g., W-9 uploaded), When the export is re-run, Then the vendor/payout moves from Exceptions to Ready and is included in totals.
Audit Trail, Notes, and Versioning
"As a controller, I want an immutable history of split changes so that any payout disputes can be resolved quickly with evidence."
Description

Record a comprehensive audit trail for every split, including who changed what and when, previous vs. new values, and associated notes and attachments. Support draft, approval, and post-payment lock states to prevent silent changes after disbursement. Provide readable history and diff views, and emit events for notifications and webhooks. This ensures accountability, reduces disputes, and supports internal reviews and external audits.

Acceptance Criteria
Record Change History for Split Edits
Given an existing Smart Split with version V And user U with permission Edit Split When U changes any split attribute (allocation amounts/percentages, recipient, line-item mapping, purpose, memo) Then the system persists version V+1 and an immutable audit entry containing splitId, versionBefore V, versionAfter V+1, actorId, actorRole, ISO-8601 UTC timestamp, requestId, and a list of changed fields with oldValue and newValue And if the update transaction is rolled back, no audit entry or version is created And bulk edits within a single transaction are captured in a single audit entry listing all fields changed And concurrent updates are prevented via optimistic locking; a conflicting write returns 409 with no audit entry for the rejected write And read views reflect the new version within 1 second of commit
View History Timeline and Field-Level Diff
Given a split with at least 3 versions When a user opens the History view Then a reverse-chronological timeline is shown with for each entry: version number, actor display name, action summary, and timestamp shown in org timezone with UTC hover And the user can filter the timeline by actor, action type, field name, and date range And selecting any two versions renders a side-by-side diff highlighting per-field changes, including added/removed line items and recalculated totals And currency values are displayed with currency code and two decimals; percentages with one decimal; null-to-value and value-to-null transitions are clearly indicated And the History view loads in under 2 seconds for up to 500 entries on a 50 Mbps connection
State Transitions: Draft, Approval, Post-Payment Lock
Given a split is in Draft When an editor updates fields Then changes are allowed and audited When the editor submits for approval Then state transitions to Pending Approval and an audit entry records the transition and submitted snapshot When an approver (not the submitter) approves Then state transitions to Approved and an audit entry records approver, timestamp, and any approval note When payment is disbursed for the Approved split Then state transitions to Paid (Locked) and an audit entry records the payment reference and lock activation When any user attempts to edit a Paid (Locked) split Then the system blocks the change with error "Split is locked after payment; create an adjustment" and persists no data changes And an audit entry records the blocked attempt with actor, timestamp, and attempted fields
Notes and Attachments on Audit Entries
Given any change that creates an audit entry When the actor includes a note (max 2,000 characters) and attachments (≤10 files, total ≤25 MB; types: pdf,jpg,png,csv) Then the note and attachments are stored, virus-scanned, and linked to that audit entry and version And notes are immutable; an edit action appends a new audit note entry referencing the prior note And attachments added or removed before Paid create corresponding audit entries; after Paid, removal is blocked and logged as an attempted action And downloading any attachment requires authorization and is itself logged in the audit trail And all notes and attachment metadata remain retrievable for at least 7 years
Event Emission for Notifications and Webhooks
Given any audit entry creation or state transition for a split Then an event is enqueued with unique eventId, eventType, splitId, version, actorId, occurredAt (UTC), and a diff payload of changed fields And webhook deliveries begin within 5 seconds of commit, are signed (HMAC-SHA256) with the tenant’s secret, and include an Idempotency-Key header equal to eventId And failed deliveries are retried with exponential backoff for at least 24 hours or until a 2xx response is received And duplicate deliveries for the same eventId must be safely ignored by subscribers (idempotent processing) And an Events API allows querying events by splitId and time window, returning consistent results within 1 second of commit
Audit Log Export for External Reviews
Given an org admin requests an export of a split’s audit log When the request is submitted Then the system generates a downloadable package containing: JSONL of all audit entries (with full diffs, notes, attachment metadata, state transitions, eventIds) and a human-readable PDF summary And each JSONL record includes a SHA-256 content hash; the package includes a manifest with a top-level checksum and creation timestamp And exports up to 10,000 audit entries complete within 60 seconds and remain available for download for 7 days And the export action (with requester, parameters, and download events) is recorded in the audit trail
Permissions and Vendor Visibility
"As a vendor, I want to see a clear breakdown of my payout and reimbursements so that I can verify what I’ll be paid without contacting the office."
Description

Enforce role-based permissions so only authorized users can create or modify splits, approve reimbursements, and release payouts. In the vendor portal, show each vendor their allocated amounts, materials reimbursements, receipts, and payment status, without exposing unrelated financial or tenant data. Provide downloadable receipts and breakdowns for the vendor’s own records, improving transparency and reducing support tickets.

Acceptance Criteria
Restrict Split Creation and Modification by Permission
Given a signed-in user with permission key "splits.manage" and an open work order When the user creates a new split or edits an existing split via UI or API Then the operation succeeds, a 200/201 is returned, the split is persisted, and an audit record captures actor, timestamp, and before/after values Given a signed-in user without permission key "splits.manage" When the user attempts to create or modify a split via UI or API Then action controls are hidden in the UI, the API returns 403 Forbidden, no data is changed, and an access-denied event is logged
Restrict Reimbursement Approval by Permission
Given a reimbursement in Pending status and a signed-in user with permission key "reimbursements.approve" When the user approves the reimbursement Then the reimbursement transitions to Approved, the approver id/timestamp are recorded, the vendor portal reflects the new status, and an immutable audit entry is stored Given a user without permission key "reimbursements.approve" When the user attempts to approve a reimbursement Then the UI disables or hides the Approve control, the API returns 403 Forbidden, and the reimbursement remains unchanged
Restrict Payout Release by Permission
Given a work order with finalized splits and only Approved reimbursements, and a user with permission key "payouts.release" When the user triggers a payout release for the work order Then only the vendor-allocated amounts and approved reimbursements are queued for payment, the payout status updates to Processing/Released, the split locks from further edits, and the operation is idempotent on retry (no duplicate payouts) Given a user without permission key "payouts.release" When the user attempts to release payouts Then the API returns 403 Forbidden, the UI shows no Release option, and payout status remains unchanged
Vendor Portal Scoped Financial View
Given a vendor authenticated to the vendor portal viewing a work order they are assigned to When the vendor opens the payout breakdown screen or calls the vendor breakdown API Then the response displays only: their allocated amounts by line item/percentage, their materials reimbursements, their receipts (files/links), and payment statuses for their allocations; it excludes tenant PII (name, email, phone), internal financials, other vendors’ names/amounts, and unrelated work orders Given the same vendor When they attempt to access another vendor’s allocation id, reimbursement id, or receipt URL Then the API returns 403/404, no sensitive data is leaked in payloads or error messages, and the access attempt is logged
Downloadable Receipts and Payout Breakdown for Vendors
Given a vendor authenticated to the vendor portal When the vendor clicks "Download Breakdown" for a work order Then a downloadable file (PDF and CSV options) is generated containing only that vendor’s allocations, approved reimbursements, line-item references, payment statuses, and totals that reconcile to the portal view; file metadata includes work order id, vendor id, and generation timestamp Given the same vendor When the vendor downloads individual reimbursement receipts or a ZIP of all their receipts Then only their own receipt files are included, links require authentication and expire, filenames contain no tenant PII, and any missing files are reported per-item without exposing unrelated data
Multi-Vendor Work Order Visibility Isolation
Given a work order with multiple vendors and helper techs with splits by line items and percentages When Vendor A views the portal breakdown or queries the vendor breakdown API Then Vendor A sees only Vendor A’s allocations and reimbursements with correct computed amounts, and sees no identifiers, names, or amounts for Vendor B or helper techs; totals reconcile to the sum of Vendor A’s items Given an update to the split that changes allocations across vendors When Vendor A refreshes the view Then Vendor A’s visible amounts update accordingly within the next page load/API call, and historical payout/approval records remain intact and unchanged

Compliance Gate

Block or route payouts until compliance items are valid: W-9, insurance certificates, licenses, and required permits. Auto-detect expirations, request updates via SMS/email, and attach documents to the job record. Protects owners from risk, prevents audit gaps, and saves assistants from chasing paperwork at the last minute.

Requirements

Vendor Compliance Profile & Document Vault
"As a property manager, I want a single place to store and view each vendor’s compliance documents so that I can quickly confirm eligibility and avoid audit issues."
Description

A centralized, structured repository on each vendor profile to store, version, and manage compliance artifacts (W-9, insurance certificates, licenses, permits) with metadata such as issue/expiration dates, coverage limits, and jurisdiction. Documents are encrypted at rest, permissioned by role, and automatically linked to related jobs so the correct versions are attached to the work order record. Supports multiple active owners/portfolios with configurable required items per owner, notes/tags, and quick status indicators (Compliant, Expiring Soon, Missing). Integrates with FixFlow’s vendor directory and job pages to present a single source of truth, reducing last-minute paperwork chasing and audit gaps.

Acceptance Criteria
Upload and version vendor documents with metadata
- Given I have edit permissions, when I upload a W-9, insurance certificate, license, or permit to a vendor profile, then I must enter document type, issuing authority/jurisdiction (for licenses/permits), issue date, expiration date (if applicable), coverage limits and policy number (for insurance), and owner/portfolio applicability before saving. - Given required metadata is incomplete or invalid (e.g., issue date after expiration date), when I attempt to save, then the save is blocked and field-level validation errors are displayed. - Given a document of the same type and portfolio already exists, when I upload a new file, then a new version is created with an incremented version number, prior versions become read-only, and the new version is marked Current. - Given a document has multiple versions, when I view version history, then I see version number, uploader, timestamp, and metadata differences, and I can download any version subject to permissions. - Given file constraints, when I upload, then only PDF/PNG/JPG up to 25 MB are accepted and files failing antivirus scan are rejected with an error message.
Auto-link correct document versions to job records
- Given a job is created or scheduled for a vendor under Owner A, when the job is saved, then the latest Current versions of Owner A-required documents that are valid on the job scheduled start date are attached to the job record. - Given a required document will expire before the job scheduled start date, when the job is saved, then the job displays a Compliance Risk: Expiring warning listing the document and expiration date. - Given a document attached to a job receives a newer version after job creation, when I open the job, then the attachments reflect the versions that were Current at the last schedule confirmation and I am prompted to update to the newest versions with a visible change summary. - Given a job is reassigned to a different owner/portfolio, when I save the reassignment, then the attached documents are recalculated against the new owner requirements and updated accordingly.
Enforce role-based permissions on vendor document vault
- Given my role is Owner Admin or Portfolio Manager for Owner A, when I access a vendor's document vault scoped to Owner A, then I can view, download, upload, edit metadata, and create new versions. - Given my role is Assistant, when I access the vault, then I can view and upload new versions but cannot delete versions or change required-items configuration. - Given my role is Vendor Contact accessing via a secure link, when I open the link, then I can view only my requested items and upload replacements; I cannot view other owners' documents or metadata. - Given I lack access to Owner A, when I attempt to access Owner A-specific documents, then a 403 error is returned and no document content or metadata is exposed. - Given any document view or download occurs, when it happens, then an audit record is created with timestamp, user, IP, document ID, and version.
Portfolio-specific required items and compliance status indicators
- Given Owner A has configured required items (e.g., W-9, GL insurance minimum $1,000,000, state license), when I view Vendor V under Owner A, then I see a per-owner compliance badge: Compliant, Expiring Soon (<=30 days), or Missing based on presence, validity dates, and coverage thresholds. - Given an insurance document's coverage is below the configured minimum, when the document is saved, then the vendor is marked Non-Compliant for Owner A with reason Coverage below minimum. - Given I filter the vendor directory by Compliant for Owner A, when results load, then only vendors with Compliant status for Owner A are returned. - Given Owner A updates required items (e.g., adds a permit), when the change is saved, then all affected vendors' compliance statuses are recalculated within 5 minutes.
Auto-detect expirations and request updates
- Given documents are expiring within 30 days or missing for Owner A, when the nightly compliance job runs, then SMS and email requests are sent to vendor contacts with a secure upload link and a list of specific items per owner. - Given a vendor uploads via the secure link, when the upload succeeds, then the corresponding item status updates immediately, related job warnings clear if now compliant, and a confirmation SMS/email is sent. - Given an SMS/email bounces or the contact has opted out, when the job runs, then further messages are suppressed and the contact is flagged as unreachable with an alert created for the portfolio manager. - Given the reminder cadence is configured to 3 attempts over 10 days, when items remain outstanding, then reminders are sent on schedule and a final escalation notification is sent to the owner admin.
Encryption at rest and data protection
- Given any document is stored, when it is at rest, then it is encrypted using AES-256 with KMS-managed keys and encryption status is visible on an admin diagnostics page. - Given access to underlying storage outside the application, when a stored file is inspected, then its contents are encrypted and not human-readable. - Given keys are rotated per policy, when rotation occurs, then documents remain accessible without re-upload and rotation events are recorded in the security audit log. - Given a backup is restored to a non-production environment, when a document is accessed without proper roles, then access is denied and the denied attempt is logged.
Searchability with tags and notes
- Given a document version has tags and notes, when I search by tag, document type, owner, jurisdiction, or expiration window, then results return within 2 seconds for up to 10,000 documents. - Given I add or edit tags or notes on a document version, when I save, then changes are immediately searchable and recorded in the audit trail. - Given I filter a vendor's documents by Expiring Soon and tag COI, when applied, then only insurance certificates expiring within 30 days with the COI tag are shown.
Expiration Auto-Detection & Renewal Reminders
"As an operations coordinator, I want the system to alert vendors and my team before documents expire so that we don’t scramble at the last minute or risk non-compliant work."
Description

Automatic detection and tracking of document expiration dates using extracted metadata and user-entered fields. A background scheduler flags upcoming expirations and triggers configurable reminder cadences (e.g., 30/14/7 days, day-of, expired) via SMS and email to vendors and internal staff. The system updates status badges across the vendor list, job pages, and calendar, and can optionally pause new assignments or payouts when items are expired. Policies are configurable per owner/portfolio, with timezone-aware scheduling and full notification logs.

Acceptance Criteria
Auto-Detect Expiration from Uploaded Compliance Documents
Given a supported compliance document (W-9, COI, license, permit) is uploaded as PDF or image, When the system processes the file, Then it extracts the nearest future expiration date from metadata or text and saves it to the document record. And Given multiple date candidates exist, When selecting an expiration date, Then the system chooses the earliest future date relative to upload time. And Given extraction fails or no future date is found, When the user is prompted, Then the user can enter a valid expiration date in DD/MM/YYYY, MM/DD/YYYY, or ISO 8601, and the system normalizes it to the portfolio timezone. And Then the document status is set to Valid, Expiring (within configured threshold), or Expired accordingly.
Timezone-Aware Reminder Scheduler and Cadence
Given a portfolio with a configured timezone and reminder cadence (e.g., 30, 14, 7, 0, and expired) and a daily send time, When a document has an expiration date, Then reminders are scheduled at the configured local time on each cadence day relative to the expiration date, respecting daylight saving transitions. And When the expiration date or cadence configuration changes, Then pending reminders are rescheduled within 1 minute and duplicates are not sent. And When a document is marked renewed or reminders are paused by policy, Then all future reminders for that document are canceled. And Then the scheduler records a job per reminder with next-run timestamps stored in UTC and portfolio local time.
Multi-Channel Reminder Delivery to Vendors and Staff
Given a scheduled reminder fires, When the vendor has a verified email and SMS number and the portfolio policy targets vendor and selected internal roles, Then the system sends one email and one SMS to the vendor and one email to each targeted internal role. And When any channel is missing or unverified, Then the system skips that channel, logs the skip reason, and still sends via available channels. And Then messages include document type, current status, expiration date in portfolio local time, portfolio/owner name, and a secure link to update/attach the document. And Then delivery outcomes (queued, sent, failed with provider code) are recorded per recipient and channel.
Compliance Status Badges Update Across Vendor, Job, and Calendar
Given a document status changes to Valid, Expiring, or Expired, When the change is committed, Then the vendor list, vendor profile, job pages referencing the vendor, and calendar items display badges reflecting the new status within 60 seconds. And Then badge colors and tooltips are consistent across surfaces (green=Valid, yellow=Expiring, red=Expired) and tooltips show which document(s) and expiration dates. And When the user interacts with a badge, Then a details panel shows document types, expiration dates, and links to view or replace documents.
Gating New Assignments and Payouts on Expired Items
Given portfolio policy "Gate on Expired" is enabled for a document type, When that document for a vendor is Expired, Then attempts to create new job assignments for that vendor are blocked with an inline error naming the missing/expired item. And When a payout is attempted for a job linked to that vendor while the item is Expired, Then the payout is blocked unless the policy allows overrides. And Given overrides are allowed, When an authorized user provides a reason and selects an override duration, Then the system records the override, unblocks the action during the window, and re-enables gating after the override expires. And Then all gating and override events are logged with actor, timestamp, portfolio/owner, and affected jobs.
Renewal Document Upload Auto-Validates and Resumes Operations
Given a document is Expired or Expiring, When a new version with a future expiration date is uploaded and verified, Then the document status updates to Valid, future reminders for the previous version are canceled, and gating is lifted immediately. And Then the vendor’s badges refresh across vendor list, job pages, and calendar within 60 seconds. And Then the system maintains version history with prior expiration dates, uploaders, and timestamps.
Notification Logs and Audit Trail
Given any reminder, gating block, override, or status change occurs, When the event is created, Then the system writes a log entry containing portfolio and vendor identifiers, document type, actor (system or user), channel (if applicable), timestamp in UTC and portfolio local time, and outcome. And Then users can filter logs by date range, portfolio/owner, vendor, document type, channel, and outcome, and export results to CSV. And Then viewing a document record shows its full reminder history and delivery statuses.
Payout Hold & Routing Rules Engine
"As an owner, I want payouts to be automatically held when a vendor isn’t compliant so that I’m protected from financial and regulatory risk."
Description

A rules-driven control layer that blocks, releases, or reroutes payouts based on compliance status at time of approval. Rules can enforce holds for missing W-9s, expired insurance, or invalid licenses, and optionally route funds to a holding account while notifying stakeholders with reason codes. Integrates with FixFlow’s payment provider, job approval flow, and webhooks for real-time checks and idempotent decisions. Supports per-owner policies, override with role-based approval and reason capture, and clear UI messaging on the job and vendor record to explain payout status.

Acceptance Criteria
Hold Payout When W-9 Missing
Given a job is approved for payout and the vendor has no valid W-9 on file at approval time When the rules engine evaluates compliance for the payout Then the payout status is set to "On Hold - Compliance" And the reason codes include "MISSING_W9" And no funds are disbursed to the vendor account And an email and SMS notification are sent to the vendor and job approver within 1 minute including the job ID, vendor name, and reason codes, plus a link to upload a W-9 And an immutable audit log entry is created with evaluation timestamp, job ID, vendor ID, reason codes, and an idempotency key
Route Funds To Holding Account On Expired Insurance
Given a job is approved for payout and the vendor’s insurance certificate is expired as of approval time When the rules engine evaluates the payout Then funds are routed to the designated holding account "Compliance_Hold" via the payment provider And no transfer to the vendor account is created And the job record shows payout status "Rerouted - Compliance Hold" with reason code "INSURANCE_EXPIRED" and the last known expiration date And stakeholders (approver, vendor) receive a notification with the reason code and instructions to upload a new certificate And when a valid certificate is uploaded and verified, the system automatically releases the held funds to the vendor within 10 minutes, updating the audit log and notifications accordingly
Per-Owner Policy Enforcement
Given Owner A requires a valid business license for payouts and Owner B does not And the same vendor has one job for Owner A and one job for Owner B When both jobs are approved Then the payout for Owner A’s job is held with reason code "LICENSE_MISSING" And the payout for Owner B’s job proceeds without a hold And the audit log records the owner policy ID, evaluated rules, and decision per job And the job UI displays a policy badge indicating the owner-specific requirement that triggered the decision
Override With Role-Based Approval and Reason Capture
Given a payout is on hold due to compliance reasons And a user with an authorized role (e.g., Compliance Manager) attempts an override When the user initiates the override Then the system requires selection of an override reason from a predefined list and a justification between 15 and 500 characters And on confirmation, the payout is released (or rerouted) according to the selected override action And the audit log captures approver user ID, role, timestamp, original reason codes, override reason, justification, and links to affected records And notifications to stakeholders indicate the payout was overridden and by whom And users without sufficient role cannot see or execute the override action
Idempotent Decisioning With Payment Provider And Webhooks
Given the payment provider sends duplicate webhook events or retries for the same payout When the rules engine receives multiple events with the same provider reference Then only one compliance decision is persisted and applied using an idempotency key And subsequent duplicate events are logged without changing payout state And the system responds to each webhook with 2xx within 2 seconds And FixFlow and the payment provider show consistent payout status after processing
Clear UI Messaging On Job And Vendor Records
Given a payout is blocked or rerouted by the rules engine When a user views the job record or the vendor profile Then a visible status pill displays "On Hold - Compliance" or "Rerouted - Compliance Hold" And a message lists all missing/expired items with friendly labels and machine-readable reason codes And links are provided to upload required documents or open the compliance center And the vendor compliance panel shows current status, last updated timestamp, and who made changes And the status pill includes an aria-label and meets WCAG AA color contrast (>= 4.5:1)
Multiple Compliance Issues Aggregation And Notification
Given a vendor has multiple compliance issues (e.g., missing W-9 and expired insurance) When the rules engine evaluates a payout Then all applicable reason codes are aggregated into a single decision result And the UI and notifications display all reasons in priority order based on configured policy precedence And the system suppresses duplicate notifications for the same job and issue set within a 15-minute window And resolving one issue does not release the payout until all required issues are resolved or an authorized override occurs
Secure Document Collection via SMS/Email Links
"As a vendor, I want an easy, mobile-friendly way to submit my compliance documents so that I can get scheduled and paid without hassle."
Description

One-click, expiring, identity-bound links sent via SMS or email that let vendors upload or capture photos/scans of required documents without logging in. The flow guides the vendor to select the document type, enter required metadata (e.g., expiration date), and optionally fill and e-sign standardized forms (e.g., W-9). Files are virus-scanned, stored securely, and instantly associated with the vendor and pending jobs. Supports resending, language localization, and two-way SMS confirmation to reduce friction and speed compliance updates.

Acceptance Criteria
Expiring Identity-Bound Upload Link via SMS/Email
Given an admin selects a vendor and required document type When they click "Request Document" Then the system generates a single-use, identity-bound link sent via the chosen channel (SMS or email) And the link requires OTP verification sent to the same channel on first access And the token expires after the configured TTL (default 72 hours) or immediately after first successful submission And accessing an expired or already-used link shows a 410 state page with guidance to request a new link And five consecutive invalid OTP attempts lock the token and notify the requester And token identifiers are unguessable (≥128-bit entropy) and access attempts are rate-limited
Document Upload with Metadata Capture
Given a vendor opens the link and completes OTP verification When they choose "Upload Document" Then they can capture via camera or upload a file (PDF, JPG, PNG, HEIC) up to 25 MB And HEIC images are converted to JPG without loss of readable quality And the vendor must select the document type and supply required metadata fields for that type before submission And expiration date fields must be valid calendar dates not in the past And uploads show progress and support resumable transfer for files larger than 5 MB after transient network loss up to 10 minutes And on successful upload, the vendor receives on-screen and SMS/email confirmation with a document reference ID
W-9 E‑Sign Flow Without Login
Given a vendor selects W-9 When they choose "Fill and E‑Sign" Then the form enforces validation for TIN (SSN/EIN), legal name, business classification, and address And the vendor provides explicit e-sign consent and applies an e-signature with timestamp and IP address captured And upon completion, a flattened, non-editable PDF is generated and stored, along with machine-readable form data And the vendor receives a copy via their original channel, and the system records an audit trail entry referencing the job and vendor
Virus Scan and Secure Storage
Given a file is uploaded When anti-malware scanning completes Then files flagged as malicious are rejected with an explanatory message and are not persisted And clean files are accepted, encrypted in transit (TLS 1.2+) and at rest (AES-256), and stored with a SHA-256 checksum And access to stored documents is restricted to authorized roles via access control checks And all scan results and access events are written to an immutable audit log
Auto-Association to Vendor and Pending Jobs
Given a clean, valid document with required metadata When it is accepted Then it is associated to the vendor’s profile and linked to all pending jobs that require that document type And if the document satisfies the compliance requirement, the linked jobs’ compliance status updates within 5 seconds to "Satisfied" And if the document expires in less than 30 days, the system schedules renewal reminders and flags the record as "Expiring" And any job or payout previously blocked by this requirement is unblocked per policy and recorded in the audit log
Resend and Link Lifecycle Management
Given an admin views a vendor’s compliance request When they click "Resend" Then the previous token is revoked and a new token is issued and delivered via the selected channel And the system records delivery status (queued, sent, failed) and last access timestamp for the link And at most one active token per vendor per document type exists at any time And vendors accessing a revoked link are shown a message with a "Request New Link" action that notifies the requester and rate-limits to 1 request per 10 minutes
Localization and Two-Way SMS Confirmation
Given a vendor’s preferred language is set or inferred from device When they open the link Then the UI and instructions display in that language (minimum: English and Spanish) And SMS and email templates are localized accordingly and include required compliance keywords and opt-out instructions for SMS And when the vendor replies "DONE" via SMS after submission, the system confirms receipt with the document reference and short link And when the vendor texts "HELP" or "STOP", the system responds per carrier guidelines and logs consent state changes
Automated Document Validation (OCR + Rules)
"As a compliance assistant, I want the system to auto-validate uploaded documents so that I spend less time on manual checks and reduce human error."
Description

Opt-in automated checks that parse uploaded documents using OCR and templates to extract key fields (TIN, business name, policy number, coverage limits, expiration date, license number) and validate them against configurable rules. Supports TIN matching via IRS API where available, policy coverage threshold checks, jurisdiction-specific license formats, and confidence scoring with a manual review queue for low-confidence cases. Results update the vendor’s compliance status, flag discrepancies, and prompt for resubmission when required while safeguarding PII and providing an audit trail of validations.

Acceptance Criteria
IRS TIN Matching for Vendor W‑9
Given the organization has enabled automated validation and IRS TIN matching And a vendor uploads a W‑9 to their profile When OCR extracts Legal Name and TIN with confidence >= 0.90 Then the system sends a TIN/Name match request to the IRS API And if the IRS response is Match, the W‑9 compliance item is set to Valid and the vendor compliance status is updated within 5 seconds And if the response is No Match, the item is set to Failed, a discrepancy flag is created, and an SMS/email resubmission request is sent within 1 minute And if the IRS API is unavailable or times out, the item status is Pending Verification and the system retries up to 3 times with exponential backoff, logging each attempt And the audit log records request/response metadata without storing full TIN, the validator version, and the acting system identity And on all UI surfaces the TIN is masked except the last 4 digits
Insurance Certificate Validation with Coverage Thresholds and Expiration Detection
Given automated validation is enabled and minimum coverage thresholds are configured And a vendor uploads a Certificate of Insurance (COI) When OCR extracts policy number, coverage limits, additional insured indicator, and expiration date with field‑level confidence scores Then if all required fields have confidence >= 0.85, the rule engine evaluates thresholds And if thresholds are met and expiration date >= today, the COI item is marked Valid and a renewal reminder is scheduled 30 days before expiration via SMS/email And if any threshold is not met or the policy is expired, the COI item is marked Failed and a resubmission request detailing deficiencies is sent within 1 minute And if any required field confidence < 0.85, the item is routed to Manual Review and no automated pass/fail is applied And the uploaded COI is attached to both the vendor record and the originating job record
Jurisdiction‑Specific License Format and Expiration Validation
Given automated validation is enabled and the vendor’s service jurisdiction is known And the jurisdiction has a configured license format pattern and expiry rule When the vendor uploads a license document And OCR extracts license number and expiration date Then if the license number matches the jurisdiction format and expiration date >= today, the license item is set to Valid And if the format does not match or expiration < today, the item is set to Failed and a resubmission request is sent And if OCR confidence for license number or date < 0.85, the item is sent to Manual Review And when a jurisdiction rule is changed, all affected vendors are revalidated within 2 hours and statuses updated with audit entries
Opt‑In Controls and Configurable Rule Sets
Given an administrator can enable or disable Automated Document Validation per organization and per vendor category And required documents and threshold rules are configurable and versioned When automation is disabled for a vendor category Then no OCR or external validation is run on new uploads for vendors in that category and items remain Unreviewed And when automation is enabled, new uploads trigger OCR and rule evaluation And any rules change creates a new ruleset version with an effective‑from timestamp And validations executed after the change reference the active ruleset version and record it in the audit trail And administrators can run a dry‑run on a sample document to preview pass/fail outcomes without updating compliance status
Manual Review Queue for Low‑Confidence or Conflicting Extractions
Given an item is routed to Manual Review due to low confidence or conflicting extracted values When a reviewer with the Compliance Reviewer role opens the task Then the reviewer sees the original document, extracted fields with confidence scores, and redacted PII where applicable And the reviewer can correct fields and mark the item Approved (Valid) or Rejected (Failed) with a required reason code And all actions are timestamped and attributed in the audit log And upon approval or rejection, the vendor compliance status and any blocked payouts are updated within 5 seconds And the queue supports assignment, filtering by item type/status, and SLA aging timestamps
PII Safeguards and Access Logging
Given documents may contain PII such as TINs When stored, Then documents and extracted PII are encrypted at rest And when displayed, TINs are masked except the last 4 digits; full TIN is viewable only by users with PII Access permission And all full‑PII views are logged with user, timestamp, and purpose And audit logs are immutable and exportable by administrators And deletion requests purge PII from derived indexes while retaining non‑PII audit metadata
Compliance Status Propagation and Payout Blocking
Given the Compliance Gate is enabled to block or route payouts on non‑compliant vendors And a vendor’s compliance item status changes to Failed or Expired When a job attempts to initiate payout Then the payout is blocked or routed and the user sees a clear reason and link to resolve And when the item returns to Valid, payouts are unblocked automatically And all block/unblock events are recorded in the audit trail and exposed in the job timeline
Compliance Status in Scheduling & Job Flow
"As a scheduler, I want to see and enforce vendor compliance at the moment of assignment so that I don’t create jobs that later get blocked at payout."
Description

Real-time compliance checks integrated into scheduling, dispatch, job approval, and completion workflows. The calendar and vendor picker surface compliance badges and block selection of non-compliant vendors based on policy, offering compliant alternatives when available. Job pages display clear banners and next steps for resolving compliance holds, and generate tasks to collect missing items. Overrides require appropriate role-based approvals with reasons and are fully logged for auditability.

Acceptance Criteria
Vendor Picker Blocks Non-Compliant Vendors and Suggests Alternatives
Given an owner policy that disallows scheduling non-compliant vendors When a user opens the vendor picker for a job Then vendors with any missing or expired W-9, insurance, licenses, or permits are disabled with a red "Non-Compliant" badge and a tooltip listing missing items Given the vendor list contains at least one compliant vendor matching the job type and time window When the user attempts to select a non-compliant vendor Then the selection is blocked and a modal lists at least one compliant alternative with availability and contact info Given compliant alternatives exist When the modal is displayed Then the user can select an alternative and the vendor is assigned to the job successfully Given no compliant alternatives exist When the user attempts to proceed Then the system displays "No compliant alternatives available" and offers to send a compliance request to the original vendor
Calendar Shows Compliance Badges and Prevents Non-Compliant Scheduling
Given the shared calendar view is open When a vendor becomes non-compliant (e.g., insurance expires) Then the vendor's row and time slots show a red compliance badge within 5 seconds without page refresh Given a scheduler drags a job onto a non-compliant vendor's time slot When the job is dropped Then the drop is rejected with an inline message "Cannot schedule: vendor non-compliant per policy" Given portfolio policy is set to "Warn-only" When scheduling a non-compliant vendor Then the system allows scheduling but adds a yellow warning banner to the job and requires acknowledgment before saving
Job Page Compliance Hold Banner and Task Generation
Given a job page for a vendor who is non-compliant When the page loads Then a top banner displays "Compliance hold" listing specific missing items and primary CTAs: "Request W-9", "Request Insurance", "Upload Document" Given the user clicks "Request Insurance" and confirms When the action is processed Then an SMS and email are sent to the vendor with a unique upload link, and a task "Collect Insurance Certificate" is created, assigned to the assistant role, due within 2 business days Given the vendor uploads the required document via the link When the upload completes and passes basic validation (file type, required fields) Then the document is attached to the job record under Documents with metadata (type, uploaded by, timestamp), and the task auto-completes
Auto-Detect Expirations and Request Updates
Given an insurance certificate or license with an expiration date When the date is within 30 days Then the vendor status changes to "Expiring Soon" (yellow badge) and notifications are queued to the vendor to renew via SMS and email Given an item passes its expiration date When the nightly compliance check runs or the record is viewed Then the vendor becomes "Non-Compliant" (red badge) and active jobs for that vendor are placed on compliance hold per policy Given a vendor submits a new document using the upload link When the document passes validation Then the vendor's compliance status updates to "Compliant" within 5 seconds and all related holds are lifted automatically
Role-Based Override with Required Reason and Audit Log
Given a user without override permission When attempting to override a compliance hold Then the action is blocked with the message "Override requires Manager or Owner role" Given a Manager or Owner initiates an override When proceeding Then a dialog requires selecting a reason or entering at least 10 characters of justification, and on confirm the system records user ID, role, reason, timestamp, and scope (job/vendor) in the immutable audit log Given an override is active on a job When viewing the job Then a banner displays "Compliance overridden by [name] on [date]" with the reason, and a link to view the audit entry is available in Job History
Dispatch and Payout Gates Enforce Compliance
Given a dispatcher attempts to send a work order to a non-compliant vendor under a "Block" policy When clicking Dispatch Then dispatch is prevented and the UI prompts to resolve compliance or send document requests Given a job is marked complete for a vendor that is non-compliant at completion When attempting to approve payout Then payout is blocked and a checklist shows outstanding compliance items required to release funds Given the vendor becomes compliant after submitting documents When payout approval is retried Then payout is allowed and the action is logged with a reference to the compliance resolution event
Real-Time Updates, Filters, and Error Handling
Given the vendor list and picker include a "Compliant only" filter When the filter is toggled on Then only vendors with all required items valid are shown in the picker and calendar within 2 seconds Given the compliance service is temporarily unavailable When a compliance check is requested Then the UI shows a non-blocking error "Compliance status unavailable" and defaults to safe behavior per policy (e.g., block scheduling) while queuing a retry Given a vendor's status changes during scheduling due to a concurrent update When the user saves the assignment Then server-side validation re-checks compliance and prevents save if the vendor is now non-compliant, showing a clear error message
Audit Trail & Compliance Reporting
"As a portfolio owner, I want clear reports and an evidence trail for compliance actions so that I can pass audits and demonstrate due diligence."
Description

Comprehensive, immutable logs capturing document uploads, edits, validations, expirations, reminders, overrides, and payout decisions with timestamps and actors. Provides owner-ready reports by portfolio, vendor, and time range, plus an evidence pack generator that bundles current documents and event history per job or vendor for audits. Exports to CSV/PDF, role-based access controls, and data retention policies ensure transparency, compliance readiness, and minimal administrative overhead.

Acceptance Criteria
Immutable Audit Log Entries for Compliance Events
Given any compliance-related action occurs (upload, edit, validation, expiration detection, reminder send/fail, override, payout decision), When the action is committed, Then a single immutable audit entry is recorded containing: eventId (UUID), eventType, occurredAt (UTC ISO-8601), actorId, actorRole, targetType (Vendor|Job|Document|Payout), targetId, correlationId (jobId or payoutId), and for edits, before/after fingerprints. Given an authenticated user attempts to edit or delete an existing audit entry, When the request is made, Then the system denies the change with HTTP 403 and records an AuditAccessDenied event referencing the targeted eventId. Given an administrator needs to correct erroneous data, When a correction is made, Then the system appends a new Correction event that references the original eventId, includes reason and corrected fields, and leaves the original entry unchanged. Then audit entries are ordered by occurredAt and eventId, and retrieval returns results consistently with pagination for up to 10,000 events per query.
Expiration Detection and Reminder Event Logging
Given a document with expiryDate within 30 days, When the scheduler runs at 02:00 UTC daily, Then an ExpirationDetected event is created once per document per day until the document is renewed or archived. Given reminders are configured for SMS and email, When a reminder is sent, Then a ReminderSent event is recorded with channel, recipient, templateId, providerMessageId, and deliveryStatus; If the provider reports failure or bounce, Then a ReminderFailed event is recorded within 5 minutes including failure code and description. Given a document is updated with a new expiryDate and passes validation, When the update is saved, Then a DocumentValidated event is recorded and subsequent ExpirationDetected and ReminderSent events for the prior version cease.
Payout Decision Traceability Under Compliance Gate
Given a payout is requested for a job, When compliance evaluation runs, Then a PayoutEvaluated event is recorded with decision (Block|Route|Release), ruleSetVersion, requirementsSnapshot (W-9, insurance, license, permits with statuses and docIds), evaluatedAt, and evaluator (system or userId). If decision is Block and an authorized role issues an override, When the override is approved, Then an OverrideApplied event is recorded with approverId, approverRole, justification (minimum 20 characters), scope (single payout|vendor|portfolio), and expiration timestamp, and the override links to payoutId and vendorId. Then funds cannot transition to Released state unless a PayoutEvaluated event exists for the payoutId within the last 24 hours; otherwise the transition is rejected and logged as PayoutReleaseRejected.
Owner-Ready Compliance Reports by Portfolio/Vendor/Time Range
Given a user with Owner or Portfolio Manager role selects portfolio(s), optional vendor(s), and a time range, When Generate Report is requested, Then the system returns a report within 10 seconds for up to 50,000 events including summary metrics (active vendors, compliant vendors, blocked payouts, expiring documents by type) and a detail table. Then the detail table includes columns: vendorId, vendorName, portfolioId, jobId, eventType, occurredAt, decision, documentType, status, actorRole, and all rows reflect the selected filters. When Export is requested, Then CSV and PDF downloads are available; CSV is UTF-8 with headers and RFC4180 quoting and matches the on-screen row count and totals; PDF paginates and prints filters and generation timestamp.
Evidence Pack Generation per Job or Vendor
Given a jobId or vendorId and a time range are selected, When Generate Evidence Pack is requested, Then within 60 seconds a ZIP is produced containing: PDF summary, CSV audit log for the scope, and copies of current compliance documents (W-9, insurance, licenses, permits) with filenames including docId and version. Then the ZIP includes a manifest.json listing each file with SHA-256 checksum, createdAt, generator version, and scope; the pack is retained for 7 days, access-controlled to Owner, Portfolio Manager, and Assistant roles, and an EvidencePackGenerated event is recorded with download URL and expiry. When no documents exist for the scope, Then the pack still includes the audit log and summary and clearly indicates missing document types.
Role-Based Access Controls and Redaction
Given roles Owner, Portfolio Manager, Assistant, Vendor, and Read-Only Auditor, When accessing audit logs and reports, Then permissions are enforced: Vendor can view only their own vendor-scoped logs and documents; Auditor has read-only access across assigned portfolios; other roles follow organization policy; attempts outside scope return HTTP 403 and are logged as AuditAccessDenied. Then Vendor views and exports redact PII (SSN/TIN masked except last 4, emails partially masked, phone partially masked) and hide documents not permitted; Auditor exports exclude raw document files and include only metadata unless explicitly granted. When a role permission is changed, Then a RoleChanged event is recorded with actor, subject, before/after roles, and the change takes effect for new sessions within 1 minute.
Data Retention and Legal Hold Enforcement
Given retentionPolicy.auditLogsDays and retentionPolicy.documentsDays are configured, When the nightly retention job runs, Then audit entries older than the configured period are archived to cold storage and removed from primary indexes; a RetentionPurge event is recorded with counts purged per type, duration, and success status, and the job completes within 30 minutes. When a legal hold is applied to a vendor or portfolio, Then related audit entries and documents are not purged while the hold is active; hold create/update/remove actions are logged with reason, actor, and scope. Then authorized users can still query and export archived entries; for queries returning up to 100,000 entries, archived retrieval completes within 15 seconds and includes an Archived=true flag in exports.

Dispute Resolve

One-tap flow to pause or reduce a payout when issues arise—missing scope, damage claims, or tenant rejection. Collect evidence, propose adjustments, and timebox responses with transparent status for both sides. Keeps emotions out, keeps projects moving, and preserves airtight documentation for chargebacks or board reviews.

Requirements

One-Tap Payout Hold & Reduction
"As a property manager, I want to place an immediate hold or partial reduction on a vendor payout when work is disputed so that funds are protected while we investigate."
Description

Enable managers to instantly place a full or partial hold on a vendor payout from the job card with a single action. Include required reason codes, optional notes, and a confirmation prompt. The hold is non-destructive, visibly flags the job and payout line, and syncs the hold state with the payments processor. Display the held amount, remaining payable balance, and next steps to all parties with appropriate visibility. Maintain an audit trail of who held what, when, and why, and prevent payout release until resolution or override with proper permissions.

Acceptance Criteria
One-Tap Full Payout Hold from Job Card
Given a manager with Hold permissions is viewing a job card with an outstanding payable amount And the payout is not already on hold When the manager taps “Hold payout” and selects “Full hold” And selects a required reason code from the predefined list And optionally enters a note up to 1,000 characters And confirms via the confirmation prompt Then the system records a non-destructive hold on 100% of the payout amount And displays an “On Hold” badge on the job card and payout line And shows Held Amount equal to the payable total and Remaining Payable as 0.00 And disables duplicate submission via idempotency so double-taps do not create multiple holds And confirms no invoice or line-item values are altered
One-Tap Partial Hold with Amount Validation
Given a manager with Hold permissions is viewing a job card with a payable balance of X When the manager taps “Hold payout” and selects “Partial hold” And enters a hold amount in currency or percent Then the system validates that the hold amount ≥ 0.01, ≤ X, and rounds to 2 decimal places And if percent is provided, converts to currency using the payable balance and rounds to 2 decimals And if validation fails, prevents confirmation and displays an inline error explaining the rule violated And upon confirmation, the system records a non-destructive partial hold for the exact computed amount And updates the UI to show Held Amount and Remaining Payable = X − Held Amount And the hold summary is visible on the job card and payout line
Hold State Sync with Payments Processor and Retry Handling
Given the platform is connected to the payments processor And a hold (full or partial) is confirmed When the system sends the hold to the processor with a unique idempotency key Then on 2xx response the hold is marked Synced with processor_hold_id stored And on 4xx/5xx response the system retries up to 3 times with exponential backoff And if all retries fail, the hold remains effective locally but is marked Sync Pending with a visible banner and a “Retry Sync” action And all request/response metadata (status code, error message, timestamp) is logged in the audit trail And a successful later retry updates the status to Synced and clears the banner
Role-Based Visibility of Hold Details and Next Steps
Given a hold exists on a payout for a job When a user with Manager role views the job card Then they see held amount, remaining payable, reason code, notes, timestamp, and who placed the hold When a user with Vendor role views the job or payout Then they see held amount, remaining payable, reason code label, and next-step guidance (e.g., “Submit evidence” or “Propose adjustment”) And they do not see internal notes marked private When a user with Tenant role views the job Then they see a neutral status banner indicating the payout is on hold and general next steps (no internal details) And unauthorized users see no hold details And all views consistently display currency formatting and last-updated time
Immutable Audit Trail for Hold, Adjust, Release, and Override
Given audit logging is enabled When a user places, modifies, releases, or overrides a hold Then the system appends an immutable audit record including: event type, actor id and role, job id, payout id, reason code, notes (if any), previous state, new state, amount(s), timestamps (ISO 8601 UTC), and processor sync status And audit records cannot be edited or deleted; corrections require a new event And managers can view the audit timeline on the job and export it to CSV/PDF And each record includes a unique event ID and is ordered chronologically
Hold Blocks Payout Release and Controlled Override
Given a payout has an active hold When an automated or manual payout release is attempted Then the release is blocked and the UI indicates “Blocked by Hold” with a link to view details And background jobs skip over held payouts When a user with the Dispute Override permission attempts to override Then a confirmation prompt requires selection of an override reason and optional note And upon confirmation, the hold is cleared, the processor is updated, and an audit record is created And without the permission, the override action is hidden or disabled And when a resolution is recorded (outside this flow), only the non-held amount (if partial) is eligible for release until the hold is cleared
Concurrency and Idempotency Prevent Duplicate or Excessive Holds
Given two managers attempt to place holds on the same payout concurrently When the first hold is processed Then the system locks the payout hold state until completion And the second request either updates the existing hold via a controlled modify flow or is rejected with a clear message And the sum of all active partial holds never exceeds the payable amount; attempts beyond the cap are blocked with an error And repeated submissions using the same idempotency key do not create additional holds
Evidence Intake & Line-Item Tagging
"As a property manager, I want to collect and organize evidence from all parties so that decisions are fact-based and documented."
Description

Provide a guided evidence collection flow that accepts photos, videos, documents, and voice notes via web upload, mobile camera, and SMS/MMS ingestion. Automatically timestamp, attribute to the submitting party, and associate items to the relevant work order and specific line items. Support annotations, captions, and reason-code tagging, with virus scanning, file-size/type validation, and secure storage. Allow both sides (and optionally tenants) to contribute evidence with role-based visibility. Present evidence in a structured gallery and inline with disputed line items to keep reviews fast and organized.

Acceptance Criteria
Web/Mobile Upload Validation & Virus Scan
Given a user with access to a dispute evidence flow When they select files via web upload or mobile camera Then only allowed types (jpg,jpeg,png,heic,mp4,mov,pdf,doc,docx,mp3,m4a,wav) are accepted and disallowed types are blocked with an inline error Given a selected file exceeds the configured size limit defaults (images<=25MB, videos<=200MB, docs<=50MB, audio<=25MB) When the user attempts to upload Then the upload is prevented and the limit is displayed in the error Given a file passes type and size validation When the upload begins Then a progress indicator is shown and the user can cancel the upload Given an upload completes When the virus scan runs Then the evidence does not become visible until the scan returns clean; if malware is detected the item is quarantined, the uploader is notified, and the item is not attachable to the dispute
SMS/MMS Evidence Ingestion & Attribution
Given a registered vendor or tenant sends an MMS to the FixFlow evidence number with media attached and either a work-order ID in the text or a known phone number When the MMS is received Then each attachment is stored, timestamped, attributed to the sender party, and associated to the identified open work order Given multiple active work orders match the sender When an MMS arrives without an explicit work-order ID Then the sender receives an SMS asking to choose a work order and the attachments are queued until a selection is made Given an unrecognized phone number sends an MMS When received Then an SMS reply explains that registration is required and no evidence is stored Given up to 10 attachments are included in one MMS When processed Then each attachment becomes a separate evidence item preserving original filename and media type
Timestamping, Attribution, and Line-Item Tagging
Given evidence is added from any channel When it is stored Then the system records created_at in UTC (to the second), the uploader identifier (userId or phone), and the uploader role (landlord/manager, vendor, tenant) Given evidence is created within a dispute context When it is saved Then it must be associated to exactly one work order and optionally one or more line items; if initiated from a specific line item, that line item is preselected and required Given required associations are missing When the user attempts to save Then a blocking validation requires selection of a work order and at least one line item when the context demands it Given evidence is associated to line items When viewed inline on a line item Then the related line-item chips are displayed and link to the corresponding detail views
Annotations, Captions, and Reason-Code Tagging with Audit Trail
Given an image, video frame, or document preview When the user enters annotate mode Then they can add non-destructive overlays (arrows, boxes, text) and save an annotated version linked to the original Given an annotated version is saved When viewed Then the original file remains downloadable unchanged and an audit trail records who annotated and when Given evidence creation or edit for a disputed item When saving Then a caption up to 2000 characters and at least one reason code from a configurable list must be provided; multi-select is allowed Given an admin updates the reason-code list When saved Then the new codes appear in the tagging picker within 1 minute and the change is recorded in audit logs
Role-Based Visibility and Access Controls
Given landlord/manager, vendor, and optional tenant roles exist When evidence is created Then default visibility includes landlord/manager and vendor; tenant visibility is off unless explicitly enabled per item Given a user without visibility permission When attempting to access evidence via UI or direct URL Then the request is denied with HTTP 403 and the attempt is audit-logged with userId, IP, and timestamp Given the creator marks an evidence item as private to their side When saved Then only their side and admins can view until visibility is changed; visibility changes are audit-logged Given a tenant-facing link is accessed When opened Then only evidence items marked tenant-visible are shown
Structured Evidence Gallery and Inline Line-Item View
Given a dispute with associated evidence When the gallery loads Then items are grouped by line item and can be sorted by date, uploader, type, and reason code, with filters for those fields Given a gallery item is selected When opened Then a lightbox preview displays metadata (timestamp, uploader, role, linked line items, reason codes, caption) and actions to download original and annotated versions Given a line-item detail view is opened When rendered Then only evidence linked to that line item is shown inline in chronological order with counts and quick filter by reason code Given the gallery contains more than 100 items When the user scrolls or paginates Then additional items load without a full page refresh and any active filters/sorts are preserved
Secure Storage, Encryption, and Retention
Given any evidence file is stored When at rest Then it is encrypted using AES-256 (or cloud-provider equivalent) and is retrievable only via signed URLs with a maximum 10-minute TTL Given a user previews or downloads evidence When the request is made Then the transfer occurs over TLS 1.2+ and the access is recorded in an immutable audit log (userId, timestamp, evidenceId, action) Given an organization retention policy of 7 years is configured (default) When the retention period elapses Then evidence is archived or deleted per policy and the event is logged; archived items are not available to end users Given an admin revokes a user's access to a dispute When effective Then no new signed URLs are issued for that user and subsequent preview/download attempts return HTTP 403; previously issued URLs remain valid only until their TTL expires
Itemized Adjustment Proposals & Counteroffers
"As a vendor, I want to submit a counter-proposal with itemized changes so that I can fairly resolve disputes without losing the entire payout."
Description

Allow either party to propose itemized adjustments such as discounts, partial refunds, redo work, or credits, each linked to specific line items and evidence. Support templates for common scenarios, required justification fields, and automatic net payout calculations. Enable counteroffers, versioning, and threaded comments per proposal, with clear accept/reject actions and visibility rules. Show a running delta against the original quote/invoice and provide a summary of proposed financial impact before submission.

Acceptance Criteria
Create Itemized Adjustment Linked to Line Items
Given a dispute is open on a job with an existing quote/invoice When a user adds an adjustment item to a proposal Then the user must select a specific original line item by ID before the item can be saved And the user must choose an adjustment type from: discount, partial refund, redo work, credit And monetary adjustment items require an amount in the job’s currency with 2 decimal precision (> 0) And "redo work" items may have $0.00 amount but must include a required scope text field And each adjustment item must have at least one linked evidence artifact (photo, document, or message link) And the proposal cannot be submitted if any item is missing a line item link, required fields, or evidence
Enforce Justifications and Field Validation
Given the user is composing an adjustment proposal When an adjustment type is selected Then a justification text field is required with 15–2000 characters And monetary amounts must be positive numbers with 2 decimal places and use the job currency And the sum of all monetary deductions for a given original line item cannot exceed that line item’s total And inline validation messages appear immediately on field blur or value change And the Submit button remains disabled until all validations pass
Apply Templates to Pre-Fill Adjustments
Given a library of common adjustment templates is available When the user selects a template such as "Missing scope", "Damage claim", or "Tenant rejection" Then the system pre-populates one or more adjustment items with type, default justification text, and evidence placeholders And placeholders (e.g., <attach photo of damage>) must be replaced or satisfied before submission And users can edit or remove any pre-filled item prior to submission And the system records the template name on the proposal metadata
Calculate Net Payout and Display Running Delta
Given the proposal contains one or more adjustment items When the user edits types or amounts Then the system recalculates in real time: Original Total, Total Deductions, Total Credits, and Net Payout And a signed running delta versus the original quote/invoice is displayed in currency with 2 decimal precision And calculations update within 300 ms after a field change And zero-dollar "redo work" items do not affect monetary totals but are counted in item counts
Counteroffers, Versioning, and Threaded Comments
Given Party A has submitted an adjustment proposal (version 1) When Party B submits a counteroffer Then a new immutable version (version 2) is created, linked to version 1 as its predecessor, and version 1 becomes read-only And the system displays a changelog summarizing added/removed/modified adjustment items between versions And comments are threaded per proposal version; replies nest under a parent comment with author and timestamp And only one active pending version exists at a time; submitting a new version supersedes the prior pending version
Accept/Reject Actions, Visibility, and Audit Log
Given a pending proposal version is visible to both parties When a party clicks Accept Then the version locks, its status becomes Accepted, and the system records user, timestamp, and final amounts in the audit log When a party clicks Reject Then a rejection reason (minimum 10 characters) is required and the version status becomes Rejected with an audit entry And proposal content and comments are visible only to the two counterparties on the job and their org admins by default And comments can be marked Internal to restrict visibility to the author’s organization And all actions (create, edit, submit, counter, accept, reject, visibility toggles) are auditable and immutable
Pre-Submission Financial Impact Summary
Given the user has populated an adjustment proposal When the user clicks Review Summary Then a summary displays counts by adjustment type, Original Total, Total Adjustments by type, Net Payout, and Percent Change vs Original And monetary figures must exactly match the line-item rollups used in calculations And the Submit button in the summary view is disabled until the user confirms an acknowledgment checkbox And closing the summary returns the user to the editable proposal without losing unsaved changes
Timeboxed Response Windows & Auto-Escalation
"As an operations lead, I want response windows with reminders and automatic escalation so that disputes don’t stall and we meet SLAs."
Description

Configure SLA timers for each dispute stage (acknowledgment, evidence submission, proposal review) with countdowns visible to all parties. Send reminders via SMS, email, and in-app notifications at configurable intervals. On expiry, apply organization-defined rules: auto-approve, auto-deny, or escalate to a designated reviewer. Support business hours/holiday calendars, pause/resume when new evidence is submitted, and record all timer events in the audit log to ensure predictable, timely outcomes.

Acceptance Criteria
Configure Stage SLAs and Countdown Visibility
Given organization-level SLA durations are configured per stage (e.g., Acknowledgment 4 business hours, Evidence 48 calendar hours, Review 24 business hours) When a dispute enters each respective stage Then a server-backed countdown starts for that stage with the correct remaining time derived from the SLA and current time Given any participant (tenant, vendor, manager) views the dispute When the stage timer is running Then a countdown is visible to all parties, labels the active stage, updates at least every 60 seconds, and shows no more than 60 seconds drift from server time Given the countdown reaches 00:00:00 When the stage expires Then the timer stops and the expiry workflow is triggered exactly once
Multi-Channel Reminder Notifications at Configurable Intervals
Given reminder offsets (e.g., T-24h, T-1h) and channels (SMS, email, in-app) are configured for a stage When the running timer crosses those offsets Then reminders are dispatched on all selected channels to the responsible party and watchers, and each send attempt is logged Given a channel delivery fails transiently When the system retries Then it retries up to the configured limit (default 3), applies backoff, and logs success/failure; on final failure, an in-app fallback notice is posted Given reminders are scheduled When the stage timer is paused or the stage completes early Then pending reminders are suppressed or rescheduled to the new due time, and duplicates are not sent within a 5-minute window Given recipients have notification preferences or legal opt-outs When reminders are sent Then opt-outs are honored per channel while other allowed channels still deliver
Auto-Expiry Actions and Escalation to Designated Reviewer
Given an organization-defined expiry action is set for a stage (auto-approve, auto-deny, or escalate to designated reviewer/group) When the stage timer expires Then the configured action executes deterministically and exactly once, and all parties receive a notification of the outcome Given escalation is the configured action When escalation fires Then the dispute is assigned to the designated reviewer/group, a review task is created, and SLA timing for the escalation step starts if configured Given an expiry action executes When audit logs are reviewed Then the action, rule identifier/version, actor=system, and resulting dispute state are recorded with timestamps Given a transient failure occurs during action execution When the system retries Then the process is idempotent and does not duplicate state changes or notifications
Business Hours and Holiday Calendar Support
Given business hours (e.g., Mon–Fri 09:00–17:00 in org timezone) and an organization holiday calendar are configured When a stage uses a business-hours SLA Then the countdown excludes non-business hours and holidays, and the UI discloses the applied schedule via tooltip or details Given a user in a different timezone views the dispute When the countdown is displayed Then remaining duration is identical across users while the due date/time is localized to the viewer’s timezone Given a holiday is added, removed, or edited When timers are recalculated Then remaining times update within 60 seconds and an audit entry records the recalculation reason and delta Given a DST transition occurs during a business-hours SLA When the timer spans the transition Then total SLA duration is preserved without negative or duplicated time
Pause and Resume Timers on New Evidence
Given a stage timer is running When any party submits new evidence Then the active stage timer pauses immediately and a pause state with reason is shown to all parties Given the timer is paused When the stage owner resumes the timer (e.g., after evidence review) or an authorized user triggers resume Then the timer resumes with the exact remaining time preserved from before the pause, and pending reminders/expiry are rescheduled Given multiple evidence submissions occur while paused When additional evidence is received Then the timer remains paused and the pause reason list aggregates all submissions with timestamps Given pause or resume occurs When audit logs are inspected Then events include who/what triggered the change, timestamps, and remaining time before and after the change
Comprehensive Audit Logging of Timer Events
Given any timer-related event occurs (start, recalculation, reminder scheduled/sent, pause, resume, expiry, action execution, failure/retry) When audit logs are queried for a dispute Then each entry includes dispute ID, stage, event type, timestamp (UTC), actor, previous/new timer state, channel (if applicable), and correlation ID Given an authorized admin requests an export for a dispute’s timer history When the export is generated Then a chronologically ordered CSV or JSON is produced within 30 seconds and matches on-screen audit entries Given audit logs are stored When any user attempts to modify or delete an audit entry Then the system prevents mutation and records an integrity check event for the attempt
Unified Dispute Timeline & Immutable Audit Log
"As a property owner, I want an exportable, immutable record of the dispute so that I can defend decisions during audits or chargebacks."
Description

Show a single chronological timeline of all dispute events—holds, uploads, proposals, comments, reminders, decisions—with actor identity, timestamps, and status changes. Persist an immutable, append-only audit log suitable for compliance and chargeback defense, with export to PDF/CSV that includes embedded evidence references and redactions where required. Provide quick filters by event type and party to speed reviews, and ensure all entries are tamper-evident.

Acceptance Criteria
Chronological Unified Timeline Rendering
Given a dispute with events of types hold, upload, proposal, comment, reminder, and decision created at known UTC timestamps and by identified actors When the dispute timeline is loaded Then events are displayed in strictly ascending order by created_at (UTC) and show event type, actor display name and role, localized timestamp with timezone abbreviation, and previous_status → new_status where applicable And events sharing the same created_at second are secondarily ordered by monotonically increasing event_id to ensure deterministic ordering And system-generated events render actor as "System"
Immutable Append-only Audit Log with Tamper Evidence
Given an existing audit log with last hash Hn When a new dispute event is recorded Then the system appends a new immutable entry including id, created_at (UTC), actor_id or system, event_type, payload_hash, prev_hash=Hn, and entry_hash=SHA-256(prev_hash || canonical_entry_payload) And any attempt to update or delete an existing audit entry returns 403 and no changes are persisted And running VerifyAuditChain(dispute_id) returns Valid for an unaltered log and returns Invalid with the index of the first broken link if tampering is detected
Export to PDF/CSV with Evidence References and Redaction
Given a dispute including evidence files and messages containing PII with redaction flags set When an authorized user requests an export (PDF and CSV) for a selected date range with "Redact PII" enabled Then the system generates both files within 30 seconds for up to 5,000 events and provides a download link valid for 24 hours And each export includes event_id, event type, actor, timestamp (UTC and viewer timezone), status change, evidence references (file name and signed URL or attachment ID), and a redaction legend And redacted content is irrecoverably masked in the output (black boxes in PDF, [REDACTED] in CSV) with no original values embedded And the export header includes dispute_id, date range, generated_at (UTC), viewer timezone, page count (PDF), and audit chain summary (first hash, last hash, verification result)
Quick Filters by Event Type and Party
Given a dispute timeline containing at least 500 events across multiple types and parties When a user applies filters selecting multiple event types and one or more parties Then the timeline updates within 500 ms for datasets ≤1,000 events (client-side) and within 2,000 ms for datasets ≤50,000 events (server-backed) And active filters are displayed as removable pills, persisted in the URL as query parameters, and preserved on refresh or share And clearing all filters restores the full timeline and resets event counts
Timeline Integrity Across Timezones and Clock Drift
Given events originate from clients in different timezones and with potential device clock skew When the timeline is rendered Then ordering is based on server-side created_at stored in UTC, while the UI displays local times using the viewer's timezone and observes DST rules, with a toggle to show UTC And if two events share the same second, a deterministic secondary sort by event_id ensures stable order across sessions And changing the viewer device clock does not alter event ordering
Role-based Visibility and Export Authorization
Given roles tenant, vendor, landlord, and property_manager_admin When a user views a dispute timeline Then tenants and vendors see their own submissions plus system/status events; landlords and property_manager_admin see all events And only property_manager_admin and the landlord who owns the dispute can generate a full export; tenants and vendors can generate a redacted export limited to their own content And unauthorized export attempts return 403 and are recorded in the security log with user_id, dispute_id, timestamp, and reason
SLA Reminders and Decision Markers
Given configured response timeboxes for proposals and evidence submissions When deadlines are approaching (T-24h, T-1h) or breached Then the system auto-inserts reminder events and an SLA breach event with timestamps into the timeline; snooze or reschedule actions create new events without modifying prior entries And decision events include outcome, deciding actor, and effective timestamp; appeals append new events and do not overwrite prior decisions
Payment Reconciliation & Ledger Updates
"As a finance admin, I want resolved adjustments to post automatically to payments and the ledger so that our books remain accurate without manual work."
Description

On resolution or timeout, automatically compute final payable amounts, release holds, and post adjustments to the payments processor. Generate balanced ledger entries for holds, releases, refunds, credits, and fees with idempotent operations and rollback safeguards. Emit webhooks for downstream accounting, update outstanding invoices, and reflect final amounts on the shared calendar and vendor statements to keep financials accurate across the system.

Acceptance Criteria
Final Payable Computation on Dispute Resolution
Given a disputed work order with original payable amount 1200.00 and approved deductions 150.00, platform fees 30.00, and vendor credits 0.00 When both parties mark the dispute as resolved Then the system computes final_payable_to_vendor = 1200.00 - 150.00 - 30.00 - 0.00 = 1020.00 And all monetary values are rounded to 2 decimal places using half-up rounding and use the job currency And the computed amount is persisted on the dispute and work order records with a reconciliation version number And the status of the dispute is updated to "settled_resolved" within 5 seconds of resolution submission
Timeout-Triggered Reconciliation Without Response
Given a dispute with an active adjustment proposal of -200.00 labeled default_on_timeout and a timebox of 72 hours And no party action occurs before the timebox elapses When the timeout job executes Then the system marks the dispute as "settled_by_timeout" and applies the default_on_timeout proposal And computes final_payable_to_vendor = original_payable - 200.00 - applicable fees And writes an audit entry with timeout timestamp and policy used And emits the standard reconciliation webhooks as with a resolved dispute
Release Holds, Capture/Refund, and Post to Payment Processor
Given a processor authorization/hold of 1200.00 exists with hold_id "H123" And final_payable_to_vendor = 1020.00 When reconciliation commits Then the system uses idempotency key "recon-{disputeId}-{version}" to post a capture of 1020.00 and a refund of 180.00, and releases any residual hold And it records processor transaction IDs for the capture and refund And it retries transient failures up to 3 times with exponential backoff and logs each attempt And it fails fast on non-retriable errors and hands off to rollback safeguards
Balanced Ledger Entries for Holds, Releases, Refunds, Credits, and Fees
Given reconciliation finalizes with capture 1020.00, refund 180.00, and fee 30.00 When ledger posting runs Then the system creates double-entry records for: hold release, capture, refund, credits, and fees And the net sum of debits equals credits (0.00) for the reconciliation batch And each ledger entry is linked to disputeId, workOrderId, and processor transaction IDs And the batch is atomically committed; if validation fails, no entries are written
Idempotent Reconciliation and Replay Safety
Given reconciliation for dispute D has already been completed at version V When the reconciliation service receives the same request again with the same idempotency key or the workflow is retried Then no new processor actions occur and no new ledger entries are created And the service returns the original settlementId, amounts, and timestamps And webhook emission uses the same event_id and is deduplicated downstream
Rollback and Compensating Actions on Partial Failure
Given the processor capture succeeds but ledger commit fails When the failure is detected Then the system automatically issues a compensating refund equal to the captured amount or posts reversing ledger entries to restore pre-reconciliation balances, depending on failure point And it marks the reconciliation status as "error_rolled_back" or "manual_review_required" and preserves an audit trail with correlation IDs And it emits a webhook "reconciliation.failed" with error details for downstream awareness
Webhooks and Downstream Surfaces Updated
Given reconciliation is marked settled (by resolution or timeout) When posting completes Then the system emits a signed webhook "reconciliation.settled" within 60 seconds containing disputeId, settlementId, final_payable, refund_amount, fee_amounts, ledger_entry_ids, processor_txn_ids, event_id, and occurred_at And outstanding invoices are updated to reflect the final amounts; invoices are marked Paid or Partially Paid accordingly And the shared calendar displays the final payable on the job card and removes any "on hold" badge And the vendor statement shows the final payout and line-item adjustments for this work order
Role-Based Access & Data Privacy Controls
"As an account admin, I want granular permissions and privacy safeguards so that disputes are handled securely and compliantly."
Description

Implement granular permissions controlling who can initiate holds, view sensitive evidence, make proposals, and finalize resolutions. Enforce least-privilege access for tenants and vendors, with PII masking, download controls, and data retention policies configurable per organization. Log access to sensitive artifacts, support redaction on exports, and provide an admin audit of permission changes to ensure secure, compliant dispute handling.

Acceptance Criteria
Only Authorized Roles Can Initiate Dispute Holds
Given a user without the Dispute.Hold.Initiate permission When they attempt to pause or reduce a payout on a dispute Then the action is blocked with HTTP 403 and a user-facing "Insufficient permissions" message And no hold record is created or modified in the system And an access-denied audit event is recorded with actor, disputeId, and attempted action Given a user with the Dispute.Hold.Initiate permission in the same organization as the dispute When they initiate a hold Then a hold record is created with actorId, timestamp, reason, scope (amount or percent), and orgId And the dispute timeline shows the hold event to permitted roles only And affected parties receive notifications per notification policy Given a tenant or vendor on default roles When they view a dispute Then "Pause/Reduce Payout" controls are not visible and are not present in API responses
PII Masking on Sensitive Evidence for Unauthorized Viewers
Given evidence containing PII (email, phone number, street address) And a viewer without the PII.View.Unmasked permission When the evidence is rendered in UI or fetched via API Then PII fields are masked (email local-part replaced with ****, phone digits except last 2 replaced with •, street number replaced with •) And the API returns masked=true for each PII field and never includes raw values And client-side unmask controls are not available Given a viewer with the PII.View.Unmasked permission When they view the same evidence Then full PII is displayed And an "Unmasked" indicator is shown with the viewer's responsibility notice And an audit event of type evidence.view.unmasked is recorded Given any viewer When they attempt to bypass masking via direct asset URL Then the server still returns masked content or denies access per permission policy
Controlled Evidence Downloads and Watermarked Redacted Exports
Given a user without the Evidence.Download permission When they attempt to download an evidence file Then the download control is hidden in UI And direct download URLs return HTTP 403 And an access-denied audit event is recorded Given a user with Evidence.Download but without PII.View.Unmasked When they export or download evidence Then the generated file is redacted per masking policy And a visible watermark includes viewerId, disputeId, orgId, timestamp, and "REDACTED" And the download link is a signed URL that expires within 15 minutes and is single-use Given a user with both Evidence.Download and PII.View.Unmasked When they export evidence Then they can choose Redacted or Unredacted And Unredacted export requires acknowledgment of a usage notice And the export event (including redaction flag) is logged with checksum
Granular Permissions for Proposal Creation and Resolution Finalization
Given a user without Dispute.Proposal.Create When they attempt to create or edit a proposal Then the action is blocked with HTTP 403 and no proposal draft is created Given a user with Dispute.Proposal.Create When they submit a proposal adjustment Then the proposal is saved with actorId, timestamp, and change summary And visibility is restricted to parties and roles with Proposal.View Given a user attempting to finalize a resolution When they lack Dispute.Resolve.Finalize Then the Finalize action is disabled and API returns HTTP 403 Given an org policy requiring both-party acceptance And a user with Dispute.Resolve.Finalize When one party has not yet accepted Then Finalize is not permitted and the UI indicates pending acceptance Given a user with Org.Admin.OverrideFinalize When they finalize without both-party acceptance and provide a reason Then the resolution is finalized with an override flag and reason captured And all parties are notified of the override And an audit event includes before/after state and override reason
Configurable Data Retention and Automated Purge
Given an organization admin with Org.Retention.Manage When they set retention for evidence=180 days, disputes=365 days, auditLogs=730 days within allowed ranges Then the settings are validated, saved, versioned, and immediately applied to scheduling And each artifact displays its scheduled purge date based on its category and org policy Given artifacts older than their configured retention period When the nightly retention job runs Then eligible artifacts are irreversibly purged (content and thumbnails) And non-destructive metadata necessary for legal holds is preserved only if a LegalHold flag is set And a purge audit event is recorded per artifact with hash and actor=system Given an invalid retention value (e.g., negative days or exceeding platform max) When the admin attempts to save Then the save is rejected with validation errors and no change is applied
Comprehensive Audit Logging of Sensitive Artifact Access
Given any attempt to view, download, export, or unmask evidence When the action occurs Then an audit entry is created with actorId, role, orgId, actionType, artifactId, disputeId, timestamp, ip, userAgent, outcome, and reason (if denied) And the entry is visible in the org's audit feed within 5 seconds Given a denied access attempt due to insufficient permissions When it happens Then the audit outcome is "denied" and includes the missing permission key And repeated denials from the same IP within 10 minutes increment a counter for rate analysis Given an auditor with Org.Audit.View When they filter logs by disputeId, actorId, actionType, and date range Then matching entries are returned with pagination and export capability (CSV with redacted PII)
Admin Audit Trail of Permission Changes
Given a user with Org.Permissions.Manage When they add or remove a permission from a role or assign a role to a user Then the system records an immutable audit entry capturing actorId, targetUserId/roleId, orgId, before/after diff, change reason (required), timestamp, and requestId And a summary appears in the admin audit UI within 5 seconds Given a user without Org.Permissions.Manage When they attempt to change permissions Then the action is blocked with HTTP 403 and a denied audit entry is created Given an org auditor with Org.Audit.View When they export permission-change history Then the export includes full change details with no redaction of permission keys or role names And the file is watermarked with orgId and timestamp And the export action is logged with checksum

Role Blueprints

Spin up least-privilege roles in seconds with prebuilt templates for Solo Landlords, Vendor Partners, Assistants, and Tech Leads. Scope access by property, unit, action (view, schedule, approve, pay), and data type with one-click tweaks. Onboarding stays fast and consistent while reducing permission creep.

Requirements

Blueprint Templates Library
"As an owner‑admin, I want to assign a prebuilt role that matches a user’s job in one click so that I can onboard consistently and avoid over‑permissioning."
Description

Provide a curated, versioned set of least‑privilege role templates (Solo Landlord, Vendor Partner, Assistant, Tech Lead) that can be applied in one click. Each template encodes default permissions across FixFlow modules (Requests, Calendar/Scheduling, Messaging, Payments, Documents) and core actions (view, create, schedule, approve, pay, export), with defaults scoped to the minimal set needed for typical duties. Include template metadata (name, description, version, changelog), import/export for org reuse, and localization support for labels. Integrate with the permission engine so templates resolve to a deterministic policy set, and with onboarding flows so selecting a template initializes user settings and notifications consistently. Expected outcome: faster, consistent onboarding with reduced permission creep and fewer configuration errors across 1–200 unit portfolios.

Acceptance Criteria
One-Click Apply of Blueprint Template
Given an org has the Blueprint Templates Library with Solo Landlord, Vendor Partner, Assistant, and Tech Lead templates When an admin selects the "Assistant" template and clicks Apply on a pending user Then the user's role is created within 3 seconds and permissions match the template version exactly with no extra permissions granted Given apply completes When the admin opens the user's Permissions view Then the permissions matrix matches the template's policy and displays the applied template name and version Given a template is already applied to a user When the admin attempts to apply the same template and version again Then the system indicates no changes will be made and leaves the user's permissions unchanged
Deterministic Policy Resolution from Template
Given template "Vendor Partner" v1.2 exists When the permission engine resolves the template to policies Then the resulting policy hash and list of policy IDs are identical across environments for the same org and template version Given two different users in the same org are assigned "Vendor Partner" v1.2 When comparing their effective permissions Then there is no difference in allowed actions and scopes Given a template defines only allowed actions When a user attempts an action not listed (e.g., pay, export) Then the action is denied by default Given a template version is unchanged When the service restarts and caches are warmed Then the resolved policy hash remains the same
Template Metadata Versioning and Changelog
Given the templates library is open When the admin views "Tech Lead" Then the template shows name, description, semantic version (MAJOR.MINOR.PATCH), last updated timestamp, and a changelog with at least the last 5 entries Given "Assistant" v1.2 exists When "Assistant" v1.3 is published Then the changelog entry lists added, removed, and modified permissions with human-readable labels and a machine-readable diff identifier Given users are assigned "Assistant" v1.2 When v1.3 becomes available Then the system flags those assignments as "Update available" and allows previewing the permission diff before applying Given the admin applies the update to a user Then the user's assignment reflects the new version and the previous version is recorded in the assignment history
Import/Export of Templates for Org Reuse
Given the admin selects Export on "Vendor Partner" v1.2 When the export completes Then a signed JSON file is downloaded containing metadata, policy map, localization keys, and a checksum that validates against the current template Given a valid export file from another org When the admin imports it Then the system validates schema and checksum, displays name and version, and on confirmation adds it to the library without overwriting existing templates Given an import file with a name and version that already exist in the library When the admin attempts import Then the system blocks the import and explains the conflict, instructing to increment the version or rename Given an invalid file (schema or checksum failure) When the admin imports it Then the import is rejected and no template is created
Localization of Template Labels in Library
Given the org locale is set to es-ES When viewing the templates library Then template names and descriptions display in Spanish where translations exist and fall back to English where they do not Given the admin switches locale from en-US to fr-FR When the library view refreshes Then all template labels update to French within 1 second without altering underlying permissions Given an export includes i18n bundles for en-US and es-ES When importing into a Spanish-locale org Then the Spanish labels from the bundle are available immediately in the library
Scoped Access by Property/Unit/Data Type with One-Click Tweaks
Given "Vendor Partner" v1.2 default scopes When applied to a user with assigned Requests A and B Then the user can view and update only Requests A and B, can schedule only for those requests, cannot access Payments, and cannot export any data Given "Assistant" v1.2 default scopes When applied Then the user can view and create Requests for all properties in the org, can schedule on the shared calendar, can message tenants and vendors, cannot approve, cannot pay, and cannot export Given property scope tweaks When the admin applies "Tech Lead" and selects properties P1 and P2 via the one-click tweaker Then the user's effective scope is limited to P1 and P2 across Requests, Calendar, Messaging, and Documents Given data-type scope tweaks When the admin disables access to Documents Then the user cannot view or upload any documents while all other template permissions remain unchanged Given unit-level scope When the admin limits a Vendor Partner to unit U-101 only Then attempts to access any other unit's requests are denied
Onboarding Flow Initialization via Selected Template
Given the new-user onboarding wizard When the admin selects the "Vendor Partner" template Then the wizard pre-populates notification preferences, property and unit scopes (none by default), and messaging visibility per template and skips advanced permission steps Given the admin changes the selected template mid-onboarding from "Assistant" to "Tech Lead" When proceeding to the next step Then all pre-populated settings refresh to match "Tech Lead" defaults and prior derived settings are discarded unless explicitly re-tweaked Given onboarding is completed When the user first signs in Then the navigation and accessible modules match the applied template and the user can complete an end-to-end task appropriate to the role without encountering permission errors Given validation runs before finishing onboarding When a mismatch between template selection and applied permissions is detected Then the wizard blocks completion and lists the exact discrepancies to correct
Granular Permission Engine
"As a security‑minded product owner, I want a robust, scoped permission model so that users only see and do what their role and assigned properties/units allow."
Description

Implement a policy engine that evaluates permissions by resource (property, unit, request, vendor, payment, message), action (view, create, edit, schedule, approve, pay, export, manage), and data type (e.g., tenant PII), with property/unit scoping and default‑deny semantics. Support deterministic conflict resolution (explicit deny overrides allow), inheritance (org → property group → property → unit), and time‑bounded grants. Expose enforcement hooks for UI components, API endpoints, and background jobs (e.g., auto‑scheduling, SMS confirmations) to prevent unauthorized operations. Provide an "effective permissions" query for previews and audits, and cache evaluation for performance at 200‑unit scale. Expected outcome: consistent, least‑privilege access control across all FixFlow workflows without regressions to scheduling, messaging, or payments.

Acceptance Criteria
Default-Deny and Explicit Deny Overrides Allow
- Given a user with no grants, when they attempt any action on any resource, then the engine returns a deny decision with error code PERMISSION_DENIED and no side effects occur. - Given a user has an allow grant for action=approve on resource=request:123 and an explicit deny grant for action=approve on resource=property:XYZ, when evaluating action=approve on request:123, then the decision is deny and decision metadata includes deny_source=property:XYZ. - Given overlapping inherited grants that include at least one explicit deny, when evaluating, then explicit deny deterministically overrides any allow from any scope every time.
Property/Unit Scope Enforcement and PII Redaction
- Given a user has allow: view on resource=request scoped to property=A only, when they list or fetch requests from property=B, then the decision is deny and the API returns HTTP 403 with code PERMISSION_DENIED. - Given a user has allow: schedule on unit=U1 only, when attempting to schedule for unit=U2, then the decision is deny and no calendar event is created. - Given a user has allow: view on requests but deny: view on dataType=tenantPII, when viewing a request that includes tenant PII, then the engine allows the request access but redacts PII fields in responses and UI, and export of PII fields is denied.
Hierarchical Inheritance and Specificity
- Given grants exist at org, property-group, property, and unit scopes, when evaluating a resource at unit scope, then the most specific applicable grant determines the outcome unless an explicit deny exists at any scope, which overrides. - Given allow:view at org and deny:view at property-group=PG1, when evaluating view on a request under PG1, then decision is deny. - Given deny:edit at org and allow:edit at unit=U1, when evaluating edit on a request in U1, then decision is deny due to explicit deny precedence.
Time-Bounded Grants Enforcement
- Given a grant allow: approve on property=P1 with start=2025-09-10T00:00:00Z and end=2025-09-15T23:59:59Z, when evaluated before start, then decision is deny; during the window, decision is allow; after end, decision is deny. - Given overlapping time-bounded grants with one deny active during any overlap, when evaluating during the overlap, then decision is deny. - Given the system interprets grant windows in UTC, when evaluating across user time zones, then decisions are based on UTC timestamps only.
Enforcement Hooks Across UI, API, and Background Jobs
- Given UI components call the enforcement hook before rendering action controls, when the decision is deny, then buttons/links for the action are not rendered or are disabled with a tooltip indicating insufficient permissions. - Given API endpoints call the enforcement hook on each request, when the decision is deny, then the API returns HTTP 403 with error code PERMISSION_DENIED and correlationId; no state change occurs. - Given background jobs (auto-scheduling, SMS confirmations, payment runs) evaluate under their configured principal, when the decision is deny for the intended action, then the job skips the action, logs a structured audit entry, and does not enqueue downstream effects.
Effective Permissions Query for Preview and Audit
- Given a principal and a resource reference, when /effective-permissions is called with filters (resource, actions), then the response lists each requested action with decision allow/deny, evaluation scope (org/property-group/property/unit), grant id(s), and active time bounds. - Given stable inputs, when /effective-permissions is called repeatedly, then responses are deterministic and identical. - Given audit mode is requested, when the decision is deny due to explicit deny, then response includes deny_source and scope details suitable for admin review.
Evaluation Caching and Invalidation at 200-Unit Scale
- Given cold cache, when evaluating 1000 permission checks across 200 units, then p95 decision latency <= 50 ms and error rate = 0%. - Given warm cache after first pass, when evaluating the same checks, then p95 decision latency <= 10 ms. - Given a policy or grant is created/updated/removed, when subsequent evaluations occur, then affected cache entries are invalidated within 5 seconds and all post-invalidation decisions reflect the change; no stale allow is returned where deny now applies.
Property & Unit Scope Selector
"As an admin, I want to choose exactly which properties and units a role applies to so that assistants and vendors only access the parts of my portfolio they work on."
Description

Deliver an assignment UI and backend that precisely scopes roles to properties and units. Support bulk selection (by tags/groups), search/filter, and multi‑select with counts. Provide options for inclusion rules (static list vs. auto‑include new units within selected properties) and safeguards to prevent selecting all by accident. Reflect scope in all queries and UI lists (Requests board, Calendar, Payments, Messaging). Handle edge cases such as unit transfers and property archival gracefully. Expected outcome: administrators can grant access narrowly by portfolio slice, preventing overexposure of data and actions.

Acceptance Criteria
Bulk Select by Tags/Groups with Accurate Counts
Given I am assigning scope in the Role Blueprints UI And properties and units have tags/groups applied When I filter by one or more tags/groups and multi-select across paginated results Then the selection counter displays the exact number of properties and units selected And only the selected properties/units are added to the scope on save And deselecting any item immediately decrements the counter and removes it from the pending scope
Search/Filter with Persisted Multi-Select
Given I have selected multiple properties and/or units When I execute a new search, change filters, or navigate to another results page Then prior selections remain selected and the selection count remains accurate without duplicates And a Clear Selection action resets the selection to zero with a single click And search supports partial matches on property name, address, and unit number
Inclusion Rule: Static List vs Auto-Include New Units
Given I select one or more properties in the scope selector And I toggle the inclusion rule to Auto-include new units for those properties When a new unit is created under any selected property Then the role gains access to that unit on the next page refresh or API call without manual changes And when the inclusion rule is Static list, newly created units under selected properties are not accessible until explicitly added And the chosen inclusion rule is stored and visible in the scope summary
Safeguard Against Accidental Select-All
Given my current filter would select all properties and/or all units in the portfolio When I click Select All Then a confirmation dialog appears showing the total number of properties and units to be selected And the confirm action is disabled until I explicitly acknowledge by checking a confirmation box And cancelling or closing the dialog leaves the selection unchanged
Scope Enforcement Across Requests, Calendar, Payments, Messaging, and API
Given a user has a role scoped to a specific set of properties/units When they view the Requests board, Calendar, Payments, and Messaging modules Then each module only displays items belonging to the scoped properties/units And aggregate counts, totals, and badges reflect only in-scope items And direct navigation or API access to an out-of-scope resource returns 403 Forbidden without leaking metadata And exports/downloads include only in-scope records
Edge Cases: Unit Transfer and Property Archival
Given a role has Auto-include by property for Property A And the role also has a Static list that includes Unit 12 in Property A When Unit 12 is transferred to Property B Then access via Auto-include is removed because the unit is no longer in Property A And access via Static list remains because the unit was explicitly selected When Property A is archived Then the role loses access to Property A and all its units regardless of inclusion rule And archived properties/units are excluded from search/filter results and cannot be added to scopes
One‑Click Role Assignment & Onboarding
"As a property manager, I want to assign a role and invite a user in one click so that they can start scheduling and communicating without IT setup."
Description

Enable assignment of a blueprint to a user in a single step that creates the account (or updates an existing one), applies scoped permissions, and triggers onboarding communications (email/SMS invite, magic link, or SSO mapping). Include optional start/end dates for temporary access (e.g., job‑based vendor access). Preconfigure default notification settings relevant to the role (e.g., SMS confirmations for vendors, approval alerts for assistants) and integrate with Calendar so scheduling permissions take effect immediately. Log all actions to the audit trail. Expected outcome: rapid, error‑free provisioning aligned with communication and scheduling flows.

Acceptance Criteria
One-Click Provision: New Vendor Partner
Given a Vendor Partner blueprint with schedule and view permissions scoped to selected properties/units and a new user’s email and mobile number When the admin clicks “Assign” Then the system creates the user account, applies the exact selected scopes and actions, and activates Calendar access within 30 seconds And the user can schedule and view only within those scoped properties/units; attempts outside scope return 403 and are audit-logged And end-to-end provisioning completes within 5 seconds at the 95th percentile And no duplicate user record is created for the same email or phone
One-Click Update: Existing Assistant Permissions
Given an existing user and an Assistant blueprint with approve and view permissions plus selected scope tweaks When the admin assigns the blueprint Then the update is atomic; the resulting permission set exactly matches the blueprint plus selected tweaks shown in the assignment summary And re-assigning the same blueprint with the same tweaks results in a no-op (no changes) and an audit log entry indicating idempotent assignment And the user’s session reflects permission changes within 60 seconds without requiring logout
Onboarding Communications: Email/SMS Magic Link
Given onboarding via email/SMS is selected and valid contact details exist When the admin clicks “Assign” Then exactly one invite is sent per channel selected within 60 seconds containing a single-use magic link And the magic link expires after 24 hours or immediately after first successful use, whichever comes first And undeliverable messages are detected and surfaced to the admin with a retry option and are audit-logged
SSO Mapping Onboarding: Tech Lead
Given the organization has SSO configured and SSO mapping is selected for a Tech Lead blueprint When the admin clicks “Assign” Then the user is mapped to the IdP identity (by configured attribute) without sending a magic link, and the user can sign in via SSO to access exactly the assigned scopes on first login And if mapping fails (no match or ambiguous match), the assignment is not completed, an actionable error is shown to the admin, and the failure is audit-logged And successful mappings appear in the audit trail with IdP identifier and timestamp
Temporary Access Window: Job-Based Vendor
Given start and end dates are set during assignment of a Vendor Partner blueprint When the start date/time arrives Then the user’s access becomes active within 60 seconds and Calendar scheduling/confirmation permissions are enabled per scope When the end date/time passes Then all role-derived permissions are revoked within 60 seconds, SMS confirmation links no longer function, and new scheduling actions are blocked with 403 And revocation and any blocked attempts are audit-logged
Role Defaults: Notification Settings Applied
Given a blueprint with role-based default notification settings (e.g., vendors: SMS confirmations ON; assistants: approval alerts ON) When the admin assigns the blueprint to a user for the first time Then those defaults are applied and persisted to the user’s notification preferences And subsequent user-level overrides are preserved on future reassignments unless the admin explicitly selects “Reset to role defaults” And the effective notification settings are visible in the user profile immediately after assignment
Audit Trail: Role Assignment Event
Given any one-click role assignment or update occurs When the operation completes (success or failure) Then the audit trail contains a record with: initiator, target user, blueprint ID/version, scopes and actions applied, start/end dates (if any), communication channels triggered with delivery status, SSO mapping status, calendar integration outcome, timestamp, and source IP And the record is immutable, searchable within 5 seconds, and exportable via the reporting endpoint
Inline Permission Tweaks & Save‑as‑Blueprint
"As an owner‑admin, I want to tweak a template and save it for my organization so that future onboarding remains fast without sacrificing least‑privilege controls."
Description

Allow admins to make quick, safe adjustments to a selected template (toggle specific actions, add/remove data‑type access, refine property/unit scope) with real‑time impact previews. Visually indicate deviations from the base template and allow saving the customized set as an organization‑specific blueprint with versioning and notes. Provide drift detection when a base template updates, with a guided merge workflow. Expected outcome: organizations achieve least‑privilege while preserving speed of setup and consistent reuse of custom patterns.

Acceptance Criteria
Inline Action Toggles Update Real-Time Preview
Given an admin has opened Role Blueprints and selected a base template to customize When the admin toggles an action permission (e.g., view, schedule, approve, pay) Then the impact preview updates within 500 ms to reflect added or removed accessible operations and resource counts And an unsaved changes indicator appears showing the number of deviations from the base template And no live permissions are changed until Save is clicked And clicking Cancel discards changes and restores the preview to the base template state
Data-Type Access Adjustments with Redaction Enforcement
Given the admin adds or removes access to specific data types (e.g., tenant PII, payment details, attachments) When the admin changes a data-type toggle Then the preview immediately shows which fields will be visible vs. redacted for sample records And after saving, API and UI attempts to access removed data types are denied with 403 and redacted displays, and an audit log entry is recorded And restoring access re-enables visibility in both preview and live views on next refresh
Property and Unit Scope Refinement
Given the admin is refining property and unit scope for the role When the admin selects properties/units via search and multi-select, including the option "All current and future units in Property X" Then the preview shows an exact count of included properties and units and a summarized list And selecting units outside the organization is not allowed and surfaces a validation message And after saving, the role’s assignments enforce access only to the selected scope
Visual Deviation Indicators and Reset to Base
Given deviations exist from the selected base template When viewing the customization UI Then all modified permissions and scopes are visually marked as changed and are identifiable via accessible labels And clicking Reset to Base reverts all deviations in one action and clears the unsaved changes indicator And the preview reflects the base template state within 500 ms
Save as Organization Blueprint with Versioning and Notes
Given a customized role configuration with deviations When the admin clicks Save as Blueprint Then the system requires a unique name (case-insensitive) within the organization and accepts optional notes And upon save, version 1.0 is created with author, timestamp, notes, and an immutable snapshot of permissions and scope And the new blueprint appears in the template list and can be applied to create roles And saving subsequent changes as a new version increments the version (e.g., 1.1) and retains prior versions for reuse
Drift Detection and Guided Merge on Base Template Update
Given an org blueprint derived from base template version N And the base template is updated to version N+1 When the admin opens Role Blueprints Then the blueprint is flagged with a drift alert within 1 hour of the base update And clicking Merge opens a guided diff showing added, removed, and modified permissions/scopes And the admin can accept or reject each change; conflicts must be resolved before Save is enabled And saving produces a new org blueprint version with merge notes and an audit entry
Audit Trail & Effective Access Preview
"As a tech lead, I want to audit and preview effective access so that I can verify least‑privilege and troubleshoot permission issues quickly."
Description

Record immutable audit events for role/template creation, edits, assignments, scope changes, and access grants/expirations. Provide diffs for before/after permissions, exportable logs (CSV/JSON), and filters by user, resource, and date. Include a "Preview Access" mode to simulate or view as a role for a selected property/unit, showing exactly what data and actions are available across Requests, Calendar, Messaging, Payments, and Documents without granting live access. Expected outcome: transparent governance, easier troubleshooting, and compliance‑ready evidence of least‑privilege enforcement.

Acceptance Criteria
Immutable Audit Events for Role and Scope Changes
- Given an authorized admin performs role/template create, edit, assign, scope change, or access grant/expiration, When the action is confirmed, Then an audit event is appended within 1 second containing: actor ID, actor IP, timestamp (ISO 8601 UTC), event type, target user/role ID, affected properties/units, and correlation ID. - Given the audit store, When a client attempts to update or delete an existing audit event, Then the request is rejected with 403 and a new audit event records the denied attempt; no existing audit records are altered. - Given network retries, When the same operation is submitted with the same idempotency key, Then only one audit event is persisted and subsequent duplicates are ignored. - Given any audited change, When the event is written, Then it references permission snapshot IDs for before and after states. - Given chronological queries, When events are listed, Then results are ordered by timestamp desc and ties are broken deterministically by event ID.
Permission Diff on Role and Template Changes
- Given a role/template permission or scope edit, When the user clicks Save, Then a diff is generated within 2 seconds showing Added and Removed permissions grouped by module (Requests, Calendar, Messaging, Payments, Documents), action (view, schedule, approve, pay), and scope (property/unit). - Given the diff, When the user clicks Download Diff (JSON), Then a JSON file is downloaded containing before, after, added, and removed arrays with IDs and human-readable labels. - Given no net change is detected, When Save is attempted, Then the system blocks the save and displays "No differences detected". - Given an audit event is recorded, When viewing the event details, Then the same diff summary is displayed and linked to the before/after snapshots. - Given inherited or default permissions, When computing the diff, Then effective permissions are included so the diff reflects the true before/after access state.
Audit Log Filtering, Search, and Pagination
- Given existing audit events, When a user applies filters by user (ID or email), resource (role/property/unit IDs), and date range (UTC), Then only matching events are returned within 2 seconds for up to 10,000 records. - Given the filter UI, When a date range is selected, Then results include events on the start and end timestamps (inclusive) and reflect UTC in the display. - Given result sets larger than a page, When navigating pages, Then pagination supports 25/50/100 items per page, shows total count, and maintains filters and sort. - Given sorting controls, When the user toggles sort, Then events can be sorted by timestamp asc/desc; desc is default. - Given no matches, When filters yield zero results, Then an empty state is shown with "No audit events found" and zero result count.
Export Audit Logs to CSV and JSON with Integrity Metadata
- Given any current filter, When the user clicks Export CSV, Then a server job starts and provides a downloadable CSV within 60 seconds for up to 100,000 rows; larger exports are split into sequential files. - Given Export JSON is requested, When the job completes, Then a JSON Lines (.jsonl) file is available containing one event per line with the complete event payload. - Given an export completes, When the file is downloaded, Then it includes a header/metadata row (CSV) or sidecar .meta.json containing: applied filters, generated_at (ISO 8601 UTC), record_count, and SHA-256 checksum; the checksum matches the file contents. - Given access control, When a user without audit.export permission attempts export, Then the request is rejected with 403 and an audit event records the denied attempt. - Given security policy, When an export link is issued, Then it expires in 24 hours and requires authentication to download.
Effective Access Preview Mode (Simulate/View As)
- Given an admin with preview.access, When they select a role or user and choose a property/unit scope, Then the application enters Preview mode with a persistent banner stating the impersonated role/user and scope. - Given Preview mode is active, When navigating Requests, Calendar, Messaging, Payments, and Documents, Then visible data and enabled actions reflect the simulated effective permissions for the selected scope. - Given any mutating action (create/update/delete), When attempted in Preview, Then the API responds with 409 PreviewMode (no side effects), the UI shows a non-blocking notice, and no data changes occur. - Given session security, When Preview mode is entered, Then no impersonation tokens are issued and the underlying session remains the admin; exiting preview restores the prior state. - Given governance, When Preview mode is entered or exited, Then audit events are created with actor, target, scope, and timestamps.
Preview Accuracy and Scope Enforcement Across Modules
- Given a role limited to Property A, When in Preview for Property A, Then records from other properties/units are hidden and detail/API access to them returns 403/404 per policy. - Given a role without approve/pay, When viewing Payments in Preview, Then Approve and Pay actions are disabled in the UI and blocked by the API with 403 if invoked directly. - Given a role with view and schedule on Calendar, When in Preview, Then creating a schedule is allowed as a dry-run (no persistence) and labeled as Preview in all confirmations. - Given the policy engine's effective permission set, When compared to the Preview UI state across all modules and actions in a test matrix, Then there is a 100% match with no false grants. - Given scope switching, When the admin switches the preview scope between properties/units, Then the UI updates visibility and action states within 1 second without full page reload.

Policy Packs

Define per-property security policies that enforce what’s visible and who can do what. Set rules like “mask tenant phone for vendors until appointment confirmed,” “require 2FA above $500,” “no exports for assistants,” or “approval only from office IPs.” Apply packs across buildings to standardize compliance without manual setup.

Requirements

Policy Pack Schema & Versioning
"As an admin property manager, I want to define standardized policy packs so that I can enforce consistent security across properties without manual setup."
Description

Define a structured, versioned schema for Policy Packs that captures rule types (data masking/visibility, action permissions, step-up authentication, network/IP restrictions, monetary and temporal thresholds) and scope (portfolio, building group, property, role). Support validation, backward-compatible changes, defaults, and human-readable labels. Packs can be cloned, versioned, and published with effective dates. The schema must be portable (JSON-based), support conditional logic (e.g., appointment_status == confirmed), and include conflict resolution metadata for future merges. Integrates across FixFlow modules (requests, scheduling, SMS, exports, payments) so a single pack governs behavior consistently.

Acceptance Criteria
JSON Schema Validates Policy Pack Structure
- Given a policy pack JSON missing any of [id, name, version, scope, rules], When validated against the policy pack JSON Schema, Then validation fails and returns an error list with JSONPath and code per violation. - Given a policy pack JSON with rule.type in {data_masking, visibility, permission, step_up_auth, network_ip, monetary_threshold, temporal_threshold}, correct data types, and required fields present (including human-readable label and description), When validated, Then validation passes. - Given optional fields omitted, When validated, Then documented schema defaults are applied deterministically and surfaced in the validation result. - Given a policy pack JSON containing properties not allowed by the schema, When validated, Then validation fails unless metadata.allowUnknown is true, in which case unknown properties are preserved under metadata.extensions.
Semantic Versioning and Backward Compatibility
- Given a draft pack with version incremented from 1.2.0 to 1.3.0 that only adds optional fields, When published, Then it is marked backward-compatible and older engines ignoring new fields still enforce existing behavior. - Given a draft that removes or changes the type/meaning of an existing field without a migration, When published as a minor/patch version, Then publishing is blocked with a breaking-change error; it must use a higher major version (e.g., 2.0.0). - Given two versions v1.10.0 and v1.2.9, When compared by the system, Then v1.10.0 is treated as newer per SemVer rules. - Given a pack referencing a schemaVersion the engine does not support (major mismatch), When imported or published, Then validation fails with incompatible schemaVersion.
Clone, Version, and Publish with Effective Dates
- Given a published pack v1.0.0, When an admin clones it to v1.1.0-draft and sets effectiveDate to a future timestamp (UTC), Then the system schedules activation for that timestamp. - When currentTime < effectiveDate, Then v1.0.0 remains active; when currentTime >= effectiveDate, Then v1.1.0 becomes active atomically with no partial evaluations. - Given a published pack, When a user without Publisher permission attempts to publish, Then the action is denied. - Given a published pack, When changes are needed, Then a new version must be created; published artifacts are immutable (other than status deprecated/retired).
Scope Hierarchy and Conflict Resolution Metadata
- Given policies at scopes portfolio, buildingGroup, property, and role, When evaluating a request, Then the default precedence is role > property > buildingGroup > portfolio. - Given conflicting rules with metadata.priority set (integer where higher value wins), When evaluating, Then the rule with higher priority overrides regardless of scope precedence. - Given conflicts and metadata.strategy = "most_restrictive", When two rules disagree, Then the more restrictive outcome is selected using documented comparators per rule type. - When any conflict is resolved, Then the decision includes audit metadata: resolvedRuleId, losingRuleIds, strategy, priority values, and evaluation path.
Conditional Logic Evaluation
- Given a data masking rule with condition appointment_status == "confirmed", When appointment_status != "confirmed", Then tenant phone is masked for vendors; when appointment_status == "confirmed", Then the phone is unmasked. - Given a step_up_auth rule with condition payments.amount > 500 AND actor.role != "owner", When a payment of 600 is initiated by a manager, Then the system requires 2FA before proceeding. - Given a rule condition referencing an unknown field or unsupported operator, When validating the pack, Then validation fails with a condition syntax error including the offending JSONPath. - Given nested conditions using AND/OR and parentheses, When evaluated, Then short-circuit and operator precedence behave as documented and produce deterministic results.
Cross-Module Policy Enforcement Consistency
- Given an active pack with permission rule "no exports for assistants", When a user with role "assistant" attempts CSV export from any module, Then the action is blocked, a user-facing reason is shown, and an audit log entry is recorded. - Given a network_ip rule allowlist of office CIDRs, When an approval-only action is attempted from a non-allowed IP, Then the action is denied and the event is logged with source IP. - Given a step_up_auth rule threshold of 500 on payments, When a payment of 600 is attempted, Then 2FA is enforced; when 400, Then no step-up is required. - Given a data masking rule "mask tenant phone for vendors until appointment confirmed", When a vendor views the request details in scheduling or SMS threads pre-confirmation, Then the phone is masked consistently across modules.
Portable JSON Import/Export
- Given a policy pack exported as JSON from Tenant A on schemaVersion 1.x, When imported into Tenant B supporting schemaVersion 1.x, Then import succeeds and a checksum/hash of normalized JSON matches between A and B. - Given an import where the target supports 1.(x+1) with backward-compatible changes, When importing, Then the importer maps defaults for new fields, preserves unknown fields under metadata.extensions, and logs a compatibility warning. - Given an import where schemaVersion major differs (e.g., 1.x to 2.x), When importing, Then the import is rejected with an incompatible schema error and no partial data is persisted. - Given an exported pack with references to scope identifiers, When imported into a tenant with different IDs, Then a mapping step is required and validated; missing mappings cause the import to fail with a clear report.
Real-time Policy Enforcement Engine
"As a platform owner, I want policies enforced consistently at runtime so that prohibited actions are blocked regardless of interface or entry point."
Description

Implement a centralized decision service that evaluates Policy Packs at runtime for every protected operation in UI and API (view tenant contact, schedule vendor, send SMS, export data, approve spend). Provide deterministic rule evaluation, deny-by-default fallbacks, and contextual inputs (role, property, IP/CIDR, appointment status, amount, time of day). Expose lightweight SDK/hooks for client and server enforcement points with consistent error messages and user guidance. Cache policy decisions safely, log denials, and ensure enforcement covers direct API calls to prevent bypassing the UI.

Acceptance Criteria
Deny-by-Default Policy Fallback
Given any protected operation request where no matching allow rule is found in the active Policy Pack for the property When the decision engine evaluates the request Then the operation is denied with HTTP 403 (API) or disabled/blocked UI control and error code POLICY_DENIED And a user-facing message explains which policy prerequisite is unmet and how to proceed And a denial audit log entry is created with userId, action, resourceId, propertyId, timestamp, reason=no_matching_allow, correlationId Given the policy engine is unreachable or returns an error or times out after 200 ms When a protected operation is evaluated Then the operation is denied with error code POLICY_UNAVAILABLE and retry guidance And the outage is logged with severity=warn and includes duration and endpoint Given the same inputs (user, role(s), property, resource, context attributes) are evaluated repeatedly within 60 seconds When the decision is requested 10 times Then the decision outcome, error code, and decisionId are identical
Mask Tenant Phone Until Appointment Confirmed
Given a user with role Vendor attempts to view a tenant phone number for a work order at a property where a Policy Pack enforces masking until appointment=confirmed When appointment.status is not confirmed Then the phone value is masked in UI and API responses (e.g., XXX-XXX-1234) and export/notification payloads And the response includes reason code POLICY_MASKED and guidance to confirm appointment Given the appointment status transitions to confirmed When the same vendor requests the tenant phone within 5 seconds of the status change Then the full phone is returned if no other rule denies it And an audit log records the unmask event with actor and appointmentId Given a non-vendor (e.g., Manager) with allow rule for viewing contact When requesting the tenant phone prior to confirmation Then the policy for that role applies (e.g., unmasked if allowed)
Require 2FA for Approvals Above Threshold
Given a user attempts to approve spend with amount >= the Policy Pack threshold for the property (e.g., $500) When the user does not have a valid 2FA assertion within the policy’s window Then the operation is blocked with error code POLICY_2FA_REQUIRED and a 2FA challenge is initiated via the SDK And no approval side effects occur Given the user successfully completes 2FA within the allowed time window When the approval is resubmitted with the provided challenge token Then the approval succeeds if no other rule denies it And the audit log records 2FA_verified=true with amount Given an API client attempts to approve above threshold without 2FA When calling the approval endpoint Then the API returns 403 with machine-readable details: { code: "POLICY_2FA_REQUIRED", action: "approve_spend", threshold, propertyId }
No Exports for Assistants
Given a user with role Assistant at a property with a Policy Pack rule "no exports for assistants" When the user attempts any export action in UI or API Then the UI export controls are disabled with tooltip referencing policy And the server export endpoints return 403 with code POLICY_DENIED and do not generate files And an audit log records the denied attempt with export type Given a Manager role at the same property When performing export Then the export proceeds if allowed by policy
Approvals Restricted to Office IPs/CIDRs
Given a user attempts to approve an action at a property with a Policy Pack rule restricting approvals to specific IP/CIDR ranges When the request originates from a client IP not in the allowed ranges (validated via trusted proxy headers) Then the operation is denied with 403 and code POLICY_IP_DENY and guidance to connect to office/VPN And the audit log captures clientIp, matchedCidrs=[], and action Given the same user connected from an allowed IPv4 or IPv6 address When submitting the approval Then the approval is allowed if no other rule denies it Given a request with spoofed X-Forwarded-For from an untrusted source When evaluated Then the engine uses the configured trusted proxy chain to compute client IP and still denies outside allowed CIDRs
Consistent Enforcement Across UI and API via SDK/Hooks
Given a protected operation is initiated from the web UI When the UI SDK pre-check hook evaluates the decision Then the UI reflects the decision (enable, disable, mask, block) and shows standardized messages and codes consistent with server responses Given the same operation is invoked directly via API bypassing the UI When the server authorization hook evaluates the decision Then the outcome matches the UI decision for identical context and returns the same code and message template Given SDK integration errors in the UI (e.g., network failure) When the pre-check cannot complete within 200 ms Then the UI defaults to deny and surfaces POLICY_UNAVAILABLE with retry guidance Given a new action is introduced When no enforcement point is registered Then CI fails with a test asserting coverage and the action is not callable in production without a policy mapping
Safe Decision Caching and Invalidation
Given decisions are cached to improve latency When evaluating an allow decision with a cacheable context Then the cache key includes userId, roles hash, propertyId, action, resource type/id, and all context attributes that affect the rule (e.g., ip, appointmentStatus, amount, timeOfDay) And the positive decision TTL is <= 5 minutes and negative decision TTL is <= 1 minute Given a relevant context change or policy update (e.g., appointment becomes confirmed, Policy Pack edited, role changed) When a subsequent decision is requested Then any cached decision is invalidated or bypassed within 5 seconds, and the new decision reflects the change Given the cache is stale or unavailable When evaluating a decision Then the system never returns an allow based solely on stale cache for actions marked high-risk; default is deny until recomputed Given observability is enabled When monitoring the decision service under load Then p95 decision latency with cache <= 50 ms and without cache <= 150 ms, and cache hit/miss metrics are emitted
Cross-Property Assignment & Inheritance
"As an admin, I want to assign a policy pack across multiple buildings with predictable overrides so that compliance is standardized at scale."
Description

Enable applying Policy Packs to portfolios, building groups, and individual properties with clear precedence and inheritance rules. Support bulk assignment, scheduled rollouts, and safe previews before publish. When multiple packs apply, resolve via defined priority or explicit overrides. Provide drift detection to highlight properties not aligned with assigned packs, ensuring standardized compliance at scale without manual per-property setup.

Acceptance Criteria
Portfolio-Level Pack Inheritance to Groups and Properties
Given a Policy Pack is assigned to a Portfolio containing Building Groups and Properties When inheritance is enabled for the assignment Then all child Building Groups and Properties display the pack in their Effective Policies within 60 seconds And inherited policies are labeled as Inherited with the source path (Portfolio > Group > Property) And excluding a child from inheritance prevents the pack from appearing on that child and its descendants And removing the portfolio assignment removes the inherited pack from all children within 60 seconds unless an explicit local assignment exists
Conflict Resolution via Priority and Explicit Overrides
Given a Property is targeted by two or more Policy Packs with overlapping rules And each pack has a numeric priority where a higher number means higher precedence When Effective Policies are calculated Then for each conflicting rule the rule from the highest-priority pack is applied And if a Property-level explicit override exists for a rule, that override supersedes any pack regardless of priority And if two packs have the same priority and contain a conflicting rule, publish is blocked with a validation error requiring the conflict to be resolved before proceeding And the Effective Policies view shows the winning rule and its source with a justification (priority or explicit override)
Bulk Assignment with Transaction Safety and Audit Trail
Given an Admin selects a mix of Portfolios, Building Groups, and Properties totaling N entities When performing a Bulk Assign of a Policy Pack with inheritance settings Then at least 99% of targeted entities succeed or the operation rolls back with no changes applied And any partial failures produce a detailed per-entity error report and a retry option And an immutable audit record is created including initiator, timestamp (UTC), scope, counts (selected, succeeded, failed), and inheritance flags And the UI shows progress and final outcome within 2 minutes for up to 10,000 targets
Scheduled Rollout with Time Windows and Idempotent Publish
Given a Policy Pack assignment is scheduled with a specific datetime and timezone and an optional maintenance window When the scheduled time arrives Then the pack is published exactly once, within the defined window, and Effective Policies update within 60 seconds And if the window is missed (e.g., system downtime), the publish occurs at next window start and is logged as deferred And the schedule can be canceled or rescheduled up to 5 minutes before execution And execution results (success/failure and counts) are recorded in the audit trail And re-running the same schedule does not duplicate assignments (idempotent)
Safe Preview (Dry Run) Showing Impact and Blocking on Stale State
Given an Admin opens Preview for a Policy Pack assignment or change When the Preview is generated Then the system shows counts of affected Portfolios/Groups/Properties, rules added/removed/overridden, and any conflicts detected And no live data or Effective Policies are changed during Preview And the Preview snapshot records the entity versions and expires after 24 hours And attempting to publish from an expired or stale Preview (entities changed since snapshot) is blocked with a prompt to regenerate the Preview And the actual publish outcome must match the latest valid Preview snapshot in affected counts and rule deltas, otherwise the publish is aborted with details
Drift Detection, Reporting, and One-Click Remediation
Given Drift Detection runs on a schedule (at least daily) or on-demand When effective policies on a Property do not match the assigned packs and inheritance rules Then the Property is flagged as Drifted with a category (Local Override, Missing Assignment, Stale Version, Unauthorized Change) And a Drift Report lists all drifted entities with timestamps, diffs, and root-cause classification And authorized users can remediate drift in bulk (realign to assigned packs) or accept as an approved exception with an expiration date And remediation actions are audited and update Effective Policies within 60 seconds And email or in-app notifications are sent when drift exceeds a configurable threshold of Properties
Conditional Data Masking for Contacts
"As a landlord, I want vendor-facing contact details masked until an appointment is confirmed so that tenant privacy is protected without slowing scheduling."
Description

Provide rule-driven masking for sensitive fields (e.g., hide tenant phone/email from vendors until appointment is confirmed or vendor is verified). Support partial reveals (last 2 digits), reveal-on-condition transitions, and audit logs of reveal events. Ensure masking is enforced in all surfaces—vendor portal, SMS templates, work order details, notifications, and exports—while allowing authorized roles to view unmasked data per policy.

Acceptance Criteria
Vendor contact masking pre-confirmation
Given a property has a Policy Pack rule "mask tenant contact for vendors until appointment confirmed or vendor verified" And a vendor assigned to a work order is not verified And the appointment for that work order is not confirmed When the vendor views tenant phone and email in the vendor portal or via vendor-scoped API Then the tenant phone is masked to show only the last two digits (e.g., "********34"), containing no other digits And the tenant email is shown as "[hidden]" And copy-to-clipboard, print views, and accessibility trees expose only the masked values And client-side inspection, query parameters, or caching do not expose unmasked values for the vendor
Reveal on appointment confirmation
Given the Policy Pack rule "mask tenant contact for vendors until appointment confirmed or vendor verified" is active for the property And a vendor is assigned to a work order and remains unverified When the appointment is marked confirmed for that work order Then the assigned vendor can view full tenant phone and email across vendor portal, notifications, and vendor-scoped API within 5 seconds And an audit log entry is created with fields: event_type="contact_reveal", reason="appointment_confirmed", work_order_id, tenant_id, vendor_id, policy_pack_id, actor_id, actor_type, surfaces, timestamp (UTC) And only the assigned vendor gains access; other vendors continue to see masked data
Reveal on vendor verification
Given the Policy Pack rule "mask tenant contact for vendors until appointment confirmed or vendor verified" is active for the property And a vendor is assigned to one or more open work orders with unconfirmed appointments When the vendor's status changes to Verified by an authorized action or automated check Then the verified vendor can view full tenant phone and email for their assigned open work orders within 5 seconds And an audit log entry is created with fields: event_type="contact_reveal", reason="vendor_verified", vendor_id, affected_work_order_ids, tenant_ids, policy_pack_id, actor_id, actor_type, timestamp (UTC) And unassigned or unverified vendors continue to see masked data
Cross-surface masking enforcement
Given a work order with an unverified vendor and an unconfirmed appointment under the masking Policy Pack When tenant contact appears across surfaces: vendor portal lists and detail pages, SMS templates, notification previews and sends, internal work order views visible to restricted roles, and CSV/PDF exports initiated by restricted viewers Then the tenant phone is rendered as "********NN" where NN are the last two digits and no other digits are present And the tenant email is rendered as "[hidden]" And no unmasked contact data appears in any payloads, previews, final sends, or exported files for restricted viewers And automated tests exist that validate masking on each listed surface
Authorized role access and assistant export restriction
Given a Policy Pack that allows Manager and Owner roles to view unmasked contacts and denies exports for Assistant role When a Manager views a work order in the internal dashboard Then the Manager sees full tenant phone and email When an Assistant views the same work order Then the Assistant sees masked contact info (phone as "********NN", email as "[hidden]") When an Assistant attempts to export work orders or contacts Then the export is blocked with HTTP 403 (or equivalent UI error) and an audit log "export_blocked" is recorded with actor_id and policy_pack_id When a Manager performs an export Then the file contains unmasked contacts only for rows the Manager is authorized to view and an audit log "export_generated" is recorded
Re-mask on condition reversal
Given a vendor previously had tenant contact revealed due to appointment confirmation or vendor verification When the appointment is canceled or the vendor's verification status is revoked Then the vendor's access to tenant contact is re-masked within 60 seconds across portal, API, notifications, and exports And an audit log entry is created with fields: event_type="contact_remask", prior_reason, new_reason, work_order_id(s), vendor_id, policy_pack_id, timestamp (UTC) And previously sent notifications remain unchanged while all future views show masked data
Fail-safe masking and API hardening
Given the masking Policy Pack applies to a work order And the policy engine is unreachable, times out, or returns an error during evaluation When a restricted viewer requests tenant contact via UI or API Then the request succeeds with masked values (no unmasked data returned) and no 5xx is surfaced to the end user due to masking And masked values are not present in client-side source, network payloads, caches, search indexes, analytics, or logs for restricted viewers And an internal alert and diagnostic log "policy_engine_unavailable" are recorded without including unmasked contact data
Step-up 2FA Controls by Risk Threshold
"As an owner, I want 2FA required for high‑risk actions so that fraud and costly mistakes are minimized."
Description

Introduce policy-driven step-up authentication for high-risk actions (e.g., approvals above $500, bank detail changes, export of PII). Support TOTP and SMS factors, configurable thresholds per pack, grace windows (remembered device/session), and clear in-flow prompts. Integrate with the enforcement engine so 2FA is required only when the policy’s conditions are met, and record completion in the audit log.

Acceptance Criteria
Approval  Threshold Requires Step-Up
Given an approval action amount equals or exceeds the Policy Pack's Approval2FAThreshold for the property and the user's current session has no valid step-up within GraceWindowMinutes, when the user clicks Approve, then the action is blocked and a 2FA prompt is displayed offering TOTP and SMS options. Given the user submits a correct TOTP code from a registered authenticator or a correct SMS code within CodeTTLSeconds, when verification succeeds, then the approval is applied and the UI shows success without page reload. Given the user fails verification after MaxAttempts or cancels, when the attempt limit is reached or canceled, then the approval remains unapproved and a descriptive error is shown. Given verification succeeds, then an audit log entry is created containing policyPackId, ruleId, actorUserId, targetEntityId, action, amount, factorType, verificationResult=success, timestamp, ipAddress, and graceWindowSatisfied=false.
PII Export Triggers Step-Up Only Under Policy
Given ExportPIIRequires2FA=true in the active Policy Pack and no valid step-up exists in session, when a user with export permission initiates a PII export, then the system requires 2FA before file generation begins. Given ExportPIIRequires2FA=false in the active Policy Pack, when the same user initiates export, then no 2FA prompt is shown and the export proceeds. Given verification succeeds, when file generation starts, then it begins within 2 seconds and the audit log records factorType and exportArtifactId. Given verification fails or times out, when the export is attempted, then no file is generated and an audit entry with verificationResult=failed is stored.
Bank Details Change Requires Step-Up and Prevents Partial Save
Given BankChangeRequires2FA=true in the active Policy Pack and no valid step-up exists, when a user creates or updates routing/account fields and clicks Save, then a 2FA prompt is shown and the save is deferred. Given 2FA verification succeeds, when the save proceeds, then the bank details are persisted atomically and old values remain unchanged until success; an audit diff of changed fields (masked) is recorded. Given verification fails, when the user attempted save, then no bank detail changes are saved and the form shows an inline error; an audit entry includes attempted changes redacted. Given BankChangeRequires2FA=false, when a user saves bank details, then no 2FA is prompted.
Grace Window and Remembered Device
Given a user completes step-up successfully and selects RememberDevice and GraceWindowMinutes=30, when the same user performs protected actions within 30 minutes from the same device/browser fingerprint, then no prompt is shown and audit logs note graceWindowSatisfied=true. Given the same user attempts a protected action after GraceWindowMinutes elapse or from a different device/browser fingerprint or IP when EnforceIPBinding=true, then 2FA is required again. Given RememberDevice is disabled by policy, when step-up completes, then the RememberDevice option is not shown and subsequent protected actions require 2FA unless within the same uninterrupted action flow.
Per-Pack Threshold and Scope Resolution
Given multiple properties with different Policy Packs and thresholds, when a user initiates a protected action within Property A's context, then only Property A's Policy Pack is evaluated and applied. Given an action affects multiple properties simultaneously, when policies differ, then the enforcement engine applies the most restrictive combination (e.g., lowest threshold, smallest grace window) and records policyResolutionStrategy in audit. Given policy evaluation is executed, when measuring performance, then evaluation latency per request is 2150 ms at P95 and 2400 ms at P99.
Audit Log Completeness and Searchability
Given any step-up verification attempt occurs, when it is processed, then exactly one audit event is recorded per attempt with fields: policyPackId, ruleId, action, factorType, attemptNumber, result, userId, entityId, timestamp, ip, userAgent, propertyId, correlationId. Given a verification succeeds and the protected action is executed, when logging, then an additional audit event records the protected action outcome linked via correlationId to the verification attempt. Given an admin searches the audit log by userId, propertyId, date range, and action type, when the query is executed, then step-up records are returned within 2 seconds including all fields above.
Factor Availability, Selection, and Delivery Resilience
Given the user has both TOTP and SMS enrolled, when step-up is required, then the prompt defaults to the last-used factor and allows switching; SMS phone is displayed obfuscated (e.g., +1070734). Given only one factor is enrolled, when step-up is required, then only that factor is offered. Given no factors are enrolled and a verified phone exists and AllowSMSBootstrap=true, when step-up is required, then the user can bootstrap SMS by verifying an OTP to that phone and proceed. Given no factors are available and bootstrap is disallowed, when step-up is required, then the action is blocked and the user is shown guidance to contact an admin. Given an SMS delivery failure occurs, when the user requests resend, then up to ResendLimit resends are allowed and switching to TOTP remains available.
Granular Action Permissions & Export Controls
"As an operations lead, I want to restrict data exports for assistant roles so that sensitive information cannot be exfiltrated."
Description

Deliver a policy-driven capability matrix that governs specific actions by role and context (e.g., no exports for assistants, no delete for vendors, approvals only from office IPs). Map policies to a canonical action taxonomy used by UI controls and API endpoints to guarantee consistent enforcement. Include clear denial messaging, optional request-access flow, and coverage of bulk operations and scheduled jobs.

Acceptance Criteria
Assistant Export Restriction Enforced Across UI and API
Given an assistant user assigned to Property A and Policy Pack PP1 with rule "no exports for assistants" is active And the canonical action "export.data" is denied for role "assistant" on Property A When the assistant visits the Property A reports page Then the Export control is hidden or disabled with tooltip "Exports disabled by policy" And keyboard shortcuts and deep links to export are blocked with an inline message When the assistant calls POST /api/exports with property_id=Property A Then the request is rejected with 403 and error_code="export.forbidden_by_policy" and includes policy_pack_id, action_id, and property_id And an audit log entry "export.blocked" is recorded with user_id, property_id, action_id, policy_pack_id, and reason And a "Request access" link is presented which opens an approval request prefilled with action_id and property_id
Vendor Delete Prohibited and Consistently Enforced
Given a vendor user associated with Work Order WO123 in Property B and the policy denies "ticket.delete" and "comment.delete" for vendors When the vendor opens WO123 in the UI Then Delete controls for the ticket and its comments are not visible When the vendor attempts DELETE /api/tickets/WO123 or DELETE /api/comments/{commentId} Then the system returns 403 with error_code="delete.forbidden_by_role" and includes action_id And no records are deleted And an audit log "delete.blocked" is recorded with user_id, resource_id, action_id, and policy_pack_id
Approvals Restricted to Office IP Addresses
Given an approver for Property C and Policy Pack PP2 with allowed CIDR 203.0.113.0/24 for approvals When the approver attempts to approve Work Order WO555 from IP 198.51.100.10 Then the Approve control is disabled with message "Approvals allowed only from office network" When the approver calls POST /api/approvals with ip=198.51.100.10 Then the system responds 403 with error_code="ip_restriction" and includes required_cidr and policy_pack_id When the same user retries from IP 203.0.113.42 Then the approval succeeds (200) and an audit log records source_ip, action_id, and policy_pack_id
2FA Required for High-Value Approvals
Given Policy Pack PP3 enforces 2FA for approvals over $500 And user U1 has approver role and has not completed 2FA within the last 5 minutes When U1 attempts to approve a payout of $750 Then a 2FA challenge is required and the Approve action is blocked until successful verification within 5 minutes When U1 submits a valid 2FA code within 5 minutes Then the approval succeeds (200) and audit logs include mfa_required=true and mfa_verified_at When U1 submits an invalid or expired code Then the approval fails with 401 error_code="mfa_required_or_invalid" and no state is changed
Canonical Action Taxonomy Drives UI and API Enforcement
Given each UI control and API endpoint declares the canonical action_id(s) it requires And Policy Pack PP4 denies action_ids "export.data" and "ticket.delete" for role "assistant" across all properties When policies in PP4 are updated Then the permission cache invalidates and new rules take effect across UI and API within 60 seconds When an assistant attempts any control bound to those action_ids in any property Then the controls reflect the denied state and related API calls are rejected with 403 including action_id and policy_pack_id And telemetry events include action_id and policy_decision (allow/deny) for each evaluated request
Bulk Exports and Scheduled Jobs Evaluate Permissions at Execution Time
Given user U2 schedules bulk export job J1 for Property D to run at 02:00 and has export permission at schedule time And Policy Pack PP5 may change before execution When J1 starts at 02:00 Then the system re-evaluates U2's permissions, IP rules, and 2FA freshness at execution time And if evaluation denies export, J1 is canceled with status="policy_blocked", zero files generated, and a notification is sent to U2 And if evaluation allows export, J1 completes successfully and audit logs include evaluated_at, action_id, and policy_pack_id
Denial Messaging and Request-Access Flow
Given a user attempts an action that is denied by a Policy Pack When the denial occurs in the UI Then a clear message names the action, property context, and policy pack, and no sensitive details (e.g., CIDR lists) are revealed And a "Request access" CTA is shown if enabled, opening a prefilled request routed to approvers When the denial occurs via API Then the response is HTTP 403 with error_code, action_id, policy_pack_id, and correlation_id And the event is recorded in audit logs with user_id, resource context, action_id, and reason
Policy Exceptions & Approvals with Audit Trail
"As a supervisor, I want to approve temporary exceptions to strict policies so that work can continue while maintaining accountability."
Description

Provide a time-bound exception workflow when a rule blocks an action: users can request an exception with reason; designated approvers receive notifications; approvals are recorded with who/when/why, and exceptions auto-expire. All denials, approvals, and exception uses are captured in an immutable audit trail with reporting to support compliance reviews and incident investigations.

Acceptance Criteria
Exception Request on Blocked Vendor Contact Unmasking
Given a user attempts to unmask a tenant phone number and a policy blocks the action until an appointment is confirmed When the user selects "Request Exception" Then the system displays a form requiring a non-empty reason and a time window within the configured maximum And the request is saved with Pending status, capturing requester identity, policy rule, target entity, scope (property/building), timestamp, and requested duration And the blocked action remains blocked until approval
Approver Notification, Decision, and Escalation
Given an exception request is submitted When designated approvers exist for the relevant property/policy Then the system sends in-app and email notifications to approvers and assigns the request per routing rules And approvers can Approve or Deny; approval comment optional, denial reason required And on approval, the system grants the exception for the approved time window and notifies the requester And on denial, the requester is notified and the action remains blocked And if no approver acts within the configured escalation time, the request escalates to fallback approvers per configuration
Immutable Audit Trail for Requests, Decisions, and Uses
Given any exception lifecycle event occurs (request, approval, denial, expiry, revocation, use) When the event is recorded Then an append-only audit entry is created capturing event type, UTC timestamp, actor ID and role, target action, scope, reason/comment, requester/approver IDs, client IP, and correlation IDs And no role can edit or delete existing audit entries; attempts are blocked and logged And each exception use (performing the previously blocked action under an active exception) creates a linked audit entry referencing the approval
Auto-Expiry and Early Revocation of Exceptions
Given an exception has an approved start and end time When the end time is reached Then the exception automatically expires, the extra permission is revoked, and further attempts to use it are blocked and audited And if an approver/admin revokes early, access is removed immediately and the revocation is audited And the requester is notified upon expiry or revocation
Least-Privilege Scope Enforcement for Exceptions
Given an exception is approved for a specific blocked action and scope When the requester uses the exception Then access is limited to the exact action, entity, and property in the approval and does not grant unrelated permissions And mandatory controls (e.g., 2FA, office IP requirements) remain enforced unless explicitly included in the approval scope And the exception cannot be reused outside its time window or transferred to another user
Compliance Reporting for Exceptions and Outcomes
Given a compliance reviewer opens the audit report When they apply filters (date range, property/building, policy rule, outcome, requester/approver, action type) Then the system returns matching audit entries with pagination and totals and supports CSV export only for roles with Export Audit permission And all report exports are themselves audited with who, when, and applied filters And reports include a link to view the full exception chain (request, decisions, uses) per exception

Contact Shield

Protect tenant privacy with dynamic masking of phone, email, and unit contact details. Use proxy numbers for SMS/calls and require reasoned reveal with auto-expiry and a full audit trail. Vendors get the info only when they need it, and managers avoid accidental oversharing.

Requirements

Dynamic Proxy Numbering & Routing
"As a property manager, I want tenant-vendor communications to flow through proxy numbers so that both parties can coordinate without exposing personal contact details."
Description

Provision and manage per-work-order proxy phone numbers that mask tenant and vendor real contact details for SMS and voice. Route inbound and outbound messages/calls through the proxy, preserving original sender/recipient privacy while maintaining conversation continuity. Support localized numbers per property region, SMS delivery receipts, call bridging, voicemail with transcription, and tagging of communications to the corresponding work order and unit. Integrate with the shared calendar to route to the currently assigned vendor, with after-hours routing rules and configurable voicemail/fallback destinations. Expose admin settings for pool sizing, number recycling, and cost tracking. Instrument with metrics and alerts for delivery failures and unreachable numbers.

Acceptance Criteria
Per-Work-Order Proxy Provisioning & Persistence
Given a new work order with a tenant and an assigned vendor in a specific property region When the work order is saved Then the system allocates a unique proxy phone number tied to the work order and region And the proxy masks both parties’ real numbers on all SMS/calls Given an existing work order already has a proxy and the participants remain unchanged When the work order is updated Then the same proxy number is retained Given the assigned vendor changes via calendar/assignment When messages/calls arrive on the proxy Then routing is updated to the current vendor without exposing any real phone numbers And past messages/calls remain linked to the original work order and unit
Localized Number Selection by Property Region
Given the property region has available numbers in the configured pool When a proxy is provisioned Then the proxy’s country and area code match the region per configuration Given the regional pool is exhausted and fallback is enabled When a proxy is provisioned Then the system selects the nearest regional area code within the same country And records a warning event for capacity Given the regional pool is exhausted and fallback is disabled When a proxy is requested Then the system fails the allocation, surfaces an actionable error to the admin, and emits an alert within 60 seconds
SMS Routing, Delivery Receipts, and Conversation Continuity
Given a tenant sends an SMS to the work order’s proxy number during any hour When the message is received Then it is forwarded to the currently assigned vendor via SMS with the tenant’s number masked And a delivery status (queued, sent, delivered, failed) is recorded within 10 seconds Given the vendor replies via SMS to the proxy When the reply is received Then the tenant receives the message from the proxy number And the conversation is threaded under the work order and unit timeline Given the carrier returns a delivery failure or unreachable code When the event is received Then the failure code and timestamp are logged on the message And the failure metric is incremented And an alert is sent if 5+ failures occur within 15 minutes for the same work order
Voice Call Bridging and Masking
Given a tenant calls the work order’s proxy number during business hours When the call is received Then the system bridges the call to the currently assigned vendor And both caller IDs presented are the proxy number And call start/end time and duration are logged and linked to the work order Given a vendor initiates an outbound call using click-to-call from FixFlow When the system dials out Then the tenant sees the proxy number as caller ID And the call is logged and linked to the work order and unit Given a call cannot be completed (busy, unreachable, no answer) When the condition is detected Then the call is routed per configured fallback (retry, voicemail, or escalation) And a reason code is stored
Voicemail with Transcription and Configurable Fallbacks
Given a bridged call is unanswered for the configured ring duration When timeout occurs Then the caller is sent to the work order/property voicemail greeting And the voicemail is recorded and attached to the work order and unit Given a voicemail is recorded When transcription is processed Then the transcript is available within 3 minutes with a confidence score And notifications are sent per settings to the manager and assigned vendor Given an after-hours window is active When a call arrives Then the call goes directly to the configured voicemail or on-call destination per schedule
Calendar-Aware Routing and After-Hours Rules
Given the shared calendar shows a different vendor assigned to the work order at the current time When an SMS/call arrives on the proxy Then routing selects that vendor as the recipient And an audit entry records the rule, timestamp, and recipient Given no vendor is assigned for the current window When an SMS/call arrives Then the message/call is routed to the property fallback destination And the manager is notified within 1 minute Given business hours and after-hours schedules are configured per property When the current time changes state Then routing behavior switches accordingly without manual intervention
Admin Controls, Recycling, Cost, and Instrumentation
Given an admin sets pool size thresholds per region When available numbers drop below the threshold Then an alert is sent to the configured channel and a capacity warning is visible in the admin UI Given a work order is closed and the retention period elapses When recycling runs Then the proxy number is released back to the regional pool And all active routing is disabled And the audit history remains accessible Given cost tracking is enabled When monthly billing data is generated Then per-number rental and per-message/call usage costs are attributed to each property and work order Given delivery failures or unreachable numbers exceed the configured rate (>5% over 15 minutes) for a region When the metric is computed Then the system emits an alert and surfaces a dashboard warning with the impacted carriers/regions
Reasoned Reveal Gate with Manager Approval
"As an operations lead, I want any access to tenant contact details to require a documented reason and approval so that privacy is protected and unnecessary exposure is prevented."
Description

Introduce a controlled workflow to reveal real tenant contact details only when necessary. When a user requests a reveal, require a written reason, scope (specific fields requested), and intended duration. Support optional manager approval, with notifications and in-app approval queue. Enforce auto-re-masking after the approved time window, and limit copy/download actions where applicable. Log the requester, approver, reason, and timestamps to the audit trail. Provide emergency override with dual-confirm and mandatory post-incident review notes.

Acceptance Criteria
Submit Reveal Request with Reason, Scope, and Duration
Given an authenticated user with "Request Reveal" permission and an assigned work order When they open the "Request Reveal" form Then the form displays fields: Reason (required), Scope (multi-select: Phone, Email, Unit Address), Duration (required) And the submit button remains disabled until all required fields are valid Given the user enters a Reason of 15–500 characters, selects at least one Scope field, and sets a Duration between 15 minutes and 14 days When they submit the form Then a Reveal Request record with status "Pending Approval" and a unique Request ID is created And the request is linked to the work order and tenant And an audit entry is recorded with requester, reason, scope, requested duration, and timestamp And the requester sees a confirmation with the Request ID Given any field is invalid (e.g., reason too short, no scope selected, duration out of range) When the user attempts to submit Then submission is blocked and inline error messages specify the violated rule
Manager Approval Queue and Decision Workflow
Given a Reveal Request is in status "Pending Approval" and the organization policy requires manager approval When a user with "Approve Reveal" permission opens the Approval Queue Then the request appears with requester, reason, scope, requested duration, age, and links to the work order Given an approver selects Approve and confirms When the decision is submitted Then the request status changes to "Approved" And approver identity and approval timestamp are written to the audit trail And the requester receives an in-app and email notification of approval Given an approver selects Decline and enters a decision note of at least 10 characters When the decision is submitted Then the request status changes to "Declined" And the requester is notified with the decision note And the decision note is captured in the audit trail Given no action is taken for 24 hours after submission When the SLA threshold is reached Then reminders are sent to approvers and the request is marked "Overdue" in the queue
Controlled Reveal Enforces Scope and Anti-Exfiltration
Given a Reveal Request is in status "Approved" and within the approved time window When the requester opens the tenant contact panel Then only the approved Scope fields render in clear text with an "Revealed until <expiry>" indicator And all non-approved fields remain masked Given the revealed panel is displayed When the user attempts to copy, export, download, or retrieve revealed data via UI, keyboard shortcuts, or API endpoints Then copy-to-clipboard is blocked, export/download buttons are disabled, and export APIs return HTTP 403 And each access or block attempt is recorded in the audit trail Given a vendor without reveal rights views the work order When they access tenant contact details Then they see only proxy contact information unless the approval explicitly includes the vendor role and session
Auto Re-masking After Expiry and Early Revocation
Given a Reveal Request is Approved with an expiry time When the expiry time is reached Then the system re-masks previously revealed fields across all active sessions within 60 seconds And subsequent API calls return masked values for those fields And an auto-remask event is written to the audit trail Given a user with revoke permission selects "Revoke Now" prior to expiry When they confirm the revocation Then the reveal status changes to "Revoked" And fields are re-masked immediately And the requester and any active viewers receive a revocation notification Given a user keeps the reveal panel open as the expiry occurs When the expiry time passes Then the panel updates to masked state without page reload and displays an "Access expired" message Given a user seeks more time When they submit an "Extend" request with a new duration Then a new Reveal Request is created in status "Pending Approval" and linked to the original
Emergency Override with Dual Confirm and Post‑Incident Review
Given an urgent safety incident is associated with a work order When a user with "Emergency Reveal" permission initiates an Emergency Override Then they must provide a reason of at least 25 characters and select a duration not exceeding 2 hours And a distinct second user with "Emergency Confirm" permission must confirm within 5 minutes or the request auto-cancels And the same user cannot act as both requester and confirmer Given dual confirmation completes When the emergency reveal activates Then the requested fields are revealed immediately with an "Emergency" badge And all access events are logged with requester, confirmer, timestamps, IPs, device fingerprints, and geolocation Given the emergency reveal ends or is revoked When the incident owner is prompted for a post-incident review Then they must submit review notes of at least 50 characters within 24 hours And failure to submit triggers reminders every 12 hours to the owner and managers until completed
Audit Trail Completeness and Integrity
Given any reveal lifecycle event occurs (create, approve, decline, activate, access, revoke, auto-expire) When the audit subsystem records the event Then the entry includes: Request ID, Work Order ID, Tenant ID, requester, approver (if any), reason, scope, requested duration, final duration, outcome, timestamps, IP, device fingerprint, and actor role Given an auditor with "View Audit" permission filters logs by date range, work order, tenant, requester, or outcome When they execute the query Then results return within 3 seconds for up to 10,000 records and include immutable entries with a cryptographic integrity hash And permitted users can export results to CSV; others receive HTTP 403 Given a non-system user attempts to alter or delete an audit entry When the action is performed Then the system prevents modification and records a tamper-attempt event with user, timestamp, and IP
Timeboxed Access & Calendar Windows
"As a scheduler, I want contact access to be automatically limited to appointment windows so that vendors have what they need when they need it without lingering exposure."
Description

Tie access to real contact details and proxy availability to scheduled events. Automatically open communication windows around confirmed appointments (e.g., 2 hours before to 2 hours after) and close access outside those windows. Auto-expire proxies and reveals when a work order is completed or inactive past a configurable TTL. Handle reschedules by extending windows and notifying participants of updated availability. Provide portfolio-level defaults and per-work-order overrides, with clear UI indicators of current access state and remaining time.

Acceptance Criteria
Auto-Open and Close Communication Window Around Appointment
Given a confirmed appointment from 10:00 to 12:00 in the property timezone and portfolio defaults set to open 2 hours before and close 2 hours after When the current time is between 08:00 and 14:00 Then vendor-to-tenant proxy SMS/calls are allowed and messages are delivered And requests to reveal real contact details are permitted And attempts outside 08:00–14:00 are blocked And the access window opens exactly at 08:00 and closes exactly at 14:00
Block Communication Outside Window with Audit Logging
Given access is outside the active window for a work order When a vendor sends an SMS or places a call via the proxy Then the message/call is not delivered to the tenant And the vendor receives a system response indicating access is closed and the next open time And an audit log entry is created with timestamp, actor, channel, and outcome=blocked
Automatic Expiry on Work Order Completion or TTL Inactivity
Given a work order with an active proxy or revealed contact When the work order status changes to Completed Then all proxies and reveals expire immediately and further communication via proxy is blocked And the UI and API reflect access state Closed within 5 seconds And an audit log records the expiry with reason=work_order_completed Given a work order with no activity for the configured TTL of 14 days and no confirmed appointments When the TTL elapses Then all proxies and reveals expire and attempts to use them are blocked And an audit log records the expiry with reason=ttl_expired
Reschedule Extends Window and Notifies Participants
Given a confirmed appointment originally scheduled 10:00–12:00 with a 2h pre/2h post window When it is rescheduled to 13:00–15:00 Then the access window is recalculated to 11:00–17:00 And any previously open window closes immediately at the time of reschedule And the vendor and tenant receive an updated availability notification within 1 minute via SMS and in-app And the audit trail records the old and new times and the actor who rescheduled
Portfolio Defaults and Per-Work-Order Overrides
Given portfolio defaults of pre=2h and post=2h When a manager sets a per-work-order override of pre=1h and post=3h Then that work order’s window uses 1h before and 3h after for all future calculations And clearing the override reverts the work order to portfolio defaults And invalid override values outside 0–12h are rejected with a validation error
UI Indicator of Access State and Countdown
Given a user views a work order When the access is within an active window Then the UI displays Access: Open and shows a countdown to window close in hh:mm that updates at least every 60 seconds When the access is outside the window Then the UI displays Access: Closed and shows the next window start timestamp or the reason for closure (e.g., completed, ttl_expired)
Reasoned Reveal Auto-Expiry and Audit Trail
Given an active window for a work order When a user requests a reveal of real tenant contact details Then a reason field is required and captured And the reveal grants visibility only until the current window ends (or earlier if a shorter expiry is set) And after expiry, details are re-masked and further access is blocked until the next window And the audit trail records actor, reason, fields revealed, and start/expiry timestamps
Comprehensive Audit Trail & Reporting
"As a compliance reviewer, I want a detailed, exportable audit of all PII access and masked communications so that I can verify adherence to privacy policies and investigate incidents."
Description

Capture immutable logs for all sensitive events, including reveal requests, approvals/denials, expirations, proxy provisioning, call/SMS relays, and policy blocks. Store who did what, when, why (reason text), which work order/unit, IP/device fingerprints, and before/after access states. Provide search, filtering, and exports (CSV/JSON) by property, vendor, time range, and user. Surface dashboards for reveal volume, average access duration, policy exceptions, and anomalous patterns with configurable alerts to aid compliance reviews and incident investigations.

Acceptance Criteria
Immutable Sensitive Event Logging With Tamper Evidence
Given a sensitive Contact Shield event occurs (reveal request, approval, denial, expiration, proxy provisioning, call/SMS relay, or policy block) When the event is committed Then an audit record is appended within 2 seconds containing: event_id (UUID), event_type, occurred_at (UTC ISO-8601), actor_user_id, actor_role, actor_ip, device_fingerprint, property_id, unit_id, work_order_id, vendor_id, subject_user_id (if applicable), reason_text (if applicable), before_access_state, after_access_state, policy_id (if applicable), correlation_id, result_status, prev_hash, event_hash And the audit record is write-once: no API exists to update or delete it and the persistence layer rejects updates/deletes to existing records And a daily integrity job recomputes the hash chain and raises an alert if any event_hash or prev_hash mismatch is detected
Full Reveal Lifecycle Audit Coverage
Given a tenant contact reveal is requested with a required reason When an approver approves the reveal with an expiry duration Then audit records exist for the request, the approval, the reveal activation, and the scheduled expiry, each linked by correlation_id and work_order_id And each record includes before_access_state=masked and after_access_state=revealed (or reverts to masked on expiry) And when a reveal is denied, an audit record exists with result_status=denied and includes approver_user_id and denial_reason_text And when a reveal expires or is revoked, an audit record exists with result_status=expired or revoked and records who triggered it and when
Proxy Communication Relay and Policy Block Logging
Given a vendor communicates via a FixFlow proxy number (SMS or call) When a message or call is relayed or blocked by policy Then an audit record captures: direction (inbound/outbound), channel (SMS/call), proxy_number, actor_user_id (vendor), counterparty_role (tenant/manager), relay_timestamp, delivery_status (delivered/failed/blocked), duration_seconds for calls, message_character_count for SMS, policy_id and block_reason when blocked, correlation_id, work_order_id, property_id And the audit log stores no message or call content bodies
Audit Log Search, Filter, Sort, and Pagination
Given an auditor views the audit log UI When they filter by property, vendor, user, event_type, and time range Then the results match the filters and return within 2 seconds for up to 10,000 matching records And results are paginated at 100 records per page with server-side pagination and sortable by occurred_at, event_type, and actor_user_id And the total_count reflects all matches across pages and the current page shows the correct subset And filter and sort state persist via URL query parameters and can be shared
CSV and JSON Export of Filtered Audit Logs
Given an auditor has applied filters to the audit log When they export to CSV or JSON Then the export contains only records that match the active filters and includes the selected fields And CSV headers and JSON keys use snake_case; timestamps are UTC ISO-8601; boolean fields are true/false; optional fields are null when absent And exports support at least 250,000 records or 100 MB per file via streaming with a visible progress indicator And the file includes metadata (export_requested_by, export_requested_at, filter_summary, record_count) And an audit record of the export is created capturing requester, format, record_count, and completion status
Dashboards and Configurable Alerts for Anomalies and KPIs
Given dashboards are enabled for a portfolio When KPIs are computed hourly and daily Then the dashboard displays: daily reveal volume, average access duration, count of policy exceptions, and top vendors by reveal count, with anomaly flags using a 7-day moving average z-score > 3 And an admin can set alert thresholds per property for reveal volume spikes, average access duration above N hours, and policy exception rate above M%, with delivery via email and webhook And when a threshold is crossed, an alert is sent within 5 minutes and an audit record is created with alert_type, threshold, observed_value, recipients, and property_id And dashboards and alerts honor active filters for property, vendor, and time range
Proxy Number Provisioning and Release Audit
Given a proxy number is allocated, reassigned, or released for a work order When the provisioning action occurs (allocated/reassigned/released) or a mapping expires automatically Then an audit record stores: proxy_number, provisioning_action, occurred_at, actor_user_id, vendor_id, property_id, unit_id, work_order_id, mapping_target (tenant/manager/vendor), expiry_at or ttl_seconds, reason_text if manual, prev_mapping on reassignment, and result_status (success/expired) And before_access_state and after_access_state reflect masking transitions if the action changes access
Role-Based Access & Policy Controls
"As an account admin, I want to set fine-grained rules on who can view or request tenant contact info so that our team and vendors operate within defined privacy boundaries."
Description

Implement granular RBAC and policy rules governing who can request, approve, or bypass reveals and who may initiate masked communications. Define policies by role (owner, manager, coordinator, vendor), property/portfolio, vendor company, and work-order status. Support conditions such as "vendors cannot reveal tenant info," "assistants require approval," and "owners can approve for any property." Integrate with SSO group claims where available and expose an admin policy editor with test/preview to validate outcomes before deployment.

Acceptance Criteria
Vendor Communication Restrictions
Given a user with role "Vendor" on any property, When they attempt to reveal tenant phone, email, or unit number via UI or API, Then the operation is denied, no PII is returned, and API responds 403 with a policy decision ID. Given a user with role "Vendor", When viewing any work order, Then only proxy contact details are visible and actionable; real contact fields remain masked in UI and API responses. Given a user with role "Vendor", When attempting a "Request Reveal" action, Then the action is not available (hidden or disabled) and API endpoint returns 403 if called directly. Given a user with role "Vendor" initiates SMS/call from the platform, Then messages/calls route through proxy numbers and the real tenant contact is never displayed or returned by API; the event is audit-logged with the effective policy.
Assistant Reveal Approval Workflow
Given a user with role "Coordinator/Assistant" on an eligible work order, When they click Reveal, Then a reason input is required and a Reveal Request is created with status Pending and an audit record. Given a Pending Reveal Request by an Assistant, When a Manager or Owner approves it, Then the tenant contact is revealed to the requesting Assistant only for the configured expiry duration and the system records approver, timestamp, and expiry. Given a Pending Reveal Request by an Assistant, When it is rejected by an approver, Then no PII is revealed, request status is Rejected, and the UI/API show the rejection state. Given an Approved Reveal for an Assistant, When the expiry time is reached or the work order is closed, Then access auto-expires and masking resumes; subsequent API reads return masked values. Given an Assistant attempts to reveal without an approved request, Then the operation is denied and API responds 403 with a reference to the approval-required policy.
Owner Global Reveal Control
Given a user with role "Owner", When viewing reveal requests for any property in the account, Then they can approve or reject each request regardless of property/portfolio, and the decision is applied. Given a user with role "Owner", When invoking a direct Reveal (bypass) on any work order, Then the reveal succeeds without additional approval and the bypass is audit-logged with the owner identity and policy ID. Given a property-level deny policy for reveals, When an Owner approves or bypasses per the "owner-override" policy, Then the override takes precedence and the reveal is allowed; API returns 200 with unmasked fields and includes the overriding policy in the decision metadata. Given an Owner without active account access (suspended/disabled), When attempting any approval or bypass, Then the action is blocked and API returns 403.
Scoped Policies by Property/Portfolio and Vendor Company
Given Policy A denies reveals for vendor company "ACME Plumbing" on property "P-101", When a user from ACME with role Vendor attempts a reveal on a P-101 work order, Then the decision is Deny and API returns 403 with Policy A as the match. Given Policy A above, When the same user attempts a reveal on property "P-202" (no deny), Then the decision follows the next-most-specific applicable policy and its outcome is returned with the matched policy ID. Given Policy B allows Coordinators to request reveals only within portfolio "West", When a Coordinator attempts to request a reveal on a property in portfolio "East", Then the request is blocked and API returns 403 citing Policy B. Given multiple applicable policies at global, portfolio, and property scope, When a decision is evaluated, Then the most-specific scope (property > portfolio > global) takes precedence; ties are resolved by explicit Deny over Allow; the matched policy and scope are returned in decision metadata. Given an admin updates a scoped policy, When the change is published, Then subsequent decisions reflect the new rule immediately and include the new policy version in metadata.
Work-Order Status Gated Actions
Given Policy C allows reveals only when work-order status is "Scheduled" or "In Progress", When status is "New", "On Hold", or "Closed", Then reveal and reveal-approval actions are denied with 403 citing Policy C. Given Policy C above, When the status of a work order transitions from "New" to "Scheduled", Then the next reveal decision for the same user/action becomes Allow according to Policy C. Given Policy C above, When the status transitions from an allowed state to "Closed", Then any active time-bound reveals are immediately revoked and subsequent reads return masked values. Given a user attempts to initiate masked communication while status is disallowed by policy, Then the initiation action is denied and API returns 403 with the matched policy and current status in metadata.
SSO Group Claims Role Mapping and Enforcement
Given an SSO-authenticated user with group claims ["PropertyManagers", "Portfolio:West"], When they sign in, Then the system maps them to role "Manager" with scope Portfolio=West and persists a session reflecting these attributes. Given the above mapped user, When they perform a reveal decision on a West portfolio property, Then policy evaluation uses role=Manager and scope=West and returns the correct Allow/Deny with matched policy ID. Given the same user attempts the same action on a property outside scope, Then the decision is Deny with 403 due to scope mismatch, even if role mapping is Manager. Given an SSO user without recognized group claims, When they sign in, Then no privileged role is assigned and all protected actions (reveal, approve, bypass, initiate) return 403 until an admin assigns a role or a mapping is configured. Given an admin updates the SSO group-to-role mapping, When the user obtains a new token or reauthenticates, Then subsequent decisions reflect the new role/scope without requiring code changes.
Admin Policy Editor Test/Preview
Given an admin creates or edits a policy in the Policy Editor, When they run Test/Preview by selecting user, role, property/portfolio, vendor company, work-order status, and action (reveal/request/approve/bypass/initiate), Then the preview returns a Decision (Allow/Deny), the matched policy, and an evaluation trace without affecting live traffic. Given policies in Draft state, When users perform actions in production, Then Draft rules are not evaluated; only the last Published version is enforced. Given a draft with syntax or validation errors, When the admin attempts to publish, Then publishing is blocked and the editor surfaces the errors with line/field references. Given a new policy version is published, When decisions are evaluated post-publish, Then the engine uses the new version and includes the version number and timestamp in decision metadata; the previous version remains available for rollback. Given the admin performs a Preview that predicts Deny, When the same action is attempted in production after publishing, Then the live decision matches the preview result under identical inputs.
PII Redaction & Message Guard
"As a coordinator, I want automatic redaction of direct contact info in messages so that we avoid accidental oversharing while keeping conversations usable."
Description

Scan outbound and inbound messages (SMS and in-app chat) for phone numbers, emails, and unit/address patterns. When direct PII is detected, block or rewrite to the appropriate proxy and warn the sender according to policy (block, warn, allow). Support multilingual patterns, common obfuscations (e.g., "at" for @), and configurable whitelists for approved addresses. Maintain a redaction log linked to the audit trail and provide an inline preview so users understand what recipients will see.

Acceptance Criteria
Outbound SMS/in-app: warn-and-rewrite with inline preview
Given a user composes an outbound message (SMS or in-app chat) containing a 10–15 digit phone number, an email address, and a street address/unit And the workspace policy for outbound PII is set to Warn and Rewrite When the user clicks Send Then a pre-send inline preview displays the rewritten message with phone and email replaced by the assigned proxy values and the address masked as [redacted] And the user is shown a clear warning summarizing the changes and can proceed or cancel And the delivered message to the recipient contains only the proxy values and masked address, never the raw PII And a redaction event is recorded referencing the message ID and policy applied
Inbound message moderation under block policy
Given an inbound SMS or in-app message contains direct PII (phone, email, address/unit) And the workspace policy for inbound PII is set to Block When the message is received Then the message is not delivered to the recipient conversation And the sender receives an automated response stating PII is not permitted and to use platform proxies And a moderation alert is raised to managers with a masked excerpt and options to whitelist or release And an audit log entry captures detection details and action taken
Multilingual and obfuscation detection
Given messages containing PII expressed in English and Spanish (e.g., "john at ejemplo dot com", "tel: cinco cinco cinco 123 4567", "+34 600-123-456", "c/ Mayor 10, piso 2B") When the detector scans the messages Then obfuscated email patterns using "at"/"dot" and zero-width spaces are detected And phone numbers with spaces, dashes, parentheses, country codes, or words for digits are detected And common address/unit patterns for US/EU formats are detected And each detection is tagged with language and PII type for logging
Whitelist allows approved addresses and numbers
Given an admin has configured a whitelist for support@fixflow.app and +1-415-555-0100 And a message contains these exact values When the detector scans the message Then no redaction or rewrite is applied to the whitelisted values And the action is logged with reason Whitelisted and the config rule ID And similar non-whitelisted values (e.g., support@fixflow.co, +1-415-555-0101) in the same message are still handled per policy
Proxy substitution correctness and fallbacks
Given the system has active proxy mappings for the conversation participants When a real phone number or email is detected in an outbound message Then it is replaced with the correct conversation-specific proxy number or proxy email And if no proxy mapping exists for that participant, the message is blocked with an explanatory error and no content is sent And the proxy replacement preserves message readability (spacing and punctuation)
Performance and failure handling for scanning
Given normal load conditions When scanning messages up to 2000 characters Then 95% of scans complete in under 150 ms and 99% under 300 ms And when the scanning service is unavailable or times out, sending is prevented, the user sees a retry prompt within 2 seconds, and an incident is logged
Redaction log and audit trail integrity and access
Given any message that triggered detection When viewing the audit trail Then the redaction log entry shows timestamp, sender, channel, detection types and counts, policy action, masked before/after preview, message IDs, and who overrode if applicable And raw PII values are never stored in the log; only masked excerpts and secure hashes And access to the log is limited to authorized roles, and access events are themselves audited
Compliance & Data Retention Controls
"As a portfolio manager, I want retention and consent settings that meet our regulatory obligations so that we can protect tenant privacy and pass audits without manual effort."
Description

Provide configurable retention schedules for audit logs, call recordings (if enabled), SMS content, and reveal records to meet regional requirements (e.g., CCPA/GDPR). Support consent capture and honoring opt-outs/Do Not Contact flags, including suppressing proxy provisioning for blocked numbers. Include data subject request workflows to retrieve or delete personal data where legally permitted, with safeguards to preserve operational records needed for disputes. Offer compliance reports summarizing configurations, consents, and retention statuses by portfolio.

Acceptance Criteria
Configure Region-Specific Retention Policies
Given I am a portfolio admin with compliance permissions When I set retention periods for audit logs, call recordings, SMS content, and reveal records per region and save Then the system stores the configuration with version, editor, timestamp, region, and data-type scope And the policy applies to newly ingested records immediately and to existing records at the next purge cycle And invalid values outside 1–1825 days are rejected with validation errors And a change log entry is created recording old and new values
Automated Purge and Anonymization Engine
Given retention policies exist for at least one region and data type When the scheduled purge job runs daily at the configured window Then all records exceeding their retention threshold are deleted or content is redacted per data-type policy (e.g., delete recordings, redact SMS bodies) And metadata required for operational integrity (IDs, timestamps, non-PII counters) is preserved And a purge report is written with counts by data type and region, success/failure totals, error details, and job duration And failed deletions retry up to 3 times and raise an alert to portfolio admins on persistent failure
Consent Capture and Opt-Out Enforcement
Given a tenant or vendor provides consent for SMS, calls/recordings, or both When consent is captured via UI, API, or keyword flow Then the consent record includes subject identifier, channel(s), lawful basis, timestamp, capture method, and actor And Given a STOP/UNSUBSCRIBE keyword is received or a Do Not Contact flag is set When any SMS or call is initiated to that subject Then the action is blocked, no proxy is provisioned, and the initiator sees a clear error explaining the opt-out And an audit log entry is created and the subject receives SMS confirmation of opt-out (for SMS channels)
Suppress Proxy Provisioning for Blocked Numbers
Given a phone number is on the blocked list or the contact is flagged Do Not Contact When a workflow attempts to create or reuse a proxy number for that target Then proxy provisioning is suppressed, no external carrier API is called, and no charges are incurred And the system returns HTTP 403 with error code DNC_BLOCK (API) and displays a UI banner explaining the block (App) And an audit event is recorded with actor, reason, and correlation ID And authorized admins can override only by entering a documented lawful basis and a time-bound exception with auto-expiry
Data Subject Access Request (DSAR) Export
Given a verified administrator initiates a DSAR for a specific data subject When the request is submitted with subject identifiers and applicable region Then the system compiles all personal data in scope (contact details, consents, communication content/metadata, reveal records) limited to the subject and portfolio And third-party identifiers are redacted or pseudonymized And the export is produced within 7 calendar days as an encrypted ZIP containing JSON/CSV and a human-readable PDF summary And access to the export requires a one-time token, is logged, and the link expires after 14 days
Data Subject Erasure with Legal Safeguards
Given a verified and eligible erasure request is submitted for a data subject When processing begins Then personal data not required by law or active disputes is deleted or irreversibly anonymized across systems in scope And operational records needed for disputes or financial compliance are retained with PII redacted and a lawful-basis tag And a legal hold prevents deletion when a dispute is marked; holds require scope, reason, actor, and expiry And the requester receives a deletion certificate summarizing deleted items, retained items, lawful bases, and timestamps
Portfolio Compliance Configuration Report
Given I have access to a portfolio’s compliance area When I generate a Compliance Report for a date range Then it summarizes retention configurations by data type and region, consent totals and opt-out rates, DSAR/erasure counts and SLA adherence, and last purge run status And includes counts of records within 30 days of deletion and items under legal hold And the report is exportable to CSV and PDF with a signed checksum for integrity And each report generation is logged with actor, timestamp, and applied filters

Step-Up Approvals

Strengthen high-risk approvals with adaptive 2FA triggered by cost, severity, after-hours, or new device. Choose SMS, authenticator, or push and remember trusted devices for a set window to keep workflows quick. Approvals stay secure without slowing teams down.

Requirements

Timeboxed Access

Grant temporary, scoped access to a specific work order, property, or time window—then let it auto-expire. Perfect for short-term helpers or vendors on a one-off job, with optional renewal prompts if schedules shift. Reduces manual cleanup and keeps least-privilege intact.

Requirements

Scoped Access Creation
"As a property manager, I want to generate a temporary, scoped link for a vendor tied to a single work order so that they can do the job without seeing unrelated properties or tickets."
Description

Allow owners and property managers to generate temporary, least‑privilege access scoped to a single work order, a specific property, or a defined time window. Creators can set start/end timestamps, choose allowed actions (e.g., view details, update status, add notes/photos, message within thread), and assign the invitee to a named access role. The access artifact is linked to the target record(s), supports multiple concurrent invites per record without overlap, and displays an “Active Access” badge in the work order/property header. Default durations and scopes are configurable at account level and can inherit from property or portfolio settings for consistency.

Acceptance Criteria
Create Work Order–Scoped Access With Least‑Privilege Actions
Given I am an owner or property manager with permission to create scoped access And a work order WO-123 exists When I create an access invite with scope = "Work Order: WO-123" And I select allowed actions = ["view details", "add notes/photos"] And I assign the invitee to the named role "Vendor - Limited" And I set valid start and end timestamps Then the system creates a unique access artifact linked to WO-123 And an "Active Access" badge appears in the WO-123 header while the invite is active And the invitee can access only WO-123 within the time window And only the selected actions are available to the invitee; all other actions are hidden or blocked with an error And attempts by the invitee to access other work orders or properties are denied
Time-Window Activation and Auto-Expiration Behavior
Given an access invite exists with start = Tstart and end = Tend When current time < Tstart Then the invitee cannot access the scoped record(s) and sees a not-active message When current time reaches Tstart Then the invite becomes active without manual intervention And the "Active Access" badge appears on the scoped record header(s) When current time > Tend Then the invite immediately loses access and any active session is invalidated on next request And the "Active Access" badge is removed from the scoped record header(s) within 60 seconds
Named Role Assignment and Effective Permission Whitelisting
Given named access roles exist with predefined capabilities And I select a named role during invite creation And I explicitly choose allowed actions for the invite When the invite is created Then the effective allowed actions for the invitee equal the intersection of the role's capabilities and the explicitly selected actions And UI controls and API endpoints outside the effective allowed actions are not visible or return an error for the invitee And changing the role or selected actions later updates the effective allowed actions immediately
Multiple Concurrent Invites Per Record Without Conflict
Given a work order or property has one or more active access invites When I create additional access invites for the same record with different invitees Then the system accepts the new invites and assigns each a unique token/ID And the "Active Access" badge displays a count equal to the number of active invites for that record And revoking or expiring one invite does not alter the status of other invites And permission checks evaluate each invite independently based on its own scope and actions
Default Duration and Scope Inheritance From Account/Portfolio/Property
Given account-level defaults for access scope and duration are configured And portfolio- or property-level overrides may exist When I open the create-invite form for a record belonging to a property within a portfolio Then the form pre-populates scope and duration from the most specific setting: property override > portfolio override > account default And I can edit the pre-populated values before saving And saving the invite stores the chosen values without modifying the defaults or overrides
Timestamp and Field Validation on Invite Creation
Given I am creating a new access invite When I submit the form with missing required fields (invitee identifier, scope, role, start, end) Then the system blocks creation and shows inline errors for each missing field When I submit with end <= start Then the system blocks creation and displays an error indicating the end must be after the start When I submit with timestamps in various time zones Then the system accepts ISO-8601 inputs, stores in UTC, and displays them in my account time zone consistently When I submit with a non-existent target record or role Then the system blocks creation and indicates the invalid reference
Role Templates & Permission Matrix
"As an admin, I want reusable permission templates for temporary access so that I can grant the least necessary capabilities quickly and consistently."
Description

Provide prebuilt, editable templates (e.g., Vendor—Update Status, Vendor—View Only, Helper—Messaging Only) that map to granular permissions for Timeboxed Access. Templates define allowed actions, visible fields, file upload limits, messaging capabilities, and whether calendar slots can be confirmed. Admins can create custom templates, set defaults by invite type, and preview effective permissions before sending. The matrix integrates with FixFlow’s existing RBAC so that temporary roles do not exceed permanent role capabilities and are logged as scoped exceptions rather than new accounts.

Acceptance Criteria
Preview of Effective Permissions Before Invite
Given an admin with permanent role R selects a template T, a scope (work order or property), and a time window When the admin clicks Preview permissions Then the system displays a summary of effective permissions = T ∩ R, including allowed actions, visible fields, messaging capabilities, calendar slot confirmation, and file upload limits And the summary explicitly lists the selected scope and start/end timestamps And any permission in T not present in R is labeled Removed (exceeds inviter role) and excluded from effective permissions And the Send Invite action is disabled until scope and time window are valid And closing the preview does not persist any changes
Create and Save Custom Role Template
Given an admin opens Create Template When the admin enters a unique template name, selects allowed actions, chooses visible fields, sets file upload limits (count and size), toggles messaging capabilities, and toggles calendar slot confirmation And clicks Save Then the template is saved and listed with the configured attributes And duplicate names are rejected with a validation error message And the template can be set as a default for one or more invite types And the saved template can be edited later, with the last-updated timestamp reflecting the change
Enforce Template Permissions During Timeboxed Session
Given a temporary invitee accepts an invite created with template T scoped to a specific work order and active time window When the invitee accesses the portal within the active window Then they can only execute actions allowed by T within the defined scope And fields not included in Visible Fields are hidden or read-only And attempts to perform disallowed actions return HTTP 403 with an explanatory message And file uploads are limited to the configured count and size; excess uploads are blocked with an error And calendar slot confirmation is available only if permitted by T And messaging UI and send capability are available only if permitted by T And all actions are audit-logged as Scoped Exception with user id, template id, scope id, and timestamp
RBAC Containment and Scoped Exception Audit
Given an admin with permanent role R configures an invite using template T When T includes permissions not granted by R Then those permissions are automatically excluded from the effective set and a warning is shown to the admin for acknowledgment before sending And the invite can only be sent if the remaining effective permissions are non-empty and scope/time window are valid And the temporary access is recorded as a Scoped Exception event (not as a new permanent account), capturing inviter, invitee, template id, scope, start/end times And audit logs expose grant, acknowledgment, send, renewal, expiry, and revoke events filterable by user, scope, and template
Auto-Expiry and Optional Renewal of Timeboxed Access
Given a timeboxed invite with end time TE When current time reaches TE Then the invitee’s access is revoked within 60 seconds; active sessions are terminated on next request with HTTP 401 Access expired And an Access expired audit event is recorded with timestamp And within 15 minutes prior to TE, the inviter receives a renewal prompt with options to extend or let expire When the inviter approves a new end time TE2 Then access is extended to TE2 with no change to permissions, and an Access renewed audit event is recorded When the inviter ignores or declines the prompt Then no renewal occurs and access remains revoked after TE
Default Template Selection by Invite Type
Given defaults are configured: a specific template per invite type (e.g., Vendor, Helper) When an admin starts a new invite and selects an invite type Then the corresponding default template auto-selects And the admin can override the selection before sending And if the default template has been deleted or contains permissions exceeding the admin’s role, the default falls back to None, a warning is shown, and Send Invite remains disabled until a valid template is chosen
Auto‑Expiration & Time Window Enforcement
"As a landlord, I want temporary access to stop automatically at the agreed time so that I don’t have to remember to remove it and my data stays secure."
Description

Enforce hard start/end times on all temporary access artifacts. Before start, links show a scheduled message; after end, links and active sessions are immediately invalidated and logged out. A scheduler service sweeps expirations, revokes tokens, and updates UI badges in real time. Timezone handling respects property locale and owner preferences, with DST-safe calculations. Attempts to perform disallowed actions outside the window are blocked with clear guidance. Defaults and maximum TTLs are configurable per account, and calendar views overlay access windows to prevent overlap and reduce double-bookings.

Acceptance Criteria
Pre-Start Access Messaging and Lockout
Given a temporary access artifact with a future start time When a recipient opens the access link or uses an API token before the start time Then the UI displays a “Scheduled access” message including the local start time in both the property timezone and the viewer’s timezone And no resource data is returned or rendered (placeholders only) And all protected API endpoints respond with HTTP 403 and error code AccessNotStarted And an audit log entry AccessAttemptBlockedPreStart is recorded with user, artifact, and timestamp And, if the account setting “allow early-access request” is enabled, a single-click CTA to request earlier access is shown
Immediate Post-Expiry Invalidation and Logout
Given an active session created via a temporary access artifact When the artifact end time is reached Then the session is terminated within 5 seconds and the user is redirected to an Expired state page And access/refresh tokens tied to the artifact are revoked and unusable for subsequent calls And all protected API endpoints respond with HTTP 401 and error code AccessExpired after end time And an audit log entry AccessExpired and SessionTerminated is recorded And the UI badge for the artifact updates to Expired within 5 seconds without page refresh
Scheduler Sweep and Real-Time UI Updates
Given the expiration scheduler is running When any temporary access artifact reaches end time or is administratively shortened Then the scheduler flags the artifact as expired within 30 seconds And revokes associated tokens idempotently (repeated sweeps cause no errors) And pushes a real-time update to connected clients so UI badges reflect Expired within 5 seconds of revocation And records operational metrics (processed_count, revoked_count, errors) and emits alerts if error rate >1% over 5 minutes And recovers gracefully after downtime by processing all overdue expirations on startup
Timezone and DST-Safe Window Enforcement
Given a property with timezone TZ_A and an owner preference to display times in TZ_B When creating a temporary access window with start/end in wall-clock times Then persistence stores start/end in UTC with source timezone metadata And enforcement uses property timezone TZ_A to compute eligibility And all UI surfaces display the window in both TZ_A and TZ_B with clear labels And for a window that crosses a DST spring-forward gap, the duration matches the intended wall-clock difference (no +/−1h drift) And for a window that crosses a DST fall-back overlap, enforcement uses absolute UTC boundaries to prevent double-access during repeated hour And API responses include normalized UTC, TZ_A, and TZ_B timestamps
Configurable Default and Maximum TTL
Given an account admin sets Default TTL and Max TTL values When a user creates a temporary access artifact Then the start/end fields pre-populate using Default TTL relative to start And client- and server-side validation prevent any TTL that exceeds Max TTL, returning HTTP 422 with error code TTLExceeded And an audit log entry SettingsChanged is recorded when TTL settings are updated And API creation endpoints enforce Max TTL regardless of client behavior And reports expose any attempted over-limit creations for the last 30 days
Calendar Overlay and Overlap Prevention
Given the shared calendar is open for a property or vendor When temporary access windows exist for overlapping times on the same property-resource or vendor Then the calendar overlays access windows with a distinct Access color and labels And any attempt to create or edit an access window that overlaps a conflicting job or access window is blocked with inline conflict details And the conflict includes links to the conflicting items and suggested next open slots And successful creation updates the calendar in real time for all viewers
Renewal Prompt and Extension within Limits
Given renewal prompts are enabled for an access artifact When the artifact is within 30 minutes of expiry and Max TTL has not been reached Then the recipient receives a renewal prompt via the configured channel(s) with an extend link And accepting the prompt extends the end time by the configured increment without exceeding Max TTL, with immediate token/session continuity And the calendar and UI badges update to reflect the new end time in real time And an audit log entry AccessExtended is recorded with old/new end times And if Max TTL would be exceeded, the prompt indicates extension is unavailable and no change occurs
Renewal & Extension Workflow
"As a property manager, I want quick renewal options when a job runs long so that I can keep the vendor working without creating new invites or losing audit continuity."
Description

Offer proactive renewal prompts and streamlined extension requests when schedules shift. The system notifies the inviter before expiration and lets the invitee request more time with an optional reason. Owners can approve one-click extensions with preset options (2 hours, 24 hours, custom) or decline. All changes update the shared calendar, notify participants via SMS/email, and append to the work order timeline for traceability. Extensions preserve the original scope and role unless explicitly changed and respect account-level maximum TTL policies.

Acceptance Criteria
Proactive Renewal Prompt to Inviter Before Expiration
Given an active timeboxed access with expiration T and an inviter with SMS and email enabled When the current time is 24 hours before T Then the system sends a renewal prompt via SMS and email to the inviter containing the access subject, work order ID, current expiration in the property's local time, and one-click actions for +2h, +24h, and Custom And no duplicate prompt is sent again until 12 hours have elapsed or the expiration changes And the event is appended to the work order timeline with timestamp and delivery status When the access is created with less than 24 hours until T Then a prompt is sent at 1 hour before T with the same contents
Invitee Requests Extension with Optional Reason
Given an invitee with active timeboxed access expiring at T When the invitee clicks "Request Extension" from the SMS link or dashboard and selects +2h, +24h, or enters a custom duration, and optionally enters a reason Then an extension request record is created with requested delta, reason (if provided), and current expiration T And the inviter is notified via SMS and email with one-click approval/decline links And the work order timeline records the request with actor=invitee and includes the optional reason And the original expiration T remains unchanged until an approval occurs or T is reached
One-Click Approval Applies Preset Extension
Given an inviter receives a renewal prompt containing one-click +2h and +24h links for an access expiring at T When the inviter clicks a one-click link within 15 minutes and passes authentication Then the system validates policy that the new expiration does not exceed the account-level maximum TTL measured from the original start time And if valid, the expiration is extended by the selected delta from T (not from now), the original scope and role are preserved, the shared calendar is updated to the new window, participants are notified via SMS/email with the new T', and the timeline is appended with who approved, delta, and reason if any And if invalid due to policy, the action is rejected, no changes are applied, the inviter is shown an error indicating the maximum allowed expiration, and the timeline logs the failed attempt
Custom Extension Approval with Validation
Given an inviter selects "Custom" to set a new expiration for an access currently expiring at T When the inviter enters a new expiration T' or duration and confirms Then the system validates that T' is after T, does not exceed the account-level maximum TTL measured from original start time, and does not create a negative or zero extension And if valid, the expiration is updated to T', original scope and role are preserved, the shared calendar reflects T', participants are notified via SMS/email, and the timeline captures T->T' with the approver and any provided reason And if invalid, the system prevents save, displays specific validation messages, and no calendar or access changes occur
Decline Extension Request
Given an outstanding invitee extension request for access expiring at T When the inviter clicks Decline from the notification or dashboard Then the system records the decline with optional reason, notifies the invitee via SMS/email, appends the timeline entry, and leaves the expiration unchanged at T And the shared calendar remains unchanged
Calendar and Notification Synchronization on Extension
Given any approved extension results in a new expiration T' When the approval is processed Then the shared calendar event is updated within 10 seconds to reflect T' in the property's time zone And the inviter and invitee receive SMS/email notifications within 60 seconds containing the new expiration T' and a link to view details And duplicate notifications are suppressed for the same change within a 5-minute window And the access control system enforces the new expiration immediately
Concurrency and Idempotent Actions
Given multiple approval or decline actions are submitted concurrently for the same extension request When the first valid action is committed Then subsequent actions are rejected as duplicates with no changes applied And the API returns a 409 Conflict (or equivalent code), the UI shows "Already processed", and only a single timeline entry reflects the final outcome
Immediate Revocation & Kill Switch
"As an owner, I want a one-click way to revoke temporary access immediately so that I can protect my data if a situation changes."
Description

Enable instant manual revocation for any timeboxed invite from the work order, property, or access management panel. Revocation immediately invalidates links and active sessions, removes UI badges, and posts a timeline entry with the reason. Provide bulk revocation by vendor, property, or portfolio for emergencies. A confirmation step prevents accidental revokes, and a post-revoke notice can be optionally sent to the invitee. Revocation actions are fully auditable and cannot be undone without issuing a new invite.

Acceptance Criteria
Single-Invite Kill Switch — Instant Link and Session Invalidation
Given an invitee currently has active timeboxed access to a work order or property And an authorized user clicks "Revoke" for that invite and confirms When the revocation request is submitted Then all access tokens/session cookies for that invitee scoped to the revoked resource are invalidated within 2 seconds And subsequent API calls using those tokens return HTTP 401 with error_code=ACCESS_REVOKED And all previously issued access links (signed URLs) become unusable immediately and route to an Access Revoked screen And any open pages under the revoked scope redirect to Access Revoked on the next request within one round-trip
UI State Cleanup — Badges, Quick Actions, and Sharing Indicators Removed
Given an invite is revoked When the user views the affected Work Order, Property, and Access Management pages Then all guest/vendor badges related to the revoked invite are removed within 5 seconds And share buttons reflect "No active invites" and only show options to create a new invite And quick actions (e.g., Message guest, Resend link) tied to the revoked invite are no longer displayed And revoked invites no longer appear in search filters, counts, or summary chips
Timeline Entry — Reason and Actor Logged on Revocation
Given a user revokes one or more invites When revocation completes Then a timeline entry is appended to the associated work order/property stream within 2 seconds And the entry records actor name, role, timestamp (UTC), target scope, affected invitee(s), and stated reason (or "Manual revocation" if none provided) And the entry is read-only/immutable to all roles And it is visible to Landlord and Property Manager roles and excluded from invitee views
Bulk Revocation — Vendor/Property/Portfolio with Atomicity and Feedback
Given a user selects Bulk Revoke by Vendor, Property, or Portfolio And a confirmation dialog displays the scope and the exact count of active invites to be revoked When the user confirms Then all targeted invites are revoked atomically per selected scope And a result summary shows total attempted, succeeded, and failed within the dialog And for up to 500 invites the operation completes within 15 seconds And partial failures are retriable via "Retry failed" without duplicating already revoked items And each revoked invite generates its timeline entry and audit record
Safety Confirmation — Intent Verification for Irreversible Revocation
Given a user initiates a revoke action When the confirmation dialog appears Then the user must either type REVOKE or the invitee/vendor name to enable the Confirm button And the default focused action is Cancel And pressing Enter does not confirm unless the Confirm button is focused after validation And no "Undo" option is presented post-revoke; a new invite is required to restore access
Optional Post-Revoke Notice — SMS/Email to Invitee
Given a revoke action is confirmed And the user selects "Notify invitee" with SMS, Email, or Both When revocation completes Then a notification is sent using the selected channel(s) within 30 seconds And the message includes property address (if applicable), work order ID, and owner contact instructions And delivery status (sent, bounced, failed) is recorded and visible in the timeline/audit trail And invitees who click old links see the Access Revoked screen with contact instructions
Auditability and Irreversibility — Non-Repudiable Revocation Record
Given any revocation (single or bulk) occurs When viewing the audit log Then an immutable record exists containing actor ID, actor IP, timestamp (UTC), scope, invitee identifier(s), reason, and correlation ID And the record cannot be edited or deleted by any role And exporting audit logs includes these fields for the event And any attempt to reinstate access without issuing a new invite is blocked with error_code=INVITE_REQUIRED
Delivery & Identity Verification (SMS/Email Magic Link + OTP)
"As a vendor, I want a secure link I can open from my phone and verify quickly so that I can access the work order without creating an account."
Description

Deliver timeboxed invites via SMS and/or email with a signed, scoped magic link. Require the invitee to verify their identity with a one-time passcode sent to the same phone number or email used for delivery before granting access. Tokens are single-recipient, rate-limited, and bound to scope, role, and time window; they cannot exceed account TTL policies. Implement anti-forwarding safeguards via OTP, short link lifetimes before first use, and optional device remembrance for the active window. Delivery status, bounces, and failed verifications are surfaced to the inviter for follow-up.

Acceptance Criteria
SMS Magic Link Delivery with OTP Verification
Given an inviter selects SMS delivery and defines scope (work order/property), role, and time window When the invite is created Then a signed token is generated bound to that scope, role, single recipient phone (E.164), and expiry = min(window_end, account_max_ttl) Given the SMS is dispatched When the carrier returns delivery events Then delivery status (queued/sent/delivered/failed) with timestamps is recorded and surfaced to the inviter Given the invitee receives the SMS When the magic link is opened within 30 minutes of send (pre-use TTL) Then the invitee is prompted for a 6‑digit OTP sent only to the same phone number Given the OTP is entered When the correct OTP is provided within 10 minutes of issuance Then access is granted and first-use time is logged Given the link is unopened for 30 minutes after send or the time window has ended When the link is opened Then access is denied and the invite is marked expired
Email Magic Link Delivery with OTP and Bounce Handling
Given an inviter selects email delivery and defines scope (work order/property), role, and time window When the invite is created Then a signed token is generated bound to that scope, role, single recipient email, and expiry = min(window_end, account_max_ttl) Given the email is dispatched When the provider returns delivery/bounce/complaint events Then message status (queued/sent/delivered/bounced/complaint) with timestamps is recorded and surfaced to the inviter Given the invitee receives the email When the magic link is opened within 30 minutes of send (pre-use TTL) Then the invitee is prompted for a 6‑digit OTP sent only to the same email address Given the OTP is entered When the correct OTP is provided within 10 minutes of issuance Then access is granted and first-use time is logged Given the email hard-bounces or is marked as complaint When the system receives that event Then the invite is marked undeliverable and the inviter sees the reason
Scoped, Role-, and Time-Bound Access Enforcement
Given an invite has been successfully verified (magic link + OTP) When the recipient accesses FixFlow Then they can only view and act on resources within the specified scope (work order and/or property) Given API or UI actions are attempted outside the allowed scope or role When a request targets any other work orders/properties or disallowed actions Then the request is rejected with 403, audited with invite ID and recipient contact, and no data is leaked Given a time window with start and end times and an account_max_ttl When calculating session/token expiry Then expiry is set to the earlier of window_end and account_max_ttl and access is denied before window_start and after expiry Given the time window ends or the invite is revoked When the recipient attempts any action Then the session is invalidated and access is denied
Anti-Forwarding and Single-Recipient Binding
Given a magic link is opened by anyone When identity verification is requested Then the OTP is sent only to the original delivery channel (same phone or email) and cannot be redirected to a different contact Given a user attempts to change the verifying phone/email during the flow When they submit a different contact Then verification is refused, no OTP is sent to the new contact, and the attempt is logged Given a forwarded link is used by a non-recipient who cannot access the original phone/email When they attempt verification Then access cannot be obtained and the invite remains unclaimed Given a recipient has successfully verified once When OTP resends are requested later Then all resends continue to go only to the original contact for the active window
OTP and Invite Rate Limiting with Lockout and Alerts
Given an invite is active When OTP codes are submitted Then allow a maximum of 5 failed attempts within 10 minutes; on exceeding the limit, block verification for 15 minutes and record a lockout event visible to the inviter Given an invite is active When OTP resends are requested Then allow a maximum of 3 resends within 10 minutes with a minimum 30‑second interval between resends; additional requests are throttled and logged Given an inviter sends invitations When invites target the same recipient contact Then allow a maximum of 3 invites per recipient per hour and 30 invites per account per hour; further sends are rejected with a rate‑limit error and logged
Device Remembrance Within Active Window
Given a recipient completes OTP verification When they opt in to "Remember this device for this access window" Then a device‑bound remember token is set and subsequent visits from the same device/browser within the active window bypass OTP Given the same recipient uses a different device or browser When they access via the magic link Then OTP is required again and remember does not apply across devices Given the inviter revokes remembered devices for an invite or the time window ends When the recipient next accesses Then the remember token is invalidated and OTP is required again Given privacy and security requirements When remember is offered Then the default is opt‑out (unchecked), and the remember token is scoped to the invite and does not extend access beyond the active window
Access Audit Trail & Reporting
"As a portfolio manager, I want a clear audit of who accessed what and when so that I can meet compliance needs and resolve disputes quickly."
Description

Capture a complete audit log for each temporary access: creator, invitee contact, scope, role template, start/end, renewals, revocations, access attempts, IP/device metadata, and actions performed (e.g., note added, photo uploaded, status changed). Surface a per-record timeline and an account-level report with filters by date range, property, vendor, and outcome. Export to CSV and include links back to source records. Logs are immutable, retained per compliance policy, and accessible via API for integrations.

Acceptance Criteria
Per-Record Timeline for Temporary Access Invite
- Given a timeboxed access invite is created, When the record is viewed, Then the timeline shows entries for creation with creator user ID, invitee contact (email/phone), scope (work order/property/time window), role template, and start/end timestamps. - Given a timeboxed access invite is renewed, When the record is viewed, Then the timeline shows a renewal entry with previous end and new end and the actor, and the renewal count increments by 1. - Given a timeboxed access invite is revoked, When the record is viewed, Then the timeline shows a revocation entry with timestamp and actor and the invite status is Revoked. - Given the invitee performs actions (adds note, uploads photo, changes status), When the record is viewed, Then each action appears in the timeline with action type, timestamp, actor (invitee identifier), and a link to the action artifact.
Access Attempts Logging with IP/Device Metadata
- Given an invite link is accessed before start, When the attempt occurs, Then an attempt entry is logged with outcome Pre-Start, IP address, user agent/device fingerprint, timestamp, and denied=true. - Given an invite link is accessed within the valid window, When the invitee authenticates, Then a successful access entry is logged with outcome Granted, IP, device, timestamp. - Given an invite link is accessed after expiry or revocation, When the attempt occurs, Then an attempt entry is logged with outcome Expired or Revoked, IP, device, timestamp, and denied=true. - Given multiple attempts occur, When the record timeline is viewed, Then a count of attempts is displayed and entries are shown in chronological order newest-first with pagination if >50 entries.
Account-Level Audit Report Filtering by Date/Property/Vendor/Outcome
- Given audit logs exist across properties and vendors, When a user applies date range, property, vendor, and outcome filters, Then the results include only entries matching all selected filters. - Given no filters are applied, When the report loads, Then it defaults to the last 30 days and shows a total count and summary by outcome. - Given a query returns up to 10,000 entries, When the results are requested, Then the first page renders within 3 seconds and server returns page metadata (page, pageSize, total). - Given a user clicks an entry, When the entry is opened, Then a link navigates to the source record (work order/property/access record) in a new tab.
CSV Export of Audit Report with Source Links
- Given filters are applied to the audit report, When Export CSV is clicked, Then the downloaded file contains only rows matching the current filter set. - Given the CSV is generated, When opened in Excel, Then it is UTF-8 with BOM and RFC 4180-compliant with quoted fields and comma delimiter. - Given a row in the CSV, When inspected, Then it includes columns: entry_id, timestamp (ISO 8601 UTC), creator_id, invitee_contact, scope_type, scope_id, role_template, start_at, end_at, outcome, action_type, ip, device, actor_id, source_url. - Given the result set exceeds 50,000 rows, When export is requested, Then the system streams the file and completes without truncation and includes a file name of the form fixflow_audit_YYYYMMDD_HHMMSS.csv.
Immutability and Retention Enforcement
- Given an existing audit entry, When an admin attempts to edit or delete it via UI or API, Then the action is rejected with HTTP 403 and an immutable log entry is added noting the denied attempt. - Given the retention policy is configured to 7 years, When the system reaches retention thresholds, Then entries older than the policy are purged by a system job with a purge log entry capturing count, time range, and job ID. - Given the audit store uses append-only semantics, When a new entry is written, Then it receives an immutable ID and SHA-256 checksum stored with the entry and exposed via API/UI. - Given a user views an entry, When comparing checksum values, Then any checksum mismatch triggers an alert banner and the entry is flagged integrity_suspect=true.
Audit Log API for Integrations
- Given an integration with valid API credentials, When it calls GET /api/v1/audit-logs with date range, property_id, vendor_id, outcome, and page params, Then the API returns 200 with a JSON array of entries and pagination metadata. - Given invalid or missing credentials, When the API is called, Then it returns 401 Unauthorized; given valid credentials lacking permission, return 403 Forbidden. - Given the API response, When inspected, Then each entry includes all required fields (creator_id, invitee_contact, scope, role_template, start_at, end_at, renewals, revocations, attempts, actions, ip, device, source_url) and immutable flags. - Given rate limiting is configured, When more than 60 requests are made within 60 seconds per API token, Then subsequent requests receive 429 with Retry-After header.

Access Ledger

Get a tamper-evident audit of every view, download, reveal, role change, and approval—searchable by user, property, or work order. Real-time anomaly alerts flag unusual access (e.g., bulk exports, midnight reveals) and exportable reports satisfy boards, insurers, and audits.

Requirements

Unified Access Event Instrumentation
"As a compliance administrator, I want every sensitive action to be consistently logged with rich context so that I can reconstruct who did what and when during audits."
Description

Instrument all user- and system-initiated access events across web UI and API to capture views, downloads, reveals, exports, role changes, approvals, and authentication outcomes in a normalized schema. Each event must include timestamp (UTC), user and role, acting account, IP and user agent, resource type and ID (property, unit, work order, document, tenant, vendor), action, outcome, reason code (when applicable), session/request/correlation IDs, and multitenancy org identifiers. Ensure at-least-once delivery, idempotency keys, backpressure handling, and capture for automated scheduler actions with a system actor flag. Enforce consistent timezone handling, classify fields by sensitivity, and mark redaction status to support downstream privacy and security controls.

Acceptance Criteria
Event Schema Completeness and Normalization
- Every emitted event includes: timestamp (UTC ISO 8601 with 'Z'), event_id (UUID), idempotency_key, user_id (nullable), user_role, acting_account_id, org_id, ip_address (IPv4/IPv6), user_agent, resource_type (one of: property, unit, work_order, document, tenant, vendor, user, auth), resource_id, action (one of: view, download, reveal, export, role_change, approval, authenticate), outcome (one of: success, denied, error), reason_code required when outcome != success, session_id, request_id, correlation_id, system_actor (boolean), schema_version, sensitivity_classification, redaction_status. - Events failing schema validation are rejected, logged, and routed to a dead-letter queue with correlation_id preserved. - All timestamps are normalized to UTC and formatted with 'Z' suffix; no local offsets are allowed. - IP addresses are stored in canonical format for both IPv4 and IPv6.
Web UI Access Events Logged (View/Download/Reveal)
- Given a signed-in user opens a work order detail page in the web UI, When the page loads, Then one 'view' event is emitted with resource_type 'work_order', resource_id matching the page, org_id matching the user's org, and fields populated per the normalized schema. - Given the user refreshes the page, When a new request_id is issued, Then a new 'view' event is emitted with a distinct idempotency_key. - Given the user clicks Download on a document and receives 200, Then a 'download' event with outcome 'success' is emitted; if the response is 403, outcome 'denied' with reason_code 'authorization_failed' is emitted. - Given the user reveals a masked tenant phone number, When the value becomes visible, Then a 'reveal' event is emitted with sensitivity_classification 'PII' and redaction_status 'revealed'.
API Access and Bulk Export Events Logged
- Given an API client calls GET /documents/{id}/download and receives 200, Then a 'download' event is emitted with resource_type 'document' and outcome 'success'; for 404 -> outcome 'error' with reason_code 'not_found'; for 401 -> outcome 'denied' with reason_code 'unauthenticated'; for 403 -> outcome 'denied' with reason_code 'unauthorized'. - Given a bulk export returns N resources, When the export completes, Then N 'export' events are emitted (one per resource) sharing the same correlation_id and recording the API client's acting_account_id and org_id. - API-initiated events set user_agent to the provided client UA or 'api' if none is provided, record caller IP, and set actor.user_id to the API token owner.
Privileged Actions Audit: Role Changes and Approvals
- When an admin changes another user's role, Then a 'role_change' event is emitted with resource_type 'user', resource_id of the target user, actor.user_id of the admin, acting_account_id, org_id, and outcome recorded; if denied, reason_code is set to 'policy_violation' or 'insufficient_privileges'. - When a manager approves a work order, Then an 'approval' event is emitted with resource_type 'work_order', resource_id of the work order, outcome recorded ('success' or 'denied' with reason_code), and correlation_id of the approval workflow.
Authentication Outcomes Logged with Reason Codes
- Given a user logs in successfully, Then an 'authenticate' event is emitted with outcome 'success' and no reason_code. - Given a login attempt fails due to invalid credentials, Then outcome 'denied' with reason_code 'invalid_credentials' is recorded; if the account is locked, reason_code 'locked_out'; if MFA fails, reason_code 'mfa_failed'. - No secret material (passwords, OTPs, tokens) is captured in authentication events; sensitivity_classification is applied and redaction_status is recorded for any captured fields.
Delivery Semantics and Backpressure
- Each event carries an idempotency_key that remains constant across retries. - Duplicate deliveries with the same idempotency_key for the same action/resource/correlation_id do not create duplicate stored events. - During a simulated sink outage, events are retried until acknowledged; after recovery, all events submitted during the outage are persisted (at-least-once delivery). - Under sustained high load, events are buffered without data loss and eventually persisted; producer backpressure prevents unbounded memory growth and queue depth metrics are exposed.
System Actor Capture for Automated Scheduler
- When the automated scheduler performs an action that accesses or changes resources (e.g., auto-assigning a vendor on a work order), an event is emitted with system_actor = true, actor.user_id = null, user_agent = 'system/scheduler', acting_account_id set, org_id set, and all required schema fields populated. - System actor events include a correlation_id identifying the job/run that initiated the action. - System actor events record outcome and reason_code when an action is denied or errors; sensitivity_classification and redaction_status are applied when sensitive fields are accessed.
Tamper-Evident Append-Only Ledger
"As a security officer, I want the access log to be tamper-evident so that I can prove the integrity of historical records to auditors and insurers."
Description

Store events in an append-only log with cryptographic hash chaining that links each record to the previous one, producing periodic Merkle roots and integrity checkpoints. Use WORM-capable storage with encryption at rest and in transit, KMS-managed keys, and time-based retention. Provide automated integrity verification jobs, alert on verification failures, and support external anchoring of checkpoints for non-repudiation. Implement tenant-level logical segregation, access controls for read-only viewing, and tombstoned metadata for lawful erasure without breaking chain verification. Ensure disaster recovery with point-in-time restore and documented verification procedures.

Acceptance Criteria
Event Append With Cryptographic Hash Chaining and Merkle Roots
Given an authenticated ledger writer submits a new event for tenant T with a canonical payload and timestamp in RFC3339 UTC When the event is appended to the tenant T ledger Then the event record includes fields: id, tenantId, timestamp, type, payloadChecksum, writerId, prevHash, hash, version And hash = SHA-256(canonicalSerialize(id, tenantId, timestamp, type, payloadChecksum, writerId, prevHash, version)) And prevHash equals the hash of the immediately preceding event in tenant T (or 64 zeros for the genesis record) And events with timestamps older than the latest stored timestamp for tenant T by more than 5 seconds are rejected with HTTP 400 and error code LEDGER_NON_MONOTONIC_TIME And a Merkle root checkpoint record is emitted every 10,000 events or every 15 minutes (whichever occurs first), containing root, intervalStartId, intervalEndId, and intervalTimeRange And GET /ledger/tenants/{tenantId}/checkpoints returns the latest checkpoint with HTTP 200 and the root is reproducible from the covered events
Persist Events to WORM Storage with Encryption and Retention
Given a new ledger event is committed When the event blob and its index are written to object storage Then the target bucket has WORM/Object Lock in Compliance mode enabled And the object has a retention period equal to the organization’s configured ledgerRetentionYears (default 7 years, range 1–10) And server-side encryption uses a KMS CMK (AES-256) with key alias fixflow/ledger and TLS 1.2+ is enforced in transit And attempts to modify or delete the object before retention expiry are blocked by the storage provider and surfaced as HTTP 409 LEDGER_WORM_RETENTION_ACTIVE from the API And the KMS key has automatic rotation enabled (<= 365 days) and key policy restricts decrypt to the ledger service role only
Scheduled Integrity Verification and Alerting
Given the system clock reaches the top of the hour When the integrity verifier runs for each tenant Then it recomputes and validates hash chaining for all events since the last successful run and verifies Merkle roots for all checkpoints in scope And it writes a verification report with status (PASS/FAIL), checkedRange, failures[], duration, and verifierVersion And a PASS result emits metric ledger.verify.pass=1; a FAIL result emits ledger.verify.fail=1 and includes the first failing eventId and reason And on FAIL, an alert is sent to the on-call channel within 2 minutes with tenantId, failing eventId, and remediation guidance And a retry policy attempts re-verification up to 3 times with exponential backoff before paging
External Anchoring of Checkpoints for Non-Repudiation
Given a new checkpoint record is created for tenant T When the anchoring service processes the checkpoint Then it constructs a signed anchoring payload containing tenantId, checkpointId, merkleRoot, interval, and SHA-256 digest And it submits the payload to an external trusted timestamping/anchoring provider and receives an anchorId and anchoredAt timestamp And the checkpoint record is updated atomically with anchorId and anchoredAt And GET /ledger/tenants/{tenantId}/checkpoints/{checkpointId}/proof returns HTTP 200 with the provider proof artifact And offline verification with the proof reproduces the same merkleRoot and validates the provider’s signature And on anchoring failure, the system retries with exponential backoff for up to 24 hours and raises an alert after 3 consecutive failures
Tenant-Segregated Read-Only Access Controls
Given a user is authenticated with role LedgerReadOnly and scoped to tenant A When the user requests GET /ledger/tenants/A/events Then the response is HTTP 200 and contains only events where tenantId = A And attempts to access GET /ledger/tenants/B/events (B != A) return HTTP 403 LEDGER_FORBIDDEN_TENANT_SCOPE And the LedgerReadOnly role cannot invoke POST/PUT/PATCH/DELETE on any ledger endpoints (HTTP 405) And all access decisions are logged with userId, tenantId, endpoint, decision (ALLOW/DENY), and correlationId
Lawful Erasure via Tombstoned Metadata Without Chain Breakage
Given a GDPR/CCPA erasure request for data subject S is approved via dual-control workflow When the erasure processor executes for affected events Then a new tombstone event is appended per affected record, referencing eventId, fieldsRedacted[], approverIds[], and reasonCode And subsequent reads of the affected records return redacted placeholders for the specified fields while preserving the original hashes And the integrity verification job continues to PASS because original event payloads are not modified And any encryption keys (per-field or per-record data keys) for the redacted fields are scheduled for destruction, rendering original plaintext irretrievable And an immutable audit record of the erasure action is present and exportable without revealing the erased content
Disaster Recovery Point-in-Time Restore and Post-Restore Verification
Given a regional outage scenario is simulated When the operations runbook is executed to restore the ledger to timestamp T Then RPO <= 15 minutes and RTO <= 2 hours for tenants up to 200 units And post-restore, the integrity verification job runs and returns PASS for all chains and checkpoints up to T And anchored checkpoint proofs remain valid and are re-fetchable And a DR report is generated containing restorePoint, durations, verificationResults, and any discrepancies discovered
Searchable Ledger UI & Filters
"As a property manager admin, I want to search and filter the access ledger by user, property, or work order so that I can investigate incidents quickly without exporting data."
Description

Provide a dedicated Access Ledger view within FixFlow that supports fast, paginated search by user, role, property, unit, work order, document, action type (view, download, reveal, export, role change, approval), outcome, and time range. Include sorting, quick filters, saved searches, timezone toggle, and drill-down to the related resource or user profile. Display masked values for sensitive fields with reveal indicators, show correlation IDs for cross-referencing, and enable one-click export of current results respecting user permissions. Enforce least-privilege access so only authorized roles can view the ledger.

Acceptance Criteria
Role-Based Access to Access Ledger
Given an unauthenticated user, When they navigate to the Access Ledger route, Then they receive a 401/redirect to login and no ledger data is returned. Given an authenticated user without AccessLedger.View permission, When they request the Access Ledger (UI or API), Then a 403 is returned and no ledger data is exposed. Given an authenticated user with AccessLedger.View permission, When they open the Access Ledger, Then the ledger loads successfully and sensitive values remain masked by default. Given any user without ExportLedger permission, When they view the ledger UI, Then the export control is hidden/disabled and any direct export API call returns 403.
Multi-Field Search and Filtering
Given ledger entries exist across users, roles, properties, units, work orders, documents, action types, outcomes, and time, When the user applies any combination of these filters, Then results reflect the intersection (AND) of all active filters. Given a partial text input in user name, property, unit, or document fields, When searching, Then results match case-insensitive contains; ID fields (user ID, work order ID, correlation ID) match exact values only. Given a time range filter, When applied, Then results include entries whose timestamps fall within the inclusive start/end in the currently selected timezone. Given a dataset of up to 100k ledger rows, When executing a filtered search, Then p95 first page response time is ≤ 2 seconds and returns a total count. Given filters are cleared, When the user clicks Reset, Then all filters return to defaults and results refresh accordingly.
Pagination and Sorting Controls
Given default settings, When the ledger loads, Then page size defaults to 50 rows and can be changed to 25/50/100. Given a multi-page result set, When the user navigates with Next/Previous or a specific page, Then the correct page loads and boundary controls disable appropriately at first/last page. Given any visible column, When the user clicks the column header, Then sorting toggles ascending/descending and a sort indicator is shown. Given sorted, filtered results, When paginating, Then ordering is stable and no records are duplicated or skipped across pages within the same query. Given a sort change, When applied, Then p95 first page response time is ≤ 2 seconds.
Timezone Toggle for Timestamps and Filters
Given the ledger is displayed, When the user toggles timezone between Account Timezone and UTC, Then all visible timestamps, the time-range picker, and exported timestamp previews update to reflect the selected timezone. Given a selected timezone, When the page is reloaded, Then the chosen timezone persists for that user. Given a time range in one timezone, When converted by the system, Then the inclusive start/end map correctly to the other timezone without shifting the intended range boundaries.
Quick Filters and Saved Searches
Given the ledger view, When the user applies a quick filter (e.g., Last 24 Hours, Last 7 Days, Action: Reveal, My Properties), Then the corresponding filter fields update and results refresh; multiple quick filters can be combined when not conflicting, and a Clear All restores defaults. Given a configured filter/sort state, When the user saves it with a unique name, Then a personal saved search is created that captures filters, sort, and page size. Given an existing saved search, When the user selects it, Then the captured state is restored exactly. Given saved searches, When the user renames or deletes one, Then the change is reflected immediately and cannot duplicate another saved search name for the same user. Given limits, When a user attempts to exceed 50 saved searches, Then the UI blocks the action with an informative message.
Sensitive Data Masking and Reveal Workflow
Given sensitive fields (e.g., tenant contact, document tokens), When rendered, Then values are masked and a reveal indicator is present. Given a user without RevealSensitive permission, When clicking reveal, Then access is denied and the value remains masked. Given a user with RevealSensitive permission, When clicking reveal and providing a required reason, Then the value is revealed for that user for up to 15 minutes before auto-masking again, and a ledger 'reveal' event is recorded with user, field, timestamp, reason, and correlation ID. Given bulk actions, When attempting to reveal multiple sensitive fields at once, Then the action is blocked; reveals are per-field, per-row only.
Result Utilities: Drill-Down, Correlation IDs, and Export
Given a ledger row, When the user clicks the user name, property/unit, work order, or document link, Then the related profile/detail view opens (in a new tab if configured); lacking permission shows a 403/denied screen. Given a ledger row with a correlation ID, When the user clicks Copy, Then the correlation ID is placed in the clipboard and a confirmation toast appears; searching by that ID returns the exact matching record(s). Given current filtered results and visible columns, When a user with ExportLedger permission clicks Export, Then a CSV containing only those rows/columns is generated and downloaded. Given result sizes ≤ 50,000 rows, When exporting, Then the file is delivered synchronously within 10 seconds at p95; larger sets trigger an asynchronous export with an email link delivered within 5 minutes. Given any export attempt, When completed or queued, Then an 'export' audit entry is written with initiator, filter summary, row count, and file reference.
Real-Time Anomaly Detection & Alerts
"As a compliance administrator, I want real-time alerts for unusual access behaviors so that I can respond immediately and prevent data leakage."
Description

Implement a configurable rules engine to detect unusual access patterns such as bulk exports, after-hours access by org policy, high-volume reveals, rapid downloads, privilege escalation followed by sensitive access, access from new geographies or devices, and vendor access outside assigned properties. Generate real-time alerts via in-app notifications, email, and SMS with throttling, deduplication, and acknowledgement workflows. Allow per-tenant thresholds, quiet hours, suppression lists, and routing to specific recipients. Log alert lifecycle events and link alerts to the underlying ledger events for investigation.

Acceptance Criteria
Bulk Export Anomaly Detection with Throttling and Deduplication
Given tenant T has a bulk export rule threshold of 500 records within 5 minutes and a deduplication window of 10 minutes When a user exports 600 records across any work orders within 3 minutes Then a “Bulk Export” anomaly alert is created within 5 seconds of export completion with severity High and notifications are sent via in-app, email, and SMS to configured recipients Given a deduplication window of 10 minutes for Bulk Export alerts When the same user triggers another qualifying bulk export within 2 minutes of the first Then no duplicate alert record is created; the original alert’s occurrence_count increments and recipients do not receive additional notifications within the window (in-app thread updates are allowed) Given an alert throttle limit of 2 notifications per 15 minutes for Bulk Export When three distinct bulk exports occur within 15 minutes Then only two notifications are delivered immediately and the third is summarized in a digest sent at the end of the throttle window Given a suppression list contains user U When U triggers a bulk export that meets the anomaly threshold Then the anomaly is recorded and linkable to ledger events, but notifications are not sent to suppressed recipients
After-Hours Access Alert Respecting Tenant Policy and Quiet Hours
Given tenant T defines business hours 08:00–18:00 and quiet hours 22:00–06:00 in the tenant’s timezone When a user views a sensitive document at 23:30 local tenant time Then an “After-Hours Access” anomaly is created within 5 seconds with severity Medium and notifications are queued until quiet hours end unless an override is enabled Given quiet hours are enabled and the admin has set “Notify During Quiet Hours” override to true for After-Hours alerts When an after-hours access occurs at 01:15 local time Then notifications are sent immediately via in-app, email, and SMS and the override flag is recorded in the alert log Given the tenant timezone observes DST When access occurs during an ambiguous 01:30 time at fall-back Then the system classifies the event according to the tenant’s timezone rules deterministically and generates (or not) the After-Hours alert accordingly, logging the resolved offset
Privilege Escalation Followed by Sensitive Access Correlated Alert
Given a correlation window of 30 minutes for privilege-escalation rules When a user’s role is elevated (e.g., Resident to Manager) and the user accesses a sensitive ledger reveal within 15 minutes Then a “Privilege Escalation + Sensitive Access” anomaly is created within 5 seconds, links both underlying events, sets severity Critical, and routes to the security recipient group Given a whitelist of approved change tickets for planned role escalations When a user’s role elevation matches a valid change ticket within its effective window Then no anomaly is generated for subsequent sensitive access within the correlation window and the suppression reason is logged Given a role escalation occurs When no sensitive access happens within 30 minutes post-escalation Then no alert is created and the correlation window expires without residual state
New Geography or Device Access Detection with Fallbacks
Given device fingerprinting and geo-IP lookup are enabled with a 90-day user history window When a login occurs from a new device and a country not seen in the last 90 days for that user Then a “New Device/Geo Access” anomaly is generated within 5 seconds, includes device_id and country code, sets risk score per policy, and notifies per routing rules Given a user has an approved travel plan listing specific countries and dates When an access occurs from a listed country during the approved window Then no anomaly is generated and a policy_bypass reason is logged Given geo-IP lookup times out or returns an error When a login event is processed Then the system falls back to device-only detection, annotates geo_status=unknown, and proceeds with alerting if the device is new
Vendor Access Outside Assigned Properties Policy Enforcement
Given vendor V is assigned to properties P1 and P2 and policy mode is Block When V attempts to download a work order document for property P3 Then an “Out-of-Scope Vendor Access” anomaly is created within 3 seconds, the access is blocked, and the denial event is linked to the alert and ledger Given vendor V is assigned to properties P1 and P2 and policy mode is Monitor When V views a work order for property P3 Then an anomaly is created within 5 seconds, access is allowed, and notifications are sent to the designated property manager recipients Given vendor-property assignments are updated When an access occurs at least 60 seconds after the update Then the rule evaluates against the updated assignments; access and alert outcomes reflect the new scope
Routing, Per-Tenant Thresholds, Quiet Hours, Acknowledgement and Escalation
Given tenant A routes Bulk Export alerts to security@a.com and After-Hours alerts to ops@a.com When both alert types are generated Then notifications are delivered to their respective recipient lists and the alert audit shows recipients, channels, and delivery timestamps Given tenant A sets Bulk Export threshold=300 and tenant B sets threshold=1000 When a user exports 600 records in each tenant Then tenant A receives a Bulk Export alert and tenant B does not; both decisions are logged with evaluated parameters Given quiet hours 22:00–06:00 and an escalation rule for Critical severity to on-call SMS only When a Critical anomaly occurs at 01:00 Then only SMS is sent to the on-call contact, email is deferred until 06:00, and in-app shows a queued banner Given an acknowledgement SLA of 15 minutes and an alert with multiple recipients When any recipient acknowledges the alert within 15 minutes Then subsequent notifications for that incident are suppressed and alert status transitions to Acknowledged with user and timestamp Given the same acknowledgement SLA When no recipient acknowledges within 15 minutes Then the alert auto-escalates to secondary recipients, severity increases by one level, and actions are logged
Alert Lifecycle Logging and Ledger Event Linking
Given an alert is created by any anomaly rule Then the system records created_at, rule_id, tenant_id, severity, recipients, channel attempts, delivery outcomes, acknowledged_at (nullable), closed_at (nullable), and all state transitions with actor and timestamp in an immutable log Given an alert references multiple ledger events (N<=50) When an investigator opens the alert detail Then the API returns the list of linked ledger_event_ids with deep links within 200 ms and the UI displays them in order of occurrence Given an alert is closed with a disposition and notes When a user with permission closes the alert Then the closure entry is append-only and tamper-evident; subsequent edits create new revision records; exports include full revision history Given a request to export alerts between two timestamps with a limit of 10,000 rows When the export is initiated Then a CSV is generated including lifecycle fields and linked event counts within 10 seconds and the export action is logged as a ledger event
Audit Reports & Evidence Exports
"As an owner-operator, I want exportable, signed audit reports so that I can satisfy board and insurer requirements with minimal effort."
Description

Enable generation of exportable reports covering specified time ranges, users, properties, or work orders in CSV and PDF formats. Include summary statistics, detailed event listings, and cryptographic checksums with chain proofs for integrity validation. Support scheduled delivery, watermarking, column dictionaries, and large export chunking with resumable downloads. Log report generation as an event, enforce role-based access, and ensure exported content respects redaction and masking policies required by boards, insurers, and auditors.

Acceptance Criteria
Scoped time-range export by user/property/work order
Given a user with the "Compliance Reporter" role scoped to Properties P-001 and P-002 And there exist audit events across multiple properties, users, and work orders When the user requests an audit report for 2025-08-01T00:00:00Z to 2025-08-31T23:59:59Z And applies filters userId=U-789 and workOrderId=WO-456 Then the exported report includes only events where timestamp is within the range, propertyId ∈ {P-001,P-002}, userId=U-789, and workOrderId=WO-456 And the report metadata section lists the exact applied filters and normalized UTC time bounds And the total event count equals the count returned by the audit API for the same filter And the act of requesting the report is logged as an audit event with actor, scopes, filter summary, and result=success And if a user without "Compliance Reporter" permission attempts the same request, the system returns HTTP 403 and no export artifacts are generated or queued, and the denial is logged
CSV and PDF exports with summary statistics and detailed events
Given an eligible user initiates report generation in CSV and PDF formats for a known dataset When generation completes Then the CSV export is delivered as a ZIP containing audit_events.csv and audit_summary.csv encoded UTF-8 with RFC 4180-compliant quoting and a header row And the PDF export contains a Summary section with totals by eventType, actorRole, propertyId, workOrderId, and daily counts, followed by a paginated Detailed Events section And the CSV and PDF contain the same event set and summary totals And column order and labels match the published column dictionary And report generation is logged as an audit event with format list, row count, byte size, and checksum values
Column dictionary included and accurate
Given a report is generated in any format When the export artifacts are created Then a column dictionary is included (dictionary.csv in ZIP; final pages in PDF) listing each field name, description, data type, format example, and masking rule if applicable And every column present in audit_events.csv and the PDF Detailed Events section appears in the dictionary with matching name and description And automated validation finds zero missing or extra fields between the dictionary and the event dataset
Cryptographic checksums and chain proof validate integrity
Given a completed export When a verifier computes the SHA-256 of each file and compares it to values in manifest.json Then all checksums match And an events_chain.json provides a chain or Merkle proof for the ordered event list, including rootHash, eventIndex hashes, and anchorTimestamp And recomputing the chain from the event payloads reproduces the rootHash And the rootHash matches the value recorded in the system ledger for the export time window And any tampering with a single row or file causes checksum or chain verification to fail
Scheduled delivery to external recipients with secure access
Given a schedule is created to deliver a monthly PDF+CSV audit report on the 1st at 08:00 UTC to designated recipients When the schedule triggers Then the system generates the report with the saved filters and delivers notification emails containing single-use, RBAC-checked, expiring links (default expiry 7 days, configurable) And only authenticated users with permitted roles and scopes can access the links; unauthorized attempts receive 403 and are logged And delivery failures are retried up to 3 times with exponential backoff and surfaced as alerts to the owner And the schedule execution (success or failure) is logged as an audit event including recipient list hash, artifact sizes, and checksums
Watermarking and redaction/masking comply with policy
Given an export is generated for auditor consumption When artifacts are created Then the PDF pages are watermarked with “Confidential – Generated for {recipientEmail} – {UTC timestamp} – FixFlow Access Ledger” And the CSV includes a first-line comment watermark beginning with “# Confidential …” containing the same information And sensitive fields (e.g., tenantEmail, tenantPhone, accessCode, exact unit address) are masked according to policy (e.g., last 4 digits only, domain only, full masking for accessCode) And unmasking is available only to users with the “Data Unmask” permission; all unmask actions are logged And spot-check records with known values confirm masking rules are applied consistently across both formats
Large export chunking and resumable downloads
Given an export results in artifacts exceeding 100 MB or 250,000 rows When a recipient downloads the files Then the server provides chunked downloads supporting HTTP Range requests and resumable transfers using the same secure link And interrupting the download and resuming completes without data loss And the reassembled file’s SHA-256 matches the manifest checksum And partial artifacts are cleaned up automatically after link expiry, and cleanup is logged
Privacy-Aware Reveal Tracking & Redaction Controls
"As a data privacy lead, I want all reveals of masked information to require justification and be fully tracked so that we meet privacy obligations and deter misuse."
Description

Require justification codes (and optional ticket links) for reveals of masked data such as lockbox codes, bank details, W-9 files, and tenant contact info. Log each reveal with requester, scope, purpose, and duration; automatically re-mask after timeout and overlay a visual watermark during reveal sessions. Prevent reveals from being recorded in screenshots or logs by excluding sensitive values from the ledger payload while marking reveal events and secure view tokens. Enforce least-privilege access, deny reveals for unauthorized roles, and provide configurable policies per tenant.

Acceptance Criteria
Justified Reveal of Masked Data
Given a user with permission to reveal a masked field (lockbox code, bank details, W-9, tenant contact) When the user clicks Reveal Then a modal prompts for a justification code from the tenant-configured list And an optional ticket link is validated as an https URL; if policy requires it, the field is mandatory And the Confirm button remains disabled until a valid justification (and required ticket link) is provided And on confirmation the reveal begins and an event is recorded with requester ID, role, resource/field, justification code, ticket link, timestamp
Least-Privilege Role Enforcement
Given a user lacks reveal permission for the resource per tenant policy When the user attempts to reveal a masked field Then the UI control is hidden or disabled and API attempts return HTTP 403 And a deny event is logged without sensitive value exposure And zero characters of the sensitive value are rendered to the DOM
Auto Re-Mask and Watermark Session
Given a reveal session starts for a sensitive field When the value is displayed Then a visible diagonal watermark overlays the reveal area with user identifier, tenant, UTC timestamp, client IP, and token suffix And the value re-masks automatically on timeout (default 60s, policy 10–600s), on tab blur or inactivity >15s, or when Hide is clicked And after re-mask, scrolling/printing shows only the masked placeholder And text selection and copy within the reveal area are blocked; clipboard contains "REDACTED" if attempted
Ledger Event Without Sensitive Payload
Given any reveal occurs When the access ledger record is persisted and exported Then it includes requester ID, role, tenant ID, resource type/ID, property or work-order link, field name, justification code, ticket link, start/end timestamps, duration (s), client IP, user agent, reveal token ID, and policy version And it excludes the sensitive value and any hashes of it And all ledger APIs/exports return the same fields without the sensitive value And automated checks confirm the sensitive value is absent from DB tables, logs, and audit exports
Per-Tenant Policy Configuration and Enforcement
Given a tenant admin updates reveal policy (allowed roles, timeout, justification code list, ticket link requirement, off-hours window, bulk reveal threshold) When the policy is saved Then inputs are validated and versioned And the policy propagates and is enforced for new reveals within 5 minutes of save And each reveal event records the applied policy version And a default system policy applies if no tenant policy exists
Real-Time Anomaly Alerts for Unusual Reveals
Given anomaly rules are configured (e.g., >5 reveals/10 min per user, reveals 00:00–05:00 local, >3 reveals of same resource/day, >2 IPs/30 min, bulk export rate) When a rule is triggered Then alerts are sent to configured channels (email/SMS/webhook) within 60 seconds And an alert record is created with rule ID, user, tenant, related event IDs, and status And optional automatic actions (temporary throttle of reveals) are applied and logged if enabled
Secure Single-Use Reveal Tokens
Given a reveal is initiated When a secure view token is issued Then the token is single-use and scoped to tenant, user, resource ID, field name, and device fingerprint And it expires no later than (reveal timeout + 30s) and is invalid from different IP/device And any reuse or context mismatch returns 401 and is logged as suspicious And revoking the user session immediately invalidates outstanding tokens
Role Change & Approval Lineage
"As an account administrator, I want every role change and approval captured with lineage so that I can demonstrate proper access governance and separation of duties."
Description

Capture full lineage for role and permission changes, including requester, approver(s), before/after roles, justification, effective time, and links to related work orders or tickets. Enforce separation-of-duties rules (no self-approval), support multi-step approvals, and correlate subsequent sensitive access with the preceding elevation event. Expose a timeline view in the ledger for each user’s role history and alert on unusual escalations or unapproved changes. Ensure all governance actions are immutable and included in integrity proofs and reports.

Acceptance Criteria
Record Complete Role Change Lineage
Given a role change request is submitted via UI or API with justification text and links to at least one related work order or ticket When the final approver approves the request Then the ledger records a single immutable entry containing: requester_id, approver_ids in approval order, before_roles[], after_roles[], justification, effective_at (UTC ISO-8601), source (UI|API), related_ids[], request_id, and per-step timestamps. Given a role change request is approved When the ledger entry is retrieved by request_id Then all recorded fields are present, correctly typed, and match the submitted and system-derived values; timestamps are ISO-8601 UTC; arrays preserve approval order. Given a role change request omits justification or before/after roles When the request is submitted Then the system returns 400 with a validation error and no ledger entry is created.
Enforce Separation of Duties (No Self-Approval)
Given user A submits a role change request for themselves When user A attempts to approve any step of that request Then the system blocks the action with 403 "self-approval prohibited" and appends a denied_attempt event to the request audit. Given a multi-step approval workflow When the same user attempts to approve multiple required steps that must be distinct approvers Then the system blocks the second approval with 403 and no additional approval step is recorded. Given a role change request requires at least one approver distinct from the requester When the final approval is recorded Then the ledger shows distinct user IDs for requester and each approver.
Multi-Step Approval Workflow and Effective Time
Given a role change requires two approvals in order [Manager, Security] When Manager approves and Security rejects Then the request status becomes "Rejected", no role change is applied, and the ledger records both decisions with timestamps. Given a role change requires N approvals When the Nth approval is recorded Then the role change becomes effective at either the specified effective_at or immediately if none specified, and the ledger captures the actual effective_at in UTC. Given a pending multi-step approval When 48 hours elapse without action on a step Then the system escalates per policy, records the escalation event in the ledger, and notifies the next-level approver.
Correlate Sensitive Access to Prior Elevation
Given a user’s role is elevated via an approved request with correlation_id C and duration D When the user performs a sensitive action (e.g., PII view, bulk export, secret reveal) within D Then the sensitive access event is linked to C in the ledger and visible in the user’s timeline. Given a sensitive action occurs without a preceding approved elevation covering that time When the action is logged Then the system flags the event as "unapproved access", emits an alert, and marks the ledger entry with severity "high". Given multiple sensitive actions occur under the same elevation C When viewing the ledger Then each event references C and the elevation entry lists the count of correlated actions.
User Role History Timeline View
Given a user is selected in Access Ledger When the role history timeline is opened for a date range Then entries are displayed in chronological order with filters for action type (request, approval, elevation, alert), role, property, and work order, and pagination supports at least 1000 entries. Given the timeline is loaded with 1000 entries When rendered on a standard network Then initial render completes within 2 seconds and subsequent filter changes apply within 1 second. Given an entry is clicked When details are expanded Then the panel shows requester, approvers, before/after roles, justification, effective_at, related_ids, and integrity proof hash.
Immutable Governance Actions and Integrity Proofs
Given any governance action (request submission, approval, rejection, elevation effective) is recorded When the ledger writes the entry Then it is hashed and included in a hash chain with previous entries and anchored in a daily root. Given an integrity proof is requested for a ledger entry E When verification is performed Then the system returns "valid" if E’s hash and chain membership match the anchored root; otherwise returns "invalid" and raises a tamper alert. Given a ledger entry is attempted to be modified post-write by an admin When the operation is performed Then the system prevents mutation; if a correction is needed, a compensating entry is appended and linked to the original, preserving immutability.
Real-Time Anomaly Alerts for Role Escalations
Given a role escalation is approved between 23:00 and 05:00 local tenant time When the approval is recorded Then an anomaly alert is sent to configured channels within 60 seconds containing requester, approver, roles changed, justification, and effective_at. Given the same approver approves more than 5 role changes within 10 minutes When the 6th approval is recorded Then a bulk-approval anomaly alert is emitted and subsequent identical alerts are deduplicated for 5 minutes. Given a requested role change increases privileges beyond a defined risk threshold When the request is approved Then the ledger marks the escalation as "high-risk" and an alert is generated with recommended review actions.

Smart Sequencer

Automatically orders turnover tasks with built‑in cure times, building rules, vendor lead times, and quiet hours. When any step shifts, downstream dependencies and appointments reflow instantly. You get realistic, clash‑free schedules without manual drag‑and‑drop.

Requirements

Dependency-Aware Task Orchestration
"As a property manager, I want turnovers to be automatically sequenced with all constraints applied so that I get a realistic, clash-free plan without manual drag-and-drop."
Description

Implements a constraint-based scheduling engine that builds a directed acyclic graph (DAG) of turnover tasks per unit, honoring task durations, cure/buffer times, prerequisites, earliest start dates, building rules, vendor work hours, and lead-time constraints. On creation of a turnover, the system assembles a task sequence from templates, computes feasible start/finish times, identifies the critical path, and persists the resulting plan to the shared calendar. Produces a realistic, clash-free baseline schedule that integrates with FixFlow’s jobs, calendar, and messaging, and can be recalculated deterministically as inputs change. Handles time zones, business days, and partial-day granularity (15–30 min). Ensures idempotent scheduling with consistent outcomes and performance suitable for 1–200 unit portfolios.

Acceptance Criteria
Baseline Schedule Assembled from Template
Given a new turnover is created for Unit A using Template T with defined tasks, durations, prerequisites, cure/buffer times, earliest start dates, vendor assignments, and building rules, and the property time zone is America/Los_Angeles When the scheduling engine runs for the turnover Then it constructs a DAG capturing all task dependencies without cycles And computes earliest feasible start/finish times respecting durations, cure/buffer times, prerequisites, earliest start dates, vendor work hours, building rules, holidays, business days, and a minimum 15-minute granularity And identifies and flags the critical path for the turnover And persists the baseline plan to the shared calendar with event titles, start/end timestamps, property time zone, and links to immutable task IDs And the resulting schedule contains zero constraint violations and zero overlapping events for the same unit or vendor resource
Cure/Buffer Time Enforcement and Propagation
Given Task A = "Floor Refinish" (duration 6h) and dependent Task B = "Move-In Clean" with required cure/buffer time of 48h after Task A completion When Task A is scheduled or delayed Then Task B’s earliest start is at least 48h after Task A’s actual finish and outside quiet hours and vendor off-hours And all downstream tasks of Task B are shifted to maintain precedence and buffers And no downstream task starts before all its predecessors’ buffers elapse
Lead-Time Constraints and Earliest Start Dates
Given predecessor Task X = "Order Appliances" and successor Task Y = "Appliance Delivery" requiring vendor lead time of 3 business days and an earliest start of Monday 09:00 in the property time zone When Task X is scheduled to complete on Wednesday 14:00 Then Task Y’s scheduled start is the later of (Monday 09:00) and (3 business days after Wednesday 14:00), adjusted to vendor work hours and 15-minute granularity And weekends and configured holidays are excluded from the business day count And Task Y is not scheduled earlier even if the calendar shows availability
Quiet Hours and Building Rules Compliance
Given building quiet hours are 22:00–07:00 local time and rule "No noisy tasks on Sundays" is active, and Task Z = "Demolition" is marked as noisy When the schedule is generated for a date range that includes a Sunday Then no portion of Task Z is scheduled during 22:00–07:00 or on any Sunday And if Task Z spans multiple days, it is split across allowed windows while preserving dependencies and 15-minute granularity And no other tasks marked noisy appear during restricted periods
Vendor Work Hours and Time Zone Handling
Given Vendor V has work hours 08:00–17:00 America/New_York, and the property is in America/Chicago, and tasks T1/T2 are assigned to Vendor V When T1/T2 are scheduled Then all start/end times for T1/T2 fall within 08:00–17:00 in Vendor V’s local time on business days And the shared calendar displays events in the property’s time zone while preserving the correct UTC instants And there are no off-by-one-day errors across daylight saving transitions
Deterministic Recalculation and Idempotent Persistence
Given identical inputs (templates, constraints, current calendar state) for a turnover When the scheduling engine is run multiple times without input changes Then it produces identical task start/end times and the same critical path on every run And a subsequent run makes zero calendar writes and leaves event IDs unchanged When only one task’s duration increases by 2 hours Then only that task and its dependents shift by the minimal necessary amount; unrelated tasks remain unchanged And the change set is deterministic across runs
Resource Conflict Prevention and Partial-Day Granularity
Given two tasks assigned to the same vendor across different units and crew capacity = 1 When both tasks would otherwise overlap in time Then the engine sequences them to eliminate overlap with at least a 15-minute separation And no vendor, unit, or location resource has overlapping assignments anywhere in the baseline And all task times snap to 15-minute increments (or 30-minute when globally configured) And the scheduler computes a solution within 2 seconds for turnovers up to 25 tasks and within 90 seconds for a batch of 200 concurrent turnovers averaging 15 tasks each
Real-time Reschedule Propagation
"As a coordinator, I want downstream tasks to reflow automatically when something shifts so that schedules stay accurate and I don’t have to manually fix cascades."
Description

When any upstream task or appointment changes (delay, early finish, cancellation, vendor confirmation), the engine recalculates downstream tasks and vendor holds, preserving locked/confirmed slots where possible and minimizing overall disruption. Applies all constraints (cure times, quiet hours, lead times) and updates the shared calendar in near real-time. Surfaces impact analysis (e.g., new move-in ETA, tasks pushed outside working hours), flags infeasible schedules, and prompts for decisions (override, alternate vendor, extend hours). Sends notifications to affected stakeholders and records the change reason. Targets sub‑2s recompute for typical turnovers; falls back gracefully with queued recompute if larger.

Acceptance Criteria
Upstream Task Delay Triggers Reflow
Given a turnover project with dependent tasks, configured cure times, quiet hours, and vendor lead times And at least one vendor hold and one confirmed appointment exist downstream of Task A When Task A’s end time is delayed by 45 minutes Then the engine recalculates all affected downstream tasks and vendor holds within 2 seconds for projects with ≤ 30 tasks And only tasks that are dependent on Task A or that now conflict with constraints are rescheduled; unrelated tasks remain unchanged And all confirmed appointments are preserved unless infeasible under constraints And if a confirmed appointment cannot be preserved, the system proposes at least two feasible alternatives (alternate vendor or extended hours) and requires an explicit user decision And the shared calendar reflects the new schedule within 3 seconds of recompute completion
Early Finish Pulls Schedule Forward Within Constraints
Given a turnover project with dependent tasks and configured cure times, quiet hours, and vendor lead times And Task A completes 60 minutes earlier than planned When the completion is recorded Then downstream tasks are pulled forward as allowed by cure times and vendor lead times without violating quiet hours And confirmed appointments are not moved earlier without explicit user confirmation And the updated move‑in ETA is recalculated and displayed within 2 seconds for projects with ≤ 30 tasks And the shared calendar updates within 3 seconds with the new start/end times
Cancellation Reflows Dependencies and Frees Holds
Given a turnover sequence where Task B depends on Task A And Task A is canceled by a user with a required change reason selected When the cancellation is saved Then the engine removes Task A from the critical path and recalculates Task B’s earliest feasible start respecting cure times, quiet hours, and vendor lead times And all vendor holds associated solely with Task A are released within 3 seconds And any downstream task that becomes infeasible is flagged with the specific blocking constraint And the shared calendar removes Task A and reflects the reflowed schedule
Vendor Confirmation Locks Slot Preservation
Given an appointment for Vendor V on Task C is marked Confirmed And constraints (quiet hours, cure times, lead times) are active When any upstream change triggers a reflow Then the system preserves the confirmed slot for Task C if it remains feasible under constraints And if preservation is infeasible, the user is prompted to choose: override confirmation, select an alternate vendor, or extend working hours (if policy allows) And no automatic cancellation of the confirmed slot occurs without explicit user action And the selected decision and reason are recorded to the audit log
Impact Analysis and Infeasibility Reporting
Given a reflow has been computed for a turnover project When the new schedule is ready to apply Then an impact panel shows: delta to move‑in ETA, count of tasks rescheduled, list of tasks outside working hours, and any vendor conflicts And any infeasible tasks are listed with the exact violated constraint and suggested resolution actions And the schedule cannot be applied while unresolved blocking infeasibilities remain unless the user selects an override option with a recorded reason
Performance and Graceful Degradation for Large Reflows
Given a typical turnover project with ≤ 30 tasks When a reflow is triggered Then 95% of recomputations complete in under 2 seconds and the result is atomically applied to the calendar Given a large project with > 30 tasks When a reflow is triggered Then the UI indicates “Recomputing” and either applies results within 10 seconds (95th percentile) or queues the job and notifies the user on completion And until application, the prior schedule remains visible and valid (no partial state) And recompute duration and outcome are recorded as metrics
Stakeholder Notifications and Audit Trail
Given a reflow results in changes to any task or appointment When the new schedule is applied Then affected stakeholders (landlord/PM, vendors, tenants) receive notifications via their configured channels (in‑app, email, SMS) within 30 seconds And each notification summarizes the before/after times, required actions, and links to confirm or propose alternatives (where applicable) And an audit entry records initiator, timestamp, change reason, affected tasks/appointments, and before/after times, exportable as CSV
Vendor Lead-Time Auto-Booking
"As a landlord, I want the system to auto-book vendors based on their lead times and availability so that turnovers start sooner with fewer calls and texts."
Description

Integrates vendor profiles (service types, SLA lead times, capacity, working hours, blackout dates, travel buffers) to propose and place provisional holds on the earliest feasible time slots that satisfy task dependencies and building rules. Supports vendor preference ranking, alternates if the primary cannot meet the window, and automatic SMS/email confirmations with expiration windows; releases unconfirmed holds automatically. Prevents double-booking by creating provisional calendar events and reconciling confirmations into locked appointments. Exposes vendor availability hints in the UI and via API to inform planning and reduce back-and-forth.

Acceptance Criteria
Earliest Feasible Slot Computation
Given a turnover task with dependency finish T_dep and cure time C, building quiet hours Q, and a service type S with window [T_earliest, T_latest] And a vendor V with profile {SLA lead time L, capacity k, working hours W, blackout dates B, travel buffer Tb} When the system computes auto-booking at time T_req Then it proposes the earliest start >= max(T_dep + C, T_req + L, T_earliest) within W and before T_latest And the proposed window does not intersect Q, B, or exceed capacity k when Tb buffers are applied around existing commitments And the computation returns a result within 1500 ms for up to 50 candidate vendors
Provisional Hold Creation and Calendar Blocking
Given an earliest feasible slot S and duration D for vendor V When the system places a provisional hold Then it creates a provisional calendar event that blocks capacity for D with Tb buffers And assigns an expiration timestamp Exp = now + policy_hold_window And surfaces the hold in vendor calendar feeds and the landlord dashboard with status = "Provisional" And ensures idempotency so repeated requests with the same key do not create duplicate holds
Confirmation Flow With Expiry and Notifications
Given a provisional hold H with expiration Exp When notifications are dispatched Then vendor receives SMS and email containing job details, confirm/decline actions, and Exp And tenant/landlord receive a pending notification with the tentative time And if vendor confirms before Exp, H transitions to status = "Locked", calendar event is updated to locked, and stakeholders receive confirmation And if vendor declines before Exp, H is released immediately and stakeholders are notified And all notification delivery and action events are audit-logged with timestamps and channel outcomes
Vendor Preference Ranking and Alternate Selection
Given a ranked list of preferred vendors for service type S And a target window derived from task dependencies and rules When the primary vendor cannot satisfy constraints, declines, or times out at Exp Then the system evaluates the next vendor by rank, skipping any that cannot meet constraints And places a provisional hold for the first feasible alternate and repeats until one confirms or the list is exhausted And if no vendors are feasible, the system returns "No feasible availability" with next-available suggestions and reason codes (e.g., blackout, capacity, quiet hours)
Double-Booking Prevention and Confirmation Reconciliation
Given concurrent auto-booking operations for the same vendor and time range When provisional holds or confirmations are processed Then capacity constraints (including Tb buffers) are enforced atomically so no overlapping provisional or locked events exceed capacity k And on vendor confirmation, the provisional event converts to a locked appointment, and any conflicting provisional events are auto-released with notifications And operations are idempotent and safe under retries, with optimistic locking preventing race-induced double-bookings
Vendor Availability Hints in UI and API
Given a unit, service type S, and date range R When the user opens the scheduling panel or calls GET /vendors/availability with S and R Then the UI displays per-vendor earliest start and up to three next feasible windows with reason tags for exclusions And the API responds with ISO-8601, timezone-aware windows, constraint flags (quiet_hours, blackout, capacity, travel_buffer), and generated_at timestamp And responses are returned within 1500 ms and cached for 5 minutes per unique query parameters
Reflow of Holds When Dependencies or Rules Change
Given existing provisional holds or locked appointments affected by an updated dependency time, cure time, building rule, or quiet hours When the change invalidates or shifts the feasible window Then provisional holds are automatically re-evaluated and moved to the earliest feasible alternative with updated Exp and notifications to stakeholders And locked appointments surface a conflict with suggested alternatives and do not auto-move unless a policy flag enable_auto_move = true is set And availability hints and API responses reflect the change within 60 seconds
Quiet Hours Compliance
"As a building manager, I want noisy tasks to be automatically scheduled outside quiet hours so that we comply with building rules and avoid complaints."
Description

Enforces building- and HOA-level quiet hours by classifying tasks as noisy or quiet and restricting noisy work to allowed windows. Automatically routes non-noisy tasks into quiet windows to maintain progress. Supports per-building rules, holiday exceptions, and temporary overrides with approval and audit. Provides clear error messages and alternative slot suggestions when a chosen time violates quiet hours, ensuring tenants and building policies are respected without manual policing.

Acceptance Criteria
Enforce Noisy Task Quiet Hours by Building
Given Building A has quiet hours set to 20:00–08:00 Monday–Friday and 18:00–09:00 Saturday–Sunday in its local timezone And Task T is classified as Noisy When a user or the Smart Sequencer attempts to schedule Task T to start at 07:30 on a Tuesday Then the system blocks the scheduling and displays a quiet-hours violation message specifying the prohibited window (20:00–08:00) And no appointment is created or modified And the same task scheduled at 08:01 on the same Tuesday is accepted And all evaluations use the building’s local timezone
Task Noise Classification and Overrides
Given Task Type "Floor Sanding" default classification is Noisy and Task Type "Paint Touch-Up" default classification is Quiet And users with the Coordinator role may override a task’s noise classification at the task instance level When a new "Floor Sanding" task is created Then the task defaults to Noisy When a Coordinator changes the classification to Quiet and saves Then subsequent scheduling uses Quiet-hour rules for that task And the change is recorded in the audit log with user, timestamp, previous value, new value, and optional reason
Auto-Route Quiet Tasks into Quiet Windows
Given Building A has quiet hours defined And Task Q is classified as Quiet and has a free vendor slot inside quiet hours When the Smart Sequencer generates or reflows a turnover schedule Then Task Q may be placed within quiet hours to maintain progress And no Noisy task is placed within quiet hours And task dependencies, cure times, and vendor lead times are respected
Holiday Exception Handling for Quiet Hours
Given Building A defines a holiday on 2025-12-25 with quiet hours 00:00–24:00 that override standard rules When attempting to schedule a Noisy task on 2025-12-25 at any time Then the system blocks scheduling and cites the holiday name and date in the violation message And a Quiet task may be scheduled on 2025-12-25 if dependencies allow And on 2025-12-26 the standard (non-holiday) quiet hours are applied
Temporary Quiet Hours Override with Approval and Audit
Given a Coordinator submits a temporary quiet-hours override request for Building A allowing Noisy work from 19:00–21:00 on 2025-10-15 with a stated reason And an Approver with the Quiet Hours Approver role reviews the request When the Approver approves the override with explicit start and end times and defined scope (building or unit) Then Noisy tasks can be scheduled only within the approved override window and scope And all actions are recorded in an immutable audit log including requester, approver, timestamps, reason, scope, and affected tasks/appointments And after the window ends, further Noisy scheduling during quiet hours is blocked again
Alternative Slot Suggestions and Error Messaging
Given a user attempts to schedule a Noisy task at 07:30 on a weekday where quiet hours end at 08:00 When the system blocks the action due to quiet hours Then it displays an error that includes: rule name (Quiet Hours), building name, prohibited time window, local timezone, and a link to the policy And it suggests at least three next-available compliant slots within the next 14 days that also satisfy dependencies and vendor availability And selecting a suggested slot reschedules the task and updates dependent tasks accordingly without conflicts
Reflow Downstream Appointments on Quiet Hour Conflict
Given a preceding task delay would push a Noisy task into a quiet-hours period When the Smart Sequencer reflows the schedule Then the Noisy task is moved to the next permissible window after quiet hours And all dependent tasks shift to maintain lead times and cure/cure times without double-booking vendors And impacted parties (tenant, vendor, manager) are notified via SMS/email within 2 minutes of the change And the reflow completes within 5 seconds for projects up to 50 tasks
Conflict Detection & Resolution Suggestions
"As a scheduler, I want the system to flag conflicts and suggest fixes so that I can resolve issues quickly without breaking the overall plan."
Description

Detects and prevents scheduling conflicts across tasks, vendors, and shared resources (elevators, loading docks, keys, water shutoffs). Validates for over-capacity, overlapping appointments in the same unit/space, and vendor double-bookings. Offers ranked resolution options such as shifting by buffer, swapping non-dependent tasks, splitting tasks across days, or selecting alternates, with estimated impact to move-in date and cost. Provides a lightweight what‑if mode to preview outcomes before applying changes.

Acceptance Criteria
Unit Overlap Conflict Prevention
Given Unit U has an existing appointment from 2025-01-10 09:00 to 11:00 And the user attempts to schedule a new task in Unit U from 10:30 to 12:00 When the user tries to save the schedule Then the system detects a "Unit overlap" conflict identifying both task IDs and the overlapping interval 10:30–11:00 And the save is blocked until a resolution is selected And the conflict appears in the conflict list with severity "High" and an incremented conflict count
Vendor Double-Booking Detection Across Properties
Given Vendor V has capacity=1 for 2025-02-15 09:00–12:00 And Task A assigns Vendor V from 09:00 to 10:30 And Task B assigns Vendor V from 10:00 to 11:00 When Task B is saved Then the system flags a "Vendor double-booking" conflict for Vendor V with overlapping interval 10:00–10:30 And the save is blocked until the conflict is resolved And the conflict list displays Vendor V, the conflicting task IDs, and the overlap window
Shared Resource Capacity Enforcement (Elevator/Loading Dock)
Given Shared Resource "Elevator E1" has capacity=1 per timeslot And Task A requires Elevator E1 from 08:00 to 09:00 And Task B requires Elevator E1 from 08:30 to 09:30 When Task B is saved Then the system detects a "Resource over-capacity" conflict for Elevator E1 with occupancy 2/1 for 08:30–09:00 And the save is blocked until a resolution is applied And the conflict list shows the resource name, conflicting tasks, and exceeded capacity
Ranked Resolution Suggestions With Impact Estimates
Given at least one conflict exists on the schedule When the user opens Resolve Conflicts Then the system generates at least 3 resolution options where feasible, drawn from [Shift by buffer, Swap non-dependent tasks, Split across days, Select alternate vendor] And each option displays estimated move-in date impact (in hours/days) and cost delta (in currency) And options are ranked first by least move-in delay, then by lowest cost And selecting an option previews its effect without committing changes
What‑If Mode Preview Without Side Effects
Given a schedule with pending conflicts or planned changes When the user enters What‑if mode Then subsequent changes are simulated only and not persisted to the live schedule And no notifications (SMS/email) are sent to tenants or vendors while in What‑if mode And the UI labels the view as "What‑if" and provides Revert and Apply Changes controls And choosing Revert discards all simulated changes And choosing Apply Changes persists changes, recalculates dependencies, and exits What‑if mode
Dependency Reflow After Task Shift
Given Task B depends on Task A with a 24-hour cure time And quiet hours are configured from 22:00 to 07:00 When Task A is shifted later by 8 hours Then Task B is rescheduled to start no earlier than 24 hours after Task A’s new end time while respecting quiet hours And the system re-validates the schedule and updates the conflict count accordingly And any new conflicts introduced are listed for review
Cost and Move‑In Impact Calculation Accuracy
Given property vacancy cost is configured as $75/day And Vendor V hourly rate is configured as $90/hour And a proposed resolution "Split across days" adds 1 day to the move-in and 2 vendor hours When the system displays impact estimates Then the move-in impact shows +1 day And the cost impact equals $255.00
Schedule Audit Trail & Rollback
"As an owner-operator, I want a clear history of schedule changes and the ability to roll back so that I can trace decisions and recover from bad updates."
Description

Maintains a complete, immutable history of schedule versions and changes, including actor, timestamp, trigger (manual edit, vendor response, rule update), and rationale. Supports diff views between versions, a readable timeline of adjustments, and one-click rollback to a prior feasible schedule with revalidation against current constraints. Exposes audit data in the UI and via export to support accountability with owners, tenants, and vendors.

Acceptance Criteria
Immutable Audit Entry on Every Schedule Change
Given an existing Smart Sequencer schedule for a property And potential change sources include manual edit, vendor SMS response, rule update, and automatic reflow When any change that alters the schedule state is applied Then a new schedule version is created with a monotonically increasing version number And an audit entry is appended containing version_id, parent_version_id, actor_id, actor_type, trigger_type, rationale (0–500 chars), timestamp (ISO 8601 UTC), and affected_task_ids And the audit entry is immutable: any update or delete attempts return 403 and do not modify stored data And duplicate events with the same idempotency_key do not create duplicate versions or audit entries And the audit entry is persisted within 500 ms at the 95th percentile
Diff View Between Any Two Schedule Versions
Given two schedule versions A and B are selected by the user When the diff view is opened Then the system displays added, removed, and modified tasks And for each modified task shows changed fields with old and new values (start, end, vendor_id, location, notes) And constraint-related changes (cure times, quiet hours, building rules) are labeled And the view supports filtering by change type and search by task name And diffs for schedules up to 200 tasks render within 2 seconds at the 95th percentile And the downloadable diff export matches the on-screen diff exactly
Readable Audit Timeline of Adjustments
Given a schedule with 100 or more audit entries When the timeline view is opened Then entries are ordered newest-first and grouped by calendar date in the property’s timezone And each entry shows timestamp with timezone, actor display name, trigger type, and rationale (truncated to 140 chars with expand control) And selecting an entry opens the diff between that version and its parent And the timeline supports text search across actor, trigger, and rationale returning results within 1 second at the 95th percentile And pagination or infinite scroll loads in pages of 50 entries without skipping or duplicating entries
One-Click Rollback to Prior Feasible Version
Given a prior version Vn is selected in the audit timeline And current constraints (vendor availability, building rules, cure times, quiet hours) are up to date When the user clicks Rollback Then the system validates Vn against current constraints And if no violations are found, creates a new head version identical to Vn And updates linked calendar appointments and dispatches vendor/tenant notifications per policy And records an audit entry with trigger_type "rollback" referencing Vn And the rollback completes within 5 seconds at the 95th percentile
Rollback Feasibility Failure Handling
Given a prior version Vn is selected for rollback When validation detects conflicts with current constraints Then no schedule changes are committed And the user is shown a modal listing specific blocking reasons per task (e.g., vendor conflict, quiet hours violation, unmet cure time, building rule change) And the modal provides a link to open the sequencer with suggested feasible slots pre-filtered And an audit entry is recorded with trigger_type "rollback_attempt" and outcome "rejected"
Audit Data Export (CSV/JSON)
Given a user with the "Export Audit" permission When the user requests an export with a selected date range and format (CSV or JSON) Then the exported file includes version_id, parent_version_id, property_id, unit_id, actor_type, actor_id, actor_name, trigger_type, rationale, timestamp_utc, affected_task_ids And the export respects current UI filters And up to 10,000 entries export within 30 seconds and larger exports stream progressively And CSV output conforms to RFC 4180 and all timestamps are UTC And the export result count matches the number of entries returned by the API for the same filters
Access Control and Privacy for Audit Views
Given role-based permissions are configured When a user without "View Audit" permission attempts to access audit views or exports Then the system returns 403 and displays an access denied message with no data leakage When a vendor or tenant user views audit data Then they see only entries related to their work orders, with other parties’ PII redacted And internal staff with permission see full entries per policy And all audit view and export accesses are logged with user_id, timestamp, and requested scope

Gap Squeeze

Identifies idle gaps and safely overlaps compatible steps (e.g., locksmith during paint dry time), auto‑tuning buffers using historical punctuality and travel data. Compresses vacancy days while showing risk levels and one‑tap rollback if conditions change.

Requirements

Gap Identification & Timeline Compression Suggestions
"As a property manager, I want the system to highlight idle gaps and propose compressions so that I can reduce vacancy days without manually auditing calendars."
Description

Implement a scheduling analysis engine that scans maintenance timelines across units to detect idle gaps between tasks and propose safe overlap or compression opportunities. Integrate with FixFlow’s shared calendar to surface inline suggestions with a before/after timeline diff, projected vacancy-day savings, and conflict checks against existing bookings. Provide filters by unit, work order, vendor, and turnaround type, plus an API to fetch, accept, or decline suggestions with captured reasons for future learning. Ensure suggestions respect current assignments, availability, and property policies while preparing downstream steps for auto-notifications upon acceptance.

Acceptance Criteria
Auto-Detect Idle Gaps on Maintenance Timelines
Given a unit or portfolio work order timeline containing sequenced tasks with start/end times When the engine scans timelines Then it identifies idle gaps of >= 30 minutes and emits suggestions that include gap start/end timestamps and duration in minutes And gaps adjacent to tasks marked fixed-time or with hard constraints are excluded from movement suggestions that would violate their time windows And if no gaps >= 30 minutes exist, the engine returns zero suggestions for that scope
Propose Safe Overlap Using Compatibility and Buffers
Given two tasks are tagged as compatible and non-interfering and fall within allowed property policy windows And historical punctuality and travel variance data exist for the assigned vendors When the engine evaluates potential overlap Then it proposes an overlap with an explicit buffer (minutes) computed from historical variance + travel time And assigns risk level as: Low if buffer >= P90 variance, Medium if P70–P89, High if < P70 And excludes any overlap that violates vendor availability, travel feasibility, or property policies
Inline Before/After Diff with Vacancy-Day Savings
Given a compression or overlap suggestion exists for a work order When viewed on the shared calendar Then an inline card shows the before and after start/end times for affected tasks, a visual diff, and projected vacancy-day savings in hours (rounded to 0.1h) And clicking View diff highlights moved/overlapped tasks and updates total turnaround duration on-screen within 500 ms And the card displays conflict status (Clear/Blocked) with specific reasons if Blocked
Filters by Unit, Work Order, Vendor, and Turnaround Type
Given the suggestions list is visible When the user applies filters by unit, work order, vendor, and turnaround type (e.g., Make-Ready, Emergency) Then only suggestions matching all active filters are displayed And results update within 300 ms for up to 500 suggestions And the filter state is reflected in the URL and persists across page reloads And clearing filters restores the full, unfiltered list
Suggestion API: Fetch, Accept, Decline with Reasons
Given authenticated access with valid scopes When calling GET /suggestions with pagination up to 50 items Then the response includes id, scope (unit/work order), before/after snapshots, risk level, conflict status, savings, and sort keys, with p95 latency <= 400 ms When calling POST /suggestions/{id}/accept with an idempotency key Then the calendar is updated, tasks are locked, and a 200 response returns the updated timeline and notification recipients When calling POST /suggestions/{id}/decline with a required reason (enum) and optional note (<=256 chars) Then the reason is stored with actor and timestamp, and the suggestion becomes non-actionable And all write endpoints are idempotent via idempotency key or If-Match
Conflict Checks Prevent Double-Booking and Policy Violations
Given a suggestion would create a resource double-booking, violate quiet hours, skip required dependencies, or exceed travel feasibility When the engine evaluates it Then the suggestion is marked Blocked with explicit conflict codes (e.g., DOUBLE_BOOK, QUIET_HOURS, DEPENDENCY, TRAVEL) And Blocked suggestions cannot be accepted from UI or API When a suggestion is accepted Then no double-bookings are created in the shared calendar; otherwise the API returns 409 and applies no changes
One-Tap Rollback and Auto-Notifications on Acceptance
Given a suggestion is accepted When acceptance completes Then SMS/email notifications are sent to impacted vendors and tenants within 60 seconds and are logged with delivery status And a rollback control is available in UI and via API for 24 hours or until any overlapped task starts (whichever comes first) When a triggering condition occurs (e.g., vendor ETA exceeds buffer or tenant declines) Then activating rollback restores the original schedule atomically and issues updated notifications within 60 seconds, with a full audit trail
Task Compatibility & Constraint Rules Engine
"As an operations lead, I want to define which tasks can overlap and under what conditions so that Gap Squeeze never proposes unsafe or noncompliant schedules."
Description

Create a rules framework and UI to define which task types can overlap, required buffers, and sequence dependencies (finish-to-start, start-to-start), including safety/compliance guardrails and property-specific policies. Support constraints such as room-level conflicts, quiet hours, hazardous work isolation, permit prerequisites, and vendor resource limitations. Ship with sensible defaults for common maintenance steps and allow property- and portfolio-level overrides. Enforce rules during suggestion generation and when schedules are applied to ensure that compressions remain safe, compliant, and practical.

Acceptance Criteria
Rules UI Definition and Validation
Given an admin user is on the Rules UI, When they create a rule specifying task types, dependency type (Finish-to-Start, Start-to-Start, No-Overlap), required buffer (minutes), scope (Portfolio or Property), and constraint toggles (quiet hours, room conflict, hazardous isolation, permit required, vendor capacity), Then the system validates all inputs (task types exist, buffer >= 0, scope selected) and saves the rule. Given invalid inputs (e.g., negative buffer, missing task type, incompatible options), When saving, Then the save is blocked and each invalid field shows an inline error message. Given a valid rule is saved, When generating new suggestions after save, Then the rule is enforced for matching properties and tasks; existing scheduled items are unaffected. Given a user attempts to disable a non‑overridable safety/compliance guardrail, When saving, Then the save fails with an error indicating the guardrail cannot be disabled.
Property and Portfolio Override Precedence
Given default rules are active, When both portfolio-level and property-level overrides exist for the same condition, Then the property-level rule takes precedence for that property's suggestions and apply checks; otherwise portfolio-level overrides defaults; otherwise defaults apply. Given a property-level override exists for Property A, When generating suggestions for Property B, Then Property B uses portfolio-level or default rules and is not affected by Property A's override. Given an override is updated by an authorized user, When generating subsequent suggestions, Then the updated rule is applied and the change is recorded with timestamp and actor.
Sequence Dependencies and Buffers Enforcement
Given two tasks with a Finish-to-Start dependency and a 120-minute buffer, When scheduling suggestions or applying a schedule, Then the successor start time must be >= predecessor end time + 120 minutes; violations are blocked with reason "FTS buffer". Given two tasks with a Start-to-Start dependency and a 30-minute buffer, When scheduling, Then the successor start time must be >= predecessor start time + 30 minutes; overlaps beyond this constraint are prohibited. Given a user manually drags a task to a time that violates a defined dependency or buffer, When attempting to save, Then the UI snaps to the nearest compliant time or blocks the save with a descriptive error.
Room-Level Conflict Rules
Given two tasks in different rooms within the same unit and no hazardous constraints, When a rule allows cross-room overlap, Then the suggestion engine may overlap those tasks and the apply check validates the same. Given two tasks in the same room with a No-Overlap rule between their types, When generating suggestions or applying schedules, Then overlapping is suppressed or blocked with reason "Room conflict". Given a conflict rule requires room data but the room assignment is unknown, When generating suggestions, Then the conservative default (no overlap) is applied and the suggestion is excluded with reason "Missing room data".
Quiet Hours Compliance
Given a property defines quiet hours from 21:00 to 08:00 and a task type is marked "noisy", When generating suggestions, Then proposed start/end times fall outside quiet hours; any suggestion within quiet hours is excluded with reason "Quiet hours". Given a user attempts to apply a noisy task within quiet hours, When saving, Then the system blocks the action and shows the next compliant time window. Given a task type is marked "quiet", When generating suggestions, Then quiet hours rules do not restrict its scheduling.
Hazardous Work Isolation and Permit Prerequisites
Given a task is flagged "hazardous" with a 4-hour isolation window, When scheduling, Then no other tasks in the same unit may overlap within the isolation window; suggestions/apply attempts that violate are blocked with reason "Hazardous isolation". Given a task requires a permit and the permit status is not Approved, When generating suggestions or applying dependent tasks, Then those tasks cannot be scheduled before approval and are blocked with reason "Permit pending". Given the permit status changes to Approved, When re-running suggestions, Then previously blocked tasks become schedulable subject to other active rules.
Vendor Resource Capacity and Travel Buffers
Given a vendor with capacity = 1, When generating suggestions or applying schedules across properties, Then the vendor is not assigned overlapping times; overlaps are excluded with reason "Vendor capacity". Given a vendor with capacity = N (>1), When scheduling, Then overlaps are allowed up to N concurrent assignments; any suggestion exceeding N is blocked. Given a required travel buffer of 20 minutes between different locations, When scheduling consecutive assignments for the same vendor, Then a minimum 20-minute gap is enforced; violations are adjusted or blocked with reason "Travel buffer".
Adaptive Buffer Auto-Tuning
"As a property manager, I want buffers to auto-adjust based on vendor reliability and travel so that overlaps stay realistic and reduce cascading delays."
Description

Use historical data on vendor punctuality, task duration variance, no-show rates, and location-aware travel times to auto-adjust buffers between tasks at the vendor, property, and task-type levels. Learn baseline durations and confidence bands per vendor and time-of-day, and update buffers as more data is collected. Expose an aggressiveness control to balance speed versus risk, and show rationale annotations on each buffer (e.g., travel congestion, vendor reliability). Integrate with mapping/travel-time services and FixFlow’s analytics store while honoring data privacy and opt-outs.

Acceptance Criteria
Multi-Level Baseline Learning and Application
Given there are at least 10 completed tasks for the same vendor and time-of-day bucket, When a new sequence is scheduled involving that vendor in that bucket, Then the buffer uses the learned baseline (median duration) and confidence band for that vendor/time-of-day. Given there are fewer than 10 such records, When scheduling, Then the system falls back in order to task-type-at-property baseline (if at least 10), else global task-type baseline (if at least 10), else a global default buffer. Then the applied baseline tier and values are stored with the schedule version for audit. Then the buffer amount is persisted to downstream calendar events and notifications.
Travel-Time Integration and Buffer Adjustment
Given a previous task location and a next task location with a planned departure timestamp, When computing the buffer, Then the system retrieves an ETA from the mapping service using live/forecast traffic for that timestamp. Then the buffer includes at least the ETA plus a variability margin derived from the vendor’s historical travel overrun between those zones if at least 5 records exist, else a default +3 minutes. When the planned departure shifts by 5 minutes or more, Then the ETA is re-queried and the buffer recalculated within 5 seconds. Then mapping API errors trigger up to 3 retries and fallback to the last known ETA or a static model, with the rationale indicating the fallback used.
Aggressiveness Control Adjusts Buffers and Risk Indicator
Given aggressiveness settings of Conservative, Balanced, and Aggressive, When a setting is selected, Then the buffer cushion targets the 90th, 75th, and 60th percentile of the learned duration distribution respectively. Then a risk indicator shows Low (>=85% coverage), Medium (70–84%), or High (<70%) based on the selected percentile. When the aggressiveness setting changes, Then all affected buffers update within 2 seconds and display before/after deltas. Then the selected aggressiveness is saved as a portfolio default with per-workorder override support.
Rationale Annotations Visible and Auditable
Given a buffer is displayed, When the user opens buffer details, Then the rationale lists contributing factors with numeric values: vendor on-time rate (up to last 60 jobs), learned duration band, travel ETA source and timestamp, and the selected aggressiveness level. Then the annotation explicitly states any fallbacks used (e.g., task-type baseline, default margin) and the thresholds that triggered them. Then the full rationale is accessible via API and logged with an immutable schedule version ID for audit.
Continuous Learning and Timely Updates Post-Completion
Given a task is marked Completed with actual start/end and vendor check-ins, When the completion event is processed, Then vendor/task-type/time-of-day models update within 10 minutes. Then outliers greater than 3 standard deviations from the median are excluded from model updates and flagged for review. Then buffer calculations executed after the update use the refreshed statistics.
Privacy, Opt-Out, and Data Minimization Compliance
Given a vendor or property has opted out of analytics, When computing buffers, Then their historical records are neither read nor written, and only global defaults and external ETA are used. Then no PII (names, phone numbers, exact unit numbers) appears in rationales or is sent to mapping providers beyond coordinates. Then a data-processing log records each analytics read/write with subject identifiers hashed and retention policies applied per configuration.
Dynamic Recalculation on Schedule Changes
Given a scheduled task’s start time or assigned vendor changes, When the change is saved, Then all dependent buffers are recomputed within 5 seconds using current data and the selected aggressiveness. Then any buffer that would create an overlap beyond allowed compatibility rules is flagged with a warning and suggested alternative adjustments are presented. Then the user can preview and apply the updated plan, and the prior buffer set is preserved as a versioned record for audit.
Overlap Risk Scoring & What-If Preview
"As a manager, I want to see the risk level and drivers for a compressed schedule so that I can make informed decisions about applying it."
Description

Calculate a risk score for each proposed overlap or compression using buffer uncertainty, vendor reliability, historical variance, tenant access constraints, weather, and building policies. Visualize risk tiers with contributing factors and provide a what-if preview that lets users adjust buffer aggressiveness to see immediate impacts on risk and vacancy-day savings. Include explanations for each recommendation to build trust and enable rapid decision-making in the dashboard and mobile views.

Acceptance Criteria
Risk Score Computation for Proposed Overlaps
Given a proposed overlap with buffers, vendor reliabilities, historical duration variance, tenant access windows, local weather forecast, and building policies, When the scheduler generates the plan, Then a numeric risk score from 0 to 100 is computed and attached to the overlap. Given identical inputs, When the risk is recomputed, Then the resulting score is deterministic within ±0.1 and the same contributing factor weights are returned. Given missing data for any factor, When the score is computed, Then documented defaults are applied and a "data_gap" flag is included in the factors list. Given test fixtures representing Low, Medium, High, and Critical conditions, When the score is computed, Then results fall into thresholds: Low 0–24, Medium 25–49, High 50–74, Critical 75–100. Given an overlap with a hard policy violation, When the score is computed, Then the tier is "Blocked" and the risk score is 100 with a "policy_violation" reason.
Risk Tier Visualization with Contributing Factors (Web & Mobile)
Given any computed risk score, When the overlap card is rendered on dashboard or mobile, Then a tier label and color are shown: Low (green), Medium (yellow), High (orange), Critical (red), Blocked (gray) with WCAG AA contrast ≥ 4.5:1. Given a score, When the user taps/hovers the tier, Then a panel shows the top 3 contributing factors with percent contributions summing to 100% ±1%. Given the mobile view, When the panel opens, Then it fits within the viewport, is scrollable, and closes via swipe or X within 1 tap. Given a screen reader, When the card is focused, Then the tier, score, and factors are announced with accessible labels and ARIA roles.
What‑If Preview: Buffer Aggressiveness Adjustment
Given an overlap with baseline buffers, When the user adjusts buffer aggressiveness via slider (0–100) or presets (Conservative, Balanced, Aggressive), Then the risk score and projected vacancy‑day savings update within 500 ms after last input (debounce ≤ 300 ms). Given any what‑if adjustment, When the preview is shown, Then deltas versus baseline are displayed as +/− values for risk and vacancy days, and a "Revert to Baseline" action restores original values in ≤ 200 ms. Given constraints that produce a blocked state, When the user increases aggressiveness, Then the preview displays "Blocked by [constraint]" and disables "Apply Changes". Given no network connectivity, When the user adjusts the slider, Then calculations run client‑side with last‑known data and a "stale data" badge is shown.
Recommendation Explanations and Source References
Given a recommended overlap, When the details view is opened, Then a sentence‑level explanation is shown citing top factors (e.g., vendor on‑time rate 92%, dry‑time variance ±1h, rain probability 50%). Given any factor shown, When the user taps the info icon, Then a source reference is displayed indicating data source and timestamp and matches internal logs. Given two reviewers evaluating the same recommendation, When they read the explanation, Then they can reproduce within ±1 point the risk score using the listed inputs via a documented formula. Given locales en‑US and es‑ES, When explanations render, Then values are localized (dates, numbers) while numeric percentages remain unchanged.
Aggregate Risk Across Multi‑Step Compression
Given a sequence of three or more tasks with overlaps, When the plan is generated, Then an overall chain risk is computed as the probability of at least one failure based on step risks, and the highest individual step risk is also displayed. Given the what‑if preview on the chain, When aggressiveness changes, Then both overall risk and per‑step risks update, and the overall risk is between the maximum individual step risk and 100 inclusive. Given a blocked step, When the chain is displayed, Then the overall chain tier is "Blocked" and drill‑down reveals the blocking step.
Auditability and Logging of Risk Calculations
Given any risk score computation, When it completes, Then an audit record is written capturing input values, weights, timestamp, property ID, overlap IDs, user ID (if interactive), and output (score, tier, factors). Given a what‑if session, When the user makes adjustments, Then each distinct slider position applied to the model is logged with a session ID and can be replayed to reproduce outputs. Given privacy constraints, When logging occurs, Then tenant PII and vendor phone numbers are excluded or hashed per policy and verified by unit tests. Given an administrator request, When exporting logs for a property and date range, Then a CSV is generated within 5 seconds containing the fields above, and download is gated by role "Owner" or "Admin".
One-Tap Apply & Rollback with Audit Trail
"As a coordinator, I want to apply a compressed schedule and revert instantly if needed so that I can move fast without creating coordination chaos."
Description

Enable users to apply a selected compression with one tap, updating the shared calendar and triggering SMS confirmations to vendors and tenants with acceptance links. Capture a full pre-change schedule snapshot to support instant rollback if confirmations fail, delays occur, or the user changes course. Maintain an audit trail with who, when, what changed, and why, and enforce role-based permissions for applying and reverting changes. Integrate seamlessly with FixFlow’s existing notification and scheduling subsystems to minimize friction.

Acceptance Criteria
One-Tap Apply Updates Shared Calendar
- Given a user with Apply Compression permission and a valid compression plan is displayed, When the user taps Apply, Then the system updates all affected calendar events’ start/end times and assignees per the plan and sets plan state to Pending Confirmations. - Then no double-bookings exist across vendors, units, and team members; conflicts cause the apply to abort with a clear error. - Then calendar updates propagate to the shared calendar feed within 10 seconds (p95) and the apply request completes in ≤3 seconds (p95). - Then the action produces a unique Plan ID for tracking and surfaces a success confirmation to the user.
SMS Confirmations with Acceptance Links
- Given apply has succeeded and recipients are impacted, When the system sends confirmations, Then each vendor and tenant receives an SMS within 5 seconds (p95) containing a unique, signed acceptance link scoped to their assignment. - Then delivery status (queued, sent, delivered, failed) is recorded; hard failures trigger one retry and surface an alert to the user. - When a recipient taps Accept, Then their assignment is marked Confirmed and the audit trail records the event; When they tap Decline, Then the plan moves to Attention Required. - If no response by the configured window (default 2 hours), Then one reminder is sent and the plan status changes to Pending Timeout.
Risk Levels, Warnings, and One-Tap Rollback Availability
- Given the compression plan includes risk scoring, When risk exceeds the user-defined threshold, Then the Apply button is disabled until the user acknowledges a warning modal and provides a reason. - Then the plan header displays current risk level (Low/Medium/High) and the top risk factors affecting the plan. - While confirmations are pending, Then a Rollback button is visible and enabled, showing a live count of affected events and a countdown to the timeout window.
Pre-Change Snapshot and Instant Rollback
- Given a plan is about to be applied, When Apply is tapped, Then the system persists a full pre-change snapshot including all affected events (IDs, titles, start/end, assignees, notes) and notification intents with a checksum. - When Rollback is invoked, Then all calendar events revert exactly to the snapshot, and cancellation/update SMS messages are sent to affected parties with clear messaging. - Then rollback is idempotent; repeated invocations do not alter the schedule further. - Then rollback completes within 10 seconds (p95) or surfaces progress and a retry option.
Automatic and Partial Rollback on Failures or Delays
- Given any confirmation is Declined, permanently undeliverable, or a new schedule conflict arises, When the plan is in Pending Confirmations, Then the system prompts the user to Rollback or Continue; if auto-rollback is enabled by policy, Then rollback executes without prompt. - If some assignments are Confirmed and others are not, Then the system supports partial rollback of only the unconfirmed changes while preserving confirmed ones, with a clear diff preview prior to action. - All automatic actions generate user-visible alerts and audit entries with correlation to the plan ID.
Immutable Audit Trail with Reason Capture
- Given any Apply or Rollback action, When the action completes (success or error), Then an audit entry is written recording actor (user ID and role), timestamp, plan ID, action type, reason (required free-text 5–250 chars), and a structured diff of before/after fields. - Then audit entries are immutable, time-ordered, filterable by plan ID and actor, and exportable to CSV. - Then all related confirmation events (sent, delivered, accepted, declined, timed out) are recorded with correlation IDs linking to SMS messages and recipients.
Role-Based Permissions Enforcement
- Given RBAC is configured, When a user without Schedule.Compress.Apply attempts to apply a plan, Then the action is blocked with 403 and a UI message; the attempt is logged in audit. - When a user without Schedule.Compress.Rollback attempts to rollback, Then the action is blocked similarly. - Users may only act on plans within portfolios they have access to; cross-portfolio attempts are blocked in both UI and API. - Organization Owners/Managers can apply/rollback by default; Assistants can propose but not apply unless granted explicit permission.
Real-Time Signal Monitoring & Auto-Reflow
"As a property manager, I want the system to detect when conditions change and auto-adjust the schedule so that small delays don’t turn into extra vacancy days."
Description

Monitor live signals such as vendor confirmations, ETA updates, work order status changes, tenant access messages, and weather alerts to detect conditions that threaten compressed schedules. Automatically recompute timelines when thresholds are crossed and either propose a reflow or trigger a safe rollback according to defined rules. Use an event bus/webhook pattern for extensibility, notify stakeholders of adjustments, and ensure all updates respect compatibility rules and tuned buffers.

Acceptance Criteria
Ingest External Signals via Event Bus and Webhooks
Given a subscribed webhook secured with HMAC and an idempotency key When a vendor confirmation, ETA update, work order status change, tenant access message, or weather alert payload arrives Then the payload is authenticated, schema-validated, de-duplicated by event_id, and published to the internal event bus within 2 seconds with a 2xx acknowledgment Given duplicate or out-of-order events for the same work_order_id When they arrive with the same event_id or a lower/equal sequence number Then the system suppresses reprocessing and preserves causal ordering for that aggregate Given a malformed payload When it is received Then the system responds with 4xx within 1 second and routes the payload to a dead-letter queue with error_code and reason for diagnosis
Auto-Recompute on Threshold Breach (ETA Slip)
Given a compressed schedule with tuned buffer B minutes for Task T When an ETA update reduces the effective buffer below 0 minutes or exceeds the lateness_threshold configured for the org Then the system triggers schedule recomputation within 5 seconds for all impacted tasks and dependencies And the recomputation output contains updated start/end times, updated risk_score, and affected tasks list And the recomputed schedule introduces no double-bookings and no incompatible overlaps
Decision Engine: Propose Reflow vs Auto-Rollback
Given org-level thresholds propose_threshold and auto_rollback_threshold are configured When recomputation yields risk_score >= auto_rollback_threshold Then the system automatically rolls back to the last safe schedule and marks decision_type = "rollback" Given recomputation yields risk_score between propose_threshold (inclusive) and auto_rollback_threshold (exclusive) When options are generated Then the system produces at least one reflow option that respects all compatibility rules and tuned buffers and indicates delta_vacancy_days And the manager receives an approval card to apply a selected option; no changes are applied until approved Given a rollback is executed When the prior safe state is restored Then all affected stakeholder appointments are restored to previous times and state with correlation to the triggering event
Stakeholder Notifications on Adjustments
Given any applied schedule change or proposed reflow affecting a stakeholder-visible appointment When the start or end time changes by >= 5 minutes or the appointment is canceled Then the affected tenants, vendors, and manager are notified via their configured channels (SMS/email/in-app) within 60 seconds And the notification includes old_time, new_time (or canceled), reason_event_type, risk_score, and required_action (confirm/ack) And vendor notifications include one-tap confirm/decline links; tenant notifications include access window confirm link And delivery status is tracked and failures are retried with exponential backoff (30s, 2m, 10m) up to 3 attempts before marking failed
Maintain Compatibility Rules and Tuned Buffers During Reflow
Given a compatibility matrix, tuned buffers, shared calendars, and travel-time constraints When a recomputation proposes overlaps or reordered tasks Then the validator reports 0 violations for incompatible_task_overlap, buffer_underflow, double_booking, and travel_time_violation across all impacted resources and assets And all overlaps exist only between task types marked compatible with buffer >= required minimum And the shared calendar remains conflict-free for all vendors and properties after the change
Audit Log and Traceability for Schedule Decisions
Given any schedule change or proposal is generated by an event When the decision (reflow or rollback) is produced Then an immutable audit log entry is created within 1 second containing event_id, event_source, work_order_id, before_times, after_times, decision_type, risk_score, actor (system/user), timestamp, and correlation_id And the audit entry is visible in the work order Activity timeline and retrievable via API And all outbound notifications for this decision reference the correlation_id
Latency and Resilience SLAs for Monitoring and Reflow
Given a sustained input of 10 events/second across up to 200 units When events are processed Then the 95th percentile event_received→decision_generated latency is <= 5 seconds and the 99th percentile is <= 10 seconds over a 15-minute window Given a 5-minute event bus outage When service is restored Then the system drains the backlog and returns to steady-state processing within 2 minutes with no event loss, verified by contiguous sequence numbers per aggregate Given transient downstream or notification failures When retries are attempted Then the system applies exponential backoff with jitter and surfaces final failure state without blocking subsequent unrelated decisions

Access AutoPass

Generates timeboxed access credentials and logistics per step—smart lock codes, lockbox PINs, elevator bookings, parking passes, and entry instructions—then delivers them to vendors via SMS. Prevents lockouts, reduces back‑and‑forth, and keeps access windows tight.

Requirements

Timeboxed Credential Generator & Auto-Revocation
"As a property manager, I want FixFlow to automatically generate and revoke access codes tied to a scheduled job so that vendors can enter only during the allowed window and the property stays secure."
Description

Generate unique, time-bound access credentials (smart lock codes, lockbox PINs, building entry codes) tied to FixFlow job schedules. Integrate with major smart lock/lockbox APIs where available; provide manual instructions fallback for non-integrated properties. Enforce start/end windows with optional buffers, handle time zones, prevent overlapping/duplicate codes via shared calendar, and automatically revoke/disable credentials at expiration or on cancel. Support on-demand extensions with approval, rate limiting, and robust error handling with safe defaults (no access if uncertain). Log all issuances/revocations with least-privilege storage and masked display in the UI.

Acceptance Criteria
Scheduled Credential Generation with Provider Integration and SMS
Given a job with a defined start time, end time, and selected entry point on an integrated property, When the job is set to Scheduled or its schedule is updated, Then the system generates a unique credential for that entry point with validity from [start − pre-buffer] to [end + post-buffer] in the property’s time zone, And the credential format complies with the provider’s constraints (length/charset) and is not reused for that entry point for at least 90 days, And if buffers are not specified, the system applies pre-buffer = 0 minutes and post-buffer = 15 minutes by default, And if the provider API returns timeout/error/unknown status, no credential is issued or sent, the job is marked Access Pending, and the property manager is notified, And an SMS containing the credential and the access window (with timezone abbreviation) is sent to the assigned vendor phone number upon successful issuance, with delivery status recorded, And the issuance event is recorded in the audit log with masked credential and provider request ID.
Time Window Enforcement with Buffers and Time Zone Accuracy
Given a credential valid from T1 to T2 computed as [start − pre-buffer] to [end + post-buffer] in the property’s local time zone, When access is attempted at times t < T1, T1 ≤ t ≤ T2, and t > T2, Then access is denied before T1, allowed between T1 and T2, and denied after T2 as confirmed by provider/device logs, And times displayed in UI and SMS show the property’s local time and timezone abbreviation (e.g., 2:00 PM PDT), And during a DST transition, T1/T2 map correctly to absolute UTC instants and enforcement remains correct, And all validations are recorded with timestamp and outcome in the audit log.
Overlap Prevention and Code Uniqueness via Shared Calendar
Given an existing scheduled job with an active/planned access window for a specific entry point, When a second job is scheduled that overlaps the same entry point’s access window, Then the system detects the conflict via the shared calendar and either blocks scheduling with a clear conflict message or requires manager approval for parallel access, And if parallel access is approved, distinct unique credentials are issued for each job and windows are preserved without overlap in code reuse, And concurrent scheduling attempts (race conditions) do not result in duplicate credentials as verified under concurrent load testing, And no more than the provider’s maximum number of active credentials for that device are provisioned concurrently.
Automatic Revocation on Expiration or Cancellation
Given a credential with validity window T1–T2, When T2 elapses, Then the credential is revoked/disabled at the provider within 60 seconds, the job’s access status updates to Revoked, and a revocation event is logged with provider confirmation ID, And if revocation fails or provider is unavailable, retries are queued with exponential backoff, the manager is alerted, and the credential is flagged as Revocation Pending until confirmed, And any access attempts after revocation are denied as evidenced by provider/device logs, And when a job is cancelled prior to T2, the credential is revoked immediately and a cancellation SMS is sent to the vendor; no future access is possible.
Extension Request with Approval and Rate Limiting
Given an active credential for a scheduled job, When the vendor requests an extension via the provided SMS link, Then the system enforces rate limiting (max 1 request per 2 hours and max 3 approvals per job) and rejects further requests beyond limits with an informative SMS response, And only users with approval permissions can approve/deny; on approval the end time is extended by the requested amount (bounded by any configured maximum), the provider credential validity is updated, and confirmation SMS is sent to the vendor, And on denial, the original window remains unchanged and a denial SMS is sent, And all requests, approvals/denials, and updated windows are recorded in the audit log with masked credential.
Non-Integrated Property Manual Access Instructions Fallback
Given a property or entry point without a supported provider integration, When a job is scheduled, Then the system generates a manual access package (e.g., lockbox PIN if available, building entry code, elevator/parking instructions, onsite contact) tied to the job’s access window, and sends it via SMS to the vendor, And the vendor must acknowledge receipt via SMS link; if not acknowledged within 30 minutes, the manager is notified for follow-up, And if required instructions are incomplete or inconsistent, the job is marked Access Pending and no instructions are sent until completed (safe default: no access if uncertain), And the issuance of manual instructions and vendor acknowledgment status are logged.
Secure Storage, Masked Display, and Audit Logging
Given access credentials exist for a job, When users view the job in the UI, Then credential values are masked by default (e.g., ••••1234) and may only be revealed or copied by authorized roles after passing MFA; all reveal/copy actions are logged with user, time, and reason, And credentials and provider tokens are encrypted at rest; raw database access without application context does not expose plaintext, And APIs never return full credential values except to the authorized SMS delivery workflow; after expiration + 24 hours the full value is purged, retaining only masked values and metadata, And audit logs capture issuance, updates, extensions, revocations, and failures with timestamps (UTC), actor, reason, masked credential, and provider request IDs, and logs are append-only/immutable.
Property Access Instruction Templates
"As a landlord, I want reusable access instruction templates per property so that every vendor receives consistent, accurate directions without me rewriting them each time."
Description

Provide a reusable template library per property/building for access logistics: entry instructions, alarm disarm instructions (masked), lockbox location photos, gate codes, concierge notes, elevator booking steps, and parking notes. Allow dynamic placeholders (job date/time, vendor name), versioning, approvals, and multi-language content. Merge the relevant template into each work order to produce a single, consistent instruction set that reduces back-and-forth and errors, and include a secure shareable view for vendors.

Acceptance Criteria
Property-scoped Template Library Management
- Given I am an authenticated landlord or manager with edit permissions for Property A, When I create a new "Entry Instructions" template named "Default Entry", Then the template is saved under Property A's library and is not visible in Property B's library. - Given a template name already exists within the same property and type, When I attempt to save another with the same name, Then I see a validation error and the template is not created. - Given allowed template types include Entry Instructions, Alarm Disarm, Lockbox Location, Gate Code, Concierge Notes, Elevator Booking Steps, Parking Notes, When I create a template, Then I can select one of these types and the selection is persisted. - Given file attachment limits are 10 images total and 25 MB combined, When I attach photos to a template, Then uploads are accepted within limits and rejected with an error beyond limits.
Dynamic Placeholder Resolution on Merge
- Given a template contains placeholders such as {{job.date}}, {{job.timeWindow.start}}, {{vendor.name}}, and {{property.address}}, When a work order is created with a scheduled time and assigned vendor, Then the merged instruction set renders those placeholders with correct values using the property's timezone and the account's date/time format setting. - Given a placeholder is unknown or required data is missing, When merging, Then the system surfaces a merge error listing the placeholder name(s) and prevents sending until resolved or a default value is provided. - Given multi-line text fields with placeholders, When merged, Then original line breaks and list formatting are preserved in the output.
Masking and Access Control for Alarm Disarm Instructions
- Given a user lacks the "Secrets: View" permission, When viewing an Alarm Disarm template, Then the code is masked (e.g., ••••) and cannot be copied or exported. - Given a user has the "Secrets: View" permission, When they click Reveal and re-authenticate, Then the full code is shown for up to 60 seconds and then re-masked automatically. - Given a vendor share link exists, When the template includes Alarm Disarm instructions, Then the code is visible only during the defined access window and hidden outside it; requests outside the window return HTTP 403. - Given exports or emails are generated, When the instruction set includes Alarm Disarm, Then the code is redacted unless a user with "Secrets: Share" permission explicitly includes it.
Template Versioning and Approval Workflow
- Given a template is edited, When saved, Then a new version is created with incremented version number, editor identity, timestamp, and change summary. - Given the latest version is Unapproved, When attempting to merge into a work order, Then the system uses the latest Approved version and displays a warning about pending changes. - Given an approver reviews a draft, When they Approve or Reject with a reason, Then the status updates accordingly and the decision is logged in the audit trail. - Given a previous version exists, When a user selects Revert, Then a new draft version identical to that previous version becomes the latest version.
Multi-language Templates and Vendor Language Selection
- Given a template has English (default) and Spanish translations, When a vendor with Spanish preference is assigned, Then the merged instructions render in Spanish; fields lacking Spanish fall back to English and are flagged in a translation coverage report. - Given an RTL language such as Arabic is used, When rendering the vendor view and PDF, Then text direction is RTL and layout aligns appropriately. - Given Unicode characters are present, When generating the vendor view and PDF, Then characters render without substitution glyphs and pass a visual QA check.
Merge into Work Order to Produce Single Instruction Set
- Given a work order is created for Property A with scheduled date/time and assigned vendor, When merging templates, Then the system produces one consolidated instruction set containing enabled sections in the order: Entry Instructions, Gate Code, Concierge Notes, Elevator Booking Steps, Lockbox Location, Alarm Disarm, Parking Notes. - Given images are attached in templates, When merged, Then images are embedded with captions and support tap-to-zoom on mobile vendor view; the output passes a load test of 3 seconds P95 on LTE. - Given templates are updated prior to sending, When an Approved version changes, Then the instruction set refreshes to the new Approved content and displays a "Last updated" timestamp; post-send updates prompt the user to resend and log the action.
Secure Shareable Vendor View and SMS Delivery
- Given a consolidated instruction set exists, When generating a vendor link, Then the system issues a signed token URL expiring at access window end plus 1-hour grace or 72 hours maximum, whichever is sooner. - Given the link is accessed, When the token is used from any device, Then access is logged with timestamp, IP, and user agent; optional one-view mode invalidates the token after first successful view. - Given SMS delivery is initiated, When sending the link, Then message status is tracked (queued, sent, delivered, failed) and failures create an in-app alert with a manual resend option. - Given a manager revokes access, When revocation is triggered, Then the token becomes invalid within 60 seconds and subsequent requests return HTTP 410 Gone.
Elevator & Amenity Booking Orchestrator
"As a property manager, I want FixFlow to reserve service elevator slots aligned to the job window so that vendors can move equipment without delays or building violations."
Description

Automate reserving service elevators and related building amenities aligned to the scheduled job. Offer integrations via email-to-request, building portals, or concierge notifications; where not possible, generate a checklist and templated email for manual submission. Select the best slot with configurable buffers, attach COI/permit documents, track approvals/requirements, and sync reservations back to the FixFlow calendar. Include reservation details in vendor instructions and update/resend if the schedule changes.

Acceptance Criteria
Optimal Service Elevator Slot Selection with Buffers
Given a job with a scheduled date, a time window, and duration D minutes And building operating hours and blackout dates are configured And pre-buffer B1 and post-buffer B2 are configured in minutes And existing reservations for the building are known from both the building system and FixFlow calendars When the orchestrator computes availability Then it selects the earliest slot on the scheduled date that accommodates D + B1 + B2 within building hours without overlap And applies building-specific rules (e.g., move-in blackout, max concurrent bookings) to exclude conflicts And records selected slot start/end times including buffers with timezone And if no slot fits within the scheduled window, it returns status "No Slot" and provides at least 3 alternative slots within the next 14 days
Email-to-Request Booking Automation
Given the building booking method is Email and a recipient address and template are configured And required documents (e.g., COI, permits) are available When the orchestrator composes and sends the booking email Then the subject and body include job ID, unit, date, requested window, buffers, vendor contact, and building address And required documents are attached or linked via secure URLs And the email is sent from a verified sender and a unique message ID is stored And delivery/bounce status is tracked When a reply is received with approval indicators (e.g., explicit "approved", calendar invite, or confirmation number) Then the reservation status is set to Approved and parsed details (start, end, confirmation/booking ID) are stored And if no reply within 24 hours, one automated follow-up is sent and the request is flagged for review And if delivery fails or an NDR is received, the system flags Manual Submission and generates a checklist and templated email for operator use
Building Portal Booking Automation
Given the building booking method is Portal with valid credentials and booking parameters stored And required documents are available When the orchestrator submits a reservation via API or automated form Then a reservation is created for the requested window including buffers And required documents are uploaded with the reservation And the confirmation number and a reservation PDF/screenshot are captured and stored And the booking status is marked Pending or Approved based on the portal response And if submission fails due to authentication, validation, or capacity errors, the system retries once and then falls back to Manual Checklist with a generated templated email
Concierge Notification Booking
Given the building booking method is Concierge with a configured SMS/email contact And required documents are accessible via secure link When the orchestrator sends the booking request Then a message with job details, requested window, buffers, crew/vendor info, COI link, and callback number is delivered And delivery receipt is logged with timestamp When the concierge replies with confirmation or provides a reservation window/ID Then the reservation is stored with status Approved and details recorded And if no response within 12 hours, the system escalates via an alternate channel and flags for manual follow-up And if escalation fails, a Manual Checklist and templated email are generated for operator submission
Document Attachment & Requirements Tracking
Given the building defines required documents (e.g., COI, permits) with validity rules When a booking is initiated Then required documents are validated for presence, file type, and expiry date And missing or expired documents block submission and trigger a request for upload When documents are attached to the booking request Then the submission includes the documents or secure links And the orchestrator tracks acceptance or rejection from building responses And the reservation remains in Pending Docs until all required documents are accepted
Calendar Sync of Reservations
Given a reservation is created or approved When storing the reservation Then an event is created in the FixFlow calendar with title, location, reservation window including buffers, reservation/confirmation ID, and booking channel And the event is marked Busy and linked to the job and vendor records And conflicts with existing FixFlow events are detected and surfaced with a warning And iCal feeds and API consumers reflect the event within 60 seconds of change When a reservation is canceled Then the calendar event is deleted or marked Canceled with reason and timestamp
Schedule Change Propagation & Vendor Instruction Updates
Given an existing reservation and vendor assignment When the reservation is approved Then an SMS is sent to the vendor containing access window, elevator/amenity booking details, building entry/concierge instructions, and any document requirements When the reservation is updated or canceled Then an SMS is sent with the updated window or cancellation notice, and prior instructions are superseded And the FixFlow calendar event is updated accordingly with new times and confirmation/cancellation IDs And if update/cancel fails on the building channel, the vendor is not notified; the system alerts an operator and retries or falls back per channel rules And all messages, confirmations, and changes are logged with timestamps
Parking Pass & Vehicle Access Issuance
"As a vendor, I want a time-limited parking pass and clear parking instructions so that I can park near the job without tickets or wasted time."
Description

Issue time-limited parking access for vendors by integrating with supported garage/gate systems or generating printable/QR passes and clear parking instructions when integrations are unavailable. Collect vehicle/licence details, enforce pass validity by time window, include maps and loading zone guidance, and attach the pass link to the vendor SMS. Track issuance, usage, and expiration to reduce tickets and arrival delays.

Acceptance Criteria
Generate and Send Timeboxed Parking Pass via SMS
Given a scheduled work order with a defined access window and a vendor with a verified mobile number When Access AutoPass is triggered for the work order Then the system generates a unique, tokenized pass URL tied to the work order and access window And the system sends an SMS to the vendor containing the property address, access window (start–end in property local time), and the pass URL And the SMS send is queued within 10 seconds and provider status (queued/sent/delivered/failed) is recorded on the work order And the pass URL has at least 128-bit entropy and expires at window end
Integration-Based Pass Creation with Fallback to QR/Printable
Given the property has a configured and healthy integration with a supported parking/garage system When a parking pass is generated Then the system creates a time-limited credential via the integration API with start/end window, garage/zone, and vehicle plate when available And the pass page displays the integration-provided code/QR/barcode and instructions Given the property lacks a supported integration or an integration call fails When a parking pass is generated Then the system produces a FixFlow QR/printable pass with a unique token and printable PDF And the pass clearly indicates on-site validation instructions and any manual steps And all failures are logged with error codes and surfaced in the work order activity
Vehicle Details Collection and Validation
Given a vendor opens the pass link prior to entry When prompted for vehicle details Then the form requires license plate and issuing state/province, and optionally collects make, model, color, and vehicle type And client-side and server-side validation enforce allowed character sets and length per locale And the vendor cannot proceed to view the pass code until required fields are valid And the submitted plate is stored on the work order and, if integrated, updates the credential via API And the vendor can edit vehicle details until the access window start time
Enforce Pass Validity Window and Revocation
Given a pass with start and end times in the property’s local timezone When the pass QR/code is presented before the start time or after the end time Then validation returns invalid/expired and access is denied And if integrated, the credential is deactivated at or immediately after end time And if fallback, the validation endpoint rejects scans outside the window and the pass page displays “Expired”
Maps, Entry, and Loading Zone Guidance on Pass
Given property access metadata exists (map pin, loading zone, clearance, entry points) When the pass page is rendered Then it displays an interactive map with pinned parking entrance and loading zone And shows text guidance including level/space range, max vehicle height, clearance restrictions, and walking route to unit/entry And provides a downloadable map image/PDF And if metadata is missing, the page displays a standard fallback instruction template and flags the property record for completion
Track Issuance, Usage Events, and Expiration
Given a pass lifecycle from creation to expiration When key events occur Then the system records timestamps for: pass_created, sms_queued, sms_delivered_or_failed, pass_viewed, vehicle_submitted, integration_credential_created_or_failed, access_granted_or_denied, pass_expired, pass_revoked And the dashboard shows current pass status (Not Sent, Sent, Viewed, Active, Used, Expired, Revoked) per work order And all events are exportable via CSV and API And vehicle PII is only visible to authorized roles and is redacted in exports unless elevated permissions are granted
Reschedule and Cancellation Handling
Given a work order access window is rescheduled When the new window is saved Then any existing pass/credential is revoked within 10 seconds and marked Superseded And a new pass is generated with the updated window and sent via SMS with the new link And the old pass link renders a “Superseded” message and contains no active codes Given a work order is canceled When cancellation is saved Then the pass/credential is revoked immediately, old links render “Canceled,” and no codes remain valid And the vendor is notified via SMS of the cancellation
Vendor SMS Delivery with Tracking & Failover
"As a coordinator, I want proof that the vendor received and acknowledged the access details so that I can prevent lockouts and reduce check-in calls."
Description

Assemble a single SMS that includes the access code (masked as appropriate), address, time window, instructions, elevator/parking links, and a secure short link to a mobile-friendly access page. Track SMS delivery status, link opens, and vendor acknowledgments; retry on failure and fail over to voice call or email when needed. Support multi-recipient delivery (technician and dispatcher), quiet hours, localized content, and automatic logging of all communications to the FixFlow work order timeline.

Acceptance Criteria
Compose and Send Vendor Access SMS with Masking
Given a scheduled work order with address, access window, instructions, access code, elevator/parking URLs, and a generated secure access page URL And at least one recipient (technician and/or dispatcher) with a valid mobile number When the system assembles the vendor notification Then it composes a single SMS that includes: property address, local date/time window, concise instructions (max 160 chars), masked access code (mask all but last 2 digits), elevator/parking links, and a secure short link to the mobile access page And the full unmasked access code is not present in the SMS body and is only available on the secure access page And the SMS is generated in the recipient’s locale and time zone format And if the composed message exceeds one segment, non-essential text is trimmed and details are moved to the secure link so the SMS is ≤2 segments And messages are queued for send within 5 seconds of trigger and include a unique message ID per recipient And if required fields are missing (address or time window or secure link), the system blocks send and records a validation error
Track SMS Delivery Status via Provider Webhooks
Given an SMS is sent via the messaging provider with a provider message ID When the provider posts delivery events (queued, sent, delivered, failed, undeliverable, blocked, unknown) Then the system updates the per-recipient delivery status within 5 seconds of receipt And stores the latest status, provider code, error description, and event timestamp And ignores stale/out-of-order events by applying the most recent timestamped status only And exposes current delivery status in the work order UI and API And a transition to failed/undeliverable flags the attempt for retry according to the configured policy
Secure Link Generation and Open Tracking
Given a secure, recipient-specific access page URL is generated for the notification When the SMS is sent Then the URL is short-linked, uses HTTPS, contains an unguessable token, and expires at access window end plus a configurable buffer And opening the link records a per-recipient open event with timestamp, user agent, IP, and device type (where available) And multiple opens are counted with a distinct first-open timestamp and total open count And expired or invalid tokens return 410/expired page with no sensitive details and instructions to contact the manager And link tokens are single-tenant and cannot be used to access other work orders
Vendor Acknowledgment via SMS or Web
Given a vendor receives the notification When the vendor replies via SMS with an affirmative keyword (YES/Y or locale equivalents) or taps the Acknowledge button on the access page Then the system marks the notification as Acknowledged with timestamp and source (SMS/Web) And a negative keyword (NO/N or locale equivalents) marks Declined and alerts the work order owner And STOP/UNSUBSCRIBE opts the number out of future SMS, logs the event, and prevents further SMS sends to that number And if no acknowledgment is received by the configured deadline (e.g., ack_timeout_minutes or minutes_before_window_start), the system triggers escalation per policy
Retry and Failover to Voice/Email with Quiet Hours
Given an SMS send fails, delivery status becomes failed/undeliverable, or acknowledgment is not received within the configured window When retry conditions are met Then the system retries SMS up to the configured max attempts with exponential backoff (e.g., 1m, 5m, 15m) And on exhausting retries or immediate hard failures (e.g., invalid number), the system fails over to voice call with TTS containing address, time window, masked code, and a keypad prompt to acknowledge And if current time falls within configured quiet hours for the recipient’s time zone, voice calls are deferred until quiet hours end; email failover may proceed immediately And a successful acknowledgment on any channel halts further retries and marks the notification as Acknowledged And all failover actions and outcomes are recorded with timestamps and channel
Multi-Recipient Delivery and Localization
Given both a technician and a dispatcher are designated recipients When the notification is sent Then each recipient receives a personalized, localized SMS (language and time format) using their stored preferences, falling back to the property locale if unknown And delivery and acknowledgment are tracked per recipient And duplicate phone numbers across recipients are de-duplicated to a single send and shared status And an acknowledgment by any recipient satisfies the work order unless configuration require_all_ack = true, in which case all recipients must acknowledge And opt-outs are enforced per phone number regardless of recipient role
Automatic Communication Logging to Work Order Timeline
Given any communication event occurs for the notification When an event happens (send attempt, delivery update, link open, acknowledgment, retry, failover start/stop) Then the system appends an immutable timeline entry with: UTC and local timestamp, channel (SMS/Voice/Email/Web), recipient, message preview with masked code, provider message ID, status, error codes (if any), link token ID (if applicable), and acknowledgment details And timeline entries are searchable/filterable by channel, recipient, and status in the UI and via API And exporting the work order timeline includes these entries with masking preserved
Access Window Enforcement & Arrival Check-In
"As a property manager, I want access to activate only during the scheduled window with an arrival check-in so that I maintain security and know when the vendor is onsite."
Description

Activate access only during the approved window and optionally based on vendor proximity via geofencing where device and lock APIs allow. Provide an arrival check-in link that captures timestamp and (consented) location, flips job status to Onsite, and starts an onsite timer; escalate with reminders if the vendor hasn’t checked in by window start. Support end-of-visit checkout to disable access, capture notes/photos, and feed SLA metrics while improving security and visibility.

Acceptance Criteria
Timeboxed Access Activation & Denial Outside Window
Given a job with an approved access window and active credentials When the vendor attempts access before the window start Then access is denied and the attempt is logged with timestamp and reason "outside window" Given the same job When the vendor attempts access within the window Then access is granted and the unlock event is logged with timestamp Given the same job When the vendor attempts access after the window end Then access is denied, credentials are invalidated, and the attempt is logged Given a job with a modified window prior to start When the window is updated Then credential validity instantly reflects the new start and end times Given a property with a defined local timezone When evaluating access Then all time checks use the property's timezone and correctly handle daylight savings transitions
Geofenced Access Enforcement (Optional)
Given geofencing is enabled for the job and the vendor has granted location permission When the vendor device is within the configured geofence radius during the access window Then access is granted Given the same configuration When the vendor device is outside the geofence during the access window Then access is denied and the vendor receives an SMS prompting them to approach the property Given geofencing is enabled When location cannot be determined or the lock API lacks geofence support Then the system falls back to timeboxed enforcement only and records the fallback in the audit log Given organization defaults define a geofence radius When a job overrides the radius Then the job-specific radius is used
Arrival Check-In Link and Onsite State Transition
Given a scheduled job with an SMS check-in link sent to the vendor When the vendor taps the link and confirms consent to share location Then the system records check-in timestamp and coarse location, sets job status to Onsite, and starts the onsite timer Given a job already in Onsite status When the vendor taps the check-in link again Then the existing check-in is not duplicated and the last-seen time is updated Given a job with no location consent When the vendor taps the link Then the system records the check-in timestamp without location and still sets status to Onsite and starts the timer Given a job with an access window When the vendor attempts to check in before the window start Then the system prevents check-in and displays the time remaining to the vendor
Reminder Escalation When No Check-In by Window Start
Given a job with a defined access window start and an assigned vendor When no check-in is recorded at the window start time Then an SMS reminder is sent to the vendor immediately Given the same job When no check-in is recorded 10 minutes after the first reminder Then a second SMS reminder is sent to the vendor Given the same job When no check-in is recorded 20 minutes after the first reminder Then the property manager receives an in-app alert and an email notification Given reminder limits configured at the organization level When the limit is reached Then no further reminders are sent Given a reminder sequence is in progress When the vendor checks in Then all pending reminders are canceled
End-of-Visit Checkout and Access Revocation
Given a job in Onsite status When the vendor taps the checkout link Then the system records the checkout timestamp, prompts for notes and up to 10 photos, and validates that at least one of notes or a photo is provided Given the same job When checkout is recorded Then all timeboxed credentials (smart lock codes, lockbox PINs, elevator bookings, parking passes) are revoked or marked expired within 60 seconds Given checkout is completed When the system updates job state Then job status is set to Completed and the onsite timer stops Given a revocation call fails during checkout When retries are attempted Then the system retries for up to 5 minutes with exponential backoff and alerts the property manager if final revocation fails
SLA Metrics Population from Check-In/Check-Out
Given a job with check-in and checkout timestamps When metrics are calculated Then Onsite Duration equals checkout time minus check-in time with minute-level precision Given a job with a defined access window start When metrics are calculated Then On-Time Arrival is true if check-in time is less than or equal to window start plus a 5-minute grace period, otherwise false Given a job with reminder events When metrics are calculated Then Missed Check-In Escalations equals the number of reminders sent before check-in Given a job with denied access attempts When metrics are calculated Then Lockout Attempts equals the count of denied access events outside the window Given metrics are stored When the job is viewed in the dashboard or exported as CSV Then the calculated SLA metrics are visible and included in the export
Consent, Privacy, and Audit Logging
Given a vendor opens the check-in link When consent for location collection is requested Then the vendor can accept or decline and the choice is stored with timestamp and device identifier Given any access decision or state change (grant, deny, check-in, checkout, revocation, reminder sent) When the event occurs Then an audit log entry is recorded including actor, timestamp, outcome, and rationale Given a vendor declines location access When geofencing is enabled Then geofencing is not enforced and a "location not available" flag is shown to the property manager Given an organization-configured audit log retention period When events exceed the retention period Then logs are purged and the purge event is recorded
Access Change Management, Auto-Resend, and Audit Trail
"As a property manager, I want any schedule change to automatically update access and notify the vendor so that no one shows up with the wrong code or time."
Description

Automatically detect schedule or logistics changes, regenerate credentials, update related bookings (elevator, parking), and resend updated instructions to all recipients with clear change highlights. Provide one-click emergency revoke and reissue. Maintain an immutable audit log of all changes (who, what, when) and message deliveries for compliance and dispute resolution, preventing stale codes and miscommunication.

Acceptance Criteria
Schedule Reschedule Triggers Credential Regeneration and Resend
Given an existing work order with active access credentials and at least one vendor recipient And a confirmed appointment window is modified (date, start time, or end time) When the change is saved Then the system regenerates all timeboxed credentials aligned to the new window within 15 seconds And invalidates all prior credentials within 30 seconds And sends a single consolidated SMS to all recipients within 60 seconds containing the updated instructions, new validity window, and a clear "Updated" change highlight with version number and timestamp And the vendor-facing link displays only the latest version and hides superseded codes And an audit entry is written capturing who made the change, what fields changed, before/after values, and delivery attempts
Logistics Change Updates Bookings and Notifies Recipients
Given an order with associated elevator and parking bookings And a logistics parameter (building access instructions, lock location, unit number, elevator time, parking slot) is changed When the change is saved Then the system cancels or amends prior elevator/parking bookings and creates updated bookings as needed within 60 seconds And detects conflicts (unavailable time/slot) and blocks message sending until resolved, surfacing a clear error to the user And once conflict-free, regenerates credentials and resends SMS with a change summary highlighting altered logistics only And the audit trail records the booking changes including provider confirmation IDs and timestamps
One-Click Emergency Revoke
Given an order with one or more active credentials currently valid When an authorized user clicks "Emergency Revoke" Then all active credentials are revoked within 15 seconds and cannot be used thereafter And all recipients receive an "Access Revoked" SMS within 60 seconds And the system prompts the user to optionally reissue new credentials with a new window; if confirmed, new credentials are generated and sent And all revoke and reissue actions are logged immutably with user, time (UTC), reason (if provided), and affected credential IDs
Delivery, Retry, and Fallback for SMS Instructions
Given updated instructions must be sent to N recipients via SMS When messages are dispatched Then delivery receipts are captured for at least 95% of attempts and stored in the audit trail And failed deliveries are retried up to 3 times with exponential backoff (e.g., 1m, 5m, 15m) And numbers with permanent failures (e.g., unreachable/blocked) are flagged and surfaced to the user with a clear status And recipients never receive duplicate content for the same version (deduplicated within a 5-minute window) And links in SMS resolve to the latest instruction version
Immutable Audit Trail with Export
Given any change or message dispatch related to access is performed When the event is committed Then an immutable audit entry is added with tamper-evidence (e.g., cryptographic hash chain), actor identity, action type, affected fields, before/after values, related message bodies, delivery receipts, and UTC timestamps And audit entries cannot be edited or deleted via the UI or API And authorized users can filter by work order, date range, actor, or recipient and export the results to CSV and PDF within 30 seconds for up to 10,000 entries And exports include a checksum and generation timestamp
Timeboxed Windows and Stale Code Prevention
Given regenerated credentials with a new validity window When a recipient attempts to use any superseded credential Then access is denied and the attempt is logged with timestamp and recipient reference And the recipient-facing page indicates the credential is expired and shows the latest valid instructions And all regenerated credentials are valid only within the new window to the minute, expiring automatically at end time
Role-Based Control and Data Privacy
Given role-based access control is enabled When a user without Owner or Manager role attempts to revoke or reissue credentials Then the action is blocked with a permission error and no changes are made And audit logs are viewable only by Admin/Owner roles while vendors see only their own instruction history And SMS and exports redact sensitive fields (e.g., full lockbox codes shown only to authenticated vendors via link; masked in audit and exports) per configuration

StageKit

Stages supplies and materials ahead of each step by pulling unit‑specific checklists and preferred vendors. Auto‑orders consumables, schedules deliveries to arrive before work starts, and QR‑tags staging bins so techs confirm readiness. Eliminates last‑minute store runs and start delays.

Requirements

Unit-Specific Checklist Parser
"As a property manager, I want StageKit to derive a materials list from unit-specific checklists and job steps so that every task has the right supplies staged before work begins."
Description

Pulls unit- and job-type–specific checklists from FixFlow work orders and maps each step to a normalized bill of materials (SKUs, quantities, lead times, and packaging). Supports property-level templates, brand preferences, and unit attributes (e.g., fixture model, gas vs. electric) to generate accurate stage lists with needed-by dates tied to the scheduled start. Recalculates automatically when scope or start dates change, preserves version history, and allows manual overrides with audit trails. Outputs a staged packing list per step for ordering and bin labeling.

Acceptance Criteria
Checklist Parsing to Normalized BOM Mapping
- Given a FixFlow work order with unit ID and job type, when the parser runs, then it retrieves the correct unit- and job-type–specific checklist for that property. - Given a checklist step, when mapped, then BOM line items include SKU, description, quantity, unit of measure, lead time (days), and packaging type. - Given duplicate or overlapping items across steps, when normalized, then shared consumables are consolidated with step references retained. - Given an item without a resolvable SKU, when encountered, then the parser flags it as SKU_MISSING and adds a remediation note for procurement review. - Given parsing completes, then each BOM line is linked to its originating step ID for traceability.
Template, Brand Preference, and Unit Attribute Resolution
- Given property-level templates and brand preferences, when parsing, then brand-specific SKUs are selected over generic equivalents. - Given unit attributes (e.g., fixture model, gas vs. electric), when available, then only compatible SKUs are selected and incompatible variants are excluded. - Given conflicting rules, when resolving, then the priority order is unit attributes > brand preferences > property template defaults. - Given no matching preference, when defaulting, then the selected SKU is marked DEFAULT_APPLIED and includes the default source. - Given multi-vendor options, when a preferred vendor exists, then vendor-specific SKU and packaging are selected.
Needed-By Date Calculation Tied to Scheduled Start
- Given a scheduled start datetime and item lead times, when calculating, then each BOM line has a needed-by date equal to start date minus lead time business days. - Given weekends and configured holidays, when calculating, then needed-by dates skip non-business days using the property calendar. - Given a staging buffer is configured, when present, then needed-by subtracts buffer days in addition to lead time. - Given no scheduled start, when calculating, then needed-by is set to TBD and the item is excluded from auto-order workflows. - Given lead time is zero, when calculating, then needed-by equals the scheduled start date.
Automatic Recalculation on Scope or Start Date Changes with Versioning
- Given scope (steps) or start date changes, when the work order is saved, then BOM, needed-by dates, and packing lists are recalculated within 5 seconds for work orders up to 100 steps. - Given a recalculation, when completed, then a new immutable version is created with timestamp, triggering user ID, and a diff of added/removed/changed items. - Given version history exists, when requested, then prior versions can be viewed and restored without data loss. - Given a restore operation, when performed, then a new version is created capturing the reversion action and rationale. - Given recalculation errors, when they occur, then the system surfaces a failure state with actionable messages without overwriting the last good version.
Manual Overrides and Audit Trail
- Given a user with edit permissions, when manually overriding SKU, quantity, lead time, or packaging, then the override is applied to the active version and flagged as OVERRIDDEN with the user’s note required. - Given an override, when saved, then the audit trail records user, field, old value, new value, timestamp, and reason. - Given a reversion to system values, when requested, then original computed values are reinstated and the audit trail logs the reversion. - Given an unauthorized user attempts an override, when attempted, then the action is blocked and logged with a permission error. - Given multiple overrides on the same field, when applied, then the latest value is active and prior overrides remain in history.
Staged Packing List and QR Tag Output per Step
- Given computed BOM items per step, when generating outputs, then a per-step packing list includes SKU, description, quantity, packaging, and needed-by date. - Given packing lists are generated, when labeling, then QR codes per step encode work order ID, step ID, and a checksum and are printable on A4 and Letter formats. - Given an individual step exceeds 100 items, when rendering, then pagination and bin sequence numbers are added without truncation. - Given vendor ordering export is requested, when generated, then CSV/EDI files conform to the vendor schema with required fields and pass schema validation. - Given items span multiple vendors, when exporting, then separate vendor-specific files are produced with correct item allocations.
Preferred Vendor Catalog Sync
"As an operations lead, I want StageKit to keep preferred vendor pricing, lead times, and stock in sync so that auto-orders choose the best available source without manual price checks."
Description

Integrates with preferred vendors to keep pricing, stock status, lead times, and order constraints (MOQs, shipping methods) current. Supports API connectors (e.g., Amazon Business), CSV uploads for local suppliers, and manual item entries. Normalizes vendor SKUs to internal catalog items and maintains multi-vendor mappings with fallback rules. Captures taxes, fees, and return policies to inform total landed cost. Runs scheduled syncs with error handling and alerts when a required item has no available vendor source.

Acceptance Criteria
API Sync: Pricing, Stock, Lead Times, and Constraints from Connected Vendor
Given a connected vendor via API with valid credentials When the scheduled sync executes Then for each vendor SKU mapped to an internal catalog item, the system updates price, stock_status, lead_time_days, moq, and shipping_methods from the vendor payload And all fields are normalized to internal units and enumerations (e.g., stock_status ∈ {in_stock, low_stock, out_of_stock}) And each change is recorded with vendor_sync_id, vendor_timestamp, and a field-level change diff And rows missing required fields are skipped with a row-level error code and are listed in the sync report
CSV Upload Mapping and Validation for Local Supplier
Given a supplier CSV prepared using the latest template When a user uploads the file Then the system validates required columns: vendor_sku, price, stock_status and optional columns: lead_time_days, moq, shipping_methods, taxes, fees, return_policy_url And rows with missing/invalid required values are rejected with row number and error code, while valid rows proceed And a preview displays counts of to-create, to-update, and to-skip before confirmation And upon confirmation, the import upserts by (vendor_id, vendor_sku), creating new offerings or updating existing ones And an import summary is stored and viewable with counts of created, updated, skipped and their reasons
Manual Vendor Offering Entry and Normalization
Given a user with Catalog:Edit permission When the user creates a manual vendor offering for an existing internal catalog item Then the form requires vendor_id, vendor_sku, price, stock_status and validates numeric ranges and allowed enums And duplicate mappings (same vendor_id + vendor_sku to same item) are prevented And optional fields lead_time_days, moq, shipping_methods, taxes, fees, return_policy are captured if provided And on save, the mapping appears under the item’s vendor list and is included in subsequent sourcing and sync reports
Multi‑Vendor Mapping and Deterministic Fallback for Sourcing
Given an internal item mapped to two or more vendors with price, stock_status, lead_time_days, moq, shipping, taxes/fees available When StageKit needs to source the item for quantity Q with need_by_date D Then the system selects a vendor that meets MOQ (moq ≤ Q) and can deliver by D (today + lead_time_days ≤ D) And among eligible vendors, selection minimizes total_landed_cost; ties break by shortest lead_time_days, then by preferred flag, then by vendor rating And if the preferred vendor is ineligible (out_of_stock, MOQ not met, or lead_time_days violation), the next eligible vendor is chosen per the deterministic rule And the selection record stores reason_code, compared_vendors, and a landed cost breakdown for audit And if no vendor qualifies, an alert "No Available Vendor Source" is created and assigned to the appropriate portfolio
Scheduled Sync Cadence, Idempotency, and Delta Updates
Given vendor connectors configured with sync_interval_minutes and credentials When the scheduler runs Then each vendor is synced at or before its configured interval with randomized jitter to distribute load And syncs are idempotent: identical payloads produce no new change events or version bumps And only changed fields are updated; unchanged fields retain last_updated_by and last_changed_at And failures trigger exponential backoff with up to 3 retries; after 3 failures the vendor is marked Degraded and a notification is sent to admins And for catalogs up to 10,000 items, the 95th percentile sync duration is ≤ 15 minutes
Alerting When Required Item Has No Available Vendor Source
Given a StageKit job that requires an item with quantity Q and need_by_date D When, after the latest sync, no mapped vendor offering meets stock, MOQ, and lead time constraints Then an alert is created within 60 seconds including item_id, job_id, failed_constraints, last_sync_time, and suggested next actions And duplicate alerts for the same item and job are suppressed for 24 hours unless the state changes And the alert auto-resolves and notifies watchers when an eligible vendor becomes available in a subsequent sync
Landed Cost Calculation with Taxes, Fees, Shipping, and Return Policy Capture
Given a vendor offering with base price, estimated tax, shipping method/cost, and vendor fees When computing total landed cost for quantity Q to a destination with tax settings Then total_landed_cost = round(price*Q + taxes + shipping + fees, 2) and a breakdown is stored (price, taxes, shipping, fees, currency) And tax-exempt destinations apply 0 tax and store exemption_id And when vendor currency differs, conversions use the configured rate at computation time with source and timestamp recorded And return_policy window and terms are stored and displayed for sourcing decisions
Auto-Order & Budget Controls
"As a landlord, I want consumables and materials auto-ordered within budget constraints so that jobs start on time without overspending."
Description

Automatically generates purchase orders for upcoming steps when items are required and not on hand, selecting the optimal vendor based on availability, lead time, and total landed cost while meeting needed-by dates. Bundles items by vendor to reduce shipping, applies owner/property budget caps, and routes over-threshold orders for approval. Supports payment methods, tax-exempt profiles, and ship-to addresses per property. Tracks order status via webhooks/email parsing, ingests receipts, and reconciles costs back to the work order.

Acceptance Criteria
Auto-PO Creation for Upcoming Step with Insufficient On-Hand Inventory
Given a scheduled work order step with a needed-by date and required checklist items And on-hand inventory for at least one required item is less than the required quantity When the step is saved or updated Then the system generates POs for all missing quantities within 5 minutes And no PO lines are created for items where on-hand is greater than or equal to required And each PO references the originating work order and step And each PO line’s needed-by date is set to meet or precede the step start date
Vendor Selection Optimization to Meet Needed-By at Lowest Landed Cost
Given multiple approved vendors for each required item with known availability, lead time, pricing, shipping, and taxes When generating POs for missing items Then the system selects, per item, a vendor that can meet the needed-by and has the lowest total landed cost (unit price × qty + shipping + taxes − discounts) And if multiple vendors tie, the preferred vendor for the property is selected And if no vendor can meet the needed-by, the vendor with the earliest arrival is selected and the PO is flagged "Needed-by at risk" And the PO records a cost/lead-time breakdown per line for auditability
Vendor Bundling Across Steps Without Violating Needed-By
Given multiple items for the same property and ship-to with overlapping needed-by dates within a 48-hour window When generating POs Then items for the same vendor are bundled into a single PO per vendor per ship-to And shipping is estimated once per bundled PO per vendor And items with conflicting needed-by dates are split so earlier dates are not delayed And no bundling occurs across different properties or different ship-to addresses
Budget Caps Enforcement and Approval Routing
Given owner/property budget caps and approval thresholds are configured And a new PO would exceed a cap or its total exceeds the approval threshold When the PO is generated Then the PO status is set to Pending Approval and is not submitted to the vendor And designated approvers are notified with PO totals, line details, and variance against caps And upon approval the PO is submitted; upon rejection it is canceled and the requester is notified And an immutable audit log records approver, decision, timestamp, and comments And if the PO total is under all caps and thresholds, it auto-approves and submits
Payment, Tax-Exempt, and Ship-To Selection on POs
Given the property has a default payment method, optional tax-exempt profile, and a ship-to/staging address When a PO is generated Then the PO uses the property’s default payment method unless a vendor-specific override exists And applies the property’s tax-exempt ID to eligible vendors/items and sets tax accordingly And sets the ship-to to the property’s configured address or the work order’s staging address if specified And the PO header includes payment terms, tax status, and ship-to fields populated And if any required profile is missing, the PO is flagged Incomplete with a validation error
Order Status Sync via Webhooks and Email Parsing
Given a vendor sends a webhook or order update email containing the PO number/reference When the webhook is received or the email is parsed successfully Then the PO status is updated to Created/Submitted/Confirmed/Shipped/Delivered/Backordered/Cancelled as applicable within 5 minutes And tracking numbers, carrier, and expected delivery dates are captured when present And each status change is appended to the PO timeline with source (webhook/email) and timestamp And duplicate or out-of-order updates do not create duplicate events and are handled idempotently
Receipt Ingestion and Cost Reconciliation to Work Order
Given a receipt/invoice is uploaded or received by email with a PO reference and itemized charges When the document is ingested and parsed Then it is matched to the PO and originating work order And actual costs per line, taxes, and shipping are recorded and reconciled to the PO And variances greater than 2% or $5 (whichever is higher) are flagged for review And lines are marked received by quantity; the PO auto-closes when fully received And the work order’s cost summary and budgets are updated within 5 minutes And the receipt document and OCR extract are stored and linked for audit
Delivery Window Scheduler
"As a coordinator, I want deliveries scheduled to arrive 24–48 hours before the job within property access windows so that techs never wait on materials."
Description

Schedules deliveries to arrive 24–48 hours before each job step, considering vendor lead times, transit estimates, property access windows, holidays/blackout dates, and the shared FixFlow calendar to avoid conflicts. Creates calendar holds for expected deliveries, notifies stakeholders (SMS/email), and automatically reflows schedules and shipping methods if job dates move or delays occur. Captures proof-of-delivery details and flags exceptions for quick reschedule.

Acceptance Criteria
Schedule 24–48h Pre-Job Delivery Within Constraints
Given a job step with a fixed start datetime, vendor lead time, transit ETA, property access windows, holiday/blackout calendars, and the shared FixFlow calendar When the Delivery Window Scheduler runs Then it selects a delivery window that begins 24 to 48 hours before the job step start, in the property’s local time zone And the window falls entirely within allowed property access hours and not on holidays/blackout dates And the selected window does not conflict with existing holds on the shared FixFlow calendar for the property And the selection respects vendor lead time and transit ETA (scheduled ship date/time >= now + lead time and arrival within ETA) And the chosen delivery window is persisted to the job step with start/end timestamps and time zone
Create and Maintain Calendar Holds for Deliveries
Given a delivery window is scheduled for a job step When the schedule is saved Then a calendar hold is created on the shared FixFlow calendar with the delivery window start/end, property address, job ID, vendor/carrier, and access notes And the hold is visible to all stakeholders with calendar access within 1 minute And if the delivery window changes or is canceled, the hold is updated or removed within 1 minute And the hold does not overlap with another hold at the same dock/elevator resource; if a conflict would occur, the scheduler selects a non-conflicting window
Stakeholder Notifications for Delivery Scheduling and Changes
Given stakeholders are configured for the job (assigned vendor, field techs, and property manager/owner) When a delivery window is created, updated, or canceled Then SMS and email notifications are sent to each stakeholder containing property, delivery window (local time), job step, tracking link (if available), and access instructions And message delivery statuses and any replies (e.g., CONFIRM, HELP, STOP) are logged on the job timeline And if an SMS fails or email bounces, an alternate channel retry occurs and the failure is surfaced as an alert on the job
Auto-Reflow on Job Date Change or Carrier Delay
Given an existing scheduled delivery for a job step When the job step start time moves or the carrier updates the ETA such that the delivery would be earlier than 48 hours or later than 24 hours before the step Then the scheduler recalculates a compliant delivery window within 60 seconds to restore the 24–48 hour buffer And updates shipping method (e.g., upgrade to expedited) if needed and within preconfigured budget constraints And updates the calendar hold and re-notifies stakeholders of the new delivery window And writes an audit log entry detailing the change, reason, and before/after times
Multi-Step Job Orchestration Across Dependencies
Given a job with multiple steps and dependencies (Step N+1 cannot start before Step N completes) When scheduling deliveries for all steps Then delivery windows for Step N+1 are not scheduled to arrive before Step N’s delivery PoD is captured or its step is marked ready And overlapping deliveries for steps at the same property are de-conflicted on the shared calendar And materials shared across steps are consolidated into the earliest compliant delivery window when enabled by configuration
Proof-of-Delivery Capture and Readiness Confirmation
Given a delivery is expected within a scheduled window When the carrier marks delivered or a tech scans the QR-tag on the staging bin Then the system captures proof-of-delivery with timestamp, photos or signature, QR scan metadata, carrier tracking number, and delivery notes And associates the PoD to the correct job step and property And marks the step’s staging status as Ready And if PoD is not captured within 2 hours after the window end, an exception is created and stakeholders are notified
Exception Handling and One-Click Reschedule
Given a delivery attempt fails, is delayed beyond the window, or access is denied When the system receives a failure signal (carrier webhook, user flag, or missing PoD SLA breach) Then an exception record is created with cause, affected items, and recommended next valid delivery windows And a one-click Reschedule action applies the chosen window, updates shipping method if approved, moves the calendar hold, and notifies stakeholders And the time from exception creation to rescheduled window confirmation is tracked and reported And if no valid windows exist within the next 3 business days, the job step is flagged At Risk on the dashboard
QR-Tagged Staging Bins
"As a field technician, I want to scan a QR-tagged bin to confirm contents and readiness so that I can start work immediately and report discrepancies."
Description

Generates unique QR labels for each staging bin that include unit, step, bin contents, and required-by date. Provides mobile-friendly packing and confirmation flows: pick, pack, load, and on-site verify. Supports offline scanning, photo attachments for discrepancies, and chain-of-custody logs (who packed, who verified, when). Enforces role-based access so only authorized techs can complete readiness checks and automatically updates the work order’s readiness status.

Acceptance Criteria
Generate Unique QR Labels with Required Metadata
Given a new staging bin is created for a unit and step, When a QR label is generated, Then the QR code is globally unique across all properties and bins. Given a bin with unit ID, step name, bin contents summary, and required-by date, When the QR payload is decoded, Then it contains those fields and exactly matches the bin record. Given the printed QR label, When scanned using current iOS Safari and Android Chrome at 30–60 cm under typical indoor lighting, Then the scan succeeds ≥99% over 100 attempts. Given a request to reprint a label, When reprinted, Then the QR code value remains unchanged and includes human-readable unit, step, and required-by date on the label.
Mobile Pick-Pack-Load-On-Site Verify Flow
Given a unit-specific checklist, When a tech starts Pack, Then the app displays the pick list with required quantities and allows item scan to mark each as picked. Given Pack is incomplete, When the tech attempts to start Load, Then the system blocks progression and shows outstanding items. Given Pack is complete, When Load is started, Then the bin QR must be scanned to confirm bin identity and a timestamp and user ID are recorded. Given Load is complete, When On-site Verify is initiated at the destination, Then the app requires a bin scan and confirms all line items are present before allowing completion. Given a line item is short or substituted, When the step is saved, Then a discrepancy record is created and the flow cannot complete without acknowledging the discrepancy.
Offline QR Scanning and Deferred Sync
Given the device is offline, When a bin QR is scanned, Then the app records pick/pack/load/verify actions locally with timestamps and an offline indicator. Given offline-recorded actions exist, When connectivity is restored, Then all pending actions sync to the server within 60 seconds and update server state. Given the same step was completed by another user while offline, When syncing, Then the app merges non-conflicting actions and flags conflicts for user resolution without data loss. Given the user’s authorization cannot be verified offline, When On-site Verify is attempted, Then the action is captured as pending and only applied after successful authorization on sync.
Photo Attachments for Discrepancies
Given a discrepancy is created for a bin or line item, When saving the discrepancy, Then at least one photo is required and a note field is available. Given a photo is attached, When stored, Then its resolution is at least 800x600, it is time-stamped, and it is associated with the bin, item (if applicable), step, and user. Given the device is offline, When photos are captured for a discrepancy, Then they are queued and uploaded automatically on reconnection, linking to the correct discrepancy. Given a bin activity log is viewed, When discrepancies exist, Then photos are visible as thumbnails with the ability to open full-size and download.
Chain-of-Custody Audit Log
Given any pick, pack, load, or on-site verify action, When the action is saved, Then the system logs user ID, role, timestamp, action type, and bin ID immutably. Given an audit log entry is edited by an admin, When changes are saved, Then the original entry is preserved and a new version is created with editor, timestamp, and reason. Given a request to export the chain-of-custody, When export is initiated, Then a CSV is generated including all events, user and role, timestamps, and links to any associated photos. Given a bin’s activity is displayed, When multiple users handled the bin, Then the chronological chain shows who packed, who loaded, and who verified with times.
Role-Based Access for Readiness Verification
Given role permissions are configured, When a user without the 'Complete Readiness' permission attempts On-site Verify completion, Then the system blocks the action with a clear error and logs the attempt. Given a user with the 'Complete Readiness' permission, When they complete On-site Verify, Then the action succeeds and is recorded with the user’s role and ID. Given property-level overrides are set, When access is evaluated, Then step-specific permissions are enforced according to the property configuration.
Automatic Work Order Readiness Status Update
Given all bins required for a work order step are On-site Verified with no open discrepancies, When the last verification is saved, Then the work order step status updates to 'Ready' within 15 seconds. Given at least one bin is verified but others remain pending or have open discrepancies, When status is evaluated, Then the work order step status is set to 'Partially Ready'. Given a discrepancy is opened after a step was marked 'Ready', When status is re-evaluated, Then the work order step status automatically reverts to 'Not Ready' until resolved. Given the work order status changes, When the update occurs, Then the change is visible in the work order detail and via the API events feed.
Exceptions & Substitutions Engine
"As a maintenance manager, I want automatic substitutes proposed when items are backordered so that jobs aren’t delayed and specs are still met."
Description

Detects backorders, stock-outs, or late shipments and proposes compatible substitutions using equivalency rules (specs, dimensions, brand preferences) and cost/lead-time tradeoffs. Supports partial fulfillment, split shipments, and approval workflows with impact highlights (cost change, ETA change). Notifies stakeholders and updates staging lists, purchase orders, and delivery holds to keep schedules intact.

Acceptance Criteria
Backorder Detection and Auto-Hold Trigger
Given a purchase order line item is linked to a scheduled task with a configured pre-stage buffer (default 24 hours) And the vendor feed or tracking webhook updates the status to Backordered or the ETA to a date later than task start minus buffer When the update is ingested by the system Then within 60 seconds the item is marked "Exception: Backorder/Delay", the staging bin state is set to "Not Ready", and a delivery hold is placed on the task And the exception shows previous ETA, new ETA, reason, and risk level And the shared calendar displays a hold badge on the affected task And no vendor appointment is confirmed or double-booked while the hold is active
Substitution Proposal via Equivalency Rules
Given an item on the staging list is out of stock or late beyond the buffer And the item has defined equivalency rules (mandatory specs, dimensions tolerance ≤2%, required certifications, preferred brands, compatible SKUs) When the engine runs substitution search across preferred vendors and approved catalogs Then at least one and up to five compatible substitutions are proposed And each proposal includes rule match details, cost delta, ETA delta, supplier, and real-time stock status And proposals are ranked by a configurable impact score combining cost and lead-time (default weights 50% cost, 50% lead-time) And proposals that violate mandatory rules are excluded with a visible reason
Approval Workflow and Impact Highlights
Given substitution proposals exist for an exception When an approver with the "Procurement Approver" role opens the exception Then cost change and ETA change are highlighted with thresholds (green ≤0, amber ≤10% or ≤24h, red >10% or >24h) And approval requires explicit selection and confirmation And if cost increase exceeds 10%, a justification comment is mandatory And upon approval, the related PO line is updated or replaced, the staging list is updated, and deliveries are rescheduled within 2 minutes And an immutable audit log records approver, timestamp, option chosen, cost/ETA deltas, and comment And upon rejection, the next ranked option is surfaced; if none remain, the item escalates to manual procurement
Partial Fulfillment and Split Shipments
Given an order contains multiple items and at least one item can ship now while others are delayed When partial fulfillment is enabled for the order Then the system creates split shipments with unique shipment IDs linked to the original PO And delivery dates are scheduled so available items arrive ≥24 hours before their dependent tasks start And tasks dependent only on available items remain on schedule; tasks dependent on delayed items are placed on hold with reason And the shipping cost impact of the split is displayed and included in the cost delta And no duplicate PO lines are created and inventory counts are adjusted correctly And tenant and technician notifications reflect the updated delivery plan
Stakeholder Notifications and Read Receipts
Given an exception is created, updated, approved, or rejected When the exception status changes Then notifications are sent within 60 seconds to configured stakeholders (property manager, assigned technician, vendor contact) via in-app and email, and via SMS where phone numbers are verified And each notification includes the item, reason, chosen action, cost delta, ETA delta, and required next steps And action buttons (Approve, Reject, View Details) are present where applicable And read receipts are captured for in-app and email, and SMS delivery status is logged And the notification history is visible on the exception timeline
No Valid Substitution Fallback and Schedule Impact Report
Given no substitution meets mandatory equivalency rules or all proposals are rejected When the engine cannot maintain schedule within the configured buffer Then the exception escalates to Manual Procurement with priority level and summary of constraints And dependent tasks are auto-held and the calendar suggests next available windows based on vendor availability and unit access rules And a schedule impact report is generated showing affected tasks, delay duration, and cost implications And a one-click option to cancel/replace the original PO line is provided And all actions and reasons are recorded in the audit log

FlexSwap

If a vendor declines, fails to confirm, or risks lateness, the playbook auto‑swaps to pre‑vetted backups within budget and compliance rules. Dependencies and access windows update in real time so timelines stay intact without coordinator scramble.

Requirements

Auto-Swap Vendor Engine
"As a maintenance coordinator, I want the system to auto-swap in the best available backup when a vendor can’t make it so that the repair stays on schedule without manual scrambling."
Description

Implements an automated selection and assignment engine that replaces a primary vendor with a pre‑vetted backup when defined triggers occur (decline, missed confirmation deadline, predicted lateness). The engine ranks backups using configurable criteria (skills, compliance state, proximity/ETA, historical reliability, rate card alignment, tenant availability, calendar load) and assigns within budget and policy constraints. It reserves the chosen slot on the shared calendar, prevents double booking via atomic locks, and is idempotent to avoid duplicate swaps. It supports multi-step work orders and multi-unit jobs, respects blackout dates and time zones, and locks the assignment once confirmation is received or a manual hold is applied.

Acceptance Criteria
Auto‑Swap Triggered by Decline/Missed Confirmation/Lateness Risk
Given a work order is assigned to a primary vendor with a configured confirmation deadline and lateness‑risk threshold, When the vendor declines, or the confirmation deadline elapses without confirmation, or the predicted lateness probability is greater than or equal to the configured threshold, Then the engine initiates an auto‑swap within 60 seconds and records the trigger type. Given auto‑swap is initiated, When eligible backups are available, Then the engine selects and tentatively assigns the highest‑ranked eligible backup and publishes an assignment event. Given auto‑swap is initiated, When no eligible backups meet constraints, Then the engine does not change the assignment and emits a "no_eligible_backup" event.
Backup Ranking, Eligibility, and Policy/Budget Compliance
Given a candidate pool of pre‑vetted backups with attributes (skills, compliance status, proximity/ETA, reliability score, rate card, tenant availability overlap, calendar load) and configured weights, When the engine computes scores, Then it excludes any candidate lacking required skills, with expired/invalid compliance, or outside tenant availability. Given multiple eligible candidates, When scores are computed, Then the engine chooses the highest composite score subject to budget and policy constraints (estimated cost <= work‑order budget; policy flags satisfied). Given a tie on composite score, When selection occurs, Then the engine applies deterministic tie‑breakers in order: earliest ETA, lowest rate, lowest calendar load, lowest vendor ID. Given a vendor is selected, When the assignment is written, Then the engine stores the score, factors, and exclusion reasons for audit.
Atomic Calendar Reservation and Double‑Booking Prevention
Given the chosen vendor and timeslot, When reserving on the shared calendar, Then the engine acquires an atomic lock on the vendor‑timeslot and creates exactly one reservation. Given concurrent swap attempts for overlapping timeslots, When locks are contended, Then only one attempt succeeds; losers retry with exponential backoff and do not alter assignments. Given the reservation is committed, When the calendar is queried, Then no double booking exists for the vendor in that timeslot.
Idempotent Swap Processing and Safe Retries
Given duplicate swap requests with the same idempotency key (work_order_id + step_id + trigger_id), When processed repeatedly, Then only one effective assignment is created and subsequent duplicates are acknowledged without side effects. Given a transient failure during swap after selection but before commit, When the operation is retried, Then the engine resumes to a consistent state and does not create duplicate reservations or assignments. Given an idempotent swap completes, When the audit log is reviewed, Then exactly one swap record exists for that key.
Multi‑Step Work Orders and Multi‑Unit Jobs
Given a work order with multiple dependent steps and/or multiple units, When a swap occurs for a step, Then downstream step schedules, access windows, and dependencies update in real time to preserve constraints and overall timeline. Given steps or units unaffected by the swap, When the engine updates schedules, Then their assignments remain unchanged. Given a swap would violate a dependency or required access window, When evaluated, Then the engine aborts the swap and flags the work order for manual review.
Time Zones, Blackout Dates, and Availability Windows
Given property, tenant, and vendor may be in different time zones and have blackout dates, When scheduling a swap, Then the appointment is stored in UTC and rendered correctly in each party’s local time, including DST transitions. Given tenant and vendor availability windows and blackout dates, When computing candidate times, Then the engine only proposes times that are within both parties’ windows and not on blackout dates. Given SLAs defined in property local time, When evaluating lateness or deadlines, Then the engine evaluates against the property time zone.
Assignment Lock After Confirmation or Manual Hold
Given a vendor confirms the assignment, When any subsequent trigger occurs, Then the engine does not auto‑swap and leaves the assignment unchanged. Given a manual hold is applied to an assignment, When the engine evaluates triggers, Then auto‑swap is suppressed until the hold is removed. Given confirmation or hold is applied or removed, When auditing, Then these actions and timestamps are recorded and visible.
SLA Risk & Confirmation Monitoring
"As a property manager, I want real-time detection of non-confirmation and lateness risk so that the system can act before SLAs are missed."
Description

Continuously monitors vendor response signals across SMS/email and confirmation links, applies configurable deadlines and grace periods, and predicts lateness risk using factors such as estimated travel time, traffic window, vendor reliability, and overlapping commitments. When thresholds are breached or risk is elevated, it triggers the auto-swap workflow or escalations ahead of SLA breaches. Supports per-property and job-type SLAs, business hours/holiday rules, and sends preemptive nudge reminders before initiating a swap.

Acceptance Criteria
Auto-Swap on Missed Confirmation After SLA Grace
Given a job J with assigned vendor V and a property/job-type SLA with confirmation deadline D and grace period G configured And monitoring is active for J When no valid confirmation from V is received via SMS, email, or confirmation link by time D + G Then the system triggers FlexSwap to select the next eligible backup vendor within budget and compliance rules And updates J’s schedule, dependencies, and access windows in real time And sends notifications to tenant, coordinator, and the selected backup vendor And records an audit entry with timestamps, channels checked, reason "Missed confirmation", and selected backup ID And sets J’s vendor status to "Swapped" And ensures idempotency: if a swap for J occurred within the last 5 minutes, do not perform a duplicate swap
Risk-Based Early Swap Using ETA, Traffic, and Reliability
Given a configured lateness risk threshold T and a minimum lead time R minutes And a job J scheduled at time S with assigned vendor V And live inputs for V include estimated travel time, current traffic, historical reliability score, and overlapping commitments When the computed lateness risk score for V on J exceeds T at any time t <= S - R Then the system marks J as "At Risk" and starts the risk response workflow And if eligible backups exist and autoSwapOnRisk = true, schedules an auto-swap to execute after R minutes unless a valid confirmation or ETA improvement reduces risk below T And if no eligible backups exist, immediately creates a coordinator escalation with risk factors and recommended actions And records an audit entry including risk score, inputs, threshold T, and chosen action
Per-Property and Job-Type SLA Selection and Precedence
Given a property-level SLA SP and a job-type-level SLA ST may both be configured When a job J is created for property P with job type T Then monitoring applies ST if present for T; otherwise applies SP; otherwise applies the system default SLA SD And the effective SLA (deadlines, grace, nudge policy) is visible on J And if ST or SP is later updated before breach, the monitoring recalculates deadlines and reminders within 30 seconds and logs the change
Business Hours and Holiday-Aware Deadline Calculation
Given property P has timezone Z, business hours BH, and holiday calendar H defined When calculating confirmation deadlines and grace for job J Then all times are computed in timezone Z And if a deadline falls outside BH, it is rolled forward to the next BH window preserving the configured duration And holidays in H are excluded from countdown calculations And the effective computed deadline and rationale (BH/holiday adjustment) are recorded in the audit log
Multi-Channel Confirmation Capture and Deduplication
Given vendor V may confirm or decline via SMS reply, email reply link/button, or web confirmation link When V sends a confirmation signal (e.g., SMS "Y"/"Yes", email/web "Confirm") Then the system records a single confirmation event for job J with source channel and timestamp And updates job status to "Confirmed" within 5 seconds of receipt And cancels pending nudges and scheduled swaps for J And deduplicates multiple signals received within a 30-second window so only one confirmation is recorded And when V sends a decline signal (e.g., SMS "N"/"No", email/web "Decline"), the system marks J as "Declined" and triggers FlexSwap within 60 seconds
Preemptive Nudge Reminders Prior to Swap Trigger
Given a job J with confirmation deadline D and configured nudge offsets N1 and N2 minutes before D (N1 > N2) And vendor V has not confirmed When the time reaches D - N1 Then the system sends the first nudge via SMS and email including a secure one-click confirmation link and the appointment summary And records delivery status and timestamps When the time reaches D - N2 and V still has not confirmed and J is not swapped Then the system sends the second nudge via SMS and email and records delivery And if V confirms at any point, all future nudges and any scheduled swap timers are canceled within 5 seconds
Escalation When No Eligible Backup Exists Before Breach
Given a job J is at risk of SLA breach due to missed confirmation or elevated risk And the eligible backup vendor pool is empty or all candidates violate budget/compliance constraints When the system would otherwise initiate an auto-swap Then it does not swap And instead sends an escalation to the coordinator containing job details, risk reason, SLA deadlines, and constraints within 60 seconds And flags J as "Manual Intervention Required" And ensures the escalation is sent at least E minutes before D + G where E is the configured escalation lead time
Budget & Compliance Guardrails
"As an owner, I want backups chosen only within budget and with current compliance so that cost exposure and legal risk are controlled."
Description

Enforces budget ceilings, NTE amounts, and rate card limits during swap selection while validating vendor compliance artifacts (licenses, insurance, W‑9, background checks) and work-type eligibility. Rejects candidates that exceed budget or lack up-to-date compliance, supports configurable exceptions with approver role and reason capture, and accounts for ancillary costs (trip fees, overtime, taxes) in the budget check. Integrates with vendor profiles and document expiry tracking to ensure only eligible backups are considered.

Acceptance Criteria
Auto-swap Excludes Over-Budget Candidates with Ancillary Costs
Given a work order with a budget ceiling or NTE amount and configured ancillary cost rules (trip fees, overtime, taxes, surcharges) When FlexSwap evaluates a candidate vendor Then the system computes projected total cost using rate card, estimated duration, and applicable ancillary costs And if the projected total exceeds the budget ceiling or NTE, the candidate is marked ineligible and is not auto-selected And the ineligibility reason displayed includes a cost breakdown and the threshold exceeded
Compliance Block on Expired or Missing Vendor Documents
Given vendor compliance requirements for the work type (licenses, insurance, W-9, background check) with expiry dates tracked When a candidate vendor has any required document missing or expired as of the evaluation time Then FlexSwap excludes the vendor from auto-selection and manual pick lists And the UI displays specific missing/expired items with dates And no confirmation can be sent to the ineligible vendor
Work-Type Eligibility Enforcement during FlexSwap
Given a work order with a defined work type and required certifications/authorizations When FlexSwap builds the backup candidate pool Then only vendors mapped to the work type and meeting eligibility rules are included And vendors lacking required certifications are excluded with reason "Work-type not eligible"
Exception Override with Approver Role and Reason Capture
Given a candidate vendor blocked for budget or compliance When a user with the Approver role submits an exception, providing a reason and selecting scope (single job or vendor+work-type) and duration Then the system records an audit entry with approver ID, timestamp, reason, scope, and expiry And upon approval, the vendor becomes selectable for that scope while all other guardrails continue to apply And if the exception would allow budget exceedance, the UI displays the overage amount before confirmation
Rate Card and NTE Harmonization in Selection
Given a vendor rate card with base, overtime, and trip rates and caps When FlexSwap evaluates candidates against the work order NTE Then the system enforces rate caps per rate card (including overtime multipliers) And any candidate with a component rate exceeding the cap is marked ineligible even if total is under NTE And any candidate under caps but over NTE is marked ineligible
Real-Time Revalidation on Profile or Document Changes
Given the candidate list is displayed and an underlying vendor profile or compliance document changes (e.g., new insurance uploaded or license expires) When the change is saved Then FlexSwap revalidates budget and compliance for affected vendors within 60 seconds And the candidate list updates accordingly with reasons added or removed And an event is logged noting revalidation and changes in eligibility
Real-Time Dependency & Access Updates
"As a coordinator, I want dependencies and access instructions to update automatically when a vendor is swapped so that the timeline remains accurate and no one is blocked on site."
Description

Upon swap, dynamically recalculates task dependencies, crew sequencing, and access windows, updating lockbox codes, key pickup/drop details, building rules, and tenant availability blocks. Writes changes to the shared calendar and timeline, avoids conflicts with existing work, and maintains constraint integrity across related tasks. Propagates updates to downstream tasks and adjusts ETAs to keep the overall schedule intact.

Acceptance Criteria
Dynamic Recalculation on Vendor Swap
Given a scheduled task with defined dependencies and crew sequencing And a vendor swap is triggered due to decline, non‑confirmation threshold, or lateness risk When the swap is executed Then the system recalculates dependencies and crew sequencing without violating FS/SS/FF constraints for all related tasks And computes the new plan within 5 seconds of swap execution And introduces no circular dependencies And updates the shared timeline view to reflect the new sequencing
Access Credentials and Building Rules Update
Given the replacement vendor requires updated property access When the swap is executed Then lockbox codes are rotated per policy and reassigned to the new vendor And key pickup/drop instructions are updated with correct locations and time windows And building rules (work hours, insurance/compliance notes) are attached and visible on the task And access windows conform to tenant availability and building quiet hours And the shared calendar entry description reflects the new access details within 5 seconds
Calendar Conflict Detection and Resolution
Given recalculated dates/times for the swapped task When writing updates to the shared calendar Then the system prevents overlapping bookings for the same vendor, unit, or lockbox resource And auto-adjusts within allowed time windows to eliminate conflicts And ensures the final calendar has zero overlapping events per resource And preserves dependency order and tenant availability during any adjustment
Downstream Propagation and ETA Adjustment
Given downstream tasks depend on the swapped task When the swapped task’s start/end time or duration changes Then ETAs for all downstream tasks are recalculated and updated on the timeline and calendar within 10 seconds And milestone dates are preserved when feasible; otherwise the minimal shift necessary is applied And any downstream task breaching SLA/availability windows is flagged for attention
Tenant Availability Blocks Enforcement
Given tenant availability blocks exist for the unit When access windows are recomputed after a swap Then the scheduled start/end fall wholly within tenant availability blocks And no calendar event is saved outside allowed windows And if no compliant slot exists, the task is marked Needs-Reschedule with no conflicting event created
Constraint Integrity Across Related Tasks
Given related tasks have explicit constraints (e.g., same-day sequence, minimum gap, co-scheduled crews) When recalculation occurs after a swap Then all constraint rules remain satisfied in the updated plan And any computation yielding violations is rejected and re-solved until constraints pass And a validation check records zero constraint violations for the final plan
Communication Orchestrator & SMS Confirmations
"As a tenant, I want clear, timely messages when a vendor changes and an easy way to confirm my availability so that I’m prepared and appointments are kept."
Description

Generates and sends templated, localized confirmations and updates to the newly assigned vendor and impacted tenants via two-way SMS with email/voice fallbacks, handling opt-out compliance, throttling, retries, and delivery status tracking. Redacts sensitive access details until confirmation, includes smart links for one-tap confirm/decline and ETA updates, and posts conversation snippets back to the work order timeline. Ensures consistent messaging for swap events and supports per-property templates and languages.

Acceptance Criteria
Auto-Notify New Vendor on FlexSwap with Redacted Access
Given a FlexSwap reassigns a work order to a new vendor When the reassignment is finalized Then send a localized SMS to the new vendor within 30 seconds containing job summary, service address, appointment window, and a one-tap confirm/decline link And withhold all sensitive access details (e.g., door/lockbox codes, entry notes) until confirmation And mask any access codes in the message preview (show at most last 2 digits) if present in metadata And log the outbound message with a unique MessageID and initial status = "Pending" And throttle to a maximum of 2 initial notifications per vendor per work order within 10 minutes And update delivery status to Delivered/Failed within 2 minutes of carrier callback or mark as TimedOut after 15 minutes with no callback
Vendor One-Tap Confirm/Decline and ETA via Smart Link
Given a vendor receives a confirm/decline smart link or replies via SMS keywords When the vendor taps Confirm or replies CONFIRM Then record confirmation with timestamp and vendor device/user ID (if available) And prompt for ETA; when vendor submits ETA or replies "ETA HH:MM" or "ETA +15m", update the work order schedule and notify impacted tenants within 60 seconds And immediately send a follow-up SMS to the vendor with unredacted access details and any dependencies within 15 seconds of confirmation When the vendor taps Decline or replies DECLINE Then mark the vendor as declined and trigger FlexSwap to the next backup within 30 seconds And notify coordinator channel if no backups remain And expire the smart link upon reassignment or after 24 hours, whichever comes first And send a single reminder at 15 minutes if no response, respecting throttling limits
Tenant Update on Vendor Swap with Localized Template
Given a vendor swap changes the assigned vendor or appointment window When the new vendor is assigned Then send a localized SMS to each impacted tenant within 2 minutes with updated window, vendor display name (or generic descriptor per policy), and a status link And do not expose the vendor's direct contact info And include clear opt-out instructions (e.g., "Reply STOP to opt out") And throttle tenant notifications to a maximum of 3 messages per tenant per work order within any 60-minute period and deduplicate identical updates within 5 minutes And select language using tenant preference, else property default, else English And log template ID, locale, and MessageID to the work order timeline
SMS Failover to Email/Voice with Retries and Tracking
Given an outbound SMS is attempted When carrier status returns Failed/Undeliverable or the contact lacks a mobile-capable number Then retry SMS up to 3 times with exponential backoff of 1m, 5m, and 15m And if still not delivered, send email if an address exists; if email bounces or is unavailable and a voice-capable number exists, place a TTS voice call And capture input on voice calls (Press 1 = Confirm, 2 = Decline) and apply the same workflow outcomes And cap total notification attempts to 5 per event per recipient across all channels And update each attempt's status (Queued, Sent, Delivered, Failed) and channel in the timeline with timestamps and provider codes And mark overall outcome as Success or Exhausted within 30 minutes of first attempt
Opt-Out Compliance Handling and Suppression List
Given a recipient replies STOP/UNSUBSCRIBE (including supported multilingual equivalents) When the inbound message is processed Then add the sender to a suppression list within 5 seconds and send a one-time opt-out confirmation in the same language And block all non-mandatory messages to suppressed contacts across SMS, email, and voice And when a suppressed contact is targeted by any workflow, prevent send and log reason = Suppressed with reference to consent event And when a recipient sends HELP, respond with support contact and instructions to opt back in (START/UNSTOP) and record the interaction And maintain an auditable consent log (opt-in/out events) with timestamp, channel, locale, and source for at least 24 months
Per-Property Template and Language Resolution
Given the system must generate a message for a swap-related event When selecting content Then choose the per-property template if defined; else use portfolio default; else global default And validate all required placeholders are present; if any are missing, do not send and log a TemplateError with details And render in the recipient's preferred language; else property default; else English; if requested locale is unsupported, fall back to base language (e.g., es-MX -> es) And support at minimum locales: en, es, fr, zh; record template ID and resolved locale And complete template rendering within 500 ms p95 and enqueue the message within 1 second p95
Conversation Snippets Posted to Work Order Timeline
Given any inbound or outbound communication related to the work order (SMS, email, voice transcript) When the message is sent or received Then post a timeline entry within 10 seconds containing direction, channel, recipient/actor role, status, and the first 160 characters of content And redact PII and all access details per policy, preserving only necessary context And for key events (Confirm, Decline, ETA update), create structured timeline entries with normalized fields and link to underlying MessageIDs And restrict full transcript access to authorized roles; unauthorized roles see only redacted snippets And ensure timeline write operations are idempotent to avoid duplicate entries within a 2-minute window
Manual Override & Approval Workflow
"As an operations lead, I want the ability to override auto-swaps and approve exceptions with controls and traceability so that business judgment can be applied when needed."
Description

Provides a reviewer UI to inspect the system’s recommended backup, compare alternatives, apply temporary holds, override the selection, and approve budget or policy exceptions with justification. Enforces role-based permissions, records all actions, and re-runs validation checks when overrides occur. Allows reverting to the original vendor if they later confirm within policy windows.

Acceptance Criteria
Compare recommended backup to alternatives in override UI
Given a job has a system-recommended backup and at least one alternative vendor And the signed-in user has Reviewer permission When the reviewer opens the Manual Override screen for the job Then the screen displays the recommended vendor with price, ETA window, compliance status, and fit score And shows up to 5 available alternatives sorted by fit score within 2 seconds And each vendor entry shows last 3-job rating, rate type and amount, travel time estimate, and policy flags And selecting two vendors shows a side-by-side comparison panel.
Apply temporary hold to pause auto-swaps during review
Given a job is in FlexSwap pending or awaiting confirmation And the user has Reviewer permission When the user enables a Hold with a selected duration between 5 and 30 minutes Then FlexSwap pauses auto-assignments and outbound notifications for that job within 1 second And the job header shows Hold active with remaining time and the reviewer’s name And the hold auto-expires at the set duration, resuming automation And an audit entry records hold start and end times and reason text.
Override vendor selection with required justification and permissions
Given the user has Override permission And a recommended or held backup vendor is present When the user selects an alternative vendor and enters a justification of at least 20 characters And clicks Submit Override Then the system revalidates budget and policy rules for the chosen vendor And if all rules pass, assigns the chosen vendor, updates the shared calendar, and sends notifications within 5 seconds And if any rule fails, blocks the override and displays the specific failing rules And an audit log records before/after vendor, user, timestamp, and justification.
Approve budget or policy exceptions for an override
Given an override attempt fails budget or policy validation And the current user has Exception Approver permission When the user selects the failing rule categories, enters a justification of at least 30 characters, and chooses an expiration (none or date) Then the system records the approval with approver identity, rule IDs, justification, and expiration And proceeds with the override while marking the job with an Active Exception badge And sends notifications that include the exception tag And the exception automatically expires on the set date, re-enabling the rule.
Re-run validations to update dependencies and access windows after override
Given an override changes vendor or appointment time When the override is saved Then dependency and access-window checks run within 2 seconds And any conflicts are surfaced inline with specific task IDs and required actions And tenant and vendor calendars update to the new time slots And SMS confirmations are re-sent if the schedule changed And the job cannot be marked Ready until conflicts are resolved or an exception is approved.
Revert to original vendor upon timely confirmation
Given the primary vendor confirms within the configured policy window and no work has started with the backup When the reviewer clicks Revert to Original or an auto-revert rule triggers Then the system cancels the backup assignment with policy-compliant notices And restores the original vendor assignment and calendar slot And sends updated notifications to all parties within 5 seconds And records the revert action with reason, user, and timestamps.
Role-based permissions enforced and full audit trail captured
Given RBAC defines Reviewer, Override, and Exception Approver roles When a user without the required role attempts a restricted action (hold, override, approve, revert) Then the control is disabled or returns 403 with an inline message specifying the needed role And sensitive fields (rates, compliance documents) are hidden from unauthorized users And every attempt and action is captured in an immutable audit log with user ID, IP, timestamp, action, and before/after values And audit entries are filterable by job and exportable to CSV.
Decision Audit Trail & Reporting
"As a compliance analyst, I want a transparent record of why and how a swap occurred so that I can audit decisions and report performance accurately."
Description

Captures a complete, immutable log of swap decisions including triggers, candidates considered with scores, rule evaluations, compliance checks, communications sent, timestamps, and actor (system or user). Exposes searchable timelines on each work order and roll-up dashboards for KPIs like swap rate, time-to-confirm, SLA adherence, and budget variance, with export capabilities and data retention controls aligned to policy and privacy requirements.

Acceptance Criteria
Immutable Swap Decision Log Capture
- Given a vendor declines, fails to confirm within the configured window, or a lateness risk trigger fires, When FlexSwap evaluates a swap, Then an append-only decision event is recorded within 2 seconds containing: workOrderId, triggerType, triggerTimestamp, candidateList [vendorId, score, rank, reason], appliedRules [ruleId, outcome, inputs], complianceChecks [checkId, result], selectedVendorId, priorVendorId, decisionOutcome, actorType (SYSTEM|USER), actorId (if USER), requestId, correlationId, serverTimestamp. - Given an existing decision event, When a correction or override is made, Then a new superseding event is appended with priorEventId and reasonCode, and the prior event remains immutable and readable. - Given the audit log, When integrity verification runs, Then a cryptographic hash chain over event payloads validates with no gaps and any tampering sets integrityStatus="FAIL" with the first mismatched eventId surfaced. - Given a work order, When viewing its audit tab, Then events render in serverTimestamp order with local timezone display and ISO 8601 UTC on hover.
Work Order Timeline Search and Filter
- Given a work order with 10+ audit events, When a user searches by keyword, vendorId, ruleId, actorId, or outcome, Then matching events are filtered and displayed in under 300 ms for up to 500 events. - Given filter chips for eventType, dateRange, communicationChannel, and integrityStatus, When applied or removed, Then the timeline updates accordingly and shows a visible count of matching events. - Given pagination or infinite scroll, When navigating between pages, Then no events are skipped or duplicated and the URL reflects filters for shareable deep links. - Given a reset action, When clicked, Then all filters clear and the full ordered timeline reappears.
KPI Roll-up Dashboards with Drill-down
- Given a selected date range and portfolio or property scope, When the dashboard loads, Then it displays KPIs: swapRate (%), medianTimeToConfirm (minutes), SLAAdherence (%), avgBudgetVariance (currency) with trend sparklines. - Given any KPI widget, When clicked, Then it drills down to the list of underlying work orders and decisions that constitute the metric within 2 seconds and preserves filters. - Given tooltip help, When hovered, Then KPI definitions show as: swapRate = count(workOrdersWithAtLeastOneSwap)/count(workOrdersWithVendorAssignment) in period; medianTimeToConfirm = median(minutes between proposalSentAt and vendorConfirmedAt); SLAAdherence = percent of swaps confirmed before slaDeadline; budgetVariance = selectedVendorQuote - baselineBudget (displayed as average and distribution). - Given segmentation controls, When grouping by property, vendor, or coordinator, Then KPIs recalculate and charts update accordingly without page reload.
Export of Audit Trails and KPI Data
- Given a user with export permission, When exporting audit events for a date range up to 1,000,000 events, Then an async job is queued, a status toast appears, and downloadable CSV and JSON files are produced within 10 minutes. - Given the CSV export, When opened, Then it is UTF-8, includes a header row with field names, uses ISO 8601 UTC timestamps, and contains all fields specified for decision events including nested lists flattened with stable column naming. - Given large exports >50 MB, When completed, Then files are split into multi-part archives in a ZIP; the download link expires after 24 hours; exports honor active filters and role-based access restrictions. - Given PII redaction is enabled by policy, When exporting communications, Then message bodies are redacted, and emails/phone numbers are masked except last 4 characters; a metadata.json accompanies the export describing filters, generation time, and column dictionary.
Configurable Data Retention and Legal Hold
- Given an admin sets retention policies per data class (auditEvents, communications, exports) between 3 and 60 months, When saved, Then policies are validated, stored, and the UI displays the next scheduled purge date. - Given nightly purge, When records exceed their retention period, Then they are permanently deleted and a purgeSummary event is appended with counts by class, duration, and legalHoldBypass=false. - Given a legal hold applied to a work order, vendor, or portfolio, When a purge would affect held records, Then those records are excluded, with holdReason, holder, and scope displayed in the retention settings and logs; removing the hold resumes purge on the next cycle. - Given a privacy erasure request is approved, When executed, Then personal identifiers are deleted or irreversibly pseudonymized within 30 days while preserving non-personal decision metrics and referential integrity.
Role-Based Access and Sensitive Field Masking
- Given roles Owner, Admin, Coordinator, Viewer, and Vendor, When accessing audit logs and dashboards, Then each role sees only its permitted scope: Vendor sees only their work orders; Viewer read-only; Coordinator limited to assigned properties; Admin full access. - Given sensitive fields (phone, email, messageBody, complianceDocument), When viewed by non-Admin roles, Then phone/email are masked except last 4 characters, messageBody is redacted unless explicit permission is granted, and documents are downloadable only by Admin or record owner. - Given access attempts, When a user views, exports, changes retention settings, or runs integrity checks, Then an accessAudit event records userId, role, action, target, timestamp, and outcome; unauthorized actions return HTTP 403 and are logged.

Branch Paths

Turns playbooks into adaptive workflows that respond to field findings. Cleaners flag “heavy soil” and a deep‑clean branch inserts; painters report moisture and a remediation path adds with added cure time. SLAs, budgets, and notifications adjust automatically.

Requirements

Visual Branch Condition Builder
"As an operations manager, I want to define branching rules in a visual builder so that playbooks adapt automatically to field findings without manual coordination."
Description

A no-code interface to define branching rules within maintenance playbooks using field findings and form inputs. Supports condition types such as checkboxes (e.g., “Heavy soil”), numeric thresholds (e.g., moisture > 16%), and multi-selects, with AND/OR grouping and precedence. Each condition maps to a branch that can add tasks, modify durations, apply cure/buffer times, and set deltas for SLAs, budgets, and notification rules. Includes validation, preview simulation against sample data, and versioned publishing to playbook templates. Integrates with FixFlow’s existing playbook editor, permission model, and API so product teams can manage reusable rule sets across properties and vendors.

Acceptance Criteria
Build Mixed Conditions (Checkbox, Numeric, Multi-select)
Given a new rule set in the Visual Branch Condition Builder within a playbook When the user adds conditions: Checkbox "Heavy soil" is checked; Numeric field "Moisture" > 16; Multi-select "Surface type" includes "Porous" And the user groups them with AND operators And clicks Save Then the expression is rendered with tokens and operators exactly as configured (no implicit conversions) And the persisted rule set JSON contains all three conditions with correct field keys, operators, and values And reloading the editor reproduces the exact same configuration without loss or mutation
AND/OR Grouping and Precedence Evaluation
Given three conditions A, B, C mapped to real fields and grouped visually as (A AND (B OR C)) And a sample dataset D1 where A=true, B=false, C=true When the user runs Preview Then the evaluation result is true and the UI highlights the branch as matched And the evaluation trace shows parentheses-driven order of operations Given a second dataset D2 where A=true, B=false, C=false When the user runs Preview Then the evaluation result is false and the UI shows the branch as not matched And removing the inner parentheses and evaluating (A AND B OR C) with D2 produces a different trace reflecting precedence change
Branch Actions Apply Task and Timeline Modifications
Given a branch with actions: add task "Deep Clean" (60 min), modify task "Prime Walls" duration +20%, apply cure buffer +12 hours, SLA delta +1 day, budget delta +15%, and notification rule set to "Notify Supervisor on match" And sample dataset that matches the branch When the user runs Preview and then saves the rule set Then the preview diff shows the inserted task at the correct position, duration adjustments applied, and a +12h buffer reflected in the scheduled timeline And the computed SLA due date shifts by exactly +1 day And the budget estimate increases by exactly +15% And the notification rule change is listed for the affected playbook stage And no task duration becomes negative and no overlapping tasks are created in the schedule
Validation and Error Prevention Before Publish
Given an invalid configuration (e.g., unmatched parentheses, empty condition group, non-numeric threshold for a numeric field, or no actions defined for a branch) When the user attempts to Save or Publish Then the builder blocks the action and displays inline error messages adjacent to each offending field And the Publish button remains disabled until all validation errors are resolved And the first error is auto-focused and announced for accessibility And a rule set cannot exceed 50 total conditions or 10 nested group levels; attempting to do so surfaces a clear limit error
Preview Simulation With Sample Data and Diffs
Given a rule set with up to 100 conditions and at least two branches And a saved sample dataset representing a work order with field findings When the user runs Preview Simulation Then the system computes the matching branch(es) and renders a side-by-side diff of tasks, durations, buffers, SLAs, budgets, and notifications within 2 seconds And the user can edit the sample dataset inline and re-run to see updated results And the last three sample datasets persist per user and can be reloaded
Versioned Publishing and Rollback to Templates
Given a draft rule set attached to a playbook template When the user publishes with a required changelog note Then an immutable version (e.g., v1.0.0) is created and the template is pinned to that version by default And publishing subsequent changes creates v1.0.1, v1.0.2, etc., without altering prior versions And the user can change a template’s rule set reference between a specific version (pinned) and "Track latest" (auto-updates on publish) And the user can rollback a template to any prior published version with one action and an audit log entry is recorded
Permissions and API Integration for Reusable Rule Sets
Given a user with Playbook:Edit but without Playbook:Publish When they open the builder Then they can create and edit rule sets but cannot publish; Publish controls are disabled with a tooltip Given a user with Playbook:Publish When they publish a rule set Then the action succeeds and is recorded in the audit log with user, timestamp, and version And the REST API exposes endpoints to list/get/create/update/publish rule sets, secured by the existing permission model, returning 401/403 where appropriate And a published rule set can be attached to multiple playbook templates across properties and vendors; templates pinned to a version remain unchanged when a new version is published, while templates set to Track latest receive the update automatically
Dynamic Task Insertion & Re-sequencing
"As a property manager, I want tasks to insert and reorder automatically when new conditions are found so that work continues in the right order without me rebuilding the schedule."
Description

When a branch is triggered, the system inserts the appropriate tasks into the active workflow and re-sequences downstream steps to respect dependencies and prerequisites. Preserves assignees where possible, recalculates durations, and prevents duplication of already-completed steps. Provides safeguards for in-flight tasks (e.g., require confirmation if reordering would disrupt started work) and supports reversible changes if a branch is cleared. Emits events for integrations, updates checklists and forms, and maintains idempotency to ensure consistent outcomes from repeated triggers. Seamlessly interoperates with the shared calendar and vendor assignment engine.

Acceptance Criteria
Heavy-Soil Flag Triggers Deep-Clean Insertion and Re-sequencing
Given an active cleaning workflow with defined dependencies and a Deep Clean branch available When the cleaner marks Heavy Soil = true on the checklist for the current job Then the system inserts the Deep Clean task group after Initial Assessment and before any tasks depending on Clean Completed And downstream tasks are re-sequenced to preserve all prerequisite relationships with no cycles And if any inserted task duplicates an existing completed step by templateId/externalKey, it is not inserted and a note is recorded And the updated task order is persisted and visible to the user within 2 seconds And the inserted tasks inherit the property/unit context and due date window constraints
Assignee Preservation and Duration/SLA Recalculation
Given some tasks already have assignees, estimates, and due dates When branch insertion occurs Then existing assignees remain unchanged for unaffected tasks And for inserted tasks, the system auto-suggests the current assigned vendor if capacity exists; otherwise marks as Unassigned and queues assignment And total workflow duration and end date are recalculated using task duration library and dependency graph And SLA and budget fields update with a visible delta summary And all changes are logged with before/after timestamps and user/action source
Safeguards for In-Flight Tasks During Reordering
Given at least one task is In Progress and would be moved by re-sequencing When the branch insertion would shift its start or end time Then the system shows a confirmation modal with impact summary (start delta, end delta, calendar conflicts) And default action is Cancel; no move occurs unless the user confirms And if canceled, inserted tasks are placed without moving the in-progress task, maintaining dependency feasibility by delaying dependents as needed And no started task is auto-canceled, auto-completed, or re-assigned without explicit user action And the decision is recorded in the audit trail
Reversible Changes When Branch Is Cleared
Given a branch was previously triggered and its tasks inserted When the triggering condition is cleared or the branch is manually reverted Then the system offers a one-click rollback preview showing tasks to remove, tasks to move back, and schedule impacts And on confirm, unstarted inserted tasks are removed, completed inserted tasks remain recorded but detached from the active critical path, and original sequencing is restored And shared calendar, assignments, and notifications are updated to match the reverted plan within 60 seconds And an integration event BranchReverted is emitted with correlationId and reason
Idempotent Processing and Dupe Prevention for Repeated Triggers
Given the same branch trigger may arrive multiple times (UI, webhook, or mobile) When a trigger with a correlationId already processed within 24 hours is received Then no duplicate tasks are created and the workflow state remains unchanged And the API responds 200 with idempotent=true and action=no-op And already-completed steps are never re-added or re-opened And deduplication is auditable with a single event chain linked by correlationId
Calendar and Vendor Assignment Interoperability
Given inserted tasks affect start/end times When re-sequencing completes Then the shared calendar updates event times and creates entries for new tasks with correct locations and assignees And the vendor assignment engine runs to avoid double-bookings and respect blackout windows, reassigning only when conflicts exist And SMS/email notifications are sent only when assignment or time changes, with a single consolidated message per recipient per change-set And the ICS feed reflects changes within 1 minute and zero overlapping bookings exist for any single resource
Checklist and Forms Update for Inserted Branch Tasks
Given inserted tasks have associated checklists and forms When the branch inserts tasks Then the relevant checklists/forms are attached with pre-filled context (unit, photos, prior findings) And assignees can access them on web and mobile immediately And task completion is blocked until required checklist items are completed and required form fields are submitted And an integration event FormsUpdated is emitted with schemaVersion and due dates
Auto-Adjust SLAs, Budgets, and KPIs
"As a portfolio owner, I want SLAs and budgets to adjust when scope changes so that performance and costs are tracked fairly and transparently."
Description

Automatically recalculates SLA deadlines, target completion dates, and escalation thresholds when a branch changes scope. Applies predefined budget deltas for labor, materials, and trip charges; updates cost centers and approval limits; and keeps baseline versus adjusted values for transparency. Propagates revised targets to dashboards, alerts, and tenant ETAs, and flags risk when projected timelines exceed thresholds. Ensures reporting and scorecards reflect adjusted expectations so performance is evaluated fairly across vendors and properties.

Acceptance Criteria
Deep-clean branch adjusts SLA deadlines and escalation thresholds
Given a work order WO-123 with baseline SLA deadline 2025-09-10T17:00:00-04:00 and tier-1 escalation at 2025-09-10T15:00:00-04:00 And a branch rule "Deep Clean" that adds +3h to duration When the branch is applied to WO-123 Then the adjusted SLA deadline becomes 2025-09-10T20:00:00-04:00 And the tier-1 escalation recalculates to 2025-09-10T17:00:00-04:00 And both baseline and adjusted deadlines are stored and labeled for display and API retrieval
Moisture remediation branch updates budgets, cost center, and approval limit
Given baseline budget on WO-124: labor $200 (2h @ $100/h), materials $50, trip $25, total $275, cost center CC-100, approval limit $400 And a branch "Moisture Remediation" with deltas: labor +$150, materials +$200, trip +$0, new cost center CC-210, approval required if total > $400 When the branch is applied Then adjusted amounts are labor $350, materials $250, trip $25, total $625 And the cost center updates to CC-210 And an approval request is created and the work order status reflects "Pending Approval" until approved And baseline and adjusted budget lines are both visible in UI and exportable via API
Revised targets propagate to dashboard, alerts, and tenant ETA
Given a work order WO-125 whose adjusted SLA deadline is 2025-09-10T20:00:00-04:00 and target completion date is 2025-09-12 When a scope branch is applied that triggers these adjustments Then the operations dashboard displays the adjusted SLA and target within 10 seconds And existing escalation alerts are rescheduled to align with the new deadlines And the tenant ETA message updates within 60 seconds, showing the new time window and the reason "Scope change"
Baseline vs adjusted values preserved and auditable
Given WO-126 has a recorded baseline and one prior adjustment When a new branch applies additional deltas Then the system keeps the original baseline immutable And appends a new adjustment record capturing timestamp, actor (user or automation), source branch ID, per-field deltas, and resulting adjusted totals And an audit log entry is accessible in the UI history and via API with before/after values for SLA, budget, and escalation thresholds
Risk flagging when adjusted timeline exceeds thresholds
Given property policy "High Priority" requires resolution within 24h of creation And WO-127 creation time is 2025-09-09T12:00:00-04:00 When a branch adjustment pushes the adjusted SLA deadline to 2025-09-10T15:00:00-04:00 (27h) Then WO-127 is flagged "At Risk" and highlighted in red on the dashboard And a risk alert is sent to the assigned vendor and property manager immediately And the work order shows the threshold breached and hours over target
Multiple branches stack without double counting and support reversal
Given WO-128 baseline SLA deadline 2025-09-11T17:00:00-04:00 and budget total $300 And branch A adds +2h and +$75 And branch B adds +1h and +$50 materials When both branches are applied Then the adjusted SLA deadline is 2025-09-11T20:00:00-04:00 and budget total is $425 And removing branch B reverts only B's +1h and +$50, yielding SLA 2025-09-11T19:00:00-04:00 and budget $375 And recalculation completes within 5 seconds of each change
Scorecards and reports use adjusted expectations
Given WO-129 has an adjusted SLA deadline of 2025-09-10T20:00:00-04:00 And the actual completion time is 2025-09-10T19:30:00-04:00 When monthly vendor and property scorecards are generated Then WO-129 is counted as "On-time" against the adjusted SLA And KPI metrics (On-time %, Average lateness, SLA breach count) are computed using adjusted targets And reports display both baseline and adjusted values for transparency
Smart Calendar Rebooking & Cure Time Blocking
"As a scheduler, I want the calendar to rebook automatically with needed buffer times so that vendors and tenants aren’t double-booked and work finishes in the right time frame."
Description

Recomputes schedules when branches add or alter tasks by placing required buffer/cure times and reallocating vendor time blocks on the shared calendar. Detects and avoids double-bookings, suggests alternative time slots or qualified vendors based on skills and location, and respects tenant access windows and business hours. Updates calendar invites and ICS links, posts reschedule notices, and synchronizes with external calendars where connected. Ensures downstream tasks are shifted coherently to maintain logical order and resource availability.

Acceptance Criteria
Cure Time Blocking After Moisture Remediation Branch
Given a work order includes a painting task assigned and scheduled And a field report triggers a moisture remediation branch requiring a 48-hour cure buffer When the branch is added Then the system inserts the remediation task and a 48-hour cure-time block immediately after remediation And shifts the painting task to start after the cure buffer end And ensures the assigned vendor has no overlapping events during the new times And completes the recompute and calendar update within 30 seconds And displays the updated start/end times on the shared calendar to the minute
Double-Booking Detection and Auto-Reallocation
Given a vendor is assigned to multiple tasks affected by rebooking And two or more tasks overlap on the vendor’s calendar When the scheduler recomputes the plan Then the system detects each conflict And resolves by moving the conflicting task to the nearest available slot respecting dependencies, tenant access windows, and business hours And if no slot exists the same day, proposes the earliest next-day slot And confirms no double-bookings remain for that vendor And records a conflict-resolution entry with before/after times in the audit log
Alternative Slot and Vendor Suggestions (Skills + Location)
Given a task requires a specific skill and duration within an SLA window And the current vendor has no compliant availability When the system searches for alternatives Then it presents at least 2 compliant time-slot suggestions for the current vendor if any exist And if none exist, it presents up to 3 alternate vendors who match required skills and are within the configured service radius And ranks suggestions by earliest feasible start then distance And each suggestion shows start/end, vendor, skill match, distance, and SLA impact (on-track/at-risk)
Respect Tenant Access Windows and Business Hours
Given tenant access windows and vendor business hours are defined for the task When rebooking is triggered by a branch insertion or change Then all auto-selected and suggested times fall within the intersection of access windows and business hours And if no intersection exists, the system does not auto-schedule and raises a Window Constraint warning And any manual override requires a reason and user confirmation and is recorded in the audit log
Dependency Order Preservation for Downstream Tasks
Given tasks have defined dependencies and required buffers (e.g., remediation -> cure -> paint) When rebooking occurs due to branch changes Then each task is scheduled to start no earlier than its predecessors’ end plus buffers And downstream tasks across vendors are shifted to available slots without resource conflicts And project-level SLA dates are recalculated and displayed And the Gantt/Calendar view shows updated dependency lines without violations
Calendar Invites, ICS Links, and Reschedule Notifications
Given connected calendars and notification channels are configured When any task is rebooked by the scheduler Then updated calendar invites containing new start/end, location, and an ICS link are sent to the assigned vendor and tenant within 2 minutes And prior invites are canceled with a cancellation notice And an in-app activity post plus SMS/email reschedule notice is sent to all watchers And all messages reference a single source-of-truth ICS URL for the current event
External Calendar Sync Consistency
Given a two-way sync with Google or Microsoft calendars is enabled When a rebooking occurs Then the external calendar event updates to the new time within 2 minutes with a stable external event ID And if the task is reassigned to a different vendor, the event is removed from the original vendor’s external calendar and created on the new vendor’s calendar And on sync failure the system retries with exponential backoff up to 3 times and surfaces an alert And the FixFlow shared calendar reflects the final external state without duplicates
Contextual Notifications & SMS Confirmations
"As a tenant, I want clear updates when the plan changes so that I know what to expect and can confirm access."
Description

Sends targeted, understandable updates when a branch triggers, including the reason for the change, revised ETA, and next steps. Delivers via SMS and email with localized, templated content; captures vendor and tenant confirmations; and applies quiet-hour rules, retry logic, and opt-out compliance. Adjusts notification recipients and cadence based on the branch (e.g., add remediation contractor, notify tenant of extended drying period) and mirrors updates within the dashboard. Exposes webhook events for downstream systems.

Acceptance Criteria
Tenant ETA Update on Branch Trigger
- Given a branch is triggered that alters task duration or SLA - When the system processes the branch - Then send SMS and email to the tenant within 60 seconds including reason, revised ETA window, and next steps - And localize content to the tenant's locale with fallback to English - And record delivery status (queued, sent, delivered, failed) and surface it in the dashboard activity feed - And update the dashboard timeline to mirror the revised ETA
Vendor and Tenant Confirmation Capture
- Given a branch adds or reschedules a vendor and affects tenant access - When notifications are sent - Then the vendor can reply YES to confirm or NO to decline via SMS - And the tenant receives a secure link via SMS/email to confirm access or request reschedule - And confirmations/declines are processed within 30 seconds, with timestamps and source phone/email recorded - And a NO or reschedule request reopens scheduling and notifies the coordinator via email and dashboard alert
Quiet Hours Enforcement and Deferral
- Given local project time is between 21:00 and 08:00 - When a tenant-facing notification is triggered - Then suppress SMS/email to tenants during quiet hours and queue for 08:00 local send - And continue sending vendor operational notifications during quiet hours - And log deferment reason and scheduled send time in the dashboard
Opt-out and Preference Compliance
- Given a recipient has opted out via STOP or profile preferences - When an SMS is about to be sent - Then block the message and send a one-time opt-out confirmation if required - And update recipient preferences and audit log within 10 seconds - And route communication to permitted alternative channels (e.g., email) when allowed
Retry Logic and Channel Fallback
- Given an SMS attempt fails with a retriable error - When the first delivery fails - Then retry up to 3 times with exponential backoff at 1, 5, and 15 minutes - And if all retries fail, send the same content via email and flag the work order with SMS failed - And surface failure details in the dashboard and emit a failure event
Branch-specific Recipients and Cadence
- Given a remediation branch is triggered due to moisture - When notifications are generated - Then add the remediation contractor as a recipient with scope, address, and adjusted start date - And notify the tenant of the extended drying period with an initial notice and a 48-hour reminder cadence - And suppress notifications to non-impacted recipients - And display recipient changes in the dashboard recipients list
Webhook Events for Notification Lifecycle
- Given a branch-triggered notification is sent, deferred, failed, delivered, or confirmed - When the event occurs - Then emit a webhook within 10 seconds including event type, work order ID, branch ID, recipients, channel, locale, reason, ETA, next steps, delivery status, and timestamps - And sign the webhook using HMAC-SHA256 with the tenant secret - And retry on 5xx up to 5 times with exponential backoff and support replay for 7 days
Field Findings Capture (Mobile) with Evidence
"As a cleaner or contractor, I want a fast way to record conditions with photos so that the system can branch correctly without back-and-forth calls."
Description

Provides a fast mobile-first interface for vendors to record on-site conditions that can trigger branches. Supports structured inputs (toggles, picklists, numeric readings), photo/video capture with annotations, required evidence for certain branches, timestamping, and optional GPS proof-of-presence. Works offline with queued sync and prevents duplicate submissions. Shows a preview of the branch effects (tasks added, time impact) before submission to improve accuracy and reduce back-and-forth.

Acceptance Criteria
Capture Structured Field Findings (Mobile)
- Given a job with configured structured inputs, when the Field Findings screen loads, then all inputs render with labels, units, defaults, min/max constraints, and required indicators per configuration. - Given a numeric field with min=0 and max=100, when the user enters -1 or 101, then inline validation appears and submission is disabled until corrected. - Given a required picklist field, when the user has not selected a value, then the Submit button remains disabled and the field is highlighted with an error message. - Given the device locale uses comma decimals, when the user enters "12,5" in a numeric field, then the value is stored as 12.5. - Given the user taps Submit, then each saved field is stamped with an ISO-8601 UTC timestamp accurate to within 1 second.
Capture and Annotate Photo/Video Evidence
- Given the user taps Add Photo, when the camera opens, then captured photos are saved at a minimum resolution of 1024x768 and compressed to <=10 MB per image. - Given the user taps Add Video, when the camera opens, then captured videos are limited to <=30 seconds at up to 720p and compressed to <=50 MB per video. - Given media is captured, when the user enters annotation mode, then they can add arrows, freehand, and text boxes, and annotations are saved as non-destructive overlays. - Given evidence attachments, when the count exceeds 10 per submission, then the app prevents adding more and shows "Max 10 attachments reached". - Given the device is offline, when evidence is captured, then files are stored locally encrypted and marked "Pending Sync".
Enforce Required Evidence for Branch-Triggering Findings
- Given the user selects the "Heavy Soil" toggle that triggers a deep-clean branch, when evidence requirements are configured (min_photos=2), then the Submit action is blocked until at least 2 annotated photos are attached. - Given the user enters a moisture reading >18% that triggers a remediation branch, when the user proceeds to submit, then the app requires at least 1 photo of the moisture meter and 1 photo of the affected area before allowing submission. - Given all required evidence for triggered branches is present, when the user taps Submit, then the submission proceeds without evidence-related errors. - Given evidence is missing for any triggered branch, when the user taps Submit, then the app lists unmet requirements per branch and deep-links the user to add the missing items.
Preview Branch Effects Before Submission
- Given one or more findings trigger branches, when the user taps Preview, then a summary displays within 2 seconds showing tasks to be added/removed, time impact in hours, budget delta in currency, and SLA date/time shift. - Given the user modifies a finding, when the user re-opens the Preview, then the summary reflects the latest changes. - Given the user confirms the Preview and submits, then the resulting workflow exactly matches the previewed tasks, durations, budgets, and SLA changes (tolerance = zero). - Given no branches are triggered, when the user taps Preview, then the app displays "No changes will be applied" and allows submission.
Offline Capture with Reliable Queued Sync
- Given the device has no connectivity, when the user submits findings, then the submission is stored locally with a client-generated UUID, status "Queued", and visible in the Sync Queue. - Given the app is restarted while offline, when it reopens, then queued submissions persist and are visible with unchanged status. - Given connectivity is restored, when the app detects network, then queued submissions auto-sync within 30 seconds using exponential backoff and maintain original UUIDs. - Given the server receives a queued submission, when the same UUID is retried, then the server responds idempotently and no duplicate record is created. - Given a queued submission fails permanently (HTTP 4xx), when sync completes, then the item is marked "Failed" with an actionable error message.
Duplicate Submission Prevention for Same Visit
- Given a vendor submits findings for the same job and visit window, when an identical payload is submitted again within 15 minutes, then the server returns a success response with "Duplicate Ignored" and no new branches are applied. - Given a subsequent submission has any changed field value or additional evidence, when submitted, then it is treated as a new revision and processed normally. - Given the device was offline and queued multiple identical submissions, when they sync, then only the first is processed and the rest are marked "Duplicate" in the app.
Optional GPS Proof-of-Presence at Submission
- Given GPS proof is required for the job, when the user taps Submit, then the app attempts to capture GPS within 10 seconds with accuracy <=50 meters and stores lat/long, accuracy, and UTC timestamp. - Given GPS cannot meet accuracy within 10 seconds, when policy is "strict", then submission is blocked with a prompt to retry; when policy is "lenient", then submission proceeds flagged "GPS Unverified". - Given the user has denied location permission, when submission is attempted for a job requiring GPS, then the app shows a permission prompt and instructions to enable location; submission is blocked until permission is granted or policy override is applied. - Given GPS proof is not required for the job, when the user submits, then no location data is captured or stored.
Audit Trail, Analytics & Cost Impact Reporting
"As an owner-operator, I want analytics on branch usage and impact so that I can optimize playbooks and vendor contracts."
Description

Logs every branch evaluation and trigger with inputs, user/device, timestamps, and resulting workflow, schedule, and cost changes. Surfaces dashboards that compare branched versus baseline playbooks, quantify added time and cost by property and vendor, and highlight high-frequency branches for optimization. Supports CSV export and API access, enforces role-based visibility, and adheres to data retention settings. Enables continuous improvement of playbooks and vendor contracts based on real-world outcomes.

Acceptance Criteria
Branch Evaluation Audit Log Completeness
Given a branch evaluation is triggered by a user or the system When the evaluation result is saved Then an immutable audit event is recorded containing: event_id (UUID), tenant_id, property_id, unit_id (optional), work_order_id, playbook_id, playbook_version, branch_id, branch_name, trigger_type (manual|auto), evaluator_user_id (or system), vendor_id (optional), device_type, ip_address, inputs (key:value), timestamp (UTC ISO 8601), previous_state (workflow/schedule/cost), resulting_state (workflow/schedule/cost), notification_ids (optional) And the audit event is durably persisted before the user sees a success state And the audit event is queryable by event_id within 2 seconds of write And p95 write time is <= 500 ms under 50 concurrent evaluations
Schedule and Cost Change Attribution
Given a branch causes schedule, SLA, or cost adjustments on a work order When the branch is applied Then the audit event captures before/after values for start_at, end_at, SLA_due_at, estimated_cost and currency, and task_list delta (added/removed tasks with quantities) And computed fields schedule_delta_minutes and cost_delta_minor_units are stored And attribution fields include reason_code and source_branch_id And any rollback creates a compensating audit event referencing the prior event_id And cost deltas roll up to the work_order total and equal the sum of task-level deltas within ±1 minor unit rounding And schedule changes propagate to the shared calendar without creating double-bookings
Analytics Dashboard: Branched vs Baseline Comparison
Given a date range and filters (property, vendor, playbook) are selected When the Cost Impact dashboard loads Then it shows for each playbook: count_baseline_runs, count_branched_runs, percent_branched, avg_added_minutes, median_added_minutes, avg_added_cost, median_added_cost And it displays breakdown tables by property and by vendor with the same metrics And a toggle selects the baseline reference (latest published playbook version) And all totals reflect only records within the selected filters And calculations match a verified backend query within 0.5% variance And charts and tables render within 3 seconds for up to 50k work orders
High-Frequency Branch Identification
Given the Top Branches view is opened for a selected window (7/30/90 days) When results are generated Then the list shows the top 10 branches by frequency with columns: branch_name, frequency_count, frequency_percent_of_runs, median_added_minutes, median_added_cost, total_added_cost And branches with frequency_percent_of_runs >= 25% or median_added_cost >= configured_threshold are flagged High Impact And each row links to the associated playbook and a pre-filtered audit log for that branch And results recompute within 5 seconds for up to 100k audit events and match backend aggregates
CSV Export of Audit and Analytics
Given a user with Export Analytics permission and active filters/timezone When they request a CSV export for Audit Events or Cost Impact Then the file includes a header row and UTF-8 rows with RFC 4180 quoting and LF newlines And the export respects filters, timezone selection, and RBAC scope And numeric fields use dot decimal and include currency_code where applicable And exports complete within 60 seconds for up to 100,000 rows; larger exports are chunked with a manifest of part URLs And the filename includes report type, tenant slug, and date range And an audit event is recorded for the export action
API Access to Audit and Analytics
Given a client holds a valid OAuth2 Bearer token with scope analytics:read When it calls GET /api/v1/audit-events or GET /api/v1/analytics/cost-impact with filters (date_from, date_to, property_id, vendor_id, playbook_id, branch_id, work_order_id) and pagination (cursor, limit) Then the API returns 200 with JSON matching the published schema, including total_count (estimated), next_cursor, and data[] And tenant isolation and RBAC are enforced so only authorized data is returned And invalid filters yield 400 with error details; rate limit excess yields 429 with Retry-After And p95 response time is <= 2 seconds for filtered queries up to 10k records
Role-Based Visibility and Data Retention Enforcement
Given tenant roles Admin, Manager, Vendor, and Auditor with property scopes When users access audit logs, dashboards, exports, or API endpoints Then visibility is limited per role: Admin (all tenant data), Manager (assigned properties), Vendor (their work orders only), Auditor (read-only within scope) And out-of-scope access attempts return 403 and are logged And data retention is configurable per tenant (365–2555 days) with a default; events older than the retention period are purged or anonymized nightly And legal hold flags suspend purge for selected properties or work orders And purged items are excluded from UI, API, and exports, with a monthly purge summary report available

Turn Meter

A live countdown to move‑in highlights the critical path, slack by step, and what’s blocking progress. Test accelerators like adding a crew, extending hours, or priority materials and see time saved before you commit—keeping turnovers on track and stakeholders aligned.

Requirements

Live Move‑In Countdown
"As a property manager, I want a live countdown and unified timeline for each unit turn so that I can see exactly what’s left and how delays affect the move‑in date."
Description

Real-time countdown tied to the scheduled move‑in date, aggregating all turnover tasks into a unified timeline and automatically syncing with FixFlow’s shared calendar. It ingests tasks from work orders, punch lists, cleaning, painting, inspection, and key exchange steps, shows task status, dependencies, and percent complete based on vendor SMS confirmations and checklist updates, and refreshes instantly on any change. It supports multiple concurrent turns, is mobile-responsive, and persists state so the countdown continues seamlessly across devices and sessions.

Acceptance Criteria
Real-Time Calendar Sync for Turn Timeline
Given a turn task is created/updated/deleted in the countdown, When the change is saved, Then a corresponding event is created/updated/deleted in FixFlow’s shared calendar within 2 seconds with matching title, unit, start/end times, and assigned vendor. Given a relevant event in FixFlow’s shared calendar is rescheduled, When the update is saved, Then the countdown timeline reflects the new time within 2 seconds and recalculates downstream task timings and dependency indicators. Given two tasks are scheduled to overlap for the same vendor, When the overlap is detected, Then the countdown flags a conflict within 1 second and prevents saving the overlapping assignment unless the user provides an explicit override reason.
Aggregate Tasks From All Turn Sources Into Unified Timeline
Given a turn includes tasks from work orders, punch lists, cleaning, painting, inspection, and key exchange, When the timeline loads, Then all tasks from these sources appear in a single ordered view with their source labels and no duplicates. Given a new task is added to any source linked to the turn, When it is saved, Then it appears in the unified timeline within 5 seconds with correct default status and dependencies applied. Given a task is cancelled or completed at its source, When the source status changes, Then the timeline updates the task to Cancelled or Complete within 5 seconds without removing historical timestamps.
Vendor SMS and Checklist Updates Drive Task Status and Percent Complete
Given a task is assigned to a vendor with SMS updates enabled, When the vendor sends a completion confirmation via SMS linked to the task, Then the task status changes to Complete and the turn’s percent complete is recalculated within 5 seconds. Given a task has an associated checklist, When an item is checked or unchecked, Then the task’s progress updates immediately and the aggregate percent complete recalculates within 2 seconds. Given an SMS is received from an unrecognized number or without a valid task reference, When processing the message, Then no task state changes occur and the message is logged for review.
Instant Live Refresh Across Users on Any Change
Given multiple users are viewing the same turn timeline, When any user updates a task’s status, schedule, or dependency, Then all other viewers see the change reflected within 2 seconds without page reload. Given a user temporarily loses network connectivity, When the connection is restored, Then the countdown resynchronizes within 3 seconds and reflects all missed changes in correct chronological order. Given a change cannot be applied due to a version conflict, When the conflict is detected, Then the user is prompted to refresh or merge, and the timeline remains consistent with server state.
Concurrent Turns: Isolation, Switching, and Performance
Given 3 or more active turns exist, When the user switches between turns, Then each timeline renders the correct unit-specific tasks with no cross-contamination in under 1.5 seconds per switch. Given the user filters by property, building, or vendor, When the filter is applied, Then only matching turns/tasks are shown and counts (tasks total, complete, remaining) update accurately within 1 second. Given background updates occur on multiple turns, When the user opens any turn, Then the timeline reflects the latest state within 2 seconds with no stale data from other turns.
Mobile Responsiveness and Cross-Device State Persistence
Given the user accesses the countdown on a mobile device (≥375px width), When the timeline loads, Then tasks are readable without horizontal scrolling beyond the timeline area and interactive controls are reachable with a single thumb. Given the user adjusts view state (selected turn, zoom level, filters), When they sign out and sign in on another device, Then the same state is restored and the countdown continues from the current time without manual reload. Given the device is rotated between portrait and landscape, When orientation changes, Then the timeline reflows within 500 ms without losing scroll position or selection.
Critical Path & Slack Metrics
"As an operations lead, I want to see the critical path and slack for each task so that I can focus resources on the steps that determine the move‑in date."
Description

Automated calculation of the critical path for each unit turn with per-task slack (float) visualization. Tasks on the critical path are highlighted, with numeric slack values shown for non-critical tasks, and the timeline re-computes whenever durations, dependencies, or confirmations change. Supports common dependency types (finish‑to‑start, start‑to‑start), allows manual overrides of durations, and provides reusable turn templates by unit type to standardize baselines across properties.

Acceptance Criteria
Compute and Highlight Critical Path on Turn Timeline
Given a unit turn with defined tasks, durations, and dependencies When the schedule is computed Then the system calculates the critical path using current durations and dependencies And all tasks with total float = 0 are marked with a "Critical" indicator and distinct highlight in the timeline And non-critical tasks are not highlighted as critical
Display Numeric Slack for Non-Critical Tasks
Given the schedule is computed When viewing any non-critical task in the timeline Then the task displays its total float as a numeric slack value in hours with one decimal place (e.g., "Slack: 6.5 h") And the displayed slack equals the CPM-calculated total float for that task And critical tasks do not display a slack chip
Support Finish-to-Start and Start-to-Start Dependencies
Given Task A (duration 8 h) and Task B (duration 4 h) with a finish-to-start dependency When the schedule is computed Then Task B's earliest start equals Task A's finish Given Task C (duration 6 h) and Task D (duration 2 h) with a start-to-start dependency When the schedule is computed Then Task D's earliest start equals Task C's start And the critical path and slack values reflect these dependency types
Auto-Recompute Timeline on Edit to Durations, Dependencies, or Confirmations
Given a computed schedule is displayed When a user edits and saves a task duration Then the system immediately recomputes the schedule and refreshes critical path and slack values When a dependency type or predecessor is changed and saved Then the system immediately recomputes the schedule and refreshes critical path and slack values When a vendor start or completion confirmation updates a task's actuals Then the system immediately recomputes the remaining schedule and updates critical path and slack values
Manual Duration Overrides with Propagation and Reversion
Given a task with a baseline duration from a template When a user sets a manual override duration and saves Then the override value is used in CPM calculations and the task is labeled "Overridden" in its details And the schedule, critical path, and slack values update accordingly When the user selects Revert to Baseline Then the baseline duration is restored and the schedule recalculates
Create and Apply Reusable Turn Templates by Unit Type
Given a turn template exists for unit type "1BR" with tasks, durations, and dependencies When a user creates a new turn for a "1BR" unit and applies the template Then all template tasks, durations, and dependencies are copied to the new turn And the schedule can be computed immediately to produce critical path and slack values
Critical Path and Slack Visible in Timeline and Task Details
Given a computed schedule is displayed When viewing the timeline Then critical tasks are highlighted and non-critical tasks display numeric slack values When opening a task's details panel Then the task's critical status and numeric slack value are shown
Blocker Detection & Alerts
"As a maintenance coordinator, I want blockers to be detected and alerted automatically so that I can resolve issues before they slip the schedule."
Description

Automated identification of blockers that threaten the timeline, including unconfirmed vendor appointments, material backorders, access issues, and failed inspections. The system surfaces a "What’s Blocking" panel, raises in-app, email, and SMS alerts based on configurable thresholds (e.g., no confirmation 24 hours prior), and suggests next actions such as reschedule, assign backup vendor, or request rush materials. Blocking items link directly to the underlying work order, message thread, or purchase item for one-click resolution.

Acceptance Criteria
Unconfirmed Vendor Appointment Triggers 24‑Hour Blocker Alert
Given a vendor appointment is Scheduled with confirmation_status = Pending and the confirmation threshold is set to 24 hours When the current time reaches 24 hours before the scheduled start and no confirmation has been received Then the system flags a Blocker of type "Unconfirmed Appointment" tied to the appointment and its parent work order And displays the blocker in the "What’s Blocking" panel within 5 seconds of detection And sends in-app, email, and SMS alerts to the task owner and Turn Manager within 60 seconds And each alert contains a deep link that opens the related work order and vendor message thread in one click And the blocker shows suggested actions: "Send Reminder", "Reschedule", and "Assign Backup Vendor", and each action opens the correct flow
Material Backorder Detection and Alert with Rush Option
Given a purchase item linked to a turn task is marked Backordered or its ETA shifts such that the task’s earliest start would delay the move‑in date When the backorder status or ETA update is received by the system Then the system flags a Blocker of type "Material Backorder" and calculates time-at-risk (in hours) on the critical path And the blocker appears in the "What’s Blocking" panel with type, affected task, and time-at-risk And in-app, email, and SMS alerts are sent to the task owner and purchasing contact within 60 seconds, each including a deep link to the purchase item And suggested actions "Request Rush Materials" and "Select Alternate SKU" are presented and open the corresponding workflows And clearing the backorder status or selecting an alternate SKU auto-resolves the blocker within 60 seconds
Access Issue Detected Before Scheduled Work
Given a task requires property access and no valid access method (e.g., lockbox/fob code or tenant approval) is recorded by the configured threshold (default 12 hours) before the vendor start time When the threshold is reached without valid access in the system Then a Blocker of type "Access Issue" is created and shown in the "What’s Blocking" panel And in-app, email, and SMS alerts are sent to the task owner within 60 seconds, including a deep link to the access request message composer and the work order And suggested actions "Request Access", "Contact Tenant", and "Update Lockbox Code" are available and open the correct flows And entering a valid access method removes the blocker from the panel automatically within 60 seconds
Failed Inspection Generates Immediate Blocker and Next Steps
Given an inspection task is recorded with outcome = Failed When the inspection result is saved Then the system immediately creates a Blocker of type "Failed Inspection" and surfaces it at the top of the "What’s Blocking" panel And in-app, email, and SMS alerts are dispatched to the task owner and turn stakeholders within 60 seconds, with a deep link to the inspection report And suggested actions "Create Rework Work Order" (pre-filled from the failure items) and "Reschedule Inspection" are presented and open the correct flows And when the rework order is marked Complete and a subsequent inspection is recorded as Passed, the blocker auto-resolves and an all-clear in-app notification is posted
What’s Blocking Panel: Accurate List, Sorting, and One‑Click Resolution
Given one or more blockers exist for an active turn When a user opens the Turn Meter Then the "What’s Blocking" panel lists all current blockers sorted by time-at-risk (hours) descending And each item displays blocker type, affected step/task, time-at-risk, triggered threshold, owner, and last update timestamp And each item provides one-click actions relevant to the blocker type And clicking the item or the deep link opens the related work order, message thread, inspection, or purchase item within 2 seconds And the panel loads within 2 seconds with up to 50 blockers on a standard network (p50)
Configurable Thresholds and Notification Preferences Drive Alerts
Given an admin configures custom thresholds per blocker type (e.g., vendor confirmation 12 hours, access 8 hours) and per-channel notification preferences When the configuration is saved Then the system persists the settings and applies them to detection and alerting within 5 minutes And alerts only fire via the channels enabled for each recipient while always displaying in-app And changing the vendor confirmation threshold from 24 hours to 12 hours prevents a 24-hour alert and triggers at 12 hours if still unconfirmed And a Reset to Defaults action restores system defaults and they take effect within 5 minutes
Accelerator What‑If Simulator
"As a property manager, I want to test accelerators and see predicted time savings so that I can choose the most effective actions to keep the turn on track."
Description

Interactive scenario testing that estimates time saved before committing changes. Users can model accelerators such as adding a crew, extending work hours, resequencing non-dependent tasks, or selecting priority materials. The simulator runs against current dependencies and vendor capacity, shows the projected move‑in date impact and cost trade‑offs, and allows one-click apply to convert the scenario into scheduled changes while preserving a baseline for comparison and rollback.

Acceptance Criteria
Add Crew Accelerator Simulation
Given a turnover project with scheduled tasks and configured crew productivity rates When the user selects an eligible task/phase and adds one additional crew of the required trade in the simulator Then the simulator recalculates the task duration using productivity rules and vendor capacity, updates the full schedule, and displays the projected move‑in date delta in hours/days And the cost trade‑off panel displays incremental labor cost and any fixed mobilization costs based on rate cards And if capacity for the added crew is unavailable on the targeted dates, the simulator proposes the earliest available slot and flags a constraint warning And the recalculation completes in ≤2 seconds for projects with ≤200 tasks And no live schedule is changed until the user applies the scenario
Extend Work Hours Accelerator Simulation
Given vendor work hour thresholds and overtime rates are configured When the user extends daily work hours for selected tasks/vendors (e.g., 8→10 hrs) in the simulator Then the simulator compresses affected task durations where allowed, recomputes dependencies, and shows the move‑in date delta And the cost trade‑off panel includes overtime premiums after configured thresholds And tasks marked "cannot extend hours" remain unchanged and are listed with reasons And recalculation completes ≤2 seconds for ≤200 tasks
Resequence Non‑Dependent Tasks Simulation
Given a set of tasks with no hard dependencies between them When the user resequences those tasks in the simulator via drag‑drop or set order Then the simulator permits only moves that do not violate dependencies or vendor capacity And any attempted illegal move is blocked with an inline message naming the blocking predecessor/capacity And the new sequence updates the critical path and step‑level slack indicators And move‑in date delta is displayed And recalculation completes ≤2 seconds for ≤200 tasks
Priority Materials Lead‑Time Reduction Simulation
Given tasks with material lead‑time attributes and rush/priority options with associated costs and lead‑time reductions When the user selects "priority materials" for applicable tasks in the simulator Then the simulator reduces lead‑times per configuration, updates start dates, and recomputes the schedule And the move‑in date delta and incremental material/shipping cost are displayed And tasks lacking lead‑time data are highlighted and excluded with a data‑needed flag And recalculation completes ≤2 seconds for ≤200 tasks
Constraint Validation Across Combined Accelerators
Given the user has applied multiple accelerators in one scenario (e.g., add crew + extend hours + resequence) When the simulator runs the schedule Then no resulting schedule violates dependency ordering, resource capacity, or calendar blackout constraints And any conflicts are enumerated with task IDs, constraint type, and suggested resolution, and the scenario is marked invalid until addressed And the simulator prevents Apply Scenario while conflicts remain And the run completes ≤3 seconds for ≤200 tasks
Projected Move‑In Impact and Cost Trade‑Offs Display
Given the baseline schedule and current scenario calculations When the simulator finishes recalculating Then the UI shows the projected move‑in date/time, the delta vs baseline (positive/negative), and which path is critical And a breakdown panel lists top 10 tasks contributing to the delta with their individual time impact and cost deltas And totals for incremental cost (labor + materials) are shown with currency and source breakdown And the user can toggle between baseline and scenario views without leaving the simulator
One‑Click Apply, Baseline Preservation, and Rollback
Given a valid scenario with zero blocking conflicts and the user has permission to apply changes When the user clicks Apply Scenario Then the system creates an immutable baseline snapshot with timestamp and scenario name And commits the simulated changes atomically to the live schedule and recalculates the Turn Meter countdown And an audit log entry records who applied what, when, and the move‑in and cost deltas And the operation completes in ≤3 seconds and confirms success to the user And the user can perform a one‑click Rollback that restores the prior baseline within ≤3 seconds and logs the action
Vendor Capacity‑Aware Scheduling
"As a scheduler, I want the timeline to reflect real vendor capacity so that the plan is achievable and doesn’t cause double‑bookings."
Description

Scheduling that accounts for vendor availability, crew capacity, work hours, and travel buffers by integrating with FixFlow’s shared calendar and SMS confirmations. The system prevents double-bookings, suggests feasible time slots, validates scenario changes against real capacity, and writes back accepted changes to the master schedule. It supports preferred vendor tiers and backup assignments when primary vendors are unavailable.

Acceptance Criteria
Prevent Double-Booking Across Shared Calendar
- Given a vendor with N crews and confirmed jobs on the master calendar, When a user attempts to book a job that causes more than N concurrent assignments, Then the system blocks the save and displays the conflicting jobs, times, and required crew count. - Given an existing confirmed job for a vendor crew, When a new job overlaps in time for the same crew, Then the system prevents overlap and requires selecting a different crew or time before saving.
Feasible Slot Suggestions with Capacity and Travel Buffers
- Given job duration, vendor crew capacity, current calendars, travel buffers, and vendor work hours, When a user requests available slots for a date range, Then the system returns at least 3 feasible start times within the next 14 days that satisfy all constraints, computed in ≤2 seconds for portfolios up to 200 units. - Given no feasible slots in the requested window, When the search completes, Then the system returns zero results with reason codes (e.g., capacity_exhausted, outside_work_hours) and the earliest feasible date/time.
Validate Scenario Changes Against Real Capacity
- Given a what-if change (e.g., add 1 crew, extend work hours by X, priority materials), When the user runs a simulation, Then the system recalculates the schedule, shows projected move-in date delta and critical path changes, and does not modify the master schedule. - Given the user chooses to commit a simulated change, When real-world capacity constraints are violated (e.g., added crew not available, overtime not permitted), Then the system blocks commit and lists specific conflicts and affected jobs. - Given the simulated change is feasible, When the user commits, Then the master schedule updates atomically and impacted jobs reflect new times/assignments without partial saves.
SMS Confirmation Workflow and Schedule Write-Back
- Given a tentative assignment is created, When the vendor accepts via SMS within 15 minutes, Then the job is marked Confirmed, the crew/time are written to the master schedule, and the shared calendar reflects the booking. - Given a vendor declines or does not respond within 15 minutes, When the timeout elapses, Then the tentative hold is released and the next ranked backup vendor is auto-notified via SMS with the same details. - Given any SMS response, When it is received, Then the system logs the response, timestamp, and actor, and updates job status accordingly.
Preferred Vendor Tiers and Backup Assignment
- Given tiered vendor preferences for a trade and property, When auto-assigning a job, Then Tier 1 vendors are evaluated first; only if none are feasible does the system evaluate Tier 2, continuing tier-by-tier until a feasible assignment is found. - Given no vendors are feasible across all tiers, When auto-assign attempts complete, Then the system posts a no-capacity alert with reason codes and offers to queue the job to auto-book when capacity opens with notification to the requester. - Given a backup vendor is engaged, When the primary confirms before the backup, Then the system cancels the backup request and prevents double-booking across tiers.
Travel Buffers and Work Hour Compliance
- Given sequential jobs for the same crew at different locations, When calculating schedules, Then the minimum travel buffer based on configured rules and distance is inserted and cannot be reduced below the minimum. - Given travel buffers push a job beyond vendor work hours, When the user attempts to save, Then the system either proposes the next valid slot within work hours or requires explicit confirmation to schedule as overtime, tagging the job accordingly.
Audit Trail and Conflict Resolution Logging
- Given any scheduling action (create, edit, cancel, simulate, commit), When the action completes, Then the system records actor, timestamp, inputs (capacity snapshot, buffers, work hours), outcome (accepted/rejected), and reason codes in an immutable audit log. - Given a scheduling conflict is resolved (e.g., vendor swap, time shift), When the resolution is saved, Then the prior conflict record is marked resolved and linked to the new schedule entry for traceability.
Stakeholder Updates & Views
"As an owner and tenant stakeholder, I want clear, timely updates on readiness and risks so that I can plan confidently and avoid last‑minute surprises."
Description

Role-based views and notifications tailored to tenants, owners, and vendors. Tenants see a move‑in ETA window and readiness status; owners see risks, critical path tasks, and confidence levels; vendors see their assignments, deadlines, and dependencies. The system issues threshold-based updates when the countdown shifts (e.g., >12 hours) and provides a shareable, read-only link with selective redaction to protect sensitive information.

Acceptance Criteria
Tenant ETA and Readiness View
Given an authenticated tenant for a unit with an active turnover When the tenant opens the Turn Meter page Then the page displays a move-in ETA window with start and end times in the tenant's local timezone and a readiness status in ["Not Started","In Progress","At Risk","Ready"] And no owner or vendor PII, costs, or other tenants' data are visible And changes in backend state are reflected in the tenant view within 60 seconds Given no computed ETA exists When the tenant opens the page Then the ETA shows "Estimating..." and readiness shows "Not Started"
Owner Risk and Critical Path Overview
Given an authenticated owner with access to the property/unit When viewing the Turn Meter Then the owner sees a list of critical path tasks each showing: name, status, planned start/finish, slack (hours), and risk level in ["Low","Medium","High"] And a move-in confidence percentage (0–100%) with a "last updated" timestamp in the owner's local timezone And tenant PII (name, phone, email) is hidden or masked Given any critical path task has slack < 0 hours or risk = "High" When the owner view loads Then the unit is flagged "At Risk" and the confidence displayed is <= 70%
Vendor Assignments and Dependencies
Given an authenticated vendor assigned to one or more tasks When viewing the Turn Meter Then the vendor sees only their assignments with: task name, property/unit, start-by, due-by, and dependencies (names/status) And a "Blocked" badge is shown if any dependency is incomplete And timestamps are displayed in the vendor's local timezone Given a dependency transitions to complete When the vendor refreshes or receives a push update Then the "Blocked" badge clears and the task is marked actionable within 60 seconds Given a vendor attempts to access a task not assigned to them When requesting the page or API Then the system returns 403 and no unassigned task data is exposed
Countdown Shift Notification Threshold
Given a stakeholder (tenant, owner, vendor) is associated to the unit and has at least one enabled contact channel (email or SMS) and a last-notified ETA exists When the calculated move-in ETA changes by an absolute delta >= 12 hours (earlier or later) Then a notification is sent within 5 minutes including: previous ETA window, new ETA window, signed delta in hours, and current readiness/risk summary, formatted in the recipient's local timezone Given multiple ETA recalculations occur within a 60-minute window When at least one meets the >= 12-hour threshold Then only one notification is sent summarizing the latest change within that window Given the ETA delta is < 12 hours When recalculated Then no threshold notification is sent
Role-Tailored Notification Content
Given a threshold notification is triggered When the recipient is a tenant Then the message contains only: new ETA window, previous ETA window, delta, readiness status, and a link to the tenant view, with no owner/vendor details Given a threshold notification is triggered When the recipient is an owner Then the message contains: new/previous ETA windows, delta, confidence %, top 3 risk drivers (task names), and a link to the owner view, with tenant PII masked Given a threshold notification is triggered When the recipient is a vendor Then the message contains: impacted assignments, updated due-by times, dependency changes, and a link to the vendor view, with other vendors' data omitted
Shareable Read-Only Link With Selective Redaction
Given a user with sharing permission When generating a shareable link and selecting a role template (Tenant, Owner, Vendor, or Custom) and an expiry between 1 and 30 days (default 7) Then the system creates a tokenized URL that renders the corresponding read-only view And redactions are applied: Tenant link hides owner identities and costs; Owner link masks tenant PII; Vendor link shows only the generating vendor's assignments and hides costs and other vendors Given a viewer accesses a shareable link When attempting to perform any write action via UI or API Then the action is disabled and returns 403, and no state changes occur Given a shareable link is revoked by the creator or has expired When a viewer attempts access Then the system returns 410 Gone and records an audit entry with timestamp, IP, user-agent, and view type

PauseGuard

Fair, audit‑ready SLA pauses when a delay is outside your control—waiting on tenant access, HOA approvals, parts on order, weather holds. Trigger pauses via quick-select reasons or auto-detect from events (tenant “not home,” parts PO created). Timers auto‑resume when conditions clear, preserving compliance while reducing anxiety about unfair breaches.

Requirements

Pause Reason Catalog & Policy Configuration
"As a property manager, I want a standardized, configurable set of pause reasons and rules so that SLA pauses are applied consistently and compliantly across all properties."
Description

Provide an account-configurable catalog of SLA pause reasons (e.g., Tenant Not Home, Waiting on Parts, Weather Hold, HOA Approval) with mappings to SLA clocks. Admins can enable/disable reasons, set evidence requirements, define approval thresholds, cap maximum pause durations, and configure whether business hours apply. Include localization, reason codes for exports, default policies per property/portfolio, and API endpoints for CRUD. Ensure backward compatibility and migration for existing work orders. All changes are versioned and audit-logged.

Acceptance Criteria
Create/Edit Pause Reason with Localization and Reason Code
Given I am an Admin on account A and the catalog is empty When I create a pause reason with code "TENANT_NOT_HOME", names/descriptions in en-US and es-ES, and set status Enabled Then the reason is saved with an immutable code, localized labels for en-US and es-ES, default locale en-US, and status Enabled Given a reason with code "TENANT_NOT_HOME" exists When I attempt to create another reason with the same code Then the system rejects the request with a uniqueness error and no new record is created Given a reason exists with translations in en-US only When a user requests the reason list in locale fr-FR Then the system returns the en-US fallback label/description with a locale indicator of fallback Given a reason is updated to Disabled When a user attempts to select it for a new pause Then it is not available for selection, but remains visible in historical records Given any create or update to a reason When the action completes Then a new catalog version is created and an audit log records actor, timestamp, and before/after values including reason code
Map Pause Reason to SLA Clocks and Business-Hour Policy
Given a reason "WEATHER_HOLD" When I configure it to pause Response and Resolution clocks with Business Hours = true Then the policy is saved and returned by GET /pause-reasons including clocks ["response","resolution"] and businessHours true Given the same reason has Max Pause Cap = 72 hours When a user attempts to extend a pause beyond 72 cumulative hours Then the system blocks the extension and returns a clear validation error indicating cap exceeded Given the reason policy is updated When the update is saved Then a new policy version is created and an audit log entry is recorded with the changed fields
Evidence Requirements Configuration and Enforcement
Given reason "HOA_APPROVAL" requires evidence types [attachment, note] When a user initiates a pause with only a note Then the system rejects the pause and indicates "attachment required" Given reason "HOA_APPROVAL" requires [attachment, note] When a user provides both and submits Then the pause is accepted, evidence is linked to the pause record, and appears in exports with reference identifiers Given reason "WAITING_ON_PARTS" requires externalReference "PO_NUMBER" When a pause is created via API without externalReference Then the API responds 422 with machine-readable error codes describing the missing field Given evidence is provided When the pause is created Then audit log includes evidence metadata (types, filenames/ids) and the evidence is immutable except via new versioned updates
Approval Thresholds and Workflow
Given reason "TENANT_NOT_HOME" has Auto-Approve <= 4 hours and Manager Approval > 4 hours When a user requests a 3-hour pause Then the pause auto-approves and SLA clocks are paused immediately Given the same configuration When a user requests an 8-hour pause Then the pause enters Pending Approval, SLA clocks are not paused until approved, and approvers are notified Given a pending pause exists When an approver approves it Then the SLA clocks pause starting at the approval timestamp, and the decision is audit-logged with approver identity and comment Given a pending pause exists When an approver rejects it Then the pause is denied, clocks remain running, and the requester is notified with rejection reason Given two approvers act on the same pending request concurrently When both submissions are received Then exactly one outcome is persisted using optimistic concurrency and the loser receives a conflict response
Default Policies per Property/Portfolio with Inheritance and Overrides
Given a portfolio default policy P and a property override policy P' When a work order is created for that property Then the effective policy on the work order is P' Given only a portfolio default policy P exists When a work order is created for any property in the portfolio Then the effective policy is P Given the effective policy is changed after a work order is created When new pauses are created on that work order Then they use the policy version effective at the time of pause creation and do not retroactively change historical pauses Given a UI/API request for a work order When retrieving policy details Then the effective policy and version are returned in the response and displayed in the UI
API Endpoints for Catalog and Policy CRUD with RBAC and Idempotency
Given Admin role with token T When I call POST /pause-reasons with Idempotency-Key K Then the reason is created once and subsequent identical requests with K return without creating duplicates Given a non-admin user When they call POST/PUT/DELETE catalog or policy endpoints Then the API returns 403 Forbidden Given many reasons exist When I call GET /pause-reasons?status=enabled&locale=es-ES&page=2&pageSize=50 Then I receive a paginated, filterable, localized list with total count metadata Given an update includes an outdated ETag/version When PUT is attempted Then the API returns 409 Conflict requiring the client to retry with the latest version Given export/report generation via API When data is fetched Then reason codes, localized names, and policy versions are included and stable across locales Given the OpenAPI specification endpoint When I retrieve it Then endpoints, schemas, enums, and error codes for pause catalog/policy are documented and up to date
Migration and Backward Compatibility for Existing Work Orders
Given existing work orders with historical pauses prior to feature release When the feature is enabled Then all historical data remains unchanged and SLA computations remain identical Given historical pauses without mapped reasons When migration runs Then they are assigned to a system "LEGACY" reason code that is Disabled for new use, preserving history Given a previously used reason is later Disabled or renamed When viewing historical records and exports Then the original reason code and labels at time-of-use are shown for those records Given audit logging is enabled When migration executes Then each migrated item records an audit entry with migration id, actor "system", timestamp, and aggregate counts are reported for validation
Manual Pause Trigger with Evidence Capture
"As a maintenance coordinator, I want to manually pause a work order with a reason and attach proof so that the SLA clock stops when delays are outside our control."
Description

Allow authorized users to pause a work order from the dashboard or mobile with a quick-select reason, optional sub-reason, and required context (notes). Support evidence attachments (photos, PDFs, call logs, SMS transcript references) and structured fields (contact attempted, next step, expected clear date). Capture precise timestamps, actor identity, and source device. Enforce guardrails (no excessive backdating, mandatory fields per reason, time zone safe). Provide undo/override with justification and audit logging. Resume can be manual or linked to a preselected clearing condition.

Acceptance Criteria
Dashboard Manual Pause with Required Fields
Given an authorized user is viewing an in-progress work order on the web dashboard and the SLA timer is running When the user clicks "Pause SLA," selects a Reason from the configured list, optionally selects a Sub-reason, enters Notes of at least 10 characters, sets Contact Attempted (Yes/No), provides Next Step (<=140 chars), and sets Expected Clear Date-Time >= now if required by the Reason Then the Save action is enabled and, on Save, a pause record is created with timestamps captured server-side in UTC to millisecond precision, the actor’s user ID, and source device "web" And the SLA timer stops within 1 second, the work order visibly shows "SLA Paused" with the selected Reason, and the pause is added to the activity timeline And reason-specific mandatory fields are enforced (e.g., "Tenant Not Home" requires Notes and Contact Attempted; "Parts on Order" requires Expected Clear Date), with inline validation messages
Attachment Evidence Capture and Linking
Given the pause form is open When the user attaches up to 10 files (jpg, jpeg, png, heic, pdf) totaling <= 50 MB and optionally adds a call log file or links an SMS thread by Conversation ID Then all successfully uploaded attachments are linked to the pause record and previewable; failed uploads display an inline error without clearing other form inputs And each attachment records filename, MIME type, size, uploader ID, and upload timestamp; SMS links record Conversation ID and message timestamp range And attachments are visible on both web and mobile, and can be downloaded by authorized users
Backdating Guardrails and Time Zone Safety
Given the user sets a Pause Start time on the form When the Pause Start is more than 60 minutes in the past and the user lacks "SLA Override" permission Then the form blocks submission and displays "Backdating beyond 60 minutes is not allowed" And when a permitted override is attempted with "SLA Override" permission and a justification >= 10 characters is provided Then the backdated pause is accepted and the justification is stored in the audit log And all captured timestamps are stored in UTC and presented in the user’s local timezone, with computed pause duration matching UTC within ±1 second, including across DST changes And if the client device clock differs from server by > 5 minutes, server time is used and a non-blocking warning is shown
Undo/Override with Justification and Audit Trail
Given a pause record exists When the creator clicks "Undo Pause" within 15 minutes of creation Then the pause is removed, the SLA timer is recalculated as if the pause never occurred, and the undo action is logged with actor, timestamp, and source device And when more than 15 minutes have elapsed, only users with "SLA Override" permission can undo or edit the pause after entering a justification >= 10 characters Then the prior and new values (reason, sub-reason, notes, timestamps) are preserved in an immutable audit trail with before/after diffs and a unique audit entry ID
Resume via Manual Action or Preselected Clearing Condition
Given a new pause is being created When the user selects an optional Clearing Condition (e.g., Tenant confirms access, Parts received, HOA approved, Weather clear) Then the condition is saved with an event binding reference And when the bound event is detected (e.g., tenant confirms access via SMS, parts PO marked received) or a user clicks "Resume SLA" Then the SLA timer resumes within 60 seconds, a resume entry is added to the activity timeline with actor/event reference, and total paused duration updates accurately And if the Expected Clear Date elapses without the condition being met Then the SLA remains paused and the pause record notes the missed expectation (no auto-resume)
Role-Based Access and Source Device Attribution
Given role permissions are configured When a user without "Pause SLA" permission views a work order Then the Pause/Resume controls are hidden and API attempts return 403 Forbidden And when an authorized user pauses or resumes from web or mobile apps Then the action succeeds and the record includes actor ID, role, and source device labeled as "web", "mobile-ios", or "mobile-android" And all unauthorized or unauthenticated requests to create, edit, undo, or resume a pause are rejected with 401/403 and logged for security auditing
Auto-Pause from System Events
"As a small property manager, I want pauses to be auto-applied from real events so that I don’t have to micromanage timers and still remain SLA-compliant."
Description

Introduce a rules engine that auto-creates pauses when specific events are detected: tenant replies “not home” via SMS, parts purchase order is created or vendor status set to On Order, severe weather alerts for the job location, or HOA approval workflow opened. Map each event to a configured pause reason and clearing condition. Include deduplication, rate limiting, suppression windows, and confidence thresholds to avoid false positives. Allow per-account toggles and per-property overrides. Provide admin review queue for auto-pauses, with one-click confirm/adjust. All automations are fully auditable and reversible with proper permissions.

Acceptance Criteria
Auto-pause on tenant 'Not Home' SMS
Given PauseGuard is enabled at the account level and not disabled for the property And a work order has an active or upcoming appointment And the SMS classifier labels a tenant reply as 'tenant_not_home' with confidence >= 0.80 When the tenant replies indicating they are not home on the job thread Then create a Pause with reason 'Awaiting Tenant Access' starting at the message timestamp And link the pause to the triggering message ID and rule ID And do not create a new pause if an 'Awaiting Tenant Access' pause exists within the last 12 hours for the same work order (append an audit event instead) And auto-resume the SLA clock when the next appointment is set to 'Confirmed' for the work order or a vendor on-site check-in is recorded And write an auditable log containing event source, rule, confidence, timestamps, and actor
Auto-pause on Parts PO created or vendor status 'On Order'
Given a work order has a parts purchase order created or a vendor status updated to 'On Order' And the rule-to-reason mapping is configured as 'Parts on Order' with clearing condition 'Parts Received or status not On Order' When either event occurs Then create a single Pause with reason 'Parts on Order' starting at the earliest triggering event time And if both events occur within 10 minutes, deduplicate into one pause and record both sources in the audit trail And auto-resume when the PO is marked 'Received' or vendor status transitions away from 'On Order' And include PO ID and vendor ID in the audit record
Auto-pause on severe weather alert at job location
Given the work order has a geocoded service address and weather auto-pause is enabled And a weather provider issues an active alert of severity 'Warning' or higher that overlaps the scheduled work window within the next 24 hours for that location When the alert is received Then create a Pause with reason 'Weather Hold' starting at the alert effective time And auto-resume when the alert is canceled or expires, with a 60-minute debounce to prevent thrashing on rapid alert updates And write an audit record including provider, alert ID, severity, effective and expiry timestamps
Auto-pause when HOA approval workflow opens
Given the property requires HOA approval and the HOA approval workflow is initiated for the work order When the workflow status moves to 'Pending' Then create a Pause with reason 'Awaiting HOA Approval' starting at the status change timestamp And auto-resume when the workflow status is 'Approved' or 'Rejected' And if a property-level override disables HOA pauses, suppress creation and log a suppression audit entry
Admin review queue for auto-pauses with one-click actions
Given an auto-pause is created by a rule When the review queue loads Then the auto-pause appears within 5 seconds with fields: work order, reason, start time, suggested clearing condition, source event, and confidence (if applicable) And an authorized user (role 'Operations Manager' or higher) can one-click Confirm, Adjust (reason/start/end), or Reject And Confirm preserves the pause and marks review status 'Approved' And Adjust updates the pause values and writes before/after values to the audit trail And Reject removes the pause, resumes the SLA clock from the original start time, and writes a reversal audit entry
Per-account toggles and per-property overrides honored
Given per-account toggles exist for each auto-pause rule and per-property overrides can change mappings or disable a rule When a rule is disabled at the account level Then no auto-pause is created for that rule and a suppression audit entry is recorded And when a property-level override remaps a rule to a different reason or clearing condition, the override is applied and recorded in the audit And rule enablement/override checks occur before pause creation
Deduplication, rate limiting, and suppression windows across sources
Given deduplication and rate limiting are configured When multiple events for the same work order map to the same pause reason within a 12-hour suppression window Then keep a single active pause and record subsequent events as aggregated in the audit trail And do not create more than 3 auto-pauses for any single work order within a 24-hour rolling period across all reasons And treat semantically equivalent sources (e.g., 'tenant_not_home' SMS and missed vendor check-in within 15 minutes) as duplicates when configured to map to the same reason
Pause Timer Orchestration & Auto-Resume
"As an operations lead, I want the SLA timer to stop and resume automatically based on real conditions so that reported compliance accurately reflects what’s in our control."
Description

Implement a robust timing engine that suspends SLA clocks during active pauses and resumes them when clearing conditions are met (e.g., part delivered, tenant confirms access, HOA approval received, weather clears). Support multiple non-overlapping pauses per work order, stack normalization, and idempotent start/stop operations. Respect business hours and holiday calendars where configured. Handle edge cases (retroactive corrections with audit, DST/time zone consistency, vendor handoffs). Recalculate adjusted SLA metrics in real time and surface conflicts if a pause would violate policy caps or require approval.

Acceptance Criteria
Auto-Resume on Clearing Events
Given a work order with an active SLA pause for a supported reason (awaiting tenant access, HOA approval, parts on order, weather hold) When the corresponding clearing event is ingested (tenant confirms access via SMS/portal, HOA status changes to Approved, all required part POs are marked Delivered, weather feed indicates Clear per policy) Then the SLA timer resumes within 5 seconds of event ingestion And the pause segment end timestamp equals the clearing event timestamp normalized to the property time zone And an audit log entry records the clearing event source, correlation ID, and user/system actor And repeated delivery of the same clearing event does not cause multiple resumes or duplicate audit entries (idempotent)
Multiple Pauses Non-Overlapping & Stack Normalization
Given a work order with one or more pause intervals When a new pause is created that would overlap an existing active pause Then the request is rejected with HTTP 409 and no new interval is created And total paused duration equals the measure of the union of all intervals (no double-counting) And adjacent intervals of the same reason with no gap are merged into a single segment during normalization And intervals are stored in chronological order with start < end and no overlaps after normalization And ending a pause when none is active is a no-op that returns 200 and does not alter stored intervals (idempotent stop)
Idempotent Pause Start/Stop Operations
Given a Pause Start API request carrying X-Idempotency-Key K for work order W and reason R When the exact same request (same W,R,timestamps,K) is retried within 24 hours Then only one pause segment is created and the same 2xx response with the same resource ID is returned And mismatched retries using the same K but different parameters are rejected with HTTP 422 and no state change Given a Pause Stop API request with X-Idempotency-Key K2 for the active pause When it is retried Then only one stop is applied and the pause end timestamp remains unchanged And duplicate external events/webhooks map to correlation IDs and do not create duplicate segments
Business Hours, Holidays, and Time Zone/DST Consistency
Given a property-level business hours calendar and holiday list When adjusted SLA remaining is computed Then time outside defined business hours and on holidays is excluded from the SLA clock And all timestamps are stored in UTC and rendered using the property time zone And DST transitions are handled so that the fall-back hour is counted once and the spring-forward gap is not double-counted And cross-midnight and cross-DST pause segments have durations equal to the true elapsed minutes in the property time zone And if no calendar is configured, a 24x7 schedule is applied
Retroactive Pause Corrections with Full Audit
Given an admin inserts, edits, or deletes a pause segment in the past via UI or API When the change is saved Then overlapping segments are normalized without creating overlaps And adjusted SLA metrics and breach determinations are recalculated within 5 seconds And an immutable audit record captures before/after values, actor, timestamp, reason, and optional comment And reports and API responses reflect the recalculated values immediately (<=5 seconds) And all recalculations preserve union-of-intervals semantics (no double-counting)
Real-Time SLA Recalculation, UI, and Notifications
Given a pause starts or ends (manual or auto-detected) When the state change is committed Then the dashboard SLA countdown and paused state update within 2 seconds And GET /work-orders/{id} returns updated fields (paused, pauseReason, adjustedSlaRemaining, lastPauseChangeAt) within 2 seconds And webhooks pause.started, pause.ended, and sla.recomputed fire once per event in order with retry/backoff and signature And if a vendor handoff occurs during an active pause, the pause state and accumulated durations persist unchanged and no duplicate segments are created
Policy Caps, Approvals, and Conflict Surfacing
Given policy caps per reason (e.g., max 48h) and reasons that require manager approval When a new pause would exceed a cap or a pause reason requires approval Then the system prevents automatic start and marks the request as Pending Approval while the SLA clock continues And a visible conflict banner and API field expose the policy violation and required action And upon approval, the pause is applied retroactively to the requested start time and metrics are recalculated; upon denial, no changes are applied And attempts to extend beyond caps either require approval or are trimmed to the cap per policy settings, with audit entries for all decisions
SLA Reporting & Audit Trail
"As an owner-operator, I want transparent reporting and an audit trail of pauses so that I can prove compliance and defend against unfair SLA breach claims."
Description

Deliver reports and exports that show raw vs. adjusted SLA for each work order, total paused time by reason, and pause frequency trends by property, vendor, or category. Provide a tamper-evident audit trail listing who paused/resumed, when, why, and with what evidence, including links to source events. Support filters, date ranges, CSV/PDF exports, and API access. Enforce data retention policies and role-based visibility. Include an audit-friendly “case file” bundle to satisfy owner/HOA audits or claims, with immutable hashes for attachments.

Acceptance Criteria
Report Raw vs Adjusted SLA Per Work Order
Given a user with permission to view SLA reports and a date range containing work orders with and without pauses When the user generates the "SLA Performance" report Then each row includes WorkOrderID, StartAt, DueAt, RawSLAHours, PausedHours, AdjustedSLAHours, AdjustedSLAStatus And PausedHours equals the sum of all recorded pause durations overlapping the SLA window for that work order And AdjustedSLAHours equals RawSLAHours minus PausedHours and is never negative And AdjustedSLAStatus is "Met" when AdjustedSLAHours <= the configured SLA threshold and "Breached" otherwise And all timestamps use the portfolio business timezone and durations are accurate to the minute
Paused Time Aggregation by Reason
Given report filters for date range and selected properties When the user views the "Pause Time by Reason" summary Then the report displays one row per pause reason with TotalPausedHours and PercentOfTotal And TotalPausedHours equals the sum of pause durations for the filtered work orders And PercentOfTotal across all displayed reasons sums to 100% +/- 0.1 And reasons with zero paused time are omitted from the summary And values match between UI, CSV export, and API for the same filters
Pause Frequency Trends by Property, Vendor, and Category
Given filters and a grouping selection of Property, Vendor, or Category with a weekly interval When the user runs the "Pause Frequency Trends" view Then each group-week shows PausesPer100WorkOrders and AveragePauseDurationMinutes And groups are sortable by each metric and support a Top-N filter And zero-activity groups display zeros when explicitly included And pause counts match the number of pause events in the audit trail for that group and interval
Tamper-Evident Audit Trail for Pause/Resume Events
Given a work order with pause and resume events created by users and system auto-detections When the audit trail is retrieved via UI, CSV, or API Then each event includes EventID, Type (Pause|Resume), ActorID, ActorRole, OccurredAt (ISO-8601 with timezone), ReasonCode, Notes, SourceEventID (if auto-detected), EvidenceLinks[], AttachmentHashes[] And AttachmentHashes are SHA-256 values that match the downloaded files And events are append-only; edit or delete attempts return 403/409 and a new corrective event must be created instead And SourceEventID links open the related tenant/vendor/system event detail And the audit trail is ordered by OccurredAt and is identical across UI, CSV, and API
Filters, Date Ranges, and Role-Based Visibility
Given a user role (Owner, PropertyManager, Vendor, Staff) and selected filters (date range, property, vendor, category, pause reason, SLA status) When the user queries SLA reports Then results include only work orders within the user's access scope And evidence links and attachments are redacted for roles without Evidence.View permission And unauthorized export or API attempts respond with 403 and no data is returned And date range filters are inclusive by calendar day in the business timezone
CSV/PDF Exports and API Access
Given a filtered report result set of up to 10,000 rows When the user exports CSV or PDF Then the file is generated within 3 seconds, includes the applied filters in the header, and the number of rows equals the UI And CSV uses UTF-8 with headers and documented column order; PDF is paginated with page numbers and a generation timestamp And for result sets over 10,000 rows, an asynchronous export job is queued, the user is notified, and a download link is provided when complete And API endpoints return the same fields, support pagination and sorting, and require an OAuth2 token with scope reports:read
Audit Case File Bundle with Retention and Immutable Hashes
Given a single work order is selected for audit When the user generates a Case File bundle Then a ZIP is produced containing manifest.json, audit_trail.csv, evidence attachments, and report.pdf And manifest.json lists each file with filename, byteSize, sha256, and includes bundleSha256 for the entire archive And any post-generation alteration of a file causes hash verification to fail when re-validated And if retention policy has expired for any attachment, the file is replaced by a stub noting "retention_expired" while the manifest and audit trail remain intact And the bundle generation is logged in the audit trail with ActorID and bundleSha256
Pause Status UI: Banner, Timeline & Alerts
"As a dispatcher, I want a clear visual of current pauses and adjusted deadlines so that I can prioritize work and avoid accidental scheduling conflicts."
Description

Add a prominent pause banner to the work order showing current state, reason, and adjusted SLA countdown. Provide a timeline visualization of all pauses with durations and evidence. Surface proactive alerts for long-running pauses, approval-needed pauses, and upcoming SLA breaches accounting for paused time. Ensure responsive behavior for mobile, accessible components (WCAG 2.1 AA), and performant rendering for large histories. Integrate with the shared calendar to visually mark paused jobs and prevent double-booking during pause windows.

Acceptance Criteria
Active Pause Banner With Adjusted SLA Countdown
Given a work order with an active pause and an SLA due at T_due When the user opens the work order page Then a banner is displayed at the top within 500ms showing: state label "Paused", selected reason, pause start timestamp in the user's locale, and an adjusted SLA countdown that excludes all paused durations And the countdown equals max(0, T_due - elapsed_active_time) with an accuracy of ±1 second And the countdown updates every second while not paused Given no active pause When the user opens the work order page Then the pause banner is not rendered Given an active pause ends When the work order is visible Then the banner hides and the countdown resumes within 1 second without page reload
Pause Timeline Shows Durations and Evidence
Given a work order with N pause segments (N ≥ 1) When the user opens the Pause Timeline Then all N segments are listed in reverse chronological order with start timestamp, end timestamp (or "Present" if ongoing), total duration (HH:MM), and reason And each segment displays an evidence count and type chips (e.g., SMS, Photo, Note, System Event) And clicking an evidence item opens it in a modal/viewer within 500ms And if any evidence link is unavailable, a non-blocking "Evidence unavailable" message is shown Given no pauses exist When the user opens the Pause Timeline Then a zero-state message "No pauses recorded" is shown And the sum of segment durations equals the total paused time used in the adjusted SLA within ±1 minute
Proactive Alerts for Long-Running and Approval-Needed Pauses
Given an active pause exceeds the organization threshold (default 72 hours) When the work order is viewed Then a "Long-running pause" alert is displayed with current duration and reason And the alert persists until the pause ends Given a pause flagged as Requires Approval is not approved When the work order is viewed by a user with approver role Then an "Approval needed" alert with Approve and Reject actions is displayed And selecting Approve or Reject updates the pause status and removes the alert within 1 second Given a non-approver views the work order When the pause requires approval Then a read-only "Approval needed" alert is displayed without action buttons
Upcoming SLA Breach Alert Respects Paused Time
Given adjusted time-to-breach is ≤ 60 minutes for a work order When the work order is viewed Then an "At-risk SLA" alert appears showing due time and remaining adjusted time And the remaining time decreases in sync with the adjusted countdown Given an "At-risk SLA" alert is visible When the work order becomes paused Then the alert is replaced by an informational message "SLA countdown paused" within 1 second And the alert reappears automatically when the pause ends and the adjusted time-to-breach is still ≤ 60 minutes
Responsive Mobile UI for Banner and Timeline
Given a viewport width ≤ 480px When the work order is viewed Then the pause banner renders as a two-line card with state, reason, and a single adjusted countdown line without horizontal scrolling And all interactive targets (buttons, chips, toggles) are ≥ 44x44 px And the timeline entries collapse into accordions showing reason and duration in the header And First Contentful Paint ≤ 1.8s and interaction ready ≤ 2.5s on a mid-tier mobile (Moto G Power, 4G) Given a viewport width ≥ 1024px When the work order is viewed Then the banner is sticky within the details column and the timeline uses a multi-column layout without content overlap
Accessibility Compliance (WCAG 2.1 AA) for Pause UI
Given the user navigates via keyboard only When tabbing through the work order Then all banner controls, alerts, and timeline items are reachable in a logical order and have visible focus states And Enter/Space activates primary actions Given a screen reader is enabled When the pause state changes or the adjusted countdown updates by ≥ 60 seconds Then the change is announced via aria-live="polite" with meaningful labels (e.g., "Paused due to Tenant Not Home. SLA countdown paused.") And all icons and chips have accessible names And alerts use role="alert" or aria-live as appropriate And color contrast for text and interactive elements is ≥ 4.5:1; non-text UI indicators meet ≥ 3:1 And all functionality is testable with NVDA + Chrome and VoiceOver + Safari without loss of information or actionability
Performance and Calendar Integration During Pause Windows
Given a work order with 500 pause segments and 200 evidence items total When the work order is loaded on a modern desktop (i5-class, 8GB RAM) Then time-to-interactive for the Pause Banner and first 20 timeline rows is ≤ 1.5s And scrolling the virtualized timeline maintains ≥ 55 FPS with no visible jank And memory usage increase stays ≤ 150MB over baseline after rendering the timeline Given a work order has an active or scheduled pause window When viewing the shared calendar Then the job is visually marked as Paused with a distinct pattern/label and shows reason + time range on hover Given an attempt to schedule this work order within a pause window (manual or auto-schedule) When the selected time overlaps any pause window Then the system blocks the action with a conflict message and suggests the next available time outside pause windows
Pause Notifications & Approval Workflow
"As a portfolio manager, I want notifications and approvals around pauses so that extended holds are justified and stakeholders stay informed without manual follow-up."
Description

Send role-based notifications on pause start, resume, and threshold breaches via in-app, email, and SMS (and optional Slack/webhooks). Support configurable approval flows for high-impact reasons or long pauses, with SLAs on approvals and escalation paths. Block or warn on scheduling, invoicing, or closing a job while a pause is active if policy requires. Log all approvals/denials with timestamps and comments. Provide templates for vendor/tenant messaging that explain the pause and next steps without exposing internal SLA details.

Acceptance Criteria
Role-Based Multi-Channel Notifications on Pause Start and Resume
Given a work order with assigned roles and configured notification preferences When a pause is started or resumed Then in-app notifications are delivered within 5 seconds to all eligible recipients by role And email notifications are queued and sent within 60 seconds to opted-in recipients with valid emails And SMS notifications are sent within 60 seconds to opted-in recipients with verified phone numbers And optional Slack/webhook notifications are sent within 60 seconds when integrations are enabled And notifications include pause event type (start/resume), reason label, timestamp, and next-step CTA and exclude internal SLA targets in messages to tenants And duplicate notifications for the same event are suppressed for 5 minutes And delivery status (success/failure with provider IDs) is recorded per channel
Threshold Breach Escalation for Extended Pauses
Given a pause exceeds its configured threshold duration for its reason When the threshold is met Then an escalation notification is sent to the designated escalation roles via all enabled channels within 60 seconds And the escalation is logged with timestamp, pause ID, elapsed time, and recipients And repeat reminders are sent at the configured cadence until the pause is cleared or the escalation is acknowledged And acknowledging the escalation records the user, timestamp, and note and stops further reminders And clearing the pause automatically closes all open escalations
Approval Workflow for High-Impact Pause Reasons
Given a user initiates a pause for a reason or duration requiring approval per policy When the pause request is submitted Then the system creates a Pending Approval record with reason, requested duration (if provided), requester, and SLA due time for approval And approvers defined by role or routing rules receive approval requests via all enabled channels within 60 seconds And only users with approver permission can approve or deny; others are blocked And approving activates the pause immediately and starts/continues the SLA timer pause; denying cancels the pause and notifies the requester with the denial comment And all actions require a comment of at least 5 characters and are timestamped
Approval SLA and Escalation Handling
Given an approval request is pending When the approval SLA time is reached Then the system escalates to the next approver group and sends notifications within 60 seconds And if auto-approve on timeout is enabled, the system auto-approves and activates the pause; otherwise the request remains pending with escalations continuing per cadence And the system records every escalation tier, time, and outcome And approver actions after auto-approval are blocked and presented with reason and audit link
Policy Enforcement on Scheduling, Invoicing, and Closing During Active Pause
Given a work order has an active pause When a user attempts to schedule a visit Then the system enforces the configured policy: Blocked actions show an error with policy name; Warned actions show a confirmation modal with policy text and require explicit confirmation And the same enforcement applies to attempts to create or send an invoice and to close/complete the work order And policy evaluation is logged with action type, policy result (blocked/warned/allowed), user, and timestamp And admins can override blocked actions only if they have the override permission; overrides require a comment and are audited
Audit Logging of Pause Events and Approvals
Given any pause event occurs (start, resume, edit, cancel, threshold breach, escalation, approval/denial) When the event is committed Then an immutable audit entry is written with actor, role, timestamp (UTC), event type, reason code, related IDs, and before/after values where applicable And audit entries include message delivery records per channel with provider message IDs and status And audit entries are filterable by work order, property, vendor, tenant, date range, and event type and exportable as CSV within 10 seconds for up to 10,000 records
Tenant and Vendor Messaging Templates Without Internal SLA Exposure
Given a pause is started or resumed affecting a tenant or vendor When external messages are generated Then the system uses the selected template for the recipient type and locale And templates include pause reason, plain-language explanation, expected next steps, and contact options and exclude internal SLA targets and approval routing details And admins can configure templates per reason and channel with placeholders for {work_order_id}, {reason_label}, {next_step}, {contact}, and {resume_hint} And template previews render correctly with sample data and fail validation if required placeholders are missing And all outbound external messages store the rendered template version used

SLA Heatmap

A real‑time, color‑coded view of response and resolution timers across properties, issue types, and assignees. Spot brewing breaches, drill into blockers, and rebalance workloads with one click. Portfolio Ops Managers see where minutes are leaking and dispatch help before deadlines slip.

Requirements

Real-time SLA Telemetry Engine
"As a Portfolio Ops Manager, I want SLA timers to update in real time across all units so that I can see accurate breach ETAs and intervene before deadlines slip."
Description

Compute and persist per-request SLA timers for time-to-first-response and time-to-resolution using FixFlow’s event stream (ticket created, first reply sent, assignment, vendor scheduled, tenant confirmed, job completed, closed). Apply pause/resume rules for waiting-on-tenant/vendor states and property-specific business hours and holiday calendars across time zones. Continuously calculate remaining time, breach ETA, and status (on track/at risk/breached), backfill historical tickets, and emit updates for the heatmap, alerts, and reports with resilience to delayed webhooks and temporary outages.

Acceptance Criteria
Real-time Computation and Persistence of SLA Timers
Given a new maintenance request is created with property_id and SLA targets configured When the "ticket_created" event is received Then a telemetry record is created within 5 seconds with initialized timers for time_to_first_response and time_to_resolution set to 0 elapsed business minutes Given subsequent events "first_reply_sent", "job_completed", or "closed" arrive out of order within a 10-minute window When the engine processes the event stream Then elapsed business minutes and start/end timestamps are computed using event timestamps, not arrival time, and persisted idempotently with versioning Given any SLA-related event is received When processed Then the corresponding telemetry record is updated within 5 seconds and includes fields: remaining_time_first_response, remaining_time_resolution, breach_eta_first_response, breach_eta_resolution
Pause/Resume Rules with Waiting States and Business Calendars
Given a request enters "waiting_on_tenant" or "waiting_on_vendor" When the state change event is received Then both SLA timers pause within 1 second and no business time accrues during the paused interval Given the request exits a waiting state due to "tenant_confirmed" or "vendor_scheduled" or an internal assignment When the resume event is received Then the timers resume and elapsed business minutes exclude the paused duration Given the property's business hours 09:00–17:00 local time and holiday calendar When time falls outside business hours or on a holiday Then timers do not accrue; when crossing DST transitions Then no duplicate or missing minutes occur and elapsed business minutes remain monotonic Given properties in different time zones When the same wall-clock interval occurs Then business time accrual uses each property's configured time zone
Breach ETA and Status Classification
Given active telemetry with target minutes for first response and resolution When elapsed business minutes are updated Then remaining time equals target minus elapsed and never drops below zero Given remaining_time > 0 and projected breach time more than 2 business hours away When evaluated Then status = "on_track" Given remaining_time > 0 and projected breach within 2 business hours or remaining_time <= 10% of target When evaluated Then status = "at_risk" Given elapsed >= target When evaluated Then status = "breached" and breach_eta equals the first business minute exceeding the target Given status transitions When thresholds are crossed Then only forward transitions occur: on_track -> at_risk -> breached; no reversion unless target or calendar configuration changes
Historical Backfill Accuracy
Given 180 days of historical events for open and closed tickets When backfill is initiated Then telemetry records are created for all tickets within scope and both SLA timers are computed within 0.5% or 1 business minute (whichever is greater) of a verified reference calculation Given duplicate or late events exist in history When processed Then the backfill remains idempotent with no duplicate telemetry records and consistent totals after re-run Given a backfill workload of 50,000 tickets When executed Then throughput sustains at least 50 tickets/second and completes within 4 hours without increasing real-time P95 processing latency by more than 1 second
Resilience to Delayed Webhooks and Outages
Given webhook delivery delays up to 15 minutes and temporary broker outages up to 30 minutes When service recovers Then the engine replays the gap and achieves eventual consistency within 10 minutes, recomputing timers based on event timestamps Given the same event is delivered more than once When processed Then updates are idempotent and telemetry versioning or hashing prevents duplicate state changes Given a partial failure during persistence When retried Then exactly-once final state is achieved or rolled back, and an audit log entry is written with correlation_id Given the service is degraded When SLOs are measured over a 24-hour period Then the 99th percentile compute-to-persist latency remains <= 15 seconds
Telemetry Updates for Heatmap, Alerts, and Reports
Given any telemetry change affecting remaining time, breach ETA, or status When updated Then an event is emitted to the "sla.telemetry.updated" topic containing request_id, property_id, affected_timer, remaining_time, status, breach_eta, and version Given portfolio heatmap subscribers are online When events are emitted Then end-to-end P95 delivery latency from input event to subscriber receipt is <= 7 seconds and delivery success rate is >= 99.9% over a 1-hour window Given alerting rules for at_risk and breached When thresholds are crossed Then a single alert notification is generated per transition per timer with a 30-minute suppression window to prevent alert floods Given reporting jobs query the persisted telemetry When executed Then data is available in the analytics store within 5 minutes of change and reflects the latest recomputation
Heatmap Visualization & Color Coding
"As a Portfolio Ops Manager, I want a color-coded heatmap that aggregates SLA risk by property, issue type, and assignee so that I can quickly spot hotspots without opening individual tickets."
Description

Render a responsive, accessible grid that aggregates SLA risk by property, issue type, and assignee with a configurable color scale for on-track, at-risk, and breached states. Support dynamic legends, tooltips with counts and breach ETAs, sorting by severity or volume, zooming between portfolio and property views, and virtualization for large datasets. Ensure consistent performance, caching, and keyboard navigation, and expose deep links to pre-filtered drill-downs.

Acceptance Criteria
Responsive Accessible Heatmap Grid
- Given the viewport is 320–480px wide, when the heatmap loads, then the grid fits the container without horizontal page scroll; columns collapse responsively with a minimum tappable cell size of 44x44px. - Given the viewport is 768–1024px or ≥1280px, when the heatmap loads, then headers remain sticky and the grid uses available width without clipping or overlap. - Rule: All text and iconography in cells and headers meet WCAG 2.1 AA contrast (text ≥ 4.5:1; non-text UI ≥ 3:1). - Rule: Color is not the sole indicator of state; each cell includes a secondary icon or pattern corresponding to state. - Rule: Grid exposes role="grid" or role="table" with programmatic row/column headers; cells include aria-colindex/rowindex or scopes for assistive tech. - Rule: Zoom to 200% or OS font-size +200% does not cause content loss or functional breakage.
Configurable SLA Risk Color Scale
- Given admin-configured thresholds for at-risk in minutes (e.g., 60) and breach at 0, when remaining SLA time for an aggregated bucket is evaluated, then state is On Track if remaining > at-risk, At Risk if 0 < remaining ≤ at-risk, Breached if remaining ≤ 0. - Rule: Color tokens for each state are sourced from the design system and pass color-blind simulations; each state maps 1:1 to color + icon/pattern. - Given the user updates thresholds in Settings and saves, when returning to the heatmap, then all visible cells and the legend reflect the new ranges within 1 second without a full page reload. - Rule: A default scale is applied if no custom configuration exists; configuration persists per organization and per environment.
Dynamic Legend and SLA Tooltips
- Given the heatmap is visible, when the legend is shown, then it displays state labels, color swatches, and total counts for On Track, At Risk, and Breached that reflect current filters. - Given a cell is hovered with a mouse or focused via keyboard, when the tooltip opens, then it shows property, issue type, assignee, total requests in cell, counts per state, soonest breach ETA (relative and absolute), and last data refresh time. - Rule: Tooltip opens within 150 ms on hover/focus, repositions to stay within the viewport, is dismissible with Esc or click/tap outside, and is accessible via aria-describedby. - Rule: Legend and tooltip values update in real time when filters or thresholds change, without flicker or layout shift (CLS < 0.1 for the component).
Sorting by Severity and Volume
- Given the user selects Severity sort, when applied, then groups are ordered by highest proportion of Breached, then At Risk, then On Track; ties break by soonest breach ETA. - Given the user selects Volume sort, when applied, then groups are ordered by descending total request count; ties break by severity as above. - Rule: Users can sort rows and columns independently; defaults are alphabetical by group name if no sort is chosen. - Rule: Sort actions complete within 300 ms on the current dataset and the active sort is clearly indicated and persists for the session.
Navigation: Zoom and Deep Links
- Given the portfolio view is open, when the user activates zoom on a property (via row header or cell), then the property-level heatmap loads within 500 ms with a visible breadcrumb to return. - Given a property-level view is open, when the user clicks Back to Portfolio, then the previous filters, sort, and scroll position are restored. - Given the user copies a share link from the heatmap (cell or filter context), when the link is opened in another browser, then the heatmap opens with equivalent pre-filters, sort, and zoom level applied. - Rule: Deep links use validated query parameters; invalid/unauthorized values are ignored with a non-blocking notice and safe defaults. - Rule: Generated URLs are URL-encoded and remain under 2000 characters.
Virtualization, Performance, and Caching
- Rule: The grid uses row/column virtualization; DOM node count stays under 2000 for any viewport. - Given a dataset of 200 properties × 20 issue types × 10 assignees (≈40,000 cells), when scrolling continuously, then p95 scroll frame rate is ≥55 FPS on a mid‑range laptop with no blank gaps. - Given heatmap data (≤1 MB) has been received, when rendering begins, then first meaningful paint of the heatmap completes within 800 ms p95 and interactions remain responsive (main thread long tasks < 50 ms p95). - Rule: API responses are cached for 60 s with ETag/If-None-Match; repeat requests within TTL return 304 and reuse cached data unless user triggers a manual Refresh which bypasses cache. - Rule: Stale-while-revalidate updates run in the background; the component shows a Last updated timestamp that updates without full re-render or focus loss.
Keyboard Navigation and Screen Reader Support
- Given focus is on the grid, when Arrow keys are pressed, then focus moves cell-by-cell; Home/End jump to row start/end; Page Up/Down move by one viewport; Ctrl/Cmd+Home moves to the top-left cell. - Given a cell is focused, when Enter or Space is pressed, then the cell tooltip opens; when Enter is pressed on "Open drill-down", the filtered list opens; Esc closes the tooltip. - Rule: Tab order includes filters, legend, grid, and action menus with no keyboard traps; a visible focus indicator is present at all times. - Rule: Screen readers announce cell context (property, issue type, assignee), state, count, and soonest breach ETA via accessible names; state meaning is conveyed in text in addition to color. - Rule: Dynamic changes (sorting, filtering, data refresh) are announced via polite aria-live regions without stealing focus.
Configurable SLA Policies & Thresholds
"As an Admin, I want to define precise response and resolution SLAs by property and issue type with business hours and holidays so that the heatmap reflects our real commitments."
Description

Provide an admin UI and APIs to define response and resolution SLAs by portfolio, property, issue type, and priority with business hours, grace periods, and holiday calendars. Support policy versioning, effective-policy resolution on each ticket, overrides at the ticket level with audit trails, and retrospective recalculation when policies change. Validate configurations and surface conflicts or gaps to ensure accurate timer computation and visualization.

Acceptance Criteria
Define SLA Policy by Scope Hierarchy
Given an admin with SLA-config permissions When they create a policy specifying portfolio, property, issue type, and priority with response and resolution targets Then the policy is saved with a unique ID and is retrievable via UI and API And the policy’s scope resolution follows most-specific-wins (property > portfolio) for matching tickets And tickets within the defined scope display the associated policy name and targets And policies can be created at portfolio level to act as defaults when no more-specific policy exists And attempts to save a policy missing required fields (targets, scope, business-hours reference) are rejected with specific validation errors
Apply Business Hours and Holidays
Given a policy with business hours (e.g., Mon–Fri 09:00–17:00) and an assigned holiday calendar When a ticket is created with a 4-hour response SLA on Friday at 16:30 local time Then the response deadline is computed as Monday at 12:30 local time (skipping non-business hours and weekends) And when the following Monday is a holiday on the assigned calendar Then the response deadline shifts to Tuesday at 12:30 local time And all SLA deadline computations use the ticket’s property timezone And changing the assigned holiday calendar updates future deadline computations for new tickets
Grace Periods for SLA Timers
Given a policy that defines a 15-minute grace period for response and resolution When a ticket’s computed response deadline passes by 10 minutes Then the ticket is not marked as breached and the UI/API indicates it is within grace When the response deadline passes by 16 minutes Then the ticket is marked as breached and the breach timestamp equals deadline + grace period And reports and heatmaps treat within-grace tickets as not breached
Policy Versioning and Ticket Effective Policy
Given Policy A v1 effective from 2025-01-01 and Policy A v2 effective from 2025-02-01 When a ticket is created on 2025-01-15 within Policy A’s scope Then the ticket binds to Policy A v1 and uses v1’s targets When a ticket is created on 2025-02-10 within the same scope Then the ticket binds to Policy A v2 and uses v2’s targets And if multiple policies match a ticket at the same specificity on the same effective date Then the system flags a policy conflict on the ticket and does not start SLA timers until resolved
Ticket-level SLA Override with Audit Trail
Given a user with permission to override SLAs at the ticket level When they override the response target to a new duration and provide a required reason comment Then the ticket’s deadlines are recomputed immediately using the override And an audit record captures before/after values, user, timestamp, reason, and channel (UI/API) And the override is visible on the ticket timeline and retrievable via API And removing the override reverts deadlines to the effective policy and writes an audit entry
Retrospective Recalculation After Policy Change
Given an existing set of open tickets bound to Policy B v1 When Policy B v2 is published and marked active Then the system provides a recalculation job that recomputes deadlines and breach states for open tickets whose effective policy is Policy B And the job logs per-ticket diffs (old vs new deadlines and breach states) and a summary count of updated tickets And recalculation is idempotent: re-running yields no further changes if inputs are unchanged And closed tickets are not reopened; historical metrics are updated only if configured to include recalculation for reporting
Configuration Validation, Conflicts, and Gaps
Given the admin attempts to save overlapping policies at the same specificity (e.g., two property-level policies for the same issue type and priority) When saving the second policy Then the system blocks the save with a 409/conflict in API and an inline UI error listing the conflicting policy IDs When the system detects that certain issue types or priorities in a portfolio have no applicable policy Then the UI surfaces a gaps report and the API exposes an endpoint listing uncovered combinations And all validation errors include machine-readable codes and human-readable messages
Drill-Down & Blocker Analysis
"As an Ops Lead, I want to drill into a hotspot to see which tickets are at risk and why so that I can take targeted actions."
Description

Enable click-through from any heatmap cell to a detail panel listing at-risk and breached tickets sorted by breach ETA, with per-ticket SLA status, timers, and age. Surface detected blockers like awaiting tenant reply, vendor unavailability, or missing approvals using signals from messages, scheduling, and workflow states. Show a timeline of key events, quick actions (assign, note, nudge tenant/vendor), and deep links to the full ticket.

Acceptance Criteria
Click-Through Opens Detail Panel from Heatmap Cell
- Given the user is viewing the SLA Heatmap with data loaded, when the user clicks any heatmap cell, then a detail panel opens on the right within 1500 ms and anchors to the selected cell’s context (property, issue type, assignee). - Then the panel header displays the selected dimensions and the total number of tickets returned. - Then the ticket list is pre-filtered to tickets matching the selected cell, with no tickets outside that scope displayed. - Then keyboard focus moves into the panel; pressing Esc or clicking outside closes the panel and returns focus to the originating cell. - Then the URL updates with a shareable state parameter for the opened cell without triggering a full page reload.
Detail Panel Lists At-Risk and Breached Tickets Sorted by Breach ETA
- Given the detail panel is open, when tickets are fetched, then only tickets with SLA status At Risk or Breached are listed. - Then tickets are sorted by Breach ETA ascending (negative values—already breached—appear before positive values—time to breach). - Then ties are broken by updated_at descending. - Then the active sort control is visibly set to “Breach ETA (asc)”. - Then pagination or infinite scroll is available when the result set exceeds 50 tickets and the total count is displayed.
Per-Ticket SLA Status, Timers, and Age Display
- Each ticket row shows SLA phase (Response/Resolution), status badge (On Track/At Risk/Breached), time-to-breach or time-since-breach, and ticket age since creation. - Timers update in real time at least every 60 seconds without a full page refresh. - If an SLA is paused, timers display Paused with reason and do not decrement. - Hovering over relative times reveals absolute timestamps in the user’s timezone (ISO 8601). - Displayed SLA calculations match the configured SLA rules for the ticket with ≤60 seconds discrepancy.
Blocker Detection and Categorization from System Signals
- For each ticket, if messaging signals show last outbound to tenant with no tenant reply within the configured window, display blocker Awaiting Tenant Reply with timestamp. - If scheduling data shows no vendor availability before the SLA deadline, display blocker Vendor Unavailable with next available slot shown if known. - If workflow state shows a required approval not yet completed, display blocker Missing Approval with the approver role indicated. - Each blocker displays its source (Messages, Scheduling, Workflow) and the signal’s last evaluated time. - Blocker status is re-evaluated at least every 5 minutes or immediately on relevant events, and badges update accordingly.
Timeline of Key Events Renders Chronologically
- The ticket detail section in the panel shows a timeline including: creation, first response, assignment changes, SLA pause/resume, inbound/outbound messages, scheduling events, and approval decisions. - Events are ordered newest to oldest with relative time; hovering reveals absolute timestamps. - A filter allows toggling event categories (Messages, Scheduling, Approvals, SLA, Assignments); by default all are on. - The timeline loads the latest 25 events initially and can load older events on demand without duplication. - All timestamps and event data match the ticket’s audit trail.
Quick Actions Execute and Log Updates
- Assign: selecting a new assignee updates the ticket within 2 seconds; the row reflects the new assignee and a timeline entry records the change. - Add Note: saving an internal note (max 2000 chars) posts immediately, appears in the timeline, and is not visible to external recipients. - Nudge Tenant/Vendor: sending a nudge uses the party’s configured channel (SMS/email); success shows a confirmation toast; failure shows an inline error with retry option. - Nudge actions are rate-limited to 1 per recipient per 120 minutes; attempting earlier is blocked with next-available time shown. - All actions enforce user permissions; unauthorized users cannot see or invoke restricted actions.
Deep Links Open Full Ticket
- Each ticket row provides a link that navigates to the full ticket view for that ticket’s ID. - Navigation completes without a full app reload and within 2 seconds on typical broadband. - The navigation preserves return context so using Back to Heatmap restores the prior panel and filters. - Links open in the same tab by default; standard browser modifiers open in a new tab/window.
One-Click Rebalance & Dispatch
"As a Dispatcher, I want to reassign at-risk tickets from the heatmap in one click with conflict checks so that I can rebalance workloads and avoid breaches."
Description

Allow bulk or single-ticket reassignment directly from the heatmap or drill-down with conflict checks against the shared calendar to prevent double-bookings. Suggest alternative assignees/vendors based on availability, proximity, and current workload, and preview the projected impact on SLA timers before confirming. Trigger downstream actions (SMS confirmations, calendar updates, notifications) and capture a full audit trail with rollback on failures.

Acceptance Criteria
Ranked Assignee Suggestions on Reassign
Given a Portfolio Ops Manager is viewing a ticket in the SLA Heatmap or its drill-down And the ticket has a defined service window and property location When the user opens the Reassign action Then the system returns a list of up to 5 suggested assignees/vendors (or all available if fewer) within 2 seconds And suggestions are ranked by: 1) availability in the requested window (no conflicts), 2) proximity (distance ascending), 3) current workload (open jobs in next 72 hours ascending) And each suggestion displays next available time, distance in miles/km, and workload count And suggestions exclude assignees marked unavailable or out-of-coverage for the property And if no suggestions are available, a No available assignees message is shown within 2 seconds
Calendar Conflict Detection and Prevention
Given the user selects an assignee and a time window for the ticket When the user clicks Confirm Reassign Then the shared calendar is checked for conflicts covering the entire selected window before persisting any changes And if a conflict exists, the confirmation is blocked, the conflicting event details are shown, and no changes are saved And if no conflict exists, the assignment and calendar update are committed atomically so that no double-booking exists afterward And the heatmap reflects the new assignee and timing within 3 seconds of confirmation
SLA Impact Preview Prior to Confirmation
Given the Reassign modal is open with a selected assignee and time window When the user changes the assignee or time Then the projected response ETA and resolution ETA are recalculated and displayed within 1 second And each projection clearly indicates Meets SLA or Breach risk and the delta versus the current plan And the projection uses the selected assignee’s availability, proximity, and current workload in its computation And the Confirm action is disabled until a populated SLA preview is visible
Bulk Reassignment from Heatmap Selection
Given the user selects between 2 and 100 tickets in the SLA Heatmap When the user clicks Rebalance and chooses Auto-dispatch Then the system assigns each ticket to the highest-ranked available assignee without creating calendar conflicts And tickets with no conflict-free assignee are skipped with reasons recorded And the operation completes within 60 seconds for 100 tickets And the UI displays a summary: reassigned count, skipped count, and error count And each ticket’s changes are applied transactionally (per ticket success or full rollback)
Downstream Actions on Successful Dispatch
Given a reassignment (single or bulk) is successfully committed When downstream actions are triggered Then SMS confirmations are enqueued to the tenant and new assignee within 2 seconds containing ticket ID, scheduled window, and contact link And the shared calendar reflects the new assignment within 2 seconds And in-app notifications are sent to the prior and new assignee within 2 seconds And downstream actions are idempotent so retries do not create duplicate messages or events
Comprehensive Audit Trail of Reassignment
Given any reassignment attempt occurs (single or bulk) When the operation concludes (success, skip, or rollback) Then an immutable audit record is created within 1 second containing: timestamp, actor, ticket ID(s), previous assignee, new assignee, selected time window, SLA preview values at confirmation, downstream action IDs, outcome (success/rollback/skip), and error details if any And audit records are queryable by ticket ID and date range and exportable as CSV And audit records are read-only to end users
Transactional Rollback on Downstream Failure
Given a reassignment is being confirmed When a synchronous critical step fails (assignment save or calendar update) Then all changes for that ticket are rolled back to the prior state and no double-bookings remain And the user is shown an error message with a retry option and the ticket appears unchanged in the heatmap And if asynchronous steps (SMS delivery or notification delivery) fail after being queued, the assignment is retained, the failure is logged in the audit trail, and automated retries are attempted per system policy
Alerts & Subscriptions
"As a Property Manager, I want alerts when a ticket approaches or breaches SLA so that I don’t miss time-critical work."
Description

Offer configurable alerts for approaching and breached SLAs with thresholds (e.g., 2 hours to breach), channels (in-app, email, SMS), quiet hours, and escalation rules. Enable subscriptions by property, portfolio, issue type, assignee, or saved view, with digest modes and de-duplication. Include RBAC-aware targeting, links back to the relevant heatmap slice, and rate limiting to prevent alert fatigue.

Acceptance Criteria
Approaching SLA Alert via Configured Channels (RBAC-aware)
Given a user has RBAC access to Property A and has enabled in-app and SMS channels, and has set an approaching-breach threshold of 2 hours for Response SLA When any ticket in Property A enters the 2-hour window before Response SLA breach Then the user receives exactly one in-app notification and one SMS within 60 seconds of trigger And no email is sent And users without RBAC access to Property A receive no alert And the notification includes: ticket ID, SLA type (Response), time remaining (mm:ss), assignee, severity, and a deep link to the heatmap slice pre-filtered to Property A and the ticket’s issue type And the event is recorded in the Alert Audit Log with timestamp, channel, recipient, and delivery status
Quiet Hours Deferral with On-Call Escalation for Breaches
Given a subscription with quiet hours 22:00–07:00 in the user’s timezone And an escalation rule that allows breach-level alerts to on-call contacts during quiet hours When an approaching-breach alert is triggered at 23:15 Then it is deferred and included in the next digest at 07:00 When a breach occurs at 01:10 Then only the on-call escalation targets receive alerts per their channel preferences within 60 seconds And the subscribing user does not receive real-time alerts during quiet hours And all deferred alerts are delivered by 07:10 local time in a single digest
Breach Escalation Ladder and Acknowledgment Gate
Given an escalation policy: Level 1 → assignee (in-app), Level 2 → property manager (SMS+email) after 10 minutes without acknowledgment, Level 3 → portfolio ops manager (SMS) after 20 minutes without acknowledgment And acknowledgment is defined as any of: opening the alert, changing ticket status, or posting an internal note from FixFlow When a ticket breaches Resolution SLA at 14:00 and remains unacknowledged Then Level 1 is sent at 14:00 And if unacknowledged at 14:10, Level 2 is sent And if unacknowledged at 14:20, Level 3 is sent And escalation stops immediately once any acknowledgment is recorded And no recipient receives duplicate alerts across levels within the same breach incident
Scoped Subscriptions by Property, Assignee, and Saved View
Given a user subscribes to: Property B (all issue types), Assignee = "Vendor X", and a Saved View "High Severity HVAC" When alerts are triggered Then the user only receives alerts that match at least one subscribed scope And if an alert matches multiple subscribed scopes, the user receives only one alert with all matched scopes listed in the payload And unsubscribing from any scope immediately stops future alerts for that scope within 60 seconds
Digest Modes (Hourly/Daily) with De-duplication
Given a user enables Hourly Digest for approaching-breach alerts and Daily Digest for breaches When 15 approaching-breach alerts occur between 10:00–10:59 across three properties Then at 11:00 the user receives one Hourly Digest summarizing counts by property, issue type, and assignee, listing the top 10 items and including a deep link to each heatmap slice And the user does not receive any real-time alerts for those items during the digest window When 4 breach alerts occur that day Then by 18:00 local time the user receives one Daily Digest for breaches with no duplicate items from previously sent digests
Per-Recipient and Per-Entity Rate Limiting
Given rate limits of max 3 alerts per recipient per 15 minutes and max 1 alert per ticket per 30 minutes per channel When more alerts are triggered than limits allow Then excess alerts are suppressed and recorded in the Alert Audit Log with reason "rate_limited" And suppressed alerts appear in digests (if enabled) without triggering real-time notifications And once the window resets, new alerts are delivered normally
Deep Link Integrity and RBAC Enforcement
Given an alert payload includes a deep link back to the SLA Heatmap slice When the recipient clicks the link Then the Heatmap opens with filters applied to replicate the alert’s context (property/portfolio, issue type, assignee, SLA type, status) And the specific ticket row is highlighted if included And if the recipient lacks RBAC access to any part of the slice, they see an access notice and only permitted data is shown And links in email and SMS include a request_id for traceability and expire after 7 days
Filters, Saved Views & Export
"As an Ops Manager, I want to filter and share heatmap views and export data so that my team and owners can align on performance."
Description

Provide robust filters for date range, portfolio/property groups, issue type, priority, assignee, vendor, and SLA state, with the ability to save, name, and share views as permission-scoped links. Support CSV and image snapshot exports of the heatmap and underlying data, plus scheduled email reports. Ensure consistent time zone handling, include metadata in exports, and log access for auditability.

Acceptance Criteria
Multi-Filter Query Consistency
Given a user with access to specific properties and the SLA Heatmap is open When the user applies filters: Date Range, Portfolio/Property Groups, Issue Type, Priority, Assignee, Vendor, and SLA State simultaneously Then the heatmap cells, totals, and underlying ticket counts reflect only items matching all selected filters and the user’s permissions And when no items match the selected filters Then an empty state is shown with 0 counts and a clear message without errors And when any single filter is changed Then the results update within 750 ms after a 300 ms debounce And when all filters are cleared Then the default accessible dataset renders within 2 seconds
Time Zone Handling & Display
Given the user profile time zone is set to a specific IANA TZ (e.g., America/Los_Angeles) When viewing heatmap cells, tooltips, and detail panels Then all timestamps, daily buckets, and SLA timers are rendered in the user’s TZ with an explicit TZ label And when exporting CSV or image, or receiving scheduled emails Then all timestamps and SLA calculations match the user’s TZ and the export metadata includes the TZ and generation timestamp in ISO 8601 And given a date range that spans a DST transition in the user’s TZ When computing durations and daily grouping Then SLA durations and bucket boundaries are correct with no 1-hour drift
Save, Rename, and Share Permission-Scoped Views
Given a set of applied filters When the user saves the view with a unique name Then the view persists with owner, created/updated timestamps, and scope (Private/Team/Org) and appears in the saved views list And when the user renames or deletes the view Then changes are reflected immediately and the list updates within 1 second And when the user generates a permission-scoped share link for scope Team X Then only members with Team X permission can open the link; others receive a 403 with guidance And when the owner revokes the link Then previously issued links stop resolving within 60 seconds and access attempts are logged
Export CSV and Image Snapshot with Metadata
Given a filtered heatmap view When the user exports CSV Then the file downloads within 10 seconds for datasets up to 50,000 rows and includes header metadata: applied filters, generated-at (ISO 8601 + TZ), user id/email, app version, column definitions, and total row count And the number of CSV data rows matches the on-screen count for the same filters And when the user exports an image snapshot Then a PNG at 2x device pixel ratio downloads including the visible heatmap, legend, and filter chips, with colors matching on-screen within WCAG AA contrast And if the dataset exceeds 200,000 rows Then the user is prompted to schedule an emailed export instead of an immediate download
Scheduled Email Reports From Saved Views
Given a saved view and permitted recipients When the user schedules a report with a frequency (daily/weekly/monthly) and time in the user’s TZ Then recipients receive an email within 10 minutes of the scheduled time containing the CSV and PNG (or secure links) and the email subject includes the view name and date range And when the run yields zero results and the user has not disabled empty sends Then the email is sent with a "[No Data]" subject prefix And on transient failures Then the system retries up to 3 times with exponential backoff and notifies the creator on final failure And when the schedule is paused or deleted Then future sends stop within one cycle and the action is logged
Audit Logging for Filters, Exports, and Shares
Given any access to a saved view, export (CSV/PNG), share link creation/use, or schedule create/update/delete When the action occurs Then an audit log entry is written with actor (id/email), action, target id, filter hash or snapshot, timestamp (UTC), IP, user agent, and outcome (success/failure) And when an admin queries audit logs by date range and target id Then matching entries are retrievable within 2 seconds for data retained for at least 30 days And when a non-admin attempts to access audit logs Then a 403 is returned and the attempt itself is logged

Nudge Ladder

Role‑aware reminders that escalate smartly as the clock winds down—DMs for techs, SMS for vendors, email for owners—respecting quiet hours and time zones. Includes one‑tap actions (confirm access, accept job, add ETA) to shave minutes without opening the dashboard, turning nudges into progress.

Requirements

Role-Aware Channel Routing
"As a property manager, I want nudges to reach each participant on the channel they actually use so that reminders are seen and acted on quickly without me micromanaging communications."
Description

Automatically select and deliver reminders via the most effective channel for each participant based on role and preferences: in-app DMs for field techs, SMS for vendors, and email for owners. Integrates with the FixFlow user directory to resolve role, contact methods, and consent. Supports per-user channel preferences, fallback logic (e.g., SMS → email on carrier failure), and channel-specific formatting (short SMS, rich email, compact DM). Emits structured events for downstream analytics and maintains an audit log of chosen channel, delivery attempt, and outcome. Respects global notification settings and integrates with the Nudge Ladder scheduler to ensure the right person is nudged at the right time on the right channel.

Acceptance Criteria
User Role and Consent Resolution from Directory
Given a valid nudge_id and user_id exist in the FixFlow directory with role, contact_methods, and consent flags When channel routing is invoked for the nudge Then the system resolves the user's role within 500 ms and filters contact_methods to only those with consent=true and global_notifications.enabled=true And if no consented contact method exists, no message is sent, status is set to "suppressed", and an analytics event "nudge.suppressed.no_consent" is emitted And the selected primary channel equals the role default (Tech→DM, Vendor→SMS, Owner→Email) unless overridden by user preferences
Per-User Channel Preference Overrides Role Default
Given a user with role Vendor has preferences.primary_channel=email and email consent=true, SMS consent=true When channel routing is invoked Then email is selected as the primary channel And if the preferred channel is unavailable (e.g., missing address) or consent=false, the next user-specified preference is selected; if none, fall back to role default And the selection decision is recorded with rationale in the audit log
Fallback on Delivery Failure with De-duplication Window
Given primary channel is SMS and a fallback chain [SMS → Email → DM] is configured When the SMS delivery attempt returns failure or no delivery receipt within 60 seconds Then exactly one fallback attempt is sent to Email within 10 seconds of the timeout And if a late SMS success receipt arrives before the fallback is sent, the fallback is canceled And all attempts share a correlation_id and outcomes are recorded; no more than 1 fallback is attempted per nudge
Channel-Specific Message Formatting Rules
Given a nudge payload with title, action_url, and context When formatting for SMS Then the SMS body is <= 320 characters including a single shortened HTTPS link, contains no HTML, and includes the job reference code And when formatting for Email Then the email includes a subject line, HTML body with CTA button linking to action_url, and a plain-text alternative; images are optional and inline styles only And when formatting for In-App DM Then the DM text is <= 300 characters with a compact CTA link and no rich HTML
Analytics Event Emission on Routing and Delivery
Given any routing decision or delivery attempt occurs When the attempt is made or the outcome is known Then an analytics event is emitted within 2 seconds with schema fields: attempt_id, nudge_id, user_id, role, channel_selected, fallback_from, timestamp, outcome, failure_reason, latency_ms, correlation_id And events pass schema validation (contract test) and are delivered to the analytics stream with at-least-once semantics
Audit Log Records for Channel Decisions and Outcomes
Given a nudge is routed and delivery attempted When the process completes (success, failure, or suppression) Then an audit log entry is created containing nudge_id, user_id, role, decision_channel, decision_rationale, attempt_id(s), timestamps, outcome, and actor=system And audit entries are immutable, retrievable by nudge_id or user_id via API, and retained for at least 12 months
Quiet Hours and Time Zone Respect via Scheduler Integration
Given the user's time zone is set and global quiet_hours=22:00–07:00 local When a nudge is due during quiet_hours Then no channel attempt is made; the nudge is deferred to the next available window and annotated with defer_reason=quiet_hours And an analytics event "nudge.deferred.quiet_hours" is emitted, and the scheduled send time is computed in the user's local time And if global_notifications.enabled=false for the user, the nudge is suppressed entirely with outcome=suppressed and no delivery attempts
Configurable Escalation Ladder & SLAs
"As an operations lead, I want to configure a clear escalation path tied to SLAs so that tasks don’t slip and stakeholders are notified appropriately as urgency increases."
Description

Provide a rules-driven timeline that escalates reminders as job due times approach. Define steps relative to key milestones (assignment, scheduled start, ETA overdue) with recipients, channels, and message templates per step. Support per-portfolio and per-workflow presets, snooze/acknowledge pauses, and automatic suppression when status changes (accepted, on-site, completed). Include guardrails to prevent over-notifying, step-level retry policies, and manager escalation when thresholds are breached. Expose a visual editor and a machine-readable policy for versioning and auditability.

Acceptance Criteria
Steps Relative to Milestones Configuration
Given an admin opens the Escalation Policy editor for a portfolio When they add steps anchored to assignment at 0h, scheduled_start at -24h and -1h, and eta_overdue at +10m Then the UI displays computed send times in the portfolio time zone for a job with assignment=T0, scheduled_start=Tstart, eta_due=Teta Given recipients are set to Technician via DM, Vendor via SMS, Owner via Email with selected templates When the policy is saved Then the policy persists and is applied to newly created jobs in that portfolio Given a job lacks a scheduled_start When a step is anchored to scheduled_start Then that step is disabled with a validation error until scheduled_start is set Given a new job matching the portfolio and workflow is created When the scheduler runs Then future-dated notifications are enqueued for all enabled steps and none are scheduled in the past
Per-Portfolio and Per-Workflow Presets Application
Given two portfolios A and B each with a default ladder and a workflow-specific ladder "Emergency Plumbing" When a new job in portfolio A with workflow Emergency Plumbing is created Then the workflow-specific ladder for portfolio A is selected Given a new job in portfolio B with workflow General Maintenance is created When no workflow-specific ladder exists Then the portfolio default ladder is selected Given an operator overrides the ladder on a job at creation When the job is saved Then the override policy version is bound to the job and supersedes portfolio and workflow defaults Given an existing preset is edited When a new version is published Then only jobs created after publication use the new version and existing jobs keep their bound version
Automatic Suppression on Status Changes
Given a job has pending reminders for accept_job at T+30m and T+2h When the vendor accepts the job at T+25m Then all pending accept_job reminders are canceled and no further accept_job reminders are sent Given a job has pending on_site reminders anchored to scheduled_start When the technician status changes to On Site before the first reminder Then all pending on_site reminders are canceled Given a job transitions to Completed When the scheduler evaluates pending steps Then all remaining reminders for the job are canceled and an audit entry records suppression reason Completed
Snooze and Acknowledge Pauses Ladder
Given a vendor receives a reminder with one-tap Acknowledge and Snooze 30m actions When the vendor taps Acknowledge Then all steps targeting the vendor for this job are paused for the policy-defined acknowledge_pause window and the job shows Acknowledged Given the vendor taps Snooze 30m When the scheduler recalculates Then the next pending step to the vendor is delayed by 30 minutes without altering other recipients Given the acknowledge_pause window expires When the job remains unaccepted Then the ladder resumes at the next scheduled step
Step-Level Retry Policies and Notification Guardrails
Given a step has retry policy max_retries=3, backoff=5m with jitter=±1m and channel_fallbacks=[DM,SMS,Email] When a send via DM fails with a retryable error Then retries occur up to 3 times with backoff and jitter and on final failure the next channel SMS is attempted once Given per-recipient frequency cap is 3 notifications per 24h per job When attempts would exceed the cap Then further sends are suppressed with reason FrequencyCap and recorded in the audit log Given recipient quiet hours are 21:00–07:00 local time and policy=defer When a step would send inside quiet hours Then the send is deferred to 07:00 local time and SLA timers are updated to reflect the deferral Given the worker restarts When previously attempted sends are retried Then idempotency keys prevent duplicate deliveries across retries and restarts
Manager Escalation on SLA Breach
Given an SLA acceptance threshold is 2h after assignment When the job remains unaccepted at T+2h Then a manager escalation message is sent via Email and SMS with a summary of prior attempts and a one-tap Assign Backup Tech link Given the escalation is unacknowledged for 30m When the next escalation tier is configured Then the escalation is sent to the next tier and the previous tier is not re-notified Given the job is accepted after escalation When the scheduler runs Then no further escalations for this breach are sent and the audit trail records ResolvedAfterEscalation
Visual Editor and Machine-Readable Policy Versioning & Audit
Given an admin opens the visual editor When they add, edit, and reorder steps and validate Then Save is enabled only when all steps pass validation and preview renders the resolved timeline Given the admin exports the policy When the JSON is downloaded Then it conforms to the published JSON Schema including policy_id, version, created_at, created_by, steps, and checksum Given the admin publishes changes When the new version is saved Then an immutable audit record is created with diff from prior version and jobs created after this time bind to version N while existing jobs continue using version N-1 Given an API client requests GET /policies/{policy_id}/versions/{version} When the request is authorized Then the API returns the exact JSON for that version and 404 for unknown ids or versions
Quiet Hours and Time Zone Guardrails
"As a vendor, I want reminders to avoid my quiet hours in my time zone so that I’m not disturbed off-hours and can still respond promptly when I’m available."
Description

Enforce per-user quiet hours and accurate local-time scheduling to avoid sending nudges at inappropriate times. Store each user’s time zone and preferred quiet hours; queue messages to send at the earliest allowed time while honoring escalation intent. Provide emergency override logic with explicit justification and logging. Handle DST transitions, weekend/holiday rules, and display next send time in the UI for transparency. Integrates with all channels and respects global and per-user Do Not Disturb settings.

Acceptance Criteria
Quiet Hours Enforcement Per User and Channel
Given a vendor has quiet hours 21:00–07:00 in America/New_York When an SMS nudge is scheduled at 22:15 local Then the system does not send the SMS and queues it for 07:00 next calendar day local And the queued job is stamped with recipientId, channel, and calculatedSendAt in ISO-8601 with timeZone And no retries or alternative channels are attempted during quiet hours And if the user changes quiet hours to 20:00–06:00 before 07:00, the calculatedSendAt recalculates to 06:00 same day And delivery logs show no send attempts within quiet hours for that recipient and channel
Accurate Local-Time Scheduling with Stored Time Zone and DST
Given a technician profile stores timeZone = America/Los_Angeles and quiet hours 20:00–07:00 When DST starts on 2025-03-09 and the next permissible send would be 02:30 local Then the system schedules at 03:00 local (first valid time after the jump) and creates a single job When DST ends on 2025-11-02 and a queued calculatedSendAt is 01:30 local Then the system sends once at the first occurrence of 01:30 local and does not duplicate at the repeated 01:30 And UI and logs display the localized time with explicit UTC offset (e.g., PST/PDT) and the UTC timestamp And the stored time zone uses a valid IANA identifier; attempts with invalid zones are rejected with a validation error
Weekend and Holiday Rules Respect
Given an owner has weekend days = Saturday, Sunday, regional calendar = US-NY, and quiet hours 00:00–09:00 When an email nudge becomes eligible on Sunday at 10:00 local Then the system defers it to Monday at 09:00 local When a regional holiday (e.g., 2025-11-27) occurs and a nudge becomes eligible at 15:00 local Then the system defers it to the next business day at 09:00 local And if a per-user override "allow weekends after 10:00" is enabled, the system schedules at the first slot after 10:00 that is outside quiet hours And channel selection does not bypass weekend/holiday deferrals
Global and Per-User Do Not Disturb Precedence
Given org-level DND is active 00:00–06:00 UTC and a vendor's quiet hours have ended at 07:00 America/New_York When a nudge is eligible at 06:30 UTC (02:30 local) Then the system does not send due to org-level DND and reschedules to 06:00 UTC Given a technician has per-user DND = mute all until 2025-09-10T12:00:00Z When any nudge targets this technician before that time Then no messages are sent on any channel and the queue shows Blocked by DND with next check at the unmute time And lifting DND immediately recalculates and schedules within 2 seconds And DND blocks take precedence over quiet hours and are reflected in the UI reason codes
Emergency Override with Justification and Audit Log
Given a dispatcher with role On-Call initiates an emergency override for a vendor during quiet hours When they enter a justification of at least 15 characters and confirm Then the system sends the pending nudge immediately on the selected channel, bypassing quiet hours and DND And the event is recorded with fields: overrideId, actorId, targetId, channel, localSendTime, targetTimeZone, reason, relatedRequestId, createdAt (UTC), previousBlockers And overrides are rate-limited to 1 per recipient per 60 minutes per channel; attempts beyond the limit are blocked with a visible error And a banner Sent via emergency override by {actor} appears in the conversation and in the recipient notification context And an exportable audit log entry is available within 1 minute of send
Next Send Time Transparency in UI
Given a queued nudge exists for a tenant with time zone Europe/Berlin and quiet hours 21:00–08:00 When a user views the nudge details or tooltip Then it displays Next send: [localized date/time] (Europe/Berlin), reason: [e.g., Quiet hours] and the UTC equivalent And changing the tenant's quiet hours to 20:00–07:00 updates the displayed next send within 2 seconds and matches the scheduler's calculatedSendAt And if multiple blockers exist (e.g., holiday + quiet hours), the UI lists blockers in priority order and shows the computation basis And the displayed time zone corresponds to the recipient, not the viewer
Escalation Intent Preservation Across Delays
Given an escalation ladder for a request: Step 1 DM at T0, Step 2 SMS at T0+30m, Step 3 Email owner at T0+2h And the technician's quiet hours delay Step 1 by 8 hours When quiet hours end Then Step 1 is sent immediately, and Steps 2 and 3 are re-scheduled to maintain their original relative offsets (+30m and +2h from Step 1 actual send) And if the cumulative delay would push any step beyond the configured max ladder window (e.g., 6h), remaining steps are canceled and a Ladder expired event is logged And no step is sent during any recipient's quiet hours or DND; each recipient's schedule is computed independently by their local time and rules And action links (confirm access, accept job, add ETA) remain valid for at least 24 hours from their eventual send time And no duplicate sends occur across channels during rescheduling
One-Tap Action Links (Accept, ETA, Access)
"As a field tech, I want to accept a job or add my ETA from the message itself so that I can update status quickly while on the move."
Description

Embed secure, expiring smart links in nudges that allow recipients to perform common actions in one tap without opening the full dashboard: accept/decline job, set or update ETA, and confirm access instructions. Links are role-scoped and signed; support app deep-linking when authenticated and a lightweight mobile web flow when not. Validate action eligibility, prevent conflicting updates, and write results to the work order with structured metadata (user, time, channel, token). Provide instant confirmation feedback and optional follow-up message to stakeholders triggered by the action.

Acceptance Criteria
Accept/Decline Job via SMS Smart Link (Vendor)
Given a vendor receives an SMS with a signed one-tap link for an assigned work order and the link is unexpired and unused When the vendor taps Accept Then the system validates role, token, and work order eligibility, updates status to Accepted by Vendor, records acceptedAt, acceptedBy, and displays confirmation within 2 seconds And writes an audit entry with {workOrderId, action=ACCEPT, actorId, actorRole=Vendor, channel=SMS, tokenId, ip, userAgent, timestamp} And sends configured follow-up notifications to stakeholders within 60 seconds Given the same vendor and link conditions When the vendor taps Decline Then the system validates as above, updates status to Declined by Vendor, records declinedAt, declinedBy, and requires a decline reason if configured And displays confirmation within 2 seconds and notifies stakeholders within 60 seconds Given a link that is expired, revoked, or already used When it is tapped Then no work order state changes occur and the user is shown an error explaining the reason, and the API returns 410/403 as appropriate Rule: Actions are idempotent by token; repeated Accept/Decline attempts with the same token do not create duplicate audit entries or notifications
Set or Update ETA via Mobile Web Flow
Given a vendor or technician receives a signed one-tap ETA link for an active work order and is not authenticated When they tap the link and choose a date/time Then the system presents a lightweight mobile web screen, validates timezone, ensures ETA is in the future and within any configured service window, and saves etaAt with previousEtaAt captured And shows success within 2 seconds and confirms the new ETA value on-screen And emits notifications to tenant/owner/coordinator per rules within 60 seconds Given a concurrent update detected (version mismatch) When the user submits an ETA Then the system returns 409 Conflict with a prompt to refresh, and no stale data overwrites occur Rule: ETA submissions are idempotent by (workOrderId, newEtaAt, tokenId); identical repeat submissions do not create duplicate audit entries
Confirm Access Instructions via DM One-Tap (Technician)
Given a technician receives a direct message with a signed Confirm Access link for a scheduled visit When they tap Confirm Access Then the system verifies role, token, and eligibility (work order not canceled/completed), sets accessConfirmed=true, accessConfirmedAt timestamp, and optionally saves an inline note if provided And shows success within 2 seconds and posts a confirmation comment to the work order timeline And notifies the coordinator/owner as configured within 60 seconds Given a non-technician uses the link When the link is opened Then the system returns 403 Forbidden and no state changes occur
Deep-Link to App When Authenticated; Mobile Web Fallback
Given a recipient has the FixFlow app installed and an active authenticated session on the device When they tap a one-tap action link Then the OS opens the app via universal/app link, the action executes using the token, and a success screen is shown within 2 seconds Given the app is not installed or session is not authenticated When the link is tapped Then the user is routed to the mobile web flow for the same action with context preserved, requiring no login, and receives the same confirmation Rule: Fallback from app link to mobile web occurs within 1 second without user intervention
Secure, Role-Scoped, Expiring, Single-Use Tokens
Rule: Action links carry signed tokens scoped to {workOrderId, actionType, actorRole, channel} and include exp; tokens are validated server-side using rotating keys Rule: Token TTL is configurable (15 minutes–72 hours) with a default of 24 hours; requests outside TTL are rejected with 410 Gone and no state changes Rule: Tokens are single-use; on first successful state change the token is invalidated, and subsequent attempts return 410 without duplicating effects Rule: Role or channel mismatch (e.g., owner using vendor link) returns 403 Forbidden and is logged without changing state Rule: All token validation attempts (success/failure) are recorded with {tokenId, result, reason, ip, userAgent, timestamp}
Eligibility Validation and Conflict Prevention
Given an action link is invoked When the server evaluates the request Then it verifies work order eligibility (not canceled/completed, action not already satisfied, assignment matches actor) and rejects violations with 409 Conflict and a human-readable reason Given multiple concurrent actions attempt to change the same field (e.g., Accept vs Decline, competing ETA updates) When requests are processed Then the system uses optimistic locking/version checks to ensure atomicity; one request succeeds, others receive 409 and no partial updates occur Rule: Accept/Decline transitions are exactly-once per work order; no double-book or dual-state condition can be produced by replays or retries
Structured Metadata Writeback and Stakeholder Follow-Ups
Rule: Every successful action writes structured metadata to the work order and audit log: {workOrderId, action, previousValue, newValue, actorId, actorRole, channel, tokenId, sourceMessageId (if available), timestamp (UTC ISO-8601), ip, userAgent} Rule: Audit entries are visible in UI and retrievable via API within 5 seconds of action commit Rule: Stakeholder notifications (channels per role mapping) are sent within 60 seconds and include action summary, actor, and updated fields; duplicates are suppressed per correlationId Rule: Notification failures are retried for at least 15 minutes with exponential backoff and are logged; failures do not roll back the successful action
Calendar & Work Order Sync on Action
"As an owner, I want one-tap actions to immediately reflect on the shared calendar so that everyone has a single source of truth without manual coordination."
Description

When a recipient completes a one-tap action, automatically update the shared FixFlow calendar and underlying work order state. On Accept, reserve an available slot, prevent double-booking, and send confirmations to tenants. On ETA updates, adjust calendar holds and notify impacted parties. On Access confirmation, attach entry instructions and surface them in the tech’s job view. All updates are atomic, auditable, and idempotent to avoid race conditions with parallel edits or escalations.

Acceptance Criteria
Accept Action: Reserve Slot and Notify Tenants
Given a work order in "Awaiting Acceptance" with at least one available slot and a vendor receives a one‑tap Accept for a specific slot When the vendor taps Accept Then the system performs an atomic transaction that updates the work order status to "Scheduled", creates/updates a single calendar event for that slot linked to the work order, and locks the slot for that vendor/resource And prevents double‑booking by rejecting any concurrent conflicting reservations for the same vendor/resource And sends confirmations to the tenant (SMS) and owner (email) containing date/time and vendor name within 5 seconds And records the calendar event ID and notification IDs on the work order
ETA Update: Adjust Calendar Hold and Notify Impacted Parties
Given a scheduled work order with an existing calendar event and the vendor receives a one‑tap ETA update option When the vendor submits a new ETA time within the service day Then the system updates the calendar event start/end to reflect the new ETA, releases any holds on the old time, and appends the change to the work order timeline And notifies the tenant (SMS) and owner/tech (email/DM) with the new ETA and reason And ensures no overlapping events exist for the vendor/resource after the change And if the same ETA is submitted again within 24 hours, no duplicate calendar events or notifications are created (idempotent)
Access Confirmation: Attach Entry Instructions and Surface to Tech
Given a work order that requires entry and the tenant receives a one‑tap Access Confirmation When the tenant confirms access and provides or selects entry instructions Then the system marks the work order as "Access Confirmed", attaches the instructions to the work order, and surfaces them in the assigned tech’s job view And redacts sensitive instruction details in outbound notifications while retaining full details for authorized roles in the job view And writes an audit entry with actor, timestamp, and a content hash of the instructions
Idempotent Processing of One‑Tap Actions
Given each one‑tap action includes a unique action_id and correlation_id When the recipient repeats the same action (same action_id) or a delivery is retried Then the system returns success without creating additional calendar updates, state transitions, or notifications And increments a dedup counter in the audit log and links to the original committed change And if a later conflicting action with a lower precedence or older sequence arrives, it is rejected without side effects and is audit‑logged as "Rejected - Stale"
Atomicity and Rollback on Sync Failures
Given an action requires updating the work order, updating the shared calendar, and sending notifications When any downstream operation fails (e.g., calendar API error) after the transaction begins Then the system rolls back all interim changes so that work order status, calendar, and notifications remain consistent (all‑or‑nothing) And retries the operation up to 3 times with exponential backoff; on final failure, raises an alert and posts an internal note on the work order And no tenant/owner notifications are sent unless both the work order and calendar updates have committed successfully
Audit Trail for All Sync Actions
Given any one‑tap action that changes a work order or calendar entry When the action is processed Then an immutable audit record is written with action type, actor, source channel, previous state, new state, calendar event ID (if any), notification IDs, ISO‑8601 UTC timestamps, and correlation_id And the audit record is retrievable via API and UI within 1 second of commit And repeated retrieval returns the same record contents and checksum
Concurrency Control Prevents Double‑Booking and Conflicts
Given two users or automations submit conflicting updates (e.g., two Accepts for the same slot or an Accept concurrent with an ETA move) When the actions are processed within a 2‑second window Then only one action commits using optimistic locking/version checks; the losing action receives a conflict response and triggers a nudge with next available slots And the calendar reflects a single consistent event with no duplicates or partial holds And both attempts are recorded in the audit log with outcomes "Committed" or "Rejected - Conflict"
Delivery, Deduplication, and Engagement Tracking
"As a product manager, I want visibility into which nudges get results so that we can tune timing and content to improve response rates."
Description

Track end-to-end nudge delivery and engagement across channels, including sent, delivered, bounced, clicked, and actioned events. Ingest provider webhooks (SMS/email), correlate clicks to actions, and surface per-job and aggregate metrics. Implement suppression rules to prevent duplicate nudges across channels once an action is taken or a recent nudge exists within a cooldown window. Expose APIs and UI dashboards for monitoring and optimization; emit alerts on systemic failures or elevated bounce rates.

Acceptance Criteria
Lifecycle Event Tracking Across Channels
Given a nudge is initiated for a job to a recipient via SMS or Email When the system transmits the message via the configured provider Then a Sent event is persisted with jobId, recipientId, role, channel, providerMessageId, correlationId, and UTC timestamp Given the provider posts a delivery or bounce webhook for that providerMessageId When the webhook is received and validated Then a Delivered or Bounced event is persisted with the same correlation keys and provider payload snapshot And event ordering is normalized by providerMessageId and provider event timestamp And metrics in UI and API reflect the new state within 60 seconds of webhook receipt Given the recipient clicks a tracked link in the nudge When the click is captured Then a Clicked event is persisted with linkId, jobId, recipientId, channel, and UTC timestamp
Webhook Ingestion: Verification and Idempotency
Given a valid webhook request from an SMS or Email provider When the request is received Then the request signature is verified using the configured secret or certificate And a 2xx response is returned within 300 ms p95 And the event is processed idempotently keyed by providerEventId + providerMessageId to prevent duplicates Given a duplicate webhook or provider retry When it arrives Then no duplicate events are created, and processing returns idempotent success Given a webhook with failed verification or invalid schema When it is received Then the system responds with 4xx, records an audit log with reason, and does not mutate metrics Given a backlog or replay of historical webhooks When processed Then events are upserted correctly without duplication and timelines remain chronologically correct
Click-to-Action Correlation and Recording
Given a nudge includes a one-tap action link unique to jobId, recipientId, channel, and actionType When the recipient taps the link Then the intended action (e.g., Accept Job, Confirm Access, Add ETA placeholder) is executed successfully without requiring dashboard navigation And an Actioned event is persisted with actionType, actorId, jobId, channel, and UTC timestamp And a Clicked event is recorded within 1 second p95 and Actioned within 2 seconds p95 of tap And repeated taps within 10 minutes are idempotent and do not create duplicate actions Given the action is completed via dashboard instead of the link When the system detects the state change Then an Actioned event is recorded and all outstanding nudge links for that job+role are marked inactive
Cross-Channel Deduplication and Cooldown Suppression
Given a nudge was sent for a specific job and recipient on any channel within the cooldown window (default 30 minutes, configurable per role) When the scheduler prepares the next step of the ladder for the same job+recipient Then the pending send is suppressed and a Suppressed event is logged with reason=cooldown and reference to the prior nudge And cross-channel deduplication prevents sending via alternate channels during the cooldown Given an Actioned event exists for the job+role When future ladder steps are evaluated Then all scheduled nudges for that role are canceled and logged as Suppressed with reason=actioned And no further nudges are sent unless explicitly re-armed by a user override Given quiet hours deferment rules apply When a nudge falls into quiet hours Then it is deferred (not duplicated) and the deferment is recorded with next-at timestamp
Job Timeline and Aggregate Metrics Dashboard
Given a user opens a job’s Nudge Activity When events exist for that job Then a chronological timeline displays Sent, Delivered, Bounced, Clicked, Actioned, and Suppressed with timestamps, channel, recipient, and reason And selecting an event reveals structured details and provider payload snippets with secrets redacted And a freshness indicator shows last updated within 2 minutes Given a user opens the Nudge Metrics dashboard When a date range, channel, role, property, and vendor filters are applied Then the dashboard shows Send Volume, Delivery Rate, Bounce Rate, Click-Through Rate, Action Rate, and Median Time-to-Action And metrics match event logs within 1% for the same filters And the user can export current view as CSV and PNG
Metrics APIs for Jobs and Summary
Given an API client with scope read:metrics When it calls GET /v1/metrics/jobs/{jobId}/nudges Then the API returns 200 with a paginated list of events including type, channel, timestamps, correlationIds, and reasons within 500 ms p95 Given the client calls GET /v1/metrics/summary with from, to, channel, role, propertyId filters When the request is valid Then the API returns aggregate counts and rates (send, delivered, bounced, clicked, actioned, median time-to-action) that reconcile with the dashboard within 1% And results are cacheable for 60 seconds with ETag support And rate limits enforce 600 requests/min per token and return 429 with Retry-After when exceeded And the OpenAPI spec documents fields, filters, and schemas
Alerts for Systemic Failures and Elevated Bounce Rates
Given the system continuously evaluates delivery and engagement metrics When any of the following occur: - Bounce rate >= 5% over a 15-minute window with >= 100 sends - Webhook verification failures >= 1% over 5 minutes or >= 10 consecutive failures - No inbound webhooks for 10 minutes during active send periods - Delivered-to-click rate drops >= 50% versus 7-day baseline over 30 minutes with >= 200 delivered Then an alert is emitted to Slack #ops and on-call email within 2 minutes, including segment, timeframe, provider, recent deploy hash, and runbook link And alerts auto-resolve when metrics remain below thresholds for one full evaluation window And all alerts are logged with incidentId for auditing
Consent, Opt-Out, and Compliance Controls
"As a compliance officer, I want consent and opt-out honored across all nudge channels so that we meet legal obligations and maintain user trust."
Description

Manage per-user consent for SMS, email, and push/DM with granular preferences and easy opt-out mechanisms (e.g., reply STOP). Enforce frequency caps, content tagging, and jurisdictional rules (TCPA, GDPR) including quiet-hour legal requirements where applicable. Maintain immutable audit logs of consent changes and message content, handle carrier/ESP suppression lists, and provide admin tooling to export records for compliance inquiries. Integrates with routing, scheduling, and tracking to ensure only compliant nudges are sent.

Acceptance Criteria
Per-Channel Consent Preferences Management
Given a user with no prior explicit consent recorded for SMS, email, and push/DM When the user sets channel preferences (opt-in or opt-out per channel) via profile settings or a one-tap consent prompt Then the system stores the consent state per channel with timestamp (UTC), actor, source (UI/API/keyword), IP (if available), and jurisdiction context And the routing engine immediately (<=60s) enforces the new consent state on subsequent nudges And a read API returns the updated consent state per channel within 1 second of write completion And attempting to send a nudge on an opted-out channel is blocked with reason "no_consent" logged and exposed in send diagnostics
SMS Opt-Out (STOP) and Re-Opt-In (START/UNSTOP)
Given a contact with SMS consent = opted-in When the contact replies with any standard opt-out keyword (STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT) Then SMS consent is immediately set to opted-out, the number is added to SMS suppression, and a single confirmation message is sent with required opt-out confirmation text And any queued or future SMS nudges are blocked with reason "sms_opt_out" and no further SMS are sent When the contact later replies START or UNSTOP from the same number Then SMS consent is restored to opted-in, suppression is cleared, and a single confirmation opt-in message is sent And all events (opt-out/opt-in) are logged with timestamp, keyword, and message IDs
Frequency Caps Enforcement Across Channels
Given system-wide frequency caps configured (e.g., SMS: 3/day, Email: 5/day, Push/DM: 10/day) per user and per conversation When the Nudge Ladder attempts to send the Nth+1 message on a channel within the rolling 24h window Then the send is prevented, a "cap_exceeded" event is recorded with counts and window start/end, and the nudge is rescheduled to the next available legal window And an override flag is ignored unless user has explicit emergency consent configured for that channel And cap counters reset automatically when the rolling window elapses And an analytics endpoint reports current counts and remaining quota per user/channel
Quiet Hours and Time Zone Compliance
Given a user's legal jurisdiction and time zone are known or inferred and quiet hours are configured per jurisdiction When the current local time for the recipient falls within quiet hours Then no nudges are sent on any channel subject to quiet-hour rules; each attempted send is queued with reason "quiet_hours" and a scheduled send time at the next legal minute outside the window And the system adjusts for daylight saving time changes automatically And if jurisdiction is unknown, the system applies the most restrictive default policy until resolved And an audit entry records the applied jurisdiction, local time, and defer-until timestamp
Immutable Consent and Messaging Audit Logs
Given any consent change, message send, suppression, block, or reschedule event occurs When the event is persisted to the audit log Then the record includes event type, actor/system, user and channel identifiers, jurisdiction context, full message content (or cryptographic digest and secure pointer), reason codes, and precise UTC timestamp And the log store is append-only (WORM); update/delete operations are rejected and raise an error with no mutation of existing records And authorized auditors can retrieve a complete, time-ordered history for a user within 2 seconds for the last 12 months And all records contain tamper-evident hashes to verify integrity during export
Carrier/ESP Suppression List Enforcement
Given a phone number or email is present on a carrier/10DLC or ESP suppression list due to prior complaints, hard bounces, or spam classification When the system attempts to send a nudge to that destination Then the send is blocked preflight with reason "external_suppression" and no API call to the carrier/ESP is made And inbound webhook events (STOP, bounce, spam complaint) update local suppression within 60 seconds of receipt And removal from suppression requires an authorized admin action with justification, and the change is logged to the audit trail And diagnostics expose the suppression source (carrier/ESP/local) and last event timestamp
Compliance Records Export for Inquiries
Given a compliance admin with the appropriate role requests an export for a specific user or property and date range When the export is initiated Then the system generates a package (JSON and CSV) containing consent history, message contents and metadata, delivery/suppression events, jurisdiction determinations, and frequency/quiet-hour decisions within 15 minutes And the package is delivered via a signed URL that expires in 24 hours and includes a manifest with checksums for each file And an audit log entry records who requested the export, the scope, and the download access events And PII included in the export is limited to what is required to satisfy the inquiry and is clearly labeled by data category

SLA AutoTune

Data‑driven targets that adapt by property and issue type using your historical cycle times, vendor lead times, building rules, and seasonality. Get realistic SLA recommendations with confidence bands and apply with one tap. Hit more SLAs by setting goals teams can actually meet.

Requirements

Historical Performance Data Aggregation
"As an operations manager, I want FixFlow to aggregate clean historical and contextual data so that SLA recommendations reflect the true performance patterns of my portfolio."
Description

Build a robust data pipeline that consolidates past maintenance requests and lifecycle timestamps (opened, acknowledged, dispatched, scheduled, completed), vendor assignments, vendor response/completion times, cancellations, property attributes, issue categories, seasonality signals (e.g., month, weather), and building rules (access windows, quiet hours) into a normalized analytics store keyed by property and issue type. Include data quality validation, deduplication, outlier handling, and privacy safeguards. Expose a contract-stable schema and APIs for the SLA modeling service and ensure daily refresh with backfill support for late-arriving data.

Acceptance Criteria
Daily Refresh and Backfill Processing
- Given the pipeline is scheduled to run daily at 02:00 UTC, when the run completes, then all partitions for the prior calendar day are updated within 60 minutes with a success rate >= 99.5% across active properties and issue types. - Given late-arriving source events up to 30 days old, when detected, then affected records and aggregates are backfilled in the next scheduled run and visible in the analytics store within 24 hours. - Given a rerun for an already-processed date range, when executed, then the process is idempotent and produces identical record counts and checksum hashes to the last successful publish. - Given a source ingestion shortfall, when fewer than 95% of expected records arrive for any source, then the publish step is marked degraded and an automatic retry is queued within 2 hours.
Contract-Stable Analytics Schema and API
- Given the SLA modeling service calls GET /v1/analytics/sla-inputs with a valid API key, when the request is processed, then the response schema matches OpenAPI spec v1.0.0 and includes required fields: propertyId, issueType, openedAt, acknowledgedAt, dispatchedAt, scheduledAt, completedAt, vendorId, vendorResponseMinutes, vendorCompletionMinutes, cancellationFlag, accessWindows, quietHours, month, weatherCode; and responds within 500 ms p95. - Given a non-breaking field addition, when released, then a new minor schema version is published without removing or renaming existing fields, and existing v1 clients continue operating without code changes. - Given a breaking change, when required, then it is released only as /v2, with /v1 remaining available and contract-tested for at least 90 days; SLA modeling service contract tests pass against both versions. - Given an invalid request (missing required parameters or bad types), when received, then the API returns HTTP 400 with structured error codes and no partial data payload.
Normalized Fact and Aggregated Views Keyed by Property and Issue Type
- Given raw maintenance requests and lifecycle events, when transformed, then a canonical requests table stores one row per request with columns: requestId, propertyId, issueType, openedAt, acknowledgedAt, dispatchedAt, scheduledAt, completedAt, cancelledAt (nullable), vendorId (nullable), vendorAssignedAt (nullable), responseMinutes, dispatchMinutes, scheduleLagMinutes, completionMinutes. - Given the canonical requests table, when aggregated daily, then an analytics view keyed by (propertyId, issueType, date) provides: totalRequests, completedRequests, cancellationRate, p50/p90/p95 for responseMinutes and completionMinutes, and vendorLeadTimeMinutes, emitting zero-count rows for active properties/issue types with no requests. - Given requests with multiple vendor segments, when transformed, then request-level metrics are computed on the final completing vendor assignment, and a separate vendor_segments table captures per-segment metrics without double-counting requests in aggregates.
Data Quality Validation and Deduplication
- Given incoming source records, when ingested, then deterministic deduplication using (sourceSystem, externalId, eventType, eventTimestamp) yields a duplicate rate <= 0.1% over the trailing 7 days. - Given transformed records, when validated, then thresholds are met: not-null for requestId/propertyId/issueType/openedAt >= 99.9%; chronological ordering holds in >= 99.5% of applicable rows (openedAt <= acknowledgedAt <= dispatchedAt <= scheduledAt <= completedAt); referential integrity to property and vendor dimensions >= 99.8%. - Given validation failures exceed any threshold, when detected, then the publish step is blocked and the run status is set to failed with categorized error summaries. - Given each pipeline run, when completed, then a validation report is stored including total rows, deduped rows, failed checks by type, and 100 sampled offending records per check.
Outlier Detection and Handling for Cycle Times
- Given request-level cycle time fields, when outliers are computed per (propertyId, issueType, month) using the 1.5×IQR rule, then outlierFlag is set for affected rows and these rows are excluded from percentile metrics by default. - Given aggregate metrics are computed, when excluding outliers, then p50/p90/p95 reflect non-outlier rows and an outlierRate metric is published alongside totals. - Given includeOutliers=true is provided to the API, when aggregates are requested, then metrics include outliers and are labeled with outliersIncluded=true in the response. - Given anomalous durations (negative values or > 365 days), when detected, then rows are quarantined (published=false) with anomalyReason set, and excluded from all aggregates.
Privacy Safeguards and PII Minimization
- Given tenant PII in source systems, when transformed, then the analytics store contains no plaintext PII (names, phone numbers, emails, free-text notes); tenant identifiers are replaced with salted hashes or removed, and free-text fields are redacted using pattern-based scrubbing. - Given data at rest, when stored, then analytics tables are encrypted (AES-256) and access is limited to analytics roles; row-level security prevents cross-tenant access by organizationId. - Given API responses, when returned, then no PII fields are present and all identifiers are opaque UUIDs or hashed tokens. - Given a periodic privacy scan, when executed weekly, then zero columns classified as PII are detected in analytics schemas; any finding blocks further publishes until remediated.
Seasonality, Weather, and Building Rule Enrichment
- Given each request record, when enriched, then seasonality fields (month, weekOfYear), weather features (avgTempC, precipitationCode) based on property geolocation and event date, and building rules (accessWindows, quietHours) are joined with a match rate >= 98% for properties with known coordinates and rules. - Given enrichment inputs are missing, when encountered, then defaults (e.g., weatherCode=UNKNOWN) are applied without failing the run, and missingEnrichmentRate is recorded and published. - Given vendor performance metrics, when calculated, then leadTimeMinutes per (vendorId, propertyId, issueType) over the last 180 days are computed with minSampleSize >= 5; combinations below the threshold are suppressed from aggregates. - Given source updates to building rules, when received, then the next daily run reflects changes in the analytics store within 24 hours.
Per-Property SLA Modeling & Confidence Bands
"As a property manager, I want data-driven SLA targets with confidence bands so that I can set goals my team can realistically meet and defend to stakeholders."
Description

Implement a modeling service that generates recommended SLA targets (acknowledge, dispatch, schedule, resolve) per property and issue type using historical cycle times, vendor lead times, building rules, and seasonality patterns. Compute confidence bands/prediction intervals and display rationale (key drivers, sample sizes, recent trends). Version outputs, support retraining on schedule and on-demand, and provide APIs for retrieval by UI and policy engine.

Acceptance Criteria
Per-Property & Issue-Type SLA Target Generation
Given a property_id and issue_type with ≥30 completed tickets in the last 12 months When the modeling service generates recommendations Then it outputs numeric targets (hours) for acknowledge, dispatch, schedule, and resolve And targets incorporate historical cycle times, vendor lead times, building rules, and seasonality patterns And targets respect configurable per-stage min/max bounds And the response includes property_id, issue_type, targets, computed_at (UTC), data_window_start, data_window_end, model_version And if sample_size < 30, the service falls back to property-level, then portfolio-level baselines and returns fallback_level and fallback_reason
Confidence Bands & Prediction Intervals
Given generated SLA targets for a property_id and issue_type When computing uncertainty for each stage Then the response includes 50%, 80%, and 95% prediction intervals per stage as {low, high} And intervals are monotonic (p50 ⊂ p80 ⊂ p95) with non-negative bounds And backtesting over the last 6 months shows empirical coverage within ±5 percentage points of nominal for pairs with n ≥ 30 And the response includes interval_method and backtest_window metadata
Rationale & Explainability Metadata
Given a request for a recommendation payload When the API returns the recommendation Then it includes sample_size per stage, data_window, key_drivers (top 5 with importance and direction), recent_trends (90-day slope per stage), vendor_lead_time_summary, and building_rule_overrides_applied And rationale fields exclude PII and are present for n ≥ 10 And explanations are reproducible for a fixed model_version and data_cutoff
Versioning & Auditability of Outputs
Given model training completes When recommendations are persisted Then each record includes model_id, model_version (semver), schema_version, trained_at (UTC), data_cutoff, and content_hash And GET supports requesting a specific version via ?version= and the latest via ?version=latest (default) And prior versions are retained for ≥90 days and are immutable And all accesses and writes are audit-logged with actor, timestamp, and job_id And DELETE operations for recommendations are disallowed
Scheduled & On-Demand Retraining
Given a tenant defines a cron schedule in their timezone When the schedule triggers Then a retraining job starts within 10 minutes and completes within 60 minutes P95 for up to 10k property-issue pairs And a new model_version is published atomically upon success And health metrics (duration, sample counts, coverage deltas) are emitted When POST /api/v1/sla/retrain is called with scope={tenant_id|property_id} and dry_run=false Then a job_id is returned, concurrent jobs per tenant ≤ 1, and duplicate triggers are deduplicated And failures set job status to failed, emit alerts, and leave the last good version active
Building Rules & Vendor Lead-Time Integration
Given building access rules (e.g., no entries after 18:00, weekend restrictions) and vendor lead times by trade When computing schedule and resolve targets Then targets are adjusted to honor rules and lead times by shifting deadlines to the next allowable window And the response includes adjustment_details with rule_ids and lead_time_sources And invalid or conflicting rule configurations return 400 with validation_errors And configuration changes take effect within 15 minutes of update
API Retrieval for UI & Policy Engine
Given GET /api/v1/sla/recommendations?property_id=X&issue_type=Y When a valid request is made Then return 200 with targets, prediction_intervals, rationale, and versioning metadata conforming to the published schema And P95 latency ≤ 300 ms and P99 ≤ 800 ms at 200 RPS per region And support batch retrieval via POST /api/v1/sla/recommendations/batch for up to 100 pairs And support ETag/Last-Modified caching and conditional requests And return 404 when no data and fallbacks are disabled; else 200 with fallback_level and fallback_reason And monthly availability ≥ 99.9%
One-Tap Apply & Versioned SLA Policies
"As a portfolio lead, I want to apply recommended SLAs with one tap and roll them back if needed so that I can iterate quickly without risking service quality or compliance."
Description

Deliver UI and backend workflows to preview model recommendations, compare against current policies, and apply with a single action to selected properties, issue types, or the entire portfolio. Maintain versioned policies with audit trails (who, what, when), rollback capability, effective-dating, and staged rollouts (pilot cohorts). Enforce role-based permissions and capture deployment notes for compliance.

Acceptance Criteria
Preview vs Current SLA Policy Diff
Given a user with "Manage SLAs" permission opens SLA AutoTune for a chosen scope (property/issue type/portfolio) When they select "Preview Recommendations" Then the UI displays recommended SLA targets with 50/80/95% confidence bands And the current policy values are shown side-by-side And per-field deltas are highlighted (absolute and percent) And only entities within the selected scope are included And the preview generation completes within 3 seconds at p90
One-Tap Apply to Selected Scope
Given a user with "Manage SLAs" selects properties and/or issue types (or entire portfolio) and confirms Apply When they tap "Apply" Then the system writes a new policy version for each affected entity with the chosen effective date/time And the operation is atomic across the selection: all targeted entities succeed or none are changed And a confirmation summary displays counts of updated properties and issue types And the request is idempotent within 10 minutes using a requestId to prevent duplicate updates And the apply completes within 30 seconds at p95 for up to 5,000 policy rows
Versioning and Audit Trail Creation
Given any policy apply, rollback, or edit action When the action completes Then an immutable audit record is created capturing user, timestamp (UTC), action type, scope, before/after values, versionId, requestId, and deployment notes And audit records are queryable by date range, user, property, issue type, and versionId And audit entries are visible in the Change History UI within 5 seconds of commit
Rollback to Previous Version
Given a user with "Manage SLAs" views Change History and selects a prior version When they click "Rollback" Then a new version is created that clones the selected version's policy values (excluding timestamps and user) And the rollback can be scheduled for a future effective date or applied immediately And the current active version is superseded at the effective date And an audit record links rollbackVersionId to sourceVersionId
Effective-Dated Policy Activation
Given a user sets a future effective date/time (using the property's local time zone) for a new policy version When the scheduled time arrives Then the new version becomes active for the selected scope And prior versions remain preserved and marked as superseded And if the schedule is canceled before the effective time, no changes activate And activation jobs retry up to 3 times on transient errors and alert on final failure
Pilot Cohort Staged Rollout
Given a user defines a pilot cohort (subset of properties/units) for the selected scope When they apply recommendations to "Pilot only" Then only the pilot cohort receives the new active version; non-pilot remains unchanged And the system displays pilot coverage (count and percentage) in the rollout banner When the user promotes to "Full rollout" Then the same version is applied to the remaining scope with a new versionId and an audit link back to the pilot rollout
Role-Based Permissions and Mandatory Deployment Notes
Given a user without "Manage SLAs" permission When they attempt to apply, schedule, or rollback policies Then the system returns 403 Forbidden and no changes are made Given a user with "Manage SLAs" permission When they apply, schedule, or rollback policies Then they must enter deployment notes of at least 10 characters before confirmation And the notes are stored with the audit record and included in history exports
Vendor Lead Time Sync & Calendar Awareness
"As a maintenance coordinator, I want SLA recommendations to account for real vendor availability so that targets remain achievable during peak seasons or constrained schedules."
Description

Integrate vendor calendars and availability feeds to maintain live lead-time estimates by trade and geography. Ingest capacity limits, blackout dates, travel buffers, and double-booking risks, and feed these signals into SLA calculations and confidence bands. Support near-real-time updates on change, nightly reconciliation, and fallback heuristics when vendors lack connected calendars.

Acceptance Criteria
Live Lead-Time Update by Trade & Geography
Given a vendor calendar is connected and mapped to a trade and service area, When a new booking is added or an event is updated in the vendor’s calendar, Then the lead-time estimate for that trade and service area is recalculated and available in the system within 5 minutes. Given a property address in ZIP code X, When viewing SLA AutoTune inputs, Then the displayed lead time reflects the vendor(s) serving ZIP X and shows a last refreshed timestamp no older than 15 minutes. Given the computed lead time changes by ≥10%, When SLA recommendations are next viewed for any affected property/issue type, Then the SLA target and its confidence band are recalculated and updated within 2 minutes.
Capacity and Blackout Enforcement
Given a vendor publishes daily capacity limits, When FixFlow generates available appointment slots, Then no offered day exceeds the vendor’s capacity and the computed lead time shifts to the next day with available capacity. Given a vendor defines blackout dates or hours, When scheduling or computing lead time, Then times within blackout are excluded from availability and increase lead time if necessary. Given a suggested slot was excluded due to capacity or blackout, When viewing availability explanations, Then the reason code explicitly states “Capacity reached” or “Blackout” for that slot.
Travel Buffers and Geography-Aware Slotting
Given two appointments in different ZIP codes, When computing available slots for the same vendor, Then a travel buffer (default 30 minutes, configurable per vendor) is enforced between events and any violating slot is excluded from suggestions. Given the next feasible slot requires cross-zip travel time exceeding the buffer, When lead time is computed, Then the lead time is increased to the earliest slot that satisfies the travel time constraint. Given travel constraints affected availability, When the user inspects SLA inputs, Then the UI indicates “Travel buffer applied” for the impacted date/time.
Near-Real-Time Change Propagation and Nightly Reconciliation
Given a connected calendar event is created, updated, or deleted, When FixFlow receives a webhook or detects change on poll, Then the corresponding availability, double-booking risk, and lead time are updated within 5 minutes. Given any vendor with a connected calendar, When nightly reconciliation runs, Then calendars are fully re-synced and discrepancies are corrected by 03:00 local time with an audit log of added/updated/deleted events. Given the calendar provider API is unavailable, When a sync attempt fails, Then the system retries with exponential backoff for up to 1 hour and marks the vendor’s availability status as “stale” with the last successful sync timestamp.
Fallback Heuristics for Unconnected Vendors
Given a vendor has no connected calendar, When computing lead time, Then the estimate is derived from the vendor’s last 90 days median scheduling lead time; if fewer than 10 jobs exist, use the trade+region median; if unavailable, use the admin-configured default (default 2 business days). Given a heuristic-based estimate is shown, When viewing SLA inputs, Then the lead time is labeled “Estimated (no calendar)” and includes a 68% confidence band computed from the historical distribution used. Given the vendor later connects a calendar, When the first successful sync completes, Then heuristic estimates are replaced by calendar-derived lead times within 5 minutes and the label changes to “Live”.
Double-Booking Detection and Prevention
Given a proposed appointment time overlaps an existing calendar event by 5 minutes or more, When attempting to schedule, Then the slot is blocked and flagged with reason “Double-booking risk”. Given a scheduler chooses to override a conflict (if allowed by role), When saving the appointment, Then the system records an override audit entry with user, timestamp, and conflict details and still updates double-booking risk status. Given the system detects overlapping events introduced by external calendar changes, When lead time is recomputed, Then affected slots are removed from availability within 5 minutes and the earliest conflict-free slot is used to recompute lead time.
Rule Overrides & Business Constraints Engine
"As an admin, I want to enforce building rules and policy guardrails on SLAs so that recommendations never violate operational or compliance constraints."
Description

Provide a constraints engine to encode building-specific rules (access windows, union rules, doorman hours, quiet hours) and account-level policies (premium tenants, emergency categories, compliance requirements). Allow admins to set hard bounds and manual overrides that constrain or supersede model outputs, with clear justification and impact shown in the UI. Ensure constraints are testable, auditable, and applied consistently across recommendation and application flows.

Acceptance Criteria
Enforce Doorman Access Windows in Scheduling
Given a property has doorman access hours set to 09:00–17:00 local time and the access window constraint is enabled When the system generates appointment recommendations for any work order at that property Then 100% of proposed time slots fall within 09:00–17:00 local time and each slot displays an "Access Window" reason tag Given a user attempts to apply a time outside the configured access window When they click Apply Then the system blocks the action, displays an error referencing "Doorman Hours" with the next three valid slots, and no schedule is saved Given the same work order When the user opens "Preview impact" Then the UI shows a numeric count of proposed slots removed due to the access window (count > 0) and the earliest available compliant slot
Manual SLA Override for Premium Tenants
Given an account policy marks the tenant as "Premium" and defines manual SLA overrides of Response = 2h and Resolution = 24h When SLA AutoTune generates SLA recommendations for a work order involving a premium tenant Then the recommended SLAs equal 2h and 24h respectively and model baseline values are not used for the final recommendation Given the overrides include a justification text "Premium service level" When the admin clicks Apply Then the confirmation shows previous model values, final override values, and percent deltas, and the override justification is saved and displayed with a "Manual Override" badge Given the admin disables the manual override and re-runs recommendations When they view the recommended SLAs Then the values revert to model-derived outputs and the override badge no longer appears
Vendor Union Rules and Lead Time Bounds Enforcement
Given a property has a hard-bound union rule "No weekend work" and a hard-bound vendor lead time ">= 2 business days" When the system generates proposed appointment dates Then no Saturday or Sunday dates are proposed and the earliest date is at least 2 business days from now Given vendor external availability includes Saturday slots When availability is merged with constraints Then all Saturday slots are excluded and the filtered list includes a reason code "Union Rule" Given no valid slots remain after applying hard bounds When recommendations are requested Then the system returns a "No valid slots" state with a list of violated constraints and no slot is schedulable
Quiet Hours Compliance and Admin Bypass Control
Given a property defines quiet hours 20:00–08:00 and the issue type is flagged as "Noisy Work" When scheduling recommendations are generated Then any time slot overlapping quiet hours is excluded from the recommendation list Given a Scheduler user manually enters a time overlapping quiet hours When they click Apply Then the system blocks the action and shows an error referencing "Quiet Hours"; only Admins may proceed by entering a mandatory "Override reason" and selecting "Temporary override for this work order only" Given an Admin completes a quiet-hours override When the schedule is saved Then the appointment is created with an "Override" badge, the provided reason is displayed on the work order, and an audit log entry is recorded
Constraint Scope, Hard-Bound Flag, and Precedence Resolution
Given constraints exist at multiple scopes (account policies, property rules) and constraints can be flagged as Hard Bound When the system calculates SLAs and schedules Then it applies all Hard Bounds first, then Manual Overrides (permitted only if they do not violate Hard Bounds), then remaining scoped rules/policies, and finally model outputs Given a manual SLA override of Response = 2h would imply scheduling within < 1 business day while a Hard-Bound lead time ">= 1 business day" is active When the admin attempts to apply the override Then the system blocks the change with an error "Violates hard bound: Lead time" and no changes are saved Given multiple non-hard constraints conflict When recommendations are generated Then the most restrictive feasible result is selected and the UI shows an ordered "Applied constraints" list with evaluation order and effective values
Cross-Flow Consistency: Recommendations vs Apply
Given a work order at a property with active access window and quiet hours constraints When the system generates recommendation slots Then the earliest recommended slot equals the earliest feasible slot under the same constraints used by the Apply flow Given a user bypasses recommendations and attempts to Apply a time that violates any active constraint When they submit the Apply action Then the Apply flow enforces the same constraint checks and blocks or adjusts in the same manner as the recommendation flow, with identical reason codes Given constraints are edited and saved When the user re-runs both the recommendation and Apply flows Then both reflect the updated constraints in outputs and in the "Applied constraints" explanation panel
Audit Trail, Justification, and Impact Visibility
Given any constraint is created, edited, or deleted When the change is saved Then an audit log entry is recorded with user ID, UTC timestamp, before/after values, scope, hard-bound flag, and optional justification text Given a recommendation or apply action is executed and affected by constraints When viewing the work order audit trail Then there is an entry listing evaluated constraints, those applied, reason codes, final outcome, and a correlation ID linking to the constraint configuration version Given a user views "Why?" on a constrained recommendation When the explanation is expanded Then the UI lists applied constraints with labels and scopes and shows quantitative impact (e.g., "Removed N of M slots", "SLA adjusted by +Xh"), and these values match the underlying computation
Closed-Loop SLA Performance Learning
"As a team lead, I want the system to learn from actual outcomes and propose updated targets so that our SLA goals stay achievable as conditions evolve."
Description

Instrument applied SLAs to track adherence, measure hit rates against recommendations and confidence bands, and detect drift by season, property, issue type, and vendor mix. Generate suggested adjustments when performance deviates persistently and capture user feedback on accepted/rejected suggestions to improve future recommendations. Provide dashboards and alerts for underperforming segments and a safe review queue for proposed updates.

Acceptance Criteria
SLA Adherence Instrumentation by Segment
Given a work order has an applied SLA target (response and/or resolution) When the work order is created or the SLA target is updated Then the system records the SLA target id and version, target durations, recommendation id, and confidence band bounds on the ticket And records segment dimensions (property_id, issue_type, vendor_id nullable, building rules id, season/week) and key timestamps (created_at, first_response_at, scheduled_at, resolved_at) And all events are persisted to the analytics store within 15 minutes And tickets missing any required fields are flagged as data_incomplete and excluded from hit-rate denominators And per-ticket SLA outcomes (hit/miss for response and resolution) are computed on milestone events and exposed via API and dashboard
Hit Rate vs Confidence Bands Computation
Given a selected segment and time window (7/30/90 days) When computing SLA performance Then include only tickets with definitive outcomes (response_at or resolved_at present, as applicable) And exclude open/ongoing tickets from both numerator and denominator And compute hit_rate_response and hit_rate_resolution with denominators ≥ N_min (default 20); otherwise label as Insufficient Data And compare observed hit rates against the recommended SLA expected rate and 95% confidence band And assign band_position = Within/Below/Above per metric And display values identically across API, dashboard, and CSV export (difference = 0 when rounded to 2 decimals)
Persistent Drift Detection and Alerts
Given weekly aggregated SLA performance by segment When observed hit rate stays below the lower 95% confidence band for ≥ 3 consecutive full calendar weeks with ≥ 30 closed tickets per week Then create a drift_event with segment id, period, observed rate, expected rate, gap_pp, and evidence links And notify subscribed roles via in-app and email within 15 minutes And enforce an alert cooldown so a segment cannot alert more than once every 7 days unless the gap increases by ≥ 5 percentage points And mark the segment status = Underperforming until 2 consecutive weeks return to Within band
Suggestion Generation and Safe Review Queue
Given a segment has an active drift_event persisting for ≥ 2 consecutive weeks When generating a suggested SLA adjustment Then propose a new SLA target (duration) and include delta from current, expected post-change hit rate, confidence level, impacted volume, and rationale features (e.g., vendor lead times, seasonality) And place the suggestion in a Review Queue scoped to that segment And require RBAC: only Owner or Ops Manager can Approve/Reject/Edit And ensure suggestions are not auto-applied; effective date is next-new-ticket after approval And record decision, approver, timestamp, and change diff in an immutable audit log And expire suggestions after 30 days if no action And ensure applying the same suggestion twice is idempotent (no duplicate rules created)
Decision Feedback Capture and Learning
Given a reviewer Approves or Rejects a suggested SLA adjustment When recording the decision Then require selection of a reason code from a controlled list (Business Constraint, Vendor Availability, Data Quality, Seasonality Anomaly, Cost Impact, Other) and allow optional comment up to 500 characters And persist decision with user id, timestamp, segment id, suggestion id, action, reason code, and comment And publish a learning_event to the model feedback stream within 24 hours And expose a report of accept/reject rates and top reason codes by segment and period And include decision data in the analytics export API
Underperforming Segments Dashboard and Drilldowns
Given an authenticated user with Analytics access When viewing the SLA Performance dashboard Then display a ranked list of underperforming segments by gap to lower confidence band and hit rate, with filters for property, issue type, vendor, season, and date range And provide trend charts (weekly) and drill-down to the underlying ticket list with SLA outcomes And ensure data freshness latency ≤ 15 minutes from event time And allow CSV export that reproduces dashboard totals to within 0.01 tolerance And render correct local time zone based on organization settings And show an empty state with guidance when no segments meet underperformance criteria
Confidence Band Calibration and Monitoring
Given nightly model evaluation When computing calibration across segments for the past 90 days Then the percentage of segments whose observed hit rate falls within the predicted 95% band is between 90% and 98%; otherwise flag calibration_degraded And post calibration metrics and a reliability diagram to the Model Health panel And create an in-app notification to model owners if calibration_degraded persists for 3 consecutive nights And do not update recommendations for segments flagged calibration_degraded until the next successful evaluation run

Inspector Pack

One‑click export of time‑stamped SLA evidence: start/stop events, pause reasons, communications, approvals, and completion proof. Produces branded PDF/CSV packets that satisfy auditors, insurers, and city inspectors—no manual screenshot hunts or email dives required.

Requirements

One‑Click SLA Evidence Export
"As a property manager, I want to export a complete SLA evidence packet with one click so that I can quickly satisfy auditor or inspector requests without manual collection."
Description

Provide a single-action export control on Work Order detail and list views that compiles all SLA-related artifacts for the selected work order into a downloadable packet. The control defaults to the current work order scope and offers format selection (PDF or CSV). It validates user permissions, queues a quick export for single records, and presents a secure download link with a success notification. The export includes metadata (work order ID, property, vendor, time zone) and respects organization branding and redaction settings. The action is logged for traceability without exposing sensitive content.

Acceptance Criteria
Authorized User Exports SLA Evidence from Work Order Detail
Given I am signed in and viewing a Work Order detail page And my role has Export SLA Evidence permission and access to that Work Order When I click the One‑Click Export control Then the system validates my permission and Work Order access And the export request is accepted and queued And no sensitive data is displayed in the UI during the request Given I lack the permission or access When I attempt to invoke the control Then I see an authorization error message And no export job is created And a denied audit event is recorded
Export Format Selection and Scope Defaults
Given I am on a Work Order detail page When I open the One‑Click Export control Then the default scope is the current Work Order And the default format is PDF And the only selectable formats are PDF and CSV Given I select CSV format When I confirm export Then the packet is generated in CSV with fields equivalent to the PDF version And the download filename follows pattern FixFlow_<WorkOrderID>_SLA_<YYYYMMDDThhmmssZ>.<ext> And no PII (emails, phone numbers) appears in the filename
Export Packet Includes Required SLA Artifacts and Metadata
Given a Work Order with SLA‑related activity When I generate an export (PDF or CSV) Then the packet includes metadata: Work Order ID, property name and address, vendor name, and time zone used And it includes SLA evidence: start/stop timestamps, pause reasons with timestamps and actors, communications log (SMS/email) with timestamps and participants, approvals with approver and timestamp, and completion proof references (file names/IDs, thumbnails for PDF) And all timestamps are normalized to the property time zone and show the offset And all artifacts are ordered chronologically And if an expected artifact is missing, the packet displays "Not Available" rather than failing
Organization Branding and Redaction Applied to Export
Given organization branding is configured (logo, name, accent color) When I export to PDF Then the PDF shows the org logo and name in the header and uses the configured accent color for headings Given redaction settings hide tenant contact details and other PII When I export to PDF or CSV Then redacted fields are masked or omitted consistently across all sections And no unredacted PII appears in body content, headers, footers, or filenames
Export Queue, Success Notification, and Secure Download Link
Given I request a single‑record export containing up to 200 artifacts When the export is queued Then the job starts within 2 seconds and completes within 10 seconds at the 95th percentile And upon completion, I see a success toast stating the export is ready And I am presented with a secure, time‑limited download link (signed URL) And the link expires after 15 minutes or after one successful download, whichever occurs first And accessing the link after expiry returns 401/403 without revealing resource details
Audit Log Records Export Action Without Sensitive Content
Given any export attempt (success or denial) When the attempt occurs Then an audit log entry is recorded with timestamp, user ID, work order ID, format (PDF/CSV), outcome (success/denied/error), and client IP/user agent And the audit log contains no exported content, message bodies, or file attachments And redacted fields remain redacted in logs And admins can retrieve the log entry by time range and Work Order ID
One‑Click Export from Work Order List View (Single Record)
Given I am on the Work Orders list view And I have either selected a single row or opened the row action menu When I invoke One‑Click Export Then the export scope is exactly that Work Order And the same permission, format, content, branding/redaction, notification, and auditing rules apply as on the detail view And the list view remains loaded without a full page refresh
SLA Event Timeline & Pause Logic Normalization
"As a compliance reviewer, I want a normalized timeline with SLA pause logic applied so that I can demonstrate how response and resolution times were computed against policy."
Description

Aggregate and normalize all relevant events—request creation, assignment, scheduled window, vendor in-route, onsite start/stop, pauses with reasons, approvals, communications, and completion—into a single chronological timeline with consistent timestamps and time zones. Apply SLA rules to pause and resume the clock based on configured reasons (e.g., awaiting tenant response, weather delay) and surface calculated durations (total time, active SLA time, paused time). Validate event integrity, deduplicate overlapping entries from calendar and messaging subsystems, and flag missing or conflicting data with clear annotations in the export.

Acceptance Criteria
Unified Chronological Timeline Aggregation
Given a maintenance request with events from work order, calendar, messaging, and vendor app When the timeline is generated Then all events are combined into a single list ordered by event timestamp ascending And ordering is based on the event occurrence timestamp, not record creation time And each event includes event_type, source_system, actor, and ISO 8601 timestamp with timezone offset And events missing an occurrence timestamp are excluded from the timeline and counted in an export annotation of type "missing_timestamp"
Time Zone Normalization and DST Handling
Given events originate from multiple time zones And a property-level time zone is configured When the timeline is generated Then all displayed timestamps are converted to the property time zone with correct DST rules applied And each event retains its original source time zone as metadata And timestamps are rendered in the format YYYY-MM-DDTHH:mm:ssZZ And if the property time zone is not set, UTC is used and labeled accordingly
SLA Clock Pause/Resume Based on Configured Reasons
Given SLA pause reasons are configured (e.g., awaiting_tenant, weather_delay, waiting_parts) And a pause interval with a configured reason starts at T1 and ends at T2 When SLA time is calculated Then the SLA clock is paused from T1 inclusive to T2 exclusive And the paused duration for that interval equals T2 minus T1 And if a pause starts but has no end, the SLA clock remains paused until the next resume event or completion And pauses with non-configured reasons do not pause the SLA clock And all applied pause intervals are listed in the export with reason, start, end, and duration_seconds
Overlap Deduplication between Calendar and Messaging Subsystems
Given two or more events refer to the same logical milestone (e.g., onsite_start) and share the same job identifier And their timestamps are within a 5-minute window of each other When the timeline is generated Then a single consolidated event is produced for that milestone And the consolidated event uses the earliest timestamp among the merged events And the consolidated event metadata lists all contributing source_systems and event_ids And the export includes dedup_count indicating how many events were merged
Duration Calculations: Total, Active, and Paused Time
Given a request has a first timeline event at T0 and a completion event at Tn And there are zero or more pause intervals When durations are calculated Then total_time_seconds equals Tn minus T0 in seconds And paused_time_seconds equals the sum of all non-overlapping pause intervals in seconds And active_sla_time_seconds equals total_time_seconds minus paused_time_seconds And the export includes all three values in both seconds and HH:mm:ss formats And validation enforces 0 <= paused_time_seconds <= total_time_seconds and active_sla_time_seconds >= 0
Data Integrity Flags and Conflicts Annotation in Export
Given the timeline contains data issues such as overlapping pauses, end before start, or out-of-order events When the export is generated Then each issue is listed in an annotations section with fields: type, severity, event_id(s), and message And conflicting events remain in the timeline but are marked conflict=true in metadata And the export summary includes per-annotation-type counts and a total count And integrity_status is set to ok when total count equals 0, otherwise issues_found
Export Completeness and Formatting (PDF/CSV)
Given a normalized timeline is available for a request When exporting to CSV Then CSV contains columns: event_id, event_type, timestamp_local, timestamp_utc, source_systems, actor, pause_reason, conflict, annotations And a summary row or section provides total_time, active_sla_time, paused_time in seconds and HH:mm:ss When exporting to PDF Then the PDF includes branded header (logo and property name), a chronological timeline table with the same fields, and a summary section with calculated durations And all timestamps display the property time zone abbreviation
Branded PDF/CSV Packet Builder
"As a landlord brand admin, I want exported packets to carry my branding and be available as PDF and CSV so that recipients recognize the source and can analyze the data easily."
Description

Generate auditor-ready packets in PDF and CSV formats using organization-level branding (logo, colors, header/footer) and reusable templates. The PDF includes a cover summary (property, work order, vendor, SLA targets vs actuals), a detailed timeline, threaded communications (SMS/email) with participants labeled, approvals with signatory details, and completion proof (photos, signatures). CSV exports provide structured rows for events and communications with canonical field names for external analysis. Include page numbers, section bookmarks, image handling, and hyperlinks back to FixFlow records while honoring redaction and scope settings.

Acceptance Criteria
Branded Template and PDF Navigation
Given an organization has configured a logo, brand colors, and header/footer text and a packet template named "Auditor Default" is selected at the org level When a user clicks Export > Inspector Pack for a specific work order Then the generated PDF applies the organization logo, colors, and header/footer to every page according to the selected template And the selected template is saved and available for reuse by all members of the organization And the PDF includes section bookmarks for Cover, Timeline, Communications, Approvals, and Completion Proof And every page footer displays "Page X of Y" And all hyperlinks in the PDF to FixFlow records are clickable and navigate to the correct record And the exported filenames are {orgShortName}_{workOrderId}_InspectorPack.pdf and {orgShortName}_{workOrderId}_events.csv and {orgShortName}_{workOrderId}_communications.csv
Cover Summary and SLA Targets vs Actuals
Given a work order has property details, vendor assignment, and SLA targets with recorded event timestamps When the packet is exported Then the PDF cover displays property name and address, work order ID and title, and vendor name And it shows SLA targets and actuals for response and resolution times And SLA actuals exclude paused durations and are labeled Met/Not Met And all timestamps show the property’s timezone abbreviation And the cover includes a link back to the work order in FixFlow
Detailed Timeline with Pause Reasons
Given the work order has start, stop, pause, resume, and completion events with optional pause reasons When the packet is exported Then the PDF Timeline lists all events in strict chronological order with ISO 8601 timestamps, actor, event type, and pause reason when applicable And the Timeline summarizes total elapsed, paused, and working durations And each timeline entry includes a hyperlink to the corresponding FixFlow event record And the events CSV includes one row per event with canonical columns exactly: work_order_id, event_id, event_type, timestamp_iso, actor_id, actor_role, duration_seconds, pause_reason_code, link_url
Threaded Communications with Participants Labeled
Given there are SMS and email messages among tenants, vendors, and managers for the work order When the packet is exported Then the PDF Communications section displays messages grouped by thread in chronological order with sender full name and role (Tenant, Vendor, Manager), channel (SMS/Email), direction (Inbound/Outbound), and timestamp And message attachments are shown as thumbnails with filename captions and a link to the original file And the communications CSV contains canonical columns exactly: work_order_id, message_id, thread_id, channel, direction, timestamp_iso, sender_id, sender_role, recipient_ids, subject, body_plaintext, has_attachments, attachment_count, link_url
Approvals with Signatory Details
Given one or more approvals exist on the work order When the packet is exported Then the PDF Approvals section lists each approval with approver full name, title, organization, decision (Approved/Declined), method (e-sign/manual), timestamp, signature image (if available), and approval ID And each approval entry includes a hyperlink to the approval record in FixFlow And if no approvals exist, the section displays "No approvals recorded"
Completion Proof Photos and Signatures
Given completion photos and captured signatures are attached to the work order When the packet is exported Then the PDF Completion Proof section renders photos and signatures maintaining aspect ratio without cropping and with captions showing timestamp and uploader And images larger than the page width are automatically scaled to fit; smaller images are centered And each image and signature includes a hyperlink to the original file in FixFlow And multi-page image layouts do not overlap other content and preserve page margins
Redaction and Scope Settings Honored
Given the user selects scope filters and redaction rules (e.g., date range, exclude internal notes, hide tenant PII) for the export When the packet is exported Then both PDF and CSV omit all items outside the selected scope and apply redactions to configured fields And redacted values appear as blacked-out boxes in the PDF and as empty cells in CSV And hyperlinks to redacted records are removed And the cover displays a "Redactions Applied" note listing the active rules and a count of redacted fields
Evidence Integrity Seal & Verification
"As an insurance reviewer, I want tamper-evident seals and a verification link so that I can trust that the evidence packet hasn’t been altered."
Description

Create a tamper-evident integrity layer for each packet by hashing the compiled contents and storing the checksum and generation timestamp in FixFlow. Embed the checksum and a short verification URL within the PDF/CSV so recipients can validate authenticity. Ensure underlying audit logs and event records are immutable post-export while retaining a reference to the source versions. Include indicators in the packet when any source record has changed since export, prompting regeneration for up-to-date evidence.

Acceptance Criteria
Packet Hash and Timestamp Generation on Export
Given a user exports an Inspector Pack When the export completes Then the system generates a SHA-256 checksum over the canonical compiled packet contents and stores: checksum (hex), packet ID, exporter user ID, file type (PDF/CSV), and generation timestamp (UTC, ISO 8601) in FixFlow And a subsequent immediate recomputation of the checksum over the same compiled contents matches the stored checksum And if checksum storage fails, the export is aborted and an error message is returned to the user without producing a downloadable file And the checksum value is unique per packet instance and cannot be null
Checksum and Short Verification URL Embedded in Exports
Given a sealed packet is generated When the PDF is opened Then the first page footer contains the full SHA-256 checksum (hex), a short code (last 10 chars), and a short verification URL, and the PDF includes a scannable QR code pointing to that URL And when the CSV is opened Then the first two lines are comment metadata rows containing the checksum and short verification URL (e.g., # FixFlow-Checksum: <hex>, # FixFlow-Verify: https://fixflow.app/v/<code>) And the embedded checksum and URL exactly match the values stored in FixFlow for that packet
Online Authenticity Verification Endpoint
Given a recipient visits the short verification URL for a packet When they provide no file Then the page displays the packet’s checksum, generation timestamp, exporter, file type, and a verification status of "Unverified (file not provided)" When they upload the received PDF/CSV file Then the system recomputes the checksum using the same canonicalization rules and compares it with the stored checksum And if they match, the page shows status "Valid" with packet metadata; if they do not match, status "Invalid - content modified" is shown with guidance to request a new export
Seal Canonicalization Rules (Tamper-Evident)
Given a sealed packet file is produced When computing/verifying the checksum Then the algorithm hashes the compiled packet contents using deterministic ordering, normalized UTF-8 encoding, and excludes only the bounded Integrity Seal block that carries the checksum/URL And modifying any byte outside the Integrity Seal block causes verification to fail And modifying only the Integrity Seal block (e.g., altering the checksum text) causes verification to fail
Immutability of Source Audit Logs and Event Records Post-Export
Given a packet is sealed When a user attempts to edit any audit log or event record included in the packet Then the system prevents in-place edits and instead creates a new version while preserving the original version referenced by the packet And the packet stores immutable references (record IDs + version IDs) for all included items And only system processes can alter the sealed snapshot references; direct deletes/updates of referenced versions are blocked and logged
Change Detection Indicator and Regeneration Prompt
Given a packet is sealed When any source record included in the packet has a newer version created after the packet’s generation timestamp Then the packet’s verification page displays an "Out-of-date: source records changed since export" indicator with a list of changed items and timestamps And newly downloaded packets include a visible "Out-of-date — Regenerate for current evidence" banner on the first page/header row And authorized users see a "Regenerate Packet" action that produces a new packet with a new checksum and updates references
PII Redaction & Role‑Based Scope Controls
"As a property manager, I want to hide tenant PII and internal notes in the packet so that I can share evidence externally without exposing sensitive information."
Description

Provide configurable redaction controls to exclude or mask personally identifiable information (tenant names, phone numbers, emails, unit numbers) and internal-only notes from exports. Support role-based defaults (e.g., external share vs internal) and per-export overrides with a real-time preview. Persist organization-level policies and jurisdiction-specific presets. Clearly annotate redacted fields in the packet and include a disclosure statement. Ensure redaction is applied consistently across PDF, CSV, images, and threaded communications.

Acceptance Criteria
External Export Default Redaction Applied
Given an organization policy defines external exports must redact tenant names, phone numbers, emails, unit numbers, and internal-only notes When a user initiates an Inspector Pack export with scope "External" Then the live preview masks all configured fields with "[REDACTED]" across all sections And the generated PDF and CSV match the preview exactly And no unmasked instances of those fields appear in PDF text, CSV cells, threaded communications, or generated images And the packet includes a Redaction Summary listing redacted categories and counts by field type
Internal Export Role-Based Defaults
Given role-based defaults are configured so internal exports do not redact PII or internal-only notes unless overridden When a user exports an Inspector Pack with scope "Internal" Then the live preview shows tenant names, phone numbers, emails, unit numbers, and internal notes unredacted by default And switching scope from "Internal" to "External" immediately applies the external redaction defaults in the preview And the exported files mirror the selected scope's defaults with no deviation
Per-Export Override with Real-Time Preview
Given role-based defaults are applied for the selected scope When the user toggles the per-export override to unmask only unit numbers Then only unit numbers change from "[REDACTED]" to their actual values in the preview while other fields remain masked And the preview updates within 500 ms of the toggle And the exported PDF/CSV exactly match the preview And starting a new export reverts to role-based defaults (the override does not persist to organization policy)
Jurisdiction Preset Application (CCPA-CA)
Given the organization has jurisdiction preset "CCPA-CA" available When the user applies the "CCPA-CA" preset to an export Then names, emails, phone numbers, and unit numbers are masked with "[REDACTED]" regardless of role defaults And the preview displays a visible tag indicating the active preset And the disclosure statement in the packet references the applied preset by name and timestamp And the export artifacts (PDF, CSV) reflect the preset rules consistently
Redaction Annotations and Disclosure in Packet
Given any fields are redacted in the export When the packet is generated Then each redacted value is replaced in-line with the token "[REDACTED]" (not blank or null) in PDF and CSV And a disclosure statement appears on the packet cover page describing the redaction policy/preset and categories affected And a Redaction Summary section lists each category redacted and total occurrences And annotations do not alter document layout beyond replacing values (no page breaks or text overflow errors)
Cross-Format and Media Consistency
Given a work order containing tenant PII and internal-only notes When the same export content is produced as PDF and CSV and includes generated images (e.g., communication transcript screenshots) Then the exact same fields are masked with the identical token "[REDACTED]" across PDF text, CSV cells, and rendered images And no unredacted PII appears in PDF metadata, embedded image metadata (EXIF), or CSV file properties And a full-text search for a sample email and phone number returns zero matches across all exported files
Policy Persistence and Default Scope Controls
Given an admin updates the organization-level redaction policy and role-based defaults When the policy is saved Then subsequent exports by any user inherit the updated defaults according to selected scope (External or Internal) And the policy save is versioned and timestamped, and the active version is indicated in the export UI And jurisdiction-specific presets are stored and can be selected per export without modifying the base policy
Bulk Export & Background Processing
"As a portfolio manager, I want to export packets for many work orders in the background so that I can fulfill broad audit requests without waiting in the UI."
Description

Enable selection of multiple work orders by date range, property, vendor, or tag and process exports asynchronously as background jobs. Provide progress tracking, rate limiting, and retry logic. Deliver results as a secure downloadable ZIP with one packet per work order and an index CSV. Notify users via in-app notifications and email with expiring download links. Optimize for large datasets with pagination and memory-safe streaming to prevent timeouts and UI blocking.

Acceptance Criteria
Bulk Selection by Filters and Pagination
Given I filter work orders by any combination of date range, property, vendor, and tag When the result set spans multiple pages Then the list is paginated and shows the total matching count And selecting "Select all filtered" selects all matching work orders across all pages And the selected count equals the total matching count And selections persist when navigating between pages or refining filters that do not remove a selected item And deselecting any individual item decreases the selected count accordingly
Asynchronous Export Job Creation and Progress
Given I have at least one work order selected When I click "Export Inspector Pack" Then the system enqueues a background job and immediately returns a Job ID and initial state "Queued" And the UI shows a job card with the Job ID, created timestamp, total_count, processed_count=0, and 0% progress And page interactions remain responsive and I can navigate away without canceling the job And the job state transitions through Queued -> Running -> Completed or Failed And progress updates (processed_count, percentage) are visible without a full page reload at least every 10 seconds
Rate Limiting and Queue Capacity Enforcement
Given a per-user concurrency limit of 1 Running export job and a per-account limit of 3 Running export jobs When a user starts a new export while they already have a Running job Then the new job is accepted in state "Queued" and does not start until the Running job completes Given more than 5 export start requests are made by the same user within 1 minute When the next start request is submitted Then the request is rejected with HTTP 429 and an in-app message explaining the throttle window And no new job is enqueued Given the account already has 3 Running export jobs When any user in the account starts another export Then the new job remains in state "Queued" until a Running slot becomes available (FIFO)
Automatic Retry and Fault Handling
Given a transient failure occurs during packet generation (e.g., temporary storage or network interruption) When the job is Running Then the failed operation is automatically retried up to 3 times with exponential backoff And if a retry succeeds, processing continues and the final ZIP contains exactly one packet per work order (no duplicates) And if all retries are exhausted for any step, the job transitions to state "Failed" and records error details including work_order_id and reason And partial artifacts from the failed attempt are cleaned up from temporary storage
Secure ZIP Delivery with Index CSV
Given a job reaches state "Completed" When I open the job details Then I can download a single ZIP archive via a signed, expiring URL valid for 72 hours And the link requires authentication and authorizes only users from the originating account; unauthorized access returns 403 And after expiration the link returns 410 Gone And the ZIP root contains an index.csv with columns: work_order_id, work_order_number, property_name, vendor_name, start_time, end_time, status, packet_filename And the ZIP contains exactly one packet file per work order with branded PDF/CSV contents
Completion and Failure Notifications
Given a job completes or fails When the job state changes to "Completed" or "Failed" Then an in-app notification is posted to the requesting user with job name, state, total processed, and a link to details And an email notification is sent to the requesting user with the same information and, if Completed, the expiring download link and expiration timestamp And notifications are delivered within 2 minutes of the state change And email delivery respects the user's notification preferences
Large Dataset Performance and Memory-Safe Streaming
Given a dataset of at least 5,000 work orders with attachments totaling at least 10 GB When an export job is run Then the export completes without server 5xx errors or request timeouts And the UI remains responsive, with page navigation and list interactions meeting p95 latency under 2 seconds while the job runs And files are written to the ZIP using streaming I/O without loading the full archive into memory And work orders are read in paginated batches to avoid exhausting worker memory
Export Access Control & Audit Logging
"As an account owner, I want permissions and a complete audit trail for exports so that I can control who generates packets and track what was shared and when."
Description

Enforce role-based permissions for generating and viewing exports, with granular scopes at organization, property, and work order levels. Require users to provide an export purpose when configured by admins. Log every export action with user, timestamp, scope, format, redaction preset, and delivery method. Expose an admin dashboard to review export activity, filter by date/user, and revoke unexpired links. Apply retention policies to generated files and align link expiration with organization security settings.

Acceptance Criteria
Role-Based Generate and View Permissions
- Given a user without the "Exports:Generate" permission, when they view any eligible page (organization, property, or work order), then the "Export Inspector Pack" action is not visible and any direct POST/trigger to the export endpoint returns HTTP 403 and no export job is created. - Given a user with the "Exports:Generate" permission, when they click "Export Inspector Pack," then the export modal opens and they can proceed subject to scope checks. - Given a user without the "Exports:View" permission, when they attempt to access export history or a download link, then the UI hides these entries and any direct GET to a download URL returns HTTP 403 and no file bytes are served.
Granular Scope Enforcement (Org/Property/Work Order)
- Given a user with organization-wide scope, when they generate or view an export for any property or work order in the organization, then the action succeeds. - Given a user with property-scoped access to Property A only, when they attempt to generate or view an export for Property B, then the action is blocked with HTTP 403 and no export file is generated. - Given a user with work-order–scoped access to WO-123 only, when they attempt to generate or view an export for WO-456, then the action is blocked with HTTP 403 and no export file is generated. - On any successful export or view, the exported content is limited to entities within the user's authorized scope (no cross-property or cross-work-order data).
Mandatory Export Purpose Prompt
- Given the organization setting "Require export purpose" is ON, when a user initiates an export, then the "Generate" button remains disabled until a purpose is provided (selection from admin-defined list or free text 1–120 characters, per configuration). - When the export is submitted with a purpose, then the purpose value is persisted with the export record and is visible in the admin export activity dashboard. - Given the organization setting "Require export purpose" is OFF, when a user initiates an export, then no purpose prompt is shown and the export proceeds normally.
Comprehensive Export Action Logging
- On successful export generation, the system writes an immutable audit record containing: user identifier, timestamp (UTC ISO 8601), scope (organization/property IDs/work order IDs), selected format(s) (PDF, CSV), applied redaction preset identifier/name, delivery method (direct download, email, or link), and export purpose if configured. - The audit record is created only once per export and appears in the admin export activity dashboard within 10 seconds of export completion. - Attempts blocked by permission or scope checks do not create export files and are recorded as access-denied security events separate from export audit records.
Admin Export Activity Dashboard & Filters
- Given an organization admin, when they open Admin > Export Activity, then they see a pageable table of export records with columns: user, timestamp, scope, format, redaction preset, delivery method, purpose (if any), and link status (active/revoked/expired). - When the admin applies filters by date range (UTC), user, format, and scope type (organization/property/work order), then the results update within 2 seconds for datasets up to 5,000 records and only matching records are shown. - Sorting by timestamp is descending by default; clicking the timestamp header toggles between descending and ascending and updates within 1 second.
Admin Revokes Unexpired Export Link
- Given an export with an active, unexpired link, when an admin clicks "Revoke link" and confirms, then within 60 seconds the link status changes to "revoked" in the dashboard and the export record logs the revocation with admin user and timestamp. - After revocation, any request to the prior link returns HTTP 410 Gone and zero bytes are served. - Revoked links cannot be reactivated; generating a new link requires creating a new export or explicitly recreating a link per admin workflow (if provided).
Retention Policy & Link Expiration Alignment
- When an export is generated, the download link expires according to the organization's "Export Link TTL" setting; the link's expiration timestamp equals now() + TTL (±1 minute for clock skew). - Generated files are retained according to the organization's "Export Retention" policy; after the retention period elapses, the file is deleted or rendered irrecoverable, and any access attempts return HTTP 404 Not Found while the audit record remains available. - A scheduled process enforces link expiration and file deletion at least daily, and updates link status to "expired" within 15 minutes of TTL elapse.

Live Badges

Embeddable countdown badges that travel with the job—inside work orders, vendor SMS links, and the tenant FixTrack page. Everyone sees the same live clock and stage (response vs. resolution) with quick actions to confirm access or propose new windows, turning visibility into on‑time outcomes.

Requirements

Live SLA Countdown Engine
"As a property manager, I want a unified countdown service that everyone references so that the response and resolution clocks are consistent and reliable across all views."
Description

Implement a single source of truth service that computes and serves real-time countdowns for both SLA stages: initial response and full resolution. The engine determines the active stage, start/due timestamps, remaining time, and risk level, accounting for property time zones, vendor business hours, holidays, grace periods, and pause conditions (e.g., awaiting tenant access). It exposes a low-latency read API and event stream (SSE/WebSocket) so all surfaces—work orders, SMS landing pages, and tenant FixTrack—render the same live clock. The service must handle reschedules, cancellations, and status changes idempotently, emit threshold events (e.g., 60/15 minutes to breach), and persist an audit trail of stage transitions and pause reasons. Expected outcome: consistent, trustworthy timers that drive on-time behaviors and power alerts and analytics.

Acceptance Criteria
Active SLA Stage Calculation with Time Zones, Business Hours, Holidays, and Grace Periods
Given a work order with property timezone America/Chicago, vendor business hours Mon–Fri 09:00–17:00 local, a holiday on 2025-11-27, and response SLA = 2 business hours with a 10-minute grace period When the work order is created at 2025-11-26T22:55:00Z (local 16:55) Then the engine sets activeStage = response, stageStart = 2025-11-26T17:10:00-06:00 (grace defers into business time), and stageDue = 2025-11-27T10:10:00-06:00 skipping the holiday And remainingSeconds reflects now() against stageDue using the property timezone rules And riskLevel is computed as: green when remaining >= 60m, amber when 15m <= remaining < 60m, red when 0 < remaining < 15m, breached when remaining <= 0 Given a property in America/Los_Angeles on DST start day with resolution SLA = 4 business hours When stageStart is 2025-03-09T01:30:00-08:00 and business hours start 08:00 Then the due time calculation honors DST (skips 02:00–02:59) and computes total business minutes accurately without double-counting or loss
Pause/Resume on Awaiting Tenant Access Adjusts Countdown and Logs Reason
Given an active response stage with remainingSeconds = 5400 and a pause condition reason = awaiting_tenant_access at 2025-09-09T10:00:00-04:00 When the pause is applied Then remainingSeconds remains constant across subsequent reads and stream updates until resume And stageDue is extended by the pausedDuration upon resume And an audit record is persisted with type = pause, reason = awaiting_tenant_access, startedAt, endedAt (on resume), actorId, and stage = response Given multiple pauses occur within the same stage When pauses and resumes are applied sequentially Then totalPausedDuration = sum of all pause intervals and the SLA clock excludes this duration from due calculation
Threshold Events (T-60/T-15) and Risk Level Updates
Given an active SLA stage with threshold events configured at 60 minutes and 15 minutes to breach When remainingSeconds transitions from > 3600 to <= 3600 Then the engine emits a single t_minus_60 event with jobId, stage, occurredAt, remainingSeconds, and monotonic sequenceNumber When remainingSeconds transitions from > 900 to <= 900 Then the engine emits a single t_minus_15 event with the same schema And threshold events are idempotent: duplicates with the same sequenceNumber or idempotencyKey are not delivered twice to subscribers or sinks And riskLevel updates to amber at <= 60 minutes and to red at < 15 minutes and is pushed to subscribers within 1 second of the transition (p95)
Real-time Stream Delivery (SSE/WebSocket) with Initial Snapshot, Heartbeats, and Reconnect
Given a client subscribes to the SLA stream for jobId via SSE When the connection is established Then an initial full state snapshot (activeStage, stageStart, stageDue, remainingSeconds, riskLevel, version) is delivered within 200 ms (p95) And heartbeats are sent at least every 15 seconds while no state changes occur And state-change events are delivered within 500 ms (p95) from internal commit to client emission Given the client reconnects with Last-Event-ID or sequenceNumber after a transient disconnect < 2 minutes When the stream resumes Then missed events are replayed in order without duplication, up to the retention window Given a WebSocket subscriber When the server detects no pong within 30 seconds after a ping Then the server closes the connection gracefully with a retry-after hint
Idempotent Handling of Reschedules, Cancellations, and Status Changes
Given two identical reschedule requests with idempotencyKey = abc123 arrive within 5 seconds When the engine processes them Then only one recalculation occurs, one audit entry is persisted, and one stream event is emitted Given an out-of-order event stream where update version=6 arrives before version=4 When both are processed Then the engine applies version=6 and ignores or parks version=4, leaving final state consistent with the highest version Given a cancellation event is received during an active stage When cancellation is committed Then activeStage becomes none, remainingSeconds = 0, a cancellation audit record is persisted, and all subsequent duplicate cancellations with the same idempotencyKey are no-ops
Consistent Timers Across Surfaces via Low-Latency Read API
Given three clients (work order UI, vendor SMS page, tenant FixTrack) request the timer for the same job within the same second When they call GET /v1/sla/{jobId} Then each response contains identical activeStage, stageStart (ISO 8601 with timezone), stageDue, remainingSeconds, riskLevel, and version fields And responses include ETag and Last-Modified headers; a subsequent request with If-None-Match/If-Modified-Since returns 304 when unchanged And p95 latency for GET /v1/sla/{jobId} is <= 150 ms and p99 <= 300 ms under 1,000 concurrent requests And remainingSeconds never increases unless due to a documented cause (pause/resume, reschedule, grace-period adjustment), which is reflected by a higher version
Immutable Audit Trail of Stage Transitions and Pauses
Given any stage transition (response -> resolution, resolution -> completed) or pause/resume When the change is committed Then an audit record is persisted with id, jobId, previousStage, newStage, reason (nullable), actorId (nullable system), occurredAt, effectiveFrom, effectiveTo (nullable), and correlationId/idempotencyKey And audit records are immutable and append-only; updates require a new record with linkage to the prior one And GET /v1/sla/{jobId}/audit returns records ordered by occurredAt ascending with pagination and filters by type and date range And the audit stream emits the same records with the same sequenceNumber as the state stream, ensuring consumers can rebuild state from audit alone
Embeddable Live Badge Widget
"As a tenant, I want to see a live badge on my FixTrack page and SMS link so that I know exactly how much time remains and what stage the job is in."
Description

Deliver a lightweight, embeddable badge component that renders the active stage label (Response/Resolution), live countdown, risk color state, and contextual quick actions. The widget must load via a single JS snippet, be themeable to match landlord branding, responsive across devices, and accessible (keyboard + screen readers, ARIA live regions). It should subscribe to the countdown engine via SSE/WebSocket with graceful fallback to polling, support sandboxed cross-origin embedding, and work in low-JS environments (static fallback with periodic refresh). It must be under a strict size budget, avoid PII leakage, respect locale/timezone, and expose configuration flags (compact/expanded, show/hide actions). Integration points include FixFlow work orders, vendor SMS link pages, and tenant FixTrack pages.

Acceptance Criteria
Single-Snippet Embed and Initial Render Across Host Pages
Given a host page (work order, vendor SMS link page, or tenant FixTrack) with an empty container div and no prior FixFlow scripts When a single <script src> snippet with required data-config attributes is added and the page loads on a mid‑tier device over emulated Slow 4G Then the badge renders the active stage label, live countdown, risk color state, and visible container within 1500 ms FCP and without console errors And the shipped JS payload (excluding cached shared runtime) is ≤ 35 KB gzipped and ≤ 120 KB uncompressed And the widget does not create global variables beyond the FixFlowBadge namespace and does not mutate host styles outside its own scope And the badge renders identically across the three integration contexts listed above
Real-Time Countdown Sync with SSE/WebSocket and Polling Fallback
Given network conditions that allow real‑time connections When the badge initializes Then it connects via SSE or WebSocket and updates the visible clock at 1 Hz with client drift ≤ 1 s from server time and reflects stage changes within 3 s Given real‑time connections are blocked by CSP/firewall When initialization occurs Then the badge falls back to HTTPS polling at a 15 s interval and maintains countdown continuity locally between polls And connection retries use exponential backoff capped at 60 s and auto‑recover to real‑time when available And if connectivity is lost, an offline indicator appears and the countdown continues using last known deadline And in low‑JS environments, a static fallback renders with at least a 60 s auto‑refresh and shows the correct stage and time remaining
Actionability: Confirm Access and Propose New Window
Given the badge is in a stage that permits actions and actions are enabled by config When a user activates Confirm Access via click or keyboard Then a request is sent to the FixFlow API, the action is idempotent, receives a 2xx response, and the badge state updates within 3 s to reflect confirmation Given a user selects Propose New Window When a valid time window is submitted Then the proposal is validated client‑side and server‑side, a 2xx response is received, and the countdown/stage adjust accordingly or show pending status And actions are hidden or disabled when not permitted by role, stage, or config, with accessible error/tooltip messaging on denial And duplicate submissions are prevented (button shows progress, disabled during request) and network/API errors display a retry path without losing focus And actions behave appropriately per context: opens an in‑app drawer on work orders, navigates to a lightweight hosted form on vendor SMS pages, and an inline flow on FixTrack
Accessibility: ARIA Live, Keyboard, and Contrast
Given a user relying on a screen reader When the badge loads and updates Then the stage and time remaining are exposed with an accessible name and announced on stage changes and minute boundaries via aria‑live=polite without announcing every second And all interactive elements have a logical tab order, visible focus indicators, and are operable via keyboard only (Enter/Space) with no keyboard traps And color contrast for text and critical icons meets WCAG 2.1 AA (≥ 4.5:1 for body text) including under custom themes And motion respects prefers‑reduced‑motion and no flashing content exceeds WCAG thresholds
Branding: Theming and Configuration Flags
Given a host provides CSS custom properties and/or a theme token object via data‑config When the badge renders Then it applies brand colors, typography, and corner radius while preserving required contrast ratios and focus states And runtime theme changes are reflected within 200 ms without reloading and without layout shift > 0.1 CLS And configuration flags (compact|expanded; showActions true|false) deterministically alter layout and visibility as documented And in compact mode the countdown, stage label, and risk color remain visible and legible at 12 px minimum font size; in expanded mode quick actions are visible when enabled
Security and Embedding: Cross-Origin, CSP, and PII Safeguards
Given the badge is embedded cross‑origin in an iframe with sandbox="allow-scripts allow-same-origin" When it initializes Then it functions without needing additional sandbox permissions, communicates with its host only via postMessage, and does not access or mutate parent DOM And it operates under a restrictive CSP without unsafe-inline/unsafe-eval, with SRI on the script tag, and only connects to approved FixFlow domains And no PII (names, phone numbers, emails, full street addresses) is rendered in the badge UI or sent in URL query strings; requests carry only opaque IDs/tokens in headers/body over HTTPS And logs/analytics emitted by the badge exclude PII and can be disabled via config
Localization and Responsiveness: Locale, Timezone, and Layout
Given the host provides locale and timezone or defaults are inferred When the badge renders Then time and numbers are formatted with Intl APIs respecting locale (including RTL) and timezone, with correct behavior across DST transitions And 12/24‑hour formatting follows locale or explicit config and can be overridden via data‑config And the layout remains usable at widths from 280 px to desktop, with no horizontal scroll, tap targets ≥ 44×44 px, and text does not overflow And the risk color and text remain legible on small screens and high‑contrast mode
SLA Stage Sync and Pause Rules
"As a vendor, I want the timer to pause when I’m waiting for tenant access approval so that I’m not penalized for delays outside my control."
Description

Define and enforce the business rules that map job lifecycle states to SLA stages and govern when countdowns start, switch, pause, and resume. The system must transition from Response to Resolution based on explicit events (e.g., vendor acknowledges, on-site check-in), and pause automatically when waiting on external dependencies (tenant access confirmation, parts on order with approved exception, force majeure). Rules must be configurable by portfolio (e.g., different response SLAs by severity) and recorded with reason codes for transparency. On resume or reschedule, due times are recalculated and broadcast to all clients. Expected outcome: fair, transparent timing that reflects reality and drives accurate accountability.

Acceptance Criteria
Transition to Resolution on Acknowledgment or On-Site Check-In
Given a work order is in the Response stage with portfolio and severity rules configured And the live badge is visible on the work order, vendor SMS page, and tenant FixTrack When the vendor performs an Acknowledge action via the SMS link or portal OR completes an on-site Check-In Then the system stops the Response countdown and starts the Resolution countdown within 1 second of the triggering event And the stage label updates to "Resolution" on all badge surfaces within 5 seconds And the response duration, event type (Acknowledge or Check-In), and timestamps are recorded in the audit log And duplicate Acknowledge/Check-In events do not create duplicate transitions or time adjustments
Automatic Pause: Awaiting Tenant Access Confirmation
Given a job is in the Resolution stage and access/appointment confirmation from the tenant is required When the vendor proposes a new time window via badge quick action OR the manager flags the job as waiting on tenant access Then the SLA countdown pauses within 1 second with reason code "Awaiting Tenant Confirmation" And the badge shows Paused with the reason on all surfaces within 5 seconds And no SLA time accrues while paused And pause start timestamp, initiator, and reason code are written to the audit log When the tenant confirms access via FixTrack or SMS Then the countdown resumes and the due time is recalculated from the remaining SLA time and recorded in the audit log When the tenant declines or proposes a different window Then the system remains paused and prompts for reschedule, preserving SLA time until a new window is set or access is confirmed
Automatic Pause: Parts On Order with Approved Exception
Given a job requires parts and is in an active SLA stage (Response or Resolution) When the vendor requests a "Parts on Order" exception and the property manager approves it Then the SLA countdown pauses within 1 second with reason code "Parts on Order (Approved Exception)" And the badge shows Paused with the reason on all surfaces within 5 seconds And no SLA time accrues while paused And the approval, reason code, and timestamps are recorded in the audit log When parts are received and the vendor or manager records "Parts Received" or resumes work Then the countdown resumes, remaining time is recalculated, and the new due time is displayed and audited And if the exception request is not approved, no pause is applied and the SLA continues to run
Manual Pause: Force Majeure with Reason Codes and Approvals
Given an active SLA stage is running When an authorized user applies a Force Majeure pause and selects a controlled reason code with a required note Then the SLA countdown pauses within 1 second and all badge surfaces show Paused with reason within 5 seconds And no SLA time accrues while paused And the user, role, reason code, note, and timestamps are recorded in an immutable audit log When an authorized user resumes the SLA Then remaining time is recalculated and the updated due time is displayed across all badge surfaces and recorded in the audit log
Portfolio- and Severity-Based SLA Configuration Applied
Given Portfolio A is configured with Response SLA: Emergency = 2h, Routine = 24h and Resolution SLA: Emergency = 24h, Routine = 7d And Portfolio B is configured with Response SLA: Emergency = 4h, Routine = 48h and Resolution SLA: Emergency = 48h, Routine = 10d When a work order is dispatched under Portfolio A with severity Emergency Then the Response due time is now + 2h and displayed on all badges within 5 seconds When a work order is dispatched under Portfolio B with severity Emergency Then the Response due time is now + 4h and displayed on all badges within 5 seconds When a job transitions from Response to Resolution Then the Resolution SLA target is applied based on that job’s portfolio and severity and the new due time is shown When severity or portfolio is changed by an authorized user Then SLA targets and due times are recalculated immediately and the change is recorded in the audit log
Resume or Reschedule Recalculates and Broadcasts Due Times
Given a job is paused with an active reason code When the pause is cleared (e.g., tenant confirmed, parts received) or an appointment is rescheduled via badge quick action Then remaining SLA time is recalculated excluding all paused durations And the new due time is broadcast to the work order UI, vendor SMS page, and tenant FixTrack within 5 seconds And all clients display the same due time and stage after the update And the recalculation inputs (prior due time, paused durations, reason) and outputs (new due time) are recorded in the audit log
Unified Badge Stage and Clock Consistency Across Surfaces
Given a job with a live badge embedded in the work order, vendor SMS page, and tenant FixTrack When any SLA state change occurs (start, switch, pause, resume) Then all badge instances display the same stage label and pause reason (if any) within 5 seconds And the countdown clocks remain synchronized with a maximum drift of 1 second across surfaces And when a client reconnects from offline or background, it re-syncs the stage and remaining time within 2 seconds of reconnect
One-tap Quick Actions
"As a vendor, I want to propose a new appointment window directly from the badge so that scheduling conflicts are resolved quickly without phone tag."
Description

Add context-aware actions directly on the badge—Confirm Access (tenant), Propose New Window (vendor), Running Late (vendor), Mark On Site (vendor), and Acknowledge (vendor)—that execute without login for tokened links. Each action validates permissions, updates the job schedule/state, adjusts SLA timers per rules, and triggers notifications to affected parties. UI must provide instant feedback, prevent duplicate submissions, and capture structured data (e.g., proposed window, ETA, access notes). Actions must be available consistently across the dashboard, SMS landing pages, and FixTrack, and logged for audit/analytics. Expected outcome: fewer missed SLAs through faster coordination and fewer back-and-forth messages.

Acceptance Criteria
Tenant confirms access via FixTrack (no login)
Given a valid tenant token link to FixTrack for an active job with a scheduled window When the tenant taps Confirm Access and optionally enters access notes (0–280 chars) Then the token is validated against the job-tenant relationship and the action is authorized And the job state updates to Access Confirmed without changing the scheduled window And response/resolution SLA timers remain unchanged And vendor and PM receive notifications as configured within 60 seconds And the badge button immediately changes to Confirmed and becomes disabled within 500 ms And the server responds in ≤1.5 s and the UI shows success within ≤2 s And subsequent taps or refreshes return the same confirmation without creating duplicates And an audit log entry is recorded with action=confirm_access, actorRole=tenant, surface=FixTrack, jobId, timestamp, and notes length
Vendor proposes new appointment window via SMS link
Given a valid vendor token link for a job with a scheduled visit When the vendor taps Propose New Window and selects a start and end in the property time zone with duration ≥30 minutes and within allowed hours Then overlapping with the vendor's FixFlow calendar or other accepted job windows is rejected with suggestions And on valid submission the job schedule updates to Proposed (Pending Confirmation) with the new window stored And the resolution SLA is paused until acceptance/decline per configured rules And the badge switches to show the Proposed window and 'Awaiting Confirmation' with a paused indicator within 3 seconds across all surfaces And tenant and PM are notified with one-tap options to accept/decline within 60 seconds And the server responds in ≤1.5 s; duplicate submissions within 60 seconds are idempotent And an audit log entry includes oldWindow, newWindow, timezone, vendorId, and surface=SMS
Vendor reports running late from badge
Given a vendor has a confirmed appointment today When the vendor taps Running Late and submits a new ETA later than the current ETA (same day) Then the ETA is updated and displayed on the badge within 3 seconds And the resolution SLA is extended by min(config.lateCapMinutes, newETA - oldETA) and flagged 'At Risk' if applicable And tenant and PM are notified with the updated ETA within 60 seconds And the server responds in ≤1.5 s; the action is rate-limited to once per 10 minutes per job and idempotent on repeat payloads And an audit log entry records action=running_late, oldETA, newETA, deltaMinutes, vendorId, and surface
Vendor marks on site from badge
Given a vendor is scheduled for a visit When the vendor taps Mark On Site Then the arrival timestamp is recorded and the job state moves to In Progress And the response SLA is marked Pass if not already, while the resolution SLA continues And if device location permission is granted, lat/long is captured and stored; if not, the action still succeeds And the badge switches to the resolution countdown within 3 seconds across all surfaces And tenant and PM are notified within 60 seconds And the action is idempotent; repeated taps return the original arrival timestamp And an audit log entry includes arrivalAt, location(if available), vendorId, and surface
Vendor acknowledges new work order from badge
Given a valid vendor token link for a newly assigned job awaiting acknowledgment When the vendor taps Acknowledge Then the token and permissions are validated and the job state updates to Acknowledged And the response SLA is marked Pass and timers update accordingly And the badge reflects Acknowledged immediately and disables the button within 500 ms And PM is notified within 60 seconds And the server responds in ≤1.5 s; duplicate taps are idempotent And an audit log entry records action=acknowledge, actorRole=vendor, surface, jobId, and timestamp
Cross-surface availability, consistency, and idempotency for one-tap actions
Given a job with applicable roles When viewing the live badge on the dashboard, SMS landing page, or FixTrack Then only role-appropriate actions are visible (Tenant: Confirm Access; Vendor: Propose New Window, Running Late, Mark On Site, Acknowledge) And labels, enabled/disabled state, and countdown values are consistent across surfaces (±1 second) And after any one-tap action on any surface, all other surfaces reflect the new state within ≤3 seconds And all actions complete server round-trips in ≤1.5 s and show clear success/failure feedback; network errors present a retry without double-submitting And all actions require valid tokens or authenticated sessions; expired/tampered tokens return 401 with no state change And every action emits exactly one audit/analytics event and zero duplicate schedule changes
Secure Link Tokens and Permissions
"As a landlord, I want expiring, role-aware badge links so that external participants can take only permitted actions without needing full accounts."
Description

Protect badge views and actions with signed, short-lived, role-scoped tokens embedded in URLs and embeds. Implement claims that restrict what each viewer can see and do (tenant vs. vendor vs. landlord), enforce expiration and revocation, and apply rate limiting and replay protection. Ensure CSRF protection where applicable, CORS/sandbox policies for embeds, and redaction of sensitive fields. Provide admin tools to invalidate tokens and rotate keys without breaking active jobs. Expected outcome: frictionless access for external users via SMS links while maintaining least-privilege security and compliance.

Acceptance Criteria
Tenant SMS Link: Role-Scoped Access and Frictionless Entry
- Given a tenant receives a signed SMS link for Job X - When they open the link within 24 hours of issuance - Then the badge loads without login and shows only Job X with actions: Confirm Access and Propose New Window - And sensitive fields (vendor phone, landlord contact, other unit identifiers) are not displayed - And the token claim role=tenant and jobId=X is enforced server-side; requests for any other job return 403 - And after 24 hours or upon revocation, subsequent requests return 401 with a user-facing "Link expired" message and a regeneratable link workflow
Vendor SMS Link: Action Permissions, Redaction, and Audit
- Given a vendor receives a signed SMS link for Work Order Y - When they open the link within 24 hours - Then they can Accept/Decline the window, Propose an alternate time, and Mark En Route if enabled for Y - And tenant PII (phone, email, exact unit number beyond building) is masked unless explicitly allowed by policy - And the token cannot access landlord-only actions (cancel job, view billing, modify SLA) - And every action is recorded with token jti, vendorId claim, IP, user-agent, and timestamp in an immutable audit log
Signed Tokens: TTL, Integrity, and Expiration Behavior
- Given tokens are signed with asymmetric keys and include a kid header - When a token is tampered or signed with an unknown/retired key - Then validation fails with 401 and no internal error details are leaked - And action tokens have max TTL 15 minutes; view tokens have max TTL 24 hours; the server enforces max TTL regardless of client-provided exp - And a clock skew of ±2 minutes is tolerated; outside that range returns 401 - And tokens are scoped to jobId and role; missing/extra scopes cause 403 for out-of-scope resources
Revocation and Key Rotation Controls
- Given an admin invalidates all tokens for Job Z via Security > Revoke - When any previously issued token for Job Z is used - Then access is denied within 60 seconds of revocation with 401 and an "Link expired" message - And admin can revoke by user/phone, by token jti, or by role for a job, and each action is audit-logged with actor, scope, and reason - And a bulk revoke across an account completes within 2 minutes for up to 10,000 tokens and surfaces progress to the admin - Given an admin rotates signing keys - When rotation is applied - Then new tokens use the new active key immediately; old tokens validate for a configurable grace period (default 24 hours) unless revoked - And the JWKS endpoint serves both keys during grace and removes the retired key after grace without downtime
Replay Protection and Rate Limiting
- Given a write-action token with a unique jti/nonce is used - When the same token is replayed - Then the second and subsequent attempts return 409 Duplicate (or 401 Replayed) and no state change occurs - And the server stores jti values until exp + 15 minutes to block late replays - And rate limits are enforced: max 5 write actions per minute per token and 60 GET requests per minute per IP; exceeding returns 429 with Retry-After - And idempotency keys are honored for client retries; duplicate requests with the same idempotency key are coalesced to a single state change
CSRF Protection for Web Actions
- Given a user is in an authenticated web session (landlord or staff) - When they perform state-changing actions via web forms or AJAX - Then a valid CSRF token (double-submit cookie or same-site token) is required; missing/invalid tokens return 403 with no state change - And SMS link GET exchanges for one-time action tokens are CSRF-exempt, but POSTs using session cookies are not - And SameSite=Lax or Strict cookies are set appropriately; cross-site POSTs cannot succeed without a valid CSRF token
Embeds: CORS, CSP, and Sandbox Policies
- Given the Live Badge is embedded in an external site or portal - When the host origin is not on the account’s allowed-origins list - Then the embed refuses to render and returns 403; no token or stack trace is exposed in error responses - And CORS allows GET to badge resources only from allowed origins; preflight requests from others return 403 - And the iframe uses sandbox with allow-scripts and allow-same-origin; forms, popups, and top-navigation are disallowed - And CSP sets frame-ancestors to self and allowed origins; inline scripts are blocked; connect-src is limited to approved endpoints - And cross-origin messaging uses postMessage with strict origin and message-type validation
Risk Alerts and Escalations
"As a property manager, I want alerts when a job is at risk of missing its SLA so that I can intervene before it becomes a breach."
Description

Generate proactive alerts when countdowns hit configurable risk thresholds (e.g., entering yellow/red), when no acknowledgment is received within a window, or when a breach is imminent. Support channel preferences per stakeholder (SMS, email, in-app, Slack/webhook), quiet hours, and escalation paths (vendor manager, owner) with acknowledgment loops. Alerts must include one-tap actions (e.g., Nudge Vendor, Offer Next Slot) and link back to the live badge. All alerts and outcomes are recorded for reporting. Expected outcome: earlier interventions that reduce SLA breaches and improve on-time arrival.

Acceptance Criteria
Threshold-Based Risk Alert on Live Badge Stage Change
Given a work order has configured yellow/red thresholds for response and resolution stages And the live badge countdown is active for a stage When remaining time first crosses into a yellow or red threshold for that stage Then a risk alert is sent to each stakeholder within 60 seconds according to their channel preference And the alert content includes: stage name, threshold color, remaining time (mm:ss), work order ID, one-tap actions (Nudge Vendor, Offer Next Slot), and a link to the live badge And duplicate alerts for the same transition per stakeholder are suppressed (one alert per threshold transition per stage) And the event is recorded with transition type, recipients, channel, and timestamps
No Acknowledgment Within Window Escalation
Given an alert requires acknowledgment And an escalation path is configured (e.g., vendor manager -> owner) When no acknowledgment is received within the configured window (default 15 minutes) Then the system escalates to the next contact in the path via their preferred channel And marks the escalation level on the alert And continues escalating at each interval until acknowledgment is received or the path is exhausted And upon acknowledgment by any level, further escalations stop And all steps are logged with level, time, recipient, channel, and acknowledgment status
Quiet Hours Handling and Deferred Delivery
Given a stakeholder has quiet hours configured And an alert is generated that is not marked Critical When current time is within the stakeholder’s quiet hours Then the alert is queued for delivery at the first minute after quiet hours end And if an SLA breach is projected before quiet hours end, the alert is routed to the configured always-on fallback recipient/channel And alerts marked Critical (e.g., red threshold or breach-imminent within 15 minutes) bypass quiet hours only on channels flagged as allowed during quiet hours And the alert log records whether quiet hours deferral or bypass occurred
Channel Preference Delivery and Live Badge Link
Given stakeholder channel preferences with priority order across SMS, email, in-app, and Slack/webhook When an alert is dispatched Then the system delivers via the highest-priority active channel for that stakeholder And if that channel reports delivery failure within 2 minutes (e.g., SMS undeliverable, webhook non-2xx), the system retries once and then falls back to the next channel And the alert content for each channel includes a working link to the live badge that resolves in under 2 seconds and opens the correct work order and stage And delivery outcomes per channel (delivered, failed, fallback used) are recorded
One-Tap Actions Execute and Update Job
Given an alert contains one-tap actions (Nudge Vendor, Offer Next Slot, Confirm Access) When the recipient taps an action link Then the action executes without login using a single-use token that expires in 30 minutes And a success confirmation appears within 3 seconds And the work order and live badge state are updated accordingly And the system logs actor identity, action type, token ID, outcome, and timestamp And expired or reused tokens are rejected with a safe error and no job changes
Alert Audit Logging and Reporting
Given the system sends, escalates, or receives acknowledgment for any alert When viewing the alert audit log by work order or date range Then each entry includes: work order ID, badge stage, threshold state (green/yellow/red/breach-imminent), event type (initial, escalation, acknowledgment, action), recipient, channel, delivery status, timestamps, and one-tap action outcomes And logs are filterable by date range, property, vendor, and SLA state And queries returning up to 10,000 entries complete within 2 seconds And data is retained for 24 months
Breach-Imminent Prediction Notification
Given a live badge has remaining time less than or equal to the configured lookahead (default 30 minutes) and the stage is not yet breached And no breach-imminent alert has been sent for the current stage in the last 60 minutes When the condition is detected Then the system sends a breach-imminent alert with recommended one-tap actions and the live badge link to stakeholders per their preferences within 60 seconds And if the breach is averted (stage returns to green) before the deadline, the alert entry is updated to Averted And if the breach occurs, the alert entry is updated to Breached and a single breach notification is sent
SLA Performance Analytics
"As an owner, I want SLA performance reports by vendor and property so that I can identify bottlenecks and reward reliable partners."
Description

Capture and aggregate stage durations, pause reasons, attempt counts, acknowledgment latency, and on-time rates across vendors, properties, and severities. Provide dashboards and CSV export to analyze response vs. resolution performance, identify bottlenecks (e.g., access confirmation delays), and measure the impact of badges and quick actions. Include cohort and time-series views, filter by portfolio, and surface vendor scorecards with trendlines. Expected outcome: data-driven improvements to scheduling, vendor selection, and tenant communication that increase SLA adherence.

Acceptance Criteria
SLA Stage Duration Calculation and On‑Time Classification
- Given severities have defined response_SLA_minutes and resolution_SLA_minutes, when a job moves from Created to Vendor Acknowledged and from Created to Completed, then response_time_minutes and resolution_time_minutes are computed at 1‑minute precision. - Given approved pauses exist, when computing resolution_time_minutes, then intervals with pause_type = Excludable are excluded from resolution and counted in pause_minutes_excluded; Non‑excludable pauses are included and counted in pause_minutes_included. - Given computed durations and SLAs, then response_status and resolution_status are set to On Time or Late using strict <= comparison. - Given aggregations by portfolio/vendor/property/severity/date range, then p50, p90, average, and on‑time rates match a manually verified sample within ±1 minute for times and ±0.1% for rates.
Pause Reasons Capture and Bottleneck Reporting
- Given a user pauses a job, when they save, then selecting a pause_reason from the predefined list is required and an optional note is allowed. - Given pause events exist, when viewing Analytics > Bottlenecks, then pause reasons are ranked by total_pause_minutes and job_count, with "Access Confirmation Delay" highlighted if in the top 3. - Given a reason is marked Excludable in settings, when analytics refresh, then resolution_time_minutes recomputes excluding those intervals and an "Exclusions Applied" indicator is visible. - Given a bottleneck row is clicked, then the dashboard drills down to the contributing job list with the same filters applied.
Acknowledgment Latency and Attempt Counts Tracking
- Given a vendor receives a job notification with a Live Badge link, when the vendor first acknowledges (Accept or Propose) via SMS or web, then acknowledgment_latency_minutes is recorded as Created → first_acknowledgement at 1‑minute precision. - Given outreach attempts are made (SMS, call, email, badge prompt) to vendor or tenant, when an attempt is sent, then attempt_count_vendor or attempt_count_tenant increments and timestamps are logged; duplicate attempts within 1 minute are de‑duplicated. - Then analytics surfaces median, p90 acknowledgment_latency_minutes and average attempt counts by vendor/property/severity matching raw event logs within ±1 minute/±0.1%.
Portfolio and Faceted Filtering on SLA Dashboard
- Given a user applies filters (portfolio(s), property(s), vendor(s), severity, status, date range), when Apply is clicked, then all widgets, tables, and exports reflect the filter and a filter summary chip list is shown. - Given no filters are set, then the default scope is the user’s assigned portfolio with a last 30 days date range. - Then totals displayed equal the sum of the visible table rows for the same filter with zero discrepancy. - Then 95th percentile dashboard load time is ≤ 3 seconds for result sets up to 10,000 jobs.
Cohort and Time‑Series Views with Badge Impact
- Given a date range is selected, when viewing Time Series, then response_on_time_rate% and resolution_on_time_rate% are charted by day and week with exact values on hover. - Given jobs with Live Badges enabled vs not enabled, when Cohort Compare is toggled, then the dashboard shows A vs B metrics and deltas (absolute and %) controlled by current severity and vendor filters. - Given a badge rollout date is set, then Before vs After cohorts are computed correctly and identical results are present in the CSV export for the same filters.
Vendor Scorecards with Trendlines
- Given Vendor view is opened, then each vendor shows: response_on_time_rate%, resolution_on_time_rate%, p50/p90 response_time_minutes, p50/p90 resolution_time_minutes (excluding excludable pauses), avg vendor_attempts and tenant_attempts, pause_rate%, and top_pause_reason. - Given a 12‑week window, then a sparkline trendline per metric is rendered with ±% change vs the prior 12‑week window. - Given a vendor row is clicked, then a detail view opens with a pre‑filtered job list and metrics consistent with the parent view within ±0.1%.
CSV Export of SLA Metrics
- Given any filter set and selected granularity (job‑level or vendor‑aggregate), when Export CSV is clicked, then a UTF‑8 CSV downloads within 30 seconds for up to 50,000 jobs. - Then job‑level CSV includes columns: job_id, property_id, vendor_id, portfolio_id, severity, created_at, acknowledged_at, completed_at, response_time_minutes, resolution_time_minutes, response_status, resolution_status, pause_minutes_total, pause_minutes_excluded, top_pause_reason, vendor_attempt_count, tenant_attempt_count, acknowledgment_latency_minutes, live_badge_enabled, timezone. - Then aggregate CSV includes: vendor_id, portfolio_id, severity, date_bucket, jobs_count, response_on_time_rate%, resolution_on_time_rate%, p50/p90 response_time_minutes, p50/p90 resolution_time_minutes, avg vendor_attempts, avg tenant_attempts, top_pause_reason. - Then CSV totals and rates match the dashboard for the same filters within ±0.1% and timestamps are ISO 8601 in the selected timezone.

Smart Rescheduler

Let tenants instantly pick a new appointment window from the portal when plans change. Available slots are pulled from the shared calendar, respect urgency and quiet hours, and confirm via SMS in seconds—eliminating phone-tag and reducing no‑shows.

Requirements

Tenant Self-Service Reschedule UI
"As a tenant, I want to choose a new appointment window from my portal so that I can reschedule without phone calls or delays."
Description

Provide an in-portal, mobile-first interface that lets tenants select a new appointment window from a curated list of valid options. The UI clearly displays service duration, vendor (or “vendor TBD”), earliest available times based on urgency, and any policy constraints (quiet hours, minimum notice). It supports local time display, accessibility standards, inline error handling, and a confirmation step before committing changes. Integrates with FixFlow authentication, the appointments service, and the real-time slot API to fetch availability and submit the reschedule in one seamless flow, updating the dashboard in near real time.

Acceptance Criteria
Show Valid Reschedule Slots Respecting Policies
- Given an authenticated tenant with an eligible appointment, when they open the Reschedule UI, then the system fetches availability via the real-time slot API using appointment type, duration, vendor (if assigned), property location, urgency level, and tenant timezone. - Then the UI lists only valid slots that: exclude quiet hours; honor minimum notice; include required travel/buffer times; avoid vendor double-bookings; and match the service duration. - The first displayed slot is the earliest time allowed by urgency and policy; if urgency allows policy overrides, the applied override is indicated in-line. - Each slot displays: local date, local start–end window, service duration, and vendor name or “Vendor TBD.” - If fewer than 5 valid slots exist in the next 14 days, show all available and display a “Limited availability” notice. - Initial availability renders within 1500 ms at p95 under normal load; subsequent pagination/refresh renders within 800 ms at p95.
Local Time and Timezone Handling
- All dates/times render in the tenant’s detected local timezone with abbreviation (e.g., 3:00–5:00 PM PDT). - If the property timezone differs from the tenant’s, a non-intrusive note indicates both timezones; slots remain in tenant local time. - Daylight Saving Time transitions are handled: slot durations remain accurate and windows do not shift incorrectly on boundary days. - If timezone detection fails, default to the property timezone and provide a selectable control to change timezone; changing timezone triggers a fresh availability fetch and re-renders within 800 ms p95. - Timestamps sent to backend include timezone/offset to ensure consistent booking semantics.
Accessibility and Keyboard-Only Operation
- The Reschedule UI conforms to WCAG 2.1 AA: color contrast ≥ 4.5:1, visible focus indicators, proper landmarks/roles, and labeled controls. - All interactive elements are reachable via keyboard (Tab/Shift+Tab) in logical order; Arrow keys navigate slot lists/grids; Enter selects a slot; Esc cancels the dialog or closes overlays. - Screen readers announce each slot with date, time window, duration, vendor label, and selection state; confirmations include a descriptive heading and summary. - Inline errors are announced via an ARIA live region (polite) and focus moves to the error container when errors appear. - Touch targets meet a minimum 44×44 px size on mobile without overlapping actionable regions.
Authorization and Data Integrity on Reschedule
- Only the authenticated tenant associated with the appointment can open the Reschedule UI for that appointment; unauthorized access returns a 403 and shows an access-denied screen. - Backend validates appointment ownership and reschedulable state (not cancelled/completed/locked within policy window); violations return 4xx with user-friendly inline messaging and disabled confirm actions. - The system uses optimistic concurrency or slot holds so two users cannot book the same slot; conflicts return a specific error code handled by the UI. - Successful reschedules create an audit record with tenant ID, prior slot, new slot, timestamp, and source IP/user agent.
Inline Error Handling and Conflict Resolution
- If a selected slot becomes unavailable before confirmation, the UI shows “That slot was just booked—please pick another,” refreshes availability within 500 ms, and clears the selection. - For network/timeouts/5xx, an inline banner with auto-retry (up to 2 attempts with backoff) appears; the user can also manually retry without losing context. - Validation errors (e.g., minimum notice or policy rules) display inline near the affected control; the Confirm button is disabled until a valid selection is made. - All error states are announced via ARIA live region and logged to the client telemetry with error codes for support triage.
Confirmation Step, SMS, and Calendar Updates
- After selecting a slot, a confirmation step summarizes date/time window, duration, vendor (or “Vendor TBD”), property address, and relevant policies with Confirm and Cancel controls. - On Confirm, the reschedule request is submitted; on success, the UI shows a success state within 2 seconds p95 and disables duplicate submissions. - SMS confirmation is sent to the tenant’s verified number within 10 seconds p95 and includes appointment window, vendor label, and a link back to details. - Shared and vendor calendars update atomically to prevent double-booking; if any calendar write fails, the transaction rolls back and the UI reports failure with next steps. - The tenant dashboard and appointment detail reflect the new time within 3 seconds via realtime push; if push is unavailable, a fallback poll updates within 15 seconds.
Mobile-First Responsive UI
- The reschedule flow is fully usable on 320 px-wide screens without horizontal scroll; primary actions remain visible or in a sticky footer. - Slot items are tap-friendly with minimum 44 px height and provide visual feedback within 100 ms on interaction. - P95 time-to-interactive on a mid-tier mobile device over 4G is ≤ 3 s for return visits and ≤ 5 s for first visit; feature-specific bundle size ≤ 200 KB gzipped. - The UI adapts to light/dark mode per OS preference while maintaining WCAG AA contrast for text and interactive elements.
Real-Time Slot Computation Engine
"As a tenant, I want to see only valid time windows so that my selection won’t be declined later due to conflicts."
Description

Implement a server-side availability engine that compiles open slots from the shared calendar across vendors, considering service duration, travel buffers, existing bookings, vendor work hours, holidays/blackouts, property quiet hours, and time zones. The engine returns ranked, deduplicated, conflict-free windows via an API, with caching and rate limiting for performance and cost control. It supports urgency-based acceleration (e.g., sooner windows for emergencies) and exposes feature flags for rollout. Designed for transactional consistency with booking holds to ensure what tenants see is truly bookable.

Acceptance Criteria
Cross-Vendor Slot Computation with Core Constraints
Given a shared calendar containing vendors with defined work hours, existing bookings, travel buffers, and service duration inputs And a request includes propertyId, serviceType, desiredDateRange, and tenantTimeZone When the API GET /availability/slots is called with a valid request Then the engine returns only slots where the vendor is available for the full serviceDuration plus required travelBuffer And no slot overlaps existing bookings or vendor breaks And slots respect each vendor’s work hours in the vendor’s local time zone And slots are computed across all eligible vendors assigned to the property/service And each returned slot includes vendorId, start, end, timeZone, and rankingScore And all timestamps are RFC3339/ISO-8601 with explicit offsets And the response excludes any slot earlier than now in the tenant’s local time
Quiet Hours, Holidays, and Blackout Compliance
Given property-level quiet hours and blackout dates and vendor holidays/blackouts are configured When slots are computed for any requested date/time range Then no slot start or end falls within property quiet hours And no slot intersects property blackout dates or vendor holidays/blackouts And quiet-hour boundaries are enforced inclusively at minute precision And rule precedence is property blackouts > vendor blackouts > vendor work hours
Ranked, Deduplicated, and Conflict-Free Results
Given multiple vendors can satisfy the request and overlapping windows may exist When the engine returns slots Then slots are sorted ascending by start time; ties are broken by higher urgency score, then vendor priority, then vendorId And the list contains no duplicate entries with the same vendorId, start, and end And for any single vendor, no two returned slots overlap And each slot includes a rankingScore field consistent with the returned order
Urgency-Based Acceleration
Given system configuration emergencySlaHours = 4 and vendorNoticeBuffer = 60 minutes When the API is called with urgency = emergency Then the engine includes any feasible slot starting within 4 hours even if earlier than the normal notice buffer And the earliest feasible slot appears first in results When the API is called with urgency = normal Then no slot starts earlier than now + vendorNoticeBuffer
API Performance, Caching, and Rate Limiting
Given cacheTtlSeconds = 60 and normalized request payloads are used for cache keys When the same request is repeated within 60 seconds Then the response is served from cache and p95 latency <= 200 ms When a request is a cache miss for a 7-day date range Then p95 latency <= 1200 ms And API enforces a rate limit of 60 requests per minute per tenant and returns HTTP 429 with Retry-After on breach And cache keys include feature flags, urgency, tenantId, propertyId, and timeZone to prevent cross-contamination
Booking Holds and Transactional Consistency
Given holdTtlMinutes = 5 and an Idempotency-Key header is provided When a client POSTs /availability/holds for a specific vendorId/start/end Then the engine atomically creates a hold and returns holdId and expiresAt And the held slot is immediately excluded from subsequent availability responses And concurrent hold requests for the same vendorId/start/end result in exactly one hold; others receive HTTP 409 When a hold is confirmed via POST /bookings with holdId Then the slot is booked and the hold is consumed When the hold expires or is canceled Then the slot reappears in availability within 5 seconds
Feature Flags Controlled Rollout
Given a feature flag availabilityEngine.v1 is disabled for a tenant When the tenant calls GET /availability/slots Then the request is routed to the legacy engine and response includes header X-Engine: legacy When availabilityEngine.v1 is enabled for the tenant or property Then the new engine serves the response and includes header X-Engine: v1 And toggling the flag takes effect within 60 seconds without redeploy And flags support percentage rollouts and per-tenant allowlists And disabling the flag routes traffic back to legacy without errors
Quiet Hours and Urgency Policy Enforcement
"As a property manager, I want reschedules to honor quiet hours and urgency so that visits comply with policy and meet expectations."
Description

Add configurable policy rules that automatically restrict or expand eligible appointment windows based on property-level quiet hours, building regulations, vendor preferences, and task urgency. Rules support per-property overrides, emergency exceptions, and manager approvals for after-hours work. Enforcement occurs during slot generation and at commit time to prevent policy drift. Admin screens allow managers to define windows, categories, and exceptions, ensuring compliant and predictable scheduling that reduces tenant complaints and violation risk.

Acceptance Criteria
Quiet Hours Filtering for Routine Tasks
Given a property has quiet hours configured as daily time ranges in the property's local timezone And a maintenance task is categorized as Routine (not Urgent or Emergency) When the tenant opens Smart Rescheduler to select a new time Then only appointment slots wholly outside the quiet hours are displayed And any slot overlapping quiet hours by 1 minute or more is excluded And if no eligible slots exist within the policy search horizon, a message explains why and offers a “Request manager assistance” action
Emergency Exception Expands Windows Without Approval
Given a maintenance task is categorized as Emergency And the property policy allows emergency exceptions during quiet hours When the tenant opens Smart Rescheduler Then appointment slots during quiet hours are displayed subject to vendor availability and emergency windows And when the tenant selects a slot, the booking is confirmed immediately without manager approval And tenant and vendor receive SMS confirmations within 10 seconds of commit And the booking is tagged as “Emergency Exception” in the event and audit log
Manager Approval Required for After-Hours Non-Emergency
Given a maintenance task is categorized as Urgent (not Emergency) And the selected slot falls within quiet hours And the policy requires manager approval for after-hours non-emergency work When the tenant attempts to book the slot Then the slot is placed on hold for the configured hold duration And an approval request is sent to the assigned manager via SMS/email with Approve and Decline actions And the tenant is shown a Pending Approval state with countdown and next steps And if approved within the hold duration, the appointment is confirmed and SMS notices are sent And if declined or the hold expires, the hold is released, the tenant is notified, and the tenant is returned to the slot picker And an approval outcome record is written to the audit log
Vendor Preference Enforcement During Slot Generation
Given vendor preferences exist (working hours, no-weekends, blackout dates) for the assigned vendor(s) And property quiet hours and building regulations are configured When Smart Rescheduler generates eligible slots Then slots violating vendor preferences are excluded even if property policy would allow them And slots violating property quiet hours or mandatory regulations are excluded even if vendor availability would allow them And if all slots for a day are excluded by vendor preferences, the UI indicates “No vendor available” for that day
Per-Property Overrides and Rule Precedence
Given portfolio-level default rules and per-property overrides are defined And certain building regulations are marked as Mandatory When generating eligible slots for a property with overrides Then property-level overrides take precedence over portfolio defaults And Mandatory regulations cannot be overridden by property settings And the active rule source (Default, Property, Mandatory) is shown on the admin policy screen for each applied rule
Policy Revalidation at Commit Prevents Drift
Given a tenant selected a slot more than N minutes ago or while policies may have changed When the tenant commits the reschedule Then the system revalidates the selection against current quiet hours, vendor preferences, regulations, and approval requirements And if any rule is violated, the commit is blocked with a specific reason code and the slot list is refreshed And no calendar event is created until the selection passes revalidation
Admin Policy Configuration and Validation
Given an admin defines quiet hours, urgency categories (Routine, Urgent, Emergency), exceptions, and vendor preference templates on the policy screen When the admin saves the configuration Then overlapping or invalid time ranges are rejected with inline validation messages And valid configurations are saved and propagated to slot generation within 60 seconds And all changes are versioned with timestamp, actor, and optional comment for audit
SMS Confirmation and Notification Workflow
"As a tenant, I want immediate SMS confirmation of my new appointment so that I know it is booked and everyone is informed."
Description

Trigger instant SMS confirmations when a tenant selects a new slot, including appointment details, links to view/manage, and optional ICS attachments. Support vendor notifications, delivery status tracking, retries, and automatic email fallback when SMS fails or is opted out. Allow Y/N reply to confirm, with status reflected in the appointment timeline. Templates are localized, configurable, and compliant with opt-in/opt-out requirements. Integrates with the messaging provider, audit logs, and the appointments service to maintain a single source of truth.

Acceptance Criteria
Instant SMS Confirmation on Tenant Reschedule
Given a tenant selects a new appointment slot in the portal and clicks Confirm When the appointment service persists the new slot Then the system sends an SMS to the tenant's opted-in phone number within 10 seconds containing: localized appointment date/time, property location, vendor name, appointment ID, and a short manage link And the SMS includes a secure link to download an ICS file when ICS is enabled for the organization And the notification record stores provider message ID, send timestamp, recipient phone, and template version And the appointment timeline shows a "Confirmation SMS sent" entry within 15 seconds with correlation to the message ID
Y/N Reply to Confirm with Timeline Update
Given a confirmation SMS was sent to the tenant When the tenant replies "Y" (case-insensitive, whitespace-tolerant) within 72 hours and before the appointment start Then the appointment status updates to Confirmed and the timeline records the reply text, timestamp, and masked phone number And a vendor notification is sent only on the first successful confirmation for that appointment version When the tenant replies "N" under the same constraints Then the system marks the appointment as Declined by tenant, sends a reschedule link to the tenant, and notifies the vendor When the reply is not recognized as Y/N Then the system auto-replies with a help message once per 24 hours per recipient And STOP/STOPALL/UNSUBSCRIBE/CANCEL/END/QUIT trigger opt-out, suppress further SMS, and switch future confirmations to email
Delivery Status Tracking, Retries, and Email Fallback
Given the messaging provider posts webhook status events When a message status is delivered, failed, undeliverable, or expired Then the system maps it to internal states and updates the message record and timeline within 5 seconds of webhook receipt And on transient failures the system retries up to 3 times using exponential backoff of 1m, 5m, and 15m And after final failure or if the recipient is opted out, the system sends an email fallback within 2 minutes containing the same appointment details, manage link, and ICS attached And idempotency is enforced using appointment_version + recipient + purpose to prevent duplicate notifications And the timeline displays each attempt and the fallback outcome with timestamps
Vendor Notification Mirroring with Quiet Hours
Given a tenant reschedules and a confirmation SMS is sent When vendor notifications are enabled for the property Then the vendor receives a notification via preferred channel (SMS if opted-in, else email) respecting quiet hours 21:00–08:00 local unless urgency = High And notifications queued during quiet hours are automatically sent at 08:00 local And the vendor message includes tenant display name (or "Tenant" if privacy enabled), unit/property, appointment date/time, and a manage link, without exposing tenant phone number And vendor notification status is tracked with the same delivery, retry, and fallback rules as tenant notifications And duplicate vendor notifications for the same appointment version are suppressed
Template Localization, Configuration, and Compliance
Given the tenant's locale and the organization's template settings When rendering an SMS or email notification Then the localized template for that locale is used and all variables are validated; missing variables cause fallback to a safe default template and an error is logged And each SMS includes the business name and opt-out instructions; if the message exceeds 160 characters, it is sent as up to 3 concatenated segments or a shortened template is applied And SMS is sent only if explicit opt-in with timestamp exists; otherwise the system sends the email fallback and records suppression due to consent And admins can edit templates per locale and channel, with preview rendering and test-send to sandbox numbers gated by permission
ICS Attachment and Manage Links
Given ICS attachments are enabled for the organization When generating the ICS for a rescheduled appointment Then the ICS contains UID=appointment_id@fixflow.app, DTSTART/DTEND with timezone, SUMMARY, DESCRIPTION, LOCATION, ORGANIZER, and ATTENDEE And the ICS is available via a signed URL linked from the SMS that expires after 30 days, serves content-type text/calendar, and is <= 50 KB And the manage link is a short URL that deep-links to the tenant portal appointment detail and includes tracking parameters And if the appointment is changed after the SMS is sent, a new ICS is generated with SEQUENCE increment and the update notification references the new link
Audit Logging and Single Source of Truth Synchronization
Given any notification send, status change, reply, retry, or fallback event occurs When the event is processed Then an immutable audit log entry is persisted with actor (system or masked phone), entity=appointment, appointment_version, message_id, channel, template version, and outcome And the appointments service remains the single source of truth; the notification service only sends based on the latest appointment_version and marks older sends as superseded when a newer version exists And a daily reconciliation job compares provider event counts to timeline entries and raises an alert if variance exceeds 1% for any 24h window
Double-Booking Guard and Atomic Hold
"As a vendor coordinator, I want the system to prevent double-bookings during reschedules so that technicians are not overbooked."
Description

Introduce short-lived, atomic holds on candidate time slots when surfaced to a tenant and transactional locking when committing a reschedule to prevent race conditions and double-bookings. Holds have a TTL and are released on expiration or cancellation. The commit path validates no conflicts arose during selection and gracefully prompts the user to choose another time if needed. Works across distributed instances with idempotent operations, ensuring reliable concurrency control at scale.

Acceptance Criteria
Atomic Hold Applied When Slots Are Shown
Given a tenant opens the reschedule flow for a work order and the system returns candidate slots When slots are surfaced to the tenant Then an atomic hold is created per slot with TTL 120 seconds (configurable) And the hold records tenantId, workOrderId, resourceId, start, end, expiresAt, and a session/idempotency key And slots with active holds or confirmed bookings are not returned to other users And holds are created exactly once per slot per response
Hold Expiration and Cancellation Release
Given a hold exists When its TTL elapses without commit Then the hold is automatically released and the slot becomes discoverable to searches within 5 seconds Given a tenant cancels the reschedule flow or closes the session When the cancellation/disconnect is detected Then all associated holds are released immediately Given a node/process crash occurs When the expiration sweeper runs Then expired holds are removed idempotently so no stale holds persist
Transactional Commit Prevents Double-Booking
Given a tenant confirms a held slot When the commit API is called with holdId and idempotencyKey Then the system acquires a distributed lock on the resource/time range and validates no conflicts And on success, atomically creates the appointment, marks the hold consumed, releases any sibling holds for that tenant, and returns 200 with appointmentId And on conflict, returns 409 with no appointment created and provides fresh available slots And at no time are two bookings created for the same resource and time range
Idempotent Commit Across Retries and Instances
Given commit requests may be retried or duplicated When multiple commit requests with the same idempotencyKey are received within the idempotency window (e.g., 60 minutes) Then at most one appointment is created And subsequent identical requests return the original result without side effects And no duplicate SMS confirmations are sent And the behavior is consistent across multiple distributed instances
Held Slots Hidden From Other Tenants
Given Tenant A holds slot S for a resource When Tenant B searches availability overlapping S during the hold window Then S is not offered to Tenant B And after the hold is released or expires, S may be re-offered if still conflict-free
Policy Validation at Commit (Quiet Hours and Urgency)
Given quiet hours and urgency policies are configured When a tenant attempts to commit a held slot Then the system re-validates the slot against current policies at commit time And if the slot violates quiet hours and no urgency override applies, the commit is rejected with 422 and alternatives are returned And if an urgency override applies, the commit proceeds
Audit Trail and Reason Capture
"As a landlord, I want a complete history of reschedules and reasons so that I can analyze patterns and resolve disputes quickly."
Description

Record every reschedule event with timestamp, actor (tenant/manager/system), original and new time windows, selected reason code, free-text notes, channel (portal/SMS), and related notification IDs. Surface the history in the appointment timeline and export via reporting. Data powers analytics on reschedule frequency, lead times, and root causes, enabling operational improvements and policy tuning while providing defensible records for disputes.

Acceptance Criteria
Tenant-Initiated Reschedule via Portal
Given an existing scheduled appointment And an authenticated tenant on the portal selects a new time window And the tenant selects a required reason code and may enter free-text notes (max 1000 chars) And SMS confirmations are sent to applicable parties with generated notification IDs When the tenant submits the reschedule Then an audit event is stored within 2 seconds containing: appointmentId, eventId (UUIDv4), timestampUtc (ISO 8601), actor=tenant, channel=portal, originalWindow {startUtc,endUtc}, newWindow {startUtc,endUtc}, reasonCode, notes (if provided), notificationIds (array) And the event is immediately retrievable via API and appears on the appointment timeline after refresh
Manager-Driven Reschedule via Portal
Given a manager opens an appointment in the dashboard And selects a new time window When the manager selects a reason code and optionally enters notes (max 1000 chars) and confirms the change Then the system validates that reasonCode is present; if missing, a visible error prevents submission And upon successful submission, an audit event is recorded with: appointmentId, eventId, timestampUtc, actor=manager, channel=portal, originalWindow, newWindow, reasonCode, notes (if provided), notificationIds for any confirmations sent And the event persists across reloads and is visible in the timeline within 2 seconds
System-Automated Reschedule from Calendar Conflict
Given the shared calendar produces a conflict that triggers automatic rescheduling When Smart Rescheduler assigns a new time window per rules Then an audit event is recorded with: appointmentId, eventId, timestampUtc, actor=system, channel=system, originalWindow, newWindow, reasonCode=Auto_Conflict_Resolution, notes auto-generated describing the trigger And notificationIds include all outbound SMS IDs generated due to this reschedule (if any) And the event is available via API and shown in the timeline without user action
Appointment Timeline UI Displays Audit History
Given an appointment with three or more audit events When a user opens the appointment timeline view Then events render in reverse chronological order within 1.5 seconds for up to 50 events And each entry shows: localized timestamp, actor, channel, originalWindow, newWindow, reasonCode, notes indicator (expand/collapse if notes exist) And notificationIds are displayed as clickable tokens linking to the related SMS log view And loading more events preserves order and does not duplicate entries
Audit Trail Reporting Export (CSV)
Given a user applies a date range and optional property/unit filters in reporting When the user exports the Audit Trail as CSV Then the CSV contains one row per audit event with headers: appointmentId,eventId,timestampUtc,actor,channel,originalStartUtc,originalEndUtc,newStartUtc,newEndUtc,reasonCode,notes,notificationIds And all timestamps are UTC in ISO 8601 format; notificationIds are semicolon-separated if multiple And only events matching the filters are included; empty fields are present but blank where applicable And the export completes within 10 seconds for up to 50,000 events and the file downloads successfully
Immutability and Access Control of Audit Records
Given an existing audit event When a non-admin attempts to edit or delete the event Then the action is rejected with an authorization error and no data changes occur And when an admin submits a correction, a new audit event is appended with reasonCode=Correction and notes referencing the prior eventId; the original event remains unchanged And deletions of audit events are not permitted for any role
Reschedule Limits and Access Controls
"As a property manager, I want to control how often and how late tenants can reschedule so that operations remain efficient and fair."
Description

Provide configurable guardrails such as maximum reschedules per job, minimum notice before start time, blackout days, and fee or approval requirements beyond thresholds. Enforce role-based permissions for who may override limits and capture justification on override. The tenant UI proactively warns about limits and shows next eligible windows. Backend validation ensures consistent enforcement across channels, including API-triggered changes.

Acceptance Criteria
Enforce Maximum Reschedules Per Job
- Given a job with maxReschedules=2 and rescheduleCount=2, when a tenant attempts to reschedule via the portal, then the request is rejected with errorCode=RESCHEDULE_LIMIT_REACHED and HTTP 409. - Given the above, then the appointment remains unchanged in the shared calendar and no SMS is sent. - Given a job with maxReschedules=2 and rescheduleCount=1, when the tenant submits a valid new slot, then the reschedule succeeds and rescheduleCount increments to 2. - Given concurrent reschedule attempts that would exceed the limit, when processed, then at most one succeeds and the others fail with errorCode=RESCHEDULE_LIMIT_REACHED.
Enforce Minimum Notice Before Start Time
- Given minNotice=4h and an appointment starting in 3h 59m, when a tenant or vendor attempts to reschedule, then the request is rejected with errorCode=MIN_NOTICE_VIOLATION and HTTP 409. - Given minNotice=4h and an appointment starting in 4h 00m or more, when rescheduling to an allowed slot, then the request is accepted. - Given daylight-saving or timezone differences, when computing minNotice, then the system uses the property's timezone and produces the same decision via UI and API.
Honor Blackout Days and Quiet Hours
- Given blackoutDays=[Sun] and quietHours=20:00–07:00 in property timezone, when tenants view available slots, then slots on Sundays and within quiet hours are not displayed. - Given an API reschedule request targeting a slot within a blackout or quiet hour, then the request is rejected with errorCode=BLACKOUT_VIOLATION and HTTP 409. - Given vendor override is not used, when a manager attempts via dashboard to pick a slot in blackout, then the dashboard blocks with the same errorCode and does not alter the calendar.
Fee or Approval Required Beyond Threshold
- Given freeRescheduleThreshold=1 and feeAmount=$25, when a tenant initiates a 2nd reschedule, then the system requires either payment of $25 or manager approval before confirming. - Given the tenant chooses to pay, when payment is authorized, then the reschedule is confirmed and an SMS confirmation is sent; when payment fails, then the reschedule is not confirmed and no SMS is sent. - Given manager approval is granted, when approval is recorded, then the reschedule confirms without fee and an SMS confirmation is sent. - Given fee or approval is required, when using the public API, then the response is 202 with state=pending and reason=APPROVAL_REQUIRED until payment or approval completes.
Role-Based Override Permissions With Justification
- Given roles={Admin, Manager} have overrideRescheduleLimits=true and roles={Tenant, Vendor} have overrideRescheduleLimits=false, when a user without permission attempts to override, then the override controls are hidden in UI and API returns HTTP 403. - Given a user with permission selects "Override", when submitting, then the system requires a non-empty justification (min 10 characters) and records userId, role, timestamp, and justification with the change. - Given an override is approved, when conflicts are bypassed (limit, minNotice, blackout), then the reschedule is confirmed and the audit record links to the appointment and job.
Tenant UI Warnings and Next Eligible Windows
- Given the tenant triggers a guardrail (e.g., max reschedules reached or min notice violation), when attempting to pick a blocked slot, then the UI displays a banner stating the violated rule and the earliest date/time the tenant can reschedule. - Given guardrails and calendar availability, when the tenant opens the rescheduler, then the UI displays at least 3 next eligible windows computed by applying minNotice, blackoutDays, quietHours, and reschedule thresholds. - Given the tenant changes date filters, when the calendar re-renders, then only eligible windows are selectable and ineligible slots are disabled and cannot be submitted.
Consistent Enforcement Across Channels and Audit Trail
- Given identical guardrail settings, when the same reschedule attempt is made via tenant portal, manager dashboard, and public API, then each channel returns the same decision and errorCode. - Given any reschedule attempt (success, blocked, or pending approval/fee), when processed, then an immutable audit log entry is created with actor, channel, beforeSlot, afterSlot, decision, reason/errorCode, and optional payment/approval reference. - Given simultaneous requests for the same job from multiple channels, when processed, then the system uses atomic transactions so only one final state is committed and the others receive errorCode=STATE_CONFLICT with HTTP 409.

Entry Consent

A secure, time‑boxed “OK to enter if I’m out” toggle that captures digital consent with timestamps and optional access notes (pets, alarm, gate code). Vendors see the green light only for the approved window, speeding access while preserving a clean audit trail.

Requirements

Secure Consent Toggle & Time Window Selection
"As a tenant, I want to quickly grant time-limited permission to enter my unit so that vendors can access my home if I’m out without delays."
Description

Provide a tenant-facing, mobile-first toggle to grant entry consent for a specific start and end time, paired with an explicit acknowledgement of terms. Include intuitive date/time pickers with timezone normalization, validation for minimum/maximum window lengths, and clear inline guidance. Persist consent as a structured record linked to the work order, including unit, request ID, and selected window. Expose CRUD APIs for create/update/revoke and ensure graceful handling of partial saves, retries, and offline-to-online transitions.

Acceptance Criteria
Mobile Toggle Grants Time-Boxed Entry Consent
Given a tenant views a specific work order on a mobile device with no active consent When the tenant toggles "OK to enter" on and selects a start and end time that are both in the future with end > start Then the system saves consent and the API POST /work-orders/{id}/consents returns 201 with the persisted record including startUtc and endUtc And the UI displays a success confirmation, the active consent state, and the read-only approved window And the date and time inputs render as mobile-optimized pickers with a visible timezone label And the consent is active only between startUtc and endUtc; outside that window vendor visibility indicates "no consent" And the vendor calendar shows a green access indicator only for the approved window
Timezone Normalization Across Tenant and Vendor Views
Given the tenant device timezone is TzA and the vendor account timezone is TzB When the tenant selects a 10:00–12:00 window in TzA and submits consent Then the record is stored in UTC with startUtc and endUtc matching the correct conversion from TzA And the tenant sees 10:00–12:00 in TzA, the vendor sees the corresponding converted interval in TzB, and ICS/export uses UTC with TZID metadata And DST transitions are correctly handled such that a 2-hour window remains two hours after conversion
Window Length Validation and Inline Guidance
Given org policy sets minWindow=30 minutes and maxWindow=24 hours When the tenant attempts to save a window shorter than minWindow, longer than maxWindow, or with start < now Then the Save action is disabled, inline error text appears adjacent to the invalid field explaining the rule with an example, and no server call is made And when a valid window is entered, the error clears and Save becomes enabled
Terms Acknowledgement Gate
Given the terms checkbox is unchecked When the tenant toggles "OK to enter" on and sets a valid window Then the Save button remains disabled and a notice indicates that terms must be acknowledged When the tenant checks the terms checkbox Then Save becomes enabled, and upon submission the consent record stores termsAccepted=true and termsVersion=current
Consent Record Persistence and Linkage
Given a valid submission When consent is created Then a record is persisted containing at minimum: consentId, workOrderId, unitId, tenantId, startUtc, endUtc, status=active, createdAt, updatedAt, termsAccepted, termsVersion, source=mobile And the record is retrievable via GET /work-orders/{id}/consents/{consentId} and included in the work order detail response And the consent automatically transitions to status=expired at endUtc and no longer appears as active
API Update and Revoke with Audit Trail
Given an active consent whose startUtc is in the future When PATCH /consents/{id} updates startUtc and/or endUtc to a new valid window Then the API returns 200 and the record is updated with an audit entry capturing actor, timestamp, and before/after values When DELETE /consents/{id} is called by an authorized actor (tenant or landlord) Then the API returns 204, status changes to revoked, vendor green access indicator is removed immediately, and an audit entry is recorded And attempts to update or revoke after endUtc return 422 with no change
Offline Draft, Retry, and Idempotent Sync
Given the tenant is offline When the tenant toggles consent on, selects a window, acknowledges terms, and taps Save Then a local draft is stored and the UI shows Pending Sync; no server record is created yet And if the user has entered partial data (e.g., only start time), the draft persists locally and is restored on re-open; no server submission occurs until all required fields are valid When connectivity is restored, the app retries submission with exponential backoff using an idempotency key; on success the UI updates to Active without user action And after 6 failed retries the UI shows a clear error with a manual Retry option; manual retry uses the same idempotency key to prevent duplicates
Time-Box Enforcement & Auto-Expiry
"As a property manager, I want the system to restrict vendor entry to the approved window so that liability is minimized and schedules remain accurate."
Description

Enforce the consent window at the backend and UI layers so that vendor authorization (“green light”) is active only within the approved timeframe. Automatically expire consent at the end time, prevent scheduling outside the window, and handle overlapping or duplicate windows with conflict resolution rules. Account for daylight saving changes and tenant/vendor timezone differences, and surface clear status messages before, during, and after the window.

Acceptance Criteria
Vendor Green Light Active Only Within Consent Window
Given a consent window exists for Request R1 on Unit U1 from [start] to [end] When the vendor checks authorization at time t via UI or API Then authorized=true only if [start] <= t < [end] and a green indicator is shown in the UI And authorized=false with error_code="CONSENT_INACTIVE" otherwise And API response time <= 500 ms And UI reflects state changes within 5 seconds of crossing [start] or [end]
Automatic Expiry and Audit Trail at End Time
Given a consent window with end time [end] When [end] is reached Then the system updates consent status to "Expired" within 60 seconds And emits an audit log entry {event:"consent.expired", request_id:R1, window_id:W1, occurred_at:UTC timestamp, actor:"system"} And the UI banner updates to "Consent expired at [local time]" and removes the green indicator And subsequent authorization checks return HTTP 403 with error_code="CONSENT_EXPIRED"
Prevent Scheduling Outside Consent Window
Given Entry Consent is enabled with window [start, end] When a user attempts to create or modify a vendor appointment that starts before [start] or ends after [end] Then the action is blocked with validation message "Scheduling must be within consent window [start – end]" And no calendar event is created or updated And no SMS/notifications are sent And the first three available slots fully inside [start, end] are suggested (if any)
Overlapping and Duplicate Consent Windows Resolution
Given multiple consent windows exist for the same request When two windows overlap or are exact duplicates Then the most recently updated window is marked Active and earlier overlapping windows are marked Superseded And authorization evaluates only the Active window And exact duplicates are deduplicated to a single Active record with merged audit references And the vendor UI displays only one Active window with a link to view superseded entries in the audit trail
Timezone and Daylight Saving Handling
Given the property timezone is TZ_P (e.g., America/Los_Angeles) and the vendor timezone may differ When a consent window is created, displayed, and enforced across viewers and spans potential DST changes Then the backend stores windows in UTC with source timezone metadata and evaluates authorization using the property timezone rules And all UI times are rendered in the viewer’s local timezone with correct DST offsets And test cases for spring-forward and fall-back days authorize only within the intended local times (no ±1 hour drift)
Status Messaging Before, During, and After Window
Given a consent window [start, end] When viewing before [start] Then the UI shows "Consent scheduled" with countdown to [start] and no green indicator When viewing during [start, end) Then the UI shows "Consent active until [time]" with green indicator and minutes remaining When viewing at or after [end] Then the UI shows "Consent expired at [time]" with an action to request new consent And status text is consistent across web UI and outbound messages (email/SMS templates)
Access Notes & Sensitive Data Handling
"As a tenant, I want to add access notes like pet info and a gate code so that vendors can enter safely without me being present."
Description

Capture optional access notes such as pets, alarm instructions, and gate or lockbox codes, storing them with encryption at rest and in transit. Apply field-level permissions so vendors see only what they need and only during the consent window, with masking for sensitive values and automatic redaction in exports. Support configurable retention policies, audit changes to notes, and provide inline warnings to tenants about sharing sensitive information.

Acceptance Criteria
Encryption of Access Notes at Rest and In Transit
- Given an authenticated user submits access notes, When the notes are transmitted to the backend, Then the connection uses TLS 1.2+ and HSTS is enforced. - Given access notes are persisted, When data is written to storage, Then notes (including sensitive fields) are encrypted at rest using AES-256 with managed keys and key rotation enabled. - Given an HTTP (non-TLS) request is made to submit or fetch notes, When the request reaches the edge, Then it is refused or redirected to HTTPS and no note data is processed over plaintext. - Given an encryption key rotation event occurs, When previously stored notes are accessed, Then they remain decryptable to authorized roles and the rotation is recorded in security logs.
Field-Level Permissions and Consent Window Enforcement for Vendors
- Given a vendor is authenticated, When viewing a work order outside the active consent window, Then access notes are not retrievable in UI or API (HTTP 403) and placeholders are shown instead. - Given a vendor is authenticated within an active consent window, When viewing access notes, Then only fields marked vendor_visible=true are returned; all other sensitive fields appear masked (e.g., "••••"). - Given a consent window expires or is revoked, When the vendor attempts to view notes, Then access is blocked within 30 seconds for existing sessions and all subsequent requests are denied and audited. - Given a vendor attempts to fetch hidden fields via direct API calls, When the request is processed, Then the response excludes those fields and the attempt is logged.
Masking and Controlled Reveal of Sensitive Values
- Given a manager with reveal permission views a sensitive field, When clicking Reveal, Then the full value is displayed for no longer than 60 seconds and then automatically re-masked. - Given a user has not completed MFA in the last 5 minutes, When attempting to Reveal a sensitive value, Then the system requires MFA re-auth before reveal proceeds. - Given a sensitive value is revealed, When the event is logged, Then the audit entry records user, timestamp (UTC), field, record ID, and reason; vendor roles cannot perform Reveal. - Given front-end rendering, When viewing masked fields, Then the full value is never present in page source, client logs, or third-party analytics payloads.
Automatic Redaction in Exports and Notifications
- Given an export (CSV, PDF, bulk API) is requested, When the requester lacks export_sensitive permission, Then sensitive fields are replaced with "REDACTED" in the output. - Given the requester has export_sensitive permission, When generating the export, Then sensitive fields are masked (e.g., last 2 characters only) unless include_full_sensitive is explicitly enabled with MFA and a required business justification. - Given email or SMS notifications are sent, When they reference access notes, Then full sensitive values are never included; only masked snippets and a secure link requiring authentication are sent. - Given a client attempts to include sensitive fields via query parameters or hidden columns, When the export is generated, Then server-side redaction still applies and the attempt is audited.
Configurable Retention Policies for Access Notes
- Given a retention policy is configured at the portfolio or property level, When the retention period elapses after work order closure, Then sensitive fields are purged or irreversibly redacted and non-sensitive notes follow the configured retention. - Given no retention policy is configured, When notes are stored, Then the system applies the default retention duration defined by the organization settings. - Given the daily retention job runs, When it processes eligible records, Then deletions/redactions are logged with counts and a report is available to admins. - Given a legal hold is placed on a work order or portfolio, When the retention job runs, Then affected records are exempt from purge until the hold is removed and this is auditable.
Audit Trail for Access Notes Changes and Access
- Given any create, update, delete, reveal, or view of access notes occurs, When the action completes, Then an immutable audit record is stored with actor, role, timestamp (UTC), IP, action, record ID, and old/new values with sensitive diffs masked. - Given an admin searches the audit log, When filtering by date range, user, or record ID, Then matching results are returned and exportable with sensitive values redacted. - Given audit data retention is required, When the system manages audit records, Then they are retained for at least 24 months unless longer is configured by policy.
Inline Sensitive-Info Warning for Tenants
- Given a tenant focuses a sensitive access note field (e.g., gate code, alarm PIN), When typing a value matching sensitive patterns, Then an inline warning appears explaining risks and masking behavior, and a confirmation checkbox is required before saving. - Given a tenant submits access notes with sensitive fields, When saving, Then the values are masked in the UI immediately after save and tied to the active consent window if one is set. - Given the tenant has not acknowledged the warning, When clicking Save, Then the submission is blocked and an accessible error message indicates that acknowledgment is required. - Given localization is enabled, When the tenant’s locale is set, Then the warning and field labels display in the appropriate language.
Vendor Green-Light Indicators & Calendar Integration
"As a vendor, I want a clear green-light indicator for a job during the approved time so that I know exactly when I am authorized to enter."
Description

Display a prominent green-light authorization badge on vendor job cards and the shared calendar only within the consent window, with real-time updates and offline-safe caching for mobile. Send SMS/in-app confirmations containing the approved window and access notes when available, and include deep links back to the job. Prevent double-booking by reconciling consent windows with existing schedules and highlight conflicts with actionable prompts.

Acceptance Criteria
Green-Light Badge Visibility Within Consent Window
Given a job has an active entry-consent window from T1 to T2 (server time), When the current time is between T1 and T2, Then the vendor job card and shared calendar entry display a prominent green-light authorization badge. Given the current time is before T1 or after T2, When the view refreshes or receives a push update, Then the green-light badge is not displayed and any prior badge is removed within 60 seconds. Given multiple consent windows exist for a job, When a new window starts after a prior window ends, Then the badge reflects only the currently active window. Given a job has no active consent window, Then no green-light badge is displayed anywhere. Given access notes exist for the consent, When the badge is displayed, Then an access-notes indicator is visible with secure tap-to-view access notes.
Real-Time Update Propagation for Consent Changes
Given a landlord enables, disables, or edits an entry-consent window, When the change is saved, Then all open vendor job cards and shared calendar views reflect the new state within 5 seconds without manual refresh. Given a client is temporarily disconnected during the update, When connectivity resumes within 60 seconds, Then the client retrieves the latest consent state and updates the badge accordingly. Given conflicting client cache exists, When the server version differs from the local version, Then the server version prevails and the UI shows the updated window and badge state. Given a push delivery fails, When retries are exhausted, Then the UI reconciles on next poll within 60 seconds and logs the failure for observability.
Mobile Offline Caching and Resync of Consent State
Given a vendor viewed a job while online, When the device is offline, Then the last-known consent state, window, and access-notes presence are cached and displayed with a "Last updated <timestamp>" label, and the badge shows an "Offline—verify on reconnect" state. Given the device regains connectivity, When the app resumes or a foreground sync occurs, Then the consent state and notes revalidate with the server and the badge updates within 10 seconds. Given the cached consent data is older than 24 hours, When offline, Then the app warns that data may be outdated and suppresses the green-light badge until validation. Given no cached data exists for the job and the device is offline, Then no green-light badge is shown and a placeholder indicates data unavailable.
Outbound Confirmation Messages with Window, Notes, and Deep Link
Given consent is granted for a job from T1 to T2, When the consent is saved, Then SMS and in-app confirmations are sent to all assigned vendors containing the approved window (in the vendor’s timezone), any available access notes, and a deep link to the job. Given access notes are absent, When the message is composed, Then the notes section is omitted and the message still sends successfully. Given a recipient taps the deep link, When the FixFlow app is installed and the user is authenticated, Then it opens the job details screen; otherwise it opens a secure web view after authentication. Given message delivery fails transiently, When a retry policy is applied, Then the system retries up to 3 times and records success/failure with timestamps for the audit log.
Double-Booking Prevention via Consent-Schedule Reconciliation
Given a vendor has an existing appointment from S1 to E1, When a user attempts to schedule a new job within consent window W1 that overlaps S1–E1, Then the calendar highlights the conflict and displays an actionable prompt. Given the conflict prompt is shown, When the user selects a resolution (pick a different time within W1, choose a different vendor, or request override), Then the system validates the choice and updates the schedule accordingly. Given a conflict remains unresolved, Then the system blocks saving the conflicting schedule and explains why. Given an override is requested, When the user has manager privileges, Then the system records an audit entry and allows the override while maintaining the conflict indicator until saved. Given no overlap exists with W1 and existing appointments, Then the schedule saves without warnings.
Timezone-Accurate Window Display and Evaluation
Given consent windows are stored in UTC, When displayed to users, Then start and end times are converted to each viewer’s timezone with correct daylight-saving adjustments. Given the landlord and vendor are in different timezones, When the vendor views the job, Then the green-light evaluation uses server time (UTC) and the displayed window is in the vendor’s local time with a clear timezone label. Given a vendor changes their timezone preference, When the job is reloaded, Then the displayed window updates to the new timezone without altering the underlying UTC values. Given the device clock is skewed by more than 2 minutes, Then the system still evaluates window state using server time and displays a subtle server-time reference to avoid confusion.
Consent Audit Trail & Evidence Export
"As a property manager, I want a verifiable audit trail of tenant consent so that I can demonstrate compliance and resolve disputes if they arise."
Description

Record an immutable audit log capturing who granted consent, timestamps, device/IP metadata, consent terms version, any changes or revocations, and vendor view/access events. Provide role-based access to the log within the work order and enable export as PDF/CSV with redaction for sensitive fields. Ensure logs are tamper-evident, time-synchronized to UTC, and searchable by unit, request, tenant, or date range.

Acceptance Criteria
Log Consent Grant With Required Metadata
Given a tenant toggles "OK to enter" ON for a work order When the action is saved Then an audit entry is appended (not updated) with fields: eventType=CONSENT_GRANTED, actorId, actorRole=Tenant, workOrderId, unitId, consentWindowStart, consentWindowEnd, termsVersionId, timestamp (UTC ISO 8601 with trailing Z), ipAddress, userAgent, deviceIdentifier (if available), eventId (UUID), previousEntryHash, entryIntegrityHash And the entry is immutable/read-only via API and UI (update/delete attempts return 405 and are logged) And the entry becomes visible in the work order "Consent Audit" tab to authorized roles within 2 seconds of save
Log Consent Revocation and Notes Changes
Given an existing consent is active for a work order When the tenant toggles consent OFF Then an audit entry is appended with eventType=CONSENT_REVOKED and the same required metadata fields in UTC And the consent window is closed and reflected in subsequent entries Given consent access notes are edited (e.g., pets/alarm/gate code) When the changes are saved Then an audit entry is appended with eventType=CONSENT_NOTES_UPDATED including beforeValue and afterValue snapshots (sensitive values subject to redaction policy) and UTC timestamp
Track Vendor View and Access Events
Given a vendor assigned to the work order opens the work order or consent banner When the page or API is viewed Then an audit entry is appended with eventType=VENDOR_VIEW, vendorId, organizationId, channel (web/mobile/API), and timestamp in UTC Given a vendor attempts entry during a time window check-in When an access check is recorded Then an audit entry is appended with eventType=VENDOR_ACCESS_ATTEMPT, withinWindow=true|false derived from consentWindowStart/End, vendorId, and UTC timestamp
Tamper-Evident Log and Integrity Verification
Given the audit log contains N entries When a Compliance Admin triggers "Verify integrity" from the work order audit tab or integrity API Then the system recomputes the hash chain (previousEntryHash → entryIntegrityHash) and returns status=VALID with the latest chainHeadHash And all entries remain append-only (create-only); delete or update requests return 405 and are logged with eventType=WRITE_REJECTED Given a test harness introduces a single-bit alteration to any stored entry When integrity verification runs Then status=INVALID is returned and the first mismatching entryId is identified in the result
Role-Based Access to Audit Log In-Context
Given user roles {Tenant, Vendor, PropertyManager, LandlordOwner, ComplianceAdmin} When accessing the work order "Consent Audit" tab or audit API Then access is enforced as: Tenant → view entries for their own work order with sensitive fields redacted; Vendor (assigned) → view consent status and their own view/access events only; PropertyManager/LandlordOwner/ComplianceAdmin → view full audit log And unauthorized users or unassigned vendors receive HTTP 403 with no partial data leakage And deep-linked audit URLs require authentication and authorization equivalent to in-context access
Export Evidence as PDF/CSV With Redaction Controls
Given an authorized user is viewing the work order audit log When they select Export and choose format (PDF or CSV) and Redaction setting (On by default) Then the file is generated and downloaded within 5 seconds for logs up to 10,000 entries and the export event (who, when, format, redaction) is logged And when Redaction=On, the following fields are masked with "REDACTED": ipAddress, deviceIdentifier, userAgent, accessNotes (including gate codes) And when Redaction=Off, only users with role=ComplianceAdmin may proceed after confirming a warning modal And the export includes: header with workOrderId, unitId, tenantId (or anonymized if redacted), date range, termsVersionId(s), integrity status (VALID/INVALID), chainHeadHash; all timestamps are UTC with trailing Z
Search and Filter Audit Logs by Unit/Request/Tenant/Date
Given the audit log search interface When a user filters by any combination of unitId, workOrderId, tenant identifier (name/email/phone), and UTC date range Then only matching audit entries are returned, ordered by timestamp desc by default, with pagination And for datasets up to 100,000 entries per property, the first page (50 items) returns within 2 seconds And results respect role-based access controls and redaction rules; cross-property data is not returned to users without access And empty-result queries display 0 results without exposing any PII
Identity Verification for Consent
"As a tenant, I want to verify my identity before granting entry consent so that my authorization cannot be forged."
Description

Require OTP-based verification (SMS and email) before recording consent or revocation, with rate limiting, expiration, and lockout after repeated failures. Store verification proofs tied to the consent record, support fallbacks for unreachable channels, and provide accessible flows for users with limited SMS/email access. Ensure minimal friction for repeat verifications within a short window while maintaining security standards.

Acceptance Criteria
Consent Toggle Requires OTP Verification
Given an authenticated tenant toggles "OK to enter" on a maintenance request When the tenant requests verification Then the system sends a 6‑digit OTP via SMS by default and displays a secure OTP input Given the OTP has not been verified When the tenant navigates away, refreshes, or attempts to submit without a valid OTP Then the consent state is not saved and vendor access is not granted Given the tenant enters the correct OTP within 5 minutes of issuance When they submit the OTP Then the consent is recorded with a UTC timestamp, the approved entry window is activated, and a success confirmation is shown Given the tenant enters an incorrect OTP When submitted Then an error is displayed, the failed attempt is counted, and consent is not recorded
Revocation Requires OTP Verification
Given a tenant attempts to revoke previously granted entry consent When the tenant requests verification Then the system issues a new 6‑digit OTP via the tenant’s last successful channel preference, with ability to choose the alternate channel before sending Given the tenant provides the correct OTP within 5 minutes When the OTP is submitted Then the consent is revoked, the audit trail records the revocation with UTC timestamp, and vendors immediately lose access to the entry window Given the tenant fails OTP verification When they submit an incorrect or expired OTP Then revocation does not occur and an informative error is shown
OTP Expiration, Resend, and Rate Limiting
Given an OTP is issued When 5 minutes elapse without successful verification Then the OTP expires and subsequent submissions are rejected with an expiration message Given a tenant requests a resend When less than 30 seconds have passed since the last send Then the resend action is disabled with a countdown indicator Given resend limits When the tenant requests OTP sends on a single channel Then no more than 3 sends are allowed per 15 minutes and no more than 6 sends per 24 hours; additional requests are blocked with guidance to try the alternate channel Given a new OTP is sent When the resend occurs Then all previously issued OTPs for that verification flow are invalidated
Lockout After Repeated Verification Failures
Given a tenant attempts OTP verification for a single consent or revocation action When 5 incorrect OTP submissions occur within 15 minutes on the same channel Then verification for that action and channel is locked for 15 minutes, a countdown is shown, and no further attempts are accepted on that channel during lockout Given a lockout is in effect When the tenant opts to try the alternate channel Then the system permits alternate‑channel verification and requires a simple bot‑mitigation check before sending a new OTP Given repeated lockouts When 3 lockouts occur within 24 hours for the same account Then a 24‑hour hard lock is applied to OTP verification for consent actions, the user is notified via email, and a security alert is recorded
Store Verification Proofs Tied to Consent Record
Given an OTP verification succeeds for consent or revocation When the system records the action Then a verification proof is stored and linked to the consent record, containing: method (SMS/email/voice), provider message/call ID, masked destination, issued_at, verified_at (UTC), attempt_count, result, and non‑reversible hashes of IP and device identifier; the OTP value itself is never stored in plaintext Given an auditor or admin requests an export for a request When the audit log is exported Then the consent record includes a reference to the verification proof and its metadata fields; sensitive fields are masked and hashes are preserved Given data retention policies When 24 months elapse after request closure Then verification proofs are eligible for purge according to retention settings
Fallback and Accessibility When Primary Channel Is Unreachable
Given an OTP send attempt via SMS When the provider reports delivery failure or no delivery confirmation within 60 seconds Then the UI offers immediate fallback options to email or voice call, without losing the current verification flow Given a user selects voice call When the call connects Then the system reads the 6‑digit code twice, offers a repeat option via DTMF, and logs the call attempt; the code validity remains 5 minutes from issuance Given accessibility requirements When a user navigates the verification UI Then all controls are keyboard accessible, labeled for screen readers, meet WCAG 2.1 AA contrast, and inline errors include programmatic associations to inputs
Reduced‑Friction Re‑Verification Within Short Window
Given a tenant successfully verified an OTP for consent or revocation within the last 8 hours When the same tenant initiates another consent or revocation on the same maintenance request from the same browser session or device‑bound trust token and contact info is unchanged Then the system bypasses a new OTP challenge, reuses the prior verification, and records a verification proof referencing the prior verification ID Given the prior verification is older than 8 hours, occurs on a different device/session, or contact info changed When the tenant initiates consent or revocation Then a fresh OTP challenge is required
Consent Revocation & Change Notifications
"As a tenant, I want to revoke or change my entry consent before the scheduled time so that I stay in control if my plans change."
Description

Allow tenants to revoke or modify consent before the start time, with configurable cutoffs (e.g., 15 minutes before) to protect vendor travel time. Propagate real-time updates to vendors and managers via in-app alerts, SMS, and calendar updates, and automatically adjust the green-light state. Record revocations in the audit log, notify affected schedules, and provide clear guidance to reschedule when needed.

Acceptance Criteria
Revoke Consent Before Cutoff
Given a tenant has an active entry consent window and the current time is earlier than the configured cutoff relative to the start time When the tenant revokes consent Then the appointment green-light state switches to Off within 5 seconds And an in-app alert is delivered to assigned vendor(s) and manager(s) within 5 seconds And an SMS notification is sent to each recipient within 60 seconds And the calendar event status updates to "Cancelled by Tenant" with revocation timestamp within 60 seconds And an audit log entry is recorded with event_type=consent_revoked, actor=tenant, appointment_id, previous_state, new_state, timestamp (ISO8601 with timezone), channel, and IP/device And the tenant is shown a confirmation with a reschedule prompt
Modify Consent Window and Notes Before Cutoff
Given a tenant has an active entry consent window and the current time is earlier than the configured cutoff relative to the start time When the tenant modifies the consent time window and/or access notes Then the consent window updates immediately and the green-light reflects the new window And access notes changes are saved and visible to vendor(s) and manager(s) And in-app alerts are delivered to vendor(s) and manager(s) within 5 seconds detailing the changes (old → new) And SMS notifications summarizing the change are delivered within 60 seconds And the calendar event updates start/end time and description within 60 seconds And an audit log entry is recorded with event_type=consent_modified, diff of fields changed, timestamp, actor, and prior values
Block Changes At Or After Cutoff
Given a property has a configured cutoff window for changes And the current time is at or after the cutoff relative to the appointment start time When a tenant attempts to revoke or modify consent Then the action is blocked with an error explaining changes are locked X minutes before start And no changes are made to the consent window, green-light, or calendar event And no notifications are sent to vendor(s) or manager(s) And an audit log entry is recorded with event_type=consent_change_blocked, attempted_action, timestamp, and actor
Configurable Cutoff Enforcement and Time Zone Accuracy
Given a property-level cutoff value (in minutes) is set or defaults to 15 minutes if unset When calculating change eligibility Then the cutoff is applied using the property’s time zone and the appointment’s start time And the UI displays the exact remaining time until cutoff to the tenant And changes are allowed only when now < start_time - cutoff And changes are blocked when now ≥ start_time - cutoff
Real-Time Notification Propagation and Retry
Given a revocation or modification is accepted When notifications are dispatched Then in-app alerts appear for vendor(s) and manager(s) within 5 seconds And SMS messages are queued within 10 seconds and delivered within 60 seconds And the calendar feed/event updates propagate to connected calendars within 2 minutes And if an SMS delivery fails, the system retries up to 3 times over 5 minutes and logs delivery status per recipient And offline recipients receive the in-app alert on next app/session start with the original timestamp
Green-Light Visibility Control for Vendors
Given consent is revoked or the window is modified When vendor(s) view the appointment Then the green-light indicator is hidden or shown only for the currently approved window And after revocation, the indicator displays Off and access is marked Not Authorized for that window And updated notes and timing are visible on the vendor dashboard and in the appointment details
Rescheduling Guidance After Revocation
Given consent has been revoked before the start time When the system updates the appointment Then the appointment status changes to Needs Reschedule And vendor(s) and manager(s) receive an action link to propose alternate time slots And the tenant receives a prompt to accept or propose alternatives And all proposals and selections update the calendar and are logged in the audit trail

Prep Checklist

Issue‑specific prep tips and micro‑guides (clear under sink, move furniture, crate pets, locate shutoff) appear right in the portal before the visit. Tenants check off steps, boosting first‑visit fix rates and preventing delays or return trips.

Requirements

Dynamic Issue-Based Checklists
"As a property manager, I want checklists to be automatically tailored to the reported issue so that tenants receive only relevant prep steps and vendors arrive prepared."
Description

Map maintenance issue categories to curated prep steps and automatically attach the relevant checklist to each work order when an appointment is created or the issue type changes. Support global defaults and per-property, per-unit, and per-vendor overrides, including conditional steps (e.g., pets present, water shutoff access). Ensure versioned templates, required vs. optional step flags, and safe fallbacks if an issue type is unmapped. This enables consistent, context-aware guidance that improves first-visit fix rates and reduces return trips while aligning with FixFlow’s scheduling and ticket lifecycle.

Acceptance Criteria
Auto-Attach Checklist on Appointment Creation
Given a work order has an issue type mapped to a checklist template and an appointment is created When the appointment is saved Then the system attaches the resolved checklist template within 2 seconds And the checklist is visible in the tenant portal and vendor view within 5 seconds of refresh And the attached checklist includes all steps from the selected template version with required/optional flags preserved And exactly one checklist is attached per appointment (no duplicates) And the attachment event is recorded in the audit log with user, timestamp, template ID, and version
Auto-Update Checklist on Issue Type Change
Given a work order has an attached checklist based on the current issue type When the issue type is changed before the visit start time Then the system replaces the attached checklist with the resolved template for the new issue type within 2 seconds And previously checked steps that exist in the new template (matched by step stable ID) remain checked; others are cleared And tenant and vendor views display the updated checklist on next load And the replacement is logged with prior and new template IDs and versions And if the issue type is reverted, the system re-resolves and updates accordingly
Override Hierarchy: Global, Property, Unit, Vendor
Given checklist templates exist at multiple scopes (global default, vendor, property, unit) When resolving which template to attach for a work order with known unit, property, and (optionally) vendor Then precedence is applied: Unit override > Property override > Vendor override > Global default And if multiple templates exist at the same scope for the same issue category, the template marked current with the highest version is selected And if no vendor is assigned, vendor scope is skipped without error And the chosen scope, template ID, and version are stored on the work order for auditing
Conditional Steps Rendering (Pets, Shutoff, Access)
Given a template includes conditional steps driven by attributes (e.g., pets_present, shutoff_access, elevator_available) When those attributes evaluate to true for the work order from unit profile, tenant answers, or dispatcher input Then the corresponding steps are included with their configured required/optional flags And when the attributes evaluate to false, those steps are omitted And if an attribute value changes before the visit, the checklist recalculates included conditional steps within 2 seconds without altering the checked state of unaffected steps And the evaluation sources and inclusion/exclusion results are recorded in the audit log
Required vs Optional Steps Enforcement
Given an attached checklist contains required and optional steps When a tenant submits Prep Complete for the appointment Then submission succeeds only if all required steps are checked And if any required steps are unchecked, submission is blocked and a list of missing required steps is displayed And staff users can override the block with a mandatory reason which is logged And optional steps never block submission
Versioned Templates and Work Order Snapshotting
Given templates are versioned with immutable historical versions and one current version per scope-category When a checklist is attached to a work order Then the template version ID and step stable IDs are snapshotted on the work order And later edits create a new template version and do not mutate the already-attached checklist And a staff user can update the attachment to the latest version, migrating matching steps by stable ID and logging a migration record And reporting can filter by attached template version
Safe Fallback for Unmapped Issue Types
Given an issue type has no mapped template at unit, property, vendor, or global scopes When an appointment is created or the issue type changes to this unmapped value Then a system-defined General Prep fallback checklist is attached And an admin alert is created with the unmapped category and affected work order And the tenant portal labels the checklist as General Prep And appointment creation is not blocked by the fallback
Tenant Prep UI with Progress Tracking
"As a tenant, I want an easy, mobile-friendly checklist I can check off before the visit so that I know what to do and avoid delays or rescheduling."
Description

Display the checklist prominently within the tenant portal appointment view with clear step titles, expandable micro-tips, estimated time per step, and required/optional labels. Allow tenants to check and uncheck items with autosave, show percentage completion and a "complete by" deadline tied to the appointment time, and provide mobile-first, accessible interactions (WCAG-compliant labels, keyboard navigation, readable contrast). Persist per-step timestamps and handle multi-user households by user identity. Ensure fast performance and graceful degradation across devices and networks.

Acceptance Criteria
Inline Checklist Display with Expandable Micro-Tips
- Given a tenant opens the appointment view with a prep checklist, Then the checklist appears within the primary viewport above other appointment details without requiring more than one scroll. - Each item displays: a step title (<= 60 characters), a Required/Optional badge, and an estimated time label (e.g., "~3 min"). - Micro-tips are collapsed by default; When the tenant activates the per-item "Show tips" control via click/tap/Enter/Space, Then the tips expand and the control label/state toggles to "Hide tips". - The expand/collapse state is preserved while the user remains on the page and when navigating back within the same session. - If an item has no tips, no expand/collapse control is rendered for that item.
Autosave Check/Uncheck with Offline Resilience
- When a tenant checks or unchecks an item, the visual state updates immediately and an autosave request is initiated within 500 ms. - If the network is unavailable or the request fails, the change is queued in local storage and retried with exponential backoff for up to 24 hours. - While queued, the UI displays a subtle "Saving…" indicator; on success it changes to "Saved" within 100 ms; on final failure a non-blocking alert is shown with a Retry action. - Saved changes persist server-side and are reflected after page reload; cross-device views reflect the update within 10 seconds of a successful save. - Autosave must not trigger duplicate items or data corruption when the same item is toggled repeatedly within 2 seconds (last action wins).
Completion Percentage and Appointment-Tied Deadline
- The progress bar displays percentage = (completed items / total items) * 100, rounded to the nearest whole number; the numerator includes both Required and Optional items. - A "Complete by" deadline displays the appointment start date/time in the tenant’s appointment timezone. - When current time <= deadline, the deadline label uses a neutral style; when current time > deadline and any Required item is incomplete, an "Overdue" state with warning icon and accessible text is shown; interactions remain enabled. - When all Required items are complete, a visible "All required steps complete" status is shown adjacent to the progress indicator, regardless of Optional item state. - Progress percentage and status update within 200 ms after any item is toggled.
Accessibility and Keyboard Navigation (WCAG 2.2 AA)
- All checkboxes have programmatically associated labels (for/id), correct roles, names, and states; expand/collapse controls expose aria-expanded and are operable via keyboard (Enter/Space). - Focus order follows visual order; no keyboard traps; focus indicators have a minimum 2px outline with contrast ratio >= 3:1 against adjacent colors. - Text contrast ratio >= 4.5:1 (normal) and >= 3:1 (large); interactive controls meet WCAG 2.2 AA. - Progress updates (percentage and status) announce via aria-live="polite" within 1 second of change without disrupting focus. - Feature passes automated accessibility tests (axe/lighthouse) with 0 critical violations and passes manual smoke tests on NVDA+Chrome (Windows) and VoiceOver+Safari (iOS) for core tasks: navigate list, toggle item, expand tips, read progress.
Per-Step Timestamps and Multi-User Identity Handling
- On each toggle, the system records userId, itemId, action (checked/unchecked), and an ISO 8601 timestamp in the appointment’s timezone. - The UI displays "Last updated <relative time> by <first name>"; if household privacy is enabled, display "by someone in your household" instead of the name. - If two household users toggle the same item within a 5-second window, the server applies last-write-wins by timestamp; all connected clients reflect the resolved state within 10 seconds. - A per-item history endpoint returns the last 20 toggle events with userId and timestamp for audit; response time <= 500 ms at p95. - Users without permission to view other members’ names never see identifying details in the UI or API responses.
Performance Targets on Mobile Networks
- On a mid-tier mobile device over 4G (400 ms RTT / 1.6 Mbps), Time to Interactive for the checklist view <= 2.5 s and Largest Contentful Paint <= 2.5 s at p75. - Toggling an item to confirmed save completes within 1 s on broadband and within 3 s on 3G; offline queueing occurs within 100 ms. - The feature’s incremental JS+CSS payload <= 80 KB gzipped and total additional assets <= 200 KB; no single tip image > 50 KB. - P95 main-thread long tasks from this feature total < 200 ms during initial render and < 50 ms per toggle interaction.
Graceful Degradation and No‑JavaScript Fallback
- If JavaScript is disabled or fails, the checklist renders as an HTML form with server-side processing: checking/unchecking items and clicking "Save progress" persists state and returns the updated page without errors. - On devices as small as 320 px width, all controls are accessible without horizontal scroll; tap targets are >= 44 x 44 px; content reflows correctly in portrait and landscape. - When the Save-Data header is present or the app detects low-bandwidth, rich media in tips is deferred or replaced with compressed alternatives, and core interactions remain fully functional.
Automated Pre-Visit Reminders
"As a tenant, I want timely reminders with a link to my prep checklist so that I remember to complete the steps before the technician arrives."
Description

Send automated SMS and email reminders that include a deep link to the tenant’s checklist at configurable intervals (e.g., 48h, 24h, 2h before the visit), respecting time zones and quiet hours. Escalate messaging if required steps remain incomplete, and stop reminders when all required steps are done. Use FixFlow’s existing messaging infrastructure and templates, with per-property branding, opt-out management, and delivery analytics. Include a secure token in links to avoid login friction while maintaining privacy.

Acceptance Criteria
Scheduling Respects Configured Intervals, Time Zones, and Quiet Hours
Given a visit is scheduled with the tenant’s local time zone and the property has reminder intervals configured (e.g., 48h, 24h, 2h) When the current time reaches each configured interval prior to the visit in the tenant’s local time Then the system queues one reminder per enabled channel (SMS, email) using the corresponding templates And reminders that would occur during configured quiet hours are deferred to the earliest allowed time outside quiet hours on the same calendar day And no duplicate reminders are sent for the same interval and channel And intervals that have already passed at the time of scheduling are skipped And Daylight Saving Time transitions are handled so the reminder occurs at the intended local offset
Secure Tokenized Deep Link to Checklist
Given a reminder is generated for a visit When the message is composed Then it contains a deep link URL with a signed, unguessable token scoped to the tenant and visit And the token allows access to the checklist without login and no PII is present in the URL And the token expires at the earlier of visit end time + 24h or 72h after issuance And access with an invalid or expired token returns a 401/invalid-link page with guidance to request a new link And all token issuance and usage events are logged with timestamp and IP for audit
Escalation Messaging When Required Steps Incomplete
Given required prep steps are defined for the visit and one or more required steps are incomplete When the 24h reminder is sent Then the message uses the “Action Needed” template variant and includes the count of incomplete required steps And when the 2h reminder is due and required steps remain incomplete Then an additional SMS is sent (if SMS opt-in) with urgent copy and the deep link And no more than one escalation message per channel per interval is sent
Auto-Suppression Upon Completion of Required Steps
Given all required steps are marked complete before the next scheduled reminder When the scheduler evaluates pending reminders for that visit Then all future reminders for that visit are canceled across all channels And any queued but unsent messages for that visit are dequeued and not delivered And completion within 10 minutes of a due reminder suppresses that reminder
Per-Property Branding and Template Usage via Existing Messaging Infrastructure
Given a property has branding assets and template overrides configured in FixFlow When reminders are sent for that property Then messages use the property’s branding (logo, sender name) and template variants via the existing messaging service And sender IDs, reply-to addresses, and SMS from-numbers are selected per property configuration And if no overrides exist, system defaults are used And no new third-party provider is invoked outside the existing messaging infrastructure
Opt-Out Management and Compliance
Given a tenant has opted out of SMS or email for the property When reminders are sent Then the opted-out channel is not used for that tenant And SMS supports STOP/UNSTOP/HELP keywords with compliant confirmation messages And emails include an unsubscribe link scoped to reminder communications And opt-out changes take effect within 1 minute and are recorded in the audit log
Delivery Analytics and Link Tracking
Given reminders are sent When delivery receipts and engagement events are received from providers and the application Then each message’s status is stored as queued, sent, delivered, or failed with timestamps, channel, and template version And checklist link clicks are tracked and attributed to the message and tenant And per-property dashboards report send, deliver, fail, and click-through rates for the last 30 days And analytics data is exposed via existing reporting APIs
Vendor Readiness Visibility
"As a vendor technician, I want to see whether the tenant completed critical prep steps so that I can plan accordingly and avoid wasted trips."
Description

Expose tenant checklist completion status to vendors and dispatchers on the shared calendar and work order detail views with clear badges (e.g., Ready, At Risk, Incomplete) and a breakdown of which required steps are pending. Surface alerts for incomplete critical steps prior to departure and enable quick actions to message the tenant or propose a reschedule. Integrate with route planning to avoid wasted trips and reflect readiness in technician day views.

Acceptance Criteria
Calendar and Work Order Badge Visibility
Given a work order has an associated tenant prep checklist When a vendor or dispatcher views the shared calendar (month/week/day) or the work order detail view Then a readiness badge is displayed with one of the labels: Ready, At Risk, Incomplete And the same label is shown consistently across calendar tiles, event popovers, and the work order detail header And tapping/clicking the badge reveals percent of required steps completed and a last-updated timestamp in the user’s local timezone And the badge updates within 5 seconds of any tenant completing or unchecking a required step
Status Derivation and Pending Step Breakdown
Given checklist steps are classified as required or optional and required steps may be flagged as critical When the system computes readiness for a work order Then status is Ready when all required steps are complete And status is At Risk when at least one non-critical required step is incomplete and no critical required steps are incomplete And status is Incomplete when at least one critical required step is incomplete And the work order detail view shows a Pending Steps section listing all incomplete required steps with a Critical indicator where applicable And optional steps do not affect status and are listed separately if incomplete And any tenant update to a step reflects in the Pending Steps section within 5 seconds
Pre-Departure Critical-Step Alert and Acknowledgement
Given at least one critical required step is incomplete for an upcoming appointment When a technician taps Depart, Start Route, or attempts to check in within 30 minutes of the scheduled start time Then the system blocks the action and displays an alert listing the outstanding critical steps And the alert provides actions: Message Tenant and Propose Reschedule, and an Acknowledge and Proceed option only for roles with override permission And selecting Acknowledge and Proceed requires entering a reason and is recorded with user, timestamp, and reason in the work order log And navigation/route start does not initiate until an allowed action is taken
Quick Actions: Message Tenant and Propose Reschedule
Given there are pending required steps When the user selects Message Tenant from the alert or work order detail Then a prefilled SMS draft includes the work order ID, scheduled time, the list of pending steps, and a link to the tenant portal And upon send, the message and delivery status are logged in the work order timeline and visible to dispatchers and assigned vendor And messaging is rate-limited to a maximum of 3 outbound tenant messages per hour per work order When the user selects Propose Reschedule Then the user can select up to 3 time slots within the next 7 days respecting vendor working hours And the tenant receives SMS and email to accept/decline; upon acceptance, the calendar updates automatically, notifications are sent to the vendor and dispatcher, and readiness recalculates And if the tenant declines or no response occurs within 12 hours, the proposal expires and the original appointment remains
Route Planning Integration With Readiness
Given a technician’s daily route is generated with assigned stops When a stop has status Incomplete Then it is excluded from the active route by default and listed in a Needs Attention queue with the reason When a stop has status At Risk Then the optimizer positions it after Ready stops when feasible without violating configured SLAs When a stop’s readiness status changes Then the route recomputes within 60 seconds and any reordering or exclusion is surfaced via an in-app notification to the technician and dispatcher And dispatcher manual overrides are allowed, persist across recomputes until explicitly reset, and overridden stops are marked Manual in the UI
Technician Day View Readiness and Notifications
Given a technician has assigned jobs for the day When viewing the Technician Day view Then each job card shows the readiness badge and a filter control allows filtering by Ready, At Risk, and Incomplete And the view header displays counts of jobs by readiness status When any assigned job transitions from Incomplete or At Risk to Ready Then the technician receives a push notification within 30 seconds and tapping it opens the job detail And if the device is offline, the view shows the last-synced readiness state with an Offline indicator and auto-syncs updates upon reconnection
Role-Based Visibility, Privacy, and Audit
Given a user is authenticated When viewing the shared calendar or a work order Then only assigned vendors/technicians and dispatchers in the relevant portfolio can see readiness badges and pending-step details for that work order; other roles are denied access And only tenant name, unit identifier, and phone number are visible to vendors; internal notes and non-essential PII are hidden And all readiness status changes, alert acknowledgements, messages sent, and reschedule proposals are recorded in an immutable audit log with user, timestamp, and action details And the API and UI return 403 Forbidden for unauthorized attempts to access readiness details outside the user’s portfolio
Critical Step Confirmation Gate
"As a dispatcher, I want to require confirmation of critical prep steps before finalizing a visit so that we reduce on-site surprises and cancellations."
Description

Require explicit tenant confirmation for designated critical steps (e.g., water shutoff access, cleared workspace) before finalizing appointment confirmation. Provide deadline-based gating and warnings, with configurable cutoffs that trigger a soft or hard block. Offer a guided flow to reschedule or request assistance, and allow manager overrides with reason capture. Log all confirmations and overrides for accountability and display status in scheduling and communications.

Acceptance Criteria
Tenant Confirms All Designated Critical Steps
Given an appointment with designated critical steps and a logged-in tenant When the tenant opens the Prep Checklist in the portal Then each critical step displays a checkbox, a "Critical" label, and a micro-guide link And the Confirm Appointment action remains disabled until all critical step checkboxes are checked And each checkbox change is persisted within 1 second with userId and UTC timestamp And once all critical steps are checked, the appointment criticalStatus updates to "Ready"
Configurable Gating Rules and Cutoffs
Given a manager configures gating rules and cutoff hours When a new appointment is created for an issue type or property Then the system resolves gating as: appointment-specific > issue-type > property > global default And supports gating types "Soft" or "Hard" and cutoffHours in whole hours (1–168) And the tenant-facing UI shows the exact deadline timestamp in the tenant's timezone And changes to configuration affect only appointments created after the change unless a manager explicitly reapplies rules to an existing appointment
Soft Block Before Cutoff (Warning and Proceed with Acknowledgment)
Given gating = Soft and cutoffHours = X for the appointment And current time is within X hours of the appointment And criticalStatus != "Ready" When the tenant attempts to confirm the appointment Then show a warning modal with risk summary and a required acknowledgment checkbox And allow confirmation only after the tenant checks acknowledgment and clicks Proceed And record acknowledgment text, userId, and UTC timestamp; set criticalStatus to "Soft-Bypass"
Hard Block After Cutoff with Guided Reschedule/Assistance
Given gating = Hard and cutoffHours = X for the appointment And current time is within X hours of the appointment And criticalStatus != "Ready" When the tenant attempts to confirm the appointment Then block confirmation and present options: Reschedule or Request Assistance And choosing Reschedule opens the scheduler with the current vendor; on save, the original slot is released and a new appointment is created And choosing Request Assistance creates a manager task listing the unconfirmed critical steps and prevents confirmation And all actions are logged with appointmentId, userId, and UTC timestamp
Manager Override Captures Reason and Logs Event
Given an appointment is blocked due to missing critical steps And the current user has Manager role or higher When the manager selects Override Then a reason field (10–250 characters) is required before submission And on submit, the appointment is confirmed and criticalStatus is set to "Override" And the reason, managerId, and UTC timestamp are written to the audit log And tenant and vendor notifications include a "Manager Override" note
Status Visible in Scheduling and Communications
Given any change to criticalStatus for an appointment When the scheduler view or job details are opened by staff Then a status badge displays one of: Ready, Missing, Soft-Bypass, Override with distinct colors and tooltips And SMS/email templates can include the {{critical_status}} token that renders the human-readable status And vendor calendar entries include the status in the description And status updates propagate to all clients and outbound notifications within 5 seconds
Comprehensive Audit Logging
Given any confirmation, acknowledgment, override, reschedule, or assistance request related to critical steps When the action is attempted Then create an immutable audit log entry with: appointmentId, affectedStepIds, actorType, actorId, action, oldStatus, newStatus, reason (if any), timestamp (UTC), and source IP/userAgent And the entry is queryable by appointmentId and exportable to CSV by managers And if log persistence fails, the user action is not committed and a retry-safe error is shown
Embedded Micro-Guides & Media
"As a property manager, I want visual micro-guides embedded in prep steps so that tenants can follow instructions correctly without confusion."
Description

Enable rich, lightweight instructional content within steps, including images, short clips, and annotated diagrams that illustrate actions like locating a shutoff valve or clearing a workspace. Provide an admin content library with reusable assets, per-step attachments, alt text, captions, and localization support. Deliver media via CDN with device-adaptive formats and a low-bandwidth mode. Cache assets for fast loads and verify safe content types.

Acceptance Criteria
Mobile Low-Bandwidth Media Delivery
Given a tenant on a mobile device with a simulated 3G connection (400–700 kbps) and Low Bandwidth Mode enabled or auto-detected When they open a Prep Checklist step containing one image and one short clip (≤30 seconds) Then all media URLs resolve to the CDN domain And the image is delivered as WebP or AVIF at ≤1200px width and ≤300 KB And the video streams as HLS/DASH with a max rendition of 360p at ≤500 kbps And the first contentful media placeholder appears within ≤1.5 seconds And the first video frame renders within ≤2.0 seconds or a static thumbnail with a play icon is shown And media does not auto-play; playback starts only on user action
Admin Content Library Upload and Reuse
Given an admin is in the content library upload flow When they upload an allowed file type (image/jpeg, image/png, image/webp, image/svg+xml, video/mp4, video/webm, video/quicktime) within size limits (images ≤5 MB, videos ≤50 MB) Then the system stores the original and generates device-adaptive renditions (images: 480w, 720w, 1200w; video: 240p, 360p, 480p) And the admin can set metadata: title (required), alt text (required for images), caption (optional), tags (optional), locales (min: en-US, es-ES) And the asset receives a unique ID and can be attached to multiple Prep Checklist steps without duplication And editing the asset’s metadata updates all attached steps within ≤60 seconds
Per-Step Media Attachments and Inline Experience
Given a Prep Checklist step in the admin editor When the admin attaches up to 5 media assets and defines their display order Then the tenant portal renders those assets inline in the specified order with tappable thumbnails And tapping opens a lightbox with pinch/zoom for images and standard controls for video And annotated diagrams render callouts/labels correctly and remain legible at 200% zoom And the step loads with total initial media transfer (excluding video until play) ≤2 MB
Localization and Fallback for Media Metadata
Given a tenant profile locale of es-ES When localized alt text and captions exist for es-ES or the base language es Then Spanish strings are displayed for all attached media And if a localized string is missing, the system falls back to en-US And admins can preview each asset in a chosen locale before publishing And assets support at least en-US and es-ES at launch
Caching, CDN, and Versioned Invalidation
Given a first-time load of a step containing media Then CDN responses include Cache-Control: public, max-age=86400, immutable for hashed asset URLs and ETag/Last-Modified for originals When the tenant revisits the same step within 7 days Then cached media renders in ≤500 ms without re-downloading unchanged assets When an asset is updated in the library Then a new versioned URL is generated and becomes available at the CDN edge globally within ≤5 minutes And the app references only the latest versioned URL after publish
Safe Content-Type Verification and Sanitization
Given an admin attempts to upload a file Then only files with allowed MIME types (image/jpeg, image/png, image/webp, image/svg+xml, video/mp4, video/webm, video/quicktime) are accepted And files must pass anti-malware scanning and SVG sanitization; otherwise the upload is rejected And rejected uploads return a clear error message and are not persisted And served assets include X-Content-Type-Options: nosniff and the correct Content-Type header And requests for quarantined or rejected assets return HTTP 403 and are logged
Accessibility and Media Controls Compliance
Given a tenant using a screen reader and keyboard-only navigation When interacting with a step containing images, videos, and annotated diagrams Then every image has non-empty alt text and diagrams provide accessible descriptions for callouts And videos include captions and a transcript link; media controls are keyboard reachable and ARIA-labeled And no media auto-plays; caption visibility preference persists per user And annotations and controls remain legible in high-contrast mode And the experience meets WCAG 2.1 AA criteria for 1.1.1, 1.2.2, 1.4.3, 2.1.1, and 2.4.3
Completion Analytics & Audit Trail
"As an operations lead, I want analytics and an audit trail of checklist completion so that I can measure impact and optimize our process."
Description

Track and report checklist engagement and completion by issue type, property, vendor, and time window. Correlate completion with first-visit fix rates, return visits, and appointment outcomes to quantify impact. Provide dashboard widgets, filters, and CSV export. Maintain a per-work-order audit log with who completed which step and when, along with any overrides, to support accountability and continuous improvement.

Acceptance Criteria
Dashboard: Prep Checklist Metrics Overview
Given I am a Manager viewing Analytics > Prep Checklist for the default last 30 days When the dashboard loads Then I see widgets for: total checklists issued, completion rate (%), avg steps completed per checklist, completion rate by issue type (top 5 by volume), properties with lowest completion (top 5), vendors with highest completion (top 5) And values reflect the currently selected filters and time window And numeric values match underlying records within ±0.1% and percentages are rounded to 1 decimal place And the default time window is last 30 days ending today at 23:59:59 in the property timezone And the dashboard initial render completes within 2 seconds at p90 for up to 10,000 work orders in range
Filters: Issue Type, Property, Vendor, Date Range
Given multi-select filters for issue type, property, and vendor, and a date range with presets (7, 30, 90 days) and custom range When I apply or clear any filter Then all KPIs, charts, and tables update within 1.5 seconds at p90 And the URL updates to reflect current filter state so it persists on reload and can be shared And clearing filters resets to last 30 days, all issue types, all properties, all vendors And results for any filter combination reconcile with raw data counts within ±0.1% And date boundaries are inclusive and evaluated in the property timezone
Correlation: Completion vs Outcomes
Given I am viewing the Correlation section with active filters When data is available for the selected period Then show for completed vs not completed cohorts: first-visit fix rate, return visit rate, and no-access/tenant-not-ready rate, each with counts and delta (percentage points) And hovering a metric reveals its calculation (numerator/denominator) And counts and rates reconcile with the filtered dataset within ±0.1% And if a cohort has fewer than 30 work orders, display “insufficient data” instead of a rate
Export: Filtered Analytics CSV
Given filters are applied on the analytics view When I click Export CSV Then a UTF-8 CSV downloads with a header row and columns: work_order_id, property_name, unit, issue_type, vendor_name, appointment_start_utc, appointment_timezone, checklist_steps_total, checklist_steps_completed, completion_percent, completion_timestamp_utc, first_visit_fix, return_visits_count, appointment_outcome, override_flag, override_by_user_id, override_timestamp_utc, created_at_utc, updated_at_utc And the CSV respects current filters and sorting And tenant PII (names, emails, phone numbers) is excluded And timestamps are ISO 8601 in UTC with a separate timezone column And for up to 50,000 rows the file is delivered within 15 seconds at p90; larger exports are blocked with guidance to narrow filters And the CSV row count equals the total rows shown in the UI table for the same filters
Audit Log: Per-Work-Order Step Completion Trail
Given a work order with a checklist When I open its Audit Log tab Then entries appear in reverse chronological order with: event_type (step_completed, step_uncompleted, override_applied, checklist_issued, checklist_viewed), step_id/label (if applicable), actor_type (tenant, vendor, manager, system), actor_id, timestamp (UTC and property timezone), source (portal/SMS/API), and metadata And the log is append-only and immutable; edit/delete attempts are blocked and recorded as security events And overrides require a reason and record actor_id and timestamp And at least the last 500 events are visible with pagination or infinite scroll to load older events And timestamps are synchronized to server time within ±2 seconds
Data Freshness and Consistency
Given a checklist step is completed or an override is applied When the action is saved Then the analytics KPIs and correlation widgets reflect the change within 60 seconds at p95 And a manual page refresh never shows stale values for those records And analytics metrics are computed from the same event store as the audit log; random spot checks of 10 work orders show matching completion percentages And failures in downstream aggregation do not affect the audit log; a visible banner indicates degraded analytics with automatic retry within 5 minutes
Access Control and Privacy
Given role-based permissions are enforced When an Owner or Manager accesses Analytics Then they see metrics only for properties they have access to And a Vendor sees analytics limited to their assigned work orders; filters and data for other properties/vendors are hidden And a Tenant cannot access analytics or audit logs; direct navigation returns 403 And audit logs and CSV exclude tenant PII (names, emails, phone numbers), exposing only tenant_id where needed

Live Map ETA

A privacy‑safe live map with traffic‑adjusted ETA and status updates (en route, parked, at door). Tenants can signal “running late” or request a short hold, reducing missed connections and anxiety about arrival timing.

Requirements

Privacy-Safe Real-Time Location Sharing
"As a vendor, I want to share my live approach to the job without revealing my exact location outside the property area so that tenants can see I’m on the way while my privacy is protected."
Description

Implements opt-in, time-bound location sharing for assigned vendors during an active work order, available via mobile web (PWA) or native app. Location samples every 10–30 seconds with on-device throttling, snapped-to-road rendering, and coarse-grain fuzzing (e.g., 100–200m) until inside the property geofence to protect privacy. Uses one-time, signed tokens scoped to a specific work order; sharing auto-starts within the appointment window or on vendor tap and auto-stops on job completion or manual stop. No background tracking outside job windows. Data is retained for 24–48 hours for audit and dispute resolution, then purged. Supports low-battery mode, unreliable GPS conditions, and offline buffering with queued updates. Exposes a secure, read-only stream for the ETA engine and tenant UI; never exposes raw coordinates to tenants outside the geofence.

Acceptance Criteria
Opt-In and Time-Bound Start/Stop
- Given a vendor is assigned to a work order with an appointment window, When the current time enters the window or the vendor taps "Start Sharing", Then location sharing begins within 5 seconds and a visible in-app sharing indicator appears. - Given sharing is active, When the vendor taps "Stop Sharing" or the job is marked complete, Then location streaming stops within 5 seconds and the token is revoked. - Given it is outside any appointment window and sharing has not been manually started, Then no location samples are collected or transmitted and no background location service is active.
Scoped One-Time Token Security
- Given sharing starts, When the system issues an access token, Then the token is signed, single-use, scoped to the specific work order and vendor, and set to expire at appointment end plus a 30-minute grace period. - Given an attempt to use the token for a different work order, after revocation, or after expiry, Then the request is rejected with 401/403 and returns no location data. - Given a tenant UI request, Then only the tenant-safe stream endpoint is accessible; the raw-coordinates endpoint is inaccessible to tenant roles.
Privacy Fuzzing and Road-Snapped Rendering
- Given sharing is active and the vendor is outside the property geofence, Then the tenant UI receives positions fuzzed to 100–200 m and road-snapped; raw coordinates are never included in tenant payloads. - Given the vendor crosses into the property geofence, Then the tenant UI switches to high-precision positions (<20 m) and emits status "At Door" within 10 seconds. - Given the vendor remains outside the geofence for >60 seconds, Then the fuzzed offset jitter randomizes at least once per minute to prevent path reconstruction.
Sampling Rate, Throttling, and Low-Battery Mode
- Given normal conditions (battery ≥20% and GPS available), Then the device samples location every 10–30 seconds with adaptive throttling and never more frequently than once per 5 seconds. - Given low-battery mode (battery <15%), Then the sampling interval increases to 30–60 seconds and high-power sensor use is reduced. - Given the app is backgrounded during an active sharing session, Then samples continue at the configured intervals subject to OS limits and are queued if delivery is constrained.
Offline Buffering and Queued Updates
- Given network connectivity is unavailable during an active sharing session, Then the device buffers at least 15 minutes of timestamped location samples encrypted at rest. - When connectivity is restored, Then buffered samples are uploaded in order within 60 seconds and the server deduplicates and applies monotonic timestamps. - Given the tenant UI requests live data while offline buffering is active, Then it displays the last known timestamped position and a "Signal Lost" banner within 10 seconds of detecting connectivity loss.
Retention and Purge Policy
- Given location records are stored for audit, Then the system retains them for a configurable window between 24 and 48 hours and purges them automatically beyond the configured maximum. - When a purge occurs, Then subsequent queries for the affected work order return zero records and a purge event is logged with timestamp and actor "system". - Given an authorized admin requests manual purge for a work order, Then records are removed within 60 seconds and are not recoverable via standard APIs.
No Tracking Outside Job Windows
- Given there is no active sharing session and the current time is outside all appointment windows, Then the app does not request device location in the background and no location events are transmitted. - Given a vendor has granted "Always" location permission, Then outside job windows the app refrains from polling location and energy impact remains below 1% per hour as measured by OS diagnostics. - Given push messages or silent notifications are received outside job windows, Then they do not trigger location capture or transmission.
Traffic-Adjusted ETA Engine
"As a tenant, I want ETAs that reflect real traffic conditions so that I can plan my time and avoid waiting unnecessarily."
Description

Provides continuously updated ETA calculations using live traffic and historical speed profiles. Recomputes routes on location deltas, traffic incidents, or threshold changes (e.g., >2 minutes ETA shift), with provider fallback (e.g., Mapbox/Google/OpenRoute) and rate limiting. Supports batched ETAs, time zones, and unit display preferences. Publishes ETA updates via an event bus to the tenant UI, calendar, and notifications. Handles reroutes, road closures, and vendor stops with robust error handling and exponential backoff. Includes health metrics (update frequency, provider latency, ETA accuracy) and configurable SLAs (e.g., updates within 10 seconds of position change).

Acceptance Criteria
ETA Recalculation on Position Change
Given a vendor device location update is received with delta ≥ configurable delta_threshold_m (default 50 m) And live traffic data is available When the engine processes the update Then it recalculates the route and ETA using live traffic and historical speed profiles And publishes an ETA_UPDATED event containing job_id, vendor_id, old_eta_iso, new_eta_iso, source="position_change", calc_provider, calc_latency_ms, route_version And completes recalculation and event publication within 10 seconds of receipt of the location update for ≥95% of events (p99 ≤ 15 seconds)
ETA Threshold Change Broadcast
Given continuous ETA tracking is active for a job And a last_published_eta exists When the newly computed ETA differs from the last_published_eta by ≥ 2 minutes Then an ETA_UPDATED event is published within 5 seconds with source="eta_threshold_change" and includes delta_seconds And duplicate publications are suppressed for the same job within a 10-second debounce window unless the absolute delta increases by ≥ 5 minutes
Provider Fallback with Exponential Backoff
Given the primary routing provider returns a 5xx, 429, or exceeds provider_timeout_ms When computing an ETA for a job Then the engine retries with exponential backoff (initial 500 ms, factor 2.0, jitter, max_delay 8 s, max_attempts 3) And upon persistent failure it fails over to the next configured provider within 2 seconds and completes the ETA computation And the emitted event includes provider_failover=true and records primary_error_code and failover_provider And no single job issues more than 3 retry bursts within a 1-minute window (retry budget)
Rate Limiting Compliance for External Providers
Given provider rate limits are configured (e.g., qps_limit, daily_quota) When the system receives bursty load resulting in simultaneous single and batched ETA requests at 120% of expected peak Then outbound calls per provider never exceed qps_limit in any rolling 1-second window And 429 responses attributable to client overuse are 0 during a 10-minute load test And requests over capacity are queued or served from cached results with max staleness ≤ 60 seconds And a RATE_LIMIT_THROTTLE metric is emitted with count ≥ number of throttled requests
Batched ETAs with Time Zones and Unit Preferences
Given a batch request of 50 jobs spanning multiple time zones and mixed display preferences (12/24h, mi/km) When the engine computes batched ETAs Then p95 batch completion time ≤ 2 seconds and p99 ≤ 3 seconds And each result includes job_id, eta_iso in the tenant’s local time zone, distance and duration formatted per the tenant/vendor unit preferences, and correlation_id matching the request And results preserve input ordering or include a stable sort_key to reconstruct original order
Reroute Handling for Incidents and Closures
Given the current route becomes invalid due to a provider-reported closure/incident or the vendor is detected off-route by >100 m for >30 seconds When the condition is detected Then the engine computes an alternative route avoiding the affected segment And publishes an ETA_UPDATED event with source="reroute", reason_code (closure|incident|off_route), new route_version, and new_eta_iso And the reroute and publication occur within 10 seconds of detection for ≥95% of cases
Event Bus Publishing and Health/SLA Monitoring
Given any ETA update is produced by the engine When the event is emitted to the bus Then it conforms to schema versioned as eta.v1 with required fields {event_id, occurred_at, job_id, vendor_id, source, old_eta_iso, new_eta_iso, calc_provider, route_version} And the event is delivered to UI, calendar, and notifications topics with end-to-end p95 latency ≤ 2 seconds and p99 ≤ 5 seconds And the engine emits metrics: update_frequency_per_job, provider_latency_ms, and eta_abs_error_sec; SLA violations for position_change_update_sla_sec=10 trigger alert ETA_SLA_BREACH when >1% over a 15-minute window And rolling 7-day eta_abs_error_sec meets targets (median ≤ 5 minutes, p90 ≤ 10 minutes)
Tenant Live Map View and Status Feed
"As a tenant, I want a live map and clear status updates so that I know exactly when to be ready without constantly messaging for updates."
Description

Delivers an embeddable web widget in the Tenant Portal with SMS deep-link support, showing a live map, route polyline, traffic-adjusted ETA, arrival window, and real-time status (en route, parked, at door). Includes a low-bandwidth mode with simplified updates, accessibility compliance (keyboard, screen reader, high-contrast), and localization for key markets. Obscures vendor exact origin and identity details; reveals precise position only within property geofences. Provides a lightweight activity feed (last update time, ETA changes, vendor accepted hold) and a contact-safe escalation link to FixFlow support if the vendor is stalled.

Acceptance Criteria
Embeddable Widget: Live Map, Route, ETA, Arrival Window
Given an authenticated tenant with an active visit scheduled within the next 24 hours When the Tenant Live Map widget is embedded in the portal and loaded on a 4G connection Then a loading skeleton renders within 1 second and the map with route polyline renders within 3 seconds Given the backend route service returns an ETA and arrival window When the widget displays timing Then the ETA (in minutes) and the arrival window (start–end) match the backend values within ±1 minute Given the vendor position changes by >= 50 meters or the ETA delta is >= 2 minutes When an update is received Then the marker, polyline, and ETA refresh within 3 seconds Given the widget is embedded via iframe When rendered at widths 320–1200px and heights >= 360px Then no scrollbars appear and layout remains responsive
Privacy Safeguards: Vendor Origin and Identity Obfuscation
Given the vendor is outside the property geofence When the position is shown Then the marker position is fuzzed to a random point within a 500 m radius and no street address is displayed Given the vendor is outside the property geofence When inspecting the network payloads Then no origin coordinates, home/base address, or vendor PII (name, phone, photo, company) are transmitted to the client Given the vendor enters the property geofence When the position updates Then precise position (<= 15 m accuracy) is shown and the door/entrance pin is visible Given the tenant opens the vendor detail panel When displayed Then only generic role labels (e.g., "Your technician") are shown with no direct contact info
SMS Deep Link to Active Visit
Given a tenant receives an SMS containing a FixFlow deep link with a signed one-time token When the link is tapped within 15 minutes Then the widget opens the correct visit context in the default browser within 3 seconds without requiring login Given the deep link token is expired or replayed When the link is tapped Then the tenant is prompted to authenticate and the link is invalidated Given multiple upcoming visits exist When the deep link is used Then the visit loaded matches the visitId in the token and others are not accessible from the link Given the device has an unsupported browser or blocks third-party cookies When the deep link is opened Then a low-bandwidth view loads with the same ETA/status information
Status Feed and Stall Escalation
Given an active visit with live tracking When the vendor publishes state transitions Then the widget displays one of: en_route, parked, at_door, with timestamps in tenant timezone Given ETA changes by >= 2 minutes When the update is received Then an activity feed entry is appended with the previous ETA, new ETA, and change magnitude Given a tenant requests a short hold and the vendor accepts When the acceptance is received Then the feed shows "Hold accepted" with the new arrival window Given no position or status update is received for > 10 minutes during en_route or the ETA exceeds the arrival window by > 15 minutes When the condition is detected Then a "Need help?" escalation link appears Given the tenant taps the escalation link When the form is submitted Then a FixFlow support ticket is created containing visitId, last update timestamps, and telemetry, without exposing vendor contact details, and the link is rate-limited to 1 submission per 30 minutes
Low-Bandwidth Mode Fallback
Given network throughput is < 150 kbps or RTT > 800 ms, or the user enables Low Data Mode When the widget loads or detects degraded network Then the widget switches to low-bandwidth mode, hides map tiles, and shows text ETA, distance, and a directional indicator Given low-bandwidth mode is active When updates stream Then total network usage stays <= 25 KB per minute averaged over 5 minutes Given the user toggles low-bandwidth mode When the setting is changed Then the preference persists for the session and is reflected in telemetry as low_bandwidth=true Given low-bandwidth mode is active When status updates occur Then all critical actions (running late, request hold, escalation) remain available and functional
Accessibility: Keyboard, Screen Reader, High-Contrast
Given a keyboard-only user When navigating the widget Then all interactive elements are reachable in logical order, have visible focus, and are operable with Enter/Space; arrow keys pan the map only when the map canvas has focus Given a screen reader user When the widget loads and updates Then key information (ETA, arrival window, current status, last updated time) is announced via aria-live="polite" regions with meaningful labels Given the UI is rendered When evaluated for contrast Then text and interactive elements meet WCAG 2.1 AA contrast ratio (>= 4.5:1) and a high-contrast theme toggle is available Given the user prefers reduced motion When animations would play Then motion is reduced or disabled in accordance with prefers-reduced-motion
Localization: Language, Units, Timezone
Given tenant locale is set to en-US, es-ES, fr-CA, or pt-BR (or derived from Accept-Language) When the widget renders Then all visible strings are localized and fall back to English only if a translation key is missing Given a localized locale When times are displayed Then arrival window and timestamps are shown in the tenant’s timezone with correct daylight saving adjustments Given a localized locale When distances are displayed Then units use locale conventions (miles for en-US; kilometers for es-ES, fr-CA, pt-BR) Given the tenant changes language in profile When the widget reloads Then the selected language is applied without layout breakage or text truncation
Arrival State Detection and Automation
"As a property manager, I want standardized arrival states to trigger the right notifications and logs so that communications are consistent and performance is measurable."
Description

Determines and transitions visit states using geofences and motion signals: En Route (default when tracking starts), Arrived at Property (enter property geofence), Parked (speed ~0 within curb geofence for N seconds), and At Door (vendor one-tap or micro-geofence near entry). Triggers downstream automations: notify tenant, update calendar, and start on-site timer. Includes guardrails against false positives (GPS drift filters, hysteresis, manual override) and auditability (who/what changed state and when). Exposes configuration per property type (multi-unit vs. single-family) and supports unit-level geofences when available.

Acceptance Criteria
Transition: En Route to Arrived at Property
Given tracking is active and the current state is En Route And the device enters the property geofence polygon with horizontal_accuracy <= 30m And the device remains inside the polygon for >= 15 consecutive seconds And average speed during the prior 2 minutes was >= 3 m/s When the dwell threshold is met Then the state transitions to Arrived at Property within 5 seconds And an audit entry is recorded with source=geofence, old_state, new_state, timestamp, and sample_ids And no transition occurs if the device exits the polygon before 15 seconds of continuous dwell
Transition: Arrived at Property to Parked
Given the current state is Arrived at Property And a curb geofence exists (circle radius default 25m) And instantaneous speed <= 0.5 m/s for >= 20 consecutive seconds while inside the curb geofence When these conditions are satisfied Then the state transitions to Parked within 5 seconds And samples outside the curb geofence do not trigger Parked even if speed is 0 And an audit entry is recorded with source=speed+geofence and the transition details
Transition: Parked to At Door
Given the current state is Parked And either (A) the vendor taps "At Door" in the app or (B) the device enters the unit micro-geofence (radius default 8m) with accuracy <= 10m for >= 5 consecutive seconds When the first qualifying condition occurs Then the state transitions to At Door within 3 seconds And the on-site timer starts at the transition timestamp And the tenant receives an "At door" SMS within 10 seconds And an audit entry records source=manual or source=micro-geofence accordingly And if both A and B occur, only one transition is recorded
Guardrails: GPS Drift Filters and Hysteresis
Given the visit is active and the current state is one of [En Route, Arrived at Property, Parked, At Door] When location samples have horizontal_accuracy > 50m or show speed spikes > 8 m/s lasting < 5 seconds Then those samples are excluded from state evaluation And exit hysteresis applies: device must be outside the property geofence for >= 20 consecutive seconds before demoting to En Route And oscillation is prevented: at most 1 automatic state transition per 60 seconds per visit And if GPS is unavailable for > 120 seconds, the last state is retained and an audit note "location_unavailable" is added
Manual Override and Audit Log
Given a user with role in [Vendor, Dispatcher, PropertyManager] has permission to edit visit state When the user manually sets the state to one of [En Route, Arrived at Property, Parked, At Door] Then the new state is applied within 3 seconds And downstream automations fire as for automatic transitions, with notifications labeled "manual" And an immutable audit record is saved with user_id, role, reason (required), old_state, new_state, source=manual, and timestamp And the visit audit history displays all state changes in chronological order
Property Type Configuration and Unit-Level Geofences
Given a property configured as Multi-Unit with unit micro-geofences available When a visit targets a specific unit Then Arrived at Property uses the property geofence and At Door uses the unit micro-geofence And thresholds are configurable per property: arrived_dwell_seconds default 15 (range 5–60), parked_speed_threshold 0.5 m/s, parked_dwell_seconds default 20 (range 10–120), micro_geofence_radius default 8m (range 5–20) And for Single-Family properties without a micro-geofence, At Door may be satisfied by vendor one-tap And configuration changes are versioned in audit logs and apply to visits created after the change
Automations: Tenant Notifications, Calendar Update, On-Site Timer
Given a scheduled visit with a linked tenant phone and calendar event When the state transitions to Arrived at Property Then the tenant receives an SMS "Vendor has arrived" within 10 seconds and the calendar event status updates to On Site When the state transitions to Parked Then the tenant receives an SMS "Vendor is parked" within 10 seconds and the Live Map shows status=Parked When the state transitions to At Door Then the tenant receives an SMS "Vendor is at your door" within 10 seconds and the on-site timer starts with accuracy ±1 second And duplicate notifications are de-duplicated so no more than one SMS per state per visit is sent
Tenant Delay and Short-Hold Requests
"As a tenant, I want to request a brief delay or hold so that the vendor doesn’t miss me and the visit can still proceed smoothly."
Description

Enables tenants to signal “Running late” with preset options (5/10/15 minutes) or request a short hold (up to 10 minutes) directly from the live map. Sends actionable prompts to the vendor (accept/decline/counter) with timers and auto-expiry. Applies business rules (max holds per job, cutoff window near end of appointment, property manager overrides) and updates ETA/calendar upon acceptance. All interactions are logged to the work order timeline, with clear tenant/vendor visibility and safeguards to prevent indefinite holds or silent no-shows.

Acceptance Criteria
Running Late Preset With Vendor Response and Auto‑Expiry
Given RunningLateOptions = [5, 10, 15] minutes and VendorResponseTimeout = 3 minutes and TenantResponseTimeout = 3 minutes and CounterRange = 5–30 minutes in 5-minute steps, When a tenant taps "Running late" and selects 10 minutes from the live map, Then a vendor prompt with Accept/Decline/Counter(5–30, step 5) is dispatched within 10 seconds via in-app and SMS, and a 3-minute response timer starts. When the vendor accepts within the timer, Then the job status changes to "Running late accepted", the ETA is recalculated as ETA + 10 minutes with traffic, and both parties see the updated arrival window within 10 seconds. When the vendor declines within the timer, Then the ETA remains unchanged, the tenant is notified within 10 seconds, and the request is logged as declined. When the vendor counters to 15 minutes within the allowed range, Then the tenant receives the counter within 10 seconds and must accept or decline within TenantResponseTimeout; acceptance updates ETA + 15 minutes, decline leaves ETA unchanged, and lack of response auto-expires to no-change. When the vendor does not respond within VendorResponseTimeout, Then the request auto-expires, both parties are notified, the vendor prompt is withdrawn, and the expiry is logged.
Short Hold Request With Cap and Cutoff Enforcement
Given ShortHoldMaxPerJob = 2 and ShortHoldMaxMinutes = 10 and AppointmentEndCutoffMinutes = 15 and AllowedVendorStatuses ∈ {"en_route","parked","at_door"}, When a tenant requests a short hold for 8 minutes while the vendor status is "at_door", Then the vendor receives an Accept/Decline/Counter(1–10 minutes) prompt with a running timer VendorResponseTimeout, and the tenant sees "Hold requested" with a countdown. When the tenant has already used ShortHoldMaxPerJob holds on the job, Then the hold button is disabled and a message "Hold limit reached" is shown with an option to notify the property manager. When the current time is within AppointmentEndCutoffMinutes of the scheduled appointment end, Then new hold requests are blocked with a reason "Within cutoff window" and a link to request PM override. When the vendor accepts within the timer, Then the appointment pause is applied for the selected minutes (capped at ShortHoldMaxMinutes), the ETA and calendar are updated accordingly, and both parties see "Hold accepted" with an end time. When the vendor counters, Then the tenant must accept/decline within TenantResponseTimeout; acceptance applies the counter minutes, decline keeps the prior ETA; no response auto-expires to no-change.
Calendar/ETA Update on Acceptance Without Double‑Booking
Given a requested delay or hold of D minutes and the vendor’s next appointment starts at Tnext and BufferMinutes = 10, When the vendor attempts to accept, Then the system computes MaxAcceptableDelay = max D such that current appointment end + D + BufferMinutes <= Tnext. When D <= MaxAcceptableDelay, Then acceptance is allowed, the vendor’s calendar is updated to extend by D minutes with no overlap, and the traffic-adjusted ETA is recalculated and shown to both parties within 10 seconds. When D > MaxAcceptableDelay, Then acceptance is blocked, the vendor sees "Conflict with next appointment" and is offered Counter = MaxAcceptableDelay (if > 0) or Decline only, and no calendar changes are made.
Property Manager Override for Blocked or Expired Requests
Given a hold/delay request is blocked due to limit or cutoff, and a Property Manager is assigned to the work order, and PMOverrideMaxMinutes = 15, When the PM clicks "Override" and selects M minutes (M <= PMOverrideMaxMinutes), Then the system bypasses the block, applies the selected minutes subject to non-overlap rules, updates ETA/calendar, and notifies both tenant and vendor that an override was applied. When the override would create a calendar conflict, Then the system displays the conflict and requires PM confirmation to push back subsequent appointments; on confirm, affected appointments are shifted and notified; on cancel, no changes occur. When an earlier request auto-expires, Then the PM can reopen it within 10 minutes and apply an override; after 10 minutes, reopen is disabled.
Audit Log and Cross‑Party Visibility
Given any request, response, counter, acceptance, decline, or auto-expiry event, When the event occurs, Then a timeline entry is created within 2 seconds capturing timestamp (UTC), actor (tenant/vendor/PM/system), action, previous and new ETA, requested minutes, response outcome, and delivery channels, and it is visible on the work order to tenant, vendor, and PM with role-appropriate labels. When notifications are delivered, Then delivery receipts (in-app read, SMS sent/delivered) are recorded and linked to the timeline entries. When a timeline entry is generated, Then it is immutable to tenant and vendor, and editable by PM only for adding a note (not altering facts), with edits tracked as a new timeline entry.
Safeguards Against Indefinite Holds and No‑Shows
Given CumulativeHoldMaxMinutes = 15 and NoShowVendorWaitMaxMinutes = 15 and EscalationAfterExpiries = 2, When cumulative accepted short holds on a job would exceed CumulativeHoldMaxMinutes, Then new hold requests are blocked or auto-capped to the remaining minutes, with reason displayed and PM override option. When a vendor is "at_door" and two successive tenant requests expire without tenant confirmation, Then the system escalates by notifying the PM and presenting the vendor with "Mark no-show" after NoShowVendorWaitMaxMinutes total waiting. When the vendor marks "no-show" or the wait timer elapses, Then the job status updates to "Tenant no-show", the work order timeline records evidence, and downstream workflows (e.g., reschedule prompts, fee policies if configured) are triggered.
Notification Delivery and Fallbacks
Given both tenant and vendor have verified phone numbers and in-app accounts, When any request or response is issued, Then in-app notifications are sent immediately and SMS fallbacks are sent if the recipient is offline or push fails, with a single consolidated message per event. When SMS delivery fails after 2 attempts within 2 minutes, Then the system surfaces "Delivery failed" on the timeline and reattempts in-app delivery on next login; PM is notified for critical events (requests awaiting response). When the recipient opens the in-app notification, Then the pending timer and actionable buttons are displayed with the remaining time synchronized to server time (drift <= 2 seconds).
Calendar Sync and SMS Notifications for ETA Changes
"As a property manager, I want ETA and arrival changes to automatically update the calendar and notify tenants so that schedules stay accurate and no-shows are reduced."
Description

Bi-directionally integrates ETA/status updates with the FixFlow shared calendar to prevent double-bookings and keep time blocks accurate. Sends proactive SMS messages at key milestones (tracking started, significant ETA shift, parked, at door) with customizable templates and localized content. Includes STOP/HELP compliance, quiet hours, throttle rules, link-shortening to the live map, and tenant identity verification before revealing job context. Provides delivery/engagement metrics and fallbacks if SMS fails (email/push where available).

Acceptance Criteria
Bi‑Directional ETA–Calendar Sync and Double‑Booking Prevention
Given a scheduled job with an assigned vendor and live tracking enabled When the vendor’s ETA changes by 3+ minutes Then the corresponding calendar event start/end are updated within 30 seconds And the vendor’s availability grid reflects the new time block And no two confirmed events overlap for the same vendor; if an overlap would occur, the system marks a conflict, prevents new auto-assignment in the overlapping window, and notifies the manager Given a manager edits the event time in the shared calendar by 2+ minutes When the change is saved Then the vendor-facing ETA baseline and subsequent notifications use the updated window within 30 seconds
Tracking Started SMS with Localized, Customizable Template and Short Link
Given a tenant has a valid E.164 phone number and a language/locale preference When tracking starts for the vendor Then send an SMS within 15 seconds using the active template for that locale And include a branded, unique short link to the live map And include required compliance copy (e.g., "Reply STOP to opt out. HELP for help.") And log provider message ID, timestamps, and locale used
Significant ETA Shift Notifications with Throttle and Quiet Hours
Given throttle rules of max 1 shift SMS per 10 minutes and max 3 per job, and significant shift defined as max(5 minutes, 15% of remaining travel time) When the ETA changes by a significant amount Then send a shift SMS within 20 seconds subject to throttle limits And coalesce multiple ETA changes within the throttle window into one message using the latest ETA And do not send during configured quiet hours (default 21:00–08:00 tenant local); queue until quiet hours end unless ETA < 30 minutes And record whether the message was sent immediately, queued, or skipped with reason
Status Change SMS: Parked and At Door
Given FixFlow statuses include parked and at_door When the vendor updates status to parked Then send an SMS within 15 seconds indicating arrival and estimated walk-up time When the vendor updates status to at_door Then send an SMS within 10 seconds indicating arrival at the door And if tenant identity is unverified, omit vendor name/unit details and include a verification link And update the shared calendar event status accordingly
Tenant Identity Verification Before Revealing Job Context
Given a tenant opens the live map link When the tenant is not verified Then require one-time code to the same phone or last-name + unit match before revealing job context And rate-limit to 3 attempts per 15 minutes; lock for 15 minutes after limit exceeded And upon successful verification, reveal vendor name, unit, and appointment details; otherwise keep redacted And bind the session to the verified number; tokens expire at job completion or 2 hours after at_door, whichever is earlier
Delivery and Engagement Metrics with Fallback Channels on SMS Failure
Given an outbound SMS is attempted When delivery status returns failed or undelivered Then trigger fallback within 60 seconds via push (if opted-in) or email (if available) using the matched template and link And record SMS provider status code, error, and attempt count And track per-message delivery events, link clicks, and replies (STOP/HELP/START) And display per-job metrics (sent, delivered, failed, clicked) in the dashboard with CSV export
STOP/HELP Compliance and Opt‑Out/Opt‑In Handling
Given the tenant replies STOP or an equivalent opt-out keyword Then immediately confirm opt-out and cease further SMS to that number across all jobs, except responding to HELP or confirming START And add the number to a suppression list enforced for all future sends When the tenant replies HELP Then send a localized HELP response with support contact details and data rates disclaimer When the tenant replies START/UNSTOP Then re-enable SMS to that number and confirm opt-in And persist consent state with timestamp and source for audit

Photo Verify

After completion, tenants see before/after photos and the completed scope in one place. Approve in one tap or flag what’s wrong with guided reasons—triggering a quick follow‑up without creating a brand‑new ticket, building trust through transparency.

Requirements

Before/After Photo Capture & Pairing
"As a vendor technician, I want to upload and pair before/after photos to each completed task so that tenants can clearly see what changed and the landlord has verifiable proof of work."
Description

Enable vendors to upload multiple images at job completion via mobile web or dashboard, with support for camera capture and file upload. Automatically tag images as “before” or “after” using timestamps and task states, with a simple manual pairing UI to correct matches. Enforce per-work-order association, size/type validation, image compression, orientation correction, and metadata scrubbing. Store originals securely with generated thumbnails and web-optimized variants, using signed URLs. Provide upload progress, retries, and error handling to ensure reliable submissions.

Acceptance Criteria
Multi-Image Upload via Mobile and Web
Given a vendor is authenticated and on Work Order X’s completion page on mobile web or desktop dashboard When they select Add Photos and choose either Take Photo (camera capture) or Choose Files (file picker) and select multiple images Then the system accepts at least 10 images in a single batch without page reload And camera capture is available on compatible mobile devices And all successfully uploaded images appear in the Work Order X gallery with immediate visual confirmation (thumbnail and count).
File Type and Size Validation
Given the photo upload dialog is open for Work Order X When the vendor selects files Then only files of type JPEG/JPG, PNG, or HEIC (MIME: image/jpeg, image/png, image/heic) are accepted And any file larger than 25 MB or with an unsupported type is rejected before upload And each rejected file displays an inline error message stating the specific reason (e.g., “File exceeds 25 MB limit” or “Unsupported file type”).
Automatic Before/After Tagging
Given Work Order X has timestamps for In Progress (T_start) and Completed (T_done) And each uploaded image has capture_time from EXIF if present, otherwise upload_time is used When images are uploaded to Work Order X Then any image with time < T_start is tagged "before" And any image with time >= T_start is tagged "after" And the default tag shown in the completion flow UI reflects this mapping for each image.
Manual Pairing & Tag Correction UI
Given images for Work Order X have initial before/after tags When the vendor opens the Pair Photos UI Then they can drag a "before" onto an "after" to create a pair, unpair an existing pair, and re-pair as needed And they can change a photo’s tag between "before" and "after" And the UI supports keyboard navigation with visible focus and labels (WCAG 2.1 AA) And saving persists all changes and reload shows the same pairs and tags And at least 20 pairs can be created and saved without performance degradation.
Image Processing, Compression, Orientation, and Metadata Scrubbing
Given an image is successfully uploaded for Work Order X When server-side processing completes Then the original image bytes are stored securely And a web-optimized variant is generated with the longest edge ≤ 1920 px and file size ≤ 1.5 MB And a thumbnail is generated with the longest edge ≤ 320 px and file size ≤ 50 KB And the displayed images are corrected to upright orientation regardless of device capture orientation And all EXIF/IPTC/XMP metadata (including GPS, device, and author) is stripped from all stored variants And 95% of images complete processing (variants + orientation + scrubbing) within 5 seconds of upload.
Per-Work-Order Association and Secure Access via Signed URLs
Given images are uploaded under Work Order X When any user attempts to reference those images Then each image is associated to exactly one work order and cannot be attached to another And direct access to storage locations without a valid signed URL returns 403 And generated signed URLs are read-only, scoped to the specific image variant, and expire within 15 minutes And an expired or tampered signed URL cannot be used to fetch the image.
Reliable Uploads: Progress, Retries, Resumable, and Errors
Given the vendor is uploading images for Work Order X When uploads commence Then each file shows an individual progress indicator from 0% to 100% And transient failures (e.g., network timeouts, 5xx) are automatically retried up to 3 times with exponential backoff And uploads for files > 5 MB are resumable so that reconnecting continues from the last completed chunk And going offline pauses uploads and automatically resumes when online And failed files are clearly marked with an error message and a Retry action And retries do not create duplicate images in the work order gallery (idempotent behavior).
Completion Scope Summary
"As a tenant, I want to see a clear checklist of what was completed alongside photos so that I can quickly understand the work and decide whether to approve."
Description

Display a consolidated, read-only summary of the completed scope alongside photos: tasks completed, notes, parts used, timestamps, and any deviations from the original request. Pull data from the existing FixFlow work order schema and present it as a clear checklist aligned to each task’s photos. Ensure consistency across devices, include localized date/time, and support long-form vendor notes with truncation/expand behavior.

Acceptance Criteria
Mobile completion summary view
Given a completed work order with tasks, notes, parts, timestamps, and deviations exists in the FixFlow schema When the tenant opens the Photo Verify screen on a mobile device Then a consolidated, read-only completion summary is displayed adjacent to the photos And the summary lists all completed tasks, vendor notes, parts used (name, quantity, unit), and start/end/completion timestamps And any deviations from the original request are shown with reason and status And no editable controls (inputs, edit icons, save buttons) are present And all displayed values exactly match the underlying work order data (field-for-field) except for date/time formatting
Checklist aligned with task photos
Given each task may have before and after photos associated in the work order When the completion summary renders Then each task appears as a checklist item aligned with its photo thumbnails And before and after photos are labeled and ordered chronologically And if multiple photos exist, a count badge is shown and tapping/clicking opens a gallery viewer And if a task has no photos, a "No photos provided" placeholder is displayed without breaking the checklist And opening a photo displays a full-screen/lightbox viewer with swipe/arrow navigation and a close control
Localized date/time display
Given the tenant has a stored locale and timezone in their profile When timestamps (started, completed, vendor arrival) are displayed in the summary Then each timestamp is formatted using the tenant’s locale conventions and profile timezone And 12h vs 24h format follows locale rules And if profile timezone is unavailable, the device timezone is used as a fallback And each timestamp includes a timezone abbreviation or UTC offset And relative time formats (e.g., "2 hours ago") are not used
Vendor notes truncation and expand
Given vendor notes may exceed typical on-screen length When the completion summary loads Then notes longer than 3 lines or 240 characters are truncated with an ellipsis and a "See more" control And activating "See more" expands to show the full note text And a "See less" control collapses the note back to the truncated view And the expanded/collapsed state persists while the user remains on the page And line breaks in the note are preserved and links are rendered as plain text (non-clickable)
Cross-device consistency and responsiveness
Given supported browsers (Chrome, Firefox, Edge, Safari) on desktop and mobile When viewing the summary on screens from 320px to 1440px width Then the same fields, order, and values are displayed consistently across devices And no horizontal scrolling is required at 360px viewport width And interactive targets (e.g., expand controls, gallery open) meet a 44x44px minimum on touch devices And images maintain aspect ratio and fit within their containers without obscuring critical content
Schema data mapping and integrity
Given the FixFlow work order schema includes task_id, task_name, completion_status, notes, parts_used[], and timestamps{started, completed}, and deviations[] When the summary loads Then each schema field maps 1:1 to a corresponding UI element, with no data mutation beyond display formatting And parts used display name, quantity, and unit from parts_used entries And deviations display description, type, and approval status when available And null or empty fields render a standardized "Not provided" placeholder And the UI updates to reflect backend changes if the work order is updated before tenant approval
Error handling and performance
Given variable network conditions up to 800ms latency and 3G Fast bandwidth When loading the completion summary Then a skeleton loader is shown until data is available And above-the-fold summary content renders within 2.5 seconds at the 95th percentile And photo load failures display a retry control and a non-blocking placeholder And data fetch failures show a non-blocking error with a "Retry" action and are logged to the client error tracker And merely viewing or encountering errors does not create a new ticket
One-Tap Tenant Approval
"As a tenant, I want to approve the completed work in one tap so that the request can be closed without back-and-forth."
Description

Provide a tenant-facing approval screen with an accessible, single-action Approve button. On approval, record a digital acceptance event with timestamp and user identity, transition the work order to an Approved state, and trigger any downstream actions (e.g., vendor payout eligibility, closeout notifications). Include optional confirmation step, reminders via SMS/email after N hours, and configurable auto-approval after a defined window if no response.

Acceptance Criteria
One-Tap Approval Button: Visibility & Accessibility
Given a work order is in "Pending Tenant Approval" and the tenant opens the approval screen When before/after photos and the completed scope have loaded Then exactly one primary "Approve" button is visible and enabled And the button has an accessible name "Approve work order", meets WCAG 2.2 AA contrast, and has a pointer target size ≥ 44x44 px And on tap/click, a loading state appears and subsequent taps are ignored until completion And if required assets are not loaded or the work order is not in "Pending Tenant Approval", the "Approve" button is disabled
Acceptance Event Audit: Timestamp & Identity
Given an authorized tenant successfully approves a work order Then the system records an immutable acceptance event containing workOrderId, tenantUserId, ISO 8601 UTC timestamp, source channel, and correlationId And the event is stored within 1 second of approval and retrievable via audit log UI and API And the event cannot be modified or deleted by tenant users
State Transition & Downstream Actions on Approval
Given a work order is in "Pending Tenant Approval" When an approval is successfully processed (tenant or system) Then the work order state updates to "Approved" within 2 seconds And vendor payout eligibility is set to true And closeout notifications are sent to configured recipients (vendor, landlord/manager, tenant) via configured channels within 5 minutes And any scheduled reminders for the work order are canceled
Optional Confirmation Prompt Behavior
Given the org/workspace setting "Require approval confirmation" is enabled When the tenant taps Approve Then a confirmation prompt displays with explicit options "Approve" and "Cancel" And choosing "Approve" proceeds with approval; choosing "Cancel" dismisses without state change Given the setting is disabled, tapping Approve proceeds immediately without a prompt
Reminder Notifications After N Hours
Given reminders are enabled with N-hour cadence and channels (SMS and/or email) And a work order is in "Pending Tenant Approval" with no tenant action When N hours elapse since the initial approval request or the previous reminder Then a reminder is sent via the configured channels containing the approval link and work order reference And reminders do not send more than once per interval and stop immediately upon approval or flagging And each reminder send attempt is logged with timestamp and delivery status
Configurable Auto-Approval After Inactivity Window
Given auto-approval is enabled with a window W hours And the work order remains "Pending Tenant Approval" with no tenant approval or flag When W hours elapse Then the system auto-approves the work order, creating a system-generated acceptance event (actor: "system", reason: "Auto-approval window elapsed") And the work order transitions to "Approved" and downstream actions and cancellations occur as per manual approval And if any tenant flag was submitted before W, auto-approval does not execute
Idempotency & Concurrency: Prevent Double Approvals
Given multiple approval attempts occur (duplicate taps, page refreshes, or simultaneous authorized users) When the first valid approval is processed Then subsequent attempts return a non-error "Already approved" response with no additional acceptance events or duplicate downstream actions And exactly one acceptance event exists for the work order And the UI reflects the Approved state across all sessions within 5 seconds
Guided Flagging & Feedback
"As a tenant, I want to flag what’s wrong using guided reasons and photos so that the right follow-up happens without me starting over."
Description

Offer a structured alternative to approval: tenants select a predefined reason (e.g., Issue not fixed, Incomplete, New damage, Access issue, Other), optionally add comments and attach photos. Validate inputs, persist structured feedback on the work order, and create a linked follow-up item rather than a new ticket. Display inline guidance and examples to reduce vague reports and speed resolution.

Acceptance Criteria
Required Reason Selection on Flagging
Given a tenant is viewing the Photo Verify screen for a completed work order And chooses to flag instead of approve When the flagging form opens Then the reason list includes exactly: "Issue not fixed", "Incomplete", "New damage", "Access issue", "Other" And the Submit button is disabled until a reason is selected And if "Other" is selected, a comment field becomes required with a minimum of 10 characters And attempting to submit with missing required inputs shows inline validation messages and does not create a follow-up item
Photo Attachments with Validation
Given the flagging form is open When the tenant attaches photos Then up to 5 images can be attached And only JPG, JPEG, PNG, or HEIC files are accepted And each file must be 10 MB or smaller And invalid files are rejected with a specific error message indicating type or size And attached photos display as thumbnails with an option to remove before submission
Inline Guidance by Selected Reason
Given the flagging form is open When the tenant selects a reason Then contextual guidance text specific to the selected reason is displayed with at least one example of what to include And a brief tip suggests helpful photo angles where applicable And guidance is collapsible/expandable and does not block form submission And the guidance appears within 500 ms of reason selection
Persist Feedback and Create Linked Follow-up (No New Ticket)
Given the tenant provides valid inputs and submits the flag When the backend processes the request Then the feedback is persisted on the original work order with fields: reason, optional comment, photo URLs, submittedBy, submittedAt And exactly one follow-up item is created and linked to the work order with relation type "Follow-up" And no new root ticket is created And the work order and follow-up each display a cross-link to the other in their respective detail views
Idempotent Submission and Error Handling
Given the tenant taps Submit multiple times or experiences a slow network When the submission is received by the backend Then only one follow-up item is created for a given work order and client idempotency key within 5 minutes And the Submit button is disabled during the request and re-enabled after success or error And on network/server error, a clear error message is shown and no follow-up is created; the tenant can retry without data loss
Work Order Timeline and Visibility
Given feedback has been submitted When viewing the original work order timeline Then an entry appears showing the selected reason, a truncated comment preview (first 140 characters), photo count, and a link to view details And the timestamp reflects the tenant’s local timezone And the entry is visible to the landlord/property manager and assigned vendor, and not visible to unrelated tenants
Auto Follow-Up Scheduling
"As a property manager, I want flagged items to auto-schedule a follow-up with the assigned vendor so that issues are resolved quickly without creating a new ticket."
Description

When a job is flagged, automatically propose follow-up appointment slots based on the assigned vendor’s shared calendar and property access constraints, preventing double-bookings. Allow tenant selection of times, send SMS confirmations, and create a follow-up subtask linked to the original work order. Manage vendor declines/reschedules, enforce SLAs, and synchronize time zones. Update all stakeholders as status changes.

Acceptance Criteria
Auto-Propose Follow-Up Slots Upon Flag
Given a completed work order is flagged by the tenant from Photo Verify And the work order has an assigned vendor with a connected shared calendar And property access constraints are defined for the unit When the flag is submitted Then the system calculates and displays at least 3 viable follow-up appointment slots within the defined SLA window And no proposed slot begins before the earliest access time or after the latest access time for the property And the proposal is presented to the tenant within 15 seconds of submission
Double-Booking Prevention and Constraint Validation
Given the vendor calendar includes existing events and holds And building blackout dates and unit access rules exist When generating follow-up slots Then the system shall exclude any slot that overlaps existing vendor events by any amount And exclude slots on blackout dates or outside access hours And mark each proposed slot as validated against all constraints
Tenant Selection and SMS Confirmation
Given proposed slots are displayed to the tenant When the tenant selects a slot and confirms Then the appointment is reserved and written to the vendor shared calendar And the follow-up status changes to "Scheduled" And SMS confirmations containing date, time with time zone, address, work order ID, and management link are sent to the tenant and vendor And if the slot becomes unavailable prior to write, the tenant is notified and prompted to choose from freshly regenerated slots
Follow-Up Subtask Creation and Linking
Given a follow-up appointment is scheduled When the system persists the booking Then a follow-up subtask is created and linked to the original work order as a child record And the subtask stores fields: parentWorkOrderId, followUpReason, vendorId, scheduledStart, scheduledEnd, timeZone, SLATarget And both parent and child show cross-links in their timelines and lists
Vendor Decline/Reschedule Flow
Given a scheduled follow-up exists When the vendor declines or proposes a new time via the notification link Then the current slot is released And the system proposes at least 3 new slots respecting calendars and constraints within the SLA window And the tenant is notified to reselect and the status changes to "Reschedule Needed" And if no vendor response within 2 hours of initial notification, escalate to the manager via alert
Time Zone Normalization and Display
Given tenant, vendor, and property may be in different time zones When proposing and confirming appointments Then all timestamps are stored in ISO 8601 UTC in the database And displayed in each party’s local time with the correct time zone abbreviation and UTC offset in UI and SMS And reminders scheduled across DST changes reflect the correct local time
Stakeholder Updates, Audit Trail, and SLA Enforcement
Given any status change for the follow-up (Proposed, Scheduled, Rescheduled, Completed, No-Show, Cancelled) When the status changes Then notifications are sent to tenant, vendor, and landlord/manager per their channel preferences And an immutable audit log entry is recorded with actor, action, timestamp (UTC), and payload summary And if not scheduled within 48 hours of flag, the SLA is marked breached and an escalation alert is sent to the manager
Notifications & Audit Trail
"As a landlord, I want a clear audit trail and timely notifications so that I have accountability and records for disputes."
Description

Send configurable notifications (SMS, email, in-app) at key milestones: photos ready, approved, flagged, follow-up scheduled/rescheduled/completed. Maintain an immutable audit log recording who uploaded/viewed photos, who approved/flagged, timestamps, IP/device metadata, and any state changes. Provide an exportable report for disputes or compliance and a timeline view within the work order.

Acceptance Criteria
Photos Ready Notification Delivery
Given a work order’s before/after photos are marked Ready for Review for a specific tenant with channel preferences configured When the status changes to Photos Ready Then notifications are sent to the tenant via each enabled channel (SMS, email, in‑app) within 60 seconds and include work order ID, property/unit, and a secure review link And delivery status and provider message IDs are recorded in the audit log and shown in the timeline And duplicate notifications for the same event/recipient are prevented within a 10‑minute idempotency window and the suppression is logged And on transient SMS failure the system retries up to 3 times within 2 minutes, falling back to email on final failure and logging each attempt And disabled channels are not used and the preference decision is logged
Approval Event Notifications & Audit Entry
Given a tenant approves the completed work after reviewing photos When the Approve action is submitted Then an immutable audit entry is recorded with actor ID, role=Tenant, timestamp (UTC), IP, device/user‑agent, previous state=Pending Review, new state=Approved And landlord and assigned vendor are notified via their enabled channels within 60 seconds with an approval summary and work order link And the timeline displays the approval entry and notification delivery statuses And any subsequent approval attempts do not change state, return an informative message, and are logged as attempted duplicate actions
Flagged Review Notifications & Audit Entry
Given a tenant flags the work and selects a guided reason with optional photos/comment When the Flag action is submitted Then an immutable audit entry is recorded with reason codes, attachment checksums, timestamp (UTC), IP, device/user‑agent, previous state=Pending Review, new state=Flagged And landlord and assigned vendor are notified within 60 seconds with the reason summary and link to view flagged items And a follow‑up item is created within the same work order and linked in the timeline (no new ticket) And duplicate flag submissions within 5 minutes do not create additional follow‑ups and are logged as duplicates
Follow‑Up Scheduling Lifecycle Notifications
Given a follow‑up exists for a flagged work order When a schedule is created for a specific date/time window Then the system checks the shared calendar for conflicts, blocks double‑bookings with a clear error, and on success sends confirmations to tenant and vendor via enabled channels within 60 seconds including date/time, timezone, and instructions Given a scheduled follow‑up is rescheduled When the new time is saved Then reschedule notifications are sent to all parties, the shared calendar is updated, and the audit log records prior and new schedules, who made the change, and an optional reason Given a follow‑up visit is marked completed When completion is saved Then completion notifications are sent and the audit log records previous state and new state=Completed with actor metadata and completion timestamp And all lifecycle notifications capture delivery receipts (delivered/failed/bounced) and reflect statuses on the work order timeline
Photo Upload and View Audit Entries
Given a vendor uploads before/after photos When the upload succeeds Then an audit entry is recorded per photo with uploader ID, role, timestamp (UTC), file name, size, SHA‑256 checksum, and storage URI Given a tenant, landlord, or vendor views the photo set or an individual photo When the view occurs Then an audit entry is recorded with viewer ID, role, timestamp, IP, device/user‑agent, and object viewed, and is visible per role‑based permissions in the timeline Given any attempt to delete or modify an audit entry When the operation is attempted Then the system disallows the change (HTTP 403) and logs the attempt as a separate audit entry
Exportable Audit Report for Work Order
Given an authorized user requests an audit report export for a work order When the request is submitted Then the system generates PDF and CSV files within 30 seconds for up to 500 events and 50 photos and returns signed download links that expire after 24 hours And the export includes a chronological event timeline (uploads, views, approvals, flags, schedules/reschedules/completions, notifications with delivery receipts), actor details, timestamps (UTC and property‑local), IPs, device metadata, and photo checksums/URIs And if date or event‑type filters are applied, only matching entries are included and the applied filters appear in the report header And if the requester lacks permission, access is denied and the attempt is audited
Timeline View Within Work Order
Given a user with access opens a work order When viewing the Timeline tab Then events display in reverse chronological order with clear event‑type labels/icons and initial load completes under 2 seconds for up to 200 events on a 10 Mbps connection And the user can filter by event type (Photos, Views, Approvals, Flags, Notifications, Scheduling) and time range, with results updating under 500 ms after filter changes And each event expands to show details (actor, metadata, provider message IDs, delivery status, checksums) and includes a copyable event ID And if more than 200 events exist, additional events load lazily in pages of 50 while keeping the UI responsive and showing total event count
Permissions, Privacy & Secure Access
"As a tenant, I want confidence that only authorized people can view my unit’s photos so that my privacy is protected."
Description

Restrict visibility of photos and scope summaries to the relevant tenant(s), assigned vendor, and authorized staff. Use tokenized, expiring links in notifications, role-based access checks in the UI/API, and signed CDN URLs for media. Support revocation, watermarking of shared previews, rate limiting, and data retention policies aligned with privacy requirements.

Acceptance Criteria
Tenant opens expiring photo verification link from SMS/email
Given a tenant receives a notification containing a tokenized verification link with a 24-hour TTL When the tenant opens the link within 24 hours Then the system validates the token signature, TTL, role (tenant), and work order association and displays only that work order’s photos and scope And access is denied with 410 Gone and no sensitive details if the token is expired or revoked And access is denied with 403 Forbidden if the token role or work order association do not match And each successful or failed access attempt is captured in an immutable audit log with timestamp, user agent, IP, and outcome
Role-based access enforcement for photos and scope across UI and API
Given an authenticated caller with role Tenant, Vendor, or Staff (and associated relationships), or an unauthenticated/unauthorized caller When the caller requests photos or scope for a specific work order via UI or API Then access is granted only if: Tenant is linked to the unit for that work order; Vendor is assigned to that work order; Staff has permissions media.read and workorder.read And unauthorized requests receive 403 Forbidden without disclosing the existence of the resource; nonexistent resources return 404 Not Found And responses contain only fields necessary for the role (e.g., tenants do not see other tenants’ PII; vendors see only scope and scheduling details) And results are filtered such that a user cannot enumerate media from other work orders And all authorization decisions are logged with user ID, role, work order ID, and decision
Signed CDN URLs protect media delivery
Given a validated request for a media file When generating a CDN delivery URL Then the URL is signed using HMAC-SHA256 with path, expiry (10 minutes), and nonce; the signature key supports rotation And requests to the CDN without a valid signature or with an expired signature receive 403 Forbidden And signed URLs are path-scoped to a single media asset and cannot be reused for other paths And Cache-Control for previews is private, max-age=600; CDN honors origin headers and does not serve on invalidation And signature validation failures and CDN 4xx responses are logged with correlation IDs
Immediate revocation of access by staff
Given an authorized staff member selects "Revoke access" for a work order’s shared media When the staff member confirms revocation Then all active notification tokens and associated signed CDN URLs become invalid within 60 seconds And subsequent link or CDN access attempts return 403 Forbidden (or 410 Gone for expired tokens) And a revocation audit log entry is recorded with actor, reason, work order ID, and timestamp And issuing a new link generates a new token; previously revoked tokens remain invalid
Watermarked shared previews to deter redistribution
Given a preview (image or PDF) is generated for external sharing When the preview is rendered Then a semi-transparent diagonal watermark including work order ID, recipient role, and access timestamp covers at least 10% of the image area with minimum 24px text height on 1x density And the watermark remains visible after up to 20% cropping from any edge and scaling down to 480px width And attempts to request an unwatermarked preview without an authorized Staff role are denied with 403 Forbidden And original full-resolution master files remain unwatermarked but are protected behind signed URLs
Rate limiting on token and media endpoints
Given requests to token issuance/validation and media download endpoints When a single IP exceeds 60 requests per minute or a single token exceeds 10 requests per minute Then the service returns 429 Too Many Requests with a Retry-After header indicating the remaining window And rate-limit counters reset at window boundaries and are recorded per IP and per token And rate-limit events are emitted to monitoring with IP hash, token ID, endpoint, and count And allow-listed service accounts bypass limits but are still audited
Data retention and deletion for photos and scope
Given a work order is marked Completed When 365 days have elapsed since completion (or a configured retention period) Then all associated media and derived previews are queued and permanently deleted from origin storage and CDN within 24 hours And minimal metadata (work order ID, media ID, deletion timestamp) is retained for audit per policy; no binary content remains And upon approved early deletion requests, media is purged within 7 days and reflected in the audit trail And subject access exports exclude deleted media and include tombstones indicating deletion dates

Quick Reopen

If the issue resurfaces within a set warranty window, tenants can reopen the original work order from the portal with one tap. All context, photos, and history carry over—avoiding duplicate requests and keeping calendars clean.

Requirements

One-Tap Reopen in Tenant Portal
"As a tenant, I want to reopen my recently closed work order with one tap when the issue resurfaces so that I can keep all history intact and avoid creating a new request."
Description

Expose a prominent Reopen button on closed work orders in the tenant portal when the ticket is eligible under the configured warranty policy. Display a clear warranty countdown and a brief summary of what will happen on reopen. On confirmation, transition the ticket to a Reopened state while preserving the original work order ID, keeping the conversation thread visible, and surfacing any required inputs (e.g., optional new photos or notes). Ensure responsive web support, accessibility compliance, and graceful handling of offline or repeat taps to prevent accidental duplicate actions.

Acceptance Criteria
Show Reopen Button Only During Warranty Window
Given a tenant is authenticated in the tenant portal and viewing a closed work order, and the work order is eligible under the configured warranty policy When the work order details page loads Then a prominent enabled “Reopen” button is rendered in the action area, labeled and aria-labeled as “Reopen work order” And the button is not rendered for ineligible tickets (warranty expired/disabled) or non-closed statuses (e.g., Open, Reopened) And only tenants associated with the unit/ticket can see the button; unauthorized users do not see it
Display Accurate Warranty Countdown Timer
Given a closed, warranty-eligible work order is viewed in the tenant portal When the page renders Then a warranty countdown is displayed next to the Reopen control in the format “Warranty: <time remaining>” And the time remaining is calculated against the configured policy and the property’s timezone And the countdown updates at least once per minute and never displays negative time And upon expiry while the page is open, the countdown changes to “Warranty expired” and the Reopen button is removed within 60 seconds
Confirm Reopen With Optional New Evidence
Given a tenant clicks the Reopen button on an eligible, closed work order When the confirmation dialog appears Then the dialog summarizes the action (the same ticket will be reopened and the landlord will be notified) And shows optional inputs for a brief note and optional new photos And the Confirm action proceeds whether or not optional inputs are provided, while Cancel closes the dialog without changes And validation errors are shown inline if unsupported file types are added or network errors occur during photo upload
Reopen Transitions State and Preserves Work Order Context
Given a tenant confirms the Reopen action When the backend accepts the request Then the original work order transitions to status Reopened without generating a new work order And the original work order ID remains unchanged and visible And the entire conversation thread and media remain visible, with any new note/photos appended with timestamps And an activity entry “Ticket reopened by tenant” is recorded and visible to tenant and landlord
Prevent Duplicate Actions and Handle Offline Attempts
Given a tenant taps Reopen multiple times in quick succession or experiences intermittent connectivity When the first Reopen request is submitted Then the UI disables the Confirm button and shows a progress indicator until a response returns And subsequent taps are ignored and no duplicate reopen actions are created (idempotent request) And if offline, the UI indicates the action is queued and retries automatically when connectivity returns; if the server indicates the ticket is already reopened, the UI reconciles to Reopened without error And if the warranty expires before the server processes the queued request, the action fails gracefully with a clear message and a CTA to submit a new request
Accessible and Responsive Reopen Flow
Given tenants use varied devices and assistive technologies When interacting with the Reopen button and confirmation dialog Then all interactive elements are keyboard accessible with visible focus, Enter/Space activates primary actions, and ESC closes the dialog And screen reader labels/roles are present (button name announced, dialog has role=dialog with focus trap and descriptive title) And color contrast meets WCAG AA (≥4.5:1 for text) and touch targets are at least 44x44 px on mobile And layouts render correctly from 320px to 1440px widths without truncation or overlap
Handle Expired Warranty by Guiding to New Request
Given a tenant views a closed work order that is no longer warranty-eligible When the details page loads Then no Reopen control is shown And an informational message explains the warranty has expired and provides a clear CTA to start a new maintenance request And if a tenant follows a stale deep link directly to the Reopen confirmation, the system blocks the action, shows the expiry message, and offers the new request CTA
Warranty Window Validation & Policy Controls
"As a property manager, I want to define and enforce warranty windows and limits for reopens so that tenants can reopen when appropriate and vendors are protected from out-of-scope work."
Description

Implement a configurable policy engine that determines reopen eligibility based on warranty windows set at global, property, category, and vendor levels. Validate eligibility at the moment of action using the ticket’s closure timestamp and property timezone, enforce maximum reopen counts, and support property manager overrides with reason capture. Provide clear, inline messages for eligible, near-expiry, and ineligible cases, and log all policy decisions for auditability.

Acceptance Criteria
Global Warranty Eligibility Within Window
Given a global warranty window of 30 days and no property, category, or vendor overrides are set And ticket T1 was closed at 2025-08-01 15:00 in the property's timezone (America/Chicago) When the tenant attempts to reopen T1 at 2025-08-31 14:59 America/Chicago Then the system allows the reopen and links the activity to the original work order And the inline eligibility message displays the remaining time to the minute in the property's timezone When the tenant attempts to reopen at 2025-08-31 15:01 America/Chicago Then the system blocks the reopen And the inline message states the warranty window expired at 2025-08-31 15:00 America/Chicago
Policy Precedence Across Levels
Given configured warranty windows: global = 30 days, property P1 = 20 days, category Plumbing = 10 days, vendor V1 = 7 days And ticket T2 belongs to property P1, category Plumbing, and vendor V1 When eligibility is evaluated Then the engine uses the most specific applicable setting (vendor) and applies 7 days Given ticket T3 belongs to property P1, category Plumbing, and has no vendor-specific setting When eligibility is evaluated Then the engine applies the category window of 10 days Given ticket T4 belongs to property P1 with no category or vendor-specific settings When eligibility is evaluated Then the engine applies the property window of 20 days Given ticket T5 has no property, category, or vendor-specific settings When eligibility is evaluated Then the engine applies the global window of 30 days
Property Timezone and DST Handling
Given ticket T6 belongs to a property with timezone America/Los_Angeles and a 1 day warranty window And T6 was closed at 2025-03-09 01:30 America/Los_Angeles (DST transition day) When the expiry timestamp is computed Then the expiry is 2025-03-10 01:30 America/Los_Angeles Given a user views T6 from a device set to America/New_York When eligibility is displayed Then all time calculations and timestamps are based on the property's timezone and clearly labeled with that timezone And eligibility decisions are identical regardless of the viewer's device timezone
Maximum Reopen Count Enforcement
Given max_reopen_count is configured to 2 at the property level for property P2 And ticket T7 in P2 has reopen_count = 1 and is within the applicable warranty window When the tenant attempts to reopen T7 Then the system allows the reopen and increments reopen_count to 2 And the updated count is persisted on the ticket Given ticket T8 in P2 has reopen_count = 2 (at the configured maximum) When the tenant attempts to reopen T8 Then the system blocks the reopen And the inline message states "Reopen unavailable — Max reopen count reached (2/2)"
Authorized Manager Override With Reason Capture
Given ticket T9 is ineligible due to an expired warranty window And the signed-in user is a Property Manager with override permission When the user selects "Override and Reopen" Then a reason input is required with a minimum of 10 characters And upon confirmation the ticket is reopened despite ineligibility And the override reason is stored with the event and visible in the ticket timeline And an audit entry records actor, prior decision, policy source(s), and override reason Given the signed-in user is a Tenant or a Manager without override permission When the ticket is ineligible Then the "Override and Reopen" option is not presented
Inline Eligibility Messaging States
Given a near-expiry threshold configured to 24 hours at the property level And ticket T10 has 5 hours remaining in the property's timezone When the tenant views T10 Then the message shows "Reopen available — 5h 00m remaining" and a near-expiry visual state Given ticket T11 has more than 24 hours remaining When the tenant views T11 Then the message shows "Reopen available — Xd Yh remaining" without the near-expiry state Given ticket T12 is ineligible due to expired window When the tenant views T12 Then the message shows "Reopen unavailable — Warranty window expired on <timestamp in property TZ>" Given ticket T13 is ineligible due to reaching the maximum reopen count When the tenant views T13 Then the message shows "Reopen unavailable — Max reopen count reached (N/N)"
Comprehensive Audit Logging of Policy Decisions
Given an eligibility evaluation or reopen attempt occurs for ticket T14 When a decision is made (Allow, Block, or Overridden-Allow) Then the system writes an immutable audit event containing: ticket id, actor id (or anonymous), action type, decision, decision source level(s) used (vendor/category/property/global), configured values (warranty window, max count), computed timestamps (closed_at, expires_at) in the property timezone, rendered messages, override reason (if any), correlation id, and event timestamps in UTC and the property timezone And the audit event is queryable by ticket id in the admin audit UI and exportable via API And both successful reopens and blocked attempts produce audit entries And if the audit write fails, the state change does not proceed and an error is returned to the caller
Work Order Context Carryover & Timeline Audit
"As a landlord, I want all prior context to remain attached to the reopened ticket with a clear audit trail so that I can see what changed and hold parties accountable."
Description

Retain and display the full history of the original work order upon reopen, including photos, files, comments, vendor notes, and system events, while appending a Reopened event to the timeline with who/when/why metadata. Maintain the same work order identifier, increment a reopen counter, and capture a versioned snapshot at each close and reopen for forensic traceability. Preserve links to related invoices, quotes, and approvals, and ensure attachments remain deduplicated and accessible.

Acceptance Criteria
Reopen Carries Over Full Work Order Context
Given a closed work order contains photos, files, comments, vendor notes, and system events And the tenant is within the warranty window When the tenant reopens the original work order from the portal Then the reopened work order displays all prior photos, files, comments, vendor notes, and system events without loss And no new duplicate records are created for pre-existing attachments or notes And each carried-over item retains its original timestamp and author and appears in correct chronological order
Timeline Reopened Event with Who/When/Why
Given a user initiates a reopen action on a closed work order When the reopen is confirmed Then a 'Reopened' event is appended to the timeline And it records the actor identity (user ID and role), UTC timestamp to the second, and a mandatory reason text (min 5 characters) And the 'Reopened' event appears immediately after the most recent 'Closed' event in chronological order And previous timeline events remain unchanged
Work Order ID Persistence and Reopen Counter Increment
Given a closed work order with identifier WO-{id} and reopen_count = N When the work order is reopened Then the identifier remains exactly WO-{id} across UI, notifications, and API responses And reopen_count increments to N+1 and is visible in UI and API And all existing deep links and references to WO-{id} continue to resolve without redirection or 404 errors
Versioned Snapshots at Close and Reopen
Given a work order is closed or reopened When the close or reopen event is committed Then the system creates an immutable snapshot capturing all fields, timeline entries, counters, and attachment references And the snapshot is assigned a unique version ID and UTC timestamp And all snapshots are retrievable by version ID via UI and API And comparing consecutive snapshots accurately shows changes to fields, timeline, and reopen_count
Financial Links Preservation on Reopen
Given a closed work order has linked invoices, quotes, and approvals When the work order is reopened Then all existing links persist and remain navigable from the work order And the financial record statuses are unchanged by the reopen And no duplicate invoice, quote, or approval records are created And link integrity checks for each related record return success
Attachment Deduplication and Accessibility
Given the work order contains attachments with stored checksums and access URLs When the work order is reopened and no new files are uploaded Then existing attachments remain accessible via the same URLs/IDs with HTTP 200 responses And no new attachment records are created When a user uploads a file identical in checksum to an existing attachment after reopen Then the system deduplicates by checksum, references the existing file, and does not create a new blob And the timeline records the upload attempt and deduplication outcome
Auto Scheduling & Notifications on Reopen
"As a tenant, I want the reopened ticket to automatically notify the vendor and offer scheduling options so that the issue is handled quickly without extra calls or emails."
Description

On reopen, re-run assignment rules to route the ticket back to the original vendor by default (configurable), check the shared calendar for availability, and propose the next open slots without creating conflicts. Send SMS and email notifications to tenant and vendor with confirmation links, update calendar entries upon acceptance, and handle fallbacks when the original vendor is unavailable (e.g., reassign to backup pool). Respect notification quiet hours, include ICS attachments, and keep all confirmations threaded in the work order timeline.

Acceptance Criteria
Reopen Within Warranty Routes to Original Vendor
Given a closed work order with vendor X and a configured warranty window of Y days And the tenant reopens the work order within Y days of completion When the system processes the reopen Then the assignment rules are re-run And the original vendor X is selected as assignee by default And if configuration flag defaultToOriginalVendor = false, standard routing rules determine the assignee And the work order retains all prior context, photos, and history And if the reopen occurs after Y days, the tenant is prompted to submit a new work order instead and no scheduling or notifications are triggered
Non-Conflicting Slot Proposal on Reopen
Given the work order is reopened and an assignee vendor is determined And a shared calendar with existing events and buffers is enabled When the system generates proposed time slots Then at least 3 next available slots within the vendor’s working hours in the next 7 days are proposed (both values configurable) And no proposed slot overlaps with existing holds or confirmed events for the tenant, unit, or vendor And slot durations match the service type default duration with configured buffer times applied And tenant blackout periods are excluded from proposals And all proposed times are shown in the tenant’s and vendor’s local time zones (based on property and vendor settings)
Tenant and Vendor Notifications with Confirmation Links
Given proposed slots have been generated When notifications are sent Then the tenant receives SMS and email containing the proposed slots and a unique confirmation link per slot And the vendor receives SMS and email with the reopen notice and the same slot options and a link to confirm or propose alternatives And all messages use the configured templates and localization for the recipients And each email includes an ICS attachment for each proposed slot And each SMS includes a shortened, trackable URL to the confirmation page
Calendar Hold and Confirmation Flow
Given proposed slots are sent And soft holds are placed on the shared calendar for each proposed slot When the tenant or vendor confirms a slot via the link Then the selected slot becomes a confirmed calendar event for both parties And all other soft holds for the work order are automatically released And the work order status updates to Scheduled with the confirmed datetime And duplicate confirmations after the first are rejected with an informative message And calendar updates sync in real time to the dashboard and participant calendars
Vendor Unavailable Fallback Reassignment
Given the original vendor is the default assignee And the vendor has no availability within the configured scheduling window or is marked unavailable/out-of-office When the system evaluates fallback rules Then the work order is reassigned to an eligible vendor from the backup pool based on priority and skills matching And proposed slots are regenerated for the new vendor without conflicts And the tenant and the new vendor are notified of the reassignment with updated confirmation links and ICS And the original vendor receives a courtesy notification of the reassignment And all actions are logged in the work order timeline
Notification Quiet Hours Enforcement
Given organization quiet hours are configured with start and end times per timezone When a notification would be sent during quiet hours for a recipient Then the notification is queued for delivery at the next allowable send time in the recipient’s timezone And messages marked timeSensitive are sent during quiet hours only if configuration allows And queued notifications preserve original content, links, and ICS attachments And an audit entry records the delay reason and scheduled send time
Threaded Confirmations in Work Order Timeline
Given the work order is reopened and notifications are sent When recipients view, confirm, decline, or propose new times via SMS, email, or web Then each action is appended as a threaded entry in the work order timeline And entries include timestamp, actor (tenant/vendor/system), channel (SMS/Email/Web), and action details And ICS attachment references and calendar event IDs are included where applicable And the full sequence from reopen through scheduling is visible in chronological order
Duplicate Request Prevention & Merge
"As a property manager, I want the system to prevent duplicate tickets and merge related requests so that calendars stay clean and vendors aren’t double-booked."
Description

Detect potential duplicates by monitoring new submissions from the same unit/tenant/category within the warranty window and offer the user the option to reopen the prior ticket instead. If a duplicate is created, provide tools to merge tickets, consolidate conversations and media, and remove redundant calendar events while maintaining cross-references and audit logs. Use heuristics and optional image/keyword similarity to improve matching, and allow admin override when tickets should remain separate.

Acceptance Criteria
Prompt to Reopen Within Warranty Window
Given a tenant for Unit U has a previously Resolved ticket T1 in category C within the warranty window And the tenant starts a new request for Unit U and selects category C When the tenant enters a title/description and optional photos and proceeds to submit Then the system evaluates the submission for duplicates And if a match candidate exists, the tenant is shown a prompt to Reopen ticket T1 with ticket ID, last updated date, and thumbnail And the prompt offers actions: Reopen T1 or Create New Ticket And the prompt displays an explanation of the match (e.g., same unit, category, recent date) And the submission is paused until the tenant chooses an action
One-Tap Reopen Preserves Context and Avoids New Ticket
Given a duplicate prompt offering Reopen for ticket T1 is displayed When the tenant chooses Reopen Then no new ticket record is created And ticket T1 status changes to Reopened And the tenant’s new text and photos are appended to T1 as a new comment with timestamp and author And T1 retains its original ticket ID and full history And stakeholders (assigned vendor, landlord, tenant) receive reopen notifications via configured channels And no duplicate calendar events are created
Similarity Matching with Keywords and Images
Given duplicate detection is enabled with a configurable threshold and image similarity setting When a new submission within the warranty window is evaluated Then a match score is computed using unit, category, time proximity, keyword similarity, and perceptual image similarity (if enabled) And if the score is greater than or equal to the configured threshold, the prior ticket is suggested to Reopen And the suggestion includes a "Why suggested" link listing the top matching factors And when image similarity is disabled at the property, the score excludes image factors And when the threshold is updated, the suggestion behavior reflects the new threshold
Admin Merge Consolidates Data and Removes Redundant Events
Given two tickets T1 and T2 are identified as potential duplicates And an admin has merge permissions When the admin selects Merge and chooses T1 as primary Then conversations, comments, attachments, tags, and checklist items from T2 are merged into T1 with authors and timestamps preserved And calendar events belonging to T2 are canceled or merged to avoid double-bookings, with updated confirmations sent as needed And participants from T2 are subscribed to T1 notifications And T2 is closed with status "Merged Into T1" and a permanent cross-reference is created between T1 and T2 And an audit log entry records the merge with before/after state And all affected parties receive a merge notification summarizing changes
Admin Override to Keep Tickets Separate
Given the system flags T1 and T2 as potential duplicates When an admin selects Keep Separate and provides a reason Then duplicate prompts for the pair T1–T2 are suppressed for the duration of the warranty window And both tickets remain independent with no data consolidation And a "Not Duplicate" label with the recorded reason is added to both tickets And an audit log entry records the override action and reason
Configurable Warranty Window per Property
Given a property has a warranty window setting in days When an admin updates the warranty window to W days Then duplicate detection and reopen prompts consider prior tickets closed within the last W days only And if W is set to 0, duplicate prompting and auto-reopen suggestions are disabled for that property And changes apply to submissions created after the setting update and are visible in system diagnostics
Comprehensive Audit Logging for Reopen and Merge
Given audit logging is enabled When a reopen is accepted or a merge or override action is performed Then the log captures actor, timestamp, action type, affected ticket IDs, previous and new statuses, and any calendar modifications And each log entry is immutable and viewable by admins in the ticket history And logs can be exported in CSV for a selected date range
Admin Controls & Limits for Reopen
"As a property manager, I want granular controls over how reopens work so that the policy matches my business needs and vendor agreements."
Description

Provide a settings panel for landlords/property managers to enable or disable Quick Reopen per account and property, set default warranty durations, define maximum reopen attempts, require additional evidence (e.g., new photo or note) on reopen, customize confirmation copy, and choose notification channels. Enforce role-based access for who can change policies and who can perform manual overrides, and record all changes in an administrative audit log.

Acceptance Criteria
Toggle Quick Reopen at Account and Property Levels
Given I am a user with Policy:Edit permission When I set the account-level Quick Reopen to Disabled and save Then all properties inherit Disabled unless explicitly overridden And the tenant portal hides the Reopen action for affected properties within 60 seconds Given account-level Quick Reopen is Disabled When I enable Quick Reopen for Property A and save Then tenants of Property A see the Reopen action subject to other constraints within 60 seconds And property-level setting takes precedence over account-level for enforcement And the effective setting persists across logout/login and service restarts
Warranty Window Configuration and Enforcement
Given account default warranty is 14 days and Property B overrides to 7 days And a work order at Property B was completed at 2025-09-01T12:00 (property local time) When a tenant attempts to reopen at 2025-09-08T11:59 (property local time) Then the reopen submission is accepted When the tenant attempts to reopen at 2025-09-08T12:01 (property local time) Then the reopen is blocked And the tenant sees the configured "Reopen window expired" message And no new work order is created And the time window is evaluated in the property's local timezone
Maximum Reopen Attempts Enforcement
Given account max reopen attempts is 2 and Property C overrides to 1 And a work order in Property C has 1 completed reopen cycle When the tenant attempts to reopen again Then the submission is blocked with the configured "Maximum reopen attempts reached" message And the existing work order remains unchanged And an event is recorded with work order ID and reason "max_attempts_exceeded" When I increase Property C max attempts to 2 and save Then the same work order allows one additional reopen
Evidence Requirement on Reopen
Given the evidence requirement is set to "Photo or Note required" When a tenant taps Reopen without attaching a photo and without entering a non-empty note Then the form displays inline validation and prevents submission And validation messages are accessible to screen readers When the tenant provides at least one new photo or a note containing non-whitespace characters and submits Then the reopen succeeds And the provided evidence is attached to the work order timeline and visible to managers and vendors
Custom Confirmation Copy and Preview
Given I configure confirmation copy for success and denial with placeholders {workOrderId}, {propertyName}, {warrantyDays} When I click Preview for Tenant Portal and SMS Then the preview renders with sample data replacing placeholders And saving persists the templates and records a new template version When a tenant successfully reopens a work order Then the portal displays the custom success copy and an SMS with the same messaging is sent within 30 seconds When a reopen is blocked by policy Then the portal displays the custom denial copy And if no custom copy is configured, system defaults are used
Notification Channel Selection and Delivery
Given for Property D I set notifications: Tenant SMS=On, PM Email=On, Vendor Email=On, PM Push=Off When a tenant submits a reopen Then the Tenant receives an SMS confirmation containing work order ID and property name within 30 seconds And the PM and Vendor each receive an email containing a work order link, reopen evidence summary, and timestamps And no push notification is sent to the PM And delivery outcomes (success/failure) are recorded in the work order activity
Role-Based Access and Manual Override with Audit
Given role permissions: users with Policy:Edit can edit policies; users with Policy:View cannot; users with Reopen:ManualOverride can override constraints When a Policy:View user attempts to change warranty duration or max attempts Then the change is blocked with an authorization error And no setting is modified When a Policy:Edit user updates the warranty duration and saves Then the change is applied And an audit log entry records actor, timestamp, scope (account/property), previous value, new value, and a mandatory change reason When a user with Reopen:ManualOverride reopens a work order beyond warranty or max attempts and provides a reason Then the system allows the reopen And the override is tagged in the work order timeline And an audit entry records actor, timestamp, work order ID, constraint bypassed, and reason
Reopen Analytics & SLA Reporting
"As a landlord, I want to track reopen trends and their impact on SLAs so that I can improve vendor selection and maintenance policies."
Description

Add dashboards and exports that report reopen rate, average days between close and reopen, distribution by property, unit, category, and vendor, within-warranty vs out-of-warranty attempts, and the impact of reopens on resolution time and costs. Include drill-down links to the underlying tickets and timeline events, trend lines over time, and webhook events for BI integration. Reflect reopens in SLA calculations with clear attribution for pauses, resets, and exceptions.

Acceptance Criteria
Dashboard Metrics: Reopen Rate and Avg Days to Reopen
Given a selected date range, portfolio scope, and account timezone When the Reopen Analytics dashboard loads Then it displays Reopen Rate as percentage and Avg Days Between Close and Reopen with 1 decimal precision Given the same filters When the underlying dataset is sampled and manually computed Then the displayed Reopen Rate equals unique work orders reopened in range divided by work orders closed in range times 100 Given the same filters When multiple reopens occur for the same work order in range Then it counts once toward Reopen Rate numerator and includes each reopen interval in Avg Days calculation Given the same filters When there are no reopens in range Then Reopen Rate shows 0 percent and Avg Days shows 0.0 with an empty state message Given the timezone is changed When the dashboard reloads Then metric calculations use the new timezone for date bucketing
Distribution and Drill-Down by Property, Unit, Category, and Vendor
Given a selected grouping dimension of Property, Unit, Category, or Vendor When the Distribution view is loaded Then each row shows count of reopens, percent of total, and the row totals sum to the displayed total reopens Given a grouping is selected When the user sorts by count or percent Then rows sort accordingly and ties are secondary sorted by name ascending Given a distribution row is clicked When the drill-down opens Then a prefiltered ticket list appears matching the same date range, scope, and the clicked value Given a ticket is selected from the drill-down When the ticket detail opens Then the timeline shows close and reopen events with timestamps and actors Given filters are changed When the distribution view reloads Then counts and percentages update to reflect the new filters
Warranty Classification: Within- vs Out-of-Warranty Reopen Attempts
Given a configured warranty window recorded on each closed work order When a reopen event occurs Then the system classifies it as within-warranty if reopen time minus prior close time is less than or equal to the recorded warranty window; otherwise out-of-warranty Given an out-of-warranty attempt is made from the tenant portal When the attempt is logged Then it is counted in out-of-warranty attempts and excluded from within-warranty counts Given the warranty window configuration changes after close When historical analytics are viewed Then classification uses the warranty window recorded on the work order at the time of close Given the warranty classification filters are applied When the dashboard reloads Then totals for within-warranty and out-of-warranty attempts match the sum of classified events in the drill-down
Impact Metrics: Resolution Time and Added Costs from Reopens
Given a work order that is reopened at least once When computing impact metrics Then Final Resolution Time equals first created time to final close time inclusive of all reopen cycles Given the same work order When computing impact metrics Then Added Resolution Time equals Final Resolution Time minus Initial Resolution Time from first created to first close Given cost line items are present When computing costs Then Added Cost equals sum of costs added after the first close and Total Cost equals original cost plus added cost Given a date range and filters When viewing impact cards Then the dashboard shows average Added Resolution Time for reopened tickets and compares it to non-reopened tickets in the same range Given the export for impact metrics is downloaded When totals are aggregated from the file Then they match the on-screen totals for the same filters within rounding tolerance
Trend Lines Over Time and CSV Export
Given a date range When Trend granularity Month or Week is selected Then the chart displays one point per bucket with counts of reopens and Reopen Rate per bucket computed using the same definitions Given filters are applied When the trend chart reloads Then values and counts match the filtered dataset Given the user clicks Export CSV When the file is generated Then it includes one row per reopen event with columns ticket_id, property, unit, category, vendor, close_datetime, reopen_datetime, days_between, warranty_classification, original_cost, added_cost, total_cost, sla_reset_flag, sla_paused_minutes, sla_exception_code, event_id Given the same filters and date range When the CSV is downloaded Then the number of rows equals the number of reopen events in the trend for that period Given the CSV is opened When dates are inspected Then all datetimes are in ISO 8601 with timezone offset
Webhooks for BI Integration on Reopen and SLA Updates
Given webhooks are configured with a destination URL When a work order is reopened Then the system sends a work_order.reopened event with payload containing ticket_id, property_id, unit_id, category_id, vendor_id, prior_close_datetime, reopen_datetime, days_between, warranty_classification, actor_type, actor_id, idempotency_key, signature Given the receiver returns a non-2xx response When delivery is attempted Then the system retries up to 3 times with exponential backoff and preserves event order per ticket Given a work order SLA is recalculated due to a reopen When the recalculation completes Then the system sends a work_order.sla_updated event with payload containing ticket_id, sla_reset_flag, paused_minutes, exception_code, prior_sla_state, new_sla_state, recalculated_at Given the webhook is delivered When the payload is validated Then the signature verifies using the configured secret
SLA Recalculation with Reopen Attribution
Given SLA rules are configured for response and resolution When a ticket is reopened within warranty Then SLA clocks and milestones reset according to configuration and the report attributes a Reset event to the reopen Given a ticket is reopened out of warranty When SLA is recomputed Then SLA clocks do not reset and any paused durations due to tenant unavailability or vendor scheduling are attributed with labeled reasons Given SLA exceptions are applied When the SLA report is viewed Then each reopened ticket shows fields for reset flag, paused minutes by reason code, exception code if any, and final breach or pass status Given a date range and filters When SLA summary metrics are displayed Then counts of breaches and average time to resolution reflect the recomputed clocks for reopened tickets and match drill-down totals

Product Ideas

Innovative concepts that could enhance this product's value proposition.

Snap-Triage Intake

Tenants snap a photo, select symptoms, and FixFlow auto-classifies severity and routes to the right vendor. Cuts false emergencies and speeds first-time fixes with required details.

Idea

One-Tap Approvals

Send instant cost approvals via SMS or email with itemized estimates and photos. Auto-escalate after deadlines and log decisions for audits.

Idea

Vendor Wallet Payouts

Pay vendors on completion with ACH or debit push, tied to signed work orders. Trigger partial holds for missing photos or permits.

Idea

Granular Guardrails

Set per-property roles and least-privilege access for staff, vendors, and assistants. Mask tenant contact details and require 2FA for approvals.

Idea

Turnover Playbooks

One-click templates schedule cleaners, painters, and locksmiths with dependencies and lead times. Auto-insert access windows and SMS tenants to compress vacancy days.

Idea

SLA Stopwatch

Live timers track response and resolution SLAs per issue type and property. Color alerts and nudge texts prevent misses; exportable logs satisfy inspectors.

Idea

FixTrack Portal

A lightweight tenant page showing live appointment status, technician ETA, and completion photos. Cuts 'status' calls and boosts trust.

Idea

Press Coverage

Imagined press coverage for this groundbreaking product concept.

P

FixFlow Launches AI Intake and Auto‑Scheduling to End Maintenance Phone‑Tag for Small Property Teams

Imagined Press Article

San Francisco, CA — September 9, 2025 — FixFlow, the lightweight web dashboard built for independent landlords and small property managers, today announced the general availability of its AI Intake and Auto‑Scheduling suite designed to centralize maintenance requests, eliminate phone‑tag, and slash response times across portfolios of 1–200 units. Built from the ground up for speed and clarity, FixFlow turns tenant photos and short videos into clean, routable work orders that auto‑book the right vendor on a shared calendar and confirm by SMS in seconds. Tenants submit, managers supervise, and vendors show up prepared—no chaotic email‑and‑phone workflows required. What’s new and why it matters • Vision AutoFill: AI analyzes uploaded images to identify fixtures, detect likely faults, and assess hazards, then auto‑fills category, severity, and preferred vendor while extracting visible model and serial numbers. • Adaptive Prompts: Dynamic, issue‑specific questions collect must‑have details like shutoff access, noise patterns, and entry permissions, cutting follow‑up calls and return trips. • Severity Guard: Real‑time risk scoring flags true emergencies, presents clear safety steps, and escalates after‑hours issues to on‑call vendors while deferring non‑critical requests to business hours. • Model Match: OCR captures appliance and equipment details, attaches relevant manual snippets, and suggests likely parts or tools so technicians arrive prepared. • Duplicate Shield: Image and text similarity checks merge duplicate submissions and notify tenants of status updates, keeping calendars clean and preventing double‑bookings. • Privacy Blur and Offline Snap: On‑device redaction protects tenant privacy, while low‑signal capture with auto‑sync ensures reliable intake from basements, garages, or rural properties. For Solo Landlords juggling a handful of units, the system means fewer late‑night calls and more first‑visit fixes. For Portfolio Ops Managers with small teams, it means a centralized queue that load‑balances assignments and prevents scheduling conflicts. In‑House Maintenance Leads can prioritize with a live shared calendar, while Preferred Vendor Partners confirm appointments via one‑tap SMS links. How it works end‑to‑end Tenants open the request page, snap a photo, and pick observable symptoms. Vision AutoFill and Adaptive Prompts quietly do the heavy lifting—capturing model numbers, entry notes, and preferred time windows while Severity Guard determines urgency and safety actions. Approved requests flow into a consolidated dashboard where FixFlow auto‑schedules the right vendor or in‑house tech based on rules, skills, and availability. The shared calendar sends SMS confirmations with Live Map ETA for day‑of visibility, while Smart Rescheduler lets tenants pick a new slot instantly when plans change. Quotes “Small operators deserve enterprise‑grade speed without enterprise‑grade complexity,” said Maya Chen, FixFlow co‑founder and CEO. “By turning a single photo into a fully routable ticket and auto‑booking the right pro, we’re giving independent landlords back their evenings while improving first‑visit fix rates.” “As a remote owner, I live on confirmations and a shared calendar,” said Riley Thompson, a Remote Owner managing units in two states. “FixFlow’s Severity Guard cut false emergencies, and Model Match meant our tech showed up with the exact part for a finicky dishwasher. That was a first‑visit fix that used to take two trips.” “Text‑based confirmations with all the access notes have reduced our back‑and‑forth by half,” said Jamie Park, owner of Park Plumbing and a Preferred Vendor Partner. “We can see calendar holds, confirm with one tap, and arrive with the right tools because the photo intake actually includes the details we need.” Measured impact Early adopters reported a 38% faster first response, a 29% increase in first‑visit fixes, and a 41% reduction in duplicate tickets across mixed portfolios. With Duplicate Shield and a shared calendar, teams also eliminated accidental double‑bookings that previously derailed days. Availability and setup The AI Intake and Auto‑Scheduling suite is available today to all FixFlow customers. Setup takes minutes: import properties, invite vendors, and turn on policy‑based scheduling rules. Role Blueprints and Policy Packs help teams roll out least‑privilege access and privacy controls in a click. New DIY Landlords can start with templates that standardize request intake and auto‑scheduling by issue type. Who it’s for • Solo Landlord: Capture and route everything from one dashboard; avoid phone‑tag with auto‑confirmations. • Portfolio Ops Manager: Centralize work orders, load‑balance assignments, and track response times. • In‑House Maintenance Lead: Prioritize on a shared calendar and update statuses from the field. • Preferred Vendor Partner: Confirm jobs via SMS, see holds, and cut admin overhead. • Tenant Communications Assistant: Triage, update, and escalate urgent issues in one place. About FixFlow FixFlow is the lightweight, modern maintenance ops platform that centralizes intake, scheduling, communications, and payouts for independent landlords and small property managers. Teams use FixFlow to cut response times, prevent double‑bookings, and replace email‑and‑phone chaos with a clean, shared source of truth. Media contact Alex Romero, Communications Lead press@fixflow.app +1‑415‑555‑0137 https://fixflow.app/press

P

FixFlow Unveils ProofHold Escrow and Instant PushPay to Pay Vendors Faster With Control

Imagined Press Article

San Francisco, CA — September 9, 2025 — FixFlow today introduced a new payments and compliance stack that accelerates vendor payouts while protecting budgets and audit trails. Anchored by ProofHold Escrow and Instant PushPay, the release gives independent landlords and small property managers the speed vendors love with the controls owners demand. The announcement brings enterprise‑grade clarity to portfolios of 1–200 units, tying payouts to evidence and automating the back‑office steps that normally require spreadsheets, phone calls, and manual follow‑ups. Vendors get paid faster; managers keep spend aligned with policy and see exactly what’s outstanding—no status‑chasing required. What’s included • ProofHold Escrow: Funds are automatically escrowed at work order approval and released when required proof items—completion photos, tenant sign‑off, permit number—are submitted. Partial holdbacks by line item or percentage prevent overpay while keeping vendors funded. • Instant PushPay: Offer real‑time debit push or same‑day ACH the moment a job is marked complete and checks pass. Vendors choose payout speed with transparent fees, reducing “payment status” calls and keeping crews loyal. • Milestone Releases: Break larger, multi‑visit jobs into milestones with predefined amounts and evidence requirements. Automatic partial payouts keep projects moving without manual spreadsheet tracking. • Smart Splits: Divide a single payout across lead contractors, helpers, and materials reimbursement with rules tied to line items or percentages. FixFlow generates separate receipts and handles 1099 tracking automatically. • Compliance Gate: Validate W‑9s, insurance certificates, licenses, and permits before money moves. Auto‑detect expirations, request updates via SMS/email, and attach documents to the job record. • Dispute Resolve: Pause or reduce a payout when scope issues arise. Collect evidence, propose adjustments, and timebox responses with transparent status for both sides. Why it matters Small teams have historically faced an impossible trade‑off: pay quickly to keep vendors happy or slow down to protect budgets and compliance. FixFlow eliminates the trade‑off by tying speed to proof and policy. That means faster cash flow for vendors and fewer exceptions for managers—plus a clean, exportable audit record. Quotes “Fast, fair, and documented is the gold standard for maintenance payouts,” said Priya Desai, Head of Payments at FixFlow. “ProofHold Escrow and Instant PushPay give small operators the same control towers as big enterprises—without adding overhead. If the work is done and the evidence is there, vendors get paid today.” “As a Portfolio Ops Manager, I need to push funds quickly while guarding budgets,” said Marcus Lee, who oversees 120 units across two neighborhoods. “Compliance Gate blocks risky payouts automatically, and Milestone Releases keep rehab crews funded without me chasing photos and permits. We’ve cut our payout cycle from seven days to under 24 hours.” “We used to wait a week for checks,” said Jamie Park, owner of Park Plumbing and a Preferred Vendor Partner. “With Instant PushPay, we see funds same‑day once photos hit. It’s predictable and transparent, so I can keep my best techs booked on FixFlow jobs.” How it works Approvers see a Context Card summarizing severity, habitability impact, access notes, warranty coverage, and prior work history. With Step‑Up Approvals and Policy Packs, sensitive payments trigger adaptive 2FA and follow least‑privilege rules. Upon approval, ProofHold Escrow sets aside funds and shows vendors exactly what’s needed for release. When vendors upload completion artifacts, FixFlow validates requirements, runs Compliance Gate checks, and triggers Instant PushPay or same‑day ACH, generating receipts and updating the ledger automatically. If something changes, ChangeGuard flags scope variance and routes for one‑tap re‑approval. Measured impact Early users report a 52% reduction in payout cycle time, a 37% decrease in inbound payment status inquiries, and a 100% completion rate on compliance documents prior to release. Smart Splits eliminated off‑platform reimbursements and reduced end‑of‑month reconciliation work by hours per job. Availability and pricing The payments and compliance suite is available today to all FixFlow customers. Instant PushPay supports real‑time debit push and same‑day ACH with transparent fees; ProofHold Escrow and Compliance Gate are included in Growth and above plans. Existing customers can enable the features in Settings with one click; new customers can start a free trial and invite vendors in minutes. Security and audit readiness Access Ledger provides a tamper‑evident audit of every view, reveal, role change, approval, and payout with exportable reports for boards, insurers, and auditors. Role Blueprints, Timeboxed Access, and Contact Shield minimize data exposure while keeping workflows friction‑light for busy teams. About FixFlow FixFlow is the modern maintenance operations platform that centralizes intake, scheduling, communications, approvals, and payouts for independent landlords and small property managers. By pairing AI‑assisted diagnosis with policy‑based scheduling and payments, FixFlow cuts response times, prevents double‑bookings, and pays vendors faster—without sacrificing control. Media contact Alex Romero, Communications Lead press@fixflow.app +1‑415‑555‑0137 https://fixflow.app/press

P

FixFlow Debuts Turnover Playbooks to Compress Vacancy Days Across 1–200 Units

Imagined Press Article

San Francisco, CA — September 9, 2025 — FixFlow today announced Turnover Playbooks, an adaptive automation suite that sequences cleaners, painters, locksmiths, and techs with realistic dependencies, access logistics, and live reflow—so make‑readies finish on time and vacancy days shrink. For small property teams, the sprint from move‑out to move‑in is where schedules break, vendors no‑show, and hidden dependencies derail plans. Turnover Playbooks bring an operator’s eye to the chaos with tools that plan for cure times, building rules, lead times, and quiet hours—then re‑sequence automatically when anything shifts. What’s inside • Smart Sequencer: Automatically orders turnover tasks with built‑in cure times, building rules, vendor lead times, and quiet hours. When a step moves, all downstream appointments reflow in real time. • Gap Squeeze: Identifies idle gaps and safely overlaps compatible steps—like locksmith during paint dry time—using historical punctuality and travel data. Risk levels and one‑tap rollback keep decisions safe. • Access AutoPass: Generates timeboxed access credentials and logistics per step—smart lock codes, lockbox PINs, elevator bookings, parking passes—and delivers them via SMS to prevent lockouts. • StageKit: Stages supplies and materials ahead of each step by pulling unit‑specific checklists and preferred vendors. Auto‑orders consumables and schedules deliveries so work starts without delays. • FlexSwap: If a vendor declines, fails to confirm, or risks lateness, the playbook auto‑swaps to pre‑vetted backups within budget and compliance rules. • Branch Paths: Cleaners flag heavy soil and a deep‑clean branch inserts; painters detect moisture and remediation steps add with required cure time. SLAs, budgets, and notifications adjust automatically. • Turn Meter: A live countdown to move‑in shows critical path, slack by step, and what’s blocking progress. Test accelerators—add a crew, extend hours, priority materials—and see time saved before you commit. • PauseGuard: SLA‑fair pauses for reasons outside your control—waiting on tenant access, HOA approvals, parts on order, weather holds—resume automatically when conditions clear. Who benefits • Turnover Tactician Tessa: Coordinate multiple trades to compress vacancy days with to‑the‑minute precision. • Data‑Driven Operator Dana: Model scenarios, compare outcomes, and apply the winning plan with one tap. • Short‑Stay Steward Skylar: Automate cleanings and quick repairs between checkout and check‑in windows to protect reviews and revenue. • HOA Liaison Liam: Keep boards and residents aligned with transparent schedules and updates. Quotes “Vacancy days are NOI killers, and most misses come from hidden dependencies and access friction,” said Jordan Alvarez, COO of FixFlow. “Turnover Playbooks make your plan realistic on day one and resilient when reality strikes on day two.” “I used to live in a color‑coded spreadsheet,” said Tessa Martinez, a make‑ready coordinator managing three mid‑rise buildings. “Smart Sequencer and Gap Squeeze gave me back hours a week and two full days per month on the calendar. Locksmith during paint dry time is my new favorite overlap.” “Access AutoPass eliminated 90% of our lockouts,” said Skylar Brooks, a short‑stay operator across eight condos. “Vendors get codes and parking instructions by SMS right when they need them, and Turn Meter keeps my team laser‑focused on the critical path.” How it works Start with a One‑Click Playbook template for typical turns, then customize tasks, dependencies, SLAs, budget caps, and preferred vendors. StageKit ensures materials arrive before the first crew rolls. When a step completes early or slips, Smart Sequencer and Gap Squeeze reflow schedules and propose safe overlaps. If a vendor declines or risks lateness, FlexSwap swaps in a pre‑vetted backup and updates access windows via Access AutoPass automatically. Branch Paths adapt the plan to field findings without blowing up budgets or timelines, while Turn Meter and Live Badges keep stakeholders aligned with the same live countdown. Measured impact Early customers saw a 23% reduction in vacancy days, a 34% drop in coordination phone calls, and 80% fewer lockouts during turns. Teams hit more SLA targets with PauseGuard protecting the clock for delays outside their control and Smart Rescheduler letting tenants pick new windows instantly when plans change. Availability Turnover Playbooks are available today for all FixFlow accounts. Existing customers can enable Playbooks in Settings and choose from starter templates; new customers can start a free trial and import properties in minutes. Role Blueprints and Policy Packs streamline onboarding for assistants, vendors, and short‑term cleaners with least‑privilege defaults. About FixFlow FixFlow is the modern maintenance operations platform for independent landlords and small property managers. From AI‑powered intake to auto‑scheduling, vendor confirmations, and payments, FixFlow unifies maintenance so small teams move faster without adding headcount. Media contact Alex Romero, Communications Lead press@fixflow.app +1‑415‑555‑0137 https://fixflow.app/press

P

FixFlow Introduces SLA Intelligence Suite to Hit Response and Resolution Targets With Confidence

Imagined Press Article

San Francisco, CA — September 9, 2025 — FixFlow today launched the SLA Intelligence Suite, a set of real‑time tools that help small property teams hit response and resolution targets without the stress. With live heatmaps, role‑aware nudges, adaptive targets, and inspector‑ready evidence, independent landlords and small property managers finally get the visibility and control that large enterprises take for granted. The SLA Intelligence Suite ties approvals and scheduling to timers that reflect your reality—not wishful thinking. Teams see brewing risks before breaches happen, rebalance workloads with one click, and generate airtight evidence packets for boards, insurers, and city inspectors in seconds. What’s included • SLA Heatmap: A live, color‑coded view of response and resolution timers across properties, issue types, and assignees. Spot bottlenecks and drill into blockers instantly. • SLA Escalator: Connect approvals to SLAs with live timers. Auto‑send reminder nudges, escalate to alternates as deadlines near, and auto‑approve under defined emergency rules. • Nudge Ladder: Role‑aware reminders that escalate smartly—DMs for techs, SMS for vendors, email for owners—respecting quiet hours and time zones. One‑tap actions like confirm access or add ETA turn nudges into progress. • SLA AutoTune: Data‑driven targets that adapt by property and issue type using your historical cycle times, vendor lead times, building rules, and seasonality. • Inspector Pack: One‑click export of time‑stamped SLA evidence—start/stop events, pause reasons, communications, approvals, and completion proof. Branded PDF/CSV packets satisfy auditors without screenshot hunts. • Live Badges: Embeddable countdowns that travel with the job—inside work orders, vendor SMS links, and the tenant FixTrack page—so everyone sees the same clock and stage. • PauseGuard: Fair, audit‑ready SLA pauses when delays are outside your control—waiting on access, parts on order, weather holds—auto‑resuming when conditions clear. Why it matters Hitting SLAs in small shops is less about trying harder and more about seeing sooner. With real‑time risk visualization, automated nudges that meet people where they are, and targets tuned to your actual constraints, teams spend fewer minutes firefighting and more time finishing work. The result: higher tenant satisfaction, fewer fines or rent concessions, and calmer workdays. Quotes “Small operators shouldn’t have to guess whether they’re on track,” said Luis Ortega, Head of Product at FixFlow. “SLA Heatmap and Nudge Ladder make risk visible hours before a miss, and AutoTune sets targets teams can actually hit. Add Inspector Pack, and you’re always audit‑ready.” “Our compliance posture has never been stronger,” said Casey Nguyen, a risk‑conscious manager overseeing 90 units. “PauseGuard preserves fairness, Inspector Pack ends the evidence scramble, and Live Badges keep vendors and tenants aligned on the same clock.” “With AutoTune, our targets finally match vendor lead times and building rules,” said David Kim, a Portfolio Ops Manager for a 160‑unit scattered site. “We rebalance assignments from the heatmap and use Nudge Ladder to nudge the right person at the right moment. SLA misses are down 31% month over month.” How it works The moment a request is approved, SLA Escalator starts response and resolution timers and pairs them with your policy rules. Nudge Ladder sequences reminders to the right roles on the right channels, each with one‑tap actions that shave minutes. SLA Heatmap surfaces brewing breaches by property and issue type, enabling instant drill‑downs and reassignment. When reality deviates—waiting on parts, tenant not home—PauseGuard pauses timers with clear reasons and auto‑resumes when conditions clear. At any point, Inspector Pack produces an exportable packet with a full event timeline, communications, approvals, and completion photos. Measured impact Pilot customers reported a 28% reduction in SLA breaches, a 35% drop in status‑check calls from tenants, and a 42% improvement in on‑time vendor arrivals when Live Badges and Nudge Ladder were enabled together. Managers gained back hours each week previously lost to manual chases and screenshot hunts. Availability The SLA Intelligence Suite is available today for all FixFlow plans. Enable components individually or as a bundle from Settings. Live Badges and Inspector Pack are available to vendors and tenants without requiring logins, keeping collaboration fast and secure. About FixFlow FixFlow is the modern maintenance operations platform that centralizes intake, scheduling, communications, approvals, and payouts for independent landlords and small property managers. With AI intake, auto‑scheduling, and SLA‑driven workflows, FixFlow helps small teams move at enterprise speed without enterprise overhead. Media contact Alex Romero, Communications Lead press@fixflow.app +1‑415‑555‑0137 https://fixflow.app/press

Want More Amazing Product Ideas?

Subscribe to receive a fresh, AI-generated product idea in your inbox every day. It's completely free, and you might just discover your next big thing!

Product team collaborating

Transform ideas into products

Full.CX effortlessly brings product visions to life.

This product was entirely generated using our AI and advanced algorithms. When you upgrade, you'll gain access to detailed product requirements, user personas, and feature specifications just like what you see below.