Property maintenance

FixFlow

Faster Fixes, Stronger Trust

FixFlow automates maintenance intake, photo-first tenant triage, approvals, technician scheduling, and tenant communication for independent landlords and small property managers overseeing 1–150 units, cutting response times 60% and lowering emergency repair costs 25% while saving managers three to five hours weekly per property by prioritizing issues and coordinating vendors.

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 to deliver fast, transparent, and cost-effective maintenance, protecting homes and boosting tenant satisfaction at scale.
Long Term Goal
Within 5 years, power maintenance for 50,000 independent landlords, cut emergency repair incidents by 20%, and reclaim 100,000 manager-hours weekly.
Impact
FixFlow enables independent landlords and small property managers (1–150 units) to cut maintenance response times 60%, lower emergency repair costs 25%, and save managers 3–5 hours weekly per property by centralizing intake and automating triage and approvals.

Problem & Solution

Problem Statement
Independent landlords and small property managers overseeing 1–150 units struggle with slow, fragmented repair intake and poor photo documentation, relying on messaging apps and spreadsheets that lack prioritization, approvals, vendor scheduling, or audit trails.
Solution Overview
Centralize maintenance intake and automate triage: photo-first tenant submissions apply priority rules that trigger one-tap repair approvals and calendar-synced vendor scheduling, cutting back-and-forth and stopping small leaks from becoming costly emergencies.

Details & Audience

Description
FixFlow automates maintenance intake, technician scheduling, approvals, and tenant communication for small rental portfolios. It serves independent landlords and small property managers overseeing 1–150 units. FixFlow cuts response times by 60%, reduces emergency repair costs 25%, and saves managers 3–5 hours weekly per property through prioritized triage and vendor coordination. Its photo-first tenant triage with one-tap repair acceptance delivers clear diagnostics and instant approvals.
Target Audience
Independent landlords and small property managers (25–60) needing faster, cheaper repairs who juggle maintenance manually.
Inspiration
At midnight a tenant sent a blurred sink photo, water pooling on linoleum; my friend’s phone vibrated with three VoIP calls while he cross-checked spreadsheets and scrolled an unsure vendor list. A small drip became a ceiling stain by morning. That frantic hour inspired FixFlow: photo-first intake, instant triage, one-tap approvals and calendar-synced vendor scheduling so small landlords never lose time or tenant trust.

User Personas

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

R

Remote Operator Riley

- Manages 6–35 long-term rentals across two metros - Age 32–48; full-time professional; tech-comfortable - Uses Buildium/Google Sheets; leans on mobile - Monthly maintenance spend $2k–$12k; strict budgets

Background

Scaled rentals while holding a demanding day job, juggling texts with multiple vendors. A costly after-hours flood exposed the need for standardized intake and automated approvals across distances.

Needs & Pain Points

Needs

1. Centralize photo-first intake across all properties. 2. Auto-triage and route by priority and location. 3. Approve estimates asynchronously across time zones.

Pain Points

1. After-hours emergencies due to unclear triage. 2. Vendor no-shows from scheduling misfires. 3. Tenant ping-pong across SMS and email.

Psychographics

- Obsessed with responsiveness without being on-call - Values automation that’s visibly accountable - Data over anecdotes; evidence beats intuition - Trusts tools that reduce dependence on people

Channels

1. BiggerPockets forum 2. LinkedIn landlord groups 3. YouTube landlord tips 4. Google Search vendor sourcing 5. Facebook local rentals

T

Turnover Tactician Tessa

- Oversees 80–150 beds across clustered houses - Age 27–40; operations-focused; on-site often - Peaks July–September; weekend work common - Tools: Google Calendar, Slack, Excel; limited staff

Background

Cut teeth orchestrating back-to-back move-outs and make-readies where one missed part derails dozens of units. Built color-coded spreadsheets but needs something faster and fail-proof during peak.

Needs & Pain Points

Needs

1. Batch-triage issues by building and unit. 2. Calendar-based vendor waves for turns. 3. Real-time access windows from tenants.

Pain Points

1. Missed move-in dates from scheduling slips. 2. Duplicate tickets for the same unit. 3. Tenants unresponsive for key access.

Psychographics

- Lives for deadlines and throughput - Craves visual clarity under pressure - Prefers batch over one-off tasks - Measures success in on-time turn keys

Channels

1. Facebook student housing groups 2. LinkedIn property ops 3. YouTube make-ready workflows 4. Google Workspace Marketplace 5. Reddit r/Landlord

C

Compliance Keeper Cam

- Manages 50–90 mixed LIHTC/Section 8 units - Age 35–55; compliance-first mindset - Uses Yardi Breeze or AppFolio; heavy email - Faces city/HQS inspections quarterly

Background

A failed reinspection once triggered penalties and tenant frustration. Since then, built meticulous checklists and insists on timestamped, photo-backed records for every work order.

Needs & Pain Points

Needs

1. Timestamped, photo-backed completion records. 2. SLA timers with escalation rules. 3. Approval thresholds tied to unit programs.

Pain Points

1. Missing photo proof during audits. 2. Expired SLAs from manual tracking. 3. Unclear approval trail for subsidies.

Psychographics

- Documentation is protection, not bureaucracy - Zero tolerance for SLA breaches - Transparency builds stakeholder trust - Prefers repeatable, auditable routines

Channels

1. HUD/LIHTC forums 2. LinkedIn compliance groups 3. Gov delivery newsletters 4. YouTube inspection prep 5. Google Search policy updates

D

Data-Driven Devon

- Controls 15–80 scattered-site units - Age 30–50; finance/analytics background - Uses Buildium + spreadsheets + Looker/BI - Quarterly cost targets; vendor scorecards

Background

After spotting cost creep in spreadsheets, built custom dashboards but struggled with messy inputs. Now prioritizes structured data capture at intake to enable clean reporting.

Needs & Pain Points

Needs

1. Consistent issue categorization and severities. 2. Vendor performance dashboards and SLAs. 3. Estimate comparisons against historical averages.

Pain Points

1. Dirty data from freeform requests. 2. Inconsistent vendor quality over time. 3. Surprise overages after job completion.

Psychographics

- If it’s measured, it improves - Optimizes processes like financial models - Skeptical of fluff, loves hard metrics - Prefers levers over heroics

Channels

1. LinkedIn analytics communities 2. BiggerPockets data threads 3. YouTube spreadsheet automation 4. Google Search benchmarks 5. Substack real estate ops

S

Sustainability Saver Sage

- Runs 10–50 mid-tier units, older stock - Age 28–45; sustainability-inclined; rebate-aware - Uses smart sensors where possible - Utility costs a top-three expense

Background

Inherited aging buildings with leaky fixtures and drafty windows. Grants and rebates sparked a shift toward preventative maintenance and evidence-backed retrofits.

Needs & Pain Points

Needs

1. Flag repeat issues indicating systemic problems. 2. Schedule seasonal tune-ups automatically. 3. Track savings from avoided emergencies.

Pain Points

1. Hidden water leaks inflating bills. 2. Repeat HVAC failures each season. 3. Reactive repairs killing rebates.

Psychographics

- Prevention beats repair every time - Frugal, but invests in payback - Motivated by footprint and savings - Loves trendlines and early warnings

Channels

1. YouTube energy savings 2. LinkedIn proptech sustainability 3. Utility rebate portals 4. Reddit r/PropertyManagement 5. Google Search leak detection

G

Guest-Ready Gabe

- Manages 10–40 listings across Airbnb/VRBO - Age 26–42; hospitality mindset - Operates with cleaners plus on-call vendors - Lives by review scores and response times

Background

A string of 4-star reviews due to slow repairs threatened Superhost status. Formalized maintenance intake and tightened coordination between cleaners, vendors, and guests.

Needs & Pain Points

Needs

1. Instant triage from cleaner photos. 2. Same-day vendor scheduling windows. 3. Proactive guest updates with ETAs.

Pain Points

1. Canceled stays from unresolved issues. 2. Vendor delays between turnovers. 3. Disjointed messaging across apps.

Psychographics

- Speed is service; delays cost bookings - Reputation is the currency - Prefers checklists and playbooks - Mobile-first, always on the move

Channels

1. Facebook STR host groups 2. Airbnb Community Center 3. YouTube hosting tips 4. Reddit r/AirBnBHosts 5. Google Search local vendors

Product Features

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

Guided Retake

Prompts tenants with instant, on-screen shot guidance when an upload is unclear—wide room context, close-up of source, brand/plate, and simple checks (e.g., paper towel under pipe). Ensures diagnostic-quality photos in one pass, cutting back-and-forth, misdiagnosis, and unnecessary dispatches.

Requirements

Instant Photo Quality Check
"As a tenant, I want the app to tell me instantly if my photo is clear and correctly framed so that I can capture usable images without guesswork."
Description

Real-time analysis of captured photos to detect blur, low light, glare, poor focus, incorrect distance, and off-subject framing, returning immediate pass/fail guidance with specific, actionable tips. Runs on-device when possible for sub-second feedback, with server-side fallback on older devices. Exposes a quality score and failure reasons to the tenant UI, logs metrics to the case record, and warns before submission if minimum thresholds are not met. Integrates with FixFlow’s intake flow so that high-quality images accompany the ticket, reducing back-and-forth and misdiagnoses, and enabling technicians to pre-diagnose without a site visit.

Acceptance Criteria
On-Device Sub-Second Analysis
Given a tenant captures a photo on a device that supports on-device analysis When the photo shutter completes Then quality analysis runs locally without any network request and completes within ≤500 ms p95 and ≤1000 ms p99 And the result includes: qualityScore (0–100), passFail (boolean), and detectedIssues among [blur, low_light, glare, poor_focus, incorrect_distance, off_subject] And the analysis path is recorded as on_device
Server-Side Fallback on Unsupported or Slow Devices
Given a tenant captures a photo on a device without on-device capability OR the on-device analysis does not start or exceeds 800 ms And network connectivity is available When the photo is captured Then the client sends the image to the server for analysis once, with retry disabled And a response is returned within ≤2000 ms p95 and ≤3000 ms p99 And the result schema matches on-device (qualityScore, passFail, detectedIssues) with analysis path server Given the same preconditions but network connectivity is unavailable When the photo is captured Then the UI shows an "Analysis unavailable offline" notice within 200 ms and queues analysis for when connectivity returns And the photo is marked analysisState: unknown and will trigger a submission warning if unchanged
Actionable Pass/Fail Guidance and Tips
Given an analyzed photo has passFail = false with one or more detectedIssues When guidance is shown to the tenant Then the UI displays a clear Fail state and 1–3 actionable tips mapped to the top detectedIssues And each tip is ≤80 characters, at Flesch-Kincaid grade ≤6, and contains a concrete action (e.g., Turn on room lights, Move closer until fixture fills most of frame, Tilt to reduce glare) And after a retake, the guidance updates within 300 ms to reflect new detectedIssues and score
Quality Score and Failure Reasons in Tenant UI
Given analysis completes (on-device or server) When the tenant views the photo review card Then a numeric qualityScore (0–100) is displayed with a status label: Good (≥threshold) or Needs retake (<threshold) And the default threshold is 70, configurable per account (50–90) And up to 3 failure reason chips are shown using the detectedIssues taxonomy And accessibility names for score and reasons are provided for screen readers
Submission Warning for Below-Threshold Photos
Given at least one required photo has qualityScore < threshold OR analysisState = unknown When the tenant taps Submit Then a blocking modal lists the specific photos failing or unknown with their scores/reasons And the modal offers Retake (navigates to camera for that photo) and Submit anyway (requires explicit confirmation) And if Submit anyway is chosen, a low_photo_quality flag is stored on the case with the photo IDs And if all required photos meet threshold, no modal appears and submission proceeds immediately
Per-Photo Metrics Logged to Case Record
Given any photo is analyzed When the intake session is saved Then the case record stores per-photo metrics: photoId, analysisPath (on_device|server|unknown), analysisDurationMs, timestamp, qualityScore, passFail, detectedIssues[], retakeCount, deviceModel, osVersion And the metrics are immutable snapshots tied to the specific photo version And these fields are available in the case timeline and export API
High-Quality Images Attached to Intake Ticket
Given the tenant completes the intake flow When the ticket is created Then for each required photo prompt, the latest photo meeting threshold is attached; if none meet threshold, the highest-scoring photo is attached and flagged low_photo_quality And each attachment includes metadata: qualityScore, passFail, detectedIssues, analysisPath And rejected earlier retakes are not attached by default but remain retrievable in the case history
Guided Shot List
"As a tenant submitting an issue, I want a short, guided list of required shots tailored to my problem so that maintenance can diagnose and schedule without follow-up."
Description

Dynamic, issue-type–aware shot sequencing that prompts tenants to capture a standard set of photos: wide-room context, close-up of the suspected source, brand/model plate, and a simple diagnostic check (e.g., paper towel under pipe, breaker position, thermostat display). Provides step-by-step instructions, examples, and progress indicators, enforces required shots with optional retakes, and auto-labels and orders images for technician review. Templates are mapped to common maintenance categories and can be configured by property managers. The captured set is attached to the work order with metadata, improving first-time fix rates and reducing unnecessary dispatches.

Acceptance Criteria
Template Selection and Sequencing by Issue Type
Given a tenant selects issue type "Plumbing > Leak under sink" during maintenance intake When the Guided Shot List starts Then the "Plumbing-Leak" template with exactly 4 steps (1. Wide room, 2. Close-up source, 3. Brand/Model plate, 4. Diagnostic check: paper towel under pipe) is loaded and displayed in that order Given a tenant selects issue type "HVAC > No cooling" When the Guided Shot List starts Then the "HVAC-NoCooling" template with exactly 4 steps (1. Thermostat display, 2. Air handler/furnace wide shot, 3. Brand/Model plate, 4. Breaker position) is loaded and displayed in that order Given a tenant selects an issue type with no explicit mapping When the Guided Shot List starts Then the system loads the Default template with the 4 standard steps (Wide room, Close-up source, Brand/Model plate, Diagnostic check)
Diagnostic Check Prompting and Capture
Given the active template includes a diagnostic check step (e.g., paper towel under pipe) When the user reaches that step Then the instruction explicitly states the check to perform and requires a photo to be taken before proceeding Given the issue type is "HVAC > No cooling" When the user reaches the breaker step Then the UI instructs to show the breaker position in the photo and blocks progression until a photo is captured for that step Given the issue type is "HVAC > No cooling" When the user reaches the thermostat step Then the UI instructs to capture the thermostat display and blocks progression until a photo is captured for that step
Required Shots Enforcement and Retakes
Given one or more required steps are incomplete When the user attempts to submit the shot list Then submission is blocked and a message identifies the missing required steps Given a photo has been taken for any step When the user taps Retake Then the previous photo is discarded, the step retains its label, and the new photo replaces it in the same sequence position Given all required steps are complete When the user submits Then the submission succeeds without requiring any additional optional shots
Guided Instructions and Progress
Given any step in the Guided Shot List is displayed When the screen renders Then it shows step-specific instruction text and an example/reference image thumbnail Given a user starts the sequence When they advance through steps Then a progress indicator shows "Step X of Y" and updates accurately on next/back navigation and retakes Given the final step is completed When the user reaches the end of the sequence Then the UI shows 100% completion and enables submission
Auto-Labeling and Ordered Gallery for Technicians
Given a tenant submits the Guided Shot List When the technician opens the associated work order Then the photos appear in a gallery ordered by template sequence and labeled with step names (e.g., "1. Wide room", "2. Close-up source", "3. Brand/Model plate", "4. Diagnostic check") Given labels and order are generated automatically When the work order is viewed in web and mobile technician views Then the same labels and order are consistently displayed across views
Manager-Configurable Templates and Category Mapping
Given a property manager has template management permissions When they create or edit a template Then they can define step names, instructions, and example images and save/publish the template Given a manager maps a template to one or more maintenance categories When a tenant selects any of those categories Then the mapped template is used for the Guided Shot List Given a manager updates a template When new intakes begin after the update Then the new version is applied, and in-progress or submitted intakes continue using the prior version Given no template is mapped to a selected category When an intake begins Then the Default template is applied
Work Order Attachment With Metadata
Given a tenant submits a completed Guided Shot List When the work order is created/updated Then all captured photos are attached to the work order with shot-type label, sequence index, and capture timestamp metadata Given the work order is opened by a manager or technician When they view the attachments Then each photo’s metadata (label and sequence) is visible in the UI or available via API response Given submission completes successfully When the tenant or manager opens the work order within 15 seconds Then the attached photos and their labels are present
AR Framing Overlays
"As a tenant, I want on-screen outlines and cues that show exactly where and how to aim the camera so that I can take the right shot quickly."
Description

On-screen visual aids and optional haptic/audio cues that guide the tenant to frame shots correctly, including outlines for common targets (brand plate, pipe joint, outlet), horizon leveling, subject centering, and distance indicators. Offers low-light and glare suggestions (enable flash, change angle) and confirms alignment before capture. Works in web and native capture flows with graceful degradation on devices without advanced sensors. Improves shot accuracy and consistency across tenants, decreasing retake cycles and enabling clearer remote diagnosis.

Acceptance Criteria
Brand Plate Overlay Alignment and Confirmation
- Given the tenant selects "Brand plate" guidance, When the camera opens, Then a rectangular overlay and a center grid are displayed. - Given the overlay is active, When the detected plate centroid is within 5% of the frame center, the roll angle is within ±3°, and the plate fills 15–30% of frame height (±10% tolerance), Then the overlay turns green and the Capture button enables. - Given alignment conditions are not met, When the tenant attempts to capture, Then the Capture button remains disabled and a textual hint is shown indicating the failing condition (Center, Level, or Distance). - Given haptic/audio cues are toggled on, When alignment first transitions to pass state, Then emit a single short haptic tap and a brief chime; When cues are off or unsupported, Then no haptic/audio is emitted. - Given alignment is in pass state, When it remains stable for 1 second, Then an "Aligned" check appears next to the Capture button.
Pipe Joint Close-Up Overlay and Distance Guidance
- Given the tenant selects "Pipe joint" guidance, When the camera opens, Then a circular overlay with a crosshair is displayed. - Given the overlay is active, When the joint’s detected circle overlaps the overlay within ±10% size and the centroid is within 5% of frame center, Then the Distance indicator evaluates the apparent size or depth and displays Good when the estimated range is 10–20 cm from the lens, enabling the Capture button. - Given the Distance indicator shows Too close or Too far, When the tenant adjusts distance, Then the indicator updates within 100 ms and the Capture button remains disabled until the state is Good. - Given specular highlights within the region of interest exceed 5% of pixels above 98% luminance, Then show the hint "Tilt to reduce glare" with an animated arrow. - Given average scene luminance is below the low‑light threshold, Then show the hint "Low light: increase light or enable flash" and display a Flash toggle if the device supports flash. - Given a hint is shown, When the triggering condition resolves for 500 ms, Then the hint auto‑dismisses.
Horizon Leveling and Subject Centering Enforcement
- Given the camera is open with any target, When the device roll angle magnitude is >2°, Then a level bar displays in red with an arrow indicating the correction direction. - Given the tenant adjusts the device, When the roll angle becomes within ±2° and remains for 1 second, Then the level bar turns green. - Given the centering grid is visible, When the subject’s bounding box center is within 5% of the frame center, Then centering is marked as satisfied; otherwise, show the hint "Center subject". - Given either leveling or centering is not satisfied, Then the Capture button remains disabled; When both are satisfied (and any target‑specific distance rule passes), Then the Capture button enables.
Alignment Confirmation Gate Before Capture
- Given a supported target is selected, When all required alignment checks for that target (Center, Level, Distance) pass, Then green check icons appear for each check within 100 ms. - Given checks are visible, When alignment remains stable for 1 second, Then the Capture button remains enabled; When alignment falls out of tolerance before capture, Then the button disables and checks revert to neutral. - Given the tenant taps Capture while all checks are green, Then the photo is captured and the pass state (Center/Level/Distance) and target type are saved in the photo metadata. - Given alignment checks are failing, When the tenant taps Capture, Then no photo is taken and the failing checks are highlighted with brief vibration/audio only if cues are enabled.
Web and Native Graceful Degradation
- Given the device or browser lacks motion or depth sensors, When the capture screen loads, Then only 2D overlays render and distance estimation uses size heuristics without errors or crashes. - Given the device lacks haptic or audio support, When cues are enabled in settings, Then only visual cues render and the unavailable cue toggles are hidden or disabled with a tooltip. - Given any supported browser (latest two major versions of Chrome, Safari, Edge, Firefox) on mobile or desktop, When the capture screen is active, Then overlays update at ≥24 fps and user interactions remain responsive (<100 ms tap‑to‑UI feedback). - Given camera permission is blocked or denied, When the tenant opens capture, Then a permission guidance screen is shown with a Retry button; After permission is granted, Then overlays initialize successfully. - Given overlay frame rate drops below 15 fps for more than 2 seconds, Then the system switches to Reduced Guidance Mode (static overlays, no live analysis) and displays a non‑blocking toast indicating the mode change.
Low-Light and Glare Suggestion Behavior
- Given the camera preview is active, When average scene luminance is below the low‑light threshold or exposure/ISO exceed device thresholds, Then show a "Low light" banner with guidance and a Flash toggle if supported. - Given specular highlight area within the target region exceeds 5% of pixels above 98% luminance, Then show a "Reduce glare: change angle" banner with an animated tilt indicator. - Given a suggestion banner is displayed, Then it does not obstruct the overlay target area or the Capture button and is screen‑reader accessible. - Given the triggering condition resolves for 1 second, Then the banner hides automatically; When the condition recurs, Then the banner reappears. - Given flash is enabled from the banner, When the tenant captures a photo, Then flash fires and the setting persists for the current capture session only.
Haptic and Audio Cues Preferences and Accessibility
- Given toggles for Haptic feedback and Audio cues are present in the capture UI, When disabled, Then no haptic or audio feedback is produced; When enabled and the device supports them, Then a single short haptic tap and a brief chime play upon alignment pass. - Given the device is in silent or Do Not Disturb mode, When audio cues are enabled, Then no audio is played. - Given a screen reader is active, When guidance hints and state changes occur, Then they are announced via accessible labels and do not rely solely on color. - Given visual overlays are displayed, Then non‑text contrast for guides and indicators meets WCAG 2.1 AA (≥3:1) and text size honors system font scaling.
Offline Capture & Deferred Upload
"As a tenant with poor connectivity, I want my photos to save and upload automatically when my signal returns so that my request isn’t blocked."
Description

Reliable photo capture in poor connectivity scenarios by saving images locally with encryption, compressing and queuing them for background upload and automatic resume. Provides clear upload status in the tenant UI, prevents duplicate submissions, strips EXIF GPS if disabled by policy, and enforces size limits with adaptive compression. Ensures the intake can be completed without blocking on network, reducing drop-off and support load while preserving a complete, ordered photo set when connectivity returns.

Acceptance Criteria
No Connectivity at Capture Time
Given the device has no internet connectivity And the tenant is in the photo intake step When the tenant captures 1..N photos and taps "Finish" Then each photo is saved to encrypted local storage with metadata (timestamp, capture order index, content hash, policy flags) And each photo is added to an upload queue with status "Queued" And the app shows a "Submission queued" confirmation with the number of photos queued And the intake flow completes locally without attempting any network calls And no blocking spinner or error prevents the tenant from leaving the intake flow
Intermittent Connectivity During Multi-Photo Intake
Given connectivity drops during an ongoing intake with queued photos When connectivity returns Then uploads automatically resume within 10 seconds without user action And photos are uploaded in their original capture order using the stored order index And items already uploaded are not re-uploaded And partially processed queues continue from the next pending item And no photo is uploaded more than once
Background Upload and App Restart Resume
Given there are queued photos pending upload And the tenant backgrounds or force-quits the app When the app is relaunched or background execution is permitted by the OS Then the upload queue state persists and is restored And uploads continue in the background when permitted by the OS, otherwise resume on next foreground And failed uploads are retried up to 5 times with exponential backoff (max 120 seconds between retries) while online And once all items succeed, the queue is cleared and the UI shows "All uploaded"
Local Encryption and Policy-Based EXIF Handling
Given organization policy "Attach GPS to photos" is disabled When a photo is saved locally and later uploaded Then EXIF GPS/location fields are removed from both the locally stored file and the upload payload And non-location EXIF (e.g., orientation, timestamp) is preserved unless explicitly disallowed by policy And the local file is encrypted at rest using platform-secure storage such that it is unreadable via file system access outside the app context And when the policy is enabled, EXIF GPS/location fields are preserved in the upload payload
Adaptive Compression and Size Limits
Given the default per-image size limit is 3 MB (configurable) When a captured photo exceeds the limit Then the app applies adaptive compression to reduce size while maintaining at least 1280 px on the shorter edge and JPEG quality not below 65% And if the photo still exceeds the limit after compression, the tenant is prompted to retake or accept further compression explicitly And the total submission payload is capped at 30 MB (configurable); if the queue would exceed this, the tenant is prompted to select a subset or apply additional compression And compressed images visually render without corruption in the review UI prior to upload
Duplicate Submission Prevention
Given the tenant retries a submission or captures the same scene multiple times When photos are added to the queue or retried after a failure Then a content hash is computed per photo to detect duplicates within the same intake And duplicates are not enqueued or uploaded more than once And server requests are idempotent so that replayed uploads do not create duplicate records
Tenant UI: Clear, Actionable Upload Status
Given there are queued, uploading, uploaded, or failed photos When the tenant views the intake confirmation or gallery screen Then each photo displays a status: Queued, Uploading with progress %, Uploaded with timestamp, or Failed with a Retry action And an offline banner states "Offline — uploads will resume automatically" when no connectivity is detected And tapping Retry re-queues only failed items without affecting completed ones And the tenant can delete a queued/failed photo and capture a replacement, with capture order indexes updating accordingly
Privacy Guard & Metadata Stripping
"As a privacy-conscious tenant, I want sensitive details like faces or documents automatically blurred and metadata removed so that my information is protected."
Description

Automatic detection and redaction of sensitive content (faces, IDs, mail, computer screens) in photos prior to upload, plus removal of EXIF location and device identifiers per policy. Presents an inline preview so tenants can approve redactions, with clear consent copy. Applies consistent retention and access controls, and logs redaction actions to the audit trail. Protects tenant privacy and reduces liability while preserving diagnostic utility for maintenance teams.

Acceptance Criteria
Pre-Upload Redaction and No-Leakage Safeguard
Given a tenant captures or selects a photo within the maintenance flow When pre-upload privacy processing starts Then the image is processed entirely on-device and upload is blocked until processing completes And Then all detected faces, government IDs, mailing labels, and computer screens are covered with opaque, non-reversible masks And Then no unredacted image bytes, thumbnails, or previews are transmitted over the network
Inline Redaction Preview and Explicit Tenant Consent
Given a photo with one or more redacted regions When the redaction preview is presented Then each redacted area is visibly masked and cannot be revealed And Then clear consent copy (versioned) is displayed and the Continue/Upload action is disabled until the tenant explicitly agrees And Then the tenant can choose Retake or Remove instead of agreeing And Then the preview is accessible (labels, focus order, contrast, and screen-reader text meet WCAG 2.1 AA)
EXIF and Hidden Metadata Removal Compliance
Given any image selected for upload When the redaction pipeline completes Then all EXIF GPS tags, device identifiers (Make, Model, Serial/IMEI if present), timestamps to the second, lens/maker notes, and embedded thumbnails are removed And Then orientation is preserved via pixel rotation (not metadata) where needed And Then verification with a metadata inspector returns no GPS*, Make, Model, or thumbnail fields
Redaction Accuracy and Diagnostic Utility Preservation
Given a curated test set of at least 200 annotated images across the supported sensitive categories When the redaction model is evaluated Then redaction recall is >= 95% and precision is >= 90% on the test set And Then for tenant photos captured per Guided Retake prompts, required diagnostic subjects (wide-context area, failure close-up, brand/plate) remain visible and unmasked And Then if a sensitive region overlaps a required diagnostic subject, the tenant is prompted to retake with framing guidance instead of uploading an over-redacted image
Performance and Resilience of Redaction Pipeline
Given a 12 MP photo on a mid-tier target device profile When privacy processing runs Then total time to preview with redactions is <= 2.0 seconds and memory usage stays below 300 MB And Then if on-device processing fails or times out after 5 seconds, the app shows a retry prompt; upload of unredacted images is blocked And Then processing is resumable for multi-image batches and maintains order across 1–10 images
Audit Trail and Tamper-Evident Logging
Given a redacted image is uploaded with consent When an audit entry is written Then the entry includes timestamp, request/work-order ID, anonymized tenant ID, detection categories, mask count and bounding boxes, model version, consent text version, EXIF-stripping result, and processor/device profile And Then the audit store is append-only and verifiable (hash-chained), and attempted edits are rejected and logged And Then authorized staff can view audit entries per role; tenants can request their audit entries via the privacy portal within 7 days
Storage, Access Controls, and Retention Policy Enforcement
Given upload succeeds When the image is stored Then only the redacted variant is persisted; any original is kept in memory only and securely wiped within 1 second of redaction success or immediately on cancel And Then access is restricted to Maintenance Coordinator and assigned Technician roles; all other roles receive a 403 and the attempt is logged And Then redacted images are retained until 12 months after work-order closure, then are deleted via cryptographic erasure and removal from backups within 30 days And Then any exports or shares deliver the redacted image only and contain no EXIF or hidden metadata
Technician Review & Feedback Loop
"As a technician, I want to review standardized photo sets with quality scores and request retakes when needed so that I can diagnose accurately and avoid unnecessary dispatches."
Description

A technician console view that displays the guided photo set with labels, timestamps, and quality scores, allowing techs to mark sufficiency, request specific retakes, and leave structured feedback that feeds analytics. Captures outcome data (diagnosed remotely, dispatch avoided) to measure impact and continuously refine guidance templates and quality thresholds. Enables property managers to monitor compliance and performance via reports, improving first-time fix rates and reducing unnecessary site visits.

Acceptance Criteria
Technician Console Displays Guided Photo Set
Given a maintenance ticket containing a guided photo set, When a technician opens the Technician Console photo review, Then the console renders the photo grid with labels (shot type), timestamps (tenant local time), and quality scores (0–100) within 2.0 seconds at p90 for up to 20 photos. And photos are displayed in the template-defined order and persist that order on refresh. And missing required shots display a distinct "Missing" placeholder tile. And tapping/hovering a photo reveals EXIF capture time and original filename. And an overall case quality score (0–100) is shown and updates when shot-level statuses change.
Per-Shot and Case Sufficiency Rules
Given an open ticket photo review, When a technician marks a photo as Sufficient or Insufficient and selects a reason from the standard taxonomy, Then the shot badge updates immediately and the case quality score recalculates within 200 ms. And the ticket cannot be marked "Photo Set Sufficient" unless each category has ≥1 Sufficient photo: Wide Context, Close-up Source, Brand/Plate, Simple Check. And attempting to mark the case Sufficient without meeting the rule blocks the action and lists missing categories. And all actions (marking, reasons) are timestamped and attributed in an immutable audit log.
Targeted Retake Request and Tenant Notification
Given one or more required shots are Missing or marked Insufficient, When a technician requests a retake and selects ≥1 insufficiency reason plus an optional note (≤300 chars), Then the system creates a Retake Task linked to the original shot slot and updates ticket state to "Awaiting Retake". And tenant push/SMS/email with personalized guidance is sent within 60 seconds, including deep-link to camera flow with the specific shot(s) preselected. When the tenant uploads, Then new photos auto-associate to the requested slot(s), supersede prior Insufficient photos, and notify the assigned technician. If no upload is received within 24 hours, Then an automatic reminder is sent and the task is flagged SLA-breached in the console.
Structured Technician Outcome Feedback Capture
Given a technician is closing the photo review, When they submit the Outcome form, Then required fields must be present and valid: Remote Diagnosis (Yes/No), Dispatch Avoided (Yes/No), Root Cause (picklist), Estimated Site-Visit Saved (minutes, integer 0–300); Notes optional (≤500 chars). And the Close action remains disabled until validation passes. And submission writes a normalized analytics event within 5 seconds and surfaces in the analytics store for reporting. And editing outcomes post-close requires Supervisor role and records a before/after audit entry with timestamp and user.
Manager Performance and Compliance Reporting
Given a property manager with Reporting permission, When they open the Maintenance Performance dashboard, Then they can view for a selectable date range (7/30/90 days): First-Time Fix Rate (%), Dispatches Avoided (%), Avg Retake Cycles per Ticket, Photo Review SLA Compliance (%), Technician Feedback Completion (%). And filters (property, technician, issue type, date range) apply within 2 seconds at p90 and update all widgets consistently. And Export to CSV delivers a file within 30 seconds for up to 10,000 rows; exported aggregates match on-screen values within ±0.5%. And selecting any metric enables drill-down to the contributing ticket list with links to ticket detail.
Analytics-Driven Guidance Refinement Loop
Given ≥50 outcome/feedback records for a guidance template in the last 14 days, When any shot category’s Insufficient rate exceeds 20%, Then the system generates a Guidance Update Suggestion listing the top 3 insufficiency reasons and proposed actions (e.g., add prompt, adjust quality threshold). And suggestions appear in the Admin Guidance Center within 24 hours with impact estimates and supporting charts. When an admin applies a suggestion, Then a new template version is created; new tickets use the new version while existing tickets retain their original version. And all template/threshold changes are versioned with author, timestamp, change summary, and are queryable for pre/post metric comparisons over matched 14-day windows.

Valve Finder

Uses computer vision to highlight likely shutoff valves or breakers directly on the submitted photo with arrows and labels, plus unit-specific tips from past tickets. Helps renters stop damage fast without phone support, reducing water/gas loss and emergency costs.

Requirements

Real-time Valve Detection & Overlay
"As a renter, I want the app to highlight likely shutoff valves on my photo so that I can stop leaks fast without waiting for phone support."
Description

Implement a computer-vision pipeline that detects likely shutoff valves and electrical breakers in tenant-submitted photos (and live camera previews) and renders on-image overlays with arrows and labels indicating where to turn/push/pull to stop flow or power. Support common fixtures (ball valves, gate valves, angle stops, hose bibs, gas quarter-turns, and breaker toggles), orientation detection, and rotation/lighting robustness. Color-code overlays by utility (water/gas/electric), allow tap-to-zoom and tap-to-expand mini-instructions, and ensure sub-2s end-to-end inference for a responsive UX. Integrate with FixFlow’s intake triage so overlays appear immediately after photo upload in web and mobile apps via a standardized inference API and cross-platform rendering component. Expected outcome: renters can quickly locate and operate the correct control, reducing damage, calls, and time-to-mitigation.

Acceptance Criteria
Sub-2s End-to-End Inference (Photo & Live Preview)
Given a tenant submits a photo via FixFlow web or mobile intake or opens live camera preview When the image/frame is processed through the inference API and rendered by the cross-platform component over LTE (>=10 Mbps) or Wi‑Fi Then for still photos, the first overlay appears within 2000 ms at P95 and <= 3000 ms at P99 end-to-end (client request to overlay drawn) And for live preview, overlay latency per frame is <= 150 ms at P95 with an effective overlay refresh rate >= 10 FPS when the device is stationary And a non-blocking loading indicator is shown if overlay time exceeds 500 ms
Detection Accuracy Across Supported Controls
Given a blinded validation set of >= 1200 annotated images covering ball valves, gate valves, angle stops, hose bibs, gas quarter-turns, and breaker toggles across varied environments When the model runs inference at the selected operating point Then per-class precision >= 90% and recall >= 85% And mean IoU of target overlays >= 0.50 And non-control false-positive rate <= 5% per image And when multiple candidates are present, the top-1 prediction is correct in >= 90% of cases
Operation Direction & Instruction Correctness
Given an identified control with known actuation type and orientation When overlay arrows and labels are rendered Then the indicated action (turn left/right, push, pull, flip) matches ground truth in >= 95% of validation samples And the overlay label includes utility and control type (e.g., Water • Ball Valve) localized to the app language And if action confidence < 0.70, show a cautious state (“Confirm direction”) with generic mitigation guidance instead of a definitive arrow
Rotation, Lighting, and Motion Robustness
Given input photos/frames with rotations of 0–360°, low light (10–30 lux), strong backlight, and moderate motion blur When inference and rendering are executed Then accuracy metrics from the Detection Accuracy criterion degrade by no more than 5 percentage points And automatic rotation correction normalizes overlays to the displayed orientation And overlays remain stable (no jitter > 10 px between adjacent preview frames when the device is held steady)
Utility Color-Coding & Accessibility Compliance
Given overlays for water, gas, and electric utilities When they are rendered on web and mobile Then water uses blue (#1E88E5), gas uses yellow (#FDD835), and electric uses red (#E53935) consistently And each overlay includes a distinct icon and text label so meaning is not color-dependent And all interactive elements meet 44x44 px minimum touch targets and support VoiceOver/TalkBack with descriptive names And color/contrast meets WCAG 2.1 AA for text and non-text elements
Tap-to-Zoom and Tap-to-Expand Mini-Instructions
Given a user taps an overlay target When the gesture is recognized Then the view zooms to the ROI within 150 ms and shows a mini-instruction card with 1–3 steps And a second tap or swipe-down dismisses the card and restores the prior zoom level And the interaction behaves consistently on iOS, Android, and desktop web without layout shift > 0.25 (CLS)
Intake Triage Integration & Standardized API Rendering
Given a tenant completes photo upload during FixFlow intake When the upload succeeds Then the client calls the standardized inference API (v1) with schema {image, deviceInfo, locationHint?, utilityHint?} And the response provides overlays [{id, utility, controlType, geometry, action, confidence, tips?}] that render via the shared component And if API error occurs or max-confidence < 0.40, a graceful fallback message is shown with a link to manual shutdown guidance; no app crashes And request/response IDs are correlated in logs for troubleshooting
Multi-Utility Classification & Contextual Instructions
"As a renter, I want tailored instructions for the specific utility shown so that I know exactly how to shut it off safely."
Description

Extend detection with classification of the utility type (water, gas, electrical breaker) and control state (open/closed/unknown), generating context-specific, step-by-step micro-instructions and safety warnings. Encode regional conventions (e.g., lever parallel/perpendicular for ball valves, clockwise vs. counterclockwise indicators on gate valves), include brief diagrams/animations, and surface hazard cautions for gas/electrical operations. Present the correct action (e.g., quarter-turn to perpendicular for gas shutoffs) alongside the overlay, localized units/terminology, and a one-tap acknowledgment before proceeding for higher-risk utilities. Integrate with the existing FixFlow guidance framework to reuse content blocks and analytics. Expected outcome: clearer, safer tenant actions and fewer escalations.

Acceptance Criteria
Photo Utility Classification: Water/Gas/Electrical
Given a tenant submits a photo via Valve Finder When the system processes the image Then it outputs utility_type ∈ {water, gas, electrical_breaker, unknown} with ≥95% top-1 accuracy on a balanced 1,000-image validation set And if classification confidence < 0.70, it returns utility_type=unknown, suppresses actionable guidance, and prompts for a clearer photo or escalation And the overlay renders an arrow to the most likely control and a label matching the utility_type within 2 seconds p95 on 4G
Control State Detection (Open/Closed/Unknown)
Given a detected control for a classified utility When the system analyzes control orientation/indicators Then it outputs control_state ∈ {open, closed, unknown} with ≥90% accuracy on an annotated 600-image validation set And for ball valves: lever parallel to pipe ⇒ control_state=open; perpendicular ⇒ control_state=closed And for gate valves: clockwise rotation closes the valve; guidance reflects this when control_state≠closed And for breakers: handle in OFF position ⇒ control_state=closed; ON ⇒ open (energized) And the overlay displays the inferred control_state label adjacent to the control
Context-Specific Micro-Instructions & Safety
Given utility_type and control_state are determined When generating tenant guidance Then the system produces 3–7 micro-steps tailored to the utility with the first actionable step being the correct action to stop flow/power And for ball valves: include “Turn lever 90° to perpendicular to the pipe to close” And for gate valves: include “Turn wheel clockwise until snug (do not overtighten)” And for breakers: include “Push handle firmly to OFF to de-energize” And each step is written at ≤8th-grade reading level and includes a visible step count/progress indicator And guidance displays a utility-specific safety warning before any risky action
Regional Conventions & Localization
Given the tenant’s locale and region are known When rendering labels, instructions, and cautions Then terminology is localized (e.g., en-GB: “consumer unit”/“fuse box”; en-US: “breaker panel”) and displayed consistently across UI and overlay And ball-valve lever conventions (parallel=open, perpendicular=closed) are applied consistently in all locales And any directional terms (clockwise/counterclockwise) reflect regional wording where applicable And localization tests for 5 target locales achieve 100% string coverage with no missing placeholders and no hard-coded English
Inline Diagrams/Animations Delivery
Given a guidance step requires a motion or orientation When rendering the step Then an inline diagram or animation illustrating the action is displayed beneath the step title And each media asset is ≤1 MB and loads within 2 seconds p95 on a 1.5 Mbps connection And each media asset includes alt text and supports play/pause/replay controls And if media fails to load, a static fallback diagram is shown within 1 second
High-Risk Acknowledgment Gate
Given utility_type ∈ {gas, electrical_breaker} When the tenant opens the guidance Then a blocking safety modal appears with “I Understand” and “Cancel” options And actionable steps remain disabled until “I Understand” is tapped And the acknowledgment event is logged with tenant_id, ticket_id, utility_type, locale, and timestamp And if “Cancel” is tapped, the system exits guidance and offers escalation options
Guidance Framework Integration & Analytics
Given guidance is generated for a maintenance ticket When storing and rendering content Then each instruction step and media item references an existing FixFlow guidance content_block_id for reuse And if unit-specific tips exist from past tickets, the top relevant tip is appended as the final step; otherwise default tips are used And analytics events overlay_shown, content_rendered, step_completed, ack_tapped, action_confirmed, tip_shown are emitted with ticket_id, unit_id, utility_type, control_state, model_confidence, and locale And ≥99% of emitted events are queryable in the analytics store within 5 minutes
Unit-Specific Tips & Past Ticket Recall
"As a property manager, I want renters to see unit-specific tips from past tickets so that they can find the shutoff faster without calling me."
Description

Build a retrieval layer that uses unit ID, property metadata, asset records, and resolved tickets to surface unit-specific guidance (e.g., “Main shutoff is behind the left kitchen kick plate”) next to the overlay. Rank tips by recency, confirmations from technicians, and manager-curated notes; cache per unit for low-latency delivery. Provide a lightweight admin interface for managers/techs to edit, pin, or retire tips and to associate tips with detected fixture types. Integrate with FixFlow’s knowledge store and permissions to restrict visibility to the relevant portfolio. Expected outcome: faster first-time success by leveraging historical knowledge unique to each unit.

Acceptance Criteria
Tip Retrieval by Unit Context at Intake
Given an authenticated user with permission to view unit U of property P and an intake includes unitId=U, property metadata, and a photo with detected fixture types F[] When the Valve Finder requests unit-specific tips for U with context {property, assets, resolved tickets, F[]} Then the Tips API returns up to 5 tips ordered by rank, each with fields {tipId, text, score, pinned, associatedFixtureTypes, provenanceType, provenanceIds, lastConfirmedAt?, lastConfirmedBy?} And tips not scoped to property P are excluded And API latency is <= 800 ms P95 on cold retrieval and <= 300 ms P95 when served from the per-unit cache
Tip Ranking by Recency and Confirmation Signals
Given a candidate set of tips with attributes {pinnedFlag, createdAt, lastConfirmedAt, confirmationCount, managerCuratedWeight} When the ranking function executes Then all pinned tips appear before non-pinned tips And non-pinned tips are ordered by a composite score prioritizing recency and technician confirmations; ties break by lastConfirmedAt descending And a technician-confirmed tip within 90 days ranks above any unconfirmed tip older than 180 days, all else equal
Per-Unit Cache Behavior and Invalidation
Given tips for unit U have been requested and cached with a default TTL of 15 minutes When the same query context is requested again before TTL expiry and no edits occurred Then the response is served from cache and includes a cacheHit=true indicator with P95 latency <= 300 ms And when any tip for U is created, edited, pinned, or retired by an authorized user Then the per-unit cache for U is invalidated within 30 seconds and subsequent requests reflect the change
Admin CRUD, Pin/Retire, and Audit Logging
Given a manager or technician with edit permissions for property P opens the admin interface When they create, edit, pin, or retire a tip linked to unit U or property P Then the change is saved to the knowledge store with an audit record {actorId, action, timestamp, before, after} And retired tips are excluded from tenant-facing retrieval responses within 30 seconds And pinned tips appear at the top of results until unpinned And users without permissions receive 403 and no modifications are made
Fixture-Type Association and CV Matching
Given tip T is associated with fixture types {t1..tn} or marked as general And the CV detector outputs fixtureTypesDetected with confidences When fixtureTypesDetected includes any type in {t1..tn} with confidence >= 0.6 Then T is eligible for display; otherwise T is suppressed unless T is explicitly pinned for unit U And if no fixture-specific tips qualify, up to 3 general unit/property tips may be returned
Portfolio Permissions and Data Isolation
Given user X belongs to Portfolio A with access to properties {P1..Pn} When X requests tips for unit U in property Px Then only tips scoped to Px or U are returned And requests for units in properties outside Portfolio A return 403 with no tip data And automated permission tests cover allow/deny matrices for manager, technician, and viewer roles
Provenance and Resolved Ticket Linkage
Given a tip originates from a resolved ticket, asset record, or manager note When the Tips API returns the tip Then the response includes provenanceType and sourceIds linking to the origin And lastConfirmedBy and lastConfirmedAt are populated when technician confirmation exists And in the admin UI, selecting the provenance link opens the source record (authorized users only)
Confidence-Driven Guidance & Escalation
"As a renter, I want the app to ask for more photos or connect me to help when it’s unsure so that I don’t waste time on the wrong control."
Description

Expose model confidence per detected control and drive the UX with thresholds: high confidence shows a single primary overlay; medium confidence shows top candidates with “request another photo” prompt; low confidence triggers a structured capture flow (different angles, flash guidance) and offers immediate escalation to phone/chat with on-call contacts. Log chosen paths, time-to-mitigation, and outcomes to inform model tuning. Integrate with FixFlow’s triage and on-call routing to prioritize emergencies and auto-create tickets when escalation occurs. Expected outcome: safer guidance with fewer false positives and faster handoff when automation is uncertain.

Acceptance Criteria
High-Confidence Single Overlay Flow
Given threshold_high is 0.85 and the top detected control has confidence ≥ 0.85 When the photo analysis completes Then exactly one primary overlay is displayed for the highest-confidence control And no other candidate overlays are shown And the displayed confidence value is shown to two decimal places And no "Request another photo" prompt is visible And a "Not correct?" action is available that, when tapped, switches the session to the Medium path and logs the reason
Medium-Confidence Multi-Candidate Flow
Given threshold_medium_low is 0.55 and threshold_high is 0.85 and the top detection confidence is ≥ 0.55 and < 0.85 When the photo analysis completes Then up to the top 3 candidate overlays are shown, ranked by confidence And each candidate displays its confidence to two decimal places And a prominent "Request another photo" CTA with capture tips is displayed And the user can select a candidate and is asked to confirm "Is this the valve? (Yes/No)" And selecting No keeps the user in Medium path and prompts for another photo And all selections, confirmations, and prompt uses are logged with timestamps
Low-Confidence Structured Capture and Escalation
Given no detections or all detection confidences are < 0.55 When the photo analysis completes Then a guided capture flow starts instructing the user to take 3 photos: front, left 45°, right 45° with flash guidance And detection is re-run after each photo set And if after 2 iterations no detection reaches ≥ 0.55, escalation options (Phone and Chat) are presented within 5 seconds And choosing to escalate auto-creates a FixFlow ticket containing unit, tenant, photo set, confidence summary, and session path And escalation start time is logged; all media and context are attached to the ticket
Confidence Exposure and Event Logging
Given any detection result When the session progresses through High, Medium, or Low paths Then the system logs: per-detection confidences, thresholds applied, chosen UX path label, user actions (select, retry, escalate), and timestamps (photo_received_at, overlay_shown_at, mitigation_started_at, mitigation_confirmed_at or escalation_started_at) And an outcome tag is recorded (mitigated, false_positive, escalated, no_access) And the log record is persisted to the analytics store within 5 seconds and retrievable by session ID
Emergency Triage Integration and Auto Ticketing
Given FixFlow triage marks the issue as Emergency or the user reports active leak/gas smell When escalation is triggered from the Valve Finder flow Then the system routes to the on-call contact per routing rules and priority Emergency And a FixFlow ticket is auto-created with unit, contact details, severity, SLA, media, confidence summary, and UX path And the on-call contact receives push/SMS notification within 60 seconds with acknowledgment tracking And tenant is shown status and ETA once acknowledged; ticket status updates are logged
Time-to-Mitigation Capture and Handoff SLA
Given any Valve Finder session When the user confirms mitigation (e.g., "Water stopped") or escalation occurs Then time_to_mitigation (photo_received_at → mitigation_confirmed_at) or time_to_handoff (escalation_pressed_at → first_notification_sent_at) is computed and stored And these metrics are available via reporting/export endpoints filtered by property, unit, and date range And for escalations, P95 time_to_handoff is ≤ 60 seconds across sessions in a 24-hour window
Feedback Loop & Annotation Tool for Managers/Techs
"As a technician, I want to confirm or correct highlighted valves on photos so that the system learns and future tenants get accurate guidance."
Description

Provide in-portal tools for technicians and managers to verify outcomes and annotate the correct valve/breaker on tenant photos. Capture renter confirmations (“This stopped the leak”) and corrections, create a review queue for ambiguous cases, and allow pinning of authoritative reference photos per unit. Store annotations as training data with consent and versioned datasets, and schedule periodic model updates with rollback toggles. Feed confirmed tips into the Unit-Specific Tips retrieval layer. Expected outcome: continuous accuracy improvement and property-tailored guidance with minimal extra effort.

Acceptance Criteria
Tech Annotates Correct Valve on Tenant Photo
Given a technician opens a tenant-submitted photo in the portal, When they select the Annotation Tool and draw a pointer/box and choose a label from the controlled list (e.g., Main Water Shutoff, Gas Valve, Breaker), Then the annotation is saved with coordinates, label, unit ID, ticket ID, annotator ID, and timestamp. Given the technician clicks Save, When the network is available, Then the annotation persists within 2 seconds and remains after page reload; When offline, Then the change is queued and syncs within 30 seconds of reconnection. Given an annotated photo, When another authorized user views the ticket, Then overlays render with labels and can be toggled on/off. Given a saved annotation, When the technician marks it as “Authoritative candidate,” Then it is added to the manager review queue.
Manager Reviews Ambiguous Annotations Queue
Given a model prediction is low-confidence (<0.60) or conflicting, or a user flags an item as ambiguous, When the system processes the ticket, Then an entry appears in the Review Queue within 1 minute with thumbnail, unit, ticket link, and suggested labels. Given a manager opens a queue item, When they choose Approve, Correct, Request Clarification, or Reject, Then the decision is recorded with actor, timestamp, and rationale and the annotation state updates accordingly. Given Approve, When applied, Then the annotation becomes Authoritative and exits the queue; Given Correct, Then a new version is created and the prior version is retained in history with a version ID. Given a queue item is older than 24 hours without action, When the daily job runs, Then an escalation notification is sent to the assigned property manager.
Renter Outcome Confirmation Capture
Given a tenant receives annotated guidance, When they tap the CTA, Then they can select one outcome: “Stopped the leak,” “Reduced but not stopped,” “No change,” or “Cannot access,” and optionally upload a follow-up photo. Given a tenant submits an outcome, When received, Then it is timestamped, linked to the ticket and unit, and visible to staff within 1 minute. Given the outcome is “Stopped the leak” or “Reduced but not stopped,” When processed, Then the associated annotation is marked Outcome-Verified and success metrics update for that label and unit. Given the tenant indicates “Location is wrong,” When they place a marker on the photo and submit, Then a proposed correction annotation is created and added to the manager review queue. Given the outcome is negative (“No change” or “Cannot access”), When recorded, Then the ticket is flagged for escalation and appears in the manager review queue.
Data Consent Capture and Enforcement
Given a tenant uploads media, When they reach the consent step, Then they are presented with an explicit opt-in control (default off) to allow photos/annotations for model training, with a link to the privacy policy, and they cannot proceed without making a choice. Given a consent choice, When saved, Then the record stores user or contact ID, unit, ticket, timestamp, jurisdiction, and choice, and is immutable except via a new versioned update. Given consent = No or later Revoked, When building the training dataset, Then the tenant’s media and related annotations are excluded and any prior copies are purged within 7 days, with a purge log entry. Given an authorized admin requests consent history, When viewed, Then a complete audit trail is shown and can be exported as CSV within 10 seconds.
Pin Authoritative Reference Photo Per Unit
Given an annotation is Authoritative, When a manager or technician selects “Pin as Reference,” Then the photo and overlay become the unit’s active reference with a version ID and notes. Given a new reference is pinned for the same unit, When saved, Then it supersedes the prior active reference; the prior remains accessible in history and can be restored via Rollback. Given a unit has an active reference, When Valve Finder serves tips to a tenant from that unit, Then the reference photo and overlay appear in the guidance panel and are prioritized above generic examples.
Schedule and Roll Back Model Updates
Given dataset version V is finalized with a manifest (data sources, counts, consent filters, hash), When an ML Admin schedules training, Then a training job is created with start time and expected duration and on completion registers model version M with validation metrics (precision/recall/F1 per class). Given model version M is registered, When the admin promotes M to Production, Then the serving layer routes 100% of inference traffic to M within 5 minutes with health checks and canary metrics captured for at least 15 minutes. Given degraded metrics or incidents, When the admin triggers Rollback, Then traffic reverts to the prior stable model within 2 minutes and an incident record is logged with timestamps and affected requests.
Feed Confirmed Tips into Unit-Specific Tips Layer
Given an annotation is Authoritative and Outcome-Verified, When the tips indexer runs (every 15 minutes), Then the tip is added to the Unit-Specific Tips index for that unit with label, short instruction, and reference link. Given a tenant from the same unit opens a new maintenance ticket, When tips are fetched, Then the unit-specific confirmed tip appears in the top 3 suggestions with its annotated overlay. Given no unit-specific tips exist for a unit, When tips are fetched, Then property-level or global high-confidence tips are displayed instead, with unit-specific slots left empty rather than showing low-confidence items.
Offline & Low-Bandwidth Support
"As a renter with weak signal, I want the app to still guide me and upload photos when it can so that I can act immediately."
Description

Ensure the flow remains useful on poor connections by adding client-side image compression, background upload with retry, and a degraded mode that serves cached generic shutoff instructions and capture guidance while deferring server inference. Detect network state, queue uploads, and reconcile results to retrofit overlays once connectivity resumes. Optimize payloads and use placeholders to maintain user confidence during delays. Expected outcome: reliable guidance and reduced drop-offs in low-connectivity environments.

Acceptance Criteria
Auto-switch to Degraded Mode on Connectivity Loss
Given the renter is in the Valve Finder flow and network is offline or latency > 2000 ms for 3 consecutive checks, When the renter attempts to submit a photo, Then the app switches to Degraded Mode within 1 second and displays an "Offline: guidance only" banner. Given Degraded Mode is active, When the renter navigates capture guidance, Then the app shows cached generic shutoff instructions (water, gas, electric) in the renter’s selected language if available, otherwise defaults to English. Given Degraded Mode is active, When the renter views instruction images, Then no network calls are made except lightweight connectivity checks every 15 seconds.
Client-side Image Compression for Low Bandwidth
Given a photo is captured in-app, When preparing for upload on low-bandwidth (< 150 kbps) or offline, Then the image is compressed to ≤ 400 KB and ≤ 1280 px max dimension with EXIF orientation preserved. Given compression occurs, When validating quality, Then text and valve-level details remain legible with SSIM ≥ 0.92 versus the original on the test set. Given multiple photos (up to 5), When queued for upload, Then the total payload size is ≤ 2 MB.
Background Upload with Persistent Retry
Given the device is offline or bandwidth is poor, When a photo is captured, Then it is added to a local FIFO upload queue with UUID, checksum, and timestamp. Given queued items exist, When connectivity resumes, Then uploads restart automatically in the background with exponential backoff (base 2 s, max 60 s) and up to 10 retries per item. Given the app is relaunched, When it starts, Then the queue is restored and duplicates are prevented via checksum/idempotency key. Given the same photo was uploaded previously, When a retry is attempted, Then the server handles it idempotently and the item is marked Synced only once.
Retrofit Overlays After Inference Completion
Given a photo was submitted in Degraded Mode, When server inference completes, Then valve/breaker overlays are applied to the correct photo(s) and saved to the ticket within 5 seconds of receiving results. Given overlays are applied, When the renter reopens the ticket, Then they see the annotated photo with arrows/labels and a "New guidance available" notice. Given a conflict between cached generic tip and inference result, When the renter views results, Then the generic tip is replaced and a versioned update is recorded on the ticket.
Placeholders and Progress Indicators Preserve Confidence
Given a photo is captured offline, When the user returns to the preview screen, Then a placeholder overlay states "Analyzing when online" and per-photo status is shown as Queued/Uploading/Retrying/Synced. Given background upload is in progress, When progress cannot be determined, Then the UI displays an indeterminate spinner and the last-attempt timestamp. Given all items are queued for > 5 minutes while in Degraded Mode, When intake answers indicate high severity, Then the app offers a prominent "Call emergency line" CTA.
Cached Generic Shutoff Instructions Availability
Given the app is freshly installed and online, When first launched, Then it prefetches and caches generic shutoff instructions and capture guidance for water, gas, and electric (images + text) sized ≤ 5 MB total with a 30-day TTL. Given cache exists, When offline, Then the instructions load in < 500 ms from local storage. Given new content is published, When online, Then the app refreshes the cache in the background using ETags without blocking the flow.
Low-Bandwidth Mode Heuristics and Lightweight Assets
Given periodic network probes run, When downlink < 150 kbps or RTT > 1500 ms on 3 of 5 consecutive checks, Then the app enters Low-Bandwidth Mode (without going offline) and defers non-essential requests. Given Low-Bandwidth Mode is active, When instruction assets are requested, Then lightweight variants are served (WebP/AVIF, ≤ 100 KB per image) and text is prioritized. Given bandwidth improves above thresholds for 60 seconds, When checks pass, Then the app exits Low-Bandwidth Mode and resumes normal asset delivery.
Privacy, Consent & Data Retention Controls
"As a renter, I want control over how my photos are used and stored so that I feel safe sharing images of my home."
Description

Introduce a consent gate explaining photo analysis purpose, scope, and retention; enable property-level retention settings and tenant deletion requests; apply access controls so only authorized staff can view annotated images. Encrypt photos at rest and in transit, redact incidental PII where feasible, and maintain audit logs of views, annotations, and exports. Provide region-aware compliance settings (e.g., GDPR/CCPA) and a data minimization mode that stores overlays/annotations without raw images when configured. Expected outcome: tenant trust, regulatory compliance, and secure handling of sensitive visual data.

Acceptance Criteria
Tenant Consent Gate for Photo Analysis
- Given a tenant attempts to submit a maintenance photo, when the photo upload screen loads, then a consent modal is displayed before any image is transmitted. - Given the modal is displayed, then it includes the purpose of analysis, scope (computer vision identification of valves/breakers), retention periods, rights, and a link to the privacy policy in the tenant’s language. - Given the modal is shown, when the tenant taps "Agree", then explicit consent (timestamp, user ID, property ID, locale, IP, consent version) is recorded and linked to the ticket. - Given the modal is shown, when the tenant taps "Decline", then no image is uploaded, the flow is halted, and a support alternative (call/text) is presented. - Given the tenant’s region is detected, then the consent copy uses region-specific disclosures (e.g., GDPR/CCPA) and correct language. - Given 12 months have elapsed since last consent or the policy version changes, when the tenant next attempts an upload, then consent is re-requested.
Property-Level Data Retention Settings
- Given a staff user with Owner or Admin role, when they open Property > Privacy Settings, then they can configure separate retention periods for raw images and overlays (range: 0–36 months; default is region-based). - Given a retention value is saved, then the UI displays the effective purge date for existing assets and validates the value against legal minimums for the selected regime. - Given assets exceed their retention period, when the nightly purge job runs, then eligible assets are irreversibly deleted from primary storage and scheduled for deletion from backups within 30 days. - Given deletions occur, then an audit log entry is created with counts per asset type and property, and success/failure status. - Given retention is set to 0 months for raw images, then raw images are deleted within 24 hours of upload unless Data Minimization Mode is enabled (see separate criterion).
Tenant Data Deletion Requests
- Given an authenticated tenant, when they submit a data deletion request from Privacy settings, then identity is verified via a 6-digit code sent to their registered email or phone. - Given verification succeeds, then all tenant-associated images, overlays, and derived annotations not under legal hold are deleted within the regime-required SLA (GDPR: 30 days; CCPA/CPRA: 45 days). - Given the request is processed, then the tenant receives a confirmation message/email with a summary of deleted categories and any items excluded due to legal holds. - Given deletion occurs, then related access tokens are revoked and audit logs record requester, verifier, time, scope, and outcome.
Access Control and Auditing for Annotated Images
- Given role-based access control, when a user without roles Owner, Property Manager, or Maintenance Coordinator requests an annotated image, then the system returns 403 and logs the denied attempt. - Given an authorized user requests view, then access is granted via a signed URL valid for ≤10 minutes and any exported image is watermarked with property ID and user ID. - Given any view, export, or annotation edit occurs, then an immutable audit log entry is written including user ID, action, asset ID, timestamp, IP, and channel (UI/API), retained for at least 24 months. - Given an Owner/Admin filters audit logs by property and date range, then results are returned within 3 seconds for up to 10,000 events.
Encryption at Rest and In Transit
- Given any client upload or download, then TLS 1.2+ with modern ciphers and HSTS is enforced; HTTP requests are redirected to HTTPS and uploads over insecure channels are blocked. - Given images and overlays are stored, then they are encrypted at rest with AES-256 and keys are managed in a KMS with automatic rotation at least every 90 days. - Given external SSL configuration tests are run, then the service achieves an A or better rating on SSL Labs and has no open medium-or-higher severity crypto findings.
Region-Aware Compliance Settings
- Given a property address and tenant locale, when a ticket is created, then the system assigns GDPR for EEA/UK properties and CCPA/CPRA for California properties; otherwise default global policy is applied. - Given a compliance regime is set, then consent copy, retention defaults, and deletion SLAs reflect that regime automatically. - Given a staff user overrides the regime, then the change is logged and applies to new tickets only; existing tickets retain their original regime. - Given a data export request, then the file includes regime-required disclosures (data categories, purposes, recipients) and is delivered within the applicable SLA.
Data Minimization Mode (Overlays-Only)
- Given Data Minimization Mode is enabled for a property, when a tenant uploads a photo, then the raw image is processed in transient storage and deleted within 60 seconds of overlay generation completion. - Given overlay generation fails, then the raw image is not persisted; the tenant is shown a fallback instruction to contact support and the incident is logged with error details. - Given minimization mode is enabled, then only overlays and annotation metadata are stored and viewable by authorized staff; raw image endpoints return 404. - Given minimization mode is toggled on or off, then the change is logged and does not retroactively delete or restore previously stored raw images.

Skill Router

Maps detected issue to the right trade, certification level, and tools (e.g., licensed plumber + snake), then auto-suggests the best vendor based on first-time-fix history. Prevents misdispatches, boosts fix rates, and avoids costly escalations or callbacks.

Requirements

Trade & Certification Mapping Engine
"As a property manager, I want FixFlow to automatically identify the right trade and certification from tenant photos and notes so that I dispatch the correct professional the first time."
Description

Classifies incoming maintenance requests (photos, text, voice transcripts) into the correct trade and sub-trade, and determines the minimum certification/licensure level needed to legally and safely perform the work in the request’s jurisdiction. Produces a structured routing profile that includes trade, certification tier, risk level, and confidence score. Leverages FixFlow’s intake pipeline and taxonomy, supports region-specific rules, brand/equipment recognition, and multi-language inputs, and exposes results to downstream modules (approvals, scheduling, vendor selection). Enables real-time updates if new evidence arrives and supports versioned models for safe iteration. Expected outcome: fewer misdispatches, higher first-time-fix rate, and faster response times for 1–150 unit portfolios.

Acceptance Criteria
Accurate Trade & Sub‑Trade Classification (Multimodal Intake)
- Given a labeled holdout set spanning ≥12 trades and ≥35 sub-trades, when processed by the engine, then achieve ≥92% top-1 trade accuracy and ≥88% top-1 sub-trade accuracy. - Given inputs as photo-only, text-only, and voice-transcript-only, when processed, then each modality independently meets ≥90% of the above accuracy targets and combined modalities meet or exceed them. - Given any request, when a result is produced, then p95 end-to-end inference latency ≤800 ms and p99 ≤1200 ms. - Given a result, when confidence is emitted, then it is calibrated with ECE ≤0.07 and ranges 0.00–1.00 with two-decimal precision. - Given confidence thresholds by risk (Low ≥0.60, Medium ≥0.75, High ≥0.85), when the score is below threshold, then set riskLevel="High", route to human review, and do not auto-suggest a vendor.
Jurisdictional Certification Mapping Compliance
- Given a request with jurisdiction code (ISO_3166-2 state/province + municipality), when certification is determined, then 100% of sampled cases (n≥300) match the authoritative rules database for legal minimum licensure. - Given hazard indicators (e.g., gas leak, electrical burning smell, flood), when detected, then certificationTier elevates to the highest applicable license for that jurisdiction and riskLevel="High". - Given missing or ambiguous jurisdictional rules, when encountered, then certificationTier="Unknown", riskLevel="High", and a rules_gap event is logged with ruleVersion and source, within 200 ms of detection. - Given any mapping result, when persisted, then it includes ruleVersion, ruleSource, and an auditable decision trace covering all applied rules.
Structured Routing Profile Output Contract
- Given any processed request, when emitting the routing profile, then the JSON conforms to schema v1.0 with fields: trade, subTrade, certificationTier, riskLevel{Low|Medium|High}, confidence[0..1], jurisdiction{country, region, locality}, evidenceRefs[], modelVersion, ruleVersion, createdAt (ISO 8601 UTC), correlationId. - Given the schema, when validated against 1000 diverse samples, then 100% pass required fields and enums; no additional properties allowed beyond schema. - Given numerical fields, when emitted, then confidence has two-decimal precision and timestamps are timezone-normalized to UTC. - Given backward compatibility, when schema v1.1+ is enabled, then v1.0 consumers continue to function via feature flags and down-conversion without data loss for required fields.
Input Understanding: Brand/Equipment Recognition & Multi‑language Support
- Given photos containing appliance/HVAC/water heater nameplates or logos from the top 50 brands, when processed, then brand recognition achieves ≥90% precision and ≥88% recall; model number extraction achieves F1 ≥0.85. - Given recognized brand/model, when mapped, then subTrade specialization and required tools are populated with ≥95% correctness on a labeled test set (n≥300). - Given user text or voice transcripts in EN, ES, FR, or PT, when processed, then sub-trade accuracy is ≥85% and latency overhead vs EN is ≤5% at p95. - Given noisy ASR transcripts with WER up to 20%, when processed, then trade accuracy degrades by no more than 3 percentage points vs clean text. - Given unsupported languages, when detected, then language is flagged as "Unknown", processing falls back to safe default with human review required and a translation_needed flag set.
Real‑time Reclassification on New Evidence
- Given an initial routing profile, when new evidence (photo/text/voice) is attached, then the engine re-evaluates and publishes an updated profile within p95 ≤500 ms and p99 ≤900 ms of evidence ingestion. - Given reclassification, when the trade/subTrade or certificationTier changes, then an event delta is emitted containing previous and new values, changeReason, and evidenceRef, and downstream subscribers receive it within ≤200 ms broker latency. - Given multiple evidence items within 60 seconds, when processed, then updates are debounced to at most one publish per 2 seconds unless riskLevel escalates to High. - Given version history, when queried, then all profile revisions for a request are retrievable with monotonic sequence numbers and timestamps.
Downstream Integration with Approvals, Scheduling, and Vendor Selection
- Given a completed routing profile, when the Approvals service requests it via GET /routingProfiles/{id}, then response time ≤200 ms p95 and the payload passes contract tests for required fields. - Given event publication on topic fixflow.routingProfile.ready, when consumed by Scheduling and Vendor Selection, then 99.9% of messages are delivered at-least-once with idempotency ensured via correlationId. - Given vendor catalogs with trade/certification tags, when Vendor Selection runs, then only vendors meeting trade=subTrade and certificationTier ≥ required are eligible, and the top suggestion uses first-time-fix score as primary rank signal. - Given an end-to-end test case (e.g., clogged drain in jurisdiction X), when executed in staging, then the correct trade, certification, and a top-1 eligible vendor are produced automatically with no manual overrides.
Model Versioning & Safe Iteration Guardrails
- Given a new modelVersion, when enabled in shadow mode on ≥10% traffic, then no production decisions use its outputs; comparison metrics (accuracy, latency, calibration) are logged for A/B analysis. - Given canary rollout at 5%/25%/50%/100%, when promotion criteria are checked, then the new model must meet or exceed baseline on trade accuracy (non-inferiority margin ≤1 pp) and p95 latency (non-inferiority margin ≤50 ms) before advancing. - Given degradation beyond thresholds or incident alerts, when triggered, then automatic rollback to prior modelVersion completes within ≤5 minutes and traffic is fully restored. - Given each decision, when logged, then modelVersion, featureFlag, and config hash are included to ensure reproducibility in audits.
Tool & Material Requirements Inference
"As a dispatcher, I want the system to specify the tools likely needed so that I avoid sending vendors who can’t complete the job in one visit."
Description

Infers likely tools and materials required for a job (e.g., drain snake, flaring tool, replacement fill valve, P-trap) based on issue details, photo cues, equipment brand/models, and historical repair outcomes. Outputs a structured checklist attached to the work order and vendor recommendation, and prompts the chosen vendor to confirm tool availability before accepting. Integrates with the Skill Router’s trade mapping, vendor profiles (declared tooling capability), and scheduling to reduce return trips and callbacks. Supports confidence scoring and allows operator override.

Acceptance Criteria
Photo-First Inference Produces Structured Checklist
Given a tenant submits a maintenance request with at least one photo and a description of 20+ characters When Tool & Material Requirements Inference runs Then it returns a checklist object containing items[].type (tool|material), items[].name, items[].quantity (integer >= 1), items[].part_id (optional), items[].confidence (0..1), items[].source_evidence[], and overall_confidence (0..1) And Then the checklist contains at least three items when overall_confidence >= 0.5; otherwise it returns an insufficient_evidence flag with min_evidence_required And Then the checklist is attached to the work order and vendor recommendation within 2 seconds p95 of inference completion and visible to operator and vendor views And Then the system logs inference_id, model_version, and input_hash for auditability
Confidence Scoring and Operator Override
Given a checklist is generated When an operator removes, adds, or edits any item or quantity Then the change is persisted with user_id, timestamp, reason (optional), and previous_value in an audit trail And When an override occurs Then overall_confidence is recalculated and operator_overridden = true is set And Then the vendor prompt content updates within 5 seconds to reflect the latest checklist And Then the operator can approve or revert overrides in a single action; approvals lock the checklist from further automated updates until explicitly unlocked
Vendor Capability Cross-Check and Confirmation Prompt
Given a vendor is auto-suggested by Skill Router and the vendor profile declares tooling/material capabilities When the checklist includes any required item not in the vendor's declared capabilities Then that vendor is excluded from auto-suggestion And When a dispatcher manually selects a vendor with capability gaps Then the system prompts the vendor to confirm availability for each missing item and blocks acceptance until all required items are confirmed And Then 100% of jobs that reach Accepted status have vendor_confirmed_tools = true for all required items And If no vendor meets all requirements Then the system surfaces the top three vendors ranked by capability coverage and requires operator acknowledgment of mitigation options before dispatch
Integration with Trade and Certification Mapping
Given Skill Router has mapped the trade and certification level for the issue When the checklist is generated Then at least 95% of listed items map to the selected trade taxonomy; any non-matching item is flagged for operator review before dispatch And Then items that require a licensed certification are only included when the certification level matches; otherwise dispatch is blocked until certification is raised or items are removed And Then the checklist includes a derived required_certification field equal to the highest certification requirement among listed items
Brand/Model-Specific Material Inference
Given the issue includes a recognized brand/model from metadata, OCR on photos, or user input When inference runs Then material items include model-specific parts with manufacturer SKU or normalized catalog part_id where applicable and default quantities from repair playbooks And Then each model-specific item lists its evidence source (manual entry | OCR | prior work order | EXIF) and has confidence > 0.6; if no model is recognized, universal parts are suggested with confidence <= 0.6 and needs_verification = true And On a curated test set of 200 labeled cases Then Top-1 correct model-specific part prediction accuracy is >= 80% and Top-3 accuracy is >= 95%
Work Order Attachment and Scheduling Gate
Given a checklist exists When scheduling attempts to move a work order to Booked Then the system requires vendor confirmation of all required items; if confirmation is missing, the status change is blocked with a clear error message And When any checklist item changes after booking Then the system triggers a re-confirmation request to the vendor and sets the job to Pending Vendor Confirmation if not acknowledged within 15 minutes And Then the checklist is included in outbound notifications and accessible via API GET /work-orders/{id}/checklist with response time <= 500 ms p95
Outcome Logging and Quality Feedback
Given a job completes When the technician submits completion details and photos Then the system captures actual tools and materials used (names, quantities, part_ids) and first_time_fix (boolean with reason if false) And Then it computes precision and recall of predicted vs actual items per category and updates a dashboard metric daily And Then any return visit within 7 days on the same issue is tagged as a callback and linked to the original inference_id for quality analysis
Vendor Skill Scoring & First‑Time‑Fix Ranking
"As a property manager, I want vendor suggestions ranked by likelihood of first-time fix so that I minimize callbacks and costs."
Description

Calculates per-trade and per-problem-type performance scores for vendors using first-time-fix rate, callback frequency, SLA adherence, tenant satisfaction, and cost efficiency. Uses time-decayed weighting, property type context (single-family vs. multifamily), building age, and equipment/brand expertise to rank vendors for the detected issue. Integrates with vendor profiles, service areas, and FixFlow’s dispatch policies to produce a top-N ranked list with scores and reason codes. Handles cold-start via license level, certifications, and curated references.

Acceptance Criteria
Top‑N Ranked Vendor List With Scores and Reason Codes
Given a detected issue with trade, problem_type, property_type, building_age, and equipment_brand, and a set of eligible vendors When the system is asked to return top_N=5 vendors for the issue Then it returns min(5, eligible_vendor_count) vendors sorted by descending composite_score And each item includes vendor_id, composite_score (0–100), rank, and reason_codes[] And no ineligible vendor appears in the list
Time‑Decayed Performance Weighting
Given historical job records with completion_date timestamps across the last 24 months When computing vendor performance metrics Then each job's weight is w(t_days)=0.5^(t_days/half_life_days) with half_life_days configurable (default 90) And for half_life_days=90, a job 30 days old has higher weight than one 180 days old, and their weight ratio is >=8:1 And changing half_life_days updates composite_scores without other input changes
Context‑Aware Ranking by Property and Equipment
Given an issue with property_type, building_age, and equipment_brand values When a vendor has at least 5 matching historical jobs for that context Then context-specific metrics contribute >=70% of the vendor's performance component for this ranking Else the context contribution is (matching_jobs/5) of the performance component, capped at 100% And reason_codes include applied_contexts and matching_job_counts
Eligibility and Dispatch Policy Filtering
Given vendor profiles with service_areas, active_licenses, certifications, insurance_status, and dispatch_policies When ranking for a property location and requested_time_window Then vendors outside service_areas, with expired/insufficient licenses or certifications, missing insurance, or violating dispatch_policies are excluded before scoring And all returned vendors have eligibility_status=valid and include eligibility_reasons in reason_codes
Cold‑Start Ranking via Credentials and References
Given a vendor has fewer than 5 historical jobs for the relevant trade/problem_type When computing their score Then a cold_start_score is derived from license_level, certifications, curated_references, and brand_expertise flags, normalized to 0–100 And the vendor may appear in the ranked list if eligible, with reason_codes containing 'cold_start' and contributing_factors And cold_start vendors are labeled provisional in reason_codes
Per‑Trade and Per‑Problem‑Type Score Isolation
Given vendors with records across multiple trades and problem_types When ranking for trade=T and problem_type=P Then only T/P-relevant jobs and metrics contribute to the composite_score (except globally neutral penalties like policy violations) And jobs from other trades/problem_types do not affect the composite_score
Composite Score Factor Inclusion and Normalization
Given available metrics for FTF_rate, callback_rate, SLA_adherence, tenant_satisfaction, and cost_efficiency When computing composite_score Then all five factors are included after normalization to a common 0–100 scale And any missing factor is imputed from trade/problem_type baselines with a penalty, noted in reason_codes And composite_score is bounded within 0–100
Availability & Constraints‑Aware Suggestion
"As a coordinator, I want recommendations that consider availability and constraints so that jobs are assigned to vendors who can actually attend on time and within budget."
Description

Combines vendor performance ranking with real-time availability, proximity, travel time, price caps, emergency tier, property access windows, and tenant preferences to produce actionable vendor suggestions. Respects approved vendor lists, exclusion rules, and workload balancing. Integrates with FixFlow Scheduling to hold tentative slots and with Approvals to enforce budget thresholds. Provides fallbacks when preferred vendors are unavailable and supports auto-advance to next best option after timeouts.

Acceptance Criteria
Ranked Vendor Suggestions Respecting Constraints
Given a maintenance issue is classified and required trade, certification level, and tools are determined And a vendor pool with performance metrics exists When the system generates vendor suggestions Then it ranks vendors by a composite score weighting first-time-fix rate, certification/tool match, availability fit, travel time, and price compliance per configuration And it returns the top 3 suggestions by default (configurable) And vendors failing any hard constraint (missing certification/tools, no qualifying availability, or exceeding price caps) are excluded And ties are broken by earliest available start, then lowest travel time, then lowest estimated cost And each suggestion includes a rationale with score components and constraint pass/fail indicators
Real-Time Availability and Travel-Time Filtering
Given a property access window and tenant time preferences are set on the ticket And vendor calendars and locations are available in real time And the emergency tier defines a response SLA When generating vendor suggestions Then only vendors with an available slot starting within the access window and meeting the SLA are included And ETA and travel time are calculated using live traffic data timestamped within the last 5 minutes And vendors whose travel time exceeds the configured maximum or whose availability conflicts with tenant preferences are excluded And each suggestion displays the proposed arrival window and ETA
Budget Caps and Approvals Enforcement
Given a property- or portfolio-level price cap and vendor rate cards enabling an estimated job cost And Approvals policy thresholds are configured When a suggested vendor would exceed a cap or requires approval Then the vendor is labeled "Requires Approval" and cannot be auto-booked And an approval request is created with vendor, estimate, and cap delta And no tentative scheduling hold is placed until approval is granted And upon approval the booking proceeds; upon denial or timeout the next compliant vendor is suggested
Approved List Compliance and Exclusions
Given an approved vendor list and exclusion rules for the property/manager When generating vendor suggestions Then only vendors on the approved list are included unless fallback policy explicitly allows network vendors And any excluded vendor (e.g., suspended, tenant-blocked, conflict of interest) is never shown And the suggestion list records reasons for exclusion and exposes them in audit logs
Workload Balancing for Equally Ranked Vendors
Given two or more vendors have composite scores within the configured tie threshold And vendor daily/weekly capacity limits are configured When assembling the suggestion order Then first-position nominations are rotated to balance assignments over the trailing 30 days (configurable) And vendors at or above capacity are deprioritized or removed And the selection event updates vendor load counters atomically to prevent over-assignment
Tentative Slot Hold and Auto-Advance on Timeout
Given Scheduling integration is enabled And a user selects "Hold" on a suggested vendor slot When the hold is placed Then the slot is reserved for the configured TTL without double-booking And the vendor is notified per channel preferences And if the vendor declines or TTL expires without confirmation, the hold is released and the next best vendor is automatically contacted And all state transitions are logged with timestamps
Fallback Suggestions with SLA-Adherent Alternatives
Given no preferred or approved vendors satisfy all constraints When generating vendor suggestions Then the system proposes fallback vendors that meet minimum certification, SLA, distance, and budget constraints per policy And fallback suggestions are clearly labeled as such And if no vendors qualify, the system returns a "No Match" outcome with specific unmet constraints and recommendations for which constraints to relax in priority order
License & Insurance Compliance Gate
"As a risk-conscious manager, I want the system to block non-compliant dispatches so that we avoid legal exposure and costly escalations."
Description

Validates that suggested vendors meet jurisdictional license requirements and have active insurance coverage matching the mapped trade and risk level. Performs automated checks (where APIs exist), tracks document expirations, and blocks or warns on non-compliance based on configurable policy. Stores verification artifacts in the job’s audit record and surfaces compliance status in the recommendation UI to prevent illegal or risky dispatches.

Acceptance Criteria
Automated License Verification by Jurisdiction and Trade
Given a job with a mapped trade, certification level, and jurisdiction from the property address And a vendor candidate with recorded license identifiers When the Skill Router evaluates vendors for recommendation Then the system queries the jurisdiction licensing registry via API (if available) with a 2s timeout And validates license class/type matches the mapped trade and required certification level And validates license status is Active with an expiration date on or after the scheduled service date (or today if not yet scheduled) Then the vendor is marked License_Compliant=true only if all validations pass, otherwise License_Compliant=false with reason codes (e.g., TYPE_MISMATCH, EXPIRED, INACTIVE, NOT_FOUND) And the verification result is timestamped and stored for audit
Insurance Coverage Validation by Risk Level
Given a job with mapped trade and risk level (Low/Med/High) and configured required policy types and minimum limits per level And a vendor candidate with insurance records (policy type, limits, effective/expiration dates) or API-accessible COI When the Skill Router evaluates vendors Then the system validates that all required policy types exist (e.g., General Liability, Workers’ Comp) and limits >= configured minimums And validates policies are active across the scheduled service window (or as-of today if unscheduled) And records failing checks with reason codes (e.g., MISSING_POLICY, BELOW_LIMITS, EXPIRED, UNVERIFIED) Then the vendor is marked Insurance_Compliant=true only if all validations pass, otherwise Insurance_Compliant=false And the verification result is timestamped and stored for audit
Configurable Policy Gate and Override Workflow
Given an organization-level Compliance Policy set to Block, Warn, or Off And a vendor with License_Compliant and Insurance_Compliant flags computed When the Skill Router generates recommendations Then under Block: any vendor with either flag=false is excluded from auto-suggestions and cannot be assigned; attempts return a blocking error message with reasons And under Warn: non-compliant vendors appear with a Non-Compliant badge and cannot be auto-assigned; authorized roles may manually assign only after providing an override reason; the override user, reason, timestamp, and policy are recorded in the audit And under Off: compliance status is displayed but does not prevent suggestion or assignment And policy changes apply to new evaluations; existing jobs are re-evaluated on refresh or at assignment time
Expiration Tracking and Proactive Notifications
Given vendor license and insurance expiration dates are stored And an organization has configured ExpiryWarningDays=N (default 30) When a document will expire within N days Then the vendor’s compliance status becomes Warning with days-to-expiry displayed in the UI And email/in-app notifications are sent to the vendor and property manager within 1 hour of detection, listing expiring items and required actions When the expiration date passes without renewal Then the affected compliance flag switches to false automatically, and the vendor is handled per current policy (Block/Warn/Off) And upon upload or API confirmation of renewal, the system re-verifies and updates status within 5 minutes
Audit Trail and Verification Artifact Storage
Given any license or insurance verification (API or manual) When a verification attempt is performed Then the system appends an audit record including: job ID, vendor ID, verification type (license/insurance), source (API/manual), normalized details, result (pass/fail/warning/unknown), reason codes, policy in effect, timestamp, and verifier (system/user) And for API checks, the normalized response snapshot and checksum are stored; for manual checks, the uploaded document file, metadata, and checksum are stored And audit records are immutable (append-only); corrections create a new version linked to the prior record And audit entries are viewable from the job timeline within two clicks and exportable as PDF/JSON
Recommendation UI Compliance Surfacing and Controls
Given the recommendation UI lists candidate vendors When compliance evaluations are available Then each vendor displays a compliance badge: Compliant (green), Warning with days-to-expiry (amber), or Non-Compliant with reason codes (red) And a default filter hides Non-Compliant vendors when policy=Block; users may toggle “Show non-compliant” to view them And a details panel shows license number, issuing authority, class, expiration; insurance policies, limits, expiration; last verified timestamp; and any override information And attempting to assign a Non-Compliant vendor triggers behavior per policy (block or require override) with corresponding messaging
Resilience, Caching, and Performance for Compliance Checks
Given external compliance APIs may be slow or unavailable When evaluating a vendor’s compliance Then the system uses live API results if responses return within 2s per API; otherwise it falls back to a cached result not older than T hours (configurable, default 24) And if no cache exists and API calls fail or time out, the status is set to Unknown and treated per policy (Block→exclude; Warn→include with Unknown badge; Off→include) with visible “verification unavailable” messaging And the system completes compliance evaluations for up to 10 vendor candidates within 5s at p95, logging metrics for latency, timeouts, and fallbacks
Confidence Thresholds & Misdispatch Safeguards
"As an operator, I want safeguards when the system isn’t confident so that we reduce misdispatches and protect service quality."
Description

Applies confidence thresholds to classification outputs and vendor suggestions. If confidence is low or conflicting (e.g., plumber vs. handyman), automatically requests clarifying photos/questions from the tenant, suggests immediate safety steps for emergencies (e.g., shut-off), or routes to human triage. Provides an explicit manual override with reason capture and tracks outcomes to continually tighten thresholds. Reduces misroutes and unnecessary emergency callouts.

Acceptance Criteria
Low Confidence Classification — Clarification Workflow
Given a maintenance ticket has predicted_trade and predicted_trade_confidence below configured_min_trade_confidence and emergency_indicator is false When the ticket is created or updated with classification results Then the system sends the tenant a clarification request within 30 seconds including at least 2 required photo prompts and up to 3 multiple-choice questions And the system prevents auto-dispatch until clarification is received or triaged by a human And if no tenant response within 10 minutes, a reminder is sent; after 30 minutes, the ticket is routed to human triage And all actions and timestamps are logged to the ticket audit trail
Conflicting Trade Predictions — Human Triage Gate
Given the top_two_trades_confidence_gap is less than configured_conflict_delta and both confidences are >= configured_min_trade_confidence When classification results are produced Then the ticket is queued to the Human Triage inbox with label "Trade Conflict" and priority set to High And auto-dispatch is disabled until a human selects a trade or requests more info And the triage UI displays top 2 trades, their confidences, and key features/rationales used And if not triaged within 15 minutes, an on-call notification is sent
Emergency Indicators — Immediate Safety Instructions
Given emergency_indicator is true OR predicted_issue_category in ["Gas Leak","Active Water Leak","Electrical Fire Risk"] with confidence >= configured_emergency_threshold When the ticket enters this state Then the system immediately presents step-by-step safety instructions to the tenant (e.g., shut off water/gas, cut power) in-app and via SMS And the system provides a one-tap call button to the designated emergency line/vendor And the tenant must acknowledge completion or inability within 2 minutes And if no acknowledgement within 2 minutes, the ticket is escalated to on-call human triage and auto-dispatch to emergency vendor is enabled per account policy And all safety guidance and acknowledgements are stored in the audit trail
Vendor Suggestion Confidence Gate — Manual Approval
Given suggested_vendor_confidence < configured_min_vendor_confidence OR suggested_vendor.first_time_fix_rate_90d < configured_min_ftf_rate When a vendor is proposed for dispatch Then the system does not auto-dispatch And the system displays the top 3 vendors with confidence, first_time_fix_rate_90d, ETA, coverage, and certification fit And dispatch requires explicit approval by a user with role in ["Manager","Dispatcher"] And if emergency_indicator is true and no approval within 5 minutes, the system escalates to on-call human triage And all approvals/declines are captured with user, timestamp, and rationale
Manual Override — Reason Capture and Auditability
Given a permitted user selects "Manual Override" for trade or vendor When the user submits the override Then the system requires a reason_code selection and a free-text explanation of at least 15 characters And the system records the predicted values, confidences, and override details in the audit trail And the dispatched job is tagged manual_override = true And overrides are included in analytics and export with user, timestamp, and reason_code
Outcome Tracking — Threshold Tuning Feedback Loop
Given a job has been completed and outcome data is available When the completion is recorded Then the system captures first_time_fix (boolean), callbacks_within_14_days, actual_trade_used, escalations, and whether a misdispatch occurred (predicted_trade != actual_trade_used) And the system links these outcomes to initial predictions, confidences, vendor selection, and any overrides And a weekly job computes misdispatch_rate_30d and first_time_fix_rate_30d by trade and vendor And if misdispatch_rate_30d exceeds configured_target_misdispatch_rate for any trade, the system generates threshold_adjustment recommendations with rationale for review And threshold changes are versioned and require approval by an authorized user before activation
Routing Explainability & Audit Trail
"As a team lead, I want to see why a vendor was suggested so that I can trust the system and resolve disputes with tenants and vendors."
Description

Generates a clear, human-readable rationale for each recommendation, including mapped trade, required certification, inferred tools, vendor performance metrics, availability constraints, compliance checks, and model/version identifiers. Persists a tamper-evident audit record for each routing decision to support QA, dispute resolution, and compliance reviews. Provides export and API access for reporting and continuous improvement loops.

Acceptance Criteria
Explainability: Complete Routing Rationale
Given a new maintenance request with at least one photo When the Skill Router produces a routing recommendation Then the rationale includes: mapped trade, required certification level, inferred tools list, selected vendor and top-3 alternatives with first-time-fix rate, recall rate, and average time-to-fix, availability window considered, compliance checks results (permit/insurance/license), model and ruleset identifiers and versions, and UTC timestamps And each rationale data point references its source (request field IDs, feature IDs, vendor record IDs) And the rationale is rendered in human-readable plain language <= 800 characters in the UI while a full-detail JSON payload is stored in the audit record
Vendor Selection: Factor Scores and Tie-Breaks
Given multiple eligible vendors for a request When the system ranks vendors Then the ranked list includes normalized scores for first-time-fix probability, distance/ETA, availability within SLA, cost band, and compliance validity And the selected vendor's score is >= the second-ranked vendor's score and any tie is broken by higher compliance validity, then shorter ETA, then lower estimated cost, deterministically And the rationale includes the numeric scores, tie-break path taken, and reasons disqualifying any filtered-out vendors
Tamper-Evident Audit Record
Given a routing recommendation is finalized or overridden When the audit record is written Then the record includes the full input snapshot, rationale JSON, selected and alternative vendors, actor IDs, and decision metadata And the record payload hash and a previous-record hash are stored to create a verifiable chain And the record is signed using the current key version; upon read, signature and chain verification return Verified=true; any alteration yields Verified=false and an alert event
Override Transparency and Traceability
Given a user with permission override.recommendation performs an override When they submit the override with a required reason Then a new audit entry is appended linking to the prior recommendation, capturing diff of changed fields, actor, timestamp, and new rationale And the original record remains immutable and is not edited And the override event is flagged for QA review with a status of Pending Review until acknowledged
Export: UI and Scheduled Delivery
Given a property manager selects a date range and properties in the Audit Export UI When they request export Then a downloadable CSV and JSON are generated within 10 seconds for up to 10k records, including all rationale fields, model IDs, integrity proofs, and redacted PII per policy And record counts in the file equal the count shown in the UI filter summary And an optional scheduled export can be configured to deliver to S3 or email with signed URL links that expire in 24 hours
API Access: Filtered Retrieval and Pagination
Given an API client with OAuth2 scope audit.read and a valid tenant ID When it calls GET /v1/audit-decisions with filters (dateFrom/dateTo, propertyId, vendorId, trade, decisionType, modelVersion) and pageSize<=500 Then the API returns 200 with a paginated list, a nextPageToken when more results exist, and includes ETag and checksum for each record And rate limiting is enforced at 60 requests/minute per client with 429 responses and Retry-After header on excess And fields parameter supports sparse fieldsets; responses exclude PII fields unless scope pii.read is present
Compliance Review Evidence Package
Given an auditor requests evidence for a work order ID When the user generates an evidence package Then a ZIP is produced within 60 seconds containing: audit log entries, rationale text and JSON, referenced photos, vendor license/insurance snapshots at decision time, and a proof file with chain hashes and signatures And the proof validates end-to-end on a verifier tool returning Verified=true And the package contents are deterministic for the same inputs and do not include redacted PII

Repair Predictor

Forecasts probable fix path, parts list, and cost/time range using historical lookalikes from photo patterns. Surfaces approval thresholds and recommends instant pre-approval for low-risk jobs, accelerating decisions and keeping SLAs on track.

Requirements

Photo Pattern Detection
"As a property manager, I want FixFlow to analyze tenant photos and identify the issue type so that predictions and next steps are generated without manual review."
Description

Ingest tenant-uploaded photos and automatically detect issue category, affected component(s), severity, and contextual metadata to create structured features for prediction. Process images in real time and normalize outputs for downstream services, integrating with FixFlow’s intake pipeline to auto-tag tickets and trigger the Repair Predictor. Include mobile/web upload support, confidence scoring with thresholds for manual review, and privacy-safe handling and storage of media. Expected outcome is faster, more accurate triage that drives higher-quality predictions with minimal manual intervention.

Acceptance Criteria
Mobile Photo Upload - Real-Time Detection and Tagging
Given a tenant uploads a single photo via the mobile app (JPG/PNG/HEIC/WebP up to 10 MB) When the upload completes and the image reaches the detection service Then detection results are produced within 2 seconds p95 and 4 seconds p99 And Then results include issue_category, affected_components[], severity, and confidence scores (0.00–1.00) per field And Then a processing_id is generated and associated with the intake ticket And Then the client receives a 200 response containing the normalized payload and processing_id
Web Multi-Photo Upload - Consistency Across Images
Given a tenant uploads 3–5 photos for a single ticket via web intake When all photos are processed Then each photo has per-photo predictions persisted with confidences And Then the ticket-level auto-tags use the highest-confidence consistent category across photos when max_confidence ≥ 0.85 And Then if top categories conflict (score difference < 0.05) or no category ≥ 0.85, the ticket is routed to manual review And Then per-photo predictions are linked to the ticket for downstream services
Severity Classification with Confidence Thresholds and Routing
Given a processed photo with a predicted severity and severity_confidence When severity_confidence ≥ 0.85 Then severity is auto-applied to the ticket And When 0.60 ≤ severity_confidence < 0.85 Then the ticket is queued for manual review with suggested severity And When severity_confidence < 0.60 Then severity is set to unknown and the ticket is queued for manual review And Then the routing decision and confidence are captured in the audit log
Normalization and Schema Compliance for Downstream Services
Given detection results are ready for downstream consumption When the payload is constructed Then it conforms to FixFlow schema v1.0 with fields: ticket_id, processing_id, issue_category (enum), affected_components (array of enums), severity (enum), confidences (map), metadata (object) And Then enum values map to the canonical taxonomy and unknowns map to "unknown" And Then numeric confidences are rounded to two decimals And Then payload size is ≤ 50 KB And Then contract tests with the Repair Predictor succeed with 100% required fields present
Privacy-Safe Handling and Storage
Given an image is uploaded When the system persists media Then EXIF GPS and PII-bearing EXIF tags are stripped And Then faces and license plates detected with confidence ≥ 0.80 are blurred before persistence And Then original, unredacted images are deleted within 5 minutes of redaction And Then redacted images are stored encrypted at rest and served only via signed URLs expiring ≤ 15 minutes And Then all access and transformations are recorded in an immutable audit log
Auto-Trigger Repair Predictor on Successful Detection
Given detection completes with issue_category_confidence ≥ 0.85 and severity_confidence ≥ 0.60 When the ticket is updated with auto-tags Then the Repair Predictor is invoked within 1 second with the normalized payload And Then the call returns 2xx or is retried with exponential backoff up to 3 attempts And Then the ticket displays "Prediction Pending" until a prediction response is received or retries are exhausted
Manual Review Queue and Override Flow
Given a ticket is routed to manual review due to confidence thresholds When a manager opens the review item Then the UI displays redacted images, top-3 predictions per field with confidences, and extracted metadata And Then the manager can approve suggested values or override any field And Then upon submission, the ticket updates within 2 seconds and downstream triggers fire accordingly And Then the review item is marked resolved and the decision is logged for model feedback
Lookalike Retrieval Engine
"As a technician coordinator, I want the system to find past repairs similar to the current issue so that recommendations are grounded in real outcomes."
Description

Implement a similarity search service over historical repair cases that combines image embeddings and structured attributes (e.g., appliance model, property type, region, seasonality) to retrieve the most relevant past incidents with known outcomes. Integrate with the data warehouse and a feature store to ensure freshness and consistency, supporting filters and business constraints (e.g., only show lookalikes from the same climate zone). The engine returns top-k cases, their outcomes, and metadata to inform downstream recommendation and estimation modules, improving accuracy and grounding predictions in real-world evidence.

Acceptance Criteria
Top‑K Lookalike Retrieval Combining Image and Attributes
Given a new repair intake with at least one photo and structured attributes (appliance_model, property_type, region, season), when the service is called with top_k=10 and default similarity weights (image=0.7, attributes=0.3), then exactly 10 unique historical cases are returned ordered by composite_similarity descending. Given the same request, when inspecting each result, then each item includes case_id, similarity_score in [0,1] with ≥3 decimal precision, embedding_model_version, and attribute_vector_version. Given a fixed validation set of ≥500 labeled incidents, when running offline evaluation, then Recall@5 ≥ 0.70 and MRR@10 ≥ 0.65. Given identical inputs, versions, and a fixed seed, when the query is repeated 3 times, then the result ordering is identical; ties are broken by newer updated_at then lower case_id.
Business and Climate Filters Enforcement
Given a query with climate_zone set to the tenant’s climate_zone, when the engine executes, then all returned cases have the exact same climate_zone value. Given filter parameters (region, property_type, appliance_model prefix match) and exclusions (exclude_vendor_ids), when applied, then no returned case violates any filter or exclusion (0 tolerance). Given filters that yield fewer than top_k matches, when the engine executes, then it returns all available matches (< top_k) and includes result_count and reason="filtered_exhausted" in the response. Given an invalid filter value (e.g., unknown climate_zone), when requested, then the API responds 400 with error_code="INVALID_FILTER" and no partial results.
Data Freshness and Consistency via Feature Store
Given a new historical case lands in the data warehouse and is materialized in the feature store, when 15 minutes have elapsed, then the case is retrievable by queries it should match (p95 ≤ 15 minutes across 24h). Given an index refresh is in progress, when serving queries, then all results in a single response reference the same feature_store_snapshot_ts and embedding_index_version and consistent_snapshot=true. Given monitoring runs, when evaluating staleness_gauge over rolling 24h, then p95 ≤ 15 minutes and max ≤ 30 minutes. Given a mixed-version simulation, when detected, then the engine aborts the response and emits 503 with error_code="INCONSISTENT_SNAPSHOT".
Response Payload Contract and Schema Validation
Given a successful query, when validating the response, then it conforms to schema_version N and includes: query_echo, cases[], and paging; each case has case_id (string), similarity_score (float), outcome {fix_path, parts[], cost_range {min,max,currency}, time_range {min_hours,max_hours}}, and metadata {appliance_model, property_type, region, season, climate_zone, created_at}. Given contract tests against schema_version N, when executed in CI, then 100% of tests pass and no required field is missing. Given a client requests Accept-Schema-Version=N, when served, then responses remain backward compatible for all minor versions N.x; incompatible changes increment N+1. Given an unknown optional field appears, when clients validate, then it is ignored without error; given a missing required field, then the response is rejected and the service error budget is debited.
Error Handling and Fallback Behavior
Given all candidates fail to meet min_similarity_threshold=0.35 after filters, when responding, then the engine returns cases=[] and sets fallback_reason="no_candidates" with HTTP 200. Given an index timeout > 800 ms, when a cached result for the identical query exists and is fresh (≤10 minutes), then the engine serves it with served_from_cache=true; otherwise it returns 504 with retry_after seconds and no body data. Given any 4xx/5xx error, when returned, then the payload includes trace_id, error_code, and human_readable message; success_rate over 7 days ≥ 99.5%.
Latency, Throughput, and Pagination Consistency
Given online traffic in staging at 50 RPS and top_k ≤ 10 with ≤3 filters, when measured over 30 minutes, then p95 latency ≤ 400 ms and p99 ≤ 800 ms with error_rate < 0.5% and CPU < 70% of allocated. Given a result set larger than top_k, when requesting next_page_token, then the next page returns non-overlapping items consistent with the original snapshot (same snapshot_ts and index_version) and preserves ordering rules. Given three consecutive pages are requested, when aggregated, then the union equals the first 3*top_k items of the full ordering with no duplicates.
Fix Path Recommender
"As a field technician, I want a recommended repair plan before I arrive so that I can bring the right tools and resolve the issue in one visit."
Description

Generate a probable step-by-step repair plan from detected issue features and retrieved lookalikes, including pre-visit checks, on-site diagnostics, parts replacement steps, and escalation criteria. Provide confidence for each step and an overall success probability, and export the plan into work order templates used by scheduling. Integrate with technician profiles and property constraints to tailor recommendations. The outcome is a standardized, high-confidence workflow that reduces repeat visits and shortens time-to-resolution.

Acceptance Criteria
Generate Plan from Detected Issue Features
- Given a maintenance intake with detected issue labels, extracted features, and at least one photo, When the recommender runs, Then it returns a plan containing sections in order: pre-visit checks, on-site diagnostics, parts replacement steps, and escalation criteria. - Given a property type and specific equipment/appliance model are known, When the plan is generated, Then each step references the target component/equipment, includes an estimated duration in minutes, and a required skill tag. - Given the plan is generated, Then an overall success probability in the range [0.00, 1.00] with two-decimal precision is returned and each step includes a confidence score in [0.00, 1.00]. - Given valid input, When a plan is requested, Then generation latency meets p50 ≤ 3s and p95 ≤ 8s measured over the last 200 requests.
Confidence Boundaries, Rationale, and Threshold Flags
- Given any step with confidence < 0.60, Then the step is flagged as "low-confidence" and includes a rationale referencing at least one lookalike case ID and the top three contributing features. - Given overall success probability is returned, Then it is within [0.00, 1.00] and the response includes aggregation_method and weights such that client-side recomputation differs by ≤ 0.01. - Given any step has missing or non-numeric confidence, Then plan generation fails with error code RP-CONF-001 and a list of offending step indices.
Tailoring by Technician Profiles and Property Constraints
- Given technician profiles (certifications, tools, coverage) and property constraints (access hours, HOA rules, warranties) exist, When the plan is generated, Then each step includes required skill and tool tags and is marked constraint_check = "passed"; no step violates a known constraint. - Given a step requires a certification that no available technician holds, Then the plan includes an escalation recommendation specifying the missing certification. - Given the property has an active warranty for the affected component, When a replacement step is proposed, Then the plan proposes a warranty path and flags the replacement step as "do-not-execute" unless the warranty feasibility check returns false.
Export to Scheduling Work Order Template
- Given a generated plan, When exporting, Then a work order payload conforming to schema version >= 1.2 is produced with fields: work_order_id, property_id, step_list[], parts_list[], estimated_time_minutes, success_probability, technician_skill_tags[]. - Given the payload is posted to the scheduling API sandbox, Then the API responds with HTTP 2xx and echoes a work_order_id, and subsequent retrieval returns the same step count and matching parts_list hash. - Given a successful export, Then a plan_version UUID is stored, linked to the work order, and visible in audit logs with timestamp and actor. - Given schema validation fails on export, Then the system returns a retryable 4xx with code RP-EXP-400 and a per-field error list.
Escalation Criteria and Risk Handling
- Given any step is tagged with a safety risk or the detected issue is categorized as emergency, When the plan is generated, Then escalation steps include "immediate shutoff" and "manager approval required" positioned within the first two steps. - Given overall success probability < 0.70 or more than two steps are low-confidence, Then the plan includes an escalate_for_review step with a required reviewer role and blocking flag = true. - Given a plan requires escalation, When exported, Then the resulting work order has approval_status = "Pending" and is not schedulable until approval is recorded.
Provenance and Lookalike Utilization
- Given lookalike cases are retrieved, When generating the plan, Then at least three lookalike case IDs with similarity scores are attached to the plan and each step references at least one source case ID. - Given similarity scores are included, Then they are normalized to [0.00, 1.00] and the weights used for any aggregation sum to 1.00 ± 0.01. - Given a user opens the explain view for a plan, Then the view renders within ≤ 2s and displays the top five contributing features with weights and linked media thumbnails.
Repeat Visit and Resolution Time Outcomes
- Given an A/B pilot over four weeks with at least 100 qualifying work orders per arm, When Fix Path Recommender is enabled, Then repeat-visit rate decreases by ≥ 15% and median time-to-resolution decreases by ≥ 20% versus control with p-value ≤ 0.05. - Given the outcome targets are not met, Then a rollback flag is set and the feature stays disabled by default for new properties.
Parts & Pricing Generator
"As a property manager, I want an auto-generated parts list with pricing so that I can approve repairs quickly and avoid delays."
Description

Produce an itemized parts and materials list based on the recommended fix path and detected make/model, including SKUs, quantities, compatible alternatives, and supplier-specific pricing and availability. Integrate with vendor catalogs, regional price books, and purchase order creation to enable one-click procurement. Provide tax, shipping, and lead-time estimates to surface potential delays. This enables faster approvals and reduces procurement-related bottlenecks.

Acceptance Criteria
Generate Itemized Parts List from Fix Path
Given a recommended fix path with detected make and model for the asset, and required step metadata (units, dimensions, waste factors) is available When the Parts & Pricing Generator runs Then it produces an itemized list where each line has: SKU, description, quantity, unit of measure, manufacturer, make/model compatibility, and step reference And quantities are calculated from step metadata with rounding rules applied And the list contains no null/empty values for required fields and passes schema validation And generation completes within 2 seconds for up to 20 line items
Aggregate Supplier Pricing and Availability
Given vendor catalog integrations and the correct regional price book for the property's location are configured When the system requests pricing and availability for each SKU Then it returns for each line at least three supplier offers when available (or all if fewer), including supplier ID, contract price, list price, currency, stock status, available quantity, lead-time (days), and last-updated timestamp And all prices used were refreshed within the last 24 hours; otherwise the offer is marked 'Stale' And all monetary values are normalized to the property’s currency using the regional price book rules
Recommend Compatible Alternatives
Given a primary SKU has been identified When compatible alternatives are queried Then at least two compatible alternatives are returned when they exist, each including compatibility evidence (models/specs), price, lead-time, and substitution notes And alternatives that reduce total cost by ≥10% or reduce lead-time by ≥30% versus the primary are flagged 'Recommended' And alternatives that violate warranty or building-code rules configured for the property are excluded
Calculate Tax, Shipping, and Lead-Time; Surface Delays
Given the property’s ship-to address, jurisdiction, and preferred shipping method are known When totals are calculated Then estimated tax is computed per jurisdiction rules and item taxability, shipping is estimated from carrier rate cards and item weight/dimensions, and overall order lead-time equals the slowest item lead-time plus transit And a 'Potential Delay' alert is shown if overall lead-time exceeds the SLA for the issue type or any item is backordered >3 days And the UI displays a clear breakdown of subtotal, tax, shipping, total, and earliest delivery date
One-Click Purchase Order Creation and Vendor Splitting
Given a single offer per line item has been selected and the user has purchasing permission When the user clicks 'Create PO' Then the system generates one or more POs split by supplier, each including bill-to/ship-to, vendor, line items (SKU, description, qty, unit price), taxes, shipping, totals, payment terms, and expected delivery dates And each PO is transmitted via the vendor’s configured channel (API/email/EDI) and recorded with status 'Submitted' And the user receives an on-screen confirmation and downloadable PO within 5 seconds
Fallbacks and Manual Review on Data Gaps
Given pricing, SKU mapping, or integrations are missing or error When the generator executes Then affected lines are flagged 'Needs Review' with reason code (e.g., NO_PRICE, NO_SKU, INTEGRATION_ERROR) And fallback suppliers and regional price book defaults are attempted automatically And if no valid offer exists after fallbacks, PO creation is blocked and a procurement task is created with all context and error details And all failures are logged with a correlation ID
Auditability and Change Tracking
Given a generated parts list and selected offers exist When a user edits quantities, selects an alternative, or overrides pricing Then the system records user, timestamp, field changed, before/after values, and justification And totals and lead-time are recalculated immediately and versioned And the complete audit trail is attached to the work order and exportable
Cost & Time Range Estimation
"As a landlord, I want realistic cost and time ranges so that I can budget appropriately and keep tenants informed."
Description

Estimate probabilistic cost and duration ranges (e.g., P50/P90) for the recommended fix path by combining labor rates, travel times, vendor performance, historical variance, and seasonality factors. Present min/most-likely/max along with confidence indicators and sensitivity to key assumptions. Integrate with SLA tracking to flag potential breaches and recommend schedule adjustments. Calibrate models via backtesting to maintain accuracy and trust.

Acceptance Criteria
P50/P90 Range and Confidence Display
Given a generated estimate for a work order When the results are returned to the UI Then cost and duration ranges show P50 and P90 point estimates And min, most-likely, and max values are shown for both cost and duration And min <= P50 <= P90 <= max for both cost and duration And a confidence indicator is displayed as High (>=0.80), Medium (0.60-0.79), or Low (<0.60) based on model confidence score And values are rounded to currency cents and 0.1 hours And no negative values are displayed; values are floored at 0 with an error logged if encountered
Input Factor Integration and Traceability
Given an estimate is generated for a specific property, issue type, vendor, and target service date When the calculation trace is opened in the Factors panel Then the following inputs are present with values: local labor rate, travel time estimate, vendor on-time rate, historical variance for the issue type, seasonality factor And each input shows its contribution to cost and duration as an additive delta or percentage And the sum of contributions plus base equals the presented P50 estimates within ±1% rounding tolerance And input timestamps comply with freshness SLAs (labor rates and vendor metrics updated within 24 hours; seasonality within 7 days)
Sensitivity Analysis and What-If Adjustments
Given the baseline estimate is visible When the user increases the labor rate by 10% using the what-if control Then the cost P50 increases by 10% ±1% and updates within 1.5 seconds And when the user switches to a vendor with 20% faster historical duration, the duration P50 decreases by at least 10% within 1.5 seconds And a ranked sensitivity list displays the top 3 drivers by absolute impact on cost and duration
SLA Breach Flagging and Schedule Recommendations
Given a work order has a resolve-by SLA with 48 hours remaining When the predicted P90 duration exceeds the remaining SLA window Then the system flags severity as High if excess >20%, Medium if 5-20%, Low if <5% And at least one recommendation is presented that, if applied, reduces P90 below the SLA (e.g., earlier slot, faster vendor) And applying a recommendation recalculates the estimate within 2 seconds and updates the SLA status And if no recommendation can reduce P90 below the SLA, an escalation alert is generated to the designated channel
Backtesting Coverage and Auto-Calibration
Given the nightly backtest runs on the last 90 days of closed work orders with valid actuals When coverage metrics are computed Then at least 85% of actual costs and durations fall within their respective predicted P90 bands And between 40% and 60% of actuals are less than or equal to the predicted P50 And if any metric breaches its threshold for 3 consecutive runs, a calibration job is queued and a notification is logged to the monitoring channel And post-calibration, the next backtest run records the new model version and improved metrics or marks the run as failed
Seasonality Adjustment Behavior
Given an HVAC heating issue in a cold-climate location during a peak winter month When the estimate is computed for a service date in January Then a seasonality factor >= 1.2 is applied to either cost or duration relative to the off-peak baseline And when the service date is changed to June with all else equal, the P50 cost and duration decrease by at least 10% And the UI displays a Seasonal adjustment applied label with a tooltip explaining the factor
Auto-Approval Policy Engine
"As a portfolio owner, I want low-risk repairs auto-approved within my limits so that work proceeds without unnecessary delays."
Description

Evaluate predicted cost, duration, and risk against configurable approval thresholds to recommend instant pre-approval for low-risk jobs and route high-risk cases for review. Support per-portfolio policies, vendor whitelists, maximum spend caps, and audit logs for compliance. Provide clear rationale and allow managers to override with justification. Integrate with FixFlow’s approval workflow and notifications to accelerate decisions while maintaining control.

Acceptance Criteria
Instant Pre-Approval for Low-Risk Within Policy
Given a predicted job with cost <= portfolio.preApprovalCap AND duration <= portfolio.durationThreshold AND riskScore <= portfolio.riskThreshold When the policy engine evaluates the prediction for that portfolio Then the decision is set to "Pre-Approved" And the rationale includes cost, duration, riskScore, thresholds, and policyVersion And an approval event is posted to the FixFlow workflow And manager notification is sent within 60 seconds And an audit log entry is created with timestamp, portfolioId, policyVersion, input values, and decision.
Route High-Risk or Over-Threshold Jobs to Manual Review
Given a predicted job where any threshold is exceeded (cost > cap OR duration > threshold OR riskScore > threshold) When the policy engine evaluates the prediction Then the decision is set to "Needs Review" And the rationale lists each breached threshold with actual vs limit And the job is placed in the portfolio approver queue And no vendor is notified And an audit log entry is recorded with reasonCodes.
Per-Portfolio Policy Selection and Isolation
Given multiple active portfolios with distinct policy configurations And a job is associated to portfolio X When the policy engine evaluates the job Then only policy X is applied And the decision is reproducible by re-evaluating with policy X and the same inputs And the audit log stores portfolioId, policyVersionId, and hash of inputs.
Vendor Whitelist and Vendor-Specific Caps
Given portfolio X has a vendor whitelist and optional vendor-specific caps And a job qualifies for pre-approval under portfolio caps When the assigned vendor is on the whitelist and within any vendor-specific cap Then the decision includes "Pre-Approved" with vendorId And the workflow recommends dispatch to that vendor And if the assigned vendor is not on the whitelist or exceeds vendor-specific cap, the decision is "Needs Review" with reason.
Maximum Spend Cap Enforcement (Per Unit and Portfolio)
Given portfolio and/or unit-level maximum spend caps are configured for period P And the system has period-to-date spend for the unit and portfolio When predictedCost + periodToDateSpend exceeds any configured cap Then the decision is "Needs Review" And the rationale includes predictedCost, periodToDateSpend, cap, and period P And the audit log captures these fields.
Manager Override With Mandatory Justification
Given a user with approver role views a policy decision When the user overrides the decision to Approve or Reject Then a justification of at least 10 characters is required And the override is recorded with userId, timestamp, oldDecision, newDecision, justification, and policyVersion And notifications and workflow state reflect the override immediately.
Workflow Integration, Notifications, and Dedupe
Given the policy engine publishes a decision When the FixFlow workflow receives the event Then the approval state updates within 2 seconds at the 95th percentile And notifications are sent to manager, tenant, and vendor only when appropriate (vendor only on Approve/Pre-Approved) And duplicate notifications are deduplicated within a 10-minute window And failures are retried with exponential backoff up to 3 times and logged.
Explainability & Feedback Loop
"As a property manager, I want to see why a recommendation was made and report the actual outcome so that the system gets smarter over time."
Description

Display the rationale behind predictions, including key lookalike cases, influential features, and uncertainty indicators, to build user trust. Capture user feedback (accept, edit, override) and actual outcomes (final parts, cost, and time) post-completion to continuously retrain models and monitor drift. Integrate with analytics and model lifecycle management to measure accuracy, bias, and impact over time, closing the loop between predictions and real-world results.

Acceptance Criteria
Prediction Detail Rationale Visibility
- Given a repair prediction is displayed in the job detail view, When the user opens the Rationale panel, Then show 3–5 lookalike cases each with thumbnail, title, and similarity score between 0.00 and 1.00 (rounded to two decimals). - Given the Rationale panel is open, Then display the top 5 influential features with human-readable labels and signed contribution weights, and provide a tooltip definition for each feature. - Given a prediction exists, Then surface an uncertainty indicator showing cost and time P50 and P90 ranges and a risk tier (Low/Medium/High) based on configurable thresholds. - Given the Rationale panel is requested on a 4G connection, Then time-to-first-contentful-render for the panel is ≤ 800 ms in 95th percentile. - Given the panel is rendered, Then it is fully keyboard navigable and meets WCAG 2.1 AA for color contrast, with ARIA labels on lookalike items and feature rows.
Lookalike Case Drill-Down
- Given a user clicks any lookalike case in the Rationale panel, When the drill-down modal opens, Then display anonymized case summary including final parts list, total cost, total time, issue category, and property type, with tenant/owner PII omitted or masked. - Given the drill-down is displayed, Then show the top 3 matching features and their contribution to similarity with scores. - Given the user has permission to view originating work orders, Then provide a link to the source record; When the user lacks permission, Then hide the link and display a permission notice. - Given a lookalike drill-down is opened, Then an audit log entry is recorded capturing userId, timestamp, predictionId, lookalikeId, and action "view_lookalike".
Feedback Capture: Accept, Edit, Override
- Given a prediction is shown, When the user selects Accept, Then persist a feedback event with userId, timestamp, predictionId, and set plan version v1 = predicted plan; show confirmation within 500 ms. - Given a prediction is shown, When the user selects Edit and modifies parts, cost, or time, Then save plan version v2 with a diff from v1; require a reason code from a controlled list and allow an optional comment. - Given a prediction is shown, When the user selects Override and enters a custom plan, Then persist the override plan as the active plan; require a reason code; flag the prediction as overridden for training labels. - Given any feedback event is saved, Then it is visible in the audit trail with who, what, when, and before/after values, and is queryable via analytics within 5 minutes (p95).
Outcome Capture on Work Order Closure
- Given a work order linked to a prediction is moved to Closed, Then require entry of final parts list, total labor hours, total cost (currency), and completion timestamp; block closure if any required field is missing or invalid. - Given outcomes are saved, Then compute deltas vs predicted plan and assign labels: CostWithin15 (|actual-predicted|/predicted ≤ 15%), TimeWithin20 (|actual-predicted|/predicted ≤ 20%), and PathMatch (actual fix path equals predicted). - Given outcomes are saved, Then write an outcome event joined to predictionId, workOrderId, and modelVersion to the analytics store within 5 minutes (p95), with write success rate ≥ 99%. - Given outcomes are saved, Then display a post-mortem summary in the job timeline including predicted vs actual for parts, cost, and time.
Training Data Ingestion and Quality Gates
- Given the daily ingestion job runs at 02:00 UTC, Then aggregate prior-day predictions, feedback, and outcomes into training datasets with a prediction-to-label join rate ≥ 95%. - Given ingestion executes, Then enforce schema validation with batch rejection if > 1% of records fail schema; emit counts of total, valid, invalid. - Given records are written, Then PII detection must report 0 PII findings in training and feature stores; any finding fails the job and notifies MLOps. - Given datasets are produced, Then referential integrity checks across feature, label, and metadata tables pass at ≥ 99.9%. - Given the job completes, Then success/failure metrics and lineage are published to the MLOps dashboard and pager alerts fire on failure within 5 minutes.
Model Performance and Drift Monitoring
- Given a model is deployed, Then compute weekly metrics: Cost MAPE ≤ 25%, Time MAPE ≤ 30%, Fix-Path Top-1 Accuracy ≥ 70%, and Brier Score ≤ 0.20; breaches for 2 consecutive weeks trigger a Critical alert to Slack and email. - Given predictions are being served, Then track coverage (predictions/emittable requests) ≥ 98%; falling below triggers a Warning alert. - Given feature distributions are logged, Then compute PSI for top 20 features weekly; PSI > 0.2 triggers Warning and PSI > 0.3 triggers Critical drift alert. - Given any alert is generated, Then create an incident with owner, timestamp, metric, threshold, and resolution target SLA of 48 hours.
Analytics Integration and Explainability Export
- Given any prediction is generated, Then emit an analytics event containing predictionId, modelVersion, timestamp, userOrgId, issue category, rationale payload (top lookalikes, feature weights), and uncertainty bands; event available in warehouse within 5 minutes with ≥ 99% delivery success. - Given feedback or outcomes occur, Then emit corresponding events with consistent keys (predictionId, workOrderId) enabling end-to-end lineage; reconciliation job reports discrepancy between monitoring and warehouse metrics < 2% daily. - Given analytics data is stored, Then retain 24 months of prediction, rationale, feedback, and outcome events with partitioning by date and modelVersion, and support GDPR deletion by predictionId within 30 days.

Confidence Meter

Displays AI triage confidence with human-readable reasons (e.g., “90% likely P-trap leak; mineral staining detected”). Offers one-click actions—request follow-up photos, switch to video, or schedule inspection—so approvers act with clarity and control.

Requirements

Calibrated Confidence Scoring
"As a property manager, I want a clear, reliable confidence score for the AI’s diagnosis so that I can decide next steps quickly and consistently across different issue types."
Description

Implement a service that normalizes and calibrates AI triage confidence across issue categories (plumbing, electrical, HVAC, etc.) and input modalities (photos, text, video frames). The service aggregates signals from underlying classifiers/detectors, applies per-class reliability calibration, and outputs top-N hypotheses with confidence scores and uncertainty bounds. It exposes a versioned API returning a structured payload (ticket ID, hypotheses, scores, uncertainty, model version, timestamp) within sub-500ms P95 latency and graceful fallbacks when inputs are incomplete. Integrates with FixFlow’s triage pipeline and persists results on the ticket for downstream UI, actions, and analytics.

Acceptance Criteria
P95 Latency Under Load
Given the calibrated scoring service receives requests at 50 RPS with mixed modality payloads representative of production When 10,000 valid requests are processed within 10 minutes Then the end-to-end service P95 latency at the API boundary is <= 500 ms and P99 <= 800 ms And the successful response rate is >= 99.5% And no single request exceeds 2,000 ms
Structured Versioned API Response
Given a request with ticketId and any subset of inputs (photos, text, videoFrames) When the service returns a response Then the JSON payload includes: ticketId, apiVersion, modelVersion, timestamp (ISO 8601 UTC), hypotheses[] And each hypotheses[] item includes: class, score [0,1], uncertainty.lower, uncertainty.upper And the payload validates against the published JSON Schema without additional properties causing failure
Top-N Hypotheses With Calibrated Confidence and Uncertainty
Given valid inputs spanning multiple issue categories When scoring completes Then the service returns the top 3 hypotheses sorted by score descending (or fewer if fewer plausible) And each score is within [0,1] with at most 3 decimal places And uncertainty bounds are present for each hypothesis with 95% coverage semantics (lower <= score <= upper) And the sum of scores across all returned hypotheses is <= 1.0
Graceful Fallbacks on Incomplete Inputs
Given one or more modalities are missing or unreadable When the service processes the request Then it returns 200 OK with degraded=true, fallbackReason set, and inputModalitiesUsed enumerated And uncertainty intervals are widened by at least 10% relative to full-modality baselines for the same class And no 5xx error is returned for incomplete-but-valid inputs And if all inputs are invalid or empty, then 422 Unprocessable Entity is returned with errorCode=INSUFFICIENT_INPUT
Per-Class Calibration Quality Thresholds
Given a held-out calibration dataset per class with >= 500 samples and the current modelVersion When reliability is evaluated Then the per-class Expected Calibration Error (ECE, 10-bin) is <= 0.07 And the max calibration error across bins is <= 0.15 for each class And the Brier score is improved by >= 5% versus the uncalibrated baseline for each class And for classes with < 500 samples, the system flags low-data and sets uncertainty.minWidth >= 0.2
Persistence and Downstream Availability
Given a successful scoring response When the ticket is retrieved via FixFlow API within 1 second Then the persisted scoring result is available on the ticket with an immutable record including ticketId, hypotheses, scores, uncertainty, modelVersion, and timestamp And an event is published to the analytics stream within 60 seconds with the same correlationId And repeated scoring with the same inputs is idempotent, producing the same resultHash and not duplicating ticket history; changing inputs or modelVersion increments revision
Explainable Reasons Generation
"As an approver, I want understandable reasons behind the confidence score so that I can trust the recommendation and justify my decision to owners and tenants."
Description

Create an explainability layer that converts model features and detections into concise, human-readable reasons (e.g., “mineral staining detected near trap,” “audible drip pattern,” “prior leak history on unit”). Implement a templating engine with a controlled vocabulary, evidence attribution (image region, timestamp, sensor/metadata), and privacy/PII redaction. Support multi-language strings, confidence qualifiers, and truncation rules to keep explanations scannable. Provide links to visual callouts (e.g., bounding boxes) when available and degrade gracefully if evidence is weak. Output slots map directly to the Confidence Meter UI.

Acceptance Criteria
Controlled Vocabulary Templating
Given model features and detections are available When reasons are generated Then each reason uses only phrases from the approved controlled vocabulary and allowed numeric/unit placeholders And each reason is produced via a registered template bound to the detection type And no out-of-vocabulary terms appear in the output And any unsupported term is mapped to the nearest approved synonym or the token is omitted while preserving grammatical correctness And the API response includes vocabulary_version and template_id for each reason
Evidence Attribution and Visual Callouts
Given a reason is derived from visual, audio, or metadata evidence When the reason is generated Then the response includes evidence.type in {image, audio, metadata} And for image evidence, evidence.bbox is present as normalized [x,y,w,h] with 0.0–1.0 bounds and a frame_timestamp in ISO 8601 And for audio evidence, evidence.timestamp_range is present with start/end in ISO 8601 and evidence.sample_rate_hz And for metadata evidence, evidence.source and evidence.record_id are present And evidence.link is provided when a visual/audio callout is available and is a valid URL And clicking the link highlights the referenced region/segment in the UI without error And if any required evidence field is unavailable, evidence.available=false and the reason still renders without breaking the UI
PII Redaction in Explanations
Given input media or metadata contains PII (names, phone numbers, emails, exact street addresses, license plates, SSNs, GPS coordinates) When reasons are generated and rendered Then all detected PII is replaced with redaction tokens (e.g., [redacted name], [redacted phone]) in the reason text and evidence captions And no raw PII appears in the API response fields text, captions, or evidence.link query parameters And redaction is applied before translation and truncation And a redaction_applied=true flag with redaction_types[] is included per reason when any PII is removed And unit identifiers are masked to building-level granularity (e.g., address only, no unit number) And the system logs a non-PII-bearing audit event for each redaction
Multi-language Output and Fallback
Given the requester locale is set (e.g., en-US, es-ES) When reasons are generated Then the response locale matches the requester locale And each reason text is properly localized for that locale including numbers and units And if a translation key is missing, the system falls back to English (en) and sets reason.fallback=true And right-to-left locales render with correct directionality metadata (dir=rtl) And all strings are valid UTF-8 and pass a round-trip encode/decode check And unit tests verify at least English and Spanish coverage for the controlled vocabulary
Confidence Qualifiers and UI Slot Mapping
Given a model confidence value in [0.0,1.0] When reasons are generated Then the payload includes overall_confidence_percent rounded to the nearest 5% And each reason includes a qualifier in {high, medium, low} using thresholds: high≥0.80, medium∈[0.50,0.79], low<0.50 And reasons are sorted by contribution_weight descending And the response conforms to schema ConfidenceMeterExplainability.v1 with keys: overall_confidence_percent, reasons[].text, reasons[].qualifier, reasons[].evidence, reasons[].locale, reasons[].truncated And the Confidence Meter UI renders the payload without any transformation or missing-field errors
Truncation and Scannability Rules
Given long reason texts may exceed display constraints When reasons are generated Then each reason text is limited to 90 characters and at most 2 clauses separated by a semicolon And truncation applies on word boundaries with a single ellipsis character (…) appended when truncated And reasons[].truncated=true when truncation occurs, else false And no more than the top 3 reasons are returned; any extras are omitted but logged And truncation never removes required qualifiers or redaction tokens And tooltips or detail views render the full, untruncated text when available
Graceful Degradation on Weak or Missing Evidence
Given detections with low evidence strength or missing artifacts When reasons are generated Then reasons with evidence_strength<0.30 are labeled with qualifier=low and evidence.available=false And evidence.link is omitted when the callout cannot be produced And if no reasons meet minimum quality, the API returns an empty reasons[] with message="No explainable evidence available" and HTTP 200 And action affordances in the UI remain enabled (e.g., request photos, start video, schedule inspection) And no runtime exceptions are thrown, and the payload validates against the schema
Auditability and Traceability of Explanations
Given a generated reason When the API response is produced Then each reason includes provenance fields: model_version, template_id, feature_ids[], and timestamp And a reproducible hash is provided for the set of inputs and template parameters (reason_hash) And an audit log entry is created linking request_id to reason_hash without storing PII And QA can replay the explanation with the same inputs to produce the same reason_hash
Confidence Meter UI & One-Click CTAs
"As a landlord reviewing a new ticket, I want a visual meter with reasons and quick actions so that I can resolve uncertainty and move the request forward in seconds."
Description

Build a responsive widget that displays the primary hypothesis, confidence percentage with color-coded states, top supporting reasons, and contextual one-click actions: request follow-up photos, switch to live video, or schedule an inspection. Include hover/tooltips for evidence details, accessible labels, keyboard navigation, and dark mode compatibility. The widget appears on ticket detail views in web and mobile, respects role-based visibility, and updates in real time when new evidence arrives. Handle edge states (low confidence, conflicting hypotheses, missing media) with clear guidance.

Acceptance Criteria
Primary Hypothesis & Confidence Visualization
Given a ticket with an AI triage result When the ticket detail view loads on web or mobile Then the widget displays the primary hypothesis label, the confidence as a whole percent (0–100%), and a color state of green (>=80%), amber (50–79%), or red (<50%). Given dark mode is enabled When the widget renders Then all colors meet WCAG 2.1 AA contrast (>=4.5:1) and confidence colors map to dark-mode equivalents. Given a keyboard-only user When navigating the widget Then all interactive elements are reachable in a logical tab order, have visible focus states, support Enter/Space activation, and expose ARIA labels announced by screen readers. Given a viewport width between 320px and 1440px When the widget renders Then no horizontal scrolling is required, tap targets are at least 44x44 px on touch devices, and text does not overlap.
Supporting Reasons & Evidence Tooltips
Given AI provides supporting reasons When the widget renders Then it shows the top three reasons in order of contribution with human-readable phrases. Given a user hovers, focuses, or taps a reason When the evidence tooltip is invoked Then it shows the underlying evidence details and can be dismissed via Esc, click/tap outside, or a visible Close control. Given no supporting reasons are available When the widget renders Then it displays "No supporting evidence available" and does not render empty tooltips.
One-Click: Request Follow-up Photos
Given a user with triage.act permission When they click Request follow-up photos Then a modal opens with pre-selected suggested shots based on missing evidence and a prefilled message template. Given the user sends the request When the action completes Then the tenant is notified via the default channel, the request is added to the ticket audit trail with timestamp and actor, and a success toast appears. Given the request fails When the API returns an error Then the user sees an error message with retry option and the action is not duplicated.
One-Click: Switch to Live Video
Given a user with triage.act permission and the tenant device supports live video When the user clicks Switch to live video Then the system generates a session link, sends it to the tenant, and displays a "Waiting for tenant to join" state with cancel option. Given the tenant device does not support live video When the action is attempted Then the user is offered to schedule a video-assisted inspection or send a photo request instead. Given the session starts When both parties join Then the ticket updates with session start/stop times and the action is logged.
One-Click: Schedule Inspection
Given a user with scheduling permission When they click Schedule inspection Then a scheduling modal opens with technician list filtered by skill, soonest availability, and property location constraints, defaulted to the next available slot within 72 hours. Given the user confirms a slot When the booking is saved Then an event is created on the selected technician calendar, tenant and technician notifications are sent, and the ticket status updates to "Inspection scheduled." Given conflicting calendars When a selected slot becomes unavailable before confirmation Then the user is prompted to choose another slot without saving partial data.
Role-Based Visibility & Permission Controls
Given a user without triage.view permission When the ticket detail loads Then the Confidence Meter widget is not rendered. Given a user with triage.view but without triage.act When the widget renders Then CTAs are disabled with tooltips indicating insufficient permissions and no backend action is invoked on interaction. Given permission changes at runtime When the page is refreshed or permissions are re-fetched Then the widget updates visibility and enabled states accordingly.
Real-Time Updates and Edge States
Given new evidence (photo, video, or technician note) is added to the ticket When the evidence is processed Then the hypothesis and confidence update in the widget within 5 seconds without a full page reload and an "Updated just now" indicator appears. Given the top two hypotheses differ and their confidences are within 5 percentage points When the widget renders Then a "Low consensus" banner appears with guidance to request more evidence or schedule an inspection. Given confidence is below 30% or required media is missing When the widget renders Then clear guidance is shown and Request follow-up photos is surfaced as the primary CTA.
Action Orchestration & Messaging
"As a property manager, I want one-click follow-ups and scheduling from the confidence panel so that I can gather the right evidence or book help without leaving the ticket."
Description

Implement backend workflows for one-click actions initiated from the Confidence Meter. For follow-up photos, generate guided prompts with examples and send via SMS, email, and in-app messaging; ingest replies and auto-attach to the ticket. For live video, initiate a secure video session link and route participants based on availability. For inspections, integrate with scheduling (calendar sync, technician availability, service windows) and vendor selection rules. Enforce rate limits, retries, localization of templates, and status updates back to the ticket timeline.

Acceptance Criteria
One-Click Follow-Up Photo Request Orchestration
Given an approver clicks "Request follow-up photos" from the Confidence Meter on ticket T When the action is triggered Then the system generates guided prompts with 2–3 example images based on the current AI triage category and confidence And messages are sent to the tenant via SMS, email, and in-app per tenant preferences within 5 seconds (p95), localized to the tenant’s locale, and include a secure upload link bound to ticket T And per-recipient limits of max 1 request per 10 minutes and 3 per 24 hours are enforced; attempts over limit return an explanatory error to the approver and send nothing And send results (Queued/Sent/Delivered/Failed) are captured and written to ticket T’s timeline with channel and timestamp And if the primary channel fails, exactly one fallback channel is attempted; retries do not produce duplicate prompts
Multi-Channel Reply Ingestion and Auto-Attach
Given a tenant replies to the prompt via SMS/MMS, email, or in-app with up to 12 media attachments When the system ingests the reply Then files of type jpg/jpeg/png/heic/mp4 up to 10 MB (SMS/MMS) and 25 MB (email/in-app) are accepted; others are rejected with a channel-appropriate error message And attachments are virus-scanned; HEIC is converted to JPEG; perceptual hashing de-duplicates images within ticket T And accepted media are stored in secure object storage, auto-attached to ticket T with source channel metadata, and thumbnails are generated And the ticket timeline is updated within 5 seconds (p95) with a "Photos received" entry showing count and thumbnails; the approver receives an in-app notification And any reply lacking the secure token or failing signature verification is discarded and logged without attaching to ticket T
Live Video Session Initiation and Routing
Given an approver clicks "Switch to live video" from the Confidence Meter on ticket T When the action is triggered Then a secure video session link with a single-use token expiring in 2 hours is generated and sent to the tenant and to an available technician/approver based on current availability And routing selects a technician with required skill tags; if none are available now, a scheduling prompt with the next three slots is sent And consent for recording is collected; with consent, the recording is stored and attached to T; without consent, no recording occurs And unsuccessful joins or call failures trigger one retry and an automatic reschedule offer; all state changes are posted to T’s timeline
Inspection Scheduling with Calendar Sync & Vendor Rules
Given an approver clicks "Schedule inspection" from the Confidence Meter on ticket T When the action is triggered Then the vendor selection engine chooses a technician/vendor matching required skill, service area, SLA, and availability, preferring lowest estimated total cost within SLA And the scheduler proposes at least three service windows within the next 5 business days, respecting tenant time zone and building access constraints And upon confirmation, an event is created, ICS invites are sent to all participants, and the technician’s Google/Microsoft calendar is updated within 15 seconds (p95) with conflict detection preventing double-booking And reschedules/cancellations update calendars, notify all parties, and write entries to T’s timeline; failure to sync raises an alert and marks the booking Pending Sync without double-booking
Rate Limits, Retries, and Idempotency for Actions
Given an approver clicks any one-click action multiple times within 60 seconds for ticket T When the backend processes the requests Then idempotency keys ensure only one action instance is created per action type per 60-second window And transient send/schedule failures are retried up to 3 times with exponential backoff (2s, 4s, 8s) and jitter; permanent failures are not retried and are surfaced to the approver And no duplicate messages, sessions, or bookings are created; all retry attempts are logged with outcome codes
Template Localization and Channel Compliance
Given tenant locale and timezone are set on ticket T When the system generates message templates and links Then content is localized for date/time, measurements, and language (supported: en, es, fr; fallback: en) and renders correctly for right-to-left languages where applicable And SMS content remains under 600 characters including links; URLs are shortened; email subject/body render without unresolved placeholders; in-app messages support rich text and image examples And unresolved or missing template variables block sends and raise an alert; no message is sent with raw placeholders
Ticket Timeline and Status Synchronization
Given any one-click action is initiated from the Confidence Meter When the action transitions through states (Initiated, Queued, Sent, Delivered, Failed, Completed, Canceled) Then a timeline entry is created for each state with actor, channel, timestamp, and correlation ID And the ticket shows the current action status badge in real time; entries are filterable by action type; and artifacts (photos, video recording, calendar event) are linked And errors include provider error codes and human-readable reasons; successes include counts and delivery/booking confirmations
Thresholds & Escalation Rules
"As an operations lead, I want adjustable confidence thresholds and safety rules so that our team balances speed with risk across different property portfolios."
Description

Provide configurable thresholds that determine when to auto-request more evidence, escalate to human review, or allow direct scheduling. Include category-specific thresholds (e.g., gas leak = always escalate), SLA-aware timers, and after-hours behaviors. Actions triggered by thresholds must be logged with rationale and reversible by approvers. Offer environment-level configuration with per-portfolio overrides and safe defaults. Include simulation tools to test rule outcomes before deployment.

Acceptance Criteria
Category-Specific Confidence Thresholds Determine Actions
Given environment=Production with safe defaults {schedule>=85, humanReview 60-84, evidenceRequest<60} and a category override {Plumbing>P-trap schedule>=80} When an incident is triaged as Plumbing>P-trap with confidence=82 and rationale available Then the system selects action=Direct Schedule And displays confidence=82% and top 3 rationale items And records an action log with {ruleId, ruleVersion, inputs:{category, confidence}, selectedAction, timestamp}
Always Escalate Safety-Critical Categories
Given the incident category is in {Gas leak, Carbon monoxide, Electrical burning smell} marked safetyCritical=true When AI triage returns any confidence value Then the system selects action=Escalate to Human Review And bypasses auto-scheduling and evidence requests And after-hours the case is routed to the on-call human per configuration And the action log includes rationale "safetyCritical=true"
SLA-Aware Evidence Request and Escalation Timers
Given action=Evidence Request was triggered at T0 and SLAs are {standard=30m, critical=10m} and afterHoursMode=Defer for non-critical When no tenant response is received by T0 + SLA for the incident severity Then the system escalates action=Human Review And the log records previousAction, elapsedTime, and escalationReason="SLA expired" And if after-hours and severity=non-critical and afterHoursMode=Defer, timers pause and resume at 08:00 local next business day
Environment and Portfolio Overrides With Precedence
Given environment-level rules R_env exist and portfolio-level overrides R_port are configured for Portfolio X When saving R_port Then precedence=Portfolio overrides Environment for overlapping keys; Environment provides defaults otherwise And the validator prevents gaps/overlaps in thresholds and conflicting behaviors, returning explicit errors until resolved And at runtime, incidents in Portfolio X use R_port; other portfolios use R_env
Action Logging and Reversibility by Approvers
Given the system auto-triggered an action based on thresholds When an approver clicks Undo or selects an alternative action Then the original action is reversed and any pending notifications are canceled before send And a reversal log entry is created with {actor, reason (required), timestamp, previousAction, newAction} And the UI reflects the current action state and shows the full audit trail
Simulation and Dry-Run of Thresholds Before Deployment
Given a draft rule set and a selected historical dataset {last 90 days or a random sample of >=100 incidents} When the user runs Simulation Then the system outputs action distribution, delta vs current rules, list of affected incident IDs, and projected SLA impact And no live actions or notifications are executed And Deploy remains disabled until the user acknowledges the simulation summary
Direct Scheduling Guardrails
Given the selected action would be Direct Schedule due to high confidence When vendor coverage, availability within SLA, or property access notes are missing Then the system falls back to action=Human Review And logs guardrailFailure reasons And if optOutWindowMinutes>0, the appointment is queued and notifications delayed until the window expires without reversal
Outcome Feedback & Model Learning
"As a technician and approver, I want an easy way to confirm or correct the AI’s diagnosis so that the system improves and future triage becomes more accurate."
Description

Capture approver confirmations/corrections of the AI hypothesis and the final resolution after work order completion. Store labeled outcomes, evidence snapshots, and deltas between initial and final diagnoses to measure drift and enable model retraining. Provide lightweight prompts for feedback in the UI, consent controls, and export pipelines to the ML training store. Feedback should not block workflow and must be auditable.

Acceptance Criteria
Approver Confirms AI Hypothesis at Triage
Given an approver views a maintenance intake with the Confidence Meter visible When the approver clicks "Confirm" Then the system records a confirmation event linked to the work order and triage ID And stores the AI hypothesis label, confidence score, reason snapshot, approver ID, and timestamp And the confirmation is visible in the audit log within 5 seconds And the prompt dismisses without blocking other actions And the event is queued for export if consent is enabled
Approver Corrects AI Hypothesis at Triage
Given an approver views a maintenance intake with the Confidence Meter visible When the approver selects "Correct" and chooses a different issue label (or selects "Other" and provides up to 200 characters) Then the system records a correction event linked to the work order and triage ID And preserves the original AI hypothesis and confidence And computes and stores a delta between the AI hypothesis and the corrected label And the correction is visible in the audit log within 5 seconds And the action does not block approving, messaging, or scheduling And the event is queued for export if consent is enabled
Final Resolution Capture at Work Order Close-Out
Given a work order is transitioning to Completed When the closer is prompted to select a final resolution label from a controlled list, optionally add a note (up to 500 characters), and upload up to 5 photos Then selecting Save records a final resolution event with label, note, media references, closer ID, and timestamp And computes and stores a delta between the final resolution and the latest hypothesis/correction And selecting Skip still completes the work order without delay And the event (if saved) is visible in the audit log within 5 seconds and queued for export if consent is enabled
Evidence Snapshot Storage and Versioning
Given any feedback event (confirm, correct, final resolution) is submitted When the system persists the event Then it creates an immutable evidence snapshot capturing AI model version, top-3 hypotheses, confidence, reason text, input media references, UI action, and consent state And assigns a monotonically increasing version for the work order feedback stream And snapshots are retrievable by authorized roles within 2 seconds via work order ID And all snapshots are append-only and cannot be edited after creation
Consent Controls for Feedback Collection and Export
Given an org admin views Data & Model Learning settings When the admin toggles consent off Then the UI still allows feedback submission but marks records as "Not for export" and excludes them from all exports And the consent change (actor, timestamp, old/new value) is recorded in the audit log When the admin toggles consent on Then subsequent feedback and final resolutions are included in exports And the current consent state is displayed in settings and available via API
Export Pipeline to ML Training Store
Given the export job runs on its configured schedule When it executes Then it exports all new and updated feedback events since the last successful run in the agreed schema (org ID, work order ID, event type, labels, confidence, deltas, timestamps, model version, media references/hashes, consent state) And returns success metrics including record count and watermark And retries failed batches up to 3 times with exponential backoff And raises an alert on final failure And excludes records when consent is disabled And supports an authorized on-demand export trigger
Auditability and Non-Blocking Feedback Workflow
Given users interact with the feedback prompts during triage or close-out When they choose Confirm, Correct, Save, or Skip Then no feedback action blocks core workflow actions (approve, schedule, message) from completing immediately And Confirm requires at most 1 click; Correct requires at most 4 clicks And all interactions are recorded in an append-only audit log with actor ID, role, timestamp, IP (if available), object IDs, and before/after values And authorized users can query the audit log by work order ID or date range with results in under 2 seconds
Audit & Confidence Analytics
"As a product owner, I want confidence and outcome analytics so that I can verify accuracy, tune thresholds, and demonstrate ROI to stakeholders."
Description

Deliver analytics that track confidence distribution, calibration curves, hypothesis accuracy by category, action utilization rates, time-to-approval, and downstream impact on emergency repair costs and response times. Include cohort filters (property, vendor, season), A/B comparisons across model versions, and export to CSV/BI. Retain audit logs tying each decision to confidence, reasons, and actions taken, with configurable data retention to meet compliance requirements.

Acceptance Criteria
Confidence Distribution & Cohort Filtering
- Given a date range and cohort filters (property, vendor, season) are selected, when the dashboard loads, then a histogram of confidence scores from 0–100% renders with 20 equal-width bins showing count and percentage per bin. - Given the user modifies any filter, when Apply is clicked, then all visualizations and counts refresh within 2 seconds for datasets ≤100k triage events. - Given multiple properties or vendors are selected, when filters are applied, then results use OR within a dimension and AND across dimensions, as indicated in the filter summary. - Given a user brushes a range on the histogram, when the brush is released, then an event table displays only matching events with columns [event_id, timestamp, property_id, vendor_id, predicted_category, model_version, confidence]. - Given the user clicks Export CSV, when the export completes, then a CSV downloads containing the table columns above and a header row noting filter values and date range.
Calibration Curves & Reliability Metrics
- Given a dataset with ≥1,000 predictions, when Calibration is opened, then a reliability curve renders using 10 equal-frequency bins and displays per-bin accuracy vs average confidence. - Given the user switches binning to equal-width and sets bins=20, when Apply is clicked, then the curve re-renders accordingly and updates ECE and Brier score values. - Given two model versions are selected (A and B), when Compare is toggled, then curves, ECE, and Brier score for both models render side-by-side with ΔECE displayed. - Given the user enables Confidence Intervals, when the chart renders, then 95% bootstrap CIs are shown for per-bin accuracy. - Given the user clicks Export CSV, when the export completes, then a CSV downloads with columns [model_version, bin_lower, bin_upper, n, avg_confidence, accuracy, ece_contrib].
Hypothesis Accuracy by Category
- Given ground truth outcomes are available, when the Category Accuracy view loads, then for each category the system displays support (n), accuracy, precision, recall, and F1 at the default decision threshold. - Given the user adjusts the decision threshold slider, when released, then the metrics recalculate within 2 seconds and an updated confusion matrix is rendered. - Given categories with support n<50, when metrics are displayed, then such categories are flagged with a low-sample warning icon and excluded from aggregate accuracy unless explicitly included. - Given the user opens Top Reasons, when loaded, then the top 5 human-readable reason phrases are shown for correct and incorrect predictions per category. - Given the user exports, when Export CSV is clicked, then a CSV downloads with columns [category, n, accuracy, precision, recall, f1, threshold, top_reasons_correct, top_reasons_incorrect].
Action Utilization & Funnel Analytics
- Given the Action Utilization view is opened, when a date range and cohorts are selected, then utilization rates for Request Photos, Switch to Video, and Schedule Inspection are displayed as actions per 100 triage events with 95% CIs. - Given the user clicks View Funnel, when loaded, then a funnel shows counts and conversion rates across stages: Triage -> Action Taken -> Approval -> Work Order Scheduled -> Resolved. - Given a model version is selected, when Compare to Other Version is toggled, then utilization and funnel metrics for both versions are shown side-by-side with absolute and relative deltas. - Given the user hovers a metric, when tooltip appears, then the metric definition and denominator are shown. - Given Export is clicked, when complete, then a CSV downloads with columns [date, property_id, vendor_id, model_version, action, events, actions_taken, utilization_rate, stage, count].
Time-to-Approval Metrics & SLA Impact
- Given the Time-to-Approval view is opened, when a date range and cohorts are selected, then median, p75, and p90 time from AI triage to approval are displayed, segmented by confidence bands [0–50), [50–80), [80–95), [95–100]. - Given an SLA target (e.g., 2 hours) is configured, when metrics render, then the percentage of approvals exceeding the SLA is displayed by cohort and confidence band. - Given the user selects a data point, when Drill In is clicked, then a table of underlying events with [event_id, triage_time, approval_time, time_to_approval, confidence_band, model_version] is shown. - Given Export is clicked, when complete, then a CSV downloads of the drill-in table and a separate summary CSV with per-cohort percentiles and SLA breach rates.
Downstream Impact on Emergency Costs & Response Times
- Given cost and response datasets are connected, when the Impact view loads, then average emergency repair cost per event and mean time to technician arrival are displayed for baseline vs current selection with absolute and percentage deltas. - Given the user selects A/B by model version or Pre/Post date ranges, when Compare is clicked, then the system computes differences and shows 95% confidence intervals and p-values for cost and response time deltas. - Given outlier handling is set to exclude top 1% cost and time values, when applied, then metrics recompute and the exclusion rule is noted in the header. - Given Export is clicked, when complete, then two CSVs download: summary [group, metric, value, delta, ci_lower, ci_upper, p_value] and detailed [event_id, property_id, vendor_id, model_version, cost, response_time_minutes, group].
Audit Log Completeness & Retention Controls
- Given an AI triage event occurs, when the event is stored, then an immutable audit record is created containing [event_id, created_at_utc, property_id, tenant_id_hash, predicted_category, confidence, reasons[], approver_action, approver_actor_id, action_timestamp_utc, model_version, override_flag, final_outcome, ground_truth_category, ground_truth_timestamp_utc]. - Given Search is used with filters (event_id, property_id, date range, model_version, action, confidence range), when Apply is clicked, then matching audit records return within 2 seconds for datasets ≤100k records. - Given an admin sets retention=180 days (default 365), when saved, then records older than the retention period are queued for deletion/redaction within 24 hours and a retention-change audit entry is recorded. - Given a legal hold is applied to property_id X, when retention jobs run, then records under hold are preserved until hold removal and each skip is logged. - Given Export is clicked with BI option selected, when complete, then a CSV download starts and a scheduled nightly export to a secure bucket/endpoint is created including a schema file; all timestamps are ISO 8601 UTC. - Given an unauthorized user requests audit logs, when the request is made, then the API responds 403 and the attempt is logged with actor_id and timestamp.

Hazard Proof Pack

Auto-compiles detection labels, timestamps, tenant instructions delivered, and before/after photos into a mini report attached to the ticket. Creates insurance- and audit-ready evidence without extra effort, speeding reimbursements and compliance checks.

Requirements

Auto Evidence Compilation
"As an independent landlord, I want the system to automatically compile all hazard evidence into a single report so that I can submit claims quickly without manual collation."
Description

Automatically aggregates hazard-related data for each ticket—including detection labels and severity, key timestamps (intake, triage, instructions sent, technician scheduled/arrived, job completed), tenant instructions delivered, and before/after photos—into a structured, insurance-ready mini report. Generates a PDF attachment and accompanying JSON metadata stored with the ticket, with support for auto-generation on key status changes and on-demand regeneration. Handles incremental updates without duplicating content, versions each output, and maintains a consistent section order (Summary, Timeline, Instructions Proof, Media, Technician Actions). Ensures 95th percentile report generation under 15 seconds, localizes dates/times, and gracefully retries on transient failures. Integrates with FixFlow’s ticketing, media storage, and communications logs to provide a single source of truth without manual effort.

Acceptance Criteria
Auto-Generate on Key Ticket Status Changes
Given a hazard ticket exists with detection labels and severity When the ticket transitions to any of: Triage Completed, Instructions Sent, Technician Scheduled, Technician Arrived, Job Completed Then a new Evidence Report PDF and JSON metadata are automatically generated And both artifacts are attached to the ticket and visible in the ticket’s attachments list And the report content reflects all data available at the time of generation
On-Demand Evidence Report Regeneration
Given a user with permission to manage the ticket opens the Evidence Report panel When the user selects "Regenerate Report" Then the system generates a new version using current ticket data And increments the reportVersion And attaches the new PDF and JSON without duplicating prior content And prior versions remain accessible with immutable content and timestamps
Incremental Updates Without Duplication
Given report version N exists for a ticket And new media items and/or communications logs are added to the ticket When the report is auto-generated or regenerated Then items already included in version N are not duplicated in version N+1 And new items are appended in the appropriate sections in chronological order And media is deduplicated by mediaID or content hash And communications are deduplicated by messageID
Versioning and Section Order Consistency
Given any generated report for any ticket Then sections appear in this exact order: Summary, Timeline, Instructions Proof, Media, Technician Actions And both PDF and JSON include reportVersion (monotonically increasing) and generatedAt timestamp And filenames include ticketID and reportVersion And section headings/JSON keys use consistent labels across versions
Performance and Transient Failure Handling
Given normal operating conditions and representative ticket data When reports are generated (auto or on-demand) Then the 95th percentile end-to-end generation time is under 15 seconds measured over the most recent 1,000 generations And transient failures (e.g., 429, 502, timeouts) are retried up to 3 times with exponential backoff And retries do not create duplicate attachments And failures after retries are logged and surfaced, and no partial artifacts are attached
Timezone and Locale Localization
Given a ticket has a configured property timezone and locale When a report is generated Then all timestamps in the PDF are formatted in the property timezone and locale And all timestamps in the JSON are ISO 8601 with the correct timezone offset for the property timezone And daylight saving time transitions are handled correctly And if the property timezone is changed, regenerating the report reflects the new timezone
End-to-End Data Integration and Completeness
Given a ticket contains detection labels with severity; timestamps for intake, triage, instructions sent, technician scheduled, technician arrived, and job completed; tenant instructions delivered; and before/after photos in media storage When a report is generated Then the JSON includes all required fields and valid references to media IDs/URLs that resolve successfully And the PDF includes before/after photos with capture timestamps And the Timeline lists the key timestamps in chronological order And Instructions Proof includes message content, recipient, delivery channel, and delivery timestamp from communications logs And Technician Actions include scheduled, arrival, and completion entries with actor and timestamps And both PDF and JSON are stored with the ticket and retrievable via UI and API
Insurer-Ready Template & Branding
"As a property manager, I want the report to use insurer-friendly templates with my branding so that claims are accepted and owners receive a professional document."
Description

Provides a configurable template engine to render mini reports in insurer- and audit-friendly formats (PDF Letter/A4). Supports landlord/company branding (logo, colors), customizable headers/footers, and configurable field visibility to align with insurer requirements. Includes prebuilt templates for common claim types (water leak, electrical hazard, gas leak, mold) with compliant sections, standardized terminology, and optional disclaimers. Maps FixFlow data fields to template placeholders, validates required fields before rendering, and clearly flags missing items. All templates are versioned with change history and can be selected per property, insurer, or portfolio.

Acceptance Criteria
Insurer-Ready PDF Rendering, Branding, Headers/Footers
Given a configured template with branding (logo PNG/SVG), color palette (primary/secondary hex), custom header/footer (supports {{pageNumber}}/{{pageCount}}, {{property.address}}), disclaimers (all pages or last page), and selected paper size "Letter" or "A4" When a user renders a mini report to PDF Then the PDF page size matches the selected option (Letter: 8.5x11 in; A4: 210x297 mm) And the logo appears in the header with preserved aspect ratio, minimum 150 DPI, and does not overlap content And heading and accent elements use the configured colors exactly And headers/footers render on every page with correct variables resolved and page numbering accurate And disclaimers render per configuration without truncation And all fonts are embedded; no font substitution warnings occur And generation time is ≤ 5 seconds for a 10-page report with 10 images (1920x1080) And file size is ≤ 5 MB for a 5-page report with 6 images (1920x1080)
Field Visibility by Insurer Profile
Given an insurer profile with field visibility rules toggling On/Off for sections and fields (e.g., Detection Labels, Timestamps, Tenant Instructions Delivered, Before/After Photos, Technician Notes, Cost Summary, Disclaimers) When rendering a report for a ticket associated with that insurer Then only fields marked visible are present; hidden fields and their labels are fully omitted And the layout closes gaps with no empty placeholders or extra whitespace > 8 mm And a test render shows the visibility matrix matches configuration 100% And exporting with a different insurer uses that insurer’s visibility rules And removing an insurer profile reverts to the template’s default visibility
Prebuilt Templates for Common Claim Types
Given a new workspace When an admin opens the template gallery Then four prebuilt templates are available: Water Leak, Electrical Hazard, Gas Leak, Mold And each prebuilt template contains the following sections: Incident Summary; Detection Labels & Timestamps; Tenant Instructions Delivered; Photo Evidence (Before/After); Technician/Remediation Actions; Compliance/Disclaimers; Sign-off And each prebuilt template renders to PDF without errors using sample data And terminology matches standardized FixFlow glossary for the claim type And each template can be cloned and edited without altering the original prebuilt
Data Field Mapping and Placeholder Validation
Given a template containing placeholders mapped to FixFlow fields including ticket.id, ticket.claimType, detection.labels, detection.timestamps, tenant.instructionsDelivered, photos.before[], photos.after[], property.address, unit, landlord.companyName, landlord.logo, technician.name, scheduledAt, workStartedAt, workCompletedAt When rendering with a populated ticket Then each placeholder is populated with the correct value and formatted per template rules (date format, casing) And before/after photos are inserted with captions (timestamp and label) and maintain aspect ratio And if any placeholder is unknown or unmapped, rendering is blocked and the UI lists each offending placeholder with its template location And if a mapped field is empty and optional, the section either hides or shows "Not Provided" based on template setting
Required Fields Validation and Missing Items Flagging
Given a template that defines required fields per section When a user attempts to render a report Then the system performs pre-render validation and blocks rendering if any required field is missing And the user sees a consolidated list of missing items including section, field label, current value state, and a deep link to edit And missing required photos are identified by type (before/after) and count And once all required items are provided, validation passes without warnings on re-check And a validation result (Pass/Fail with timestamp and actor) is recorded on the ticket’s audit log
Template Versioning, History, and Rollback
Given an admin edits a template and saves changes When saved Then a new immutable version is created with incremented version number, author, timestamp, and change summary And the change history lists all versions with diffs of placeholders and visibility settings And rollback to a prior version creates a new version that matches the selected version’s content; history remains intact And users can render using the latest version by default; previous versions are read-only And attempting to modify a past version directly is disallowed with a clear message
Template Selection by Property/Insurer/Portfolio with Precedence
Given selection rules exist at portfolio, insurer, and property levels When rendering a report for a ticket with property X and insurer Y Then the system selects the template using precedence: Property-level override > Insurer-level > Portfolio-level > Global default And if multiple rules match at the same level, the most recently updated rule is chosen deterministically And the selected rule is recorded on the ticket with template name and version used And changing the property or insurer on the ticket updates the selected template on next render And admins can simulate selection by inputting property and insurer to preview the resulting template
Photo Integrity, Timestamping & Watermarking
"As a landlord, I want verifiable timestamps and tamper-evident photos so that insurers trust the authenticity of the evidence."
Description

Captures, stores, and presents before/after photos with verifiable integrity. Embeds capture timestamps (and optional geotags where available), calculates a SHA-256 hash for each original image, and stores originals immutably while serving optimized derivatives for the report. Watermarks report images with ticket ID, capture time, and hash fragment to deter tampering. Preserves EXIF where permitted, normalizes orientation, and maintains chain-of-custody metadata from tenant or technician upload to report inclusion. Handles offline uploads, retries, and size constraints while ensuring consistent quality in the generated PDF.

Acceptance Criteria
Originals Are Immutable and Verifiable by Hash
Given an image is uploaded, When the server receives the exact byte stream, Then the system computes and stores a SHA-256 hash and a content-length for that byte stream. And Given the original is stored, Then it is written to immutable, append-only storage with retention controls and is never modified or overwritten. And Given derivatives are needed, When generating derivatives, Then they are produced from the stored original without altering the original. When a verification is requested, Then the system recomputes the hash from the stored original and it equals the stored SHA-256; otherwise the request is flagged and logged as integrity-failure and access is blocked. Then access to originals is read-only via time-bound URLs, and all reads/writes are audit-logged with user and timestamp.
Accurate Capture Timestamps and Optional Geotags
Given a photo with EXIF DateTimeOriginal, Then the capture timestamp is taken from EXIF; otherwise use in-app capture time; otherwise use server-receipt time, with the source clearly indicated. Then timestamps are stored in ISO 8601 UTC and displayed in the property's local timezone in the report and watermark. Given geotag data is present and geotagging is enabled for the org, Then geotags are stored with the original and included in metadata; otherwise geotags are stripped from derivatives and not shown in reports. When device time deviates by >5 minutes from server, Then an annotation "Device time variance >5m" is added to the photo metadata. Orientation is normalized for viewing, while EXIF is preserved where permitted by platform policy.
Watermarked Report Images With Ticket ID and Hash Fragment
Given a photo is included in a report or PDF, Then the derivative is watermarked with: ticket ID, capture local time, and the first 10 characters of the SHA-256 hash. Then the watermark is applied non-destructively (original remains unwatermarked), placed in the bottom-right with a 24px margin, opacity 25%, and scales so text is at least 14pt at 300 DPI. Then watermark orientation matches the final image orientation after normalization and rotation. Then at least 90% of the central image area remains unobstructed by the watermark.
End-to-End Chain of Custody From Upload to Report
Given any photo is uploaded, Then the system records: uploader role and user ID, capture source (camera/import), app version, device OS, local capture timestamp, upload timestamp, server receipt timestamp, SHA-256, storage ID, and processing job IDs. When the photo is added to or removed from a report, Then the action, actor, timestamp, and report version are appended to the audit trail. Then chain-of-custody records are append-only, hash-chained, and visible in-ticket to roles with Evidence.View. Then the mini report includes a summary table of chain-of-custody fields for each photo. Then an export endpoint returns the chain-of-custody JSON for a ticket with ETag equal to the chain hash.
Reliable Offline Capture, Queuing, and Resilient Upload
Given the device is offline, When a user captures a photo, Then it is stored locally with a SHA-256, capture timestamp, and queued state. When connectivity is restored, Then the client retries uploads with exponential backoff up to 10 attempts over 24 hours before surfacing a failure with actionable messaging. Then duplicate photos (same SHA-256) are de-duplicated in the ticket and the user is notified that a duplicate was skipped. Then images over 25 MB are client-optimized before upload to a maximum long edge of 4000 px and JPEG quality target of 80, preserving capture timestamp and metadata where permitted. Then users see status badges per photo: Queued, Uploading, Uploaded, Failed, with manual Retry available.
Optimized Derivatives and PDF Quality Consistency
Given an original is stored, When generating derivatives for the report, Then produce: thumbnail (512 px long edge) and report (2048 px long edge, JPEG quality 82, sRGB), preserving visual clarity. Then EXIF Orientation is applied before derivative generation; prohibited EXIF tags (e.g., precise GPS when disabled) are stripped from derivatives but retained in originals where permitted. Then a 20-photo report generates a PDF in ≤10 seconds server-side at P95, with total size ≤15 MB by applying progressive recompression without dropping below 150 DPI effective resolution or making watermarks illegible. Then all images in the PDF are embedded with consistent color profile (sRGB) and maintain aspect ratio without stretching.
Before/After Photo Pairing and Labeling
Given users can label photos as Before or After, Then each photo has a required state: Unlabeled, Before, or After. When generating the mini report, Then photos are grouped into Before and After sections, ordered by capture timestamp, and each shows its label and timestamp. Then at least one Before photo is required to submit the initial triage report; if missing, the report shows "Before photos missing" and submission is blocked. Then the technician closeout report displays at least one After photo if the ticket status transitions to Completed; otherwise a warning is shown and the transition requires an override reason.
Instruction Delivery Proof & Acknowledgment
"As a property manager, I want proof that safety instructions were delivered and acknowledged so that I can demonstrate due diligence during audits and claims."
Description

Logs and surfaces proof of tenant safety instruction delivery (content, language, channel, and timestamp) with read receipts or fallback delivery confirmations per channel (SMS, email, in-app). Captures acknowledgment when tenants confirm they received or followed steps (e.g., shutoff valves), including time and method. Displays this evidence in the report’s Instructions section and links back to the communications log for audit. Supports multilingual content, templates for common hazards, and gracefully records when channels don’t support read receipts. Respects opt-out and privacy preferences while still recording permissible delivery metadata.

Acceptance Criteria
Proof of Instruction Delivery per Channel
Given a hazard ticket exists and safety instructions are sent When instructions are dispatched via SMS, email, or in-app Then the system logs for each dispatch: ticket ID, recipient ID, channel, content template ID and version, language code (IETF BCP 47), sender identity, UTC ISO-8601 timestamp, and provider message ID And the system records delivery status as one of: queued, sent, delivered, failed, with the latest provider reason code if failed And if the channel supports read/open receipts, the system stores a read/open timestamp and method; otherwise it stores "read receipt not supported" or "tracking disabled" And each log entry has an immutable unique ID and is linked to the ticket and communications log
Tenant Acknowledgment of Safety Steps
Given a tenant receives safety instructions When the tenant confirms via in-app button, verified email link, SMS keyword, or IVR keypad input Then the system records an acknowledgment event with ticket ID, tenant ID, step(s) confirmed (e.g., shutoff valve closed), method, UTC timestamp, and any attachment IDs (e.g., photos) And duplicate acknowledgments within 60 seconds for the same step are deduplicated using an idempotency token while preserving the first timestamp And the acknowledgment is visible in the Instructions section and linked to the originating message in the communications log
Instructions Evidence in Report
Given delivery and/or acknowledgment events exist for a ticket When a user views or exports the ticket's Hazard Proof Pack report Then the Instructions section displays a chronological timeline including content title/template, version, language, channel, send/deliver/read statuses with timestamps, and any acknowledgments with timestamps and methods And each timeline item links to the detailed communications log entry And the exported PDF/attachment contains the same data and redacts PII fields according to role-based export rules
Multilingual Content and Templates
Given a tenant profile specifies a preferred language When generating and sending safety instructions Then the system selects the matching template language and records the language code used; if unavailable, it falls back to the default language and logs the fallback reason And the template ID, name, and version used are stored with the delivery event And right-to-left and accented characters are preserved in all channels, verified by round-trip rendering tests
Privacy Preferences and Read-Receipt Limitations
Given a tenant has opt-out or privacy preferences (channel opt-out, do-not-track) When sending safety instructions and logging metadata Then the system does not send to opted-out channels and records the opt-out reason and timestamp And if read/open tracking is disabled by preference, compliance, or channel limitations, the system does not attempt tracking and logs "tracking disabled" while still recording permissible send/deliver metadata And message content bodies are masked in logs for users without the necessary role; only template ID/title and variable names are shown in insurer/auditor exports
Delivery Failure Handling and Escalation
Given an instruction message fails to deliver or remains undelivered for 10 minutes When the failure or timeout is detected Then the system retries the same channel up to 3 times with exponential backoff and logs each attempt with attempt number, timestamp, and provider reason code And if delivery still fails, it automatically sends via an available fallback channel (respecting opt-outs) and logs the fallback attempt and outcome And if all channels fail, it flags the ticket, notifies the manager within 1 minute, and records "no delivery proof available" in the report
SLA for Send and Acknowledgment
Given a ticket has a risk level When a ticket is created or its risk level is updated to High, Medium, or Low Then initial safety instructions are sent within 2 minutes for High risk and within 10 minutes for Medium/Low; any breach triggers an alert and is logged with breach duration And if no acknowledgment is received, reminders are sent every 15 minutes up to 3 times per channel (respecting opt-outs) and each reminder is logged And the report displays SLA met/breached indicators with timestamps for both initial send and acknowledgment windows
One-Click Export & Secure Sharing
"As an independent landlord, I want to export and securely share the hazard report in one click so that reimbursements can start immediately."
Description

Enables one-click export of the mini report as a PDF (and optional ZIP of original media) from the ticket view. Generates secure, expiring share links with role-based access presets for insurers, owners, and vendors, and supports direct email send with templated messages. Tracks access events (first open, downloads) for follow-up and includes the report automatically in claim packets. Stores a copy in the ticket’s attachments and allows revocation or extension of links. Ensures consistent file sizes via compression while preserving integrity indicators for insurers.

Acceptance Criteria
One-Click PDF Export and Attachment Storage
Given a ticket with a generated mini report and at least one photo When the user clicks "Export PDF" from the ticket view Then a PDF is generated within 5 seconds for reports up to 20 pages or 30 images, is saved to the ticket’s attachments, and a success message displays the filename And Then the PDF filename follows Ticket-<TicketID>-Report-<YYYYMMDD-HHmm>.pdf And Then the PDF contains detection labels, timestamps, tenant instructions delivered, and before/after photos in chronological order And Then an Evidence Integrity section lists each media item with original filename, capture timestamp, and a cryptographic digest of the original file And Then compression keeps the PDF size at or below 25 MB by default while maintaining image rendering at or above 150 DPI; if not achievable, the user is prompted to continue with reduced resolution or include as-is
Optional ZIP Bundle of Original Media
Given the Export dialog is open When the user enables "Include original media (ZIP)" Then a ZIP is generated containing the unmodified original media files and a manifest (JSON and CSV) with filename, size, MIME type, capture timestamp, and cryptographic digest per file And Then the ZIP filename follows Ticket-<TicketID>-Media-<YYYYMMDD-HHmm>.zip and is attached to the ticket and available for sharing And Then if the estimated ZIP size exceeds 500 MB, the user is warned with an estimate and can deselect items or proceed And Then redacted or restricted media are excluded and the manifest indicates omissions
Role-Based Secure Share Link Generation
Given the user selects Share for a ticket export When a role preset is chosen (Insurer, Owner, Vendor) Then preset permissions (view-only or view+download), default expiration (Insurer: 90 days, Owner: 30 days, Vendor: 14 days), and artifact selection are applied And Then the generated link is an HTTPS URL with an unguessable token providing at least 128 bits of entropy and access is scoped to the selected report artifacts only And Then the user can override expiration between 1 and 365 days and adjust permissions before creating the link And Then the created link, its settings, creator, and timestamps are stored and visible in a Shared Links panel on the ticket
Direct Email Send Using Templates
Given the user selects Send via Email for a ticket export When a role preset and recipient emails are provided Then the email subject and body populate from the selected template with placeholders resolved for ticket ID, property address, tenant name, and claim number, and the user can edit before sending And Then the email sends within 60 seconds, includes the share link and any selected attachments, and a Sent event with recipients and timestamp is logged on the ticket And Then if any recipient bounces or the provider returns an error, a Delivery Failed alert is shown and logged with reason And Then CC, BCC, and Reply-To are supported with Reply-To set to the property manager’s address
Access Event Tracking for Shared Reports
Given a share link exists When a recipient first opens the link Then the system records the first-open timestamp and recipient identity (email if available) and displays it in the ticket timeline and Shared Links panel And Then each successful download of the PDF or ZIP records a timestamp and increments a download count, visible per link And Then the user can export an access log (CSV) per link containing link ID, recipient (if known), first open, and download events And Then if no first open occurs within 72 hours of creation, a follow-up task suggestion is created for the ticket assignee
Automatic Report Inclusion in Claim Packets
Given a claim packet is being generated for a ticket When the packet is created Then the latest mini report PDF is automatically included; if none exists, the system generates the PDF first and includes it And Then the packet preview shows the included report and allows deselection prior to finalizing And Then the packet metadata records the included report filename and generation timestamp And Then the final sent or exported packet contains the report unless the user explicitly deselected it, and this decision is logged
Link Revocation and Extension Controls
Given a share link exists When the user clicks Revoke on the link Then the link becomes immediately inaccessible and subsequent accesses show Link expired, and an audit log entry is created And Then the user can extend the link expiration date up to a maximum of 365 days total lifetime and the change is logged And Then permissions (view-only vs view+download) can be modified after creation and take effect on next access And Then the user can bulk revoke all share links for the ticket with a single action and the action is logged
PII Redaction & Role-Based Views
"As a property manager, I want tenant PII automatically redacted for external recipients so that I can meet privacy obligations without manual editing."
Description

Applies automatic redaction of tenant personally identifiable information in shared reports based on recipient role (e.g., insurer vs. technician vs. owner). Configurable redaction rules cover names, contact details, precise unit numbers where not required, and free-text fields. Presents a preview with redaction highlights before sharing and logs who approved overrides. Maintains full, unredacted originals internally with audit logging while ensuring shared artifacts comply with privacy policies (GDPR/CCPA). Integrates with FixFlow’s RBAC so only authorized users can view or export unredacted reports.

Acceptance Criteria
Insurer Share: PII Redaction by Role
Given a hazard report contains tenant full name, phone, email, precise unit number, and free-text notes with PII And the recipient role is set to "Insurer" When the user generates a shareable report Then the report renders tenant name as "Tenant" And phone and email are fully redacted as "[REDACTED]" And the precise unit number is omitted or generalized to building-level only (e.g., "Building A") And all PII substrings detected in free-text fields are replaced with "[REDACTED]" And the preview displays all redactions with highlight markers And the exported artifact (PDF/Link) and its metadata contain no unredacted PII And an automated PII scan on the exported artifact returns zero matches above the configured confidence threshold And an immutable audit log entry records actor, timestamp, recipient role, report ID, rule version, and export channel
Technician Share: Conditional PII Based on RBAC
Given a hazard report with tenant contact details and unit info And the recipient role is set to "Technician" When the user generates a shareable report Then if the sender has RBAC permission "PII.Contact.Access", full name, phone, email, and precise unit number are visible And if the sender lacks "PII.Contact.Access", phone and email are masked to last 4 digits and unit is generalized to floor/building level per rule And permitted PII types visible match the active rule for role "Technician" And the preview clearly indicates which fields will be visible vs. redacted And the audit log records actor, timestamp, role, rule version, and visibility outcome And an automated PII scan on the exported artifact reports only the permitted PII types for that role
Admin Configures Role-Based Redaction Rules
Given an admin with permission "Privacy.Rules.Manage" opens Redaction Rules When the admin creates or edits a rule set for a role (e.g., Insurer, Technician, Owner) Then the admin can set per-field actions: Redact, Mask (pattern), Generalize (granularity), or Allow And validation blocks configurations that violate organization privacy policy constraints And the rule set is saved with a Semantic Version, description, author, and effective timestamp And shares created after the effective timestamp use the new rule version And a "Simulate with Sample Report" preview shows expected redactions without persisting shares And the change is recorded in audit logs including before/after diff of settings
Share Preview With Highlighted Redactions and Overrides
Given a user initiates sharing and opens the preview for a selected recipient role When the user reviews highlighted redactions Then the preview displays a count of redacted instances by field type And clicking a redacted field to reveal requires permission "PII.Override" and an override reason And if approval workflow is enabled, an approver with "PII.Override.Approve" must approve before sharing And the audit log records requester, approver (if any), fields overridden, reason, timestamps, and rule version And overrides apply only to the current share action and do not modify the saved rule set And the final shared artifact reflects approved overrides only
Unredacted Original Retention and Traceability
Given a hazard report is generated When the system stores the report Then an immutable unredacted original is stored with checksum and storage URI And access to the original requires "Reports.Unredacted.View" permission And every view/export of the unredacted original is logged with actor, timestamp, purpose, and channel And each redacted export is saved as a derivative linked to the original by ID and rule version And integrity checks verify the derivative was produced from the recorded original and rule version
RBAC-Gated Unredacted View and Export
Given a user attempts to view or export an unredacted report When the user lacks permission "Reports.Unredacted.View" (or "Reports.Unredacted.Export" for export) Then the UI hides unredacted options and the API returns 403 for direct access attempts And a security event is written to the audit log with actor, target report ID, and outcome "Blocked" And when the user has the required permission, the unredacted export is watermarked "Internal – Contains PII" and the event is logged as "Allowed" And link-based shares of unredacted artifacts require authenticated access and inherit expiry settings per org policy
Free-Text PII Detection and Redaction
Given free-text fields in a hazard report may contain PII (names, emails, phones, unit numbers) When redaction is applied for any role that requires masking of free-text PII Then the system detects PII using configured patterns and ML with a confidence threshold And all detected PII above threshold is replaced with "[REDACTED]" and highlighted in preview And users with "PII.Override" may restore specific instances via preview with reason captured And an automated validation step confirms zero residual PII above threshold in the exported artifact

Pattern Radar

Detects recurring hazards across units (e.g., 3rd-floor stack leaks, specific faucet model failures) and recommends preventative work or batch tickets. Helps operators act proactively, lowering portfolio-wide repair costs and downtime.

Requirements

Unified Event Ingestion & Normalization
"As a property manager, I want all maintenance data standardized across units so that I can analyze trends and detect recurring issues accurately."
Description

Consolidates maintenance tickets, triage outputs, photos, approvals, scheduling events, vendor notes, and unit/building metadata (e.g., floor, riser/stack, fixture models, install dates) into a normalized schema optimized for cross-unit pattern analysis. Performs entity resolution and deduplication across channels, enriches records with CV-derived photo tags and NLP-extracted attributes, timestamps events, and maps them to a consistent unit/building hierarchy. Ensures near‑real‑time availability for analysis, enforces PII minimization, and exposes a versioned data contract for the Pattern Radar engine and dashboards.

Acceptance Criteria
Unified Ingestion and Normalization Across Sources
Given live streams of maintenance tickets, triage outputs, photos, approvals, scheduling events, vendor notes, and unit/building metadata are produced by upstream systems When the pipeline processes events Then 100% of supported event types are validated against a normalized schema and persisted with required fields: event_id, source, event_type, unit_id, building_id, event_time, ingest_time, payload, schema_version And schema validation rejects malformed events with explicit error codes; for well-formed inputs the ingestion error rate is <0.1% over any rolling 24h window And rejected events are routed to a dead-letter queue with trace_id and reason, visible in ops dashboards within 1 minute And the system sustains bursts of ≥50 events/second for 5 minutes without data loss or backlog growth >2 minutes
Entity Resolution and Deduplication
Given events from multiple channels reference the same underlying unit/building issue When entity resolution runs on incoming and historical events Then a canonical incident_id is assigned and all contributing event_ids are linked to it And replays/retries of the same source event are idempotent and create 0 additional canonical records And on a labeled test set (n≥500), pairwise precision ≥0.97, recall ≥0.93, and F1 ≥0.95 for duplicate detection And each resolution decision stores confidence, features_used, and source_ids for auditability
Photo and Text Enrichment Pipeline
Given events include photos and free-text fields (triage notes, vendor notes) When the enrichment pipeline executes Then photos are tagged with detected fixture_type, model, condition, and hazard indicators with confidence scores and provenance (CV model version) And NLP extracts attributes including model_number, fixture_type, install_date (ISO-8601), floor, riser_stack, and failure_symptoms where present And for a held-out validation set, top-1 tag precision ≥0.90 when confidence ≥0.70; items below threshold are flagged enrichment_status="low_confidence" And enrichment completes within p95 3 minutes and p99 5 minutes of ingest_time And missing or unreadable assets do not block ingestion and are marked enrichment_status="missing" with error_code
Temporal Stamping and Hierarchy Mapping
Given events may lack explicit timestamps or full location context When normalization occurs Then event_time is derived from the most trusted upstream timestamp or inferred; ingest_time is set at arrival; all timestamps are stored in UTC with source_time_zone recorded And each event is mapped to unit_id, building_id, floor, and riser_stack using authoritative metadata; mapping completeness ≥99.5% over any rolling 24h window And late-arriving events up to 7 days are accepted, correctly ordered within their unit timeline, and trigger re-computation where necessary And per-unit sequence ordering is deterministic via watermarking and sequence_numbers with exactly-once semantics to downstream stores
Near-Real-Time Availability SLO
Given valid events enter the ingestion pipeline When Pattern Radar queries the analytical store Then p95 end-to-end latency from ingest_time to queryable availability is ≤2 minutes and p99 ≤5 minutes over any 24h window And SLOs are continuously measured with dashboards and alerts that trigger within 5 minutes of breach and include current backlog, affected partitions, and probable root cause
PII Minimization and Governance
Given upstream payloads may contain tenant PII When records are normalized and stored Then only tenant_anonymous_id (hashed), contact_channel_type, and necessary unit/building identifiers are retained; phone numbers and emails are hashed or dropped; free-text PII is redacted And face/document redaction is applied to images before persistence to analytical stores And automated PII scans on sampled data achieve false-negative rate <1% on a seeded dataset; violations are quarantined and alerted within 10 minutes And access to raw payloads is restricted to designated service accounts; all access is audited with actor, purpose, and fields accessed
Versioned Data Contract and Compatibility
Given downstream consumers require a stable schema When changes are made to the normalized schema Then a machine-readable versioned contract (e.g., JSON Schema) is published in a registry with examples and field-level semantics And non-breaking additions increment the minor version; breaking changes require a major version with a minimum 30-day deprecation period and a dual-write window And backward-compatibility tests pass for the two most recent minor versions before release And Pattern Radar consumer contract tests pass in staging against the target version prior to production rollout
Hazard Pattern Detection Engine
"As an operations lead, I want the system to automatically detect recurring hazards across my portfolio so that I can address root causes before they escalate."
Description

Identifies recurring hazards across units, stacks, and buildings by clustering semantically similar tickets, photo evidence, and component attributes over configurable time windows. Combines rule-based signals (e.g., three or more leaks in a 3rd‑floor stack within 60 days) with ML/NLP on ticket text and CV on images to detect model-specific failures and location-based anomalies. Produces pattern objects with affected scope, frequency, severity, hypothesized root cause, confidence score, and supporting evidence, and runs on a schedule with on‑demand execution for urgent reviews.

Acceptance Criteria
Semantic Clustering Across Text, Images, and Components
Given a labeled validation dataset of ≥500 maintenance tickets with ground-truth cluster IDs spanning text, CV-detected classes, and component attributes within a 60-day window When the engine runs with default configuration and a 60-day time window Then the clustering achieves Adjusted Rand Index ≥ 0.70 on clusters with size ≥ 3 And the mean silhouette score across discovered clusters is ≥ 0.40 And ≥ 85% of clusters have at least one shared component attribute or CV-detected class across all members And tickets older than 60 days are excluded from clustering
Configurable Rule-Based Hazard Signals
Given a rule "≥3 leak tickets in the same 3rd-floor stack within 60 days" is enabled And three qualifying tickets occur within 60 days for stack S When the engine processes the portfolio Then a pattern is emitted within 5 minutes of ingestion of the third ticket And the pattern scope.type = "stack" and scope.id = S And frequency.count = 3 and window.length_days = 60 And disabling the rule prevents pattern emission on subsequent runs And changing the threshold to 4 prevents emission until the fourth ticket arrives
Pattern Object Schema Completeness
Given any emitted pattern object When validated against the Hazard Pattern JSON Schema v1 Then the object contains: pattern_id (UUIDv4), scope.type ∈ {unit, stack, building, portfolio}, scope.ids non-empty, frequency.count ≥ 2, severity ∈ {low, medium, high, critical}, hypothesis.text non-empty, confidence ∈ [0,1] rounded to 2 decimals, evidence.ticket_ids (≥2 IDs), evidence.image_ids present when CV contributed, created_at and window.start/window.end as ISO-8601 timestamps And 100% of emitted objects during a test run of ≥50 patterns validate successfully And invalid objects are rejected and logged with error codes without being persisted
Scheduled and On-Demand Execution
Given a schedule configured hourly from 06:00 to 22:00 local time When the schedule triggers Then the job starts within ±2 minutes of the scheduled time And completes within 10 minutes for portfolios ≤150 units and ≤2,000 tickets in the active window And resource usage stays below 2 vCPU and 4 GB RAM average during the run And an on-demand trigger with portfolio_id starts within 30 seconds and completes with identical results to the next scheduled run on the same inputs
Duplicate Suppression and Idempotency
Given the same input dataset is processed multiple times with unchanged configuration When the engine is re-run Then no duplicate pattern objects are created (same pattern_id retained) And zero new database writes occur aside from idempotency logs And when ticket evidence for an existing pattern increases, the pattern is updated as a new version with version incremented by 1 and a changelog entry listing added ticket_ids and image_ids
Confidence Thresholds and Recommendations
Given a detected pattern with confidence ≥ 0.80 When recommendations are generated Then recommendation.type ∈ {"preventative_work","batch_tickets"} is present with target scope and actionable steps And given 0.50 ≤ confidence < 0.80, a review_required flag is true and no auto ticket creation occurs And given confidence < 0.50, no recommendation is emitted And each recommendation includes an expected_avoidance_count ≥ 1 and can be exported as JSON and CSV
Location-Based Anomaly Detection Across Stacks
Given a building with stacks A, B, C and baseline leak ticket rates computed over the prior 90 days When, in the last 30 days, stack B’s leak ticket rate exceeds the building mean by ≥2 standard deviations Then the engine emits a location_anomaly pattern with scope.stack = B, method = "z-score", p_value ≤ 0.05, and confidence ≥ 0.70 And the pattern includes comparative baseline metrics (mean, stddev, stack rates) and supporting heatmap data reference
Preventative Recommendation Generator
"As a property manager, I want proactive recommendations with expected impact and cost so that I can prioritize preventative work with confidence."
Description

Transforms detected patterns into actionable preventative work, including suggested inspections, part replacements, waterproofing, or vendor escalations. Estimates cost, downtime reduction, and projected savings versus reactive repairs, and ranks actions by ROI and risk. Pulls compatible parts and procedures by fixture model, proposes time windows, and respects existing FixFlow approval workflows and budgets. Outputs clear recommendations with rationale, required materials, estimated labor, and dependencies.

Acceptance Criteria
Pattern-to-Preventative Recommendation Conversion
Given Pattern Radar has detected at least one recurring issue with confidence >= 0.75 affecting >= 2 distinct units within the last 30 days When the Preventative Recommendation Generator runs for the portfolio Then it creates at least one preventative recommendation per distinct pattern with a unique recommendation ID linked to the source pattern ID And each recommendation specifies an action type selected from {inspection, part_replacement, waterproofing, vendor_escalation} And for patterns affecting >= 3 units, it proposes a batch ticket with the full affected unit list And it excludes patterns below the configured confidence threshold And it completes generation within 15 seconds for portfolios up to 150 units
ROI and Risk Scoring & Ranking
Given recommendations are generated with cost inputs and reactive baseline data available When ROI and risk are computed Then each recommendation includes: estimated preventative cost (currency), projected downtime reduction (hours), projected savings vs reactive (currency), ROI = (savings - cost)/cost rounded to 2 decimals And a risk score in [0,100] derived from pattern likelihood and impact is present And the recommendation list is sorted by ROI descending; ties are broken by risk descending, then by earliest time window start And values are labeled with units and formatted to the account locale And for a supplied test dataset with known benchmarks, ROI values are within ±10% of expected
Parts and Procedures by Fixture Model
Given a detected pattern references one or more fixture/appliance model numbers When generating the recommendation Then the system pulls only compatible part SKUs and procedures from the parts catalog mapped to those models And each line item includes quantity, unit of measure, estimated labor hours, and required technician skill tags And if no exact model match exists, a generic procedure is proposed with confidence <= 0.6 and a flag missing_model=true And catalog lookups succeed offline via cache and refresh from source at least every 24 hours And any incompatible or deprecated SKUs are excluded and logged
Time Window Proposal and Scheduling Constraints
Given tenant availability windows, existing work orders, technician calendars, and part lead times are available in FixFlow When proposing time windows for each recommendation Then proposed windows fall within property business hours and avoid tenant blackout dates and existing work order conflicts And for batch work across >= 3 units in the same building, windows are clustered into no more than 2 contiguous blocks per day to minimize trips And earliest proposed start is >= lead_time_of_longest_part and within the next 14 days by default And window proposals are computed in <= 2 seconds per 50 recommendations And conflicts detected after scheduling result in an alternative window proposal within 1 second
Approval Workflow and Budget Compliance
Given property-level monthly budget caps, auto-approval thresholds, and approval routing rules are configured in FixFlow When recommendations are created Then any recommendation with estimated cost > remaining monthly budget is marked status=Pending Approval and routed to the correct approver ladder And recommendations with estimated cost <= auto-approval threshold are auto-approved and create draft work orders without dispatching technicians And batch recommendations apportion estimated cost per unit and roll up to the property total used for approval checks And no work order is set to status=Scheduled until approval status=Approved And an audit trail records creator, approver(s), timestamps, and changes to cost or scope
Recommendation Output Package Completeness
Given a recommendation is generated When presenting it in the UI and API Then the package includes: rationale referencing the detected pattern, action type, required materials list, estimated labor hours, dependencies (e.g., parts lead time, water shutoff), proposed time windows, estimated cost, projected savings, ROI, risk score, approval status, and links to create a batch ticket And the API payload validates against the Recommendation v1 schema with all required fields non-null And the UI card displays all required fields without truncation for standard viewport 1366x768 and supports export to PDF and CSV with matching values And 100% of seeded test patterns (n>=10) produce recommendations with non-empty rationale and at least one dependency when parts have lead time > 0
Batch Ticket Creation & Scheduling
"As a scheduler, I want to create and coordinate batch tickets from approved recommendations so that technicians can efficiently service multiple units with minimal tenant disruption."
Description

Converts approved recommendations into batch work orders across affected units, auto-assigns to in-house techs or preferred vendors, and sequences visits to minimize travel and tenant disruption. Integrates with FixFlow scheduling, tenant communications, and approval steps to coordinate access windows, send notifications, and track completion status per unit. Supports rescheduling, partial completions, and rollback if patterns are dismissed, while maintaining an auditable linkage from pattern → recommendation → tickets.

Acceptance Criteria
Batch Tickets from Approved Recommendation
Given a recommendation is approved and identifies affected units When the user selects Create Batch Tickets Then the system creates one work order per unique affected unit within a single batch And each work order includes patternId, recommendationId, batchId, and unitId fields And duplicate unit entries are ignored so no more than one ticket per unit is created And the operation is idempotent; re-triggering with the same recommendation does not create duplicate tickets And for batches up to 100 tickets, all tickets are created within 10 seconds
Auto-Assignment to Techs or Preferred Vendors
Given assignment rules for categories, locations, SLAs, and preferred vendors exist When batch tickets are created Then tickets auto-assign to eligible in-house technicians if skills, service area, work hours, and capacity match And otherwise auto-assign to the highest-ranked eligible preferred vendor And if no eligible assignee exists, the ticket status is Unassigned and a notification is sent to the manager within 1 minute And all assignments capture the rule that determined the assignment in the ticket audit log
Sequenced Visits to Minimize Travel and Disruption
Given assigned tickets with tenant availability windows and property geo/building data When the schedule is generated Then no assignee has overlapping appointments And appointments are grouped by building or proximity when tenant windows allow And quiet hours and property access constraints are respected And each ticket shows a sequence number and an arrival window of 2 hours or less And the system logs baseline vs optimized travel minutes per assignee with optimized travel minutes less than or equal to baseline
Tenant Notifications and Access Coordination
Given tenant contact preferences and availability windows are known When a batch schedule is proposed Then tenants receive initial notifications via preferred channels with date/time window and confirmation link And tenants can confirm or propose alternative times, creating a reschedule request on the ticket And unresponsive tenants receive at least two reminders at 24 hours and 2 hours before the appointment And if a tenant declines, the ticket status changes to Needs Reschedule and is excluded from routing until rescheduled And all messages and delivery statuses are logged to the ticket communication history
Rescheduling, Partial Completions, and No-Shows
Given a scheduled ticket needs to change or is marked No Show When a user or tenant triggers a reschedule Then the affected assignee’s route is re-optimized without altering completed tickets And the batch dashboard displays counts by status (Scheduled, Completed, Needs Reschedule, Cancelled) And tickets can be marked Completed individually with actual start/end times recorded And reschedule-triggered changes send updated notifications to impacted tenants within 5 minutes
Rollback on Pattern Dismissal
Given a pattern or its recommendation linked to an active batch is dismissed When an authorized user initiates rollback Then all not-yet-started tickets in the batch are cancelled with reason Pattern Dismissed And in-progress or completed tickets are preserved and excluded from rollback And reserved timeslots and vendor holds are released And tenants and assignees receive cancellation notices And an immutable audit entry records user, timestamp, and affected ticket IDs
Auditable Linkage and Reporting
Given a batch exists When viewing any ticket via UI or API Then patternId, recommendationId, and batchId are displayed/returned with navigation to each And an export endpoint returns CSV/JSON with one row per ticket including linkage fields, assignee, scheduled window, status, createdBy, createdAt, updatedAt, completedAt And linkage fields are immutable after creation And ticket search by any linkage ID returns results in under 2 seconds for up to 10,000 tickets
Portfolio Heatmap & Threshold Alerts
"As a portfolio owner, I want visual heatmaps and alerts of emerging problem areas so that I can allocate budget and attention where they will have the most impact."
Description

Provides an interactive dashboard and heatmaps that visualize emerging clusters by building, floor/stack, system type, and fixture model, with trendlines over time. Enables saved views, CSV export, and shareable links for stakeholders. Sends in‑app and email alerts when configurable thresholds are crossed (e.g., confidence ≥ 0.8 and 5+ affected units), and includes a weekly digest summarizing new and escalating patterns. Honors role-based access controls and integrates with existing FixFlow portfolio views.

Acceptance Criteria
Interactive Heatmap and Trendlines by Dimension
- Heatmap supports filters by date range, property, building, floor/stack, system type, fixture model, severity, and confidence - Cluster coloring scales automatically and matches backend counts within ±1% for the same filter set - Trendlines display the last 12 weeks aggregated weekly and match backend time‑series values for the same filters - Hover tooltip shows cluster key, affected units, median confidence, 7‑day delta, and 30‑day delta - Switching dimensions renders updated results within 1 second for datasets up to 50k incidents - Clicking a cluster opens the pattern detail view with filters preserved
Saved Views Management and Persistence
- Users with role Manager+ can save the current filters, dimensions, and sort as a named view - Saved views persist to the user account and appear across sessions and devices - A default view can be set and auto‑loads on return to Pattern Radar - View owners and Admins can rename or delete a saved view; Viewers can load but not modify - System supports up to 50 saved views per user; exceeding limit returns a clear error - Loading a saved view reconstructs the exact filter state and produces identical counts to the time of load
CSV Export of Filtered Pattern Data
- Export downloads a CSV reflecting the current filters and selection or the full filtered dataset when no selection - CSV includes header row and columns: timestamp (ISO‑8601 UTC), property_id, building, floor_stack, system_type, fixture_model, confidence, unit_id, incident_id, cluster_id, severity - Row count in CSV equals the UI record count for the same filters (±0 for completed exports) - Exports respect RBAC and exclude properties outside the user’s scope - For >100k rows, export is queued and delivered by email link within 10 minutes; UI shows queued status with job ID - Numeric and datetime fields are unformatted (no locale formatting); delimiter is comma
Shareable Read‑Only Links With RBAC
- Generate link captures current filters, dimensions, and selection and is read‑only by default - Links require authentication; after login, data shown is limited to the viewer’s RBAC scope - Links expire at the configured date or after 30 days by default; expired links return a 410 Gone page - Owners and Admins can revoke links; revocation takes effect within 1 minute - Shared view renders identical aggregates to the owner’s view for overlapping RBAC scope; mismatches >1% are flagged and logged - Link format uses opaque tokens and does not expose internal property identifiers beyond hashed cluster_id
Threshold‑Based In‑App and Email Alerts
- Users can create alert rules with dimensions, confidence threshold, affected unit threshold, and evaluation window - When confidence ≥ 0.8 and affected units ≥ 5 within the window, an alert is sent in‑app and by email within 5 minutes - Alert payload includes pattern name, dimensions, confidence, affected units, 7‑day and 30‑day deltas, and a deep link to the view - Duplicate alerts for the same pattern and rule are suppressed for 24 hours unless affected units increase by ≥20% or a higher threshold is crossed - Quiet hours can be configured per user; emails are deferred while in‑app notifications queue - Rule create/update/delete actions are audited with user, timestamp, and changes
Weekly Digest of New and Escalating Patterns
- Digest is sent weekly at 08:00 local portfolio timezone to subscribed roles (Manager+ by default) - Digest lists up to 10 new and 10 escalating patterns with confidence, affected units, and WoW change - Digest content matches the user’s RBAC scope; restricted properties are excluded - Email and in‑app digest show identical content and links to the corresponding saved view or filtered heatmap - Users can subscribe or unsubscribe from the digest; preferences persist across sessions - If there are no qualifying patterns, the digest states no new or escalating patterns and is under 50 KB
Portfolio Navigation Integration and Access Control
- Pattern Radar heatmap is accessible via Portfolio > Pattern Radar and respects the current portfolio selection - Back/forward navigation and URL query parameters fully restore view state - All views and exports honor role‑based access controls for Property Viewer, Manager, and Admin roles - Initial load time is ≤ 2 seconds for portfolios with up to 10k incidents in the last 90 days on a standard broadband connection - UI meets WCAG 2.1 AA for keyboard navigation and color contrast on heatmaps and alerts - Access to properties outside the user scope results in a 403 response and no data leakage in the UI or exports
Explainability & Operator Feedback Loop
"As a maintenance director, I want clear evidence and the ability to confirm or dismiss detected patterns so that the system learns from decisions and builds trust."
Description

Displays clear evidence and rationale for each detected pattern, including representative tickets, annotated photos, timelines, and contributing signals. Allows operators to confirm, snooze, or dismiss patterns with reasons, and to adjust sensitivity per building or system. Captures feedback into an audit trail and uses it to recalibrate thresholds and retrain models on a scheduled cadence, improving precision and reducing false positives over time.

Acceptance Criteria
Pattern Details Explainability View
- Given a detected pattern exists, when an operator opens its details panel, then the view displays: pattern title, scope (buildings/units count), detection rule version, last updated timestamp, and confidence score. - Given the pattern spans N tickets, when viewing details, then at least the top 3 representative tickets (or all if <3) are listed with links, creation dates, unit IDs, and problem categories. - Given associated photos exist, when viewing details, then at least 1 representative photo per representative ticket is shown with visible annotations highlighting contributing signals. - Given events exist, when viewing details, then a timeline chart displays occurrence counts by week for the last 12 months with hover tooltips. - Given contributing signals exist, when viewing details, then a ranked list shows each signal name, weight (0–1), and its normalized contribution (%) totaling 100% ±1%. - Given the operator clicks Export, then a PDF/CSV bundle downloads within 5 seconds containing the above evidence.
Confirm Pattern with Operator Rationale
- Given a pattern is open, when the operator clicks Confirm, then a modal requires selection of a reason from a list or a free-text rationale of at least 10 characters. - Given the operator submits Confirm, then the pattern status updates to Confirmed, a timestamp and user ID are recorded, and a success message appears within 2 seconds. - Given Confirm succeeds, then an audit record is written with before/after state, reason, and a reference to any batch-ticket recommendation generated. - Given webhooks are configured, when a pattern is confirmed, then a pattern.confirmed event is emitted within 10 seconds with pattern_id and reason.
Snooze Pattern with Duration and Reason
- Given a pattern is open, when the operator clicks Snooze, then the modal requires a snooze duration (7, 30, 90 days, or custom date) and a reason (predefined or 10+ character free-text). - Given Snooze is saved, then the pattern is removed from the Active list and appears under Snoozed with a wake date, and no notifications are sent during the snooze period. - Given a snoozed pattern is manually unsnoozed, then it returns to Active immediately and the audit trail records the action. - Given the snooze period ends, when the pattern still meets the threshold, then it resurfaces in Active within 15 minutes; otherwise it remains inactive.
Dismiss Pattern with Reason and Safeguards
- Given a pattern is open, when the operator clicks Dismiss, then a modal requires a dismissal reason (predefined or 10+ character free-text) and optional note. - Given Dismiss is confirmed, then the pattern is removed from Active and cannot reappear for 90 days unless its weighted signal score increases by ≥25% over its dismissal baseline. - Given a pattern was dismissed within the last 7 days, when the operator clicks Undo Dismiss, then the pattern is restored to Active and the audit trail records the reversal. - Given Dismiss occurs, then a training label of negative is stored for the pattern cluster with user_id, reason, and timestamp.
Per-Building and Per-System Sensitivity Controls
- Given the operator opens Settings > Sensitivity, when selecting a building and system (e.g., Plumbing), then a sensitivity control is displayed allowing thresholds from 0.1 to 0.9 in 0.05 increments. - Given the operator adjusts sensitivity, then a live impact preview shows the expected monthly alert count and historical precision/recall deltas computed from the last 90 days. - Given the operator saves changes, then new thresholds take effect within 15 minutes for the selected building/system, and an audit entry records old_value, new_value, user_id, and scope. - Given the operator clicks Reset to Default, then thresholds revert to the global default and the change is applied and logged as above.
Audit Trail, Retraining Cadence, and Threshold Recalibration
- Given any feedback action (confirm, snooze, dismiss, sensitivity change) occurs, then an immutable audit record is stored capturing action_type, user_id, timestamp (UTC), IP, reason, and before/after config. - Given audit data is requested, when exporting the audit trail, then a CSV is generated within 10 seconds for the selected date range and includes all captured fields; data is retained for at least 24 months. - Given the default model retraining cadence is weekly Sunday 02:00 local property time, when retraining completes, then a report is attached to release notes including pre/post precision, recall, and false positive rate measured on a holdout set of at least 5% of recent data. - Given retraining degrades any KPI by >2% absolute, then the system auto-rolls back to the previous model and flags the run for review, and thresholds remain at last-known-good values.

Smart Slot Match

Cross-checks tenant, vendor, and building calendars with SLA deadlines and travel buffers to propose the top three conflict-free windows. Ranks options for fastest resolution and route efficiency, cutting back-and-forth and first-available delays.

Requirements

Multi-Calendar Two-Way Sync
"As a vendor, I want my calendar to sync automatically with FixFlow so that proposed repair slots never conflict with my existing commitments."
Description

Integrates FixFlow with Google Calendar, Microsoft 365/Outlook, and iCal/ICS to read free/busy availability and write tentative holds and confirmed appointments for tenants, vendors, and property managers. Uses OAuth 2.0 and provider webhooks/subscriptions for near real-time updates, enforces least-privilege scopes (free/busy-only when full read is not consented), and normalizes time zones, working hours, and daylight saving. Supports conflict detection, rate limiting, retries, and idempotent writes to prevent double-booking, and exposes a calendar connection UI and health status per user. Ensures data privacy by masking event details unless explicitly permitted and aligns appointments with FixFlow work orders and job IDs.

Acceptance Criteria
OAuth Least-Privilege Calendar Connection
Given a user connects Google or Microsoft calendar via OAuth When the user selects free/busy-only consent Then FixFlow requests/scopes only free/busy permissions and stores encrypted tokens, and event details remain inaccessible in FixFlow And the connection is marked as FreeBusy in the UI Given the same user later upgrades consent to read/write When re-authentication completes Then FixFlow obtains read/write scope, can create/update its own events, and the connection is marked as ReadWrite Given an access token is revoked by the provider When FixFlow next calls the provider or receives an auth error Then the connection health changes to Disconnected within 5 minutes and no further writes are attempted
Two-Way Sync: Free/Busy Read and Appointment Writes
Given tenant, vendor, and manager calendars are connected When FixFlow creates a tentative hold for work order WO-123 with idempotency key K-1 Then a single provider event is created as tentative, titled "FixFlow Hold" unless detail sharing is enabled, includes external properties {workOrderId: WO-123, jobId: <ID>}, and FixFlow stores the provider eventId And if the same request is retried with K-1, no duplicate event is created When the hold is confirmed in FixFlow Then the existing provider event is updated to confirmed without creating a new event When a provider-side event time is updated Then FixFlow updates the corresponding appointment within 60 seconds of receiving the provider notification Given a calendar connected via read-only iCal/ICS When FixFlow attempts to write a hold or appointment to that calendar Then the write is blocked and the user is prompted to connect a writable provider
Near Real-Time Change Propagation (Webhooks/Subscriptions)
Given active Google and Microsoft subscriptions are established When an event is created, updated, or deleted in the provider Then FixFlow reflects the change within 60 seconds of webhook receipt And subscriptions auto-renew at least 30 minutes before expiration; on renewal failure the user is notified within 2 minutes Given a provider webhook/channel returns 404/410 or expires When FixFlow detects the condition Then it re-subscribes within 2 minutes without data loss Given a read-only ICS feed is connected When the ICS feed changes Then FixFlow polls at least every 15 minutes and updates local availability accordingly
Time Zone, Working Hours, and DST Normalization
Given participants are in different time zones with configured working hours When FixFlow creates or updates an appointment Then the event is stored in UTC and displayed in each participant’s local time And if the time falls outside a participant’s working hours, FixFlow flags it and requires explicit override before confirmation Given an appointment scheduled across a DST transition day When the time is viewed before and after the transition Then the local start and end times remain correct with no 1-hour drift or overlap
Conflict Detection and Double-Booking Prevention
Given a vendor has an existing confirmed event during a time window When FixFlow attempts to create a new appointment overlapping that window Then the write is rejected with CONFLICT_DETECTED and the conflicting event reference is returned Given two simultaneous booking attempts for the same vendor and time When FixFlow processes the writes Then only one appointment is created and the other request receives CONFLICT_DETECTED within 2 seconds Given a tentative hold exists for a time slot When FixFlow tries to create another hold for the same participant and slot Then FixFlow surfaces the conflict and does not create a duplicate hold
Provider Rate Limiting and Retry Policy
Given a provider returns HTTP 429 or a transient 5xx When FixFlow retries the operation Then it applies exponential backoff with jitter, respects the provider’s Retry-After header, and eventually succeeds without exceeding quota And if the final retry fails, FixFlow records the failure, surfaces an actionable error to the user, and queues a background retry without duplicating writes Given network timeouts occur during a write with idempotency key K-2 When FixFlow retries the write Then at most one provider event exists and FixFlow reconciles state on success
Calendar Connection UI and Health Monitoring
Given a user opens Calendar Connections in FixFlow When one or more providers are connected Then the UI displays provider, account, scope (FreeBusy or ReadWrite), last successful sync time, next renewal time (if applicable), and health status (Healthy/Degraded/Disconnected) with last error detail When a token is revoked, a subscription expires, or repeated sync failures occur Then the health transitions appropriately (e.g., Disconnected or Degraded) within 5 minutes and a Reconnect/Resubscribe action is available When the user clicks Resync Then a manual sync runs immediately, updates last sync time on success, and surfaces detailed errors on failure
SLA-Aware Slot Generation Engine
"As a property manager, I want the system to only generate appointment slots that satisfy our SLAs and building rules so that issues are scheduled compliantly and on time."
Description

Computes a set of conflict-free candidate appointment windows per work order by combining SLA due dates, issue priority, tenant availability preferences, building access rules (elevator schedules, quiet hours), vendor working hours, skill/coverage constraints, and required lead times. Applies configurable business rules and blackout periods, respects time zones, and generates windows at granularity (e.g., 1-hour, 2-hour) defined per account. Produces a ranked list of viable slots ready for scoring, guarantees options that meet or beat SLA when feasible, and flags SLA risk when constraints make compliance impossible, providing reasons and suggested mitigations.

Acceptance Criteria
Generate Conflict-Free Slots That Meet SLA
Given a work order with SLA due date, tenant availability preferences, building access rules, vendor working hours, travel buffer, and account slot granularity When the engine generates candidate appointment windows Then it returns a ranked list of conflict-free windows, providing at least three options when three or more feasible windows exist And each returned window meets or beats the SLA due date when at least one compliant window is feasible And each window duration equals the configured granularity (e.g., 1-hour or 2-hour) And each window fully fits within tenant availability, vendor working hours, and building access rules without overlap And the list is ordered by rank such that rank=1 is the most preferred option And when fewer than three feasible windows exist, all feasible windows are returned and scarcity is indicated via a flag
Ranked Options for Fastest Resolution and Route Efficiency
Given two or more viable candidate windows for the same vendor and work order When the ranking logic is applied Then windows are ordered by the following precedence: (1) SLA compliance (on-time over late), (2) earliest completion time, (3) lowest total travel gap considering required buffers from prior/next scheduled jobs, (4) fewest soft-rule relaxations required, (5) deterministic tiebreaker by earliest start timestamp And the top-ranked window minimizes SLA risk and route disruption relative to the vendor’s surrounding schedule And the ranking output includes the score/factors per window to enable auditability
Time Zone Normalization and Slot Granularity by Account
Given participants (tenant, vendor, building) in different time zones and an account-configured 2-hour slot granularity When the engine computes and returns candidate windows Then all windows are aligned to granularity boundaries in the account’s default time zone (e.g., starting at :00 of every 2-hour block) And each window includes the canonical time zone identifier and UTC timestamps to ensure unambiguous conversion And no window is proposed that would cross DST gaps/overlaps incorrectly when represented in any participant’s local time And converting a returned window to tenant or vendor local time preserves the same absolute instant
Blackout Periods and Building Access Rules Enforcement
Given account-level blackout periods (e.g., holidays), building rules (e.g., quiet hours, elevator schedules), and custom business rules (e.g., no same-day for low-priority issues) When generating candidate windows Then the engine excludes any window that overlaps a blackout period or violates a building or business rule And each excluded window is annotated internally with a machine-readable reason code for the violation And windows that comply with all rules are returned without overlap to restricted intervals And soft, override-eligible rules are identified so mitigations can be suggested
Vendor Skill, Coverage, Working Hours, and Lead-Time Enforcement
Given a work order requiring specific skills, service area coverage, vendor working hours, and a minimum lead time When the engine evaluates candidate windows for the assigned vendor Then only windows are returned where the vendor possesses the required skills and covers the work order location And each window starts no earlier than the end of the required lead time and falls within the vendor’s working hours And any window violating skill, coverage, working hours, or lead-time constraints is excluded with a corresponding reason code And if no windows remain due to these constraints, the engine returns an empty set and triggers SLA risk evaluation
SLA Risk Flagging with Reasons and Mitigations
Given constraints that make meeting the SLA due date impossible When the engine generates candidate windows Then it returns the earliest feasible windows even if they are past the SLA due date And sets an SLA risk flag on the work order output with an estimated lateness in minutes And provides a list of blocking constraints with reason codes (e.g., tenant-unavailable, vendor-off-hours, building-quiet-hours, min-lead-time) And suggests mitigations from a configured set (e.g., request tenant flexibility, expand vendor pool, authorize after-hours, override blackout with approval) And the ranking still orders windows to minimize lateness and route impact
Calendar Conflict Detection and Travel Buffer Observance
Given tenant, vendor, and building calendars contain existing events and the account requires a 30-minute travel buffer before and after each job When the engine evaluates possible windows Then it excludes any window that overlaps an existing event for any party, including the required travel buffers for the vendor And it prevents back-to-back bookings that would violate the travel buffer And partial overlaps by even one minute are considered conflicts and are excluded And all returned windows are verifiably conflict-free across all calendars with buffers applied
Ranking & Scoring for Top-3 Windows
"As a tenant, I want the best three appointment windows prioritized for speed and convenience so that I can confirm without back-and-forth."
Description

Scores each candidate slot using configurable weights for SLA compliance risk, earliest resolution time, tenant-preferred windows, vendor utilization, travel efficiency, and estimated cost, then selects and returns the top three options. Implements deterministic tie-breaking, exposes the score breakdown for transparency, and supports per-portfolio weighting presets. Ensures ranked options are strictly conflict-free at selection time by revalidating availability and locks the presented set atomically to prevent race conditions.

Acceptance Criteria
Weighted Scoring Application
Given a request with candidate slots and a weight profile for factors [slaRisk, earliestResolution, tenantPreference, vendorUtilization, travelEfficiency, estimatedCost], when scoring executes, then each slot is assigned a numeric finalScore and per-factor component scores using the provided weights. Given two otherwise identical slots differing only in one factor that favors slot A, when the weight of that factor is increased, then slot A's finalScore increases relative to slot B and the ranking updates accordingly. Given no explicit weights in the request and a portfolioId is provided, when scoring runs, then the portfolio’s active preset weights are applied. Given both portfolio preset weights and explicit request weights, when scoring runs, then explicit request weights override the portfolio preset.
Top-3 Selection and Ordering
Given at least three conflict-free candidate slots, when scoring completes and availability is revalidated, then exactly the three highest-scoring conflict-free slots are returned in descending finalScore order. Given fewer than three conflict-free candidate slots, when ranking completes, then all available conflict-free slots are returned and the response reports resultCount equal to the number returned. Given the returned top-3, then each slot is conflict-free across tenant, vendor, and building calendars and satisfies configured travel buffers and vendor shift constraints at selection time.
Deterministic Tie-Breaking
Given two or more slots whose finalScore values are equal within 0.0001, when ordering results, then ties are broken deterministically by earliestResolutionTime ascending, then vendorTravelMinutes ascending, then estimatedCost ascending, then tenantPreferenceRank ascending, then slotId ascending. Given repeated invocations with identical inputs including ties, when ranking, then the returned order is identical across runs.
Score Breakdown Transparency
Given a successful response, then each returned slot includes finalScore and breakdown fields for slaRisk, earliestResolution, tenantPreference, vendorUtilization, travelEfficiency, and estimatedCost. Given any tie was resolved, then the response includes tieBreakReason listing the applied sequence and the decisive field.
Revalidation and Atomic Locking
Given a preliminary top-3 set is computed, when immediate pre-return revalidation is performed, then any slot that has become unavailable is replaced with the next best conflict-free slot before responding. Given two concurrent requests attempt to reserve overlapping slots, when atomic locking is attempted, then only one succeeds; the other recomputes and returns an alternative set that does not include locked slots. Given a lock is held on a returned set, when a subsequent request attempts to select any of those slots before the lock is released, then it does not return those slots.
Per-Portfolio Preset Weights
Given a portfolio has saved weight presets, when a request specifies presetKey, then the named preset is applied; when presetKey is omitted, then the portfolio’s default preset is applied. Given a request specifies a presetKey that does not exist for the portfolio, when scoring runs, then system default weights are applied. Given portfolio presets are updated, when a subsequent request is made without explicit weights, then the new active default preset is used.
SLA Risk Influence
Given candidates with differing SLA compliance risk and other factors held constant, when scoring runs, then candidates with lower SLA risk receive higher slaRisk component scores and rank higher. Given all candidates violate SLA, when ranking, then the top three with highest finalScore are returned and each includes an slaRisk component indicating violation severity in the breakdown. Given at least one candidate meets SLA and another does not with all other factor values equal, when ranking, then the SLA-compliant candidate ranks above the violating candidate.
Travel Time & Route Buffering
"As a dispatch coordinator, I want travel time and buffers accounted for in scheduling so that technicians follow efficient routes and arrive within promised windows."
Description

Calculates travel times between sequential jobs using a mapping API with live traffic to estimate technician ETAs and injects configurable pre- and post-job buffers for parking, loading, and contingency. Evaluates route impact per candidate slot, prefers geographically clustered sequences, and prevents schedules that exceed daily drive-time or service radius policies. Supports multiple technicians, vehicle types, and depot starting points, and updates estimates when jobs are added, moved, or canceled.

Acceptance Criteria
Live Traffic ETA Between Sequential Jobs
Given a technician has Job A followed by Job B on the same day with confirmed service addresses When the system calculates travel time from Job A to Job B using the scheduled end of Job A plus its post-job buffer as the departure time Then the ETA and travel duration must use live-traffic routing for the target departure time and be stored with the calculation timestamp Given live traffic data is unavailable from the mapping API When calculating the travel time between Job A and Job B Then the system must fall back to historical/typical traffic profiles and flag the ETA as "estimated (no live traffic)" Given the calculated travel duration plus post-Job A buffer and pre-Job B buffer causes Job B to start after its slot window end When proposing candidate slots Then the conflicting slot must not be proposed
Configurable Pre/Post Buffers Applied to Slot Feasibility
Given organization-level default pre- and post-job buffers and technician/vehicle-level overrides exist When computing feasibility for candidate slots Then the system must apply the most specific configured buffer values and include them on both sides of each job in the sequence Given a dispatcher sets a per-job buffer override When recomputing the route Then those override values supersede default/technician buffers for that job only and are reflected in slot availability and ETAs Given buffer settings are updated When recalculating existing unstarted jobs Then affected slot feasibility, ETAs, and conflict indicators must update within 60 seconds and no overlapping schedules may remain
Daily Drive-Time and Service Radius Enforcement
Given a technician has a configured daily maximum drive-time T and a service radius R from their start depot When evaluating a candidate slot Then the sum of all planned drive durations for that day including the candidate segment must be <= T and all waypoints must be within R of the depot; otherwise the slot is rejected with a policy-violation reason Given a slot is rejected due to a policy limit When displaying slot options Then the UI must not show that slot and an audit log entry must record the policy type, threshold, and computed value
Geographic Clustering Preference in Ranking
Given multiple conflict-free candidate slots across technicians exist for a request When ranking options Then the system must prioritize slots that minimize incremental drive time added to the day’s route and secondarily minimize distance between consecutive jobs, breaking ties by earliest SLA deadline Given two slots have equal incremental drive time and identical SLA urgency When ranking Then prefer the slot that yields the smaller route bounding radius for the technician’s day
Multiple Technicians, Vehicle Types, and Depots
Given technicians each have a start-of-day depot, shift hours, and assigned vehicle type When calculating travel time for the first job of the day and between sequential jobs Then departure for the first job must be from the technician’s depot at shift start (or current on-route location if applicable) and routing must use the vehicle profile supported by the mapping API Given the mapped vehicle profile is unsupported by the API When calculating travel times Then the system must use a default profile and flag the estimate with "standard vehicle profile used" Given overlapping feasible slots exist for multiple technicians When presenting the top three options Then only technicians whose calendars accommodate the buffered sequence are included
Reactive Recalculation on Add/Move/Cancel
Given a scheduled day with jobs J1..Jn for one or more technicians When a job is added, moved, or canceled Then the system must recompute ETAs, drive durations, buffers, and policy validations for affected technicians and downstream jobs within 30 seconds and update slot rankings accordingly Given recomputation produces overlaps or policy violations When conflicts are detected Then generate conflict alerts and remove invalid slots from consideration until resolved Given a job is in progress When recomputing after changes Then treat the in-progress job’s timing as fixed and adjust only downstream jobs
Slot Hold & Confirmation Workflow
"As a tenant, I want to pick from held time slots that won’t disappear mid-selection so that I can confidently confirm an appointment."
Description

Places soft holds on calendars for the proposed top-three windows, sets an expiration timer, and promotes a hold to confirmed upon tenant and vendor acceptance. Releases holds automatically on expiration or decline, recalculates alternatives when conflicts arise, and updates the work order status and notifications accordingly. Supports rescheduling, partial-day availability, and manager overrides, and prevents double-booking by enforcing atomic updates across all linked calendars.

Acceptance Criteria
Soft Holds for Top Three Windows
Given a work order with three proposed scheduling windows generated by Smart Slot Match And tenant, vendor, and building calendars are available to place holds When the proposal is issued Then the system places soft holds for all three windows on tenant, vendor, and building calendars atomically And each hold is labeled with the work order ID and hold status And the work order status updates to "Scheduling Proposed" And notifications with the three options are sent to the tenant and vendor within 1 minute
Hold Expiration Timer and Auto-Release
Given soft holds exist with a configured expiration window When the expiration time elapses without both parties accepting Then all related holds are released atomically across all calendars And the work order status updates to "Scheduling Expired" And notifications are sent to tenant, vendor, and manager within 1 minute Given soft holds exist with a configured expiration window When either party explicitly declines all options before expiration Then all related holds are released atomically and the work order status updates to "Scheduling Declined" And notifications are sent to tenant, vendor, and manager within 1 minute
Confirmation on Dual Acceptance
Given one of the three proposed windows is selected by both tenant and vendor before expiration When the second acceptance is recorded Then the corresponding soft hold is promoted to a confirmed appointment atomically across all calendars And the other two holds are immediately released And the work order status updates to "Scheduling Confirmed" And confirmation notifications with appointment details are sent to tenant, vendor, and manager within 1 minute
Conflict Recalculation and Alternative Slots
Given any soft hold or confirmed appointment becomes conflicted due to external calendar changes When the conflict is detected Then the conflicted slot is invalidated and released atomically And the system recalculates and places soft holds on the next best three windows that meet SLA deadlines and travel buffers And the work order status updates to "Scheduling Proposed" And updated option notifications are sent to tenant and vendor within 1 minute
Atomic Double-Booking Prevention
Given concurrent requests attempt to place holds or confirm appointments that overlap for any tenant, vendor, or building When the system processes these operations Then it enforces atomic updates across all linked calendars And no double-booking occurs for any party And if a contention occurs, the losing transaction is rejected with a clear error and no partial holds remain
Rescheduling with Partial-Day Availability
Given a confirmed appointment exists and either participant updates availability to partial-day time ranges When rescheduling is initiated by any party Then the system generates the top three conflict-free windows that respect the new partial-day constraints, SLA deadlines, and travel buffers And places soft holds on those windows and requires fresh acceptance from both tenant and vendor And the original confirmed appointment remains in place until a new time is confirmed, after which it is canceled and all redundant holds are released atomically And the work order status transitions to "Rescheduling" during this process and to "Scheduling Confirmed" upon new confirmation And reschedule notifications are sent to all parties within 1 minute
Manager Override of Holds and Confirmations
Given a manager chooses to override a scheduling decision When the manager force-confirms a specific window, extends a hold’s expiration, or force-releases a hold Then the action is applied atomically across tenant, vendor, and building calendars And all conflicting holds are released and statuses updated accordingly And an audit log records the override with actor, timestamp, and reason And notifications reflecting the override are sent to tenant and vendor within 1 minute
Notifications, ICS Invites, and Reminders
"As a property manager, I want clear messages with actionable links and calendar invites sent to all parties so that confirmations happen quickly and reliably."
Description

Delivers multichannel notifications (SMS, email, in-app) to tenants, vendors, and managers with the top-three slot proposals, single-tap selection links, and clear expiration times. Sends ICS calendar invites for held and confirmed appointments, includes reschedule/cancel links, and provides reminder messages at configurable intervals (e.g., 24 hours and 2 hours before). Localizes content, respects user notification preferences, and logs delivery and engagement outcomes for reliability tracking.

Acceptance Criteria
Tenant Receives Top-Three Slot Proposals With Single-Tap Selection
Given a tenant has an open maintenance request awaiting scheduling And Smart Slot Match has computed ranked top three windows with an explicit expiration time And the tenant has SMS and email enabled in preferences When notifications are dispatched Then the tenant receives SMS and email containing exactly 3 ranked time windows with localized dates/times and the expiration timestamp And each time window includes a unique single-tap selection link And links remain valid until the expiration time and return an "expired" message after expiration And the same proposal is visible in-app with identical content And all sends and their delivery outcomes are logged per channel
Vendor and Manager Receive Coordinated Slot Proposal and Hold
Given a vendor is eligible and available for the ticket and a manager oversees the property When the top-three slot proposal is sent Then the vendor and manager each receive notifications with the same 3 ranked windows including travel buffer indicators And the manager can place a 30-minute hold on one slot via a single-tap link And upon hold, tentative (.ics) invites are sent to tenant, vendor, and manager including the hold expiration time And the held slot is excluded from other proposals during the hold window And if the hold expires without confirmation, tentative invites auto-cancel and slot links revert to selectable state
ICS Invites For Confirmed Appointments With Reschedule/Cancel
Given a proposed slot is confirmed by required parties When confirmation occurs Then .ics calendar invites are sent to tenant, vendor, and manager with correct timezone, location, attendee list, and appointment details And invites include deep links to reschedule and cancel actions And any reschedule or cancellation updates the calendar event for all attendees within 60 seconds And attendee RSVP responses (accept/decline/tentative) are captured and logged
Configurable Reminder Cadence and Quiet Hours
Given a confirmed appointment exists And reminder cadence is configured to 24 hours and 2 hours before start And user quiet hours are configured (e.g., 9:00 PM–8:00 AM local) When reminder dispatch times are reached Then reminders are sent at the configured intervals in the recipient’s local time And SMS/email reminders that fall within quiet hours are deferred to the next allowed time, while in-app reminders are posted as scheduled And channel-level opt-out preferences are honored And duplicate reminders for the same interval are not sent
Localization of Content and Timezones
Given a recipient’s preferred language is Spanish and timezone is America/Chicago When a proposal, invite, or reminder is generated Then the message content is localized to Spanish including dynamic labels and templates And all displayed times include the recipient’s local timezone abbreviation And .ics files include VTIMEZONE data matching America/Chicago And if a translation key is missing, the message falls back to English and a warning is logged
Delivery and Engagement Logging With Reliability Metrics
Given notifications are sent across SMS, email, and in-app channels When delivery attempts are made Then the system logs per message: channel, recipient identifier, provider message ID, send timestamp, delivery status (queued/sent/delivered/failed), provider response code, and retry count And selection/hold/reschedule/cancel link clicks are logged with timestamp and action And .ics RSVP responses (accept/decline/tentative) are logged And a dashboard aggregates delivery success rate and average time-to-engagement by channel daily And if channel failure rate exceeds 2% over a 1-hour window, an alert is raised and an alternate channel is used when permitted by user preferences
Audit Trail & SLA Compliance Metrics
"As an operations lead, I want visibility into how slots were generated and chosen and whether SLAs were met so that I can optimize policies and prove compliance."
Description

Captures a detailed audit of generated options, scoring factors, selections, holds, confirmations, reschedules, and cancellations, correlated to work orders and SLA outcomes. Provides dashboards and exports for time-to-first-proposal, time-to-confirmation, on-time arrival rate, travel hours saved, and first-available delays avoided. Enables troubleshooting through event timelines and supports compliance needs by retaining records with configurable retention policies and privacy controls.

Acceptance Criteria
End-to-End Scheduling Audit Log Capture
Given a work order is created and Smart Slot Match generates scheduling options When the options are computed Then the system persists an OptionsGenerated audit event within 2 seconds (p95) containing: workOrderId, eventId, eventType="options_generated", timestamp (ISO 8601 UTC, ms), actor="system", algorithmVersion, inputsSummary (tenant/vendor/building availability windows), constraints (SLA deadline, travel buffers), and the top 3 options with optionId, start/end, score, and scoringFactors And the event store is append-only and immutable And duplicate event detection prevents more than one OptionsGenerated event for the same computation (idempotency key) Given a user or system selects an option, places a hold, confirms, reschedules, or cancels When the action occurs Then a corresponding SelectionMade/HoldPlaced/AppointmentConfirmed/AppointmentRescheduled/AppointmentCancelled audit event is persisted with reasonCode, previousState, newState, actor (user/vendor/tenant/system), and correlation to the prior optionId(s) And all audit writes are retried up to 3 times with exponential backoff on transient failure and surface an error if persistence fails
SLA Correlation and Outcome Tracking
Given an organization SLA policy defines start trigger and deadline When a work order is opened and the SLA window begins Then an SLAStarted event is recorded with slaPolicyId, windowId, startTimestamp Given the first OptionsGenerated event for the work order When it is recorded Then timeToFirstProposalMinutes = (optionsGenerated.timestamp - SLAStarted.startTimestamp) is computed and stored on the work order metrics Given the first AppointmentConfirmed event for the work order When it is recorded Then timeToConfirmationMinutes = (appointmentConfirmed.timestamp - SLAStarted.startTimestamp) is computed and stored Given a TechnicianArrived event is recorded with arrivalTimestamp When compared to the scheduledStart and org onTimeThresholdMinutes Then onTimeArrival = true if arrivalTimestamp <= scheduledStart + onTimeThresholdMinutes else false Given the SLA deadline passes When compared against completion/arrival events Then slaOutcome is set to "Met" or "Breached" and correlated to windowId on all related audit events
KPI Dashboard Accuracy and Freshness
Given a user with Reporting Access opens the SLA & Audit dashboard When filters (date range, property, vendor, priority, SLA policy) are applied Then KPIs display: timeToFirstProposal (median, p90), timeToConfirmation (median, p90), onTimeArrivalRate (%), travelHoursSaved (sum, avg), firstAvailableDelaysAvoidedMinutes (sum, avg) And each KPI includes a definition tooltip that matches the computation used in data And dashboard values are refreshed at least every 5 minutes and show a "Last updated" timestamp And totals and breakdowns by property and vendor match the underlying dataset within 0.5% rounding tolerance And clicking any KPI segment opens a drill-down list of matching work orders within 2 seconds (p95)
Metrics and Audit Export Compliance
Given a user with Export permission requests a CSV or JSON export for a date range up to 31 days and result size up to 100,000 rows When the export is initiated Then the system either streams the file within 60 seconds (p95) or enqueues a job and delivers a downloadable link within 15 minutes (p95) And the metrics export contains columns: workOrderId, propertyId, vendorId, createdAt, firstProposalAt, confirmationAt, onTimeArrival (bool), travelHoursSavedMinutes, firstAvailableDelayAvoidedMinutes, slaPolicyId, slaOutcome, algorithmVersion And the audit events export contains columns: eventId, workOrderId, eventType, timestamp (UTC, ms), actorType, actorId, optionId (nullable), reasonCode (nullable), payloadHash, windowId (nullable) And timestamps are output in either UTC or a user-selected timezone and include the timezone in the file metadata And PII fields are included or masked per the user's role and privacy settings, with a mask of **** for restricted fields And every export is logged with requester, timeframe, recordCount, checksum, and retention of the export record for 90 days
Per-Work-Order Event Timeline for Troubleshooting
Given a user opens a work order's Timeline view When events are loaded Then events render in strict chronological order with inter-event latency shown between consecutive events And filters allow inclusion/exclusion by event type (options, selection, hold, confirmation, reschedule, cancel, SLA events, arrival) And selecting an event reveals details including scoringFactors snapshot for options and the diff between consecutive schedules for reschedules And all timestamps display in the user's timezone with hover to show UTC And the timeline loads within 1.5 seconds for up to 200 events (p95) And the user can export the visible timeline to CSV from this view
Configurable Data Retention and Legal Holds
Given an organization admin sets auditEventRetentionDays and metricsRetentionMonths When the policy is saved Then the new policy version is recorded and applies to subsequent purge cycles Given the nightly purge job runs When audit events exceed retention and are not on legal hold Then the system hard-deletes those events and writes a PurgeCompleted audit record with counts and ranges And metric aggregates persist unless their retention has lapsed, in which case they are recalculated on demand or removed as configured Given a work order or entity is placed on Legal Hold by an admin When purge executes Then no related audit events are deleted until the hold is removed Given records contain PII subject to privacy constraints When retention thresholds are reached Then PII fields are redacted while preserving non-PII aggregates as configured
Privacy Controls and Role-Based Access
Given role-based permissions are configured When a user without ViewSensitiveAudit attempts to access audit details containing PII (names, phone, email, free-text notes) Then PII fields are masked and access is denied with 403 for raw payload download And the access attempt is logged with userId, resource, and outcome Given a Data Subject Request for deletion is approved for a tenant When the DSR job runs Then audit payload fields containing tenant PII are redacted within 30 days while retaining metric values and structural event metadata And all redactions are logged with reversible tokens disabled (irreversible hash) Given encryption-at-rest and in-transit policies When audit data is stored and retrieved Then AES-256 at rest and TLS 1.2+ in transit are enforced and validated in security tests

One-Tap Confirm

Sends secure magic links so tenants and vendors can confirm, reschedule, or propose alternates without logging in. Temporarily holds selected slots, pushes confirmed events to calendars, and issues instant confirmations—turning days of phone tag into minutes.

Requirements

Secure Magic Links
"As a tenant or vendor, I want a secure one-tap link to manage an appointment so that I can act quickly without logging in or calling."
Description

Generate and deliver one-time, expiring, role-scoped magic links that allow tenants and vendors to confirm, reschedule, or propose alternate times without authentication. Links must be signed, time-bound, single-use, and revocable, with device and IP validation to reduce misuse. The service integrates with FixFlow’s identity and scheduling layers to fetch context (work order, unit, contact), enforce permissions, log actions, and prevent replay. Delivery supports email and SMS with localized content and deep links. Observability includes metrics on sends, opens, actions, and failure reasons to optimize reliability and conversion.

Acceptance Criteria
Tenant confirms appointment via email magic link
Given a tenant has an active magic link for Work Order X and time slot Y When they click the link within its configured TTL Then they can confirm the appointment without logging in and the link is redeemed Then the system updates Work Order X to Confirmed for slot Y, pushes events to calendars, and sends confirmation via the configured channel(s) Then an audit log is recorded with correlationId, tokenId, workOrderId, role=tenant, action=confirm, outcome=success, timestamp, IP, and userAgent Then the magic link becomes invalid for any subsequent use and returns 'link already used' without side effects
Vendor reschedules via SMS magic link with temporary slot hold
Given a vendor receives an SMS magic link for Work Order X with available times When they select alternate time Z via the link Then the system places a temporary hold on time Z for the configured hold window and shows a success state Then Work Order X is updated to Pending-Reschedule with proposed time Z and proposer=vendor Then if tenant confirms within the hold window, status becomes Confirmed and calendars are updated; if the window expires, the hold is released and both parties are notified Then all state changes and notifications are logged; the link action is single-use and cannot be reused
Token security: signed, time-bound, single-use, role- and context-scoped
Given a magic link is generated for role R with context {workOrderId, unitId, contactId, allowedActions, expiresAt} When the link is validated Then the signature verifies against the current/rotated signing key, expiresAt is in the future, and the requested action ∈ allowedActions When the token is expired, revoked, scope-mismatched, or signature-invalid Then the endpoint returns 401/403, no state changes occur, and a security_event is logged When the token is redeemed Then it is marked used and any subsequent requests return 410 Gone without side effects Then key rotation is supported such that still-valid older tokens remain verifiable until expiry
Revocation and device/IP validation reduce misuse
Given a magic link has not yet been redeemed When an admin or automated rule revokes it Then subsequent clicks return 410 Gone and no action is permitted When the link is first validated Then the token binds to device fingerprint and originating IP per policy When a later attempt occurs from a different device/IP outside policy tolerance before redemption Then the action is blocked, a security_event is logged, and no state changes occur; same-device/IP attempts proceed When redemption has occurred Then any further clicks show 'link already used' and produce no state changes
Replay and idempotency protection on action endpoints
Given an action (confirm/reschedule/propose) is initiated via a magic link When the POST to execute the action is received Then a one-time action nonce derived from the token is required and verified When duplicate POSTs or retries with the same nonce occur Then the endpoint responds idempotently with the original result and no duplicate calendar pushes or notifications are sent Then side-effecting operations use an idempotency key scoped to {workOrderId, action, timeSlot}
Localized email and SMS delivery with deep links
Given a contact has preferred locale L and channel C (email or SMS) When a magic link is sent Then the message uses locale L templates with populated {workOrderId, unit address, scheduled window} and is formatted appropriately for channel C Then the link includes a mobile deep link to open the FixFlow app when installed and falls back to a responsive web page otherwise When locale L is unsupported Then content falls back to en-US and a localization_fallback metric is recorded Then provider responses are captured; transient failures are retried per backoff policy and permanent failures record failure_reason codes
Observability and conversion funnel metrics
Given magic links are generated and delivered When lifecycle events occur Then metrics are emitted for send_attempted, sent, delivered, opened, clicked, acted_success, acted_fail, expired, revoked, segmented by channel, role, locale, and provider Then dashboards show conversion rate and median time-to-action by cohort; p95 latency from link open to action; alerts trigger when delivery error rate or conversion drop exceed configured thresholds Then audit logs include correlationId linking token and actions with fields {tokenId, workOrderId, role, action, outcome, timestamp, IP, userAgent}
No-Login Action Portal
"As a tenant, I want a simple web page from the link where I can confirm or change the appointment so that I can complete the task in seconds on my phone."
Description

Provide a mobile-first, accessible web view that loads from the magic link and presents context (job summary, location, time window) with clear options to confirm, reschedule, or propose alternates, add notes, and update contact availability. The portal validates link integrity, shows real-time slot availability, handles time zones, and surfaces any manager-required constraints (e.g., access instructions, approvals). It integrates with FixFlow’s scheduling API, permissions, and localization, and gracefully handles expired/invalid links by offering safe fallback actions (request new link, contact support).

Acceptance Criteria
Load Portal with Valid Magic Link
Given a valid, unexpired, untampered magic link tied to a specific work order and role When the user opens the link on a mobile device Then the portal loads without login and returns HTTP 200 with LCP ≤ 3.0s on standard 4G And the page shows job summary (title and masked ID), service location (city, state), and scheduled time window in the detected timezone And primary actions are visible and enabled: Confirm, Reschedule, Propose Alternate, Add Notes, Update Contact And all interactive elements are keyboard accessible with visible focus, have ARIA labels, and automated WCAG 2.1 AA checks report zero critical violations And locale is applied from link or browser with fallback to en-US And token validation and role-based permissions are enforced via FixFlow auth
One-Tap Appointment Confirmation
Given a valid link and an eligible appointment window with no conflicting holds When the user taps Confirm Then the portal places a 5-minute hold via the Scheduling API using an idempotency key and confirms the appointment within 2 seconds if successful And a confirmation screen is displayed with date/time, timezone, and reference number And an ICS calendar invite with correct timezone is generated and delivered; Add to Google/Outlook buttons are available And SMS/email confirmations are sent to the tenant/vendor and manager within 60 seconds And an audit log entry is recorded with user role, timestamp (UTC), and IP And if the hold fails due to conflict, the user sees a clear message and the slot list refreshes within 2 seconds
Reschedule with Real-Time Availability
Given the user selects Reschedule When the portal fetches open slots Then it retrieves live availability from the Scheduling API with ≤ 1s latency, filters by manager constraints (working hours, blackout dates, minimum lead time), and shows only future slots And at least the next 10 available options are displayed if present, in the user's timezone When the user selects a slot Then a 5-minute hold is placed and a countdown is shown And upon user confirmation within the hold, the new appointment is booked, the old appointment canceled, notifications are sent, and calendar invites are updated And if the hold expires before confirmation, the slot is released and the user is prompted to choose another
Propose Alternate Time Windows
Given the user cannot find a suitable slot When the user selects Propose Alternate Then the portal allows submission of up to 3 preferred time windows (minimum 1-hour duration each) within the next 30 days in the user's timezone, plus an optional note up to 500 characters And manager constraints (e.g., no weekends, access prerequisites) are displayed and enforced And windows must be in the future and non-overlapping When the user submits Then a proposal record is created, the manager is notified, and a confirmation message with reference number and expected response SLA is shown And no appointment is booked or modified until manager action
Update Contact and Access Details
Given the user selects Update Contact When the user edits their details Then phone is validated to E.164 format, email is RFC-compliant, preferred contact hours are selectable, and access instructions accept up to 500 characters And restricted fields are hidden per role; tenants cannot view vendor contact and vendors cannot view tenant contact When the user saves Then changes are persisted via API with source=portal, timestamped in UTC, and PII is masked in logs And a success message is shown within 2 seconds and an audit entry is recorded
Expired or Invalid Link Fallback
Given the link is expired or has an invalid signature When the user opens the link Then the portal renders an Expired/Invalid Link page with HTTP 401 or 410 and reveals no work-order details And actions offered are Request New Link and Contact Support And Request New Link requires verifying the contact (e.g., last 4 of phone or email) and is rate-limited to 3 requests per 24 hours per contact And no scheduling or availability API calls are made And the attempt is logged with reason code
Localization, Time Zone, and DST Accuracy
Given the user's locale and timezone may differ from the job's When viewing time windows Then all times are displayed in the user's timezone with explicit zone label and convert correctly across DST boundaries while the backend stores UTC When downloading calendar invites Then ICS files include VTIMEZONE with correct offsets so external calendars show accurate local times When the user overrides the detected timezone Then availability is re-queried and re-rendered within 1 second in the selected timezone And all text and date formats are localized per selected locale, including correct rendering for RTL languages
Slot Hold & Conflict Management
"As a vendor, I want selected time slots to be temporarily reserved while the tenant confirms so that my calendar doesn’t get double-booked."
Description

Implement temporary holds on selected appointment slots when a recipient initiates an action, preventing double-booking while they complete confirmation. Holds auto-expire after a configurable TTL and are idempotent across repeated opens of the same link. Concurrency controls ensure atomic confirmations, with clear user messaging if a slot becomes unavailable. Integrates with the scheduling engine, vendor availability, buffers, travel time rules, and blackout periods, and emits events for analytics and audit.

Acceptance Criteria
Hold Creation on Magic Link or Slot Selection
Given a secure magic link with a deep-linked slot is opened by the intended recipient When the confirmation view loads Then a hold is created for that slot for that recipient using the configured TTL and the UI shows a countdown Given no deep-linked slot is present When the recipient taps an available slot Then a hold is created for that slot using the configured TTL and the UI shows a countdown Given a hold has been created When other recipients query availability Then the held slot is marked as temporarily unavailable for the hold duration
Prevent Double-Booking During Active Hold
Given Slot S is under an active hold for Recipient A When Recipient B attempts to hold or confirm Slot S Then the system blocks the action, displays a clear message that the slot is held until <timestamp>, and presents at least 3 alternate slots if available Given Slot S is under an active hold When Recipient B refreshes availability Then Slot S remains non-selectable until the hold expires or is released Given the hold on Slot S expires When Recipient B attempts to confirm Slot S Then the system allows the confirmation if the slot still complies with scheduling rules
Idempotent Hold on Reopen and Multi-Device
Given Recipient A opens the same magic link multiple times or on multiple devices within the TTL window When the availability view loads Then no additional holds are created and the original hold ID is reused Given Recipient A refreshes the page within TTL When the view reloads Then the hold TTL is not extended unless enable_hold_ttl_refresh_on_interaction is true Given the original hold has expired When Recipient A opens the link again or taps the slot Then a new hold is created with a new hold ID
Atomic Confirmation Under Concurrency
Given two recipients attempt to confirm the same slot concurrently When the confirmation requests reach the server Then exactly one booking is created and the other request receives a 409 SlotUnavailable with a user-facing message and correlation ID Given a recipient taps Confirm multiple times or experiences a retry When duplicate confirmation requests with the same idempotency key are processed Then only one booking is created and subsequent requests return 200 with the same booking reference Given a confirmation loses the race due to a competing hold or booking When the loser receives the response Then the UI offers at least 3 alternate slots and does not show the lost slot as available
Rule-Aware Holds Respect Availability, Buffers, Travel, Blackouts
Given vendor calendars, service buffers, travel-time rules, and blackout periods are configured When a hold is created Then holds are only allowed on slots that satisfy all configured rules Given a previously valid hold becomes invalid due to a rule change When the recipient attempts to confirm Then the system prevents confirmation, informs the recipient the slot is no longer valid, and offers compliant alternatives Given a hold is confirmed When the scheduling engine recomputes the vendor route Then the resulting booking still respects buffers, travel-time, and blackout rules otherwise the confirmation is rejected with clear messaging
Hold Expiry, Messaging, and Alternatives
Given a hold is active When the TTL elapses without confirmation Then the hold auto-expires, the slot is released, and the UI updates to remove the countdown and mark the slot available to others Given a hold expires while the recipient is viewing the confirmation screen When they attempt to confirm Then the system blocks confirmation, displays 'Your hold expired' messaging, and presents at least 3 alternate slots Given network latency or offline conditions during expiry When connectivity resumes Then the UI reconciles with server state and reflects expiry within 2 seconds of resync
Analytics and Audit Event Emission
Given any hold lifecycle change When a hold is created, refreshed, released, expired, or converted to a booking Then an event is emitted with hold_id, slot_id, recipient_id, link_id, tenant_id or vendor_id, ttl_seconds, status, timestamp (UTC), and correlation_id Given a conflict occurs due to an active hold or atomic-confirm race When the server responds Then a conflict event is emitted with reason and impacted_slot_ids and is recorded in the append-only audit log Given transient analytics delivery failures When retries occur Then confirmation and release side-effects still complete, events are retried with at-least-once semantics, and duplicates are deduplicated via idempotency keys
Reschedule & Alternate Proposals
"As a tenant, I want to propose alternate times when the offered slots don’t work so that I can still get service without lengthy back-and-forth."
Description

Enable recipients to reschedule by selecting new times within configured constraints (service windows, lead times, priority levels) or to propose alternates when no suitable slots are visible. Proposed times trigger manager/vendor approval workflows as needed, with notifications and SLAs. The system reconciles proposals with availability, suggests nearest feasible options, and preserves message context and notes. All changes update the work order timeline and maintain a full audit trail.

Acceptance Criteria
Reschedule Within Configured Constraints via Magic Link
Given a recipient opens a valid, unexpired magic link for a work order And the work order has configured service windows, minimum lead times, and priority rules When the recipient selects an available time slot shown Then the system validates the slot against service windows, lead time, blackout dates, and vendor capacity And if valid and no approval is required, the appointment status updates to Confirmed and appears on the work order And a temporary hold is placed on the selected slot for the configured hold duration during confirmation And the confirmed event is pushed to all relevant calendars for tenant, vendor, and manager as configured And confirmation notifications are sent via the configured channels And if invalid, the selection is rejected with an inline reason code and the UI displays nearest feasible alternatives
Alternate Proposal Submission When No Suitable Slots Are Visible
Given a recipient opens a valid magic link and no suitable slots are visible or they choose Propose Alternates When the recipient submits one or more proposed time ranges within allowed service hours Then the system validates each proposal format and business-hours compliance without placing holds And the proposals are recorded against the work order with the proposer identity and optional notes And the appropriate approvers and/or vendor receive notifications of new proposals via configured channels And the recipient sees an acknowledgement with a pending status and reference ID
Approval Workflow Routing for Reschedules and Proposals
Given a reschedule selection or alternate proposal is created And the work order policy indicates approval is required based on priority, cost threshold, vendor, or access rules When the system evaluates the event Then the item is marked Pending Approval and routed to the designated approver(s) And approvers receive one-tap approve/decline links with proposal details and context And on approval the appointment is confirmed, calendars are updated, and notifications are sent And on decline the proposer is notified with standardized decline reason codes and prompted with nearest feasible alternatives
SLA Timers and Escalations for Pending Approvals or Responses
Given a reschedule or proposal is in a Pending state When the item is created Then an SLA timer starts using the configured target for the work order’s priority And reminder notifications are sent at configured intervals until resolution And if the SLA breaches, the system escalates to the fallback approver and manager and flags the breach on the work order And all reminder and escalation notifications are logged with timestamps and recipients
Availability Reconciliation and Nearest Feasible Suggestions
Given a proposed or selected time conflicts with availability or constraints When the system evaluates the time against vendor calendar, travel buffers, blackout dates, service windows, and lead times Then the system returns the top N nearest feasible options ranked by earliest feasible time and proximity to requested time And each suggestion lists the reason the original time was ineligible using standard reason codes And accepting a suggested time via one tap schedules it (or moves to approval if required) and sends confirmations
Preservation of Message Context and Notes Through Changes
Given a recipient adds notes while rescheduling or proposing alternates When the change is submitted Then the original message thread, attachments, and prior notes remain intact And the new notes are appended with author, timestamp, and role And notifications to stakeholders include the latest notes and a link to the full context subject to permissions
Work Order Timeline and Audit Trail for Reschedules and Proposals
Given any reschedule selection, proposal submission, approval, decline, or escalation occurs When the event is processed Then an immutable audit entry is created capturing timestamp, actor (role and identity), action, before/after values, channel (magic link, app, email), and IP/user agent where available And the work order timeline displays the event in chronological order with links to related artifacts (notes, proposals, calendar events) And audit entries are read-only and cannot be edited, only superseded by new events And the audit log is exportable via the reporting API and UI with filters for date range, actor, and action type
Calendar Push & ICS Invites
"As a vendor, I want confirmed appointments to appear on my calendar automatically so that I don’t miss or double-book jobs."
Description

Push confirmed and rescheduled appointments to tenant and vendor calendars via ICS attachments and direct integrations (Google, Microsoft 365/Outlook, Apple Calendar via ICS). Updates propagate on changes and cancellations, include location and instructions, and respect time zones. Failed pushes retry with backoff and degrade gracefully to ICS attachments. Calendar URLs are tokenized to protect details. Integrates with FixFlow notifications and scheduling events for real-time sync.

Acceptance Criteria
Direct Push to Google and Microsoft 365 on Confirmation
Given a tenant confirms an appointment via One-Tap Confirm and the tenant uses Google Calendar while the vendor uses Microsoft 365 When FixFlow processes the confirmation Then FixFlow creates events via Google Calendar API and Microsoft Graph within 10 seconds And the event title is "[FixFlow] Maintenance: <issue title>" And start/end times are stored in UTC and pushed with the correct local time zone for each attendee And the location equals the property address on file And the description includes appointment instructions and a tokenized manage URL And attendees include the tenant and vendor set to Accepted And events appear in both calendars within 60 seconds of confirmation And FixFlow stores external event IDs for future updates
ICS Fallback with Exponential Backoff on Failed Push
Given a direct calendar API push fails with 5xx or 429 When FixFlow retries the push Then it retries up to 5 times with exponential backoff (2s, 4s, 8s, 16s, 32s) with jitter And if retries exhaust, FixFlow sends an ICS attachment (METHOD:REQUEST) via email/SMS to all participants within 2 minutes And the ICS contains a stable UID, SUMMARY, DTSTART/DTEND with VTIMEZONE, LOCATION, DESCRIPTION, and a tokenized manage URL And FixFlow marks the integration push as Failed and the ICS fallback as Sent in telemetry
Reschedule Propagates to Calendars
Given an event has previously been confirmed and pushed When the tenant reschedules to a new time via the magic link Then FixFlow updates the existing external events using stored event IDs (or ICS with the same UID) within 15 seconds And only one event remains in each participant’s calendar at the new time And the previous time slot is released And updated times reflect each participant’s local time zone correctly And the description and instructions are updated accordingly And participants receive an update notification from their calendar system
Cancellation Propagation and Clean-up
Given an existing confirmed event When the event is canceled by any party through One-Tap Confirm Then FixFlow issues API delete/cancel operations and sends ICS METHOD:CANCEL with the same UID within 15 seconds And the event is removed or marked canceled in Google, Microsoft 365, and Apple Calendar within 60 seconds And no orphaned duplicate events remain for either participant And FixFlow records cancellation status and external event IDs for audit
Time Zone and DST Accuracy
Given the tenant is in America/Los_Angeles and the vendor is in America/New_York and the appointment is 9:00 AM tenant local When the event is pushed Then the tenant’s calendar shows 9:00 AM PT and the vendor’s shows 12:00 PM ET for the same appointment And events spanning DST transitions use IANA time zones and include VTIMEZONE in ICS so offsets are correct And internal storage uses UTC with source time zone captured And automated tests cover at least 10 DST boundary cases with zero failures
Tokenized Calendar URLs and Data Minimization
Given FixFlow embeds management links in calendar descriptions When a recipient accesses the link Then the URL contains a signed token with at least 128 bits of entropy scoped to the event and recipient and expiring no later than 7 days after event end And requests without a valid token return 401 and disclose no event details And tokens are redacted in logs and analytics And calendar fields omit unnecessary PII (no phone numbers or full email in description; attendee emails only in attendee metadata)
Apple Calendar Compatibility via ICS
Given a participant uses Apple Calendar on iOS or macOS When they receive ICS METHOD:REQUEST and METHOD:CANCEL messages Then Apple Calendar imports the request as a single event with correct local time, location, and description And subsequent updates with the same UID modify the existing event rather than creating duplicates And METHOD:CANCEL removes or marks the event as canceled And line breaks and special characters in DESCRIPTION render correctly without HTML tags And ICS files pass RFC 5545 validation
Instant Notifications & Audit Trail
"As a property manager, I want instant confirmations and a clear history of actions so that I have visibility and can resolve disputes quickly."
Description

Send immediate confirmations and updates to all relevant parties (tenant, vendor, manager) via preferred channels (email, SMS, in-app), including appointment details, next steps, and contact instructions. Every action from link open to final confirmation is time-stamped and recorded with actor, method, and metadata for compliance and support. Expose a timeline in the work order and emit webhook events for downstream systems. Include rate limiting, bounce handling, and localization.

Acceptance Criteria
Immediate Notifications for Confirm, Reschedule, and Cancel
Given a tenant or vendor completes a confirm, reschedule, or cancel action via a valid magic link And each recipient has saved notification preferences with an ordered list of preferred channels (email, SMS, in-app) When the action is submitted and acknowledged by the server Then notifications are dispatched to the tenant, vendor, and manager via each recipient's highest-priority available channel within 10 seconds And each notification includes appointment date, time, timezone, property address, next steps, and contact instructions And each delivery attempt is recorded per recipient-channel with a unique message_id And if the highest-priority channel is unavailable, the next available preferred channel is used within the same 10-second window
Comprehensive Audit Trail for One-Tap Link Lifecycle
Given a magic link is issued for a work order When any of the following occur: link_opened, token_validated (success/failure), action_selected, slot_held, appointment_confirmed/rescheduled/canceled, notification_sent/delivered/opened/bounced Then an append-only audit record is persisted within 1 second containing: timestamp (UTC ISO 8601), actor type and identifier, action, channel/method, and metadata (IP, user_agent, locale, message_id, slot_id if applicable) And delivery lifecycle events are linked to their notification message_id And audit records are retrievable by work_order_id and actor and are ordered by timestamp descending
Work Order Timeline Displays Event History
Given a manager opens the work order timeline for a specific work order When events exist for that work order Then the timeline lists events in reverse chronological order with event type, timestamp in the viewer's local timezone (with UTC available), actor, channel, and key metadata And the timeline updates in near real time, showing new events within 3 seconds of audit persistence And users can filter by event type (action, notification, delivery) and actor (tenant, vendor, manager, system) And each entry links to its raw audit payload for support review
Webhook Events for Downstream Systems
Given webhooks are configured and enabled for the account When any of these events occur: link_issued, link_opened, action_selected, slot_held, appointment_confirmed, appointment_rescheduled, appointment_canceled, notification_sent, notification_delivered, notification_opened, notification_bounced Then the system POSTs a JSON payload to the configured endpoint within 10 seconds including: event_type, occurred_at (UTC ISO 8601), work_order_id, actor, channel, message_id (if applicable), and metadata And each request includes an HMAC-SHA256 signature header computed with the shared secret And retries use exponential backoff for up to 24 hours on 5xx/network errors; 2xx responses stop retries; 4xx responses are not retried And an idempotency key is included to prevent duplicates for 24 hours
Notification Rate Limiting and Coalescing
Given multiple triggering events occur for the same recipient and work order within a short interval When the system prepares to send notifications Then no more than 3 notification messages per recipient per work order are sent within any rolling 5-minute window per channel And excess notifications are coalesced into a single summary message sent within 30 seconds And suppression and coalescing decisions are recorded in the audit trail with references to original event IDs
Bounce and Delivery Failure Handling with Fallback
Given a notification delivery attempt fails When the failure is a hard bounce (email) or permanent carrier error (SMS) Then the recipient-channel is marked undeliverable, no further retries occur on that channel, and the manager is alerted via their next preferred available channel within 60 seconds And the system attempts the next available preferred channel for the original recipient (if permitted) within 60 seconds And the bounce event records provider error code, reason, and message_id in the audit trail and timeline When the failure is temporary (soft bounce or transient carrier error) Then retries occur up to 3 times over 15 minutes with exponential backoff, and each attempt is logged
Localized Notification Content and Timezones
Given a recipient has a preferred locale and timezone on file When a notification is generated Then the message content and date/time formats are localized to the recipient's locale and timezone And if the locale is unsupported, the content falls back to English (en-US) and includes an ISO 8601 timestamp And right-to-left languages render with correct directionality And the audit trail records the locale and timezone used for rendering

Nudge Sequencer

Orchestrates smart reminders across SMS, email, and WhatsApp using response-time patterns to contact people at the right moment and in their preferred language. Escalates from gentle nudges to urgent prompts and optional voice IVR, boosting confirmations with fewer manual chases.

Requirements

Multi-channel Orchestration & Fallback
"As a property manager, I want nudges to reach tenants on their preferred channel with reliable fallbacks so that confirmations happen quickly without manual chasing."
Description

Implements a single sequencing engine that coordinates nudges across SMS, email, WhatsApp, and optional voice IVR with per-recipient channel preferences, automatic fallbacks, and delivery confirmation tracking. Supports provider integrations (e.g., SMS/voice gateway, email service, WhatsApp Business API), per-channel formatting and length constraints, and retry logic when delivery fails or no response is received. Triggers from FixFlow events (e.g., maintenance intake, approval pending, appointment scheduling) and records message IDs for end-to-end traceability. Outcome: higher reach and faster confirmations with fewer manual chases.

Acceptance Criteria
Honor Per-Recipient Channel Preferences
Given a recipient with channel preference order [WhatsApp, SMS, Email] And all providers are configured and healthy And an "Approval Pending" event is emitted by FixFlow for that recipient When the sequencing engine creates the first nudge Then the first send attempt uses WhatsApp And no parallel sends occur on other channels for the same step And the attempt is persisted with correlationId, recipientId, channel=WhatsApp, templateId, provider, providerMessageId, and timestamps (queued, sent) And the provider API returns HTTP 2xx and the attempt status is updated to Sent
Automatic Fallback on Delivery Failure
Given the first attempt was sent via SMS And the SMS provider returns a delivery receipt with status=Undeliverable (or a definitive failure) within 5 minutes When the fallback policy is evaluated Then the next attempt is scheduled on the next available channel in the recipient's preference list within 2 minutes of failure And the failed channel is not retried for this step And the new attempt references the same correlationId and priorAttemptId And the audit log records the failure reason and fallback decision
Retry on No Response Within SLA
Given a message attempt is Delivered on any channel And the configured no-response timeout is 4 hours And no qualifying recipient response is captured in FixFlow within 4 hours When the timeout elapses Then the engine schedules a retry according to the recipient's preference order, using the next channel not yet tried for this step And the retry attempt references the same correlationId and sequenceStep And if a response is received before the retry is sent, the retry is canceled and logged as Skipped
Provider Integration and Delivery Receipt Handling
Given an attempt is sent via the SMS/voice gateway or WhatsApp Business API or email service When the provider API responds with messageId and HTTP 2xx Then the engine stores providerMessageId, provider name, request payload hash, and response status And when the provider posts a delivery receipt webhook Then the engine updates the attempt status to Delivered/Failed accordingly within 60 seconds And if the provider API times out or returns HTTP 5xx three times consecutively, the attempt is marked as Failed(providerOutage) and fallback evaluation is triggered
Per-Channel Formatting and Length Validation
Given a composed message for SMS exceeds the configured character limit or contains unsupported characters When preparing the send Then the engine rejects the SMS attempt with reason=ContentInvalidForChannel and does not send it And the engine evaluates fallback to the next channel that can carry the content And for WhatsApp, the engine only sends using an approved templateId with all placeholders resolved; otherwise the attempt is rejected and fallback is evaluated And for Email, the engine validates subject/body length and strips unsupported HTML per policy before sending
Event-Triggered Sequencing from FixFlow
Given FixFlow emits one of: Maintenance Intake Created, Approval Pending, Appointment Scheduling Needed When the event is received by the sequencing engine Then a sequence is enqueued within 60 seconds with the correct playbook for the event type and recipient role (tenant/landlord/technician) And the sequence is correlated to the FixFlow entity (workOrderId or appointmentId) and eventId And the first step of the sequence is created with channel determined by recipient preferences
End-to-End Traceability and Auditability
Given any sequence is running When viewing the audit log for a correlationId Then every attempt shows: sequenceId, stepNumber, channel, provider, templateId, providerMessageId, timestamps (queued, sent, delivered/failed), deliveryStatus, responseCaptured flag, and priorAttemptId And attempts are ordered chronologically and grouped by step And a single click reveals the raw provider webhook payload associated with the delivery status update And exporting the audit trail returns a complete JSON/CSV without missing fields
Send-Time Optimization & Quiet Hours
"As a property manager, I want messages sent when recipients are most likely to respond and never during quiet hours so that I maximize confirmations without frustrating tenants."
Description

Learns response-time patterns per recipient and scenario to schedule nudges at moments with the highest likelihood of a reply while honoring quiet hours and local time zones. Detects and stores time zone, analyzes historical interactions, and selects the best send window with safe defaults if data is sparse. Includes A/B testing of schedules, configurable quiet-hour windows, and guardrails for urgent exceptions tied to SLAs. Outcome: improved response rates without disturbing recipients.

Acceptance Criteria
Time Zone Detection and Persistence
Given a recipient interacts via any supported channel with time-related metadata, When the system processes the interaction, Then it infers an IANA time zone using prioritized sources and stores it with source and confidence on the recipient profile. Given multiple conflicting time zone sources exist, When a higher-priority source is observed, Then the stored time zone is updated and the previous value is retained in history with timestamp and source. Given a recipient profile has a stored time zone, When scheduling any nudge, Then local send time is computed using that time zone including DST rules. Given no time zone can be inferred, When scheduling a nudge, Then the account default time zone is used; if none exists, UTC is used and the profile is flagged tz_unknown. Given a manager applies a manual time zone override, When saved, Then all future schedules use the override until changed and an audit entry is recorded.
Quiet Hours Enforcement Across Channels
Given quiet hours are configured for a recipient or property, When a nudge is scheduled within the quiet hours window in the recipient's local time, Then the send is deferred to the earliest allowed time and no message is sent during quiet hours. Given quiet hours span midnight, When computing the next allowed send time, Then the wrap-around is correctly handled to the next day. Given suppress weekends is enabled for non-urgent nudges, When the next allowed send falls on Saturday or Sunday, Then the send is deferred to the next business day at the earliest allowed time. Given a recipient's time zone changes before send time, When the change is detected at least 15 minutes prior to send, Then the scheduled send is recalculated to remain outside quiet hours in the new local time. Given channel-specific quiet hours are configured, When scheduling per channel (SMS, Email, WhatsApp), Then each channel's quiet hours are respected for that channel's sends.
Response-Time Pattern Learning and Optimal Scheduling
Given at least 5 historical interactions exist for the recipient within the same scenario type, When scheduling a new nudge, Then the selected send time maximizes the predicted reply probability and occurs within allowed hours for that recipient. Given two candidate send times have equal predicted probability, When selecting the send time, Then the earlier time within allowed hours is chosen. Given the prediction model version is updated, When scheduling subsequent nudges, Then the latest model version is used and version ID is recorded with the decision. Given a recipient replies or does not reply to a scheduled nudge, When outcomes are captured, Then the interaction record is appended to training data within 5 minutes of outcome capture.
Safe Defaults for Sparse or No Data
Given fewer than 5 relevant historical interactions exist for recipient and scenario, When scheduling a non-urgent nudge, Then the system schedules for 10:00 local time on the next business day outside quiet hours. Given the current local time is before 10:00 on a business day, When scheduling with sparse data, Then the send is set to 10:00 the same day if outside quiet hours. Given no time zone is known, When applying the default schedule, Then the account default time zone is used; if none exists, UTC is used and quiet hours are applied against that time zone. Given sparse data persists for 14 days, When scheduling continues, Then the recipient is enrolled into an A/B exploration cohort for send-time testing.
A/B Testing of Send Windows and Auto-Selection
Given A/B testing is enabled for the scenario, When a recipient qualifies for exploration, Then they are randomly assigned to a send-time variant with a 50/50 split stratified by time zone. Given each variant has accrued at least 500 recipients and statistical significance p<=0.05 with a minimum 5% absolute lift in reply rate, When the evaluation runs, Then the winning variant is auto-selected and marked as the default schedule. Given a winner has been selected, When scheduling future eligible nudges, Then 90% use the winning variant while 10% remain in exploration for continuous learning. Given an experiment runs for 30 days without reaching significance, When the stop condition is evaluated, Then the test auto-stops and the incumbent schedule remains active. Given A/B testing is active, When computing assignments and outcomes, Then opt-out rate and quiet-hours violations are tracked per variant and violations must remain at 0.
Urgent SLA Exception Guardrails
Given a ticket is marked urgent and the SLA response window will breach within 2 hours, When scheduling a nudge during quiet hours, Then the system sends only if urgent overrides are enabled for the property. Given an urgent override send occurs during quiet hours, When enforcing guardrails, Then no more than 1 urgent message is sent per 4 hours per recipient and the urgent template is used. Given the urgent flag is removed or the SLA risk subsides, When scheduling subsequent nudges, Then quiet hours are fully enforced and overrides cease. Given legal or carrier restrictions prohibit quiet-hours messaging for a channel, When an urgent override is attempted, Then the system selects an allowed channel or defers to the earliest legal time and records the reason.
Decision Logging and Auditability
Given a send-time decision is computed, When viewing the nudge record via UI or API, Then the system displays chosen send time (local and UTC), applied time zone and source, quiet hours configuration, model version, predicted score, decision type (learned/default/AB/override), and reason codes. Given a quiet-hours deferral or urgent override occurs, When the event is logged, Then the log entry includes actor (system or user), timestamp, prior and new send times, and configuration snapshot. Given an auditor exports the last 90 days of nudge decisions, When the export completes, Then 100% of sends include an associated decision log record with no missing required fields.
Language Auto-Detection & Translation
"As a tenant, I want to receive messages in my preferred language automatically so that I can understand and act on them quickly."
Description

Automatically determines a recipient’s preferred language based on prior replies, locale metadata, or property settings, and serves localized content accordingly. Integrates with translation services to generate and cache per-language template variants, supports right-to-left scripts, and enforces WhatsApp pre-approved template requirements. Provides manager overrides and fallbacks when language is unknown. Outcome: clearer messages that reduce confusion and increase action rates.

Acceptance Criteria
Auto-Detect Language from Prior Replies
Given a recipient has at least 2 of the last 3 replies detected as the same language with ≥90% confidence, When the next nudge is generated on any channel, Then preferred_language is set to that language before send and the message is localized accordingly. Given mixed-language history where no language meets the 2-of-3 and ≥90% confidence rule, When generating a nudge, Then preferred_language remains unchanged and detection_reason is recorded as "insufficient_consensus". Given preferred_language is changed by auto-detection, When persisted, Then an audit log entry records previous_language, new_language, source="reply_history", confidence, timestamp, and actor="system". Given 3 consecutive new replies arrive in a different language with ≥90% confidence, When processed, Then preferred_language updates to the new language within 1 minute and the change is logged.
Fallback and Default Language Behavior
Given a recipient has no prior replies and no locale metadata, When a nudge is sent, Then the system uses property.default_language and includes a one-tap "Change language" control/link in the message. Given property.default_language is unset and portfolio.default_language exists, When a nudge is sent, Then portfolio.default_language is used. Given default language was used due to unknown preference, When the recipient selects a new language via the control, Then preferred_language updates immediately and a confirmation message is resent in the chosen language within 30 seconds. Given channel=SMS and default-language content exceeds 160 GSM-7 chars or 70 Unicode chars by >10%, When preparing the message with unknown language, Then the system uses a concise fallback variant to keep within 1 segment (GSM-7) or 2 segments (Unicode) and records variant_id and segment_count.
Per-Language Template Generation and Caching
Given a nudge template is created or edited in the source language, When saved, Then translations are requested for all enabled target languages via the configured translation service and cached with cache_key including template_id and content_hash. Given a cached translation exists and content_hash matches, When rendering messages over a 24-hour window, Then ≥95% of translation lookups are served from cache (no external API call), with hit/miss metrics logged. Given the template content changes, When the next render occurs, Then prior cache entries are invalidated and new translations are generated within 60 seconds; no message is sent with a stale translation (content_hash mismatch blocked). Given translation for a target language fails or times out (>3s), When sending, Then the system falls back per policy (default_language or nearest approved variant), marks fallback_action, and emits an error event with request_id and provider_error.
Right-to-Left Script Support
Given target_language is RTL (e.g., ar, he), When rendering email, Then the root container includes dir="rtl" and text-align is set for RTL; all placeholders render without reordering errors. Given channel is SMS or WhatsApp and target_language is RTL, When interpolating LTR variables (e.g., numbers, dates, addresses), Then Unicode bidi markers are applied to prevent garbling, verified by snapshot tests. Given RTL content for iOS (16+), Android (12+), and WhatsApp Web, When visual tests run, Then screenshots match approved baselines with perceptual diff ≤0.5% for each client. Given bullet lists and punctuation in RTL languages, When rendered, Then list markers and punctuation appear on the correct visual side per language norms across the three clients above.
WhatsApp Template Compliance Enforcement
Given channel=WhatsApp and target_language=L, When composing a nudge, Then only pre-approved WhatsApp template IDs for L are used; free-form messages are blocked. Given no approved template exists for L, When attempting to send, Then the system selects an allowed fallback language variant per account policy if tenant opt-in permits; otherwise the send is blocked, a compliance error is logged, and the task is queued for manager review. Given a non-compliant template is selected, When sending, Then the delivery API is not called; the attempt is marked status="blocked_compliance" with reason and a manager notification is sent within 1 minute with remediation options. Given provider approval status changes for a template, When synchronized, Then the system updates local approval states within 15 minutes and uses the updated state on subsequent sends.
Manager Language Override and Persistence
Given a manager sets a contact's preferred_language to a specific value, When saved, Then all subsequent nudges across SMS, email, and WhatsApp use that language regardless of auto-detection. Given an override exists, When auto-detection suggests a different language, Then no change is applied and a detection event is recorded with outcome="ignored_due_to_override". Given a manager clears the override, When saved, Then auto-detection is re-enabled and the most recent detection result is applied on the next send. Given an override is created, changed, or removed, When auditing the contact, Then the activity log shows actor, previous_language, new_language, timestamp, and affected channels.
Escalation Ladder & Policy Controls
"As an operations lead, I want to configure escalation steps and guardrails so that urgent issues are resolved on time while respecting communication limits and tenant preferences."
Description

Configurable sequences that progress from gentle reminders to urgent prompts and optional voice IVR, driven by business rules, SLA timers, and recipient behavior. Supports per-step conditions, per-day caps, per-channel rate limits, business-hour windows, and exception lists (e.g., vulnerable tenants). Allows per-portfolio, per-property, or per-workflow policies with preview and simulation tools. Outcome: time-bound resolution with respectful communication pacing.

Acceptance Criteria
SLA-timed cross-channel escalation flow
Given a work order with a 24-hour SLA for tenant confirmation and the recipient’s preferred channel is SMS and language is Spanish When no response is recorded at 6 hours (25% of SLA) Then send a “gentle” SMS in Spanish within 5 minutes and log the send with step_id and SLA_checkpoint=25 When no response is recorded at 12 hours (50% of SLA) Then send a “gentle” Email in Spanish within 5 minutes and log the send with step_id and SLA_checkpoint=50 When no response is recorded at 18 hours (75% of SLA) Then send a “firm” WhatsApp message in Spanish within 5 minutes and log the send with step_id and SLA_checkpoint=75 When no response is recorded at 24 hours (100% of SLA) and current time is within business hours Then send an “urgent” SMS and Email in Spanish within 5 minutes and log the send with step_id and SLA_checkpoint=100 When no response is recorded at 24 hours (100% of SLA) and current time is outside business hours Then schedule the “urgent” step for the next business window start and log a deferral event with reason=outside_business_hours When any valid response (confirmation or explicit decline) is captured at any point Then cancel all pending steps within 60 seconds, stop the sequence, and mark outcome with resolution_time and final_step_id
Per-day caps and per-channel rate limiting
Given a policy with a per-recipient per-workflow daily cap of 3 total outbound messages and channel rate limits: SMS >= 120 minutes, Email >= 240 minutes, WhatsApp >= 120 minutes between sends to the same recipient When a step would exceed the daily cap Then suppress the send, record an audit event with suppressed_reason=daily_cap and remaining_cap=0, and do not reschedule the same step within the same day When a step would violate a channel rate limit for that recipient Then suppress the send, record suppressed_reason=channel_rate_limit and next_eligible_time, and attempt the next scheduled step (if any) without violating caps Then ensure a compliance report shows total_sent_per_day <= 3 and no two sends on the same channel occur within their configured cooldowns for the test recipient
Business hours window and local time-zone enforcement
Given business hours are configured as Mon–Sat 08:00–20:00 local time and IVR is allowed only 09:00–18:00 local time, and Sundays are closed When a step is scheduled for 07:30 local time Then defer it to 08:00 and log deferral with reason=before_business_hours When a step is scheduled for Sunday 10:00 local time Then defer it to Monday 08:00 and log deferral with reason=closed_day When a DST transition occurs (spring forward or fall back) Then scheduled times are computed in the recipient’s local time correctly with no duplicate or skipped steps beyond policy intent, and all audit timestamps include timezone offsets Then no IVR call attempts occur outside 09:00–18:00 local time; if a step would occur outside, it is deferred to the next IVR window
Exception list handling for vulnerable tenants
Given the recipient is on the “vulnerable_tenants” exception list for the active policy When the sequence reaches any “firm”, “urgent”, or IVR step Then downgrade to a “gentle” message on the preferred channel or skip the step if a gentle has already been sent that day, and never place IVR calls Then enforce a stricter daily cap of 1 outbound message and suppress any additional sends the same day with suppressed_reason=exception_cap Then notify the assigned manager via Email within 5 minutes after the first suppression with a link to the timeline and exception_reason=vulnerable_tenant Then all timeline events for this recipient display exception_applied=true and the exception list name
Policy scope, inheritance, and precedence
Given policies exist at portfolio, property, and workflow scopes with overlapping rules When effective policy is computed for a sequence tied to Portfolio=A, Property=P, Workflow=W Then precedence is Workflow > Property > Portfolio; the effective values reflect the highest-priority source for each setting and include an effective_policy_version id When a policy is updated at any scope Then new sequences adopt the new version immediately; in-flight sequences adopt changes only at their next step evaluation; the audit trail records from_version and to_version Then a policy preview tool shows the effective policy for a given A/P/W context with each rule’s source scope, and the preview output matches the runtime effective policy used for sends
Optional voice IVR escalation with safe fallbacks
Given IVR is enabled in the policy and the sequence reaches the IVR step after an “urgent” message without a response When current time is within IVR hours and the recipient has a valid phone number Then place up to 2 call attempts spaced 10 minutes apart; if unanswered, leave a voicemail on the second attempt if voicemail is detected; log call outcomes and recordings metadata When current time is outside IVR hours Then defer the IVR step to the next IVR window and log the deferral with reason=outside_ivr_hours When the phone number is invalid or call fails with permanent error Then skip IVR and send a fallback SMS in the recipient’s language, tagged fallback_from=ivr, within 5 minutes Then ensure total call attempts per day do not exceed 2 and total IVR attempts per sequence do not exceed 4
Two-way Reply Parsing & State Sync
"As a dispatcher, I want the system to recognize replies like yes/no or new times and automatically update the work order so that I don’t need to do manual data entry."
Description

Captures and interprets inbound replies across channels to detect confirmations, declines, and reschedule intents using keyword rules and lightweight NLP. De-duplicates signals across channels, applies idempotency, and updates FixFlow entities (work orders, approvals, appointments) in real time. Emits webhooks and audit events for downstream systems, with clear error handling and manual review queues when intent confidence is low. Outcome: fewer manual follow-ups and cleaner system of record.

Acceptance Criteria
Appointment Confirmation via Multi-Channel Reply
Given an appointment in Pending Confirmation and a tenant with a stored preferred language And FixFlow receives an inbound reply via SMS, Email, or WhatsApp matching confirmation keywords or patterns in a supported language And the intent classifier returns Confirm with confidence >= 0.90 When the reply is processed Then the appointment status is updated to Confirmed within 2 seconds (p95) and 5 seconds (p99) And the confirmation timestamp and source channel are stored And an audit event appointment.confirmed is recorded with messageId, channel, language, parsedIntent, confidence, actor=tenant, and appointmentId And a webhook appointment.confirmed is emitted with an Idempotency-Key and HMAC signature And any duplicate confirmation for the same appointment within a 15-minute window results in no additional state change
Decline/Cancellation via Reply
Given a work order approval or appointment awaiting tenant decision And FixFlow receives an inbound reply matching decline or cancel patterns in a supported language And the classifier returns Decline with confidence >= 0.90 When the reply is processed Then the related entity status is set to Declined or Canceled as appropriate within 2 seconds (p95) And any provided reason text is captured and stored And an audit event entity.declined is recorded with messageId, channel, language, parsedIntent, confidence, actor=tenant, and entityId And a webhook entity.declined is emitted with an Idempotency-Key and signature And any further nudges for that entity are halted
Reschedule Intent and Time Extraction
Given an appointment with a scheduled time And FixFlow receives an inbound reply indicating a need to reschedule in a supported language And the classifier returns Reschedule with confidence >= 0.80 When the reply includes a proposed time or time window Then the time is parsed to ISO 8601 with the tenant timezone (from profile or message metadata) And the appointment status is updated to Needs Reschedule within 2 seconds (p95) And parsed proposals (start, end) are stored; if no valid time is found, an automatic clarification message is sent And an audit event appointment.reschedule_requested is recorded with parsed times and confidence And a webhook appointment.reschedule_requested is emitted with an Idempotency-Key and signature
Cross-Channel De-duplication and Idempotency
Given multiple inbound replies from the same sender about the same FixFlow entity across SMS, Email, or WhatsApp When replies arrive within a 15-minute de-duplication window or share the same external messageId Then only the first successfully processed intent mutates state; subsequent duplicates are logged with duplicate=true and make no changes And processing uses a stable idempotency key composed of normalizedSender, entityId, and externalMessageId when available And reprocessing with the same idempotency key is a no-op that returns the original outcome and audit reference
Low-Confidence or Ambiguous Intent Routed to Manual Review
Given an inbound reply that does not meet confidence thresholds (Confirm/Decline >= 0.90, Reschedule >= 0.80) or has competing intents within 0.10 score of each other When the reply is processed Then no entity state is changed automatically And a manual review queue item is created within 2 seconds containing raw message, attachments, detected intents with scores, language, channel, sender, and related entityId And the tenant receives an automated clarification message in their preferred language And an audit event review.created is recorded; upon human decision, review.completed is recorded with the chosen intent And webhooks review.created and review.completed are emitted with idempotency and signatures
Webhook Delivery, Security, and Ordering
Given any accepted intent or review lifecycle event When emitting webhooks to downstream systems Then each payload includes eventType, entityId, timestamp, idempotencyKey, and HMAC-SHA256 signature headers with a signed timestamp And deliveries are retried up to 5 times with exponential backoff (1s, 5s, 25s, 2m, 10m) until a 2xx response is received And events for the same entity are delivered in order And undelivered events after retries are moved to a dead-letter queue visible in the ops dashboard And delivery success rate is >= 99.5% over a rolling 7-day window
State Sync Failure Recovery and Consistency Guarantees
Given a detected intent that should update a FixFlow entity When a transient error occurs during state mutation Then the operation is retried up to 3 times with exponential backoff before marking as failed And no success webhook is emitted until the entity update commits; on failure, an error audit event entity.update_failed is recorded and an alert is raised And a reconciliation job re-attempts the update within 15 minutes and resolves duplicates via idempotency keys And once recovered, the correct webhook is emitted with the original idempotencyKey and chronological audit preserved
Template Management & Personalization
"As a content admin, I want reusable, localized templates with personalization so that messages stay accurate, on-brand, and quick to deploy."
Description

Enables creation, versioning, and approval of multi-channel templates with dynamic variables (e.g., tenant_name, unit_address, appointment_time) and conditional blocks. Provides channel-specific previews, character counters for SMS segmentation, test sends, and brand controls (tone, sender IDs). Supports localization keys to pair with the Language module and guards against missing variables at send time. Outcome: fast, accurate, and on-brand communications at scale.

Acceptance Criteria
Template Creation, Versioning, and Approval Workflow
- Given a user with Editor role, when they create a template draft with at least one channel body (SMS/Email/WhatsApp), then the system assigns version 0 and status Draft. - Given a draft exists, when an Editor submits it for approval, then status changes to Pending Approval and Approver users are notified. - Given a template is Pending Approval, when an Approver approves it, then status becomes Approved, version increments to 1, and it is selectable in the Nudge Sequencer. - Given an Approved template exists, when a user starts editing, then a new draft version is created without altering the approved version. - Given multiple versions exist, when a user views history, then they can see version, author, timestamp, change summary, and can restore a prior version to draft. - Given permissions are enforced, when a non-Approver attempts to approve or reject, then the action is blocked with 403 and no state change occurs.
Dynamic Variables Validation and Fallbacks at Send Time
- Given a template uses variables (e.g., tenant_name, unit_address, appointment_time), when previewing or test-sending, then any missing variables are listed with field names and locations before send. - Given required variables are missing and no fallback is configured, when sending, then the message for that recipient is blocked and logged as Blocked—Missing Variables with count and details. - Given a variable default is configured (e.g., appointment_time = "TBD"), when the recipient lacks a value, then the default renders in all channels. - Given variable types (string, datetime) are defined, when rendering, then datetime variables format per recipient locale settings. - Given variables are provided, when rendering across channels, then the same resolved values are applied consistently in SMS, Email, and WhatsApp.
Conditional Blocks Rendering
- Given a template contains IF/ELSE conditional blocks (e.g., IF channel == SMS, IF urgency == high), when rendering, then only blocks whose conditions evaluate true are included. - Given nested conditions exist, when saving the template, then syntax is validated and errors prevent save with line/column indicators. - Given a condition references an unknown field, when saving, then save is prevented and the unknown reference is highlighted. - Given conditional blocks are present, when viewing channel-specific previews, then excluded blocks do not affect SMS character counts or email HTML output size. - Given test-send context values are set, when test-sending, then the rendered output matches the preview for the same context.
Channel-Specific Previews and SMS Segmentation
- Given an SMS body, when previewing, then the UI shows character count, detected encoding (GSM-7/UCS-2), and estimated segments with segment count updated live. - Given the SMS includes characters requiring UCS-2, when previewing, then encoding switches to UCS-2 and segments recalc accordingly. - Given URLs are present and link shortening is enabled, when previewing SMS, then counts reflect the configured shortened length. - Given Email and WhatsApp bodies exist, when previewing per channel, then responsive email and WhatsApp template previews render with the selected sender ID/from address. - Given channel limits (e.g., WhatsApp template policies) are exceeded, when validating, then errors list each violation with channel-specific guidance and block save.
Test Sends Across Channels
- Given test recipients for SMS, Email, and WhatsApp with sample variable values and language are provided, when sending a test, then each channel attempts delivery within 60 seconds and records delivery or error status. - Given per-channel rate limits, when multiple test sends occur within one minute, then throttles are enforced and remaining quota is displayed. - Given a delivery failure occurs (e.g., invalid phone), when viewing results, then provider error code and message are shown in the UI and logs. - Given "include context summary" is enabled, when sending a test, then the email includes a header listing resolved variables and condition outcomes. - Given a successful test send, when viewing audit logs, then template version, variables payload (PII-masked), recipient, channel, timestamp, and sender ID are captured.
Brand Controls and Sender ID Enforcement
- Given a brand profile defines locked elements (email header/footer, logo) and allowed sender IDs per channel, when editing a template, then locked sections are non-editable and sender selection is limited to the allowlist. - Given tone presets are configured, when an editor selects a tone, then associated phrases/disclaimers auto-apply to eligible channels and are visible in preview. - Given an editor attempts to modify a locked element, when saving, then save is blocked with a rationale and no changes are persisted to that element. - Given an Approved template is used by the Nudge Sequencer, when sending, then the outbound uses the approved sender ID and branding assets recorded at approval time.
Localization Keys and Language Module Integration
- Given a template uses localization keys for static text, when a recipient has a preferred language available, then the rendered message uses the localized strings from the Language module. - Given a key is missing for the recipient language, when rendering, then the system falls back to the default language and logs a Missing Translation warning with key names. - Given RTL languages are selected, when previewing email, then text direction and alignment switch to RTL and render correctly. - Given datetime variables are present, when locale is set, then appointment_time formats per locale and applies timezone rules if configured. - Given WhatsApp localized variants require pre-approval, when saving a WhatsApp template, then only keys mapped to approved variants are allowed; otherwise save is blocked with details.
Consent, Compliance & Audit Logging
"As a compliance-minded manager, I want every nudge to respect consent and be fully auditable so that we mitigate legal risk and maintain tenant trust."
Description

Centralizes consent capture and enforcement per channel, including opt-in/out flows, STOP/UNSUBSCRIBE handling, DNC lists, and WhatsApp opt-in records. Applies regional rules (TCPA, CAN-SPAM, GDPR/CCPA), retention policies, and quiet-hour legal constraints where applicable. Maintains immutable audit logs of all nudge events, content, targeting rationale, and user actions, with export and role-based access. Outcome: reduced legal risk and increased trust with tenants and vendors.

Acceptance Criteria
Unified Consent Record per Contact and Channel
Given a new or existing contact submits consent via any supported method (web form, SMS keyword, WhatsApp opt-in, email link) When the consent is recorded Then the system stores a time-stamped, source-tagged consent state per channel and preferred language tied to the contact ID And an audit entry is created within 2 seconds containing actor, channel, method, and geo-context Given a nudge sequence evaluates delivery options When the contact has mixed consent states across channels Then only channels with Active consent are eligible and disallowed channels are skipped with reason CONSENT_MISSING Given multiple consent updates exist for a contact and channel When the system evaluates current state Then the latest effective record by timestamp determines the state and all prior states remain immutable Given a contact has no Active consent on any channel When a sequence is triggered Then no messages are sent and a compliant opt-in invitation is queued where permitted with reason CONSENT_REQUIRED
SMS STOP/START/HELP Handling and Enforcement
Given a contact sends STOP, UNSUBSCRIBE, QUIT, CANCEL, or END (case-insensitive, multilingual) to an SMS thread When the message is received Then SMS consent is immediately revoked, a confirmation reply is sent within 5 seconds, and all future SMS are blocked except HELP and START Given a contact sends START or UNSTOP When the message is received Then SMS consent switches to Active, a confirmation reply is sent within 5 seconds, and the change is audit-logged Given any job attempts to send SMS to a revoked number When the send is queued Then the message is rejected pre-send with error SMS_OPT_OUT and no carrier charge occurs Given carrier compliance requires HELP When the contact sends HELP Then the system returns a compliance message containing identity, contact info, and opt-out instructions
Email Unsubscribe Compliance and Footer Requirements
Given any outbound email from the Nudge Sequencer When the email is constructed Then it includes a visible unsubscribe link, sender identity, and physical mailing address per CAN-SPAM Given a recipient clicks the unsubscribe link When the request is received Then the unsubscribe takes effect within 1 minute and the contact’s email channel is suppressed for future sends Given a hard bounce or spam complaint is received via feedback loop When the event is processed Then the email address is auto-suppressed and a compliance audit record is created Given a message is classified as transactional vs marketing When suppression logic runs Then transactional emails bypass marketing unsubscribe where legally permitted and the classification is included in the audit log
WhatsApp Opt-In and Template Policy Enforcement
Given a contact lacks an Active WhatsApp opt-in When the system evaluates a template send outside the 24-hour service window Then the send is blocked with WHATSAPP_POLICY_BLOCK and logged with policy details Given a compliant WhatsApp opt-in is captured (checkbox, in-app prompt, or user-initiated WhatsApp message stating consent) When the consent is saved Then the record includes timestamp, source, legal text version, and locale, and is audit-logged Given a template message is sent When localization is selected Then an approved template matching the contact’s locale is used and the approved template ID is recorded in the audit log Given a contact sends a STOP/opt-out phrase on WhatsApp When processed Then WhatsApp consent is revoked and a confirmation is sent where platform policy permits
Quiet Hours and Regional Legal Constraints Enforcement
Given a contact has a resolvable local time zone When a nudge is scheduled Then the system enforces legal quiet hours for the jurisdiction (e.g., TCPA 8am–9pm local) and any admin-defined quiet hours, deferring sends outside the window with reason QUIET_HOURS Given a contact’s time zone is unknown or conflicting When quiet hours are evaluated Then the system derives time zone from property address or area code; if unresolved, it defaults to the most restrictive policy and suppresses until a safe window Given jurisdictions require explicit consent for certain channels When eligibility is checked Then the send only proceeds if consent strength meets the jurisdictional requirement; otherwise blocked with CONSENT_INSUFFICIENT Given a daylight saving transition date When quiet hours are calculated Then local civil time with DST rules is applied correctly and logged
Do-Not-Contact (DNC) List Ingestion and Global Suppression
Given an admin uploads a CSV or calls the API to add DNC entries (phone, email) When the import runs Then entries are normalized, de-duplicated, validated, and a summary is returned with counts of imported, rejected, and reasons Given a contact matches an active DNC entry When any outbound nudge is evaluated Then all channels are suppressed regardless of per-channel consent with reason DNC_SUPPRESSION and the attempt is audit-logged Given a DNC entry includes an expiry date When the date passes Then suppression lifts automatically and the lift is audit-logged Given a user is marked high-risk by compliance When the flag is set Then the system adds or updates a DNC record with the associated note and scope
Immutable Audit Logging, Retention, Export, and RBAC
Given any nudge lifecycle event, consent change, policy check, or user action occurs When the event completes Then an append-only audit record is written within 2 seconds containing UTC timestamp, actor, subject IDs, channel, content hash, policy outcomes, and correlation IDs Given a regional retention policy is configured (e.g., 24 months EU, 36 months US) When retention jobs run Then records older than policy are archived or purged per rule without breaking hash-chain integrity and all actions are audit-logged Given a user with Compliance Officer or Owner role requests an export for a date range and data subjects When the request is submitted Then a signed CSV/JSON export is generated within 2 minutes, access-scoped to the requester, and available for 7 days Given a user without the required role attempts to view or export audit logs When access is evaluated Then access is denied with SECURITY_DENY and the attempt is audit-logged Given a periodic integrity check runs daily When the hash chain is verified Then any tampering is detected and an alert is raised to compliance with severity High

Waitlist Backfill

Auto-detects cancellations or non-responses and instantly backfills openings with best-fit jobs from a smart waitlist or nearby units. Protects vendor utilization, keeps SLAs on track, and reduces tenant downtime by recycling lost time into confirmed visits.

Requirements

Real-time Vacancy Detection & Triggering
"As a dispatcher, I want the system to automatically detect newly opened appointment slots so that I can keep technicians utilized without constant manual monitoring."
Description

Continuously monitors scheduled jobs for cancellations, tenant non-responses beyond configurable timeouts, technician no-shows, and reschedules. On detection, emits a standardized vacancy event with slot metadata (technician, time window, location, duration, job category). Debounces rapid changes, respects quiet hours, and supports per-portfolio rules. Integrates with FixFlow’s scheduler, messaging, and event bus to ensure immediate downstream handling and auditability. Outcome: openings are detected instantly, eliminating idle gaps and enabling automated backfill workflows.

Acceptance Criteria
Cancellation Vacancy Event Emission
Given a scheduled job with a defined slot and assigned technician When the tenant or property manager cancels the job via app, web, or API Then within 5 seconds the system publishes a VacancyDetected event to the event bus and marks the slot as available in the scheduler And the event payload includes event_id, portfolio_id, technician_id, slot_id, schedule_id, time_window_start, time_window_end, duration_minutes, location_address or coordinates, job_category, source=cancellation, schema_version, occurred_at And an immutable audit log record is written with actor, timestamp, and reason And downstream subscribers receive exactly one effective vacancy notification within 10 seconds, with duplicates de-duplicated by idempotency_key
Non-Response Timeout Vacancy Detection
Given a scheduled job awaiting tenant confirmation and a portfolio-specific non_response_timeout is configured When the timeout elapses without a tenant confirmation or reschedule and no active conversation activity within the configured inactivity window Then within 60 seconds the system publishes a VacancyDetected event with source=non_response_timeout and timeout_rule_id And the slot is released in the scheduler and visible via API/UI And the event includes the full slot metadata fields and portfolio_id And the audit trail records the applied timeout and rule values
Technician No-Show Vacancy Handling
Given a scheduled job with a configured no_show_grace_period and technician tracking enabled When the technician marks no-show or the system infers no-show after the grace period based on location and time data Then the system emits a VacancyDetected event within 60 seconds for the remaining available portion of the time window with source=no_show And the event includes remaining_duration_minutes and reason_details And the scheduler frees the remaining time in the technician’s calendar And an audit entry links the no-show determination evidence
Debounce Rapid Schedule Changes
Given a portfolio with debounce_window_seconds configured When a cancel-reschedule-change sequence occurs within the debounce window for the same slot Then at most one VacancyDetected event is published representing the final vacancy state after the window closes And if the slot becomes occupied again before the window closes, no vacancy event is published And duplicate state transitions within the window are suppressed in the audit log with references to the original event
Quiet Hours Respect With Immediate Eventing
Given portfolio quiet_hours configured (e.g., 20:00–08:00 in the portfolio’s timezone) When a vacancy is detected during quiet hours Then the VacancyDetected event is published immediately with quiet_hours=true and notifications_suppressed=true And no tenant or vendor outbound messages are sent until quiet hours end, after which queued notifications are released according to messaging rules And downstream automation may consume the event for backfill scheduling without sending end-user messages during quiet hours
Per-Portfolio Rule Isolation
Given two portfolios with different timeout, quiet hours, debounce, and category rules When identical scheduling situations occur in each portfolio Then each portfolio’s detection behavior follows its own rules and produces VacancyDetected events with the respective rule_ids and portfolio_id And no configuration from one portfolio affects detection in the other And access to events is restricted to authorized users within the originating portfolio
Event Reliability, Schema, and Observability
Given a transient event bus outage or publish failure When a VacancyDetected event should be emitted Then the system writes the event to a durable outbox and retries with exponential backoff for at least 15 minutes until delivery succeeds And events are published with an idempotency_key and schema_version, and consumers can reprocess safely And system metrics expose detection latency p95<=5s and publish success rate>=99.9% over a rolling 30 days And every event and state change is queryable in the audit log by event_id and slot_id
Best-Fit Scoring & Eligibility Engine
"As a scheduler, I want FixFlow to surface the best candidate to fill an opening so that I can meet SLAs while minimizing travel and disruption."
Description

Evaluates waitlisted work orders and nearby units against each vacancy using configurable constraints and weighted factors: technician skills/certifications, tool/parts readiness, job category compatibility, travel time and proximity, tenant availability windows, SLA urgency, estimated duration fit, access requirements, approval status, warranty/vendor routing, and vendor capacity. Produces a ranked shortlist with explainable scores and hard eligibility gates. Integrates with route/time services and existing FixFlow work order data. Outcome: the most suitable job is selected quickly, minimizing travel, meeting SLAs, and boosting vendor utilization.

Acceptance Criteria
Hard Eligibility Gates Exclude Ineligible Jobs
Given a vacancy slot becomes available with candidate work orders from the waitlist and nearby units When the engine applies hard eligibility gates for approval status, access requirements, technician certifications and skills, required tools and parts availability, warranty or vendor routing, and vendor daily capacity Then any candidate failing any gate is excluded with a specific reason code And the engine emits an EligibleCandidates list where every item passes all gates And an Exclusions list includes jobId, failedGate, and evaluatedAt timestamp And the evaluation completes within 500 ms for up to 100 candidates
Weighted Scoring Produces Ranked Shortlist
Given configured factor weights for skills match, job category compatibility, travel time, proximity, tenant availability fit, SLA urgency, estimated duration fit, access simplicity, approval freshness, and vendor capacity And a set of eligible candidates When the engine computes scores Then it produces a ranked shortlist of the top 5 candidates by default, configurable between 1 and 10 And each candidate includes totalScore between 0 and 100 and a factorBreakdown with per-factor contributions And ties are broken by higher SLA urgency, then lower travel time, then earlier createdAt And scores are deterministic for identical inputs and configuration And changing a weight updates totalScore accordingly within one second
Travel Time and Route Integration
Given the technician current location or next route stop and candidate job locations When the engine requests travel estimates from the route and time service with live traffic enabled Then travelTimeMinutes is used as the travel factor input And if the service errors or exceeds 300 ms, the engine falls back to straight-line distance with a penalty And successful route results are cached for identical origin and destination pairs for ten minutes And each candidate explanation includes travelTimeMinutes and distanceKm And external call latency for route estimates is at or below 300 ms at the 95th percentile
Tenant Availability and Estimated Duration Fit
Given tenant availability windows, technician shift, and a vacancy slot duration And each candidate has an estimatedDuration When the engine evaluates slot fit Then candidates that cannot fit fully within the opening and tenant window are excluded with reason DurationOrAvailabilityMismatch And candidates that fit exactly or within a configurable graceMinutes of up to 15 are eligible And the selected job proposed start time falls within the tenant window and technician shift And the explanation includes proposedStart, windowOverlapMinutes, and graceMinutesUsed
SLA Urgency and Vendor Capacity Prioritization
Given SLA due dates and severity for each candidate and vendor capacity limits per day and time window When computing eligibility and scores Then any candidate that would exceed vendor capacity is excluded with reason VendorCapacityExceeded And SLA urgency increases the score based on configuration so that jobs due within 24 hours gain at least 15 more points than jobs due in more than 72 hours when other factors are equal And when totalScore difference is less than 5 points, earlier SLA due dates outrank later ones
Explainability and Audit Trail
Given scoring and eligibility evaluation completes When results are returned Then each candidate includes an explanation array with factorName, inputValue, normalizedValue, weight, contribution, and reasonCodes And the selected job includes a summary of the top three drivers of its score And an immutable audit record is stored with candidate list, configuration version, route estimates, timestamp, and requesterId And a verification endpoint reproduces the score within plus or minus 0.1 points given the audit record
Configuration and Defaults
Given organization-level configuration for the engine When saving factor weights Then each weight is validated to be between 0 and 10 and rejected otherwise And default weights are provided for travelTime, proximity, skills, SLA, durationFit, availability, access, approvals, vendorCapacity, and categoryCompatibility And the engine can be enabled or disabled per organization with a feature flag named bestFitScoring And configuration changes are versioned, responses include configVersion, and changes take effect on the next scoring request
Instant Auto-Backfill & Calendar Update
"As a property manager, I want openings to be filled automatically so that tenant issues are resolved sooner without my manual intervention."
Description

Automatically books the top eligible candidate into the open slot with configurable modes: tentative hold pending tenant confirmation or auto-confirm under defined policies. Updates technician calendars, work order statuses, and route plans atomically to prevent double-booking. Supports partial-slot fills, grace periods, conflict resolution, and rollback if confirmation fails. Publishes updates to mobile apps, vendor portals, and integrations. Outcome: openings are converted into confirmed visits in seconds, reducing tenant downtime and preserving SLA commitments.

Acceptance Criteria
Instant Backfill on Cancellation or Non-Response
Given a scheduled visit is canceled by tenant, vendor, or dispatcher OR a scheduled visit remains unconfirmed past the configured non-response grace window When the system registers the resulting opening Then within 5 seconds a replacement candidate is selected from the waitlist or nearby units that satisfies skill, location radius, duration fit, SLA window, and availability constraints And the opening is marked as "Backfill In-Progress" with the candidate ID and selection timestamp And a booking draft is created referencing the original slot ID
Auto-Confirm Under Policy
Given Auto-Confirm policy is enabled for the work order type or vendor and the selected candidate meets all policy rules (access method, same-tech continuity, landlord approvals, cost caps) When backfill selection occurs Then the booking is created with status "Confirmed" without tenant pre-approval And confirmation notifications are dispatched to tenant and vendor within 10 seconds And the policy ID and rule matches are recorded in the audit trail
Tentative Hold with Grace Period
Given Tentative Hold mode is enabled with a grace period of N minutes When backfill selection occurs Then a hold reservation is placed on the slot with status "Tentative" and an expiry of now + N minutes And a confirmation action is sent to the tenant within 10 seconds containing the hold expiry timestamp And if the tenant confirms before expiry, the status transitions to "Confirmed" without creating any overlapping holds And if the tenant does not confirm by expiry or declines, the hold is released within 2 seconds and the next eligible candidate is attempted within 5 seconds
Atomic Update Prevents Double-Booking
Given concurrent scheduling operations may occur on the same technician and time window When a backfill booking is committed Then technician calendar, work order status, and route plan updates occur in a single atomic transaction And no overlapping event exists for the same technician and time range after commit (verified by unique/overlap constraint) And if the transaction fails, no partial changes persist in any of the three systems (calendar, work order, routing) And the operation returns a single correlation ID for end-to-end traceability
Partial-Slot Fill Handling
Given the opening duration exceeds the candidate job's estimated duration When backfill selection is made Then the candidate is placed into a contiguous partial segment that fits within the original window and does not overlap adjacent events including required travel buffers And the partial segment start/end times honor route ETA and buffer rules with a maximum deviation of ±2 minutes from computed plan And any remaining unused time in the slot is left open and eligible for subsequent backfill attempts And downstream artifacts (route, calendar, work order) display the partial fill with accurate start/end and travel times
Conflict Resolution and Waitlist Prioritization
Given multiple eligible candidates are available When ranking is performed Then candidates are scored using configured weights for SLA risk, proximity/travel time, skill match, vendor utilization, and tenant priority And the selected candidate has the highest score and an estimated arrival within the SLA window And ties are broken by earliest waitlist enqueue timestamp, then lowest travel time And the audit log persists the top 5 candidates with scores and tie-break decisions
Multi-Channel Publish of Backfill Updates
Given a backfill booking is created, confirmed, or released When the transaction commits Then updates are published within 10 seconds to technician mobile app, vendor portal, tenant app, and external integrations via webhook or message bus And each publish is idempotent and includes a monotonically increasing version to prevent duplicate processing And failures trigger retries with exponential backoff for up to 15 minutes without duplicating bookings And the final delivered state in each channel matches the system of record for start time, status, technician, and location
Multi-channel Confirmation & Escalation Flow
"As a tenant, I want to quickly confirm a newly offered appointment time so that my repair can be completed sooner."
Description

Sends time-boxed confirmations to tenants and technicians via SMS, email, and push with actionable responses (confirm, reschedule, decline). Applies templates with property branding, localization, and quiet-hour rules. If no response or a decline occurs, automatically advances to the next candidate, optionally notifying the manager for override. Logs all communications and outcomes for compliance. Outcome: rapid confirmations with minimal back-and-forth and automated fallbacks when responses stall.

Acceptance Criteria
Time-Boxed Multi-Channel Confirmation Dispatch
Given an appointment is created or rescheduled and the recipient has SMS, email, and push enabled and quiet hours are configured for the property When the event occurs outside quiet hours Then the system sends confirmation messages via SMS, email, and push within 60 seconds using the property-branded, localized template And each message contains actionable Confirm, Reschedule, and Decline options And upon the first valid response, outstanding reminders for that appointment to the same recipient are canceled Given the event occurs during quiet hours Then no confirmation messages are sent during quiet hours When quiet hours end Then the system sends the queued confirmations within 60 seconds and records that quiet hours deferral was applied
Actionable Responses Update Appointment State
Given a recipient receives a confirmation with action links and SMS keywords When they respond Confirm via any channel or tap the Confirm link Then the appointment status updates to Confirmed within 5 seconds and a confirmation receipt is sent via the originating channel When they choose Reschedule Then the system presents available time windows from Waitlist Backfill and, upon selection, updates the appointment details and notifies all parties within 60 seconds When they choose Decline or reply with a decline keyword Then the appointment status is set to Declined and escalation is triggered immediately And invalid or unrecognized replies result in a guidance message within 10 seconds including valid options
No-Response Timeout and Auto-Escalation to Next Candidate
Given a configured confirmation timeout per role and a configured maximum number of escalations and an imminent-start window When no valid response is recorded before the timeout elapses Then the system triggers Waitlist Backfill to select the next best-fit candidate within 90 seconds And sends a new confirmation to the next candidate following channel preferences and quiet-hour rules And optionally notifies the manager according to the property setting with an override link And the original candidate is marked as No Response with timestamp Given subsequent candidates also fail to respond When the maximum number of escalations is reached or the slot start time falls within the imminent-start window Then automatic escalation stops and a manager alert is sent
Localization and Property-Branded Templates
Given the tenant and technician have preferred locales and the property has branding assets and templates When a confirmation is generated Then the system selects the template matching the recipient locale with fallback to the property default locale and then en-US And renders variables including appointment date/time in the recipient’s local time zone, property address, unit, technician name, and manager contact And applies the property logo and color scheme per brand configuration And records the template_id and locale used in the communication log
Communication Logging and Audit Trail
Given any confirmation, reminder, or response is sent or received When the event is logged Then the log entry includes appointment_id, recipient_id, role (tenant/technician), channel, locale, template_id, message_id, sent_at, delivered_at (if available), response_type, response_body_hash, response_at, escalation_reason (if any), manager_notified flag, and quiet_hours_applied flag And the log entry is immutable and visible in the appointment timeline within 5 seconds And logs are filterable by appointment, recipient, channel, and date range And exporting the log for an appointment produces a machine-readable file containing the same fields
Manager Override During Escalation
Given escalation has been triggered due to a decline or timeout and manager notifications are enabled When the manager clicks the override link within the notification Then the manager can select to keep the current candidate, choose a specific alternate candidate, or pause escalation for a configurable duration And the system applies the override immediately, updates the appointment state, and logs the override with actor, action, and timestamp And affected candidates are notified or canceled accordingly within 60 seconds
Policy Controls & Safeguards
"As an operations lead, I want to define guardrails for backfill so that automation never violates policy or creates operational risk."
Description

Provides admin-configurable guardrails that govern when and how backfill occurs: max travel distance/time, work hour boundaries, job type exclusions, approval/estimate prerequisites, parts-in-hand checks, cost caps, lead-time minimums, vendor utilization balancing, and regulatory/union constraints. Supports portfolio-level policies, per-vendor overrides, and a dry-run mode with suggested actions for safe rollout. Captures override reasons for accountability. Outcome: automation respects operational policies and reduces risk while scaling across diverse portfolios.

Acceptance Criteria
Policy Scope & Override Hierarchy
- Given a portfolio-level policy is active and a per-vendor override exists for the same rule, When evaluating a backfill, Then the per-vendor override takes precedence for overridable rules and the system applies the override value. - Given a non-overridable constraint (e.g., regulatory/union constraint) exists, When a per-vendor override attempts to relax it, Then the system rejects the override and enforces the stricter, non-overridable constraint. - Given both portfolio and per-vendor policies are defined, When the system resolves effective policy for a vendor, Then it produces a deterministic policy snapshot including each rule’s source (portfolio|vendor) and effective value. - Given no vendor override exists, When evaluating a backfill, Then the portfolio-level policy is applied as the effective policy. - Given malformed or conflicting overrides (duplicate rules with divergent values) are saved, When the policy engine loads, Then the system flags the conflict, prevents activation, and returns actionable errors identifying the conflicting rules. - Given a specific backfill decision is made, When the audit entry is written, Then it includes the policy snapshot ID and rule IDs used for the decision.
Dry-Run Simulation with Suggested Backfills
- Given dry-run mode is enabled at the portfolio level, When a cancellation or non-response event occurs, Then the system generates suggested backfills only and makes no calendar changes, vendor dispatches, or tenant notifications. - Given dry-run suggestions are generated, When viewing the suggestions list, Then each suggestion includes: candidate vendor, proposed time window, predicted drive time/distance, policy checks passed/failed (with rule IDs), SLA impact, and a reason code for any rejected candidates. - Given dry-run results exist, When an admin clicks "Apply Selected", Then only suggestions that still pass all policy checks at apply time are executed; suggestions that fail re-validation are skipped with an updated reason. - Given up to 150 units and 200 waitlisted jobs, When a dry-run is triggered, Then suggestions are produced within 10 seconds at the 95th percentile. - Given dry-run mode is active, When exporting results, Then a CSV/JSON export includes all fields needed to reproduce the decision (inputs, evaluated policies, outcomes).
Override Reasons Capture & Audit Trail
- Given an attempted backfill is blocked by a policy, When a user performs a manual override to proceed, Then the system requires a reason code (from a configurable list) and a free-text justification of at least 10 characters before confirming. - Given an override is submitted, When it is recorded, Then the audit entry captures: user ID, timestamp (UTC), affected job ID(s), vendor ID, violated rule ID(s), previous decision, new decision, reason code, and justification. - Given an override is recorded, When viewing the audit log, Then entries are immutable; edits create append-only corrections with linkage to the original entry. - Given reporting is requested, When exporting audit data, Then override entries are included and filterable by portfolio, vendor, rule ID, date range, and user. - Given regulatory/union constraints are involved, When an override attempts to bypass them, Then the system blocks the override and records the attempt with reason "non-overridable constraint".
Travel Distance/Time and Work-Hour Guardrails
- Given a max travel distance (km/mi) and/or max drive time (minutes) is configured, When evaluating candidate jobs for backfill, Then any candidate exceeding either threshold is excluded with reason codes (DISTANCE_LIMIT or DRIVE_TIME_LIMIT). - Given live traffic data is available, When computing drive time, Then the system uses live data; if the service is unavailable, it falls back to historical averages and logs reason FALLBACK_TRAFFIC_DATA. - Given the vendor has an existing route, When evaluating travel for an insertion, Then drive time is calculated from the preceding confirmed stop; if none, from the vendor’s home base or depot per configuration. - Given vendor work hours and time zone are configured (including holidays and blackout periods), When proposing a backfill start time, Then the time falls within allowed hours; otherwise the candidate is excluded with reason OUTSIDE_WORK_HOURS unless overtime is enabled. - Given overtime is enabled with a maximum daily/weekly limit, When proposing a backfill that would exceed the limit, Then the candidate is excluded with reason OVERTIME_LIMIT. - Given an admin changes travel/time thresholds or work hours, When a new backfill event is evaluated, Then the updated values are applied within 60 seconds.
Lead-Time Minimums and Tenant Notice
- Given a minimum lead-time (hours) is configured, When proposing a backfill, Then the start time must be >= now + minimum lead-time; otherwise the candidate is excluded with reason LEAD_TIME_MIN. - Given same-day backfills are allowed with tenant consent, When the candidate start is < minimum lead-time, Then the system requests tenant consent via configured channels and only proceeds if explicit consent is received within the consent window; otherwise it is excluded with reason TENANT_CONSENT_MISSING. - Given tenant notification templates are configured, When a backfill is confirmed, Then the tenant receives a notification including time window, vendor info, and confirmation link, and delivery is logged. - Given a tenant declines or does not respond within the consent window, When evaluating the candidate, Then the system cancels the proposal and documents reason TENANT_DECLINED or TENANT_NO_RESPONSE.
Job Eligibility: Type Exclusions, Approvals/Estimates, Parts-in-Hand, and Cost Caps
- Given a list of excluded job types is configured, When evaluating candidates, Then any job whose type matches the exclusion list is never scheduled and is marked with reason TYPE_EXCLUDED. - Given a job requires prior approval and/or an estimate, When evaluating the job, Then the job is eligible only if approval status = Approved and an estimate document is attached; otherwise it is excluded with reason APPROVAL_REQUIRED or ESTIMATE_REQUIRED. - Given required parts are specified for a job, When evaluating eligibility, Then the job is eligible only if parts_in_hand = true or all required SKUs are marked Available at the vendor/warehouse; otherwise it is excluded with reason PARTS_NOT_AVAILABLE. - Given a cost cap is configured (global or per-vendor), When the predicted cost exceeds the cap, Then the job is not auto-scheduled and is routed for approval with reason COST_CAP_EXCEEDED; once approval is granted, it may be reconsidered. - Given multiple ineligibility reasons apply, When the system records the outcome, Then all applicable reason codes are captured to support analytics.
Vendor Utilization Balancing and SLA Protection
- Given a target utilization percentage and max daily jobs/hours per vendor are configured, When selecting a backfill, Then the algorithm prefers candidates that increase utilization toward target without exceeding max jobs/hours. - Given SLA windows are defined per job priority, When ranking candidates, Then any candidate that would violate its SLA window is excluded with reason SLA_RISK unless a policy explicitly allows SLA trade-offs. - Given multiple viable candidates remain, When ranking them, Then the system orders by (1) highest SLA risk reduction, (2) utilization improvement, (3) shortest incremental drive time. - Given a backfill is scheduled, When utilization metrics are recalculated, Then the vendor’s utilization dashboard updates within 60 seconds reflecting the new booking. - Given no candidate meets both utilization and policy constraints, When evaluation completes, Then no change is made and the event is logged with reason NO_ELIGIBLE_CANDIDATES.
Audit Trail, Metrics & SLA Impact Reporting
"As a portfolio manager, I want complete visibility into backfill decisions and their impact so that I can demonstrate ROI and resolve disputes quickly."
Description

Records an end-to-end audit for each backfill event, including trigger reason, candidates considered, scoring rationale, chosen job, communications, confirmations, and final outcome. Aggregates KPIs such as reclaimed hours, technician utilization, average response time improvement, travel time savings, and SLA adherence. Provides dashboards, exports, and webhooks for BI. Outcome: transparent decisioning, faster dispute resolution, and quantifiable ROI for landlords and managers.

Acceptance Criteria
Backfill Event Audit Record Creation
- Given a backfill trigger occurs (cancellation or tenant non-response) and the system schedules a replacement, When the backfill is confirmed or fails, Then exactly one audit record with a unique trace_id is created within 5 seconds of the event outcome. - And the record contains: trigger_reason, trigger_timestamp, candidate_set_count, candidate_ids, scoring_inputs, scoring_version, selected_job_id, selection_score, communications_sent[], confirmations[], technician_id, scheduled_slot, outcome_status, outcome_timestamp. - And the record is retrievable via API GET /audit/backfills/{trace_id} and visible in the job’s UI timeline within 2 seconds of creation.
Audit Trail Integrity, Immutability, and Access Controls
- Given an audit record exists, When any correction or note is added, Then the system appends a new immutable event linked by trace_id and prior_event_id; no in-place edits are permitted. - And each event includes server_timestamp (ISO 8601 UTC), actor_type (system/user/vendor), actor_id, and a SHA-256 content_hash; validating the hash chain detects tampering. - And only OrgRole Owner, Manager, or Auditor can view full audit details; Technicians see communications and schedule only; Tenants have no access. - And PII fields are masked in exports and webhooks for roles without PII permission. - And audit records are retained for at least 24 months and cannot be deleted by non-admin users.
KPI Aggregation and Accuracy
- Given backfill events exist in a selected date range, When nightly aggregation runs at 02:00 UTC, Then the system computes: reclaimed_hours, technician_utilization_rate, average_response_time_improvement, travel_time_savings, SLA_adherence_delta. - And cancelled backfills without a completed visit are excluded from aggregates; completed visits are included. - And recomputing metrics from raw audit events for a random 10% sample matches displayed aggregates within ±1% for rates and ±0.1 hour for durations. - And all metrics are tagged with aggregation_window, filter_set, and computation_timestamp for lineage.
SLA Impact Calculation and Attribution
- Given each job has SLA targets (response X hours, resolution Y hours), When a backfill reschedules a visit, Then the system computes per-job deltas: response_time_delta, resolution_time_delta, SLA_met_before, SLA_met_after. - And the job detail view displays the deltas and labels impact as improved, unchanged, or worsened. - And portfolio SLA adherence reflects per-job outcomes and is exposed via GET /metrics/sla with filters; responses return in under 1 second for up to 5,000 jobs.
Analytics Dashboard Filters, Drill-Down, and Performance
- Given a manager opens the Waitlist Backfill dashboard, When filters (date range, property/portfolio, vendor, technician, region) are applied, Then KPI tiles, charts, and tables refresh within 2 seconds for up to 150 units over the last 12 months. - And clicking a KPI drills down to contributing events showing: trace_id, job_id, vendor, technician, scheduled_time, travel_savings, response_improvement, SLA_impact, outcome_status. - And selecting an event opens the full audit timeline view in under 1 second.
BI Exports and Webhooks Delivery
- Given a user requests a CSV export for a filter set, When generation completes, Then the file uses schema v1 with one row per backfill event, ISO 8601 UTC timestamps, role-based PII masking, and is delivered via secure link within 60 seconds for up to 50,000 rows. - And scheduled exports can run daily or weekly to S3 or email; failures are logged with reason and notify the requester. - And webhooks post on backfill_created, backfill_updated, and backfill_finalized with HMAC-SHA256 signature, idempotency key, and retries with exponential backoff for up to 24 hours; a 2xx response marks delivery success; payload includes trace_id and authorized audit fields.
Waitlist Management UI & API
"As a coordinator, I want to manage the waitlist and job readiness so that backfills reflect current priorities and real-world constraints."
Description

Delivers a dedicated interface and API to curate the smart waitlist: add/remove jobs, set priorities, freeze or exclude specific work orders, annotate constraints (access windows, parts ETA), and bulk-import from pipelines. Supports segmenting by property, technician, vendor, or region and shows readiness signals (approvals, parts, tenant availability). Integrates with FixFlow role-based permissions and existing work order views. Outcome: coordinators can proactively shape the pool of candidates that backfill draws from, improving match quality and control.

Acceptance Criteria
Add/Remove Jobs via UI and API
Given a user with Manage Waitlist permission When they add an eligible workOrderId via UI or POST /waitlist Then the job appears in the waitlist with status "queued" within 2 seconds and the API returns 201 with the waitlistEntryId Given a job already exists in the waitlist When the same workOrderId is added again Then no duplicate entry is created and the API returns 200 with the existing waitlistEntryId Given a job is on the waitlist When the user removes it via UI or DELETE /waitlist/{waitlistEntryId} Then the job is removed from the waitlist within 2 seconds, the underlying work order remains unchanged, and the API returns 204 Given a closed or canceled work order When the user attempts to add it to the waitlist Then the request is rejected with 409 WAITLIST_INELIGIBLE and a human-readable error Given any add/remove action When it is completed Then an audit log is recorded with actor, timestamp, method (UI/API), target ids, and reason
Set Priority and Ordering Rules
Given a job on the waitlist When a coordinator sets priority to a value from 1 (highest) to 5 (lowest) via UI or PATCH /waitlist/{id} Then the new priority is persisted and immediately reflected in list ordering Given two jobs share the same priority When the list is ordered Then jobs are secondarily ordered by readinessScore (desc) then createdAt (asc) Given a concurrent update occurred When a client sends a PATCH without a current ETag/updatedAt match Then the API responds 409 CONFLICT with a retry hint Given an unauthorized user attempts to change priority When the request is processed Then the API responds 403 FORBIDDEN and UI controls are disabled Given a bulk priority change of up to 100 items via PATCH /waitlist/bulk When the request is processed Then either all updates succeed within 1 second p95 or all are rolled back and a per-item error report is returned
Freeze and Exclude Controls
Given a job on the waitlist When the coordinator toggles Freeze Then the job remains visible with a "Frozen" badge, is not auto-reordered, and is excluded from backfill candidate responses Given a job on the waitlist When the coordinator toggles Exclude Then the job is hidden from backfill candidate selection and from default views, but appears when the "Excluded" filter is applied Given a frozen or excluded job When Unfreeze or Include is applied Then the previous behavior is restored and the change is captured in audit logs Given the backfill engine requests /waitlist/candidates When frozen or excluded jobs exist Then they are not returned in the candidate set
Annotate Constraints and Readiness Signals
Given a job on the waitlist When a coordinator saves constraints (accessWindows as ISO-8601 intervals, partsEta as date, tenantAvailability slots, approvalsStatus in {approved,pending,blocked}) Then input formats are validated, past-dated partsEta is rejected, and values are persisted Given constraints or approvalsStatus change When the record is saved Then readinessStatus is recalculated: Ready if approvals=approved AND (partsRequired=false OR partsEta<=today) AND tenantAvailability present; Blocked if approvals=blocked OR (partsRequired=true AND partsEta is null); otherwise Pending Given readinessStatus is determined When the job is displayed or fetched via API Then readinessStatus and readinessReasons[] are shown alongside the job Given required constraint fields are missing for a job flagged partsRequired=true When saving Then the API rejects with 422 and field-level error details
Segmenting and Filtering by Scope
Given filters by propertyId, technicianId, vendorId, or region are applied in UI or via query params When the waitlist is fetched Then only matching jobs are returned, counts are accurate, and empty states are shown when no results match Given a user is scoped to a region or property via RBAC When they request out-of-scope segments Then the API returns 403 and UI does not expose restricted scope options Given pagination with page and size (size<=100) When retrieving up to 1,000 items Then p95 response time is <=400ms and the response includes totalCount and stable sorting across pages Given sorting by priority or readiness is selected When navigating pages Then ordering remains consistent and deterministic
Bulk Import from Pipelines and CSV
Given a coordinator selects a pipeline result set or uploads a CSV for import When they run a dry-run Then a preview is returned with row-level validation errors, duplicate detection (existing on waitlist), and an estimated readiness for valid rows Given the coordinator confirms the import with an idempotency key When processing up to 2,000 valid rows Then valid rows are added to the waitlist, duplicates are skipped, invalid rows are excluded with a downloadable error report, and an operationId is returned Given a network retry occurs with the same idempotency key When the confirm request is re-sent Then no additional entries are created and the same operationId and summary are returned Given the import completes When notifications are dispatched Then the coordinator receives a summary with counts (added, skipped-duplicate, invalid) and a link to the error CSV
Role-Based Permissions and Auditability
Given role assignments (Admin, Coordinator, Viewer) When a user without Manage Waitlist permission attempts any mutation Then the request is rejected with 403 and no changes are persisted Given a Viewer role user When accessing the waitlist Then they can read entries without tenant PII unless PII scope is granted Given any successful mutation via UI or API When the action completes Then an audit record includes before/after values, actor, timestamp, channel (UI/API), and optional reason Given identity changes via SSO group mapping When roles are updated Then permissions take effect within 5 minutes and are reflected on next token refresh

Preference Memory

Learns each tenant’s preferred days, hours, access method, and building rules, then pre-filters suggested windows to maximize acceptance. Reduces declines and reschedules while delivering a more personalized, respectful scheduling experience.

Requirements

Tenant Preference Capture
"As a tenant, I want to set my preferred days, times, and access method so that appointments are offered when I can accept them."
Description

Collect and store each tenant’s preferred days, time ranges, access methods (e.g., doorman, lockbox, self-access), language, contact channels, and building-specific constraints (quiet hours, elevator bookings, pet rules) with validation, time zone support, and sensible defaults. Allow tenants and managers to edit preferences at any time, maintain change history, and surface preferences contextually during maintenance intake and scheduling. Provide multi-language UI, accessibility compliance, and import of existing building rules to reduce setup friction and increase first-offer acceptance rates.

Acceptance Criteria
Capture core preferences with validation and sensible defaults
Given a tenant with no saved preferences, When the Preferences form loads, Then fields are present: preferred days (Mon–Sun), time ranges per selected day, access methods (doorman, lockbox, self-access, manager escort), language, and contact channels (SMS, email, phone, push). Given the tenant completes the form, When submitting, Then validations enforce: at least one day selected, each time range has start < end, ranges do not overlap per day, times are between 00:00 and 23:59, and at least one contact channel is selected. Given the building has specific constraints, When an incompatible access method is chosen, Then the form prevents submission and explains the conflict. Given the tenant leaves optional fields blank, When submitting, Then defaults are applied: Mon–Fri 09:00–17:00 local time, language from browser locale, and primary contact channel from onboarding record, with defaults clearly labeled and editable. Given successful validation, When saving, Then preferences persist to the tenant profile and are retrievable via UI and API within 2 seconds.
Time zone detection, storage, and normalization
Given a tenant in tzid America/Los_Angeles and a building in tzid America/New_York, When the tenant saves 08:00–10:00 as a preferred window, Then the system stores times in UTC with tenant tzid and displays the converted 11:00–13:00 to managers in building local time. Given a DST transition week, When preferences are displayed and used, Then the shown local times remain consistent with the tenant’s chosen hours and are scheduled using correct DST offsets. Given an API request for preferences, When responding, Then the payload includes tzid and ISO 8601 timestamps (with offsets) for all time ranges. Given manager overrides tenant tzid, When saving, Then all existing preference times are re-normalized without shifting intended local hours.
Edit permissions and audit history for preferences
Given an authenticated tenant, When updating any preference field, Then the change is saved and an audit entry is recorded with timestamp, actor, field, before/after values, and source (web/app/API). Given a manager with edit permissions, When updating a tenant’s preferences, Then the same auditing applies and the tenant-facing UI reflects the change within 2 seconds. Given a manager views a tenant profile, When opening the Preference History, Then at least the last 50 changes are listed with filters by date range, actor, and field. Given a data retention policy of 24 months, When querying audit history beyond 24 months, Then the system indicates archival status or unavailability without error.
Contextual surfacing during maintenance intake and manager triage
Given a tenant starts maintenance intake, When reaching the scheduling step, Then a read-only summary of current preferences (days/hours, access method, language, contact channel) is displayed with an Edit option. Given the tenant selects Edit during intake, When saving updates, Then the new preferences are immediately applied to the current scheduling flow and persisted. Given a manager opens a new request in triage, When the request details load, Then the tenant’s preferences and building rules are shown in a sidebar within 1 second. Given no preferences are on file, When intake begins, Then a guided 3-step inline setup appears before suggesting time windows.
Scheduling suggestions pre-filtered by tenant preferences and building rules
Given tenant preferred days/hours and building quiet hours/elevator booking windows, When generating suggested appointment windows, Then all suggestions fall within the intersection of tenant preferences, technician availability, and building constraints. Given fewer than 3 valid windows exist in the next 7 days, When generating suggestions, Then the system expands the search horizon to 14 days and displays a reason indicating which constraints limited options. Given no valid windows exist, When generating suggestions, Then the system offers to request manager override or to propose custom times outside constraints with explicit acknowledgment. Given access method requires a doorman, When suggesting times, Then only windows within doorman service hours are presented.
Multi-language UI and WCAG 2.1 AA compliance for preference capture
Given the tenant’s language preference is Spanish, When viewing the Preferences form and validation messages, Then all visible text is localized to Spanish with no fallback English strings. Given a keyboard-only or screen reader user, When navigating the Preferences form, Then all controls are reachable via keyboard, have accessible names/roles/states, errors are programmatically associated, and color contrast meets or exceeds 4.5:1. Given the tenant changes language from English to Spanish, When the page refreshes, Then existing inputs remain intact and the UI language updates within 1 second without data loss.
Import and mapping of building rules into scheduling constraints
Given a building admin uploads a CSV or JSON containing quiet hours, elevator booking windows, and pet rules, When processed, Then the file is validated, rules are mapped to the building and applicable units, and a success summary with counts is shown. Given conflicting or overlapping rules in the import, When processing, Then the system flags conflicts with line-level details and prevents activation until resolved or acknowledged. Given imported rules are active, When tenants set or edit preferences, Then UI prevents selection of hours violating building rules and explains the restriction. Given API-based rule import, When posting to the import endpoint, Then rules are processed with the same validation and mapping as file uploads.
Preference-Aware Slot Suggestions
"As a tenant, I want the app to suggest appointment windows that match my preferences so that I can confirm quickly without back-and-forth."
Description

Filter and rank appointment windows using tenant preferences, building rules, technician constraints, and SLAs to produce a scored list of suggested slots. Support hard vs. soft constraints, blackout dates and holidays, and emergency bypass logic with audit. Present top options to tenants and dispatchers in UI and via API, with rationale badges (e.g., matches quiet hours, preferred window) and fallback options when exact matches are unavailable, reducing declines and reschedules.

Acceptance Criteria
Hard vs Soft Preference Filtering and Ranking
Given a tenant with hard preferences (e.g., no weekends, access method required) and soft preferences (e.g., preferred days/hours), building quiet hours, technician constraints, and an SLA deadline When the system generates suggested appointment slots Then it excludes any slot that violates hard preferences or building rules And it includes only slots that satisfy technician availability, skill, travel buffer, and job duration And it applies positive score weights for soft preference matches and negative weights for deviations And it returns a scored list sorted by descending score with at least 3 options when available
Building Rules and Quiet Hours Enforcement
Given building rules including quiet hours and restricted work types When evaluating candidate slots Then any slot overlapping a restricted period is excluded unless an authorized emergency override is active And partial overlaps are treated as violations And each returned slot includes a rationale badge indicating compliance with building rules (e.g., "Respects quiet hours")
Technician Constraints and SLA Compliance
Given technician calendars, skills, service areas, travel buffers, and job duration estimates, and a work order with an SLA due date/time When generating suggestions Then only technicians with required skills and in-range service areas are considered And only slots that respect travel buffers and can accommodate the full job duration are included And suggested slots that complete before the SLA deadline are marked with an "SLA-safe" badge; those that cannot meet SLA are excluded unless no SLA-safe slots exist, in which case the best next slots are returned with an "SLA risk" badge
Blackout Dates and Holidays Handling
Given tenant-specific blackout dates, building blackout dates, and regional holidays When computing candidate slots Then all dates/times within blackout or holiday periods are excluded And boundary conditions at start/end of blackout periods are respected to the minute And returned slots include rationale badges when exclusions affected availability (e.g., "Holiday avoided")
Emergency Bypass with Auditability
Given a work order flagged as emergency and a user with override permissions When generating suggestions with emergency bypass enabled Then the system may include slots that violate building quiet hours or tenant blackout dates but must still respect technician availability and safety constraints And an audit record is created capturing user, timestamp, rules bypassed, reason, and affected slots And returned slots display an "Emergency override" badge and the API response includes an auditId referencing the log entry
UI and API Slot Presentation with Rationale Badges
Given a scored list of compliant slots When presenting options Then the tenant and dispatcher UIs display the top 5 slots by score by default with score, start/end time, technician, and rationale badges (e.g., "Preferred window", "Access method supported", "SLA-safe") And users can expand to view more slots and sort by time or score And the API endpoint returns JSON including slot id, start/end, technician id, score, matchedConstraints[], violatedSoftConstraints[], badges[], and rankingVersion metadata And for up to 100 candidate slots the API p95 response time is ≤ 500 ms
Fallback Suggestions When Exact Matches Are Unavailable
Given fewer than 3 slots satisfy all hard constraints and match at least one soft preference When generating suggestions Then the system returns up to 3 fallback slots that keep all hard constraints intact while relaxing soft preferences in priority order (e.g., expand hours before changing day) And each fallback slot includes badges explaining deviations (e.g., "Near preferred hours", "Outside preferred day") and a short whyNot summary of excluded options And the UI provides a control to request more times or update preferences
Adaptive Preference Learning
"As a tenant, I want the system to learn from my acceptances and declines so that future suggestions better match my real availability."
Description

Continuously update stored preferences using observed behavior (accept/decline/reschedule/no-show), response latency, and context (issue type, urgency, seasonality). Maintain confidence scores and decay over time, propose changes to tenants for confirmation, and auto-apply low-risk updates. Provide explainability (why a preference changed) and an activity log. Improves personalization accuracy and acceptance rates over time without requiring manual input.

Acceptance Criteria
Confidence Score Update and Decay
Given a stored preference with a current confidence score and configured increment/decay parameters When new evidence consistent with the preference is observed Then the confidence score increases by the configured positive_evidence_increment and is capped at 1.0 And when new evidence contradicts the preference is observed Then the confidence score decreases by the configured negative_evidence_increment and is floored at 0.0 And when no new evidence is observed for the configured decay_interval Then the confidence score decays by the configured decay_rate once per interval with timestamped updates
Behavior-Driven Updates from Accept/Decline/Reschedule/No-Show
Given a tenant action is captured as accept, decline (with selected reason), reschedule (with chosen window), or no-show When the action occurs for a suggested window Then the system records the action with timestamp, issue ID, window, and reason (if provided) And updates the relevant preference dimensions (day-of-week, time-of-day, access method) using configured weightings And marks the evidence strength based on action type (accept > reschedule > decline > no-show) per configuration And persists the updated preference values and confidence scores atomically
Response Latency as Evidence
Given response_latency_thresholds are configured per channel When a tenant responds within the fast threshold to a proposed window Then the system treats the window's attributes as positive evidence with the configured weight And when the response exceeds the slow threshold or there is no response by expiry Then the system treats the window's attributes as negative or weak evidence with the configured weight And logs latency buckets for analytics
Context-Aware Preferences by Issue Type, Urgency, and Seasonality
Given context dimensions include issue_type, urgency, and season When evidence is recorded Then the system stores and updates preferences in a context-aware model that differentiates by these dimensions And ensures that preferences for urgent issues do not overwrite non-urgent preferences and vice versa And applies seasonality decay or segmentation so that out-of-season evidence does not dominate in-season preferences
Auto-Apply Low-Risk Preference Changes
Given a candidate preference change is proposed by the model When the change's confidence score is greater than or equal to the auto_apply_confidence_threshold and the change passes policy and conflict checks Then the system auto-applies the change without tenant confirmation And sends a notification to the tenant summarizing what changed and why And records the auto-application in the activity log with rationale and audit fields
Tenant Confirmation for High-Impact Changes
Given a candidate preference change is high-impact or below the auto-apply threshold When the system proposes the change to the tenant Then the tenant is presented with old versus new values, rationale, and a one-tap approve or decline And if the tenant approves, the change is applied and confidence is set per the configured confirmation_weight And if the tenant declines, the change is not applied and negative evidence is recorded And if the tenant does not respond by the configured timeout, the proposal expires with no change applied
Explainability and Activity Log
Given any preference change (auto-applied or tenant-confirmed) When a user views the change details Then the UI shows an explanation including top evidence events, context, and confidence trajectory And the activity log entry includes timestamp, actor (system or tenant), fields changed, old and new values, confidence before and after, evidence summary, and correlation ID And entries are filterable by tenant, date range, issue, and change type and exportable in CSV
Manager Rules & Overrides
"As a property manager, I want to enforce building rules and override when necessary so that appointments comply with policies and regulations."
Description

Enable property managers to define building-wide rules (allowed service hours, required notice, elevator reservations, vendor restrictions, access requirements) and set priority relative to tenant preferences. Allow per-job overrides with reason capture, show policy conflicts and compliant alternatives, and notify affected parties. Ensure legal or building rules always supersede tenant preferences to maintain compliance while minimizing scheduling friction.

Acceptance Criteria
Enforce Allowed Service Hours and Required Notice
Given building-wide rules define allowed service hours and minimum notice windows When the system generates suggested appointment windows Then 100% of suggested slots fall within allowed hours and meet the minimum notice And non-compliant times are not selectable for confirmation And if a user attempts to select a non-compliant time, the system blocks confirmation, displays the violated rule(s), and shows at least 3 compliant alternatives within the same 7-day period
Rule Priority Over Tenant Preferences
Given tenant availability and preferences conflict with building or legal rules When the scheduling engine composes suggestions Then tenant preferences are filtered by rule priority so that no suggestion violates Required rules And the UI indicates that suggestions are filtered by building rules with a visible badge And the system records the count of filtered-out preferred times in the job context for auditability
Vendor Restrictions, Access Requirements, and Elevator Reservations
Given building rules specify vendor restrictions, access requirements (e.g., lockbox, key pickup, supervised entry), and elevator reservation policies When a manager or tenant initiates scheduling Then only vendors compliant with restrictions are eligible for assignment And the system requires the appropriate access method details before confirmation And if an elevator reservation is required by rule, the system blocks confirmation until a reservation ID/time window is captured or a linked reservation resource is booked And if no compliant vendor/time satisfies all rules, the system surfaces a blocker with the first 5 closest compliant alternatives and the option for manager override (if permitted)
Conflict Detection and Compliant Alternatives
Given a selected or proposed time violates one or more rules When the user reviews the appointment details Then the system lists each violated rule with a human-readable explanation and severity (Required, Strong, Advisory) And the system proposes at least 5 nearest compliant alternatives ranked by proximity to tenant preferences and earliest availability And the user can replace the conflicted slot with one click selecting any alternative And the replacement re-validates rules before confirmation
Per-Job Overrides with Reason Capture and Safeguards
Given a manager attempts to override a rule for a specific job When the rule is marked Strong or Advisory Then the system requires selecting a reason from a configurable list and capturing free-text notes (minimum 10 characters) before saving And the override record stores user, timestamp, rule identifier, old/new values, and job scope And when the rule is Required (legal/compliance), the override control is disabled and attempting to bypass produces a non-dismissible error explaining non-overridable status
Notifications to Tenants, Technicians, and Managers
Given an override or conflict resolution changes the confirmed appointment time, access method, vendor, or building resource When the change is saved Then the tenant and assigned technician are notified within 5 minutes via their configured channels with reason, changed fields, and any required action And notifications include a link to confirm or propose alternatives where applicable And delivery and read statuses are visible to the manager, with at least one retry on transient failures
Audit Trail for Rules and Overrides
Given any create, update, or delete of building-wide rules or any per-job override When the change is saved Then an immutable audit entry is created capturing actor, timestamp, affected property/unit, rule type, severity, before/after values, job ID (if applicable), and reason And audit entries are queryable by date range, property, rule type, and user, returning results within 2 seconds for up to 10,000 records And audit data is exportable to CSV with the same fields
Privacy, Consent & Data Governance
"As a tenant, I want control over how my preference data is used and stored so that my privacy is respected and compliant."
Description

Capture explicit consent for storing and using preference data with clear disclosures; provide opt-in/opt-out controls, data export, and deletion. Enforce access controls, audit logging, encryption at rest and in transit, and data minimization. Support jurisdictional compliance (e.g., GDPR/CCPA), retention schedules, and multi-tenant isolation. Present transparent privacy settings within the tenant portal to build trust and meet regulatory requirements.

Acceptance Criteria
Explicit Consent Capture for Preference Memory
Given a tenant who has not previously consented, When they attempt to save or enable scheduling preferences, Then a consent dialog displays purpose, data categories, retention period, rights to withdraw, and a link to the privacy policy, and the primary action is disabled until the tenant explicitly opts in. Given the consent dialog, When the tenant selects "Agree" with an unchecked-by-default checkbox and confirms, Then the system records consent with tenantId, timestamp, disclosureVersion, jurisdiction, and processingPurpose, and enables preference storage. Given the consent dialog, When the tenant declines or closes it, Then preference storage remains disabled and no preference data is persisted. Given a tenant with prior consent, When the disclosureVersion is updated with material changes, Then the next attempt to save preferences requires re-consent before processing.
Tenant Portal Opt-In/Opt-Out Controls
Given a logged-in tenant, When they open Privacy Settings, Then they see a toggle to enable/disable Preference Memory with current consent status displayed. Given the toggle is switched off, When the tenant confirms, Then new preference data is not stored or used in scheduling, and scheduling suggestions revert to neutral defaults. Given the toggle is switched on, When the tenant confirms, Then subsequent scheduling suggestions use stored preferences. Given consent is revoked, When background jobs run, Then any processing that uses preference data is halted within 5 minutes.
Tenant Data Export (Access Request)
Given a verified tenant, When they request an export of their preference data, Then the system generates a machine-readable package (JSON) including stored preferences, consent records, and related audit log entries within retention, and makes it available for secure download within 15 minutes. Given an export request, When the package is ready, Then the tenant receives a notification and can download over TLS 1.2+. Given an export package, Then it includes requestId, generatedAt, jurisdiction, and data provenance fields.
Tenant Data Deletion (Right to Erasure)
Given a verified tenant, When they request deletion of their preference data, Then the system queues the request and surfaces a visible status to the tenant. Given a deletion request, When processing completes, Then all tenant preference records and consent metadata are deleted or irreversibly anonymized except records required for legal compliance, and the tenant is notified within 30 days. Given a deletion request is pending, Then no further processing uses the tenant’s preference data. Given backups, When routine backup pruning occurs, Then deleted records are purged from backups within 30 days of deletion completion.
Role-Based Access Control and Audit Logging
Given an internal user, When they attempt to access tenant preference data, Then access is allowed only for roles with Preferences.Read permission scoped to the same organization and property, enforced server-side. Given an unauthorized access attempt, Then the system denies with 403 and records an audit event including actorId, action, resource, result, timestamp, and ipHash. Given any read or write of preference data, Then an immutable, tamper-evident audit trail is appended, retained for at least 400 days, and is searchable by tenantId and actorId.
Encryption, Data Minimization, and Retention Enforcement
Given any preference data at rest, Then it is encrypted using AES-256 or equivalent; Given any data in transit, Then it is protected by TLS 1.2+. Given encryption keys, Then they are rotated at least every 90 days and stored in a managed KMS with access logging. Given data fields for Preference Memory, Then only necessary fields are stored (preferred days/hours, access method, building rules, consent metadata, jurisdiction); no free-text notes or unrelated PII are persisted. Given inactivity, When 12 months have passed since the tenant’s last use or tenancy end + 90 days (whichever is sooner), Then preference data is automatically deleted per retention policy with an auditable record.
Jurisdictional Compliance and Multi-Tenant Isolation
Given a tenant’s property location and declared residency, When determining compliance, Then the system applies the correct flows (e.g., GDPR explicit opt-in and DSR timelines; CCPA opt-out and access/deletion rights) and records the governing jurisdiction on consent and exports. Given multi-tenant architecture, Then tenant preference data is logically isolated by organizationId and propertyId; cross-tenant queries are blocked at the data access layer and validated in integration tests. Given data residency configuration for an organization, Then tenant preference data is stored and processed only within the configured region(s).
Technician Schedule Fusion
"As a dispatcher, I want tenant preferences combined with technician availability so that suggested windows are feasible to service."
Description

Combine tenant preferences with technician calendars, travel time, skills, parts availability, and dispatch buffers to ensure suggested windows are operationally feasible. Support real-time updates, double-booking prevention, route optimization, and third-party vendor integrations (iCal/API). Expose conflicts with explanations and alternative times that preserve tenant preferences where possible, reducing dispatch conflicts and last-minute changes.

Acceptance Criteria
Feasible Window Generation with Tenant Preferences and Operational Constraints
Given a maintenance request with tenant preference bands (days/hours/access) and building rules, a pool of technicians with calendars, skills, current locations, and buffer settings, parts with ETAs, and a configured travel-time provider When the system generates suggested appointment windows for the request Then every suggested window lies within the tenant’s preferred days and hours And the assigned technician has the required skills and is not otherwise booked including buffers And parts required for the job have ETA on or before the slot start And computed travel time from the technician’s prior commitment plus pre/post buffers does not cause overlap And computed travel time to the next commitment is feasible with its buffers And if three or more feasible windows exist in the next 5 days, at least 3 are returned; otherwise all feasible windows (max 10) are returned And no suggested window violates building access rules
Real-Time Double-Booking Prevention and Locking
Given two tenants attempt to accept overlapping windows that would double-book the same technician when buffers are applied When one acceptance is confirmed Then any concurrent or subsequent acceptance that would overlap is blocked atomically And the second tenant receives a conflict response with at least 3 alternative windows that align to their preferences And the technician’s calendar shows only one confirmed booking for the overlapping period
Automatic Re-optimization on Technician Delay or Parts ETA Change
Given a confirmed schedule containing the request and downstream appointments for a technician And a real-time update occurs (technician delay >= 15 minutes, job duration extension, or parts ETA slips past a scheduled slot) When the update is received by the system Then affected appointments are re-optimized within 60 seconds to a feasible sequence that respects tenant preference bands where possible And no resulting appointment overlaps when buffers and travel times are applied And impacted tenants and the property manager receive notifications of the proposed changes
Third-Party Calendar Integration and Conflict Ingestion
Given a third-party vendor calendar connected via iCal URL and a partner dispatch API with authentication configured When events are added, updated, or removed in the external calendars Then the system reflects these as busy/free blocks for the relevant resource within 60 seconds And imported busy blocks participate in double-booking prevention and route calculations And duplicate events are not created across sync cycles And any import error is logged and surfaced with a sync status of “Fail” for that source
Daily Route Optimization Preserving Tenant Preferences
Given a technician has 4 or more feasible jobs scheduled on the same day across different locations and each has tenant preference bands When the system runs route optimization Then the resulting order yields a total estimated travel time less than or equal to the naive chronological order for the same set on the provided test dataset And no job is moved outside its tenant’s preferred day/hour window And total idle time between jobs (buffers excluded) is reduced by at least 20% versus the naive baseline on the test dataset
Conflict Explanations with Preference-Preserving Alternatives
Given a tenant’s preferred window cannot be scheduled due to constraint(s) (skill mismatch, parts ETA, travel time, buffer, vendor blackout) When the system presents scheduling options Then the UI/API returns a machine-readable conflict code and human-readable explanation referencing the exact blocking constraint(s) And at least 2 alternative windows are offered that are the nearest in time to the preferred window and preserve at least one tenant preference (day or time-of-day band) where possible And if no feasible alternatives exist, the response explicitly indicates “no feasible window” with the primary blocking constraint
Preference-Aware Notifications & Confirmations
"As a tenant, I want notifications that reflect my preferences and offer easy confirmation so that scheduling is fast and convenient."
Description

Generate personalized SMS, email, and push notifications that respect tenant language and quiet hours, offering one-tap confirmation for the top matching windows plus an easy reschedule link. Include dynamic content (reason for suggestion, building rule compliance), send reminders, and fallback to generic templates if preferences are missing. Track delivery, clicks, acceptance, and reschedules to optimize templates and timing for higher conversion.

Acceptance Criteria
Quiet Hours and Language Respect Across SMS, Email, and Push
Given a tenant profile with preferred language "Spanish" and quiet hours 21:00–08:00 local time When the system generates a notification at 22:15 local time for any channel Then the notification is queued and sent at 08:00 local time the next day And the notification content is localized to Spanish for all channels Given a tenant profile with preferred language "English" and no quiet hours When the system generates a notification at any time Then the notification is sent immediately in English
Top Matching Windows With One-Tap Confirmation
Given a tenant with saved availability preferences and access method And a work order with candidate time windows computed When a notification is generated Then the message includes 1–3 suggested windows that satisfy the tenant’s preferred days and hours and applicable building rules And each window includes a single-action confirm control appropriate to the channel (deep link, button, or push action) And selecting the confirm control confirms the chosen window without additional steps and returns a success state
Reschedule Link and Alternate Windows
Given a notification with suggested windows has been sent When the tenant clicks the reschedule link Then a reschedule page shows at least 5 alternate windows filtered by the tenant’s preferences and building rules And selecting an alternate window updates the appointment and triggers a confirmation notification And the original confirmation controls are invalidated to prevent duplicate bookings
Dynamic Reason and Building Rule Compliance Messaging
Given a notification includes suggested windows When the tenant views the content Then each suggested window displays a brief reason derived from the tenant’s preferences for why it was suggested And the message states how the suggestion complies with relevant building rules And the displayed reasons and rules accurately reflect the data used for selection
Reminder Cadence and Stop Conditions
Given a notification with suggested windows is sent and no action is taken When the configured reminder cadence elapses Then a reminder is sent on each channel enabled for the tenant And reminders respect the tenant’s quiet hours by deferring send until outside quiet hours And reminders stop immediately after the tenant accepts a window or reschedules And no more than the configured maximum number of reminders are sent
Fallback to Generic Templates When Preferences Missing
Given a tenant profile has no saved language, quiet hours, or availability preferences When a notification is generated Then the system uses the default language configured for the property And the system applies the property-level quiet hours policy And the message uses the generic template And suggested windows are based on building rules and scheduler availability only And the message includes one-tap confirmation controls and a reschedule link
Delivery and Engagement Event Tracking
Given a notification is dispatched via any channel When the provider returns a delivery receipt Then an event "delivered" is recorded with timestamp, channel, tenant ID, and notification ID Given a notification contains confirm and reschedule links When the tenant clicks a link Then an event "click" is recorded with timestamp, channel, tenant ID, notification ID, and link type Given the tenant confirms a suggested window When the confirmation is processed Then an event "accepted" is recorded with timestamp and selected window ID Given the tenant reschedules and selects a new window When the reschedule is processed Then an event "rescheduled" is recorded with timestamp and new window ID

Keyless Pass

Generates time-bound, single-use access codes for supported smart locks or lockboxes and verifies vendor identity before release. Logs entry/exit with timestamps and auto-revokes access after the window, enabling secure, tenant-free entry and fewer missed appointments.

Requirements

Universal Lock Integration Layer
"As a property manager, I want FixFlow to work with my existing smart locks and lockboxes so that I can grant secure access without replacing hardware."
Description

Build and maintain connectors to major smart lock and lockbox providers (e.g., Schlage, Yale, August, Kwikset, Igloohome, Master Lock), supporting both cloud APIs and local bridge integrations. Provide device discovery, capability mapping (one-time PINs, time-bound codes, eKeys), token lifecycle management, retries/backoff, and webhook ingestion for access events. Include a certification/sandbox flow for each provider, standardized error codes, and health monitoring with alerts. Ensure secure credential storage, least-privilege scopes, and compliance with vendor terms to guarantee reliable, scalable access control for Keyless Pass across diverse hardware.

Acceptance Criteria
Cross-Provider Device Discovery via Cloud and Local Bridges
- When valid provider credentials are supplied, initiating discovery enumerates all accessible devices across Schlage, Yale, August, Kwikset, Igloohome, and Master Lock via cloud APIs and supported local bridges. - Discovery returns within 60 seconds for up to 500 devices per provider and supports pagination for larger inventories. - Each discovered device returns a normalized schema: provider, provider_device_id, model, firmware_version, capabilities[], online_status, last_seen_at. - Delta sync is supported: providing a cursor returns only added/removed/updated devices with a new cursor for the next run. - Partial failures are captured per device with standardized error codes while the overall job completes; job result and metrics are persisted. - Discovery success rate in staging over 7 rolling days is ≥ 99.5%; p95 discovery API latency ≤ 2s (excluding provider pagination).
Standardized Capability Mapping for Access Methods
- For each device, capability mapping exposes support for: one_time_pin, time_bound_code, ekey, with constraints (code_length_min/max, max_active_codes, schedule_granularity, timezone_handling). - Creating a one-time or time-bound code on a device that supports it succeeds and returns a normalized access_code_id and effective time window in UTC. - Attempting to create an unsupported access method fails with CAPABILITY_UNSUPPORTED and no side effects on the device. - Fallback logic is applied automatically (e.g., if one_time_pin unsupported, attempt time_bound_code) and is recorded in audit logs. - Provider-specific quirks (e.g., code length ranges) are enforced via validation with clear, standardized validation errors. - 100% of certification tests per provider validate mapping parity with provider documentation and sandbox behavior.
Token Lifecycle and Secure Credential Management
- OAuth tokens/keys are stored encrypted at rest using KMS/HSM; secrets are never logged or persisted in plaintext (0 violations in CI log scans). - Token refresh occurs proactively ≥ 10 minutes before expiry; refresh failures are retried with exponential backoff (max 3 attempts) and surface AUTH_FAILED on exhaustion. - Credentials use least-privilege scopes per provider; requested scopes are documented and validated in certification. - Manual revocation is supported: an admin can revoke a provider connection and invalidate tokens within 5 minutes; subsequent calls fail with AUTH_FAILED until reauthorized. - Secrets and webhook signing keys are rotated at least every 90 days with audit logs capturing actor, timestamp, and outcome. - 7-day rolling auth failure rate remains ≤ 0.1% across providers excluding planned revocations.
Robust Request Handling with Retries, Backoff, and Idempotency
- All mutating operations (create/revoke code, enable/disable device) include an idempotency key; retried requests do not create duplicate side effects. - 5xx, 429, and network timeouts are retried with exponential backoff + jitter, respecting provider rate-limit headers; max 6 retries; total backoff ≤ 90 seconds. - After timeout, the system performs a status check to confirm final operation state before retrying to ensure exactly-once semantics when possible. - Errors are mapped to standardized codes: AUTH_FAILED, NOT_FOUND, RATE_LIMITED, VALIDATION_ERROR, PROVIDER_UNAVAILABLE, CAPABILITY_UNSUPPORTED, CONFLICT, TIMEOUT; 100% coverage with human-readable messages. - Monthly SLOs: write operation success rate ≥ 99.5% and p95 latency ≤ 1.5s excluding backoff; breaches emit alerts and are visible on dashboards. - Idempotency records expire after 48 hours and are garbage-collected without impacting auditability.
Webhook Ingestion and Access Event Normalization
- Webhook endpoints verify signatures/timestamps per provider; invalid or replayed requests are rejected with 401 and not enqueued. - Events are normalized to a common schema: event_id, provider_event_id, device_id, type(entry|exit|code_created|code_revoked|heartbeat), occurred_at(UTC), actor_type/vendor_id, correlation_id(access_code_id). - Event processing is at-least-once with deduplication by provider_event_id and replay window; no duplicate records appear downstream (0 known duplicates in 30-day window). - 99.9% of events are processed end-to-end within 5 seconds; p99 ≤ 15 seconds; lag metrics are exported. - Failures are retried with exponential backoff for up to 24 hours; irrecoverable events land in a DLQ with alerting when DLQ > 100 messages or age > 15 minutes. - Successful entry/exit events are made available to Keyless Pass within 2 seconds of normalization, and include the associated access_code_id when applicable.
Provider Certification and Sandbox Verification Flow
- A sandbox/certification environment exists for Schlage, Yale, August, Kwikset, Igloohome, and Master Lock with at least two representative device models or a validated simulator each. - Automated certification covers: auth, discovery, capability mapping, create/revoke codes, event ingestion, rate-limit handling, error mapping, and clock skew; all must pass before enabling production. - Contract tests run in CI on every PR against provider simulators; builds failing certification tests are blocked from merge. - Provider terms of service and rate-limit policies are documented and validated; no disallowed endpoints/scopes are used (0 violations in certification reports). - A human-readable runbook and rollback plan exist per provider; promotion to production requires recorded sign-off from Engineering and Security. - Certification artifacts (logs, test results, config) are archived for ≥ 1 year for audit.
Connector Health Monitoring and Alerting
- Metrics emitted per provider: request_count, success_rate, error_rate by standardized code, p95/p99 latency, webhook_lag, rate_limit_hits, auth_failures, queue_depth. - Uptime SLOs: read operations ≥ 99.9% and write operations ≥ 99.5% monthly; breaches generate PagerDuty alerts within 2 minutes. - Health endpoints expose per-provider status (OK|DEGRADED|DOWN) and last heartbeat; status reflects real-time error budgets. - Alerts trigger when error_rate > 1% for 5 minutes, webhook_lag p95 > 30s for 5 minutes, or auth_failures > 5/min; alerts include provider, region, and recent error samples. - Dashboards display current and historical metrics with 1-minute resolution; data retention ≥ 13 months. - Chaos tests (fault injection/rate-limit) are executed monthly; findings and remediations are tracked to closure.
Time-Bound Single-Use Code Engine
"As a property manager, I want to issue time-bound, single-use codes for a scheduled visit so that vendors can enter securely only during the approved window."
Description

Implement a service that generates unique, single-use access codes constrained to a scheduled window with configurable buffers (early/late grace). Support online and pre-provisioned offline codes where devices allow, prevent code collisions, and auto-revoke after window expiry or cancellation. Handle time zones and device clock drift, enforce format rules per vendor, and securely store codes with encryption and minimal retention. Provide APIs to create/update/cancel codes tied to work orders, automatic re-issue on reschedule, and safeguards against brute-force attempts. Emit lifecycle events for monitoring and downstream notifications.

Acceptance Criteria
Unique Single-Use Code Generation Without Collisions
Given a scheduled work order with a target device When the system generates a single-use access code for that device Then the code is unique across all active and reserved codes for the device and account And any detected collision during generation triggers regeneration up to 5 attempts; after 5 failed attempts the request fails with HTTP 409 and no code is created And after the first successful validation of the code, all subsequent validations are rejected with reason "already_used" and a single "code.used" event is emitted
Time-Bound Access Window with Buffers and Offline Support
Given a scheduled window [start, end] with early buffer E and late buffer L When a code is validated at timestamp t Then validation succeeds only if start - E <= t <= end + L; otherwise it fails with reason "outside_window" And for devices supporting offline codes, a pre-provisioned code validates offline within the same window; for online validation, the engine honors the same window without widening it And when the window is updated, any pre-provisioned offline code is updated on next device sync and the superseded offline code is marked "revoked" and becomes invalid after sync
Auto-Revocation on Expiry or Cancellation
Given a code tied to a work order When the work order is cancelled OR the window end plus the late buffer has passed OR the code has been successfully used (single-use) Then the code becomes invalid for all subsequent validations And for online devices, revocation is pushed within 30 seconds; for offline-capable devices, revocation applies at next sync And an event of type "code.revoked" or "code.expired" is emitted with ISO 8601 UTC timestamp and correlationId
Vendor-Specific Code Format Enforcement
Given a target device whose vendor has a format policy (length, charset, pattern, checksum, and prohibited sequences) When generating or accepting a code for that device Then the code conforms to the vendor policy; otherwise generation/acceptance fails with HTTP 400 and error code "format_violation" And attempts to submit a non-conforming custom code via API are rejected and an audit log entry is recorded with the policy violation details
Time Zone and Device Clock Drift Handling
Given a work order stored in UTC with canonical time zone TZ and buffers E and L, and a device clock drift tolerance of 5 minutes When validating a code Then the engine evaluates the window in TZ and accepts validations within [start - E - 5m, end + L + 5m]; validations outside are rejected with reason "window_miss" or "clock_drift_exceeded" And transitions over daylight saving changes are computed using TZ rules so that wall-clock times match the scheduled window
Secure Storage, Retention, and Brute-Force Safeguards
Given any stored code material Then code secrets are encrypted at rest using AES-256-GCM with keys rotated at least every 90 days, and all transport uses TLS 1.2+ And plaintext codes are deleted within 24 hours after expiry/revocation; only salted hashes and minimal metadata (workOrderId, deviceId, timestamps, outcome) are retained for 90 days with RBAC and audited access Given the verification endpoint When more than 10 invalid attempts occur for the same device or code within 5 minutes Then further attempts are blocked for 15 minutes with HTTP 429 and a "security.bruteforce_detected" event is emitted including source identifiers (IP/fingerprint)
APIs for Create/Update/Cancel with Idempotency and Lifecycle Events
Given REST endpoints POST /codes, PATCH /codes/{id}, and DELETE /codes/{id} When requests include a valid workOrderId, deviceId, window, buffers, and an Idempotency-Key Then POST returns 201 with code details; idempotent replays within 24 hours return the same response with an Idempotent-Replay indicator And PATCH supports rescheduling: the prior code is revoked and a new code is issued within 5 seconds; lifecycle events emitted include code.created, code.updated, code.revoked, code.expired, and code.used with correlationId, workOrderId, deviceId, and ISO 8601 UTC timestamps And DELETE cancels and revokes the code with 204 and emits code.revoked And the API is rate-limited to 60 requests per minute per account; exceeding the limit returns HTTP 429
Vendor Identity Verification Gate
"As a property manager, I want the technician’s identity verified before a code is released so that only authorized vendors gain entry."
Description

Require verification of the assigned vendor/technician before revealing any access code. Support multi-factor checks: login to assigned work order, OTP to registered phone, and optional document/selfie verification via third-party provider for higher assurance jobs. Enforce attempt limits, session timeouts, and device fingerprinting to reduce fraud. Allow role-based manual overrides with reason capture and audit logging. Store only necessary verification artifacts with configurable retention and capture explicit consent where applicable to meet privacy obligations. Block code display/share until verification passes.

Acceptance Criteria
Gate Access Code Behind Multi-Factor Verification
Given an assigned vendor is logged in and opens the "View Access Code" action When the vendor has not completed the required verification for this work order access event Then the system requires the configured verification steps (login + OTP; and document/selfie if high-assurance) before revealing any access code And the access code value is absent from all API responses, UI elements, and logs until verification passes And a user who is not the assigned vendor attempting the action receives a 403 and no code is revealed And on successful verification, the single-use code and its validity window (start/end timestamps) are displayed And the system records an audit entry with work order ID, vendor ID, verification methods completed, timestamp, IP, and device fingerprint hash
OTP Second Factor to Registered Phone
Given the assigned vendor has a verified phone number on their profile When the vendor requests an OTP during verification Then the system sends a 6-digit OTP to the registered phone and masks the destination on-screen (e.g., ***-***-1234) And the OTP is single-use and valid for 5 minutes And resends are limited to 3 with a minimum 30 seconds between resends And OTP entry attempts are limited to 3 per session and 10 per 24 hours; exceeding either triggers a 30-minute lockout and creates an audit event And on correct OTP entry, second-factor verification is marked complete
High-Assurance Document/Selfie Verification
Given the work order is marked as high-assurance When the vendor initiates verification Then the system invokes the configured provider's document + liveness check And code release requires provider decision = "Approved" with face match score >= 0.85 and ID not expired And the vendor profile name must fuzzy-match the document name at similarity >= 0.85; otherwise require manual review or deny And on completion, only the provider decision, confidence score, provider transaction ID, and timestamp are stored; raw images/video are not stored unless the organization's retention policy explicitly enables it And failures, timeouts (> 3 minutes), or declines block code display and notify the work order owner
Session Timeout and Re-Verification Rules
Given a vendor is in the verification flow When there is 2 minutes of inactivity or 10 minutes elapse since start Then the verification session expires and the vendor must restart verification to view the code And after successful verification, code visibility requires an active session; navigating away or 5 minutes of inactivity hides the code and requires re-verification to reveal it again And the code value is never stored in local or session storage and is redacted from client-side error logs
Device Fingerprinting and Anomaly Controls
Given a verification attempt is initiated When the system captures and hashes a device fingerprint (e.g., user-agent, OS, IP, canvas) Then the fingerprint hash is associated with the verification attempt in the audit log And if the fingerprint differs from the last verified device for this work order and vendor, step-up verification is required (OTP again; or document/selfie if high-assurance) And a vendor is limited to 2 unique device fingerprints per work order in a 24-hour period; exceeding this blocks verification for 30 minutes and alerts the work order owner And 5 or more distinct device fingerprints attempting verification on the same work order within 15 minutes triggers an automatic 30-minute lockout of verification
Role-Based Manual Override with Audit Trail
Given a user attempts a manual override When the user has role "Property Manager" or "Ops Admin" and enters a reason of at least 10 characters and selects a reason category Then the system allows a one-time code reveal window of 15 minutes for the assigned vendor and records an override audit entry And users without eligible roles cannot access override controls And the audit entry includes user ID, role, work order ID, vendor ID, reason text, category, timestamp, IP, and device fingerprint hash and marks the event as override=true And notifications are sent to the property team channel and the work order owner on override
Consent, Privacy, and Retention Controls
Given any verification step that processes personal data is about to begin When the vendor starts the verification Then the system presents localized consent text (purpose, provider, data elements, retention) and requires an explicit checkbox before proceeding And the consent record stores timestamp, policy/version ID, locale, IP, and user agent and links to the verification attempt And organizations can configure retention periods of 30, 90, or 180 days (default 90) for verification artifacts; expired artifacts are purged within 24 hours by a daily job And stored artifacts are limited to minimal necessary: OTP send/verify metadata (not code), provider decision, confidence score, provider transaction ID, device fingerprint hash; raw documents/media are not stored by default And admins can export or delete a vendor's verification artifacts upon authorized request, with completion within 30 days unless legal holds apply
Access Logging & Audit Trail
"As a property manager, I want a complete audit trail of entry and exit events so that I can resolve disputes and meet compliance requirements."
Description

Capture immutable, tamper-evident logs of all access-related events: code creation, verification passed/failed, code display, lock granted/denied, door open/close (where available), and manual overrides. Ingest device webhooks and provide offline fallbacks (technician check-in/out with geo/time stamp) with later reconciliation. Link every event to property, unit, work order, vendor, and user. Provide search/filter/export, retention policies, and evidence-grade timestamps to support compliance, dispute resolution, and incident investigation.

Acceptance Criteria
Immutable Tamper‑Evident Access Event Logging
Given any service component writes an access-related event When the write is performed Then the event is appended to an append-only ledger with a SHA-256 content hash and previous-hash linkage and is digitally signed by the service key Given any user (including admins) attempts to edit or delete a persisted log entry via UI or API When the request is made Then the system rejects the mutation and records an attempted_tamper audit event with actorId, reason, and IP address Given periodic integrity verification runs on the log When a hash-chain break or signature mismatch is detected Then the affected range is flagged with integrity_status=failed and an alert is emitted to monitoring within 60 seconds
Capture of All Keyless Pass Event Types
Given any of the following occurs: code_created, identity_verification_pass, identity_verification_fail, code_displayed, lock_access_granted, lock_access_denied, door_open, door_close (if device-supported), manual_override When the event occurs Then a log entry is created with fields: propertyId, unitId, workOrderId, vendorId (if known), userId (if applicable), deviceId, eventType, outcome, event_time (UTC ISO 8601 ms), recorded_time (UTC ISO 8601 ms), source (webhook|api|mobile|manual), correlationId Given a device supports door sensor telemetry When a door open/close signal is emitted Then door_open/door_close events are captured within 2 seconds of webhook receipt Given a manual override is reported (on-device override or physical key) When reported via UI or API Then a manual_override event is logged with reporterId and optional evidence (photoId|noteId) and is linked to the related workOrderId Given an identity verification check is performed When it passes or fails Then identity_verification_pass or identity_verification_fail is logged before any code_displayed event for the same visit
Device Webhook Ingestion with Idempotency and Ordering
Given a vendor webhook with a unique eventId is received When duplicate deliveries occur Then only one persisted log entry exists and duplicates are acknowledged with HTTP 200 Given a vendor-signed webhook is received When the signature or timestamp validation fails Then the payload is rejected with HTTP 400 and a verification_failed event is logged Given multiple events for the same device arrive out of order When they are processed Then event_time is taken from the device payload and ordering in timelines uses (deviceId, event_time, vendorSequence) to render a correct sequence Given an ingestion spike up to 100 RPS per tenant When webhooks are received Then 99th percentile acknowledgment latency is under 2 seconds and no events are dropped (buffered to durable queue)
Offline Technician Check‑In/Out with Geo/Time and Reconciliation
Given the lock device is unreachable or no webhook has been received When a technician performs mobile check-in Then an offline_check_in event is created with GPS coordinates within 100 meters of the property geofence, event_time from device clock, recorded_time from server, and optional photo proof Given a technician ends the visit When they perform mobile check-out Then an offline_check_out event is created with the same linkage fields and GPS within 100 meters Given device webhooks later arrive covering the same time window When reconciliation runs (within 15 minutes) Then offline and device events are linked via correlationId, reconciled=true is set, and durations are recalculated from reconciled events Given discrepancies greater than 5 minutes between offline and device times or GPS distance greater than 150 meters When reconciliation runs Then a reconciliation_discrepancy event is logged and the visit is flagged for review
Search, Filter, and Export of Audit Logs
Given a user with audit_view permission When they filter by propertyId, unitId, workOrderId, vendorId, userId, eventType, outcome, and date range Then the system returns matching results with pagination and sorting within 3 seconds for up to 50,000 events Given an export is requested for any valid filter set When the user selects CSV or JSON Then an export is generated with all visible fields plus a manifest containing SHA-256 checksums and a signed export metadata file and is available within 60 seconds for up to 200,000 events Given access control policies hide entities the user cannot view When a search or export is performed Then events linked to unauthorized entities are excluded consistently from results and exports
Retention Policies and Evidence‑Grade Timestamps
Given any log entry is created When timestamps are stored Then both event_time and recorded_time are persisted in UTC ISO 8601 with millisecond precision and include timezone Z; clock_drift_ms is recorded and a clock_drift_alert is emitted if drift exceeds 500 ms Given an organization sets a retention policy of N years When lifecycle management runs nightly Then events older than N years are moved to WORM storage and only purged after retention expiry; each purge writes an audit_purge event with count and date range Given a legal hold is applied to a property, unit, or work order When lifecycle management runs Then all related events are exempt from purge until the hold is removed and holds are auditable
Scheduling Sync & Dynamic Access Windows
"As a maintenance coordinator, I want access windows to update automatically with schedule changes so that technicians aren’t locked out and tenants aren’t surprised."
Description

Integrate with FixFlow work orders and calendars to automatically create, adjust, or cancel access windows when appointments are scheduled, rescheduled, or cancelled. Support multi-visit jobs, overlapping appointments, travel buffers, and emergency overrides. Enforce dependencies such as required tenant approval before activation. Ensure vendor time zone handling, edge cases for daylight savings, and automatic notifications and code regeneration when times change.

Acceptance Criteria
Auto-Create Access Window on New Appointment
Given a new FixFlow appointment is created for a work order on a property with a supported smart lock or lockbox And pre- and post-visit travel buffers are configured at the portfolio or property level When the appointment is saved to the FixFlow calendar Then the system creates a single access window from (appointment start minus pre-buffer) to (appointment end plus post-buffer) in the property’s local time And generates a unique, single-use access code scoped to that window and the assigned vendor And holds code release until all prerequisite checks pass (e.g., vendor identity verification and configured dependencies) And sends creation notifications to the vendor and designated stakeholders with window start and end in their local time And writes an audit entry with appointment ID, window times in UTC, code ID masked, and actor
Reschedule Adjusts Window and Regenerates Code
Given an existing appointment has an active or upcoming access window When the appointment time is rescheduled Then the access window updates to the new start and end times including buffers And any previously issued access code is revoked immediately And a new single-use code is generated and linked to the updated window And notifications are sent to the vendor and stakeholders describing the time change and new code issue time And the audit log records the reschedule, old and new times, revocation timestamp, and new code ID masked
Cancellation Auto-Revokes Access and Notifies
Given an appointment with a pending or active access window exists When the appointment is cancelled in FixFlow Then the access window is cancelled and the associated code is revoked within 60 seconds And the lock or lockbox denies entry using the revoked code from that point forward And notifications are sent to the vendor and stakeholders confirming cancellation and revocation And an audit entry captures the cancellation, revocation timestamp, and notifying recipients
Multi-Visit Jobs and Overlapping Appointments Handling
Given a work order requires multiple visits or multiple appointments are booked on the same lock within overlapping times When appointments are created or updated Then the system creates distinct access windows and unique single-use codes per appointment And overlapping windows on the same lock do not reuse codes and are each enforceable only within their own window And if two different vendors are scheduled to overlap on the same lock, the system flags the overlap to the manager and preserves vendor-scoped codes And travel buffers are applied per appointment without merging windows And all actions are logged with appointment IDs, window times, and code IDs masked
Tenant Approval Dependency Enforcement
Given tenant approval is required for access activation for the property And an appointment exists with an access window created When tenant approval is pending or denied at the window start Then the code remains unreleased and access is blocked, showing the vendor a reason of Awaiting tenant approval or Approval denied And when tenant approval is granted before or during the window, the code is released immediately and is valid only for the remaining window duration And if approval is not obtained by a configurable lead time before start, reminders are sent to the tenant and manager And all approval state changes and code release timing are logged
Time Zone and Daylight Saving Handling
Given the property time zone may differ from the vendor’s time zone and an appointment spans or occurs near a daylight saving time transition When the appointment is scheduled, rescheduled, or falls on a DST change date Then access windows are calculated and enforced in the property’s local time with buffers applied correctly And all notifications display times localized to each recipient’s time zone while storing UTC with offset in the audit log And on fall-back transitions, the window duration is preserved without duplicating the repeated hour And on spring-forward transitions, the window accounts for the missing hour without shortening below the scheduled duration And vendor mobile clients display a consistent countdown and local start and end that match server calculations
Emergency Override Immediate Access
Given an authorized manager marks a work order as Emergency and invokes an access override When the override is confirmed Then the system creates an immediate access window starting now for a configurable duration and generates an active single-use code And tenant approval and other non-safety dependencies are bypassed while vendor identity verification remains enforced And notifications are sent to the tenant and stakeholders indicating emergency access with reason and time bounds And the code auto-revokes at the window end and all actions are auditable with actor, reason, and timestamps
Stakeholder Notifications & Consent
"As a tenant, I want to be notified and give consent before someone enters my home so that I feel informed and secure."
Description

Provide configurable SMS/email/push notifications for tenants, vendors, and managers at key moments: upcoming visit, code activation, vendor arrival, and exit. Include message templates, localization, quiet hours, and escalation rules. Support tenant consent capture for non-emergency entries and block code activation until consent or manager override is recorded. Log all communications to the work order timeline for transparency.

Acceptance Criteria
Upcoming Visit Notification with Quiet Hours and Localization
Given a visit is scheduled and each recipient has channel preferences and a language setting And quiet hours are configured for each recipient When the system schedules the "upcoming visit" notification Then it selects the template for type "upcoming visit" in the recipient’s language and validates required placeholders And it schedules send time outside the recipient’s quiet hours (deferring to the next available minute if necessary) And it sends via each enabled channel (SMS/email/push) and records a scheduled message entry When the message is sent Then the delivery outcome per channel (Queued/Sent/Delivered/Failed) is captured and logged to the work order timeline with timestamps
Message Templates & Localization Management
Given default templates exist for notification types (upcoming visit, code activation, arrival, exit, consent request, escalation) in supported languages When a manager edits or creates a template Then the system validates that required placeholders for that type are present and no unknown placeholders remain And it provides per-channel and per-language preview before saving And saving creates a new version capturing editor, timestamp, and change notes When a template version is set to Active Then all new messages of that type use the active version and historical sends retain their original content and version linkage
Tenant Consent Capture for Non-Emergency Entry
Given a work order is marked Non-Emergency and requires tenant consent and identifies required consent parties When the system sends a consent request Then tenants can respond via secure web link (Approve/Decline) or SMS keywords (YES/NO) in their language And the system records decision, actor identity, timestamp, channel, and source IP/user-agent (for web) When all required consent parties have Approved before the deadline Then the work order consent status becomes Approved and a confirmation notification is sent to tenant(s) and manager When any required party Declines or the deadline passes without full approval Then consent status becomes Declined/Expired, code activation is blocked, and the manager is notified
Code Activation Gate on Consent or Manager Override
Given a work order is Non-Emergency and consent status is not Approved When a vendor attempts to activate or retrieve a code within the scheduled window Then the system denies activation, presents the reason, and notifies the manager of the block When a manager records an override with reason code and notes Then the system allows activation and logs the override with timestamp and actor When a work order is marked Emergency Then code activation proceeds without consent, and tenants and manager are notified of the upcoming entry per configured channels
Escalation for Non-Delivery and No-Response
Given a consent request has been initiated with an SLA deadline and escalation rules configured When initial delivery fails on a channel Then the system retries per channel policy and falls back to alternate channels if available, logging all attempts When no consent response is received within the configured interval before SLA Then the system sends up to N reminders at M-minute intervals, respecting recipient quiet hours And it notifies the manager via push/email/SMS per escalation rules When the SLA expires without approval Then the system marks consent as Escalated/Expired, blocks code activation, and logs the escalation outcome to the timeline
Arrival and Exit Notifications with Auto-Revocation
Given a single-use access code is activated for a scheduled visit window When first unlock is detected for the code or the vendor checks in via the app Then the system sends arrival notifications to tenant(s) and manager and logs an Entry event with timestamp and lock identifier When the vendor checks out via the app or the system detects lock relock with no further activity for T minutes Then the system sends exit notifications, logs an Exit event with timestamp, and auto-revokes the code When the visit window ends without an exit event Then the system auto-revokes any active code and notifies the manager of the forced revocation
Communication Logging and Audit Trail
Given any notification is sent or received or any consent/override action occurs Then the work order timeline records an immutable entry containing: event type, recipient(s), channel(s), template ID and version, content hash or snippet, delivery outcome, external message IDs, actor/system, and timestamps And entries are filterable by event type and channel and exportable as CSV/JSON And viewing the timeline shows localized message previews where permitted and links to consent artifacts (decision details and audit metadata)
Failover & Security Controls
"As a property manager, I want secure fallbacks when devices or networks fail so that jobs can proceed without compromising safety."
Description

Deliver robust safeguards and continuity: device health monitoring, anomaly detection (excess failed attempts, unexpected access times), and automated suspension of code issuance on compromised devices. Provide controlled emergency overrides and backup access options (secondary lockbox or building key desk) with strict auditing. Enforce encryption at rest/in transit, key management via KMS, rate limiting, and RBAC restricting who can view or share codes. Define SLAs/SLOs, alerting, and runbooks for operational incidents to maintain security without disrupting scheduled work.

Acceptance Criteria
Auto-Suspend Code Issuance on Compromised Device
Given a supported lock or lockbox misses 5 consecutive 60-second health heartbeats or reports a tamper/integrity failure When any user attempts to issue a new access code for that device Then the system blocks issuance, displays "Device Suspended," records an audit event with device_id, reason, actor_id, and timestamps, and notifies on-call within 2 minutes. Given a device is in Suspended state When an admin with RBAC role "Security Admin" marks it healthy after verification and a successful heartbeat is received Then code issuance is re-enabled and the transition is logged with before/after state. Given a suspended device with future appointments When the suspension is applied Then affected work orders are auto-flagged and a fallback access workflow is triggered within 5 minutes.
Anomaly Detection for Access Attempts
Given a valid appointment window When the system observes 5 failed code entry attempts within 10 minutes for a single device or an access attempt occurs more than 15 minutes outside the scheduled window Then an anomaly alert is created, the active code is temporarily locked for 15 minutes, the event is logged, and the property manager is notified by email and push. Given repeated anomalies (3 or more within 24 hours) on the same device When the third anomaly is recorded Then new code issuance is automatically suspended for that device and the device state is set to "Under Investigation".
Emergency Override with Dual Control and Audit
Given a scheduled appointment within the next 2 hours and the primary device is unavailable When a supervisor requests an emergency override Then dual authorization from two distinct RBAC roles ("Ops Supervisor" and "Security Admin") is required before issuing a single-use code valid for 60 minutes or less. Given an emergency override code is issued When the vendor requests the code Then the system verifies vendor identity against the assigned work order (matching vendor_id) and completes OTP verification via verified phone or email before release. Given an emergency override occurs When it is executed Then a complete, immutable audit record (WORM) capturing requester_ids, approver_ids, device_id, vendor_id, justification, timestamps, and IPs is stored with 365-day retention. Given an emergency override is completed When 24 hours elapse Then the system checks for a post-incident review; if missing, it escalates to the operations channel.
Backup Access via Secondary Lockbox or Key Desk
Given the primary device is offline or suspended When a backup access pathway is available (secondary lockbox or key desk) Then the system issues a time-bound, single-use backup code or digital authorization within 10 minutes and links it to the original appointment. Given a backup access is issued When the vendor arrives Then entry and exit are logged with accurate timestamps and correlated to the work order. Given backup access is used When the window expires or exit is recorded Then access is auto-revoked and no further entries are permitted. Given backup access is used When auditing is requested Then all actions are exportable as a signed CSV or JSON with checksums.
Encryption, KMS, and RBAC for Code Visibility
Given any storage of access codes or secrets When data is written at rest Then it is encrypted with AES-256 using cloud KMS-managed keys with automatic rotation at least every 90 days and access to keys is restricted by IAM. Given any transmission of codes or vendor PII When data is sent Then TLS 1.2+ is enforced with HSTS and TLSv1.0/1.1 disabled. Given a user attempts to view or share an access code When the user lacks an authorized RBAC role ("Property Manager", "Dispatcher", or "Assigned Technician" within validity window) Then access is denied with HTTP 403 and the attempt is logged. Given an access code is outside its validity window When any user views the work order Then the code is masked (last 2 digits only) and not copyable. Given access codes are displayed When a user tries to export codes in bulk Then export is blocked unless the user has "Security Admin" role and an access justification is entered.
Operational SLAs, Alerting, and Runbooks
Given normal operations When measured monthly Then code issuance API availability is 99.9% or higher and anomaly detection processing latency P95 is 60 seconds or less. Given an SLO breach or critical incident (device compromised or issuance suspended affecting more than 5 appointments) When detected Then on-call is paged within 5 minutes, an incident is created with a linked runbook, and customer communications are sent within 30 minutes. Given an incident is resolved When 5 business days have elapsed Then a postmortem with action items is published and tracked to closure.
Rate Limiting Without Disrupting Scheduled Work
Given a single user account When requesting code issuance or viewing operations via API or UI Then rate limiting is enforced at 10 requests per minute per user and 100 requests per minute per IP with 429 responses and a Retry-After header. Given automated service-to-service calls from FixFlow integrations When requests are made with a service account Then higher limits apply (up to 600 requests per minute) with circuit breakers to avoid cascading failures. Given rate limits are reached When a technician attempts to validate an already issued code at the lock Then validation and entry logging are not rate limited and continue to function.

After‑Hours Guardrails

Enforces clear, configurable off‑hours rules before any dispatch. Checks severity, building impact, shutoff status, and required remote steps, then auto-routes non‑critical issues to virtual fix first. Ensures consistent decisions across teams, slashes unnecessary callouts, and protects margins without sacrificing safety.

Requirements

Configurable Off‑Hours Policy Engine
"As a property manager, I want configurable off‑hours rules per property and portfolio so that dispatch decisions are consistent, safe, and margin‑protecting outside business hours."
Description

Implements a central, property-scoped rules engine to define off‑hours windows, time zones, and observed holidays, and to apply guardrail logic before any technician dispatch. Rules support conditions such as issue category, severity threshold, building impact, utility shutoff status, tenant vulnerability flags, and vendor availability. Actions include block dispatch, require remote troubleshooting, route to virtual fix, request supervisor approval, or escalate to emergency. Provides versioning, per‑portfolio overrides, safe defaults, and simulation mode to preview outcomes without affecting live workflows. Integrates with FixFlow intake, approvals, scheduling, and tenant communications to ensure consistent, automated decisions that reduce unnecessary callouts while maintaining safety.

Acceptance Criteria
Off-Hours Window Evaluation Across Time Zones and Observed Holidays
Given a property configured with time zone "America/Chicago", off-hours window 18:00–08:00 local time, and observed US federal holidays When an issue is submitted at 19:30 local on a non-holiday weekday Then the engine marks is_off_hours=true and decision_reason includes "within_off_hours_window" Given the same configuration When an issue is submitted at 10:00 local on a non-holiday weekday Then the engine marks is_off_hours=false Given the same configuration When an issue is submitted at 10:00 local on an observed holiday Then the engine marks is_off_hours=true and decision_reason includes "observed_holiday" Given any evaluation When the decision is computed Then the decision context includes normalized_local_time, is_holiday, off_hours_window_evaluated=true, and is persisted to the intake record
Severity-Based Dispatch Block With Auto-Route to Virtual Fix
Given is_off_hours=true and policy.severity_threshold=3 and issue.severity=2 and issue.category="appliance" and building_impact=false and utility_shutoff_status="not_required" and tenant_vulnerability=false and vendor_available=true When guardrail evaluation runs before dispatch Then action="block_dispatch" and next_step="require_remote_troubleshooting" and route="virtual_fix" and no work order is scheduled And tenant receives automated remote-instructions via the tenant communication channel within 2 minutes And no approval request is created And an audit log entry is recorded with rule_id, inputs, and outcome Given is_off_hours=true and policy.severity_threshold=3 and issue.severity=3 and issue.category="appliance" When guardrail evaluation runs Then action="request_supervisor_approval" and route is pending until approval, with SLA_decision_minutes<=10 Given is_off_hours=true and issue.severity=4 and issue.category in ["gas","electrical","water_leak"] When guardrail evaluation runs Then action="escalate_to_emergency" and on-call vendor is paged immediately and tenant is sent safety instructions within 2 minutes
Building-Wide Impact With Utility Shutoff Escalates to Emergency
Given is_off_hours=true and issue.category="water_leak" and building_impact=true and utility_shutoff_status in ["cannot_shut_off","unknown"] When guardrail evaluation runs Then action="escalate_to_emergency" and an on-call technician is scheduled within 15 minutes and a building-wide broadcast is sent to affected tenants immediately And all decisions are recorded in the audit trail with timestamps Given is_off_hours=true and issue.category="water_leak" and building_impact=true and utility_shutoff_status="can_shut_off" and tenant_has_not_attempted_shutoff=true When guardrail evaluation runs Then action="require_remote_troubleshooting" with step_list including main_valve_shutoff and water_damage_mitigation and dispatch is blocked until confirmation_received=true or 10 minutes elapse, after which action auto-upgrades to "escalate_to_emergency"
Tenant Vulnerability Flag Overrides to Require Live Dispatch
Given is_off_hours=true and tenant_vulnerability=true and issue.severity in [1,2] and building_impact=false When guardrail evaluation runs Then action="request_supervisor_approval" with urgency="high" and SLA_decision_minutes<=10 and virtual_fix is not enforced And if supervisor_approved=true within SLA then a live dispatch is scheduled within 30 minutes And if no supervisor response within SLA then auto_approve=true and a live dispatch is scheduled within 30 minutes And tenant is notified of the decision and ETA via the communication channel used at intake
Vendor Unavailable After-Hours Triggers Supervisor Approval
Given is_off_hours=true and issue.severity=3 and requires_in_person=true and preferred_vendor.available=false and fallback_vendor.available=true When guardrail evaluation runs Then action="request_supervisor_approval" with proposed_assignment=fallback_vendor and no dispatch is created until approved And if supervisor_approved=true then schedule=fallback_vendor with arrival_window<=2 hours and tenant confirmation message is sent And if supervisor_rejected=true then action="defer_to_business_hours" and tenant is notified with clear expectations and safety steps And audit log captures vendor availability snapshot and the approval decision
Simulation Mode Predicts Outcome Without Affecting Live Workflow
Given simulation_mode=true and policy_version="2.1" and a representative intake event is evaluated When guardrail evaluation runs Then a decision_preview with action, route, and rule_trace is produced within 1 second And no approvals are created, no schedules are altered, and no tenant communications are sent And an audit entry of type="simulation" with inputs and preview_outcome is recorded Given simulation_mode=false for the same inputs When guardrail evaluation runs Then the live outcome matches the prior decision_preview (action and route) for the same policy_version
Versioned Policy With Per-Portfolio Overrides and Safe Defaults
Given global_policy.version="2.1" is active and Portfolio_A overrides {severity_threshold:3, holiday_calendar:"US"} and Property_P belongs to Portfolio_A with no property-specific overrides When guardrail evaluation runs for Property_P Then effective_policy is the merge of Portfolio_A overrides with global defaults and effective_version_id is recorded on the intake record Given a new global_policy.version="2.2" is saved as status="draft" When evaluations run Then live decisions continue to use version "2.1" while simulations may target "2.2" Given version "2.2" is activated at 18:00 UTC When an intake created at 17:59 UTC awaits decision Then it is decided under version "2.1" and is not retroactively re-evaluated Given an operator performs a rollback to version "2.1" When subsequent evaluations run Then effective_version_id reflects "2.1" and the change is present in the audit trail with actor_id and timestamp
Severity Scoring & Safety Gate
"As an on‑call coordinator, I want clear severity tiers with an enforced safety gate so that true emergencies are dispatched immediately and non‑critical issues are deferred safely."
Description

Adds a deterministic severity scoring model that classifies incoming after‑hours issues into life‑safety, urgent, or deferrable tiers using structured intake fields, photo/video evidence, and issue taxonomy. Enforces a non‑bypassable safety gate: life‑safety and building‑critical events are always escalated, while lower tiers are routed to virtual fix or queued for business hours per policy. Provides transparent criteria, tunable thresholds, and audit visibility for every decision. Integrates with triage UI, approvals, and notifications to minimize risk while avoiding unnecessary dispatch.

Acceptance Criteria
Deterministic Severity Classification for After‑Hours Intake
Given an after-hours intake is submitted with completed required fields, selected issue taxonomy, and required media where applicable When the severity engine processes the submission Then it assigns a numeric severity score and maps it deterministically to one of: Life‑Safety, Urgent, or Deferrable And repeating the same input payload produces the same score and tier And missing required fields or media blocks scoring and returns explicit validation errors And unknown taxonomy values are mapped to a configured default with a documented rule in the decision log
Non‑Bypassable Safety Gate for Life‑Safety and Building‑Critical Events
Given an intake matches any configured life‑safety or building‑critical rule or scores within the Life‑Safety threshold When the case is created after-hours Then the system auto‑escalates to the on‑call workflow and shows emergency guidance to the tenant And downgrade, defer, or cancel actions are disabled for all roles until the event is resolved And initial on‑call notification is sent within 60 seconds; if unacknowledged in 2 minutes, it auto‑escalates to the next contact per schedule And all bypass attempts are blocked and written to the audit log with user, timestamp, and reason
Auto‑Route Non‑Critical Issues to Virtual Fix and Business‑Hours Queue
Given an after-hours intake scores as Deferrable per current thresholds When the case is created Then the system suppresses technician dispatch options and starts the virtual fix flow for the tenant And the case is placed into the Business Hours queue with a target review window per policy And the tenant receives step‑by‑step guidance via SMS/app within 60 seconds and can upload follow‑up media And if the tenant reports unresolved or new risk indicators, the system re‑scores immediately and escalates if thresholds are crossed
Tunable Severity Thresholds with Versioning and Guardrails
Given an administrator updates severity thresholds or rule weights When changes are saved Then the system versions the configuration with editor, timestamp, and change summary And changes apply only to new intakes; existing cases retain the prior decision unless explicitly re‑scored by a permitted user And a preview simulator shows the impact on at least the last 100 after‑hours cases before publishing And guardrails prevent configurations that could classify enumerated life‑safety indicators below Life‑Safety
Decision Transparency in Triage UI and Notifications
Given any scored after-hours case is opened in the triage UI When the decision details panel is viewed Then it shows the tier, numeric score, matched rules, key factors with weights, required media references, and the policy that determined routing And a human‑readable “Why this decision” explanation is displayed and included in notifications sent to staff And decision details can be exported as JSON and PDF from the UI
Approval and Notification Flows by Severity Tier
Given a case is classified as Life‑Safety, Urgent, or Deferrable after-hours When routing occurs Then Life‑Safety: on‑call receives SMS, push, and email; acknowledgment is required; secondary escalation triggers if no ack in 2 minutes And Urgent: approval request is sent to the designated approver; dispatch is blocked until approved or SLA timeout triggers fallback per policy And Deferrable: tenant receives confirmation of business‑hours handling and virtual fix steps; staff receive a digest notification only
Comprehensive Audit Trail and Export
Given any after-hours severity decision is made When viewing the audit log Then each case includes an immutable record of input snapshot, media hashes, score values, matched rules, tier, thresholds version, routing, notifications, user actions, and timestamps And logs are filterable by date range, property, tier, and decision outcome and exportable as CSV and JSON And audit records are retained for at least 24 months
Utility Shutoff & Building Impact Verification
"As a tenant triage agent, I want built‑in checks for utility shutoff and building impact so that I can prevent avoidable damage and correctly escalate true building‑wide incidents."
Description

Introduces mandatory verification steps during after‑hours intake to confirm whether tenants have performed safe utility shutoffs (water, gas, power) and whether the issue is unit‑level or building‑wide. Provides property‑specific shutoff instructions, captures confirmation media, and blocks dispatch until required steps are completed or a safety exception applies. Aggregates concurrent reports to detect building‑level incidents and escalates accordingly. Logs all confirmations for compliance and feeds results into severity scoring and routing decisions.

Acceptance Criteria
After‑Hours Water Leak: Shutoff Verification and Dispatch Block
Given an after-hours intake for a water leak at a property with stored water shutoff instructions When the tenant selects "Water leak" during triage Then the system displays the property-specific shutoff instructions within 2 seconds And the "Dispatch" action is disabled for this case And the tenant must either upload photo or video evidence showing the shutoff valve in the closed position and check "Water supply is shut off" OR request a safety exception And uploaded media must be captured within the last 10 minutes, be jpg/png/mp4, and meet minimum resolution (photo ≥1024x768, video ≥720p) and max size ≤25 MB And upon passing validation, shutoff_status is set to "confirmed" and the dispatch block is lifted And if the tenant selects "Cannot shut off safely," the system routes to safety exception review, records the reason, and keeps dispatch blocked until exception approval
Gas Odor Report: Safety Exception Path
Given an after-hours intake categorized as "Gas odor" or "Gas leak" When triage begins Then the system immediately displays gas safety instructions and requires tenant acknowledgment And safety_exception is set to true with reason "gas hazard" and acknowledgment timestamp recorded And emergency dispatch is enabled without requiring media confirmation And case severity is set to "Critical" and routing set to "Emergency dispatch" And on-call technician and property manager are notified within 60 seconds via configured channels And all actions are recorded in the audit log
Power Outage: Unit vs Building Impact Detection
Given after-hours intakes reporting "Power outage" for the same property When three or more distinct units submit matching outage reports within a 15-minute rolling window Then the system sets building_impact to true and creates a single building-level incident record And unit-level dispatches are suppressed and linked to the building incident And the building incident is escalated to the emergency queue with appropriate notifications And if only one unit reports an outage, impact remains unit-level and breaker check steps are presented, with dispatch blocked until steps are completed or a safety exception applies
Media Evidence Capture and Validation (All Utilities)
Given an after-hours case requiring utility shutoff verification for water, gas, or power When the tenant uploads confirmation media Then the system validates file type (jpg, png, mp4), size (≤25 MB), and minimum resolution (photos ≥1024px width; videos ≥720p) and that capture time is within the last 10 minutes (EXIF or server timestamp) And invalid submissions show explicit inline error messages and cannot be submitted And valid media is stored with a SHA-256 hash, timestamp, uploader ID, and linked to the case And the confirmation checkbox must be selected before the flow can advance
Severity Scoring and Routing Based on Shutoff and Impact
Given a triaged after-hours case with fields shutoff_status and building_impact When the severity scoring engine runs Then if shutoff_status = "confirmed" AND building_impact = false AND no hazard flags are present, severity_score decreases by ≥30% and route = "Virtual fix" And if shutoff_status ≠ "confirmed" OR building_impact = true OR hazard flags present, severity_score stays the same or increases, and dispatch eligibility = "Emergency only" And the score inputs, deltas, and resulting route are recorded in the audit log
Compliance Audit Log and Export
Given any after-hours case using the shutoff verification flow When the flow is completed or bypassed via safety exception Then the audit trail records: tenant/user IDs, timestamps per step, instruction version, media filenames and SHA-256 hashes, safety exception reason, approver ID, severity score before/after, and routing decision And audit entries are immutable and visible only to roles Admin and Owner And logs can be exported to CSV and PDF on demand, with generation completing within 10 seconds for cases ≤200 MB of attachments And logs are retained for 24 months and then archived per retention settings
Remote Troubleshooting Checklist Enforcement
"As a support specialist, I want enforced, guided troubleshooting steps with proof of completion so that I can resolve deferrable issues remotely and avoid unnecessary after‑hours dispatches."
Description

Delivers dynamic, issue‑type‑specific remote troubleshooting checklists that must be completed before after‑hours dispatch. Checklists include guided steps, conditional branching, media capture (photos/videos), and device/platform‑agnostic instructions. Supports time‑boxed exceptions for vulnerable tenants and provides one‑tap escalation when safety criteria are met. Results sync to the work order, influence routing, and are visible to technicians to reduce duplicate effort. Ensures consistent pre‑dispatch diligence that cuts unnecessary callouts while preserving safety.

Acceptance Criteria
After-Hours Dispatch Blocked Until Checklist Completion
Given it is after-hours and a new maintenance request requires remote triage When an agent attempts to dispatch a technician Then the dispatch action is disabled until all required checklist steps for the issue type are marked complete or a permitted exception is applied And the UI displays the count of remaining required steps And any blocked dispatch attempt is logged with user ID, timestamp, and reason
Dynamic Branching per Issue Type
Given an issue type with conditional branches defined in the checklist When the agent answers branching questions Then only the steps relevant to the chosen branch are displayed and required in the correct order And required steps cannot be skipped without an approved exception And checklist progress and answers persist if the session is interrupted and resumed within 24 hours
Media Capture Required for Evidence Steps
Given a checklist step requires media evidence When the agent submits the step Then at least one compliant media file (jpg, png, mp4; max 100 MB) captured or uploaded during the session is required And images must be at least 640x480 and videos between 2 and 60 seconds And web, iOS, and Android clients allow capture and upload with preview before submit And the media is attached to the work order with uploader, timestamp, and step reference
One-Tap Safety Escalation Bypass
Given defined safety criteria are met during triage (e.g., gas smell, active flooding, building-wide outage, life-safety risk) When the agent taps Escalate for Safety Then the checklist is bypassed and dispatch is enabled immediately And the work order is tagged Safety Escalation with the selected reason, user ID, and timestamp And high-priority notifications are sent to the on-call contact list within 60 seconds
Time-Boxed Exception for Vulnerable Tenants
Given a tenant is flagged vulnerable and policy allows time-boxed exceptions When the agent applies a time-boxed exception Then dispatch is permitted for the configured window (e.g., 30–120 minutes) without completing all required steps And upon expiry the system relocks dispatch until required steps are completed or a new exception is granted And the exception requires entry of reason and is recorded in the audit log with start and end times
Sync to Work Order and Technician Visibility
Given a work order is associated with the triage session When the checklist is completed or bypassed Then all answers, selected branches, media, timestamps, and agent identity are saved to the work order within 5 seconds And assigned technicians can view the full checklist results in their mobile app before accepting the job And technician-facing workflows suppress duplicate questions already answered in triage
Routing Decisions Driven by Checklist Outcomes
Given routing must be determined after-hours When checklist outcomes indicate non-critical status and required remote steps are completed or a remote fix is achieved Then the system auto-routes the case to Virtual Fix First and schedules a follow-up message to the tenant within 15 minutes And when critical criteria are met, the system recommends immediate dispatch with the appropriate trade and priority And any agent override of the recommendation requires a justification and is logged with the rule that fired
Auto‑Route to Virtual Fix With SLA
"As a portfolio owner, I want non‑critical after‑hours issues auto‑routed to a virtual fix flow with clear SLAs so that tenants are supported promptly without costly night dispatches."
Description

Automatically routes non‑critical after‑hours tickets to a virtual fix queue with configurable response SLAs, callback windows, and escalation paths. Coordinates tenant communications (acknowledgements, next‑step instructions, appointment options) and reserves next‑day technician holds when needed. Integrates with vendor scheduling, notifications, and approval workflows to maintain continuity from triage to resolution. Monitors SLA adherence and auto‑escalates if targets are missed, ensuring tenant trust while protecting margins.

Acceptance Criteria
After-Hours Non-Critical Auto-Routing to Virtual Fix
Given a maintenance ticket is created outside configured business hours And severity is classified as non-critical by guardrail rules And there is no building-wide impact and no active utility shutoff status When triage completes Then the ticket is placed into the Virtual Fix queue And no field dispatch task is created And the routing decision and rule evaluations are logged to the audit trail
SLA Timer Initiation and Visibility on Virtual Fix Routing
Given a ticket is routed to the Virtual Fix queue When routing is completed Then the response SLA timer starts immediately using the configured SLA for the property/issue type And a due-by timestamp is calculated and displayed on the ticket And the SLA configuration version is recorded on the ticket for audit And an internal reminder is scheduled at the configured warning threshold
Tenant Acknowledgement and Instructions After Routing
Given a ticket is routed to Virtual Fix after hours When routing occurs Then the tenant receives an acknowledgement message within the configured acknowledgement window via their preferred channel And the message includes next-step self-help instructions and callback window options And message delivery status (sent, delivered, failed) is recorded; on failure, the system retries via the fallback channel
Callback Window Selection and Agent Assignment
Given a tenant selects a callback window from provided options When the selection is received Then a virtual appointment is created within the selected window And the ticket is assigned to an available agent matching skills and coverage And a confirmation is sent to the tenant with the appointment details
Next-Day Technician Hold Reservation from Virtual Triage
Given routing rules indicate a likely onsite visit may be required the next day And vendor scheduling integration is enabled When the ticket is routed to Virtual Fix after hours Then a provisional hold is placed on next-day technician time matching skill, geography, and building access constraints And the hold is auto-released if the issue is resolved virtually or at the configured expiration time And hold placement and release events are synced to the vendor calendar and logged
SLA Threshold Alerts and Breach Auto-Escalation
Given a ticket is in the Virtual Fix queue with an active SLA When remaining time reaches the configured warning threshold Then an internal alert is sent to the assigned agent/queue and the ticket is flagged as at-risk When the SLA due-by timestamp is reached without required contact or resolution Then the system executes the configured escalation path: notify on-call manager, increase ticket priority, schedule an urgent callback within the defined escalation window, and convert to field dispatch if escalation rules require
Approval Workflow Integration During Virtual Fix
Given an estimated cost exceeds the configured spend threshold When the estimate is generated during virtual triage Then an approval request is sent to the designated approver with its own SLA timer And the ticket is prevented from creating a dispatch until approval is granted And upon approval, any provisional technician hold is confirmed; upon rejection, the ticket follows the configured alternative path and the hold is released
Supervisor Override, Audit & Guardrail Analytics
"As an operations lead, I want controlled overrides with full audit and reporting so that we maintain safety and accountability while allowing informed exceptions to policy."
Description

Provides role‑based override controls for after‑hours rules with mandatory reason codes, evidence attachment, and time‑stamped audit trails. Generates analytics on avoided dispatches, override frequency, safety exceptions, time‑to‑decision, and cost savings by property and team. Flags risky patterns (e.g., excessive overrides, repeated rule hits) and supports export/sharing for compliance and stakeholder reporting. Ensures accountability while enabling human judgment when policies require exceptions.

Acceptance Criteria
Supervisor Override Requires Reason Code and Evidence
Given an after-hours ticket is governed by guardrails And the current time is within the configured after-hours window When a non-supervisor attempts to override a guardrail decision Then the override action is blocked And the user sees "Supervisor access required" with an option to request review When a supervisor initiates an override Then the UI requires selection of a reason code from the configured list And the UI requires at least one evidence attachment before submission And the Submit action is disabled until required fields are complete When the supervisor submits the override Then the system records an audit entry including: ticketId, propertyId, userId, userRole, rulesOverridden, reasonCode, optionalNote, evidenceIds, decisionOutcome, timestampUTC, timeToDecisionSeconds And the ticket reflects the new decision outcome And a confirmation displays within 2 seconds
Audit Trail Is Append-Only and Complete
Given an override audit entry exists When any user attempts to edit or delete that audit entry via UI or API Then the action is denied with an error indicating audit entries are immutable When a correction is required Then a new audit entry is created that references the prior entryId and describes the correction When viewing the ticket's Audit tab Then audit entries are shown in reverse chronological order and display: timestampUTC (ISO-8601), actor, role, action, reasonCode, rulesOverridden, evidence links, before/after decision And evidence links are accessible to authorized roles and return the original files When exporting the ticket audit as CSV or JSON for the same filters Then the exported entries exactly match the entries shown in the UI
Analytics Compute Avoided Dispatches, Overrides, Safety Exceptions
Given a date range and filters for properties and teams are selected When Guardrail Analytics are generated Then the output includes for each property and team: avoidedDispatchCount, overrideCount, overrideRatePercent, safetyExceptionCount, avgTimeToDecisionSeconds, p90TimeToDecisionSeconds, estimatedCostSavings And estimatedCostSavings is calculated as configuredAvgAfterHoursCalloutCost × avoidedDispatchCount And all counts match the underlying audit events within ±1% And totals equal the sum of listed rows And when no data exists for a segment, zeros are shown
Risk Pattern Detection and Notifications
Given risk thresholds are configured: excessiveOverridesPerUserPer24h = 3; repeatedRuleHitsPerPropertyPer7d = 2 When a user or property breaches a configured threshold Then the system creates a risk flag with: patternType, subject, threshold, actualValue, timeWindow, affectedTicketIds, firstSeen, lastSeen, severity And the flag appears on the Guardrail Analytics dashboard within 5 minutes And notifications are sent to configured recipients via in-app and email When a flag is acknowledged by an authorized user Then duplicate notifications for that flag are suppressed for 24 hours unless severity increases When exporting risk flags as CSV Then all visible flags and fields are included for the selected filters
Analytics and Audit Exports with Shareable Access
Given a user with analytics.view permission selects a date range and filters When the user exports Analytics Then CSV and PDF files are generated within 30 seconds containing per-property and per-team metrics, a filter summary, and generatedAtUTC And file totals match on-screen totals When the user exports Audit logs Then CSV and JSON files include audit entries with evidence file names and links And if "Share externally" is enabled, tenant PII fields (names, emails, phone numbers) are excluded When the user creates a shareable link Then a view-only URL is created with expiry options (1h, 24h, 7d) And access to the link requires email verification and is logged with timestamp and email
Accurate Time-to-Decision Capture and SLA Metrics
Given an after-hours ticket is created When the system auto-routes a decision or a supervisor submits an override Then timeToDecisionSeconds equals decisionTimestamp - ticketCreatedTimestamp And the value is stored on the corresponding audit entry And 95% of recorded times are accurate within ±1 second of system clocks When viewing Guardrail Analytics Then median and p90 time-to-decision are displayed by property and team for the selected range And tickets without overrides use the auto-route decision time for calculations

Rate Lock Enforcer

Applies your pre‑negotiated rate cards in real time. Auto-matches jobs to covered line items, flags out‑of‑policy surcharges, and requests vendor acceptance via magic link before wheels roll. Prevents bill shock, standardizes costs across vendors, and keeps approvals fast and confident.

Requirements

Rate Card Repository & Versioning
"As a property manager, I want a single, versioned place to manage all vendor rate cards so that the correct negotiated rates are consistently enforced across intake, dispatch, and billing."
Description

Centralized, version-controlled storage for vendor rate cards scoped by vendor, trade, geography, and effective dates, with structured fields for base rates (trip, hourly, minimums), overtime/after-hours multipliers, travel zones, materials markup, and property-specific overrides. Supports CSV import/export, schema validation, conflict detection on overlapping effective periods, and role-based editing with change history. Exposes APIs for retrieval at intake, triage, approval, and invoicing to ensure a single source of truth for pricing used throughout FixFlow. Enables future-dated rate updates, rollback to prior versions, and alignment to FixFlow’s standardized maintenance taxonomy for consistent line-item mapping.

Acceptance Criteria
Create and Store Scoped Vendor Rate Card (Version 1.0)
- Given a vendor, trade, geography, and non-overlapping effective start/end dates with required fields (trip_fee, hourly_rate, minimum_hours, overtime_multiplier, after_hours_multiplier, travel_zones, materials_markup) When the user saves the rate card Then a new version is created with version = 1 and status Active for its effective window. - Given required numeric fields When validated Then trip_fee, hourly_rate >= 0; minimum_hours >= 0; overtime_multiplier, after_hours_multiplier in [0.0, 10.0]; materials_markup in [0, 100] percent; invalid values block save with field-level errors. - Given line items mapped to FixFlow taxonomy IDs When saved Then all referenced taxonomy IDs exist; any missing/invalid taxonomy ID blocks save with a descriptive error. - Given optional property-specific overrides included in the payload When saved Then overrides are stored under the parent rate card with explicit property_ids and inherit unspecified fields from the parent.
Detect and Prevent Overlapping Effective Periods
- Given an existing rate card for the same vendor+trade+geography (and same property scope if applicable) When a new card is saved with an effective period that overlaps any existing period Then the save is rejected with HTTP 422 and error_code = RATE_CARD_DATE_CONFLICT and the response lists conflicting card IDs and date ranges. - Given adjacent periods that touch at a boundary (end_date of A == start_date of B) When saved Then the save succeeds and no conflict is reported. - Given property-specific overrides When saved Then overlaps are evaluated within the same property scope; overrides may overlap parent vendor+trade+geography periods but may not overlap other overrides for the same property.
CSV Import/Export with Schema Validation and Round-Trip Integrity
- Given a CSV uploaded with the approved header schema (v1) and valid rows When import runs Then the job completes with status Succeeded and creates/updates the specified rate cards and overrides. - Given a CSV with missing required columns or wrong data types When import runs Then the job ends with status Failed and returns a per-row error report including row_number, column, and message. - Given duplicate rows by natural key (vendor, trade, geography, property_id?, effective_start, effective_end) When import runs Then duplicates are rejected with error_code = DUPLICATE_ROW and no partial create occurs for those rows. - Given existing cards When export is requested with the same schema Then the CSV contains a faithful representation of all fields including version and effective dates. - Given an export file re-imported without modification When import runs Then zero changes are applied and the summary reports 0 created, 0 updated, 0 failed.
Role-Based Editing with Field-Level Change History
- Given a user with role RateCardEditor When they create or edit a rate card Then the action succeeds; users without RateCardEditor receive HTTP 403 Forbidden. - Given any create/update/delete When committed Then an immutable audit entry is recorded with actor_id, timestamp, action, entity_id, version, and before/after field diffs. - Given a user with role RateCardViewer or higher When they request change history for a card Then the full chronological history is returned and includes the reason_note if supplied by the editor.
Future-Dated Activation and One-Click Rollback
- Given a future-dated version (effective_start in the future) and a currently active version When the current time is before effective_start Then all API retrievals resolve to the current version; when time reaches effective_start Then retrievals resolve to the future-dated version without manual intervention. - Given an active version and a prior version When a user triggers Rollback to the prior version Then a new version is created that clones the prior version’s fields, is set Active immediately, and the superseded version is marked Superseded with linkage to the rollback event. - Given caches When a version activates or rollback occurs Then downstream caches/denormalized views are refreshed within 60 seconds and API responses reflect the change afterwards.
API Retrieval with Context-Aware Resolution Across Lifecycle Stages
- Given an API request at intake/triage/approval/invoicing with vendor_id, trade, property_id, geography, and timestamp When resolved Then the system applies precedence: property_override > vendor+trade+geography > vendor+trade > trade+geography > vendor default; if none found return HTTP 404 with error_code = RATE_CARD_NOT_FOUND. - Given a valid resolution When returned Then the payload includes version, effective_start/end, resolved scope, and taxonomy-mapped line items with computed base rates and multipliers applicable to the timestamp. - Given insufficient permissions When calling retrieval APIs Then return HTTP 403 Forbidden; unauthenticated requests return HTTP 401. - Given an invalid request (e.g., malformed geography or timestamp) When calling retrieval APIs Then return HTTP 422 with field-level errors.
Property Overrides, Travel Zones, and Markup Application
- Given a parent rate card and a property-specific override that changes trip_fee and materials_markup When retrieved for that property Then the response reflects the override for those fields while inheriting other fields from the parent. - Given travel zones defined (zone code with distance bands and rates) and a property located in zone Z2 When retrieved Then the travel charge for Z2 is returned; if the property is outside all zones Then return HTTP 422 with error_code = OUT_OF_TRAVEL_ZONE unless a default zone is configured. - Given after-hours or overtime flags inferred from timestamp/business hours When retrieved Then the appropriate multiplier is applied and the minimum_hours rule is enforced in the computed preview endpoints.
Line Item Auto-Matching Engine
"As a dispatcher, I want FixFlow to automatically match a job’s scope to rate-card line items so that I get an instant, accurate cost estimate without manual lookups."
Description

Deterministic/ML-assisted engine that maps scoped jobs emerging from photo-first triage to standardized line items and quantities, then prices them using the selected vendor’s current rate card in real time. Produces a transparent breakdown, confidence score, and rationale, supports composite tasks and conditional add-ons (e.g., ladder fee, second appliance), and allows manager adjustment before vendor acceptance. Executes within sub-second latency to keep intake and approval flows responsive, with guardrails to prevent unmapped items and a fallback queue for manual classification.

Acceptance Criteria
Standardized Mapping Accuracy and Guardrails
- Given a benchmark validation set of 500 labeled scopes, When the engine runs in auto-approval mode, Then it correctly maps >=95% of scoped items to standardized line items with correct quantities. - Given any scope item with confidence below threshold or no applicable rule, When mapping runs, Then the job is routed to the fallback classification queue, auto-approval is blocked, and an "Unmapped items pending" banner is displayed. - Given a job routed to fallback, When all items are manually classified, Then the engine re-runs pricing automatically and updates the job to Ready for vendor acceptance.
Vendor Rate Card Pricing and Version Enforcement
- Given a selected vendor with active rate card version V, When pricing is applied, Then each mapped line item uses the unit rate from version V for the correct UOM and currency. - Given a mapped line item without a corresponding rate on version V, When pricing is attempted, Then pricing fails hard, the job is placed in fallback, and vendor acceptance is disabled. - Given a new rate card version V+1 becomes effective at time T, When the engine prices a job created at time >= T, Then version V+1 is used; When created < T, Then version V is used unless the manager manually refreshes pricing.
Transparent Line-Item Breakdown Output
- Given a successfully priced job, When the engine returns the breakdown, Then each line contains: standardized_code, description, quantity, uom, unit_price, extended_price, add_on_flag, policy_flag, rationale_snippet, and confidence. - Then the sum of extended_price across lines equals total_price within ±$0.01. - Then the response conforms to the published JSON schema and passes contract tests.
Composite Tasks and Conditional Add-Ons Handling
- Given a job requiring a composite task (e.g., Replace faucet), When mapping runs, Then the engine emits all constituent standardized sub-lines with correct quantities. - Given conditional triggers are present (e.g., ladder requirement, second appliance), When mapping runs, Then corresponding add-on line items are auto-added with correct quantities and pricing. - Given no trigger conditions are detected, Then no add-ons are added.
Manager Adjustment with Instant Repricing
- Given a manager edits quantities or adds/removes mapped lines prior to vendor acceptance, When changes are saved, Then the engine recalculates pricing using the current rate card within 300 ms and updates totals. - Then the confidence_status is set to "Manager Adjusted" and the audit log records before/after values, editor, and timestamp. - Given the manager reverts changes, When restore is requested, Then the original mapping and prices are restored from version history.
Confidence Score and Rationale Emission
- Given deterministic rule-based matches, When mapping completes, Then confidence_score >= 0.95 for those lines; Given ML-assisted matches, Then confidence_score >= 0.70; Else lines are routed to fallback. - Then a human-readable rationale_snippet (<=300 chars) is included per line, citing the top rule or features that drove the match. - Then overall_job_confidence is returned as the weighted average of line confidences.
Sub-Second Latency SLA and Throughput
- Given normal operating load of 50 RPS and 100 concurrent jobs, When mapping and pricing execute, Then end-to-end response time is P95 <= 1.0 s and P99 <= 1.5 s. - Given cold start conditions, When the first request after a 10-minute idle period is processed, Then latency <= 2.0 s and subsequent warm requests meet the P95/P99 targets. - Given dependency timeouts or degraded external services, When latency would exceed SLA, Then the engine fails fast within 1.2 s and routes to fallback with reason = "Timeout".
Surcharge Policy Detection & Enforcement
"As an owner, I want out-of-policy surcharges to be automatically flagged and blocked so that I don’t approve unexpected fees."
Description

Rule engine that evaluates proposed line items against policy constraints (after-hours windows, weekends/holidays, distance bands, minimum charge rules, materials markup limits, and property-specific exclusions). Flags out-of-policy surcharges with plain-language explanations, blocks auto-approval when violations exist, and offers compliant alternatives when applicable. Integrates with calendars, geofencing, and vendor profiles to evaluate eligibility and produces structured exception objects used by approvals and audit logging.

Acceptance Criteria
After-Hours Window Violation Blocking with Alternative Scheduling
Given property P has after-hours window 20:00–07:00 in policy PH-01 And job J is scheduled for 21:30 local time And vendor V proposes line item LI1 type After-hours surcharge amount 75 And policy permits after-hours surcharge only when job priority = Emergency And job priority = Standard When the rule engine evaluates the proposal Then LI1 is flagged as out-of-policy with violationType AFTER_HOURS_NOT_EMERGENCY And auto-approval for job J is blocked And an exception object is generated including fields: policyId, violationType, offendingLineItemId, message, suggestedAlternatives, blocking=true, evidence.calendarWindow And a suggested alternative is provided: reschedule to first in-window slot at or after 07:00 next day And a plain-language explanation is presented containing the phrase after-hours surcharge is only permitted for Emergency jobs for this property
Weekend/Holiday Surcharge Evaluation via Calendar Integration
Given property P uses region R holiday calendar And job J is scheduled on a date that is a holiday per R And policy prohibits weekend/holiday surcharge for routine maintenance And vendor V proposes line item LI2 type Weekend/Holiday surcharge When the rule engine evaluates the proposal Then LI2 is flagged with violationType HOLIDAY_SURCHARGE_NOT_ALLOWED And auto-approval is blocked And a suggested alternative is provided: schedule next business day between 09:00–17:00 or upgrade priority to Emergency with manager approval required=true And an audit log entry is created with exceptionId and calendarEvidence including holidayId and holidayName
Distance Band Surcharge Compliance via Geofencing
Given vendor V base location is geocoded to coordinates A And property P geofence centroid is coordinates B And policy allows travel surcharge only when haversineDistance(A,B) > 25 miles and <= 50 miles in band B2 with max surcharge 40 And vendor proposes line item LI3 type Travel surcharge amount 60 When the rule engine evaluates distance Then it computes distance and determines computedDistanceMiles=18 And LI3 is flagged as out-of-policy with violationType TRAVEL_SURCHARGE_OUT_OF_BAND And a suggested alternative is provided: set amount to 0 or reassign to nearest in-network vendor within 20 miles And exception evidence.geo includes computedDistanceMiles=18 and bandEvaluated=B2
Minimum Charge Rule Enforcement and Duplicate Fee Detection
Given policy MIN1 requires minimum charge 120 covering first 60 minutes on site including travel And vendor proposes line items: LI4 Labor 1h amount 100 and LI5 Trip fee amount 25 When the rule engine evaluates the proposal Then LI5 is flagged with violationType DUPLICATE_TRAVEL_FEE_INCLUDED_IN_MIN And auto-approval is blocked And a suggested alternative is provided: replace LI4 and LI5 with single line Minimum charge MIN1 amount 120 And exception message explains that the minimum charge covers travel and first hour labor
Materials Markup Limit Validation with Auto-Correction Suggestion
Given policy caps materials markup at 15 percent over documented cost And vendor attaches material line LI6 with costEvidence 200 and proposedPrice 250 When the rule engine evaluates materials markup Then LI6 is flagged as out-of-policy with violationType MATERIAL_MARKUP_EXCEEDS_CAP And compliantPrice is computed as 230 And a suggested alternative is provided: set LI6 price to compliantPrice 230 And a plain-language message includes calculated cap and overage values 25 percent vs 15 percent And auto-approval remains blocked until price is updated or manager grants exception
Property-Specific Surcharge Exclusion Enforcement
Given property P has exclusion EX-01 forbidding Disposal fee surcharge And vendor proposes line item LI7 type Disposal fee amount 35 When the rule engine evaluates exclusions Then LI7 is flagged with violationType PROPERTY_SURCHARGE_EXCLUDED And auto-approval is blocked And suggested alternatives are provided: roll disposal into base labor or remove fee entirely And the exception object includes fields propertyId, exclusionId EX-01, and a message referencing the property-specific policy
Structured Exception Object, Blocking, and Performance Guarantees
Given one or more violations are detected during rule evaluation When the rule engine produces the exception output Then each exception object includes fields: id, policyId, violationType, offendingLineItemIds, message (<= 240 chars, grade-8 readability), suggestedAlternatives (>=1 when applicable), blocking=true, evaluatorContext (calendarEvidence, geoEvidence, policySnapshot), createdAt (ISO-8601), correlationId And an audit log entry is emitted linking exceptionId, proposalId, propertyId, vendorId, and evaluator metrics And auto-approval status remains pending until all blocking exceptions are resolved or explicitly waived by a manager role And evaluation latency per job is <= 400 ms at p95 and <= 800 ms at p99 with external dependencies cached
Magic Link Vendor Acceptance
"As a vendor, I want a simple, secure link to review and accept the agreed scope and rates before I roll, so that I have clarity and can start work confidently."
Description

Secure, expiring, single-use links sent via SMS/email that present scope, itemized rates, caps, SLAs, and cancellation terms for one-tap acceptance prior to dispatch. Supports limited counter proposals within policy (e.g., schedule window or quantity adjustments) that route to managers when necessary. Implements signed tokens, configurable expiry, device-friendly UI, and full acceptance telemetry (timestamp, device, IP) stored in the audit log. On acceptance, reserves the schedule slot, updates the job status, and locks the agreed pricing for downstream invoicing.

Acceptance Criteria
Magic Link Delivery and Access via SMS/Email
Given a job has an assigned vendor with verified email and mobile number When a magic link is generated for pre-dispatch acceptance Then the system sends the link via email and SMS containing the job reference, property address, and a unique secure URL Given the vendor receives the message on a standard email client or SMS app When the link is tapped or clicked Then the acceptance page opens without requiring login and loads within 2 seconds in 95% of attempts
Security, Expiry, and Single-Use Enforcement
Given the magic link token is signed and configured to expire after X hours When the link is opened before expiry and has not been used Then the acceptance page displays and the Accept action is enabled Given the link is opened after expiry When the page is requested Then an Expired Link message is shown and no acceptance or counter-proposal actions are available Given the link has already been used to accept or withdraw When it is opened again Then an Already Processed state is shown and the page is read-only Given the token is altered, replayed for a different job, or malformed When the page is requested Then the system returns 401/403 and logs the attempt without revealing any job details
Rate-Card Terms Presentation Fidelity
Given the job has been matched to the vendor’s rate card When the acceptance page is rendered Then it displays scope summary, itemized line items, unit rates, caps, applicable SLAs, and cancellation terms Given line items include surcharges or fees not covered by the rate card When the page is rendered Then such items are clearly flagged as out-of-policy and cannot be accepted without manager review Given totals are calculated When the user views the page Then subtotal, capped totals, and any policy-driven limits are correctly computed and displayed with currency formatting
One-Tap Acceptance: Status Update, Slot Reservation, Price Lock
Given the vendor has reviewed the presented terms When the vendor taps Accept Then the system records acceptance, updates job status to Vendor Accepted (or equivalent), reserves the offered schedule slot, and locks the agreed pricing for downstream invoicing Given the vendor taps Accept multiple times or reloads after acceptance When the request is processed Then the action is idempotent with no duplicate reservations, notifications, or status changes Given acceptance is saved When notifications are sent Then the manager and tenant receive confirmations per notification settings
Limited Counter Proposal Within Policy and Routing
Given policy allows counter proposals for schedule window and quantity adjustments within defined thresholds When the vendor selects Propose Changes and submits allowed adjustments with a reason Then the system validates the proposal and auto-accepts if within thresholds, otherwise routes to the manager for approval Given a counter proposal is routed to the manager When the manager approves or declines Then the vendor and tenant are notified and the link reflects the new state (Approved, Declined, or Pending Manager Review) Given a counter proposal is pending manager review When the vendor reopens the link Then accept and counter actions are disabled until a decision is recorded
Acceptance Telemetry and Audit Log Completeness
Given any magic link event occurs (view, accept, decline, propose) When the event is processed Then the system captures timestamp (UTC), device user-agent, IP address, vendor identifier, action type, and token ID Given telemetry is recorded When an authorized admin retrieves the job’s audit log Then all related events are retrievable, immutable, and ordered by time Given an acceptance is recorded When the audit entry is created Then it includes a complete, read-only snapshot of the accepted terms (scope, line items, rates, caps, SLAs, cancellation terms)
Mobile-First, Device-Friendly Acceptance UI
Given the acceptance page is opened on devices with viewport widths from 320px to 1440px When the page is rendered Then the layout is responsive without horizontal scrolling, primary actions are visible without zoom, and tap targets are at least 44x44px Given a vendor uses assistive technologies When navigating the page Then all interactive elements are keyboard accessible, properly labeled for screen readers, and meet WCAG 2.1 AA color-contrast requirements Given a typical mobile network (4G ~1.5 Mbps) When the acceptance page is first loaded Then it fully renders in under 3 seconds and total transfer size is under 500 KB excluding scope images
Pre-Dispatch Cost Caps & Guardrails
"As a portfolio manager, I want enforceable cost caps tied to rate-card estimates so that jobs don’t exceed budget without explicit approval."
Description

Configurable per-job and per-property cost caps that evaluate estimated totals from matched line items before dispatch. Supports soft caps (allow proceed with approval) and hard caps (block until approval), incremental authorization for in-field additions, and required-photo checkpoints for overages. Integrates with approvals to auto-approve within cap, escalate when exceeded, and record cap logic in the audit trail and vendor acceptance artifacts.

Acceptance Criteria
Auto-Approve Within Cap (Job-Level Cap)
Given a job has matched line items with an estimated total less than or equal to the configured job-level cap And the property has a default cap configured When the system evaluates pre-dispatch costs Then the job is auto-approved without manual intervention And the applied cap source is recorded as job-level And a vendor acceptance magic link is sent with the approved estimate And the audit trail records cap amount, estimate total, cap type, cap source, and auto-approval outcome
Soft Cap Exceeded — Approval Gate Allows Proceed
Given a soft cap is configured for the job or property And the estimated total exceeds the cap When the system evaluates pre-dispatch costs Then auto-approval is bypassed and an approval request is generated to the designated approver And dispatch remains allowed only after explicit approval is granted And the approval modal shows cap amount, overage amount, and impacted line items And the audit trail records the overage and approver decision with timestamp
Hard Cap Exceeded — Dispatch Block Until Approval
Given a hard cap is configured for the job or property And the estimated total exceeds the cap When the system evaluates pre-dispatch costs Then the job is blocked from dispatch and vendor acceptance cannot be requested And an escalation is sent to the designated approver And upon approval, dispatch and vendor acceptance request are unblocked And the audit trail records the block, approver, decision, and timestamps
Incremental Authorization for In-Field Additions
Given a job has been approved and dispatched And a technician proposes a new line item or quantity increase from the field When the addition is submitted for authorization Then the system recalculates against the remaining cap And if the new total remains within cap, the addition is auto-approved And if the new total exceeds a soft cap, an approval request is required before technician can proceed And if the new total exceeds a hard cap, the addition is blocked until approval And all additions and decisions are recorded in the audit trail with before/after totals
Required Photo Checkpoint for Over-Cap Items
Given required-photo checkpoints are enabled for overages And one or more line items exceed the configured cap When an approver views the approval request Then approval actions are disabled until at least one photo is attached per over-cap line item And attempting to approve without required photos shows a specific validation error listing missing items And approved photos are stored and linked to the over-cap items in the audit trail
Cap Precedence and Scope (Job vs Property)
Given a property-level default cap and an optional job-level override When the system evaluates a job with a job-level cap Then the job-level cap takes precedence over the property-level cap And when a job has no job-level cap, the property-level cap is applied And the applied cap source (job or property) is displayed in the approval UI and logged to the audit trail
Audit Trail and Vendor Acceptance Artifacts
Given any cap evaluation or approval decision occurs When the decision is finalized Then the audit trail entry includes cap type (soft/hard), cap source (job/property), cap amount, estimate total, remaining cap, decision (auto-approved/blocked/approved), approver identity, and timestamps And the vendor acceptance artifact includes the approved estimate, cap reference, and acceptance timestamp And all artifacts are immutable and retrievable by job ID
Rate Compliance Dashboard & Alerts
"As an operations lead, I want visibility into rate compliance and savings so that I can standardize costs and address problem areas quickly."
Description

Analytics and reporting that track adherence to negotiated rates, frequency and reasons for exceptions, savings realized versus list rates, and vendor-by-vendor comparisons across properties. Provides filters by trade, geography, and timeframe; exports; and proactive alerts for expiring rate cards, rising exception rates, or vendors trending above benchmarks. Feeds continuous improvement by highlighting taxonomy gaps or rules needing refinement.

Acceptance Criteria
Filter Controls by Trade, Geography, and Timeframe
Given I am on the Rate Compliance Dashboard with data across multiple trades, geographies, and dates When I apply filters Trade = "Plumbing", Geography = "Austin, TX", Timeframe = "Last 90 days" Then every widget, chart, KPI, drilldown list, and export reflects only jobs matching those filters Given I apply multiple trades and multiple geographies When the filters are applied Then the results include any job whose trade is in the selected set and property location is in the selected set Given I clear filters When I reload the page Then default Timeframe is "Last 90 days" and Trade/Geography are set to "All" Given a dataset of up to 100k jobs within the filtered window When I apply or change filters Then the dashboard re-renders in ≤3 seconds at p95 Given I copy the page URL with active filters When another user opens the link Then the same filters are pre-applied
Dashboard KPIs: Adherence, Exceptions, and Savings
Given rate-card matched and completed jobs within the selected timeframe When the dashboard loads Then it shows Adherence % = compliant line items / total eligible line items rounded to one decimal Given the same selection When the dashboard loads Then it shows Exception Rate % = exception line items / total eligible line items Given list rates are available per line item When calculating Savings Then Savings = sum over eligible lines of max(list_rate - negotiated_rate, 0) × quantity and is displayed as total and per-job average Given canceled jobs or jobs without eligible rate-card lines When metrics are computed Then they are excluded from numerator and denominator Given a validation sample of 50 jobs When metrics are recomputed offline Then dashboard values differ by ≤0.5% Given new job data is ingested When the job status or pricing changes Then KPIs update within 15 minutes
Exception Reasons Analytics and Drilldown
Given exceptions are recorded with a standardized reason taxonomy When viewing the Exceptions widget Then the top 10 reasons are ranked by count and share, with the remainder grouped as "Other" Given a reason bar is clicked When the user drills down Then a paginated job list opens filtered to that reason with columns: Job ID, Vendor, Property, Trade, Reason, Surcharge Type, Amount, Approval Status, Date Given a job in the drilldown When opened Then it deep-links to the job detail in FixFlow in a new tab Given a line item has a free-text reason not mapped to taxonomy When metrics are computed Then it is flagged as "Unclassified" and included in "Other" with count surfaced in a "Needs Mapping" banner
Vendor-by-Vendor Comparison Across Properties
Given a selected trade, geography, and timeframe When viewing the vendor comparison table Then each vendor row displays: Adherence %, Exception Rate %, Avg variance vs negotiated (% and $), Median time-to-accept, Jobs count, Properties served Given at least 5 vendors with ≥10 jobs each When benchmarks are calculated Then a benchmark line is computed as volume-weighted average, and vendors deviating >10% above benchmark in avg variance are highlighted Given the vendor row is clicked When drilling down Then a property-level breakdown for that vendor is shown with the same metrics
Proactive Alerts: Rising Exceptions and Vendor Deviation
Given weekly aggregation by trade and geography with ≥20 eligible jobs per week When the exception rate increases by ≥30% week-over-week Then send an alert within 1 hour of week close to the assigned managers via email and in-app, including trend sparkline, absolute values, and link to filtered dashboard Given a vendor has ≥5 eligible jobs in the last 7 days When the vendor’s average variance vs negotiated exceeds the benchmark by ≥10% for ≥7 consecutive days Then send a daily alert at 9am local time with vendor, trade, affected properties, variance %, and link to vendor drilldown Given duplicate alert conditions persist When alerts are generated Then they are deduplicated into a single thread with daily updates until condition clears Given a recipient unsubscribes from a specific alert type When the next cycle runs Then alerts of that type are suppressed for that user only
Exports: CSV/XLSX and Share Links
Given any applied filters When exporting dashboard data Then the exported CSV and XLSX include the same row set and filter context in the header, with a timestamp and the requesting user Given the vendor comparison view When exporting Then columns in the file match the on-screen table plus hidden keys: Vendor ID, Property ID, Job ID where applicable Given an export of up to 100k rows When requested Then the file is generated within 60 seconds and a download link remains valid for 7 days with access control enforced Given an export completes When audit logs are reviewed Then an audit entry exists with user, filter hash, row count, file type, generated_at
Taxonomy Gap Detection and Rule Refinement Suggestions
Given exceptions or surcharges with unmapped or low-confidence categories When nightly analysis runs Then the system produces a list of taxonomy gaps with counts and top free-text exemplars Given recurring out-of-policy surcharges by description pattern When analysis runs Then the system suggests new or refined rules with an estimated impact (affected lines, projected savings) and links to create/update the rule Given suggestions are reviewed When a suggestion is accepted Then the new mapping or rule is active within 24 hours and subsequent metrics reflect the change with a change-log entry Given a rejected suggestion When analysis runs the next night Then the same suggestion is suppressed for 30 days unless the underlying pattern volume doubles
End-to-End Audit Trail & Dispute Pack
"As an accountant, I want a complete, exportable audit trail of the agreed rates and approvals so that I can reconcile invoices and resolve disputes quickly."
Description

Immutable event log capturing rate card versions, matching decisions, rule evaluations, approvals, vendor acceptance artifacts, and any subsequent changes through invoicing. Generates a shareable dispute packet (timeline, itemization, evidence) to resolve billing disagreements and integrates with invoice reconciliation to validate charges against the locked scope and rates. Exposes export and API endpoints for accounting systems and owner reporting.

Acceptance Criteria
Immutable Rate Match Logging
Given a maintenance job is auto-matched to rate card line items When the match is committed Then the system writes an append-only audit entry containing job_id, ISO8601 UTC timestamp, actor/service, rate_card_id, rate_card_version, matched_line_item_ids, rule_evaluation_summary, trace_id, and entry_hash And the entry_hash is a SHA-256 of the entry payload and prev_entry_hash And modifying or deleting the entry is blocked and returns 403, with the attempt itself logged as a separate security event And the entry appears on the job timeline UI and via GET /audit-trails/{job_id} within 5 seconds of the commit
Vendor Acceptance Artifact Capture
Given a vendor receives a magic link for scope and rates When the vendor accepts via the link Then the system stores an acceptance artifact containing vendor_id, contact, token_id, IP, user_agent, timestamp, rate_card_version_hash, accepted_itemization, and acceptance_statement And confirmation receipts are sent to vendor and manager and logged And if acceptance is not completed within 24 hours the link expires and an expiration event is logged And any subsequent scope or rate change invalidates the prior acceptance and requires re-acceptance; both artifacts remain in the audit trail
Change Log and Invoice Reconciliation
Given an approved, vendor-accepted scope exists When scope or rate changes are proposed Then the system records a diff audit entry listing fields_changed, old_value, new_value, actor, why_note, timestamp, and approval_status And changes require manager approval before becoming active; approval or rejection is logged When an invoice is uploaded or received via API Then each billed line item is compared to the locked scope/rate; per-line variance and total_variance are computed And reconciliation_status is Pass if total_variance = 0 and no unapproved items are present; otherwise Fail with reasons enumerated
Shareable Dispute Packet Generation
Given a job has an audit trail and at least one invoice reconciliation When a user requests "Generate Dispute Pack" Then a downloadable PDF and JSON bundle are produced within 10 seconds including timeline, matched items, approvals, acceptance artifacts, rule evaluations, reconciliation summary, variance table, and linked evidence (photos/files) And a shareable link valid for 14 days is created; access to the link requires token and is logged And if "Redact tenant PII" is enabled, tenant email and phone are masked except last 2 characters; redaction is reflected in both outputs
Audit Trail Export and API Access
Given an authorized user selects a date range ≤ 31 days When they request an export Then a CSV and JSON export are generated containing all audit entries in the range, split into files of ≤ 100,000 rows with continuation tokens And exports complete within 60 seconds for up to 500,000 rows or return an async job id with status polling And the REST API supports GET /audit-trails?job_id, GET /audit-trails?from=..&to=..&actor=..&event_type=.., and GET /dispute-packs/{job_id} with OAuth2; p95 latency ≤ 500 ms for ≤ 100 jobs, p99 ≤ 1.5 s; rate limit 60 req/min/token with 429 and Retry-After
Log Integrity and Access Control
Given the audit trail uses a hash chain When integrity is verified via GET /audit-trails/{job_id}/verify Then the API returns integrity=true and the head hash; if any entry is altered, integrity=false with the first failing entry id And all audit writes are signed by a service key; signature is stored and verifiable And only roles Owner, Manager, Accountant, Auditor can view/export all trails; Vendors can access only their job trails with tenant PII masked; unauthorized access returns 403 and is logged And audit entries are retained for 7 years; exports are watermarked with job_id and generated_at
Timeline UI and Performance
Given a job with up to 200 audit events When the timeline view is opened Then first meaningful paint occurs ≤ 2 seconds on a 10 Mbps connection and median laptop; initial 50 events are rendered with correct chronological order And filtering by event_type, actor, and date range updates results within 400 ms p95; infinite scroll loads the next 100 events within 700 ms p95 And selecting an event reveals all metadata fields and provides working download links for any artifacts And the timeline component meets WCAG 2.1 AA for focus management, labels, and contrast

LiveFix Triage

Launches an instant, guided remote session with annotated video, checklists, and step‑by‑step prompts to stabilize or resolve issues after hours. Captures before/after media, logs actions, and hands off cleanly if a visit is still needed. Cuts truck rolls, speeds safe mitigation, and reassures tenants in the moment.

Requirements

One-Tap LiveFix Session Launch
"As a tenant experiencing an after-hours issue, I want to start a LiveFix session with one tap so that I can get immediate help without installing an app."
Description

Launch an instant, browser-based LiveFix session from SMS, email, IVR text-back, or tenant portal with a single deep link. Perform device preflight checks (camera/mic/permissions), capture explicit consent, bind the session to the originating maintenance request, and support fallback to audio-only or photo-first mode if video fails. Provide no-app-required access, session PIN for security, and capture connection metrics for reliability reporting.

Acceptance Criteria
One-Tap Deep Link Launch From All Channels
Given a tenant receives a LiveFix deep link via SMS, When the tenant taps the link, Then the LiveFix preflight screen loads in the device's default browser within 3 seconds on a 4G connection. Given a tenant receives a LiveFix deep link via email, When the tenant clicks the link, Then the LiveFix preflight screen loads in the device's default browser within 3 seconds on a 4G connection. Given a tenant uses IVR text-back and receives a LiveFix deep link, When the tenant taps the returned link, Then the LiveFix preflight screen loads in the device's default browser within 3 seconds on a 4G connection. Given a tenant clicks Start LiveFix in the tenant portal, When the browser opens via deep link, Then the LiveFix preflight screen loads within 3 seconds and the source channel is recorded as portal. Then the system records the source channel (SMS, email, IVR, portal) for the session and associates it with the user and timestamp.
No-App Browser Access and Compatibility
Given a supported mobile device (iOS Safari 16+ or Android Chrome/Samsung Internet 110+), When the deep link is opened, Then no app installation is required, no app store interstitials are shown, and the session runs fully in-browser. Given a desktop device (Windows/macOS) using Chrome/Edge/Firefox/Safari (latest 2 versions), When the deep link is opened, Then the session loads and allows audio/video or fallback flows without requiring an app. Given an unsupported or outdated browser, When the link is opened, Then a compatibility page is shown with supported options and an immediate path to Photo-First mode. Then all supported browsers render a mobile-responsive UI with tap targets ≥ 44px and load the preflight within 3 seconds on a 4G connection or 5 seconds on broadband.
Device Preflight Checks and Permission Handling
Given the preflight screen is displayed, When camera and microphone permissions are requested, Then the system detects permission state (granted/denied/prompt) and shows OS-specific instructions if blocked. Given the device has functional camera and microphone, When permissions are granted, Then a local video preview and microphone level indicator appear within 2 seconds. Given permissions are denied or hardware is unavailable, When the user chooses Continue, Then the user is routed to Audio-Only or Photo-First options without session termination. Then the preflight logs permission outcomes, device/browser details, and timestamps to the session record for diagnostics.
Explicit Consent Capture and Audit Trail
Given the user is on the preflight screen, When the user attempts to start the session, Then a consent dialog appears describing live assistance and media handling with an unchecked checkbox and a disabled Start button. Given the user provides explicit consent by checking the box, When Start is tapped, Then the system records consent=true with timestamp, request ID, source channel, IP, user agent, consent copy version, and locale in the maintenance request timeline. Given the user declines consent, When Decline is tapped, Then the session does not start and the user is offered Photo-First submission or a technician callback, and consent=false is logged to the request timeline.
Session Binding to Maintenance Request
Given a deep link generated for maintenance request MR-####, When the tenant launches LiveFix, Then the session is bound to MR-#### and all media, actions, and metrics are attributed to that request. Given the deep link is reused within 24 hours, When it is tapped again, Then it opens the same request context and invalidates any prior live session token. Given the deep link token is invalid or expired, When it is tapped, Then an error page explains the issue and offers to request a fresh link without exposing request details. Then the maintenance request timeline displays a LiveFix session entry with start/end times, source channel, participant identity, and session outcome (video/audio/photo-first).
Secure Session PIN Authentication
Given a LiveFix session is launched, When preflight passes, Then the user is prompted for a 6-digit PIN delivered via the originating channel (and via TTS for IVR) before joining. Given the correct PIN is entered within 10 minutes, When Submit is tapped, Then access is granted and the PIN is immediately invalidated. Given three consecutive incorrect PIN attempts, When the threshold is reached, Then the session link is locked for 5 minutes and a security event is logged on the request. Given the tenant requests a new PIN, When Resend is tapped, Then a new PIN replaces the old one and is sent only to the verified originating channel.
Fallback to Audio-Only or Photo-First and Connection Metrics Capture
Given video connection negotiation fails (3 ICE failures or no video frames within 10 seconds), When failure is detected, Then the user is proactively offered Audio-Only and Photo-First options without leaving the session. Given Audio-Only is selected, When connected, Then two-way audio starts within 5 seconds and the session continues with video features disabled. Given Photo-First is selected, When used, Then the user can upload at least 5 photos and one 30-second clip with progress indicators, and all media is attached to the bound request. Then connection metrics (join time, RTT, jitter, packet loss, bitrate, reconnect count, fallback used) are sampled every 5 seconds and stored with the session for inclusion in the daily reliability report by channel.
Low-Bandwidth Annotated Video
"As an on-call coordinator, I want low-latency video with annotation tools so that I can guide tenants precisely even on weak connections."
Description

Provide adaptive WebRTC video optimized for low bandwidth with bidirectional annotation tools (draw, arrows, labels), freeze-frame and snapshot capture, tap-to-focus, and remote flashlight toggle. Persist annotated frames to the ticket, enable quick zoom and frame stabilization, and maintain end-to-end encryption in transit with regional media relays for performance.

Acceptance Criteria
Adaptive Low-Bandwidth WebRTC Stream
Given a tenant on mobile data constrained to 200 kbps uplink/300 kbps downlink with 5% packet loss and 150 ms RTT When a LiveFix video session is initiated Then the first decoded video frame renders within 3 seconds And the stream auto-adapts to sustain >=240p resolution and >=10 fps for at least 2 minutes And audio remains intelligible with >=98% non-muted frames and no disconnects And playback stall ratio is <=10% over the 2-minute window
Bidirectional Annotations During Live Session
Given an active LiveFix session on any supported client (iOS, Android, web) When either participant selects Draw, Arrow, or Label and adds an annotation Then the annotation appears on both devices within 300 ms with distinct color and user attribution And annotations support undo/redo and per-tool color selection And up to 20 annotations may be present simultaneously without dropping prior annotations And clearing annotations removes them from both devices within 300 ms
Freeze-Frame, Snapshot, and Persistence to Ticket
Given an active LiveFix session When Freeze Frame is activated Then the remote video pauses for both participants and becomes annotatable without affecting the live stream buffer When Snapshot is captured from a live or frozen frame Then the image is stored to the maintenance ticket with visible overlays, timestamp, participant ID, device type, and resolution metadata And the saved image is retrievable from the ticket timeline within 2 seconds and matches the on-screen capture
Tap-to-Focus and Remote Flashlight Control
Given the tenant device camera supports autofocus and torch and the tenant has granted camera/torch permissions When the manager taps a point on the video preview Then the tenant camera adjusts focus and exposure to that region within 700 ms and confirms with a focus indicator When the manager toggles the flashlight control Then the tenant device torch turns on/off within 1 second and the state is reflected on both clients And on devices without torch capability, the flashlight control is disabled with an explanatory tooltip
Quick Zoom and Frame Stabilization
Given an active LiveFix session on a supported mobile device When the tenant performs pinch-to-zoom or the manager requests zoom controls Then digital zoom from 1.0x to at least 4.0x is available in 0.1x increments with visual indicator of current zoom And median frame rate remains >=10 fps and end-to-end glass-to-glass latency increases by no more than 150 ms during zoom operations When stabilization is enabled Then visible handheld jitter amplitude is reduced by at least 30% compared to stabilization off, without dropping below 10 fps
End-to-End Encryption with Regional Media Relays
Given both participants are located in the same geographic region (e.g., US-West) When ICE negotiation selects media paths Then DTLS-SRTP is used with ephemeral keys; media is encrypted in transit end-to-end and not decrypted at relays And the chosen TURN/SFU relay is within the participants’ region, verified by relay region metadata And median round-trip media latency is at least 20% lower than a forced cross-region relay baseline And signalling uses TLS 1.2+ and no media keys are logged or persisted server-side
Graceful Degradation and Reconnection Handling
Given an ongoing LiveFix session When network throughput drops below 100 kbps for more than 5 seconds or a temporary outage (<10 seconds) occurs Then the session remains connected, auto-reduces bitrate and frame rate, and attempts reconnection without user action And upon reconnection (<=5 seconds after link returns), the session state (participant roles, selected tools) is preserved And any unsaved annotations on a frozen frame persist locally and prompt to snapshot-and-save to the ticket within 2 seconds of reconnection
Dynamic Safety-Gated Checklists
"As a property manager, I want guided, safety-checked steps tailored to the issue so that tenants can mitigate problems without risk."
Description

Deliver step-by-step, branching checklists tailored by issue type (e.g., leak, HVAC, electrical) with safety gating, mandatory warnings, timers (e.g., shut-off wait times), and embedded micro-videos. Authoring UI for managers to create, version, localize, and publish flows backed by a knowledge base; collect completion and outcome analytics to continuously improve mitigation success rates.

Acceptance Criteria
Electrical Issue Safety Gate Before Troubleshooting
Given the tenant selects the "Electrical – Outlet Not Working" checklist in LiveFix Triage and joins the video session When the tenant attempts to proceed to the step "Test outlet" Then the system displays a mandatory safety warning and requires explicit acknowledgment via checkbox And the system requires capture of a labeled photo or 3–5s video showing the breaker in the OFF position And the system requires the tenant to confirm "Power is OFF" via checkbox And progression to the next step remains disabled until all required acknowledgments and media are provided And attempts to bypass the gate (e.g., refresh, deep link) are blocked with a consistent error message And the audit log records user ID, timestamps, step ID, and media asset IDs
Leak Mitigation With Auto Shutoff Timer and Branching
Given the tenant starts the "Leak – Active Water" checklist and indicates the main shutoff valve is closed When the tenant confirms valve closure Then a 5-minute countdown timer starts and is visibly displayed And the next step remains locked until the timer expires And the timer state persists across app restarts and reconnections And upon expiry the next step unlocks automatically and the tenant is prompted to check for residual dripping And if the tenant selects "Severity: Major" the flow branches to the Emergency path; else it branches to Containment path And timer start/end timestamps and branch taken are recorded to analytics
Micro-Video Playback With Watch-Gated Progression
Given a step contains an embedded 20–60 second micro-video instruction When the tenant taps Play on a 4G connection (>5 Mbps) Then playback begins within 2 seconds And on 3G (>1 Mbps) playback begins within 5 seconds And if buffering exceeds 5 seconds the player auto-switches to 360p and displays a "View transcript" link And the Next button remains disabled until at least 90% of the video is watched or the tenant selects the "No video available" fallback and confirms And video watch percentage and fallback usage are recorded to analytics
Authoring: Create, Version, Localize, and Publish Checklist Flows
Given a manager with Author permissions opens the Authoring UI When they create or edit a checklist Then they can add steps of types: Instruction, Question, Safety Gate, Timer, Media Prompt, and Branch rules And they can attach micro-videos (mp4/webm up to 50 MB) and images (jpg/png up to 10 MB) And they can configure timers (30 seconds to 30 minutes), warnings, and required acknowledgments And they can save as Draft, assign a semantic version (e.g., 1.2.0), and Preview the flow And they can localize strings for English, Spanish, and French with per-locale media overrides And publishing marks the new version Active and auto-archives the prior Active version; runtime serves the latest Active per locale And all changes capture author, timestamp, and diff history in an audit log
Analytics: Capture, Aggregate, and Export Mitigation Metrics
Given tenants execute checklists during LiveFix sessions When a session ends as Resolved or Handoff Then the system stores: checklistId, version, locale, propertyId, start/end timestamps, steps completed, per-step durations, safety gate pass/fail, timers used, video watch %, outcome (Resolved/Visit), and reason codes And an Analytics dashboard displays for a selectable date range and filters (property, checklist, locale): completion rate, average time to resolve, % requiring visit, and safety incident count And users can export a CSV containing the above fields within 10 seconds for up to 50,000 rows And metrics are retained for at least 12 months
Unresolved Case Handoff With Media and Action Log
Given a triage session remains unresolved after checklist completion When the agent or tenant selects "Technician required" Then the system generates a handoff package containing: issue type, checklist name and version, steps completed/failed, safety confirmations, before/after media links, tenant contact info, preferred time windows, and notes And a work order is created in the scheduling module within 3 seconds of confirmation And the tenant receives a confirmation message with a tracking link within 10 seconds And the technician receives a magic-link summary (valid 24 hours) showing the handoff package on mobile without additional login And the handoff event is logged with correlation IDs linking session, work order, and media assets
Localization and Accessibility Compliance for Safety-Gated Content
Given the tenant’s locale is Arabic (ar) or Spanish (es) When the checklist loads Then all UI text, safety warnings, and step content render in the selected locale; RTL layout is applied for ar including mirrored icons and correct number/date formats And micro-videos provide captions in the selected locale and transcripts are accessible to screen readers And color contrast meets WCAG 2.1 AA and the flow is fully operable via keyboard and screen reader with correct focus order And switching locale mid-session updates visible content within 2 seconds without losing progress
Before/After Media Capture & Evidence Pack
"As a vendor technician, I want clear before-and-after media attached to the job so that I can understand context and verify outcomes."
Description

Capture structured before-and-after photos and short clips with timestamps, device metadata, and inline annotations, auto-compress and upload to secure cloud storage, and generate a shareable evidence pack linked to the ticket. Provide redaction tools for sensitive information, configurable retention policies, and one-click inclusion in insurance or warranty documentation.

Acceptance Criteria
After-Hours Tenant Before/After Capture
Given a tenant is in a LiveFix session on a mobile device after hours When they capture media and label it as "Before" And they later capture corresponding media labeled "After" for the same triage step Then each media item is stamped with an ISO 8601 UTC timestamp to millisecond precision And device metadata (device make/model, OS version, app version, camera resolution, orientation) is stored with the media And the system enforces at least one "Before" and one "After" item per triage step attempted And the system allows up to a configurable maximum (default 10) media items per type per ticket And if camera or microphone permissions are missing, the user receives a clear error with guidance and a retry option
Inline Annotation Capture and Persistence
Given a user adds overlays (arrows, text, shapes) during or after capture When the media is saved Then annotations are stored as non-destructive vector overlays linked to the media And annotations render consistently across devices using coordinates relative to media dimensions And annotation author and timestamp are recorded in metadata And at least 10-level undo/redo is supported before save And annotations are visible in evidence pack previews and exports by default with a toggle to exclude them
Auto-Compression and Secure Upload
Given media is queued for upload When compression is applied Then photos are compressed to <= 8 MB and videos to <= 50 MB by default (configurable) And photos retain a minimum long edge of 2048 px and videos retain >= 1080p at >= 24 fps unless the source is lower And uploads use HTTPS with TLS 1.2+ and certificate pinning in mobile apps And uploaded media is encrypted at rest with AES-256 on the server And failed uploads automatically retry with exponential backoff up to 5 attempts and resume partial uploads And users see upload progress and success/failure within 1 second of state changes And if offline, media is locally encrypted and auto-uploads within 60 seconds of reconnection
Evidence Pack Generation and Ticket Linking
Given a ticket has captured media and annotations When a user requests an evidence pack Then the system generates the pack within 30 seconds containing: a summary (HTML/PDF), metadata.json, and media organized by Before/After with annotated and original variants And the pack is linked to the ticket with a version ID and checksums for all files And a shareable URL is created with role-based access controls and a default 7-day expiry (configurable) And an in-app preview of the pack is available And all generation and access events are recorded in the audit log
Redaction Tools for Sensitive Information
Given a user enters redaction mode on a media item When they apply face/region blurs or manual brush redactions Then a redacted variant is created non-destructively and associated with the original And all exports and shared links use the redacted variant by default And only users with Admin role can access unredacted originals And each redaction action logs user, timestamp, and affected regions And a side-by-side preview is available before saving redactions
Configurable Retention Policy and Secure Purge
Given an account-wide retention policy (e.g., 90 days) is configured When a media item exceeds the retention period and no legal hold is active Then the owner is notified 7 days prior to purge And the system purges the media and all derivatives within 24 hours of expiry using secure deletion And the purge is irreversible and logged with reason, actor (system), and file checksums And a legal hold can be applied per ticket to suspend purge and is recorded in the audit log And retention settings are configurable per workspace with documented defaults and min/max bounds
One-Click Insurance/Warranty Export
Given a ticket has a generated evidence pack When a user selects "Send to Insurance/Warranty" Then the system produces a standardized PDF summary and a ZIP of media and metadata within 20 seconds And the export includes incident timestamps, location (if available), device metadata, action logs, and Before/After pairs And users with permission can choose redacted or unredacted content; default is redacted And delivery is via email to configured recipients or immediate download via an expiring link And delivery status (delivered, bounced, failed) is recorded and a note is added to the ticket
Automated Incident Log & Audit Trail
"As a landlord, I want a complete record of what happened during triage so that I have accountability and documentation for insurance and compliance."
Description

Aggregate the full session record—checklist steps, annotations, snapshots, chat transcript, decisions, timestamps, participants, and outcomes—into an immutable audit log attached to the FixFlow work order. Support full-text search, role-based access, and export to PDF/CSV for compliance, insurance claims, and internal reporting.

Acceptance Criteria
Immutable Audit Log Creation & Work Order Linkage
Given a LiveFix session concludes When the session is closed Then the system generates an immutable audit log with a unique Log ID and attaches it to the corresponding FixFlow work order Given the audit log is created When any user attempts to edit an existing recorded event Then the system prevents modification and only allows append-only corrections with actor, timestamp, and reason captured as a new event Given the work order detail page is opened by an authorized user When the page loads Then the attached audit log link and summary (event count, duration, final outcome) are visible
Comprehensive Session Data Capture
Given an active LiveFix session When events occur Then the log records: checklist steps (step_id, label, assignee, status, start/end timestamps), annotations (type, target, coordinates/frame reference, author, timestamp), snapshots and before/after media (media_id, mime_type, resolution, size, timestamp), chat transcript (sender_id, role, message, timestamp), decisions (decision_type, options presented, selection, rationale text if provided), participants (user_id, role, join/leave timestamps), and outcomes (resolved/stabilized/visit_required) Given a session contains no media When the log is generated Then media sections are present with an explicit "none captured" indicator Given intermittent connectivity during a session When offline actions are buffered Then upon reconnection, buffered events are persisted with original timestamps and marked as synced
Event Timestamping and Ordering
Given any loggable action occurs When it is recorded Then the event timestamp is stored in UTC with millisecond precision and includes a monotonically increasing sequence number per session Given multiple events share the same second When the log is viewed Then events are ordered by timestamp then sequence number to reflect actual occurrence order Given a viewer with a locale preference When the log renders Then display times show in the viewer’s local timezone with offset, while UTC is preserved in metadata and exports
Full-Text Search Across Audit Logs
Given a user with Search permission When they search by keyword or exact phrase Then matching work orders are returned within 2 seconds for a corpus up to 50,000 logs (avg log size ≤ 1 MB) Given a search is executed When results are displayed Then matches include content from transcript, checklist labels, decisions, annotations text, file names, outcomes, and participants, with term highlighting in snippets Given filters for date range, property, outcome, and role When applied to a search Then results respect all filters and display accurate total hit counts
Role-Based Access & Redaction
Given role-based access controls are enforced When a Tenant views their work order Then the audit log hides internal notes and decision rationales and redacts other parties’ PII, while allowing tenant-submitted media and messages Given a Vendor/Technician accesses a work order When viewing the audit log Then they see steps assigned to them and necessary media but cannot access tenant contact details or internal approvals Given a Manager or Owner accesses the work order When viewing the audit log Then full content and export actions are available; unauthorized access attempts are denied with a 403 and logged with actor, timestamp, and source IP
Export to PDF and CSV
Given a user with Export permission When exporting a log to PDF Then the PDF includes a cover (work order ID, property, date/time range, participants), event timeline, media thumbnails with captions, and an integrity checksum, and generates within 10 seconds for logs ≤ 500 events and ≤ 50 media items Given the same log is exported to CSV When the file is generated Then each event is a row with columns: session_id, work_order_id, utc_timestamp, seq_no, event_type, actor_id, actor_role, payload_json; media are referenced by secure time-limited URLs Given an export is completed When the download starts Then the filename follows FixFlow_AuditLog_<WorkOrderID>_<YYYYMMDDThhmmssZ>.<ext> and the export action is itself recorded in the audit log
Tamper Evidence and Verification
Given an audit log exists When integrity verification runs Then per-event hashes and a session-level hash validate to status "Valid" Given an event is altered outside the append-only pathway When verification runs Then validation status is "Invalid", access is blocked pending admin review, and a security alert is issued Given an exported PDF/CSV is received When the recipient validates the embedded checksum/signature Then it matches the server-side record and verification status is "Valid"
Smart Escalation & Dispatch Handoff
"As an on-call coordinator, I want seamless handoff to dispatch when needed so that truck rolls are only scheduled with complete, actionable information."
Description

When remote resolution is not achieved, automatically create or update a work order with preliminary diagnosis, scope, priority, required skills/parts, and preferred time windows. Recommend vendors based on availability and SLA, attach the evidence pack and audit log, sync with calendars, and notify tenant and vendor with next steps.

Acceptance Criteria
Unresolved Triage Generates Complete Work Order
Given a LiveFix session ends with status Unresolved When escalation is triggered Then the system creates or updates a work order within 30 seconds containing: property/unit ID, tenant ID, preliminary diagnosis, scope summary, priority (P1–P4), required skills, required parts list, preferred time windows, triage session ID, timestamps, and dispatcher ID And a unique work order ID is assigned and the status is set to Pending Dispatch And if an open work order exists for the same unit and issue category within the last 24 hours, it is updated instead of creating a new one and an audit entry is appended
Attach Evidence Pack and Audit Log to Work Order
Given escalation occurs from LiveFix When the work order is generated Then an evidence pack is attached including: at least 1 video or 2 photos, annotations, before/after markers (if captured), checklist results, and a triage action log marked immutable And vendor and manager can preview in‑app and download; tenant view excludes internal notes And if total media exceeds 200 MB, media is transcoded and compressed to under 200 MB while preserving annotations
Vendor Recommendation Based on Availability and SLA
Given a work order has required skills, priority, location, and SLA targets When generating recommendations Then the system returns a ranked list of up to 3 vendors with availability windows, SLA fit score, and reason codes And vendors violating exclusion rules or outside coverage radius are omitted And if no vendor meets SLA, the top nearest‑fit is shown flagged as SLA Risk with an explicit override prompt
Auto-Scheduling With Tenant Preferences and Calendar Sync
Given tenant preferred time windows and vendor availability are known When a vendor is selected Then the system proposes 2–3 next available windows that satisfy SLA and tenant preferences And when a window is confirmed by tenant or manager, calendar events are created for vendor and manager within 60 seconds, include ICS attachments in notifications, and contain address, access notes, parking/gate codes, and an evidence link And if a scheduling conflict is detected during sync, double‑booking is prevented and alternative windows are proposed automatically
Tenant and Vendor Notifications With Next Steps
Given a work order is created via escalation When creation completes Then the tenant receives confirmation via preferred channel (SMS/email/push) within 90 seconds including ticket ID, expected timeline, and safety instructions And upon vendor selection and time confirmation, the vendor receives dispatch details within 60 seconds including scope, priority, contact information, and evidence link; delivery is retried up to 3 times with exponential backoff on failure And during tenant quiet hours (10pm–8am local), non‑urgent messages are deferred until 8am; P1 urgent notifications may send immediately but include safety‑only content
Status Transitions and Duplicate Prevention on Escalation
Given escalation from a LiveFix session marked Unresolved When the work order is created Then the triage case status transitions to Escalated and the work order status to Pending Dispatch And if the tenant reports the same issue within 24 hours with a symptom match score ≥ 0.8, the report is merged into the existing work order and the tenant is informed of the existing appointment And if remote resolution is achieved, no work order is created and the triage case status becomes Resolved Remote
Access Control and Link Expiration for Shared Artifacts
Given links to evidence and logs are shared with tenant and vendor When accessed Then permissions enforce least‑privilege: tenant cannot view internal notes; vendors cannot access other tenants’ data And share links expire after 72 hours or upon job completion (whichever occurs first); expired links return HTTP 410 Gone And any view or download of artifacts generates an audit log entry with user identity, IP, timestamp, and artifact ID
Real-Time Tenant Updates & Reassurance
"As a tenant under stress, I want clear updates and reassurance during triage so that I feel safe and informed while the issue is being addressed."
Description

Provide automated, multilingual messaging via SMS/email/in-app with live status, safety reminders, ETA, and next steps during and after the session. Include accessibility features (captions, text-to-speech, large-text mode), a visible progress indicator, and post-session feedback capture (CSAT/NPS) to monitor tenant sentiment and service quality.

Acceptance Criteria
Multilingual Messaging Preference and Fallback
Given a tenant profile has a saved language preference, When a LiveFix session is initiated, Then all outbound SMS, email, and in-app messages are sent in that language using approved translations. Given no language preference exists, When the tenant’s device locale is supported, Then the session language is set to the device locale; Otherwise default to English and record the fallback. Given a localized template is unavailable, When a message is generated, Then fall back to English, include no placeholder keys, and log a missing-translation warning with template_id and language. Given a status change triggers a message, When messages are dispatched across channels, Then each message records message_id, channel, language, timestamp, and delivery status. Given a message fails delivery, When a retry is attempted, Then use exponential backoff up to 3 attempts and, if an alternate verified channel exists, send via that channel after final failure.
Real-Time Status Updates During Session
Given the LiveFix session state changes (connecting, in-session, escalation requested, ended), When a change occurs, Then the tenant receives a status update within 5 seconds via their preferred channel and in-app. Given successive identical state changes occur within 30 seconds, When preparing outbound notifications, Then suppress duplicate messages while ensuring the latest state is visible in-app. Given a status update includes a deep link, When the tenant opens it, Then the session opens directly to the status view without requiring additional login if a valid token is present. Given temporary connectivity loss, When an update cannot be sent, Then queue it and dispatch upon connectivity restoration with the original event timestamp preserved.
Safety Reminders and Acknowledgement for Critical Issues
Given an issue type is flagged as critical (gas, electrical, active leak), When LiveFix triage starts, Then send a localized safety checklist within 60 seconds including clear do/don’t steps and emergency escalation instructions. Given a safety checklist has been sent, When the tenant taps ACK or replies the keyword to SMS, Then record acknowledgement with timestamp and user id in the session log. Given no acknowledgement within 2 minutes for a critical issue, When monitoring the session, Then send one follow-up reminder and alert the on-call manager via push/email. Given the safety checklist is updated during the session, When new steps are added, Then present changes to the tenant and require re-acknowledgement.
Technician ETA and Handoff Notifications
Given an on-site visit is required, When a technician is assigned, Then send the ETA window, technician first name, masked tech ID, and contact relay link in the tenant’s language and time zone. Given the technician’s ETA shifts by more than 10 minutes, When location or schedule updates arrive, Then send an updated ETA with reason, throttled to no more than one update every 10 minutes. Given no technician is assigned within 20 minutes after escalation, When monitoring the work order, Then notify the tenant that scheduling is in progress and provide the next update time. Given a visit is canceled or rescheduled, When the schedule changes, Then notify the tenant with the new slot and a one-tap confirm/reschedule CTA.
Accessibility: Captions, Text-to-Speech, and Large Text
Given live video or audio is used, When the session begins, Then closed captions are available and can be toggled; default to on for tenants with a hearing-impaired flag. Given any message content is displayed, When text-to-speech is enabled, Then the tenant can play TTS in the selected language with at least 95% message coverage; unsupported content prompts a readable fallback link. Given large-text mode is enabled, When viewing messages or the status screen, Then font sizes scale to 150% without truncation or overlap; SMS includes a link to an accessible web view. Given WCAG 2.1 AA requirements, When rendering status and progress UI, Then color contrast, focus indicators, and keyboard navigation meet compliance.
Visible Progress Indicator and Next Steps
Given a LiveFix session is active, When the tenant opens the status view, Then a progress indicator displays current step, completed steps, and an estimate of time/steps remaining. Given the tenant disconnects and rejoins, When the session resumes, Then progress state is restored without starting over and shows the last acknowledged step. Given a step requires tenant action, When the tenant has not completed it, Then an actionable CTA is presented with clear instructions and is logged upon completion. Given the session ends, When finalizing, Then the progress indicator shows final state, summary of actions taken, and clearly stated next steps (e.g., wait for tech, monitor, no action).
Post-Session Feedback: CSAT and NPS Capture
Given a LiveFix session has ended, When within 10 minutes post-session, Then send a feedback request with CSAT (1–5) and NPS (0–10) via the tenant’s preferred channel and in-app. Given the tenant submits feedback, When storing results, Then save CSAT, NPS, free-text comment, language, and timestamps linked to session_id, property_id, and tenant_id. Given no feedback after 24 hours, When sending reminders, Then send one reminder only, respecting quiet hours between 21:00 and 08:00 local time. Given CSAT <= 2 or NPS 0–6, When feedback is received, Then generate an alert to the property manager within 5 minutes including the tenant’s comments.

Dynamic Thresholds

Sets time‑aware, issue‑specific pre‑approval limits that adapt to risk and SLAs (e.g., minor leaks vs. gas odors). Auto-approves within policy, requests the right approver when needed, and suggests deferral to business hours when safe. Eliminates midnight decision bottlenecks while keeping spend controlled.

Requirements

Adaptive Risk Scoring Model
"As an on-call property manager, I want incidents scored by risk and cost sensitivity in real time so that thresholds adapt to actual urgency and I avoid unnecessary wake-ups and overspend."
Description

Build a real-time scoring engine that evaluates incoming maintenance requests using issue type, extracted details from photo-first triage, tenant-reported symptoms, time of day, building profile (age, system type), local weather, and historical outcomes to assign a risk band (e.g., critical, urgent, routine) and a recommended spend multiplier. The score feeds Dynamic Thresholds to adjust pre-approval caps per ticket context. The engine must expose deterministic rules plus tunable weights, support A/B variants, and return scores within 200 ms to avoid intake delays. Integrates with FixFlow intake, categorization, and vendor selection to ensure downstream actions (auto-approve, defer, escalate) reflect actual risk and SLAs.

Acceptance Criteria
p95 Scoring Latency  200 ms
Given production-like traffic of 10,000 requests across peak and off-peak on warm instances When the scoring API evaluates requests end-to-end Then p95 computeTimeMs 1 200 ms, p99 1 300 ms, and error rate 1 0.1% over the test window And each response includes computeTimeMs populated And cold-start instances recover to p95 1 200 ms within 5 minutes of warm-up
Deterministic Rules Precedence and Tunable Weights
Given a ruleset that marks gas odor and active flooding as Critical When inputs match any Critical rule Then riskBand 1 Critical regardless of weight values And rulesApplied includes the triggering rule IDs in order of precedence Given a new weightsVersion is published to config When the version is activated Then it takes effect within 5 minutes, is auditable with actor, timestamp, diff, and changelog URL And rollback to the previous weightsVersion completes within 2 minutes Given identical inputs, rulesVersion, and weightsVersion When scoring is repeated across N100 runs and across environments Then riskScore, riskBand, and spendMultiplier are bit-for-bit identical
A/B Variant Support and Isolation
Given a 50/50 A:B split with stable hashing by tenantId When bucketed over 5,000 requests Then observed distribution per variant is between 48% and 52% And the assigned variantId remains stable for the same tenant for 30 days Given variants A and B have distinct weights When scoring requests Then responses include variantId, share an identical schema, and logs are tagged with variantId And no cross-variant state leakage is observed (scores for identical inputs differ only due to weights) Given variant B is disabled in config When traffic flows for 2 minutes Then 100% of new requests route to variant A
Context Ingestion and Graceful Degradation
Given building profile data and photo-first extraction detect active leak When scoring Then features from building profile and photo extraction are present in inputsUsed and topContributors and increase riskScore relative to baseline Given the weather provider is unavailable When scoring Then the engine returns HTTP 200 with a score, marks weather as missing in missingFeatures, reduces completenessScore, and emits no 5xx Given required fields are invalid or missing When requesting a score Then the engine returns HTTP 400 with explicit validation errors and does not mutate state And optional fields missing do not block scoring
Dynamic Thresholds Consumption and Action Alignment
Given riskBand 1 Routine during 00:006:00 local time and policy allows deferral When Dynamic Thresholds consumes the score Then decision 1 DeferToBusinessHours, tenant notification is sent within 60 seconds, and SLA timestamp is recorded Given riskBand 1 Critical When Dynamic Thresholds consumes the score Then pre-approval cap is raised by spendMultiplier, escalation to on-call occurs within 2 minutes, and emergency vendor selection is triggered Given riskBand 1 Urgent and estimated cost after multiplier 2 pre-approval cap When Dynamic Thresholds consumes the score Then auto-approval is issued and the approver is not paged; else it routes to the correct approver tier within SLA
Response Schema, Explainability, and Auditability
Given any successful response When inspected Then it includes riskBand  {critical, urgent, routine}, riskScore  [0,100], spendMultiplier  [0.1,3.0], rulesApplied[], topContributors[0..5], inputsUsed[], rulesVersion, weightsVersion, variantId, computeTimeMs, correlationId Given a correlationId from a scored request When querying audit logs Then inputs snapshot, derived features, rules fired, weights snapshot, and outputs are present and allow deterministic reproduction of the score Given two tickets A and B where band(A) has higher severity than band(B) When comparing spendMultiplier across 5,000 sampled pairs Then spendMultiplier(A) 1 spendMultiplier(B) in 100% of cases (monotonicity)
Time-Window Threshold Policies
"As a portfolio operations lead, I want to define pre-approval caps by issue type and time window so that spend aligns with SLAs and risk during nights, weekends, and holidays."
Description

Provide a policy framework to define base pre-approval caps per issue category (e.g., plumbing leak, gas odor, HVAC outage) with modifiers by time window (business hours, night, weekend, holiday), geography, and building class. Policies must reference SLA targets and risk bands to increase or decrease caps automatically. Include precedence rules, default fallbacks, and holiday calendars. Allow per-owner overrides and portfolio-level templates. Expose a management UI and API for CRUD, validation, and preview of effective thresholds for a given scenario.

Acceptance Criteria
Effective Threshold Calculation by Time Window
Given baseCap = 250.00 USD for issueCategory = "Plumbing Leak" in portfolio PT-1 And timeWindow modifiers: Night = +20%, Weekend = +10% And combineStrategy = multiplicative When an incident is created at 2025-12-06T23:15:00-08:00 in property timezone America/Los_Angeles (Saturday night) Then effectiveCap = 250.00 * 1.20 * 1.10 = 330.00 USD And any estimatedCost <= 330.00 USD is auto-approved; any estimatedCost > 330.00 USD is routed to approverGroup per policy And the Preview UI/API for the same scenario returns 330.00 USD with contributing modifiers ["Night","Weekend"] and combineStrategy = "multiplicative"
Precedence: Owner Override > Portfolio Template > Geography > Building Class > Default
Given an ownerOverride exists for ownerId = "O-42" setting baseCap = 400.00 USD for issueCategory = "Plumbing Leak" And a portfolioTemplate defines baseCap = 300.00 USD for the same category And geography modifier City = "Seattle" = +15% And buildingClass modifier Class = "B" = +5% When evaluating propertyId = "P-9" owned by O-42 in Seattle, buildingClass = B during Business Hours Then precedence selects ownerOverride baseCap = 400.00 USD before portfolioTemplate = 300.00 USD And effectiveCap = 400.00 * 1.15 * 1.05 = 483.00 USD And the audit trail shows layerOrder = ["ownerOverride","geography","buildingClass"] and finalValue = 483.00 USD And removing the ownerOverride causes the next evaluation to use template baseCap = 300.00 USD (geography and buildingClass still applied)
Holiday Calendar Modifies Time Window
Given holidayCalendars include "US-Federal" and customDates = ["2025-11-28"] And the property timezone is America/New_York When evaluating a request at 2025-11-28T10:00:00-05:00 Then timeWindow = Holiday And Holiday modifiers apply instead of Business Hours or Weekend modifiers And the Preview response includes sourceHoliday = "Custom (2025-11-28)" and calendar = "US-Federal + Custom" And if no holiday calendar is configured for the portfolio/owner, the engine falls back to default calendar = None and resolves timeWindow per weekday/weekend rules
SLA/Risk Band Modifiers Adjust Caps
Given issueCategory = "Gas Odor" with baseCap = 500.00 USD And riskBand modifiers: Critical = +100%, High = +50%, Medium = 0% And SLAUrgency modifiers: Urgent(<25% SLA remaining) = +25%, Normal(>=25%) = 0% And the incident riskBand = Critical And SLA target = 60 minutes; elapsed = 50 minutes (remaining ≈ 16.7% => Urgent) When evaluating the effective threshold at any timeWindow Then effectiveCap = 500.00 * 2.00 * 1.25 = 1250.00 USD And any estimatedCost <= 1250.00 USD is auto-approved; any estimatedCost > 1250.00 USD requires approver = "On-Call Manager" And the Preview/API response shows modifiersApplied = ["riskBand:Critical","SLAUrgency:Urgent"] with their percentages and computed value
Policy Management UI: CRUD, Validation, Preview
Given a user with role = "Policy Admin" opens the Threshold Policies UI When they attempt to Save a policy missing required fields (issueCategory, baseCap, scope) Then inline validation messages display per field and Save is disabled When valid inputs are provided and Save is clicked Then the policy is persisted, version increments, and a success notification displays within 1 second And the Preview panel, when given a scenario (issueCategory, timestamp, propertyId, ownerId), returns the same effectiveCap and decisionPath as the API for identical input And users without role = "Policy Admin" cannot create or edit policies (controls hidden or disabled) but can view policies if role = "Viewer"
Policy Management API: CRUD + Preview
Given REST endpoints exist: POST/GET/PATCH/DELETE /threshold-policies and POST /thresholds/preview When POST /threshold-policies is called with a valid body and Idempotency-Key header Then the API returns 201 Created with {policyId, version, ETag}; a retried POST with the same Idempotency-Key does not create a duplicate and returns the same resource When PATCH /threshold-policies/{id} is called with an If-Match ETag that does not match the latest version Then the API returns 412 Precondition Failed When invalid input is submitted Then the API returns 400 Bad Request with machine-readable error codes and field-level details When POST /thresholds/preview is called with a complete scenario payload Then the API returns 200 OK with {effectiveCap:number, currency:string, timeWindow:string, matchedLayers:array, rationale:array} and values match the UI preview for the same inputs
Default Fallbacks and Safe Defaults
Given no policy matches all of (issueCategory, owner/portfolio scope, geography, buildingClass, timeWindow) When the engine evaluates thresholds for a scenario Then effectiveCap resolves to systemDefault for the issueCategory if defined And if neither a category default nor a global systemDefault exists, decision = "Require Approval" and effectiveCap = 0.00 USD And the Preview response includes fallback = "systemDefault" or fallback = "requireApproval" with reason And the evaluation event is recorded in audit logs with full scenario inputs and fallback details
Auto-Approval and Cap Enforcement
"As a property manager, I want work orders under the dynamic cap to auto-approve and dispatch so that tenant issues are resolved quickly without manual gatekeeping."
Description

Automatically approve and authorize work orders whose estimated cost falls within the effective dynamic threshold, generating the necessary approval tokens, budget encumbrances, and vendor dispatch instructions without human intervention. For estimates exceeding the cap, hold the ticket, calculate the delta, and trigger approval routing. Log every decision with timestamp, inputs (risk band, policy version, time window), and outcome for auditability. Support partial approvals up to cap, with clear communication to vendor and tenant regarding scope limits.

Acceptance Criteria
Auto-Approval Within Dynamic Threshold (Off-Hours, Low-Risk)
Given a work order with risk band "Low" during an off-hours time window and an estimate total below the computed dynamic threshold for that context When the estimate is submitted Then the system auto-approves the work within 30 seconds And generates a single-use approval token tied to the ticket and vendor with an amount limit equal to the approved amount And encumbers the approved amount against the correct budget for the property/unit And dispatches vendor instructions containing token, approved amount, scope, and SLA And updates ticket status to "Approved — Auto" And notifies the tenant and manager within 60 seconds And appends a decision log with timestamp, inputs (risk band, policy version, time window), threshold used, estimate amount, and outcome "Auto-Approved"
Exceeding Cap: Hold and Approval Routing with Delta Calculation
Given the computed dynamic threshold for the ticket's context and an estimate total that exceeds the threshold When the estimate is submitted Then the ticket status changes to "Awaiting Approval" within 30 seconds And no approval token is issued and no vendor dispatch is sent And the system calculates the delta (estimate - threshold) and includes it in the approval request And routes the approval request to the correct approver list per policy And notifies the manager/approver(s) and tenant that approval is pending And appends a decision log with timestamp, inputs (risk band, policy version, time window), threshold, estimate, delta, and outcome "Requires Approval"
Partial Approval Up to Cap with Scope Communication
Given policy allows partial approvals and the estimate exceeds the computed dynamic threshold When the estimate is submitted Then the system issues a partial approval up to the threshold amount and sets ticket status to "Partially Approved" And generates an approval token capped at the threshold and encumbers exactly the threshold amount And dispatches vendor instructions that clearly state scope limited to work up to the cap and that additional work requires approval And notifies vendor, tenant, and manager of the cap amount and remaining delta And appends a decision log with timestamp, threshold, estimate, cap amount, remaining delta, and outcome "Partial Approval"
Approval Token and Budget Encumbrance Integrity
Given a ticket that has been auto- or partially approved When duplicate estimate events or retries are received Then token and encumbrance creation is idempotent and no duplicates are created And each token is unique, references the ticket and vendor, and enforces the cap at point of redemption And encumbrances record property, unit, GL/account, amount, and cannot exceed the approved cap And revoking a token releases the encumbrance within 60 seconds and appends a reversal log event
Audit Log Completeness and Immutability
Given any approval decision (auto, partial, requires approval) or change (revocation, revision) When an auditor queries the ticket history Then an append-only log entry exists with UTC timestamp (ISO 8601), risk band, policy version ID, time window, threshold used, estimate amount, decision outcome, approver/system actor, token ID (if any), and delta (if any) And log entries are immutable; corrections are recorded as new events with a reference to the prior entry And logs are filterable by date range, outcome, policy version, risk band, and exportable to CSV/JSON
Revised Estimate Handling and Cap Enforcement
Given a ticket with an existing auto- or partial approval and an available cap amount When a revised estimate is submitted Then if revised total is less than or equal to the available cap, the encumbrance and token reflect the new amount within 30 seconds and stakeholders are notified And if revised total exceeds the available cap, the over-cap delta is held for approval, the token cap is not increased, and the approval routing is triggered for the delta And vendor instructions include "stop at cap" guidance and a link to request approval for the delta And a log entry records old amount, new amount, cap remaining, delta, and outcome
Smart Escalation and Approver Routing
"As an owner approver, I want over-threshold requests routed to the right approver based on amount and duty schedule so that exceptions are handled fast and by the right person."
Description

Route over-threshold or high-risk incidents to the correct approver tier based on amount, category, ownership entity, and on-call schedule. Support multi-channel notifications (push, SMS, email), SLAs for response, and auto-escalation to backups. Include quiet-hours preferences, surrogate approvers, and a one-tap approve/deny/adjust action with reason capture. Ensure fail-safe behavior if approvers are unreachable, including temporary emergency caps when safety-critical risks are detected.

Acceptance Criteria
Tiered Routing by Amount, Category, Entity, and On‑Call
Given an incident whose estimated cost exceeds the dynamic threshold or is flagged high-risk And tier rules exist mapping amount ranges and categories per ownership entity to approver tiers And the current on-call schedule is active for that entity When the incident is created Then the system selects the correct primary approver tier for the ownership entity based on amount and category And assigns the currently on-call approver for that tier And records the applied rule identifier and version in the audit log And exposes the selected approver and tier in the API response payload And does not notify any approver outside quiet hours unless they are on-call or an override is set
Multi‑Channel Notification with Preferences and Delivery Receipts
Given an approval request is routed to an approver And the approver has notification preferences for push, SMS, and email When the routing occurs Then notifications are sent on all enabled channels within 10 seconds And each notification includes a single-use, signed one-tap action link (approve/deny/adjust) And each channel delivery is logged with timestamp, provider message ID, and status (sent/delivered/failed) And duplicate notifications are throttled to at most one per channel within a 5-minute window
Response SLA Tracking and Outcome
Given an approval request with an SLA defined by risk level (e.g., high-risk 15 minutes, standard 60 minutes) When the request is first sent Then an SLA timer starts and remaining time is stored and visible via API And the first-response timestamp is captured upon link open or action submission And the request is marked SLA Met if first-response occurs within the SLA window, otherwise SLA Breached And all SLA state changes are written to the audit log with correlation ID
Auto‑Escalation to Backup Approvers on Breach or Delivery Failure
Given an approval request is assigned to a primary approver with a configured backup chain And no action is taken by the primary before the SLA expires or all channels hard-fail delivery When the breach or delivery failure condition is detected Then the request escalates to the next backup approver within 30 seconds And the previously assigned approver is notified of the escalation event And the system locks the request to the first approver to act, preventing conflicting decisions And the full escalation chain (timestamps, recipients, outcomes) is captured in the audit log
One‑Tap Approve/Deny/Adjust with Reason Capture and Audit
Given an approver receives a one-tap action link via any channel When the approver taps Approve, Deny, or Adjust Then the action is processed without login if the token is valid and unexpired (TTL 15 minutes, single-use) And Deny requires a non-empty reason, Adjust requires a non-empty reason and a new amount within policy bounds And the system persists approver identity, action, reason (if provided), final approved amount, channel, and timestamp And downstream workflow status updates propagate within 2 seconds and notifications are sent to relevant parties And the action result and payload are recorded in an immutable audit trail
Quiet Hours and Surrogate Routing
Given an approver has quiet hours configured and is not on-call And a non-safety-critical incident requires approval during those quiet hours When routing is performed Then the system does not notify the primary approver And routes to the configured surrogate for that timeframe or, if none, to the entity’s on-call pool And the quiet-hours deferral and surrogate selection are logged with timestamps And any subsequent reminders respect the same quiet-hours policy
Fail‑Safe Emergency Cap When Approvers Unreachable
Given an incident is flagged safety-critical (e.g., gas odor, active leak) And no approver responds within the emergency SLA (5 minutes) or all notification channels fail When the emergency SLA condition is met Then the system automatically authorizes a temporary emergency spend cap per policy for the risk level and entity And dispatches the designated emergency vendor And notifies all approvers and the operations channel of the auto-approval and cap And blocks commitments above the cap until explicit approval is received And creates a mandatory post-facto approval task due within 24 hours
Safe Deferral Guidance and Tenant Messaging
"As a tenant, I want clear guidance when an issue is safe to defer until business hours so that I know what to do now and avoid unnecessary emergency charges."
Description

When risk is low and outside business hours, generate a deferral recommendation with safety checks, step-by-step mitigation instructions, and clear expectations on next contact time. Deliver via SMS, email, and in-app chat with read receipt and acknowledgment. If acknowledgment is not received or new risk signals appear, auto-cancel deferral and escalate. Provide multilingual templates, accessibility-compliant content, and a tenant-friendly explanation of cost implications to reduce unnecessary emergency dispatches.

Acceptance Criteria
After-Hours Low-Risk Leak: Deferral via SMS with Read Receipt and Acknowledgment
Given a maintenance request is received outside configured business hours for the property and the system classifies risk as Low and the deferral policy for the issue type is enabled When the system generates tenant guidance Then an SMS is sent within 60 seconds to the tenant’s verified phone And the message includes: safety checks, 3–7 step mitigation instructions, the exact next contact time in the property’s local timezone, a tenant-friendly cost implication note showing after-hours surcharge vs. business-hours estimate, and a single-tap/YES acknowledgment action And a read receipt is captured via link open or in-app view and time-stamped in the audit log And the deferral state is marked Active until acknowledgment or cancellation
Multi-Channel Fallback and Consolidated Read/Ack Tracking
Given the SMS send attempt fails or the tenant has no mobile number on file When sending deferral guidance Then email is sent to the tenant’s primary email and an in-app chat message is posted within 60 seconds And content remains consistent across channels, including safety checks, mitigation steps, next contact time, cost explanation, and acknowledgment control And a single acknowledgment token is shared across channels so the first acknowledgment finalizes the deferral and disables further prompts And read receipts from any channel (email open, link click, in-app view) are consolidated into one record per tenant And all delivery, read, and acknowledgment events are logged with channel, timestamp, and outcome
Safety Checklist and Mitigation Content Requirements
Given a low-risk issue qualifies for deferral under policy When composing the deferral message Then the message contains a safety checklist with at least 3 checks specific to the issue type And provides 3–7 numbered mitigation steps written at or below 8th-grade reading level And includes clear “Stop and escalate if…” boundaries with examples of high-risk signals And includes a cost implication explanation stating potential savings by deferring (percent or range) based on policy And includes a link to detailed instructions/FAQ in-app
No-Acknowledgment Timeout Escalation
Given a deferral message has been delivered and the deferral state is Active When no acknowledgment is received before the deferral_ack_timeout policy duration (default 30 minutes) expires Then the system automatically cancels the deferral and escalates to the on-call workflow within 2 minutes And the tenant is notified that dispatch is being arranged and why And the manager/on-call receives an alert with transcript, risk assessment, and context And the audit log records timeout, cancellation, and all notifications
New Risk Signal Cancels Deferral and Escalates
Given a deferral is Active When any of the following occur: the tenant replies with an escalation keyword, uploads new media that increases risk score to Medium or higher, sends a message containing high-risk keywords, or a connected sensor reports an alert Then the system cancels the deferral within 2 minutes and escalates to on-call And the tenant receives a safety-first message acknowledging the change and next steps And the on-call queue is updated with priority and full context attached And all state changes are recorded in the audit log
Multilingual Templates and Language Selection
Given a tenant’s preferred language is known or detected When generating and sending deferral guidance Then the message uses the tenant’s preferred language template (supported at minimum: English and Spanish) And dates/times and currency/percentages are localized to the tenant’s locale And if the language is unsupported, the system defaults to English and offers a one-tap link to request another language And translated content preserves placeholders/variables and remains semantically equivalent And the tenant/manager can preview and switch the language in-app before sending
Accessibility and Readability Compliance Across Channels
Given deferral content is prepared for SMS, email, and in-app chat When the content is rendered and delivered Then in-app and email variants meet WCAG 2.1 AA for contrast, semantics, focus order, and include descriptive link text and alt text for images And the SMS variant is 480 characters or fewer, or segmented with clear [1/2], [2/2] markers and includes an actionable acknowledgment instruction And phone numbers and emergency links are tappable across channels And screen readers correctly read ordered steps and safety checks in-app And the overall readability is at or below 8th-grade level by Flesch-Kincaid or equivalent measure
Policy Simulator, Versioning, and Audit Trail
"As a compliance analyst, I want to simulate policy changes and retain an audit trail of approvals so that we can optimize thresholds without risking compliance or budget overruns."
Description

Enable simulation of threshold policies against historical tickets to forecast auto-approval rates, spend, SLA compliance, and after-hours escalations before publishing changes. Maintain versioned policies with diff views, effective dates, rollback capability, and change approver workflow. Provide exportable audit logs covering inputs, scores, decisions, and messages for each ticket to meet compliance and owner reporting needs.

Acceptance Criteria
Run Simulation to Forecast Auto-Approvals, Spend, SLA, and After-Hours Escalations
Given a draft threshold policy version and a selected historical date range (1 day to 24 months) with portfolio filters (property group, issue category, severity), When the user runs the simulation, Then the system processes up to 10,000 tickets synchronously and returns results within 2 minutes. Then simulations over 10,000 tickets are processed asynchronously and complete within 15 minutes with an in-app notification on completion. Then the results display: auto-approval rate (%), projected spend total ($) and delta vs current policy ($ and %), SLA on-time rate (%) by issue category, and count of after-hours escalations. Then the results also show sample size (N), filters applied, policy version ID, and simulation timestamp. Then tickets missing required fields (cost, timestamps, category) are excluded and counted in an Exclusions metric. Then the user can export the simulation results to CSV or JSON.
View Policy Diff and Schedule Effective Date
Given a current policy and a draft policy version, When the user opens the Diff view, Then changes are listed rule-by-rule with added/removed/modified indicators showing old vs new threshold values, risk weights, approver tiers, and deferral rules. Then the user can schedule an effective date/time in the future (>= current time + 5 minutes) in the organization’s time zone. Then scheduling cannot overlap an existing scheduled publish for the same policy scope and shows a validation error if attempted. Then saving a draft requires a change title and reason.
Policy Change Approval Workflow
Given a draft or scheduled policy version requiring approval, When the requester submits for approval, Then approvers are assigned based on policy scope and highest affected spend threshold, and each receives in-app and email notifications. Then approvers can Approve or Reject with a mandatory comment, and their decision is recorded with timestamp, user ID, and version ID in the audit log. Then publishing is disabled until all required approvals are recorded. Then a rejection returns the version to Draft and notifies the requester. Then approvals expire after 7 calendar days or upon any subsequent edit to the draft.
Rollback to a Prior Policy Version
Given an active policy has one or more prior published versions, When an authorized admin selects Rollback to version X and provides a reason, Then the system switches the active policy to version X within 60 seconds and records the actor, reason, and timestamps in the audit log. Then the rolled-back version becomes Active and the previously active version becomes Archived. Then rollback affects only new decisions from the effective timestamp and does not retroactively change historical ticket decisions. Then a post-rollback simulation of the last 30 days runs automatically and displays a comparison banner when complete.
Export Audit Logs with Required Fields
Given a date range (up to 24 months) and optional filters (property, issue category, decision outcome), When a user with Audit Export permission requests an export, Then the system generates CSV and JSON downloads containing, per ticket: ticket_id, policy_version_id, normalized inputs, rule hits, risk score, decision outcome (auto-approved, escalated, deferred), approver IDs (if any), messages sent (type and channel), timestamps for each decision step, and final cost (if available). Then exports up to 100,000 rows are delivered within 5 minutes; larger exports are delivered as a zipped batch within 30 minutes. Then each export includes row count and a SHA-256 checksum. Then exports are retained for download for 7 days and are immutable.
Decision Traceability and Replay
Given a ticket ID and a selected policy version (current or draft), When the user runs Decision Replay, Then the system presents a step-by-step evaluation showing input normalization, rules evaluated, thresholds applied, intermediate scores, final decision, and message templates selected. Then the replay result is labeled Simulated and does not alter the ticket state or notifications. Then the user can compare Simulated vs Actual decision side-by-side and export the trace to JSON.
After-Hours Deferral and Escalation Forecasting
Given organizational after-hours windows and safety-critical categories are configured, When a simulation is run, Then the results include counts of tickets forecast to be deferred to business hours and those escalated after-hours, segmented by issue category and severity. Then safety-critical categories (e.g., gas odor, fire, active flood) are never forecast as deferred even if thresholds would otherwise allow deferral. Then the simulator flags with a warning if projected SLA breach rate increases by more than 2 percentage points vs current policy. Then the results include an estimate of cost impact from deferrals ($) and mean response time delta (minutes).

Next‑Day Saver

Compares emergency vs. next‑day options with a simple risk‑and‑cost view, then books the earliest daylight slot when it’s safe to wait. Communicates the plan to tenants with mitigation steps and live status. Avoids premium rates without eroding trust or timelines.

Requirements

Real-Time Risk Assessment Engine
"As a property manager, I want the system to assess if an issue can safely wait until daylight so that I avoid emergency premiums without risking tenant safety or property damage."
Description

Evaluate incoming maintenance requests to determine whether deferring to the next daylight window is safe. Ingests triage photos, issue type (e.g., leak, electrical, HVAC), tenant-reported severity, property criticality, and external data (weather alerts, temperature) to compute a risk score and classification (Safe to Wait, Borderline, Emergency). Integrates with FixFlow’s photo-first intake and categorization, applies rule sets and ML-based heuristics, and outputs a decision with human-readable rationale. Reassesses risk when new evidence arrives and exposes scores via API/UI for downstream booking and approval flows. Expected outcome: accurate, explainable recommendations that reduce unnecessary emergency dispatches without compromising safety.

Acceptance Criteria
End-to-End Initial Risk Scoring from Photo-First Intake
Given a new maintenance request containing at minimum: 1+ photos, issue type, tenant-reported severity, property criticality, property location, and timestamp; and external data (weather alerts, temperature) is reachable When the request is submitted via FixFlow intake Then the engine computes a risk score (0–100) and classification in {Safe to Wait, Borderline, Emergency} within 3.0 seconds at p95 And returns a human-readable rationale including at least 3 contributing factors and a confidence value [0–1] And persists the decision with requestId, score, classification, rationale, modelVersion, rulesetVersion, and assessedAt And emits a risk.assessed event for downstream booking/approval consumers
Reassessment on New Evidence and State Transitions
Given an existing request with a previously stored risk decision When new evidence is attached (e.g., additional photos, updated severity, sensor reading, or new weather alert) Then the engine recomputes risk within 2.0 seconds of evidence ingestion And records an audit trail entry containing previous and new scores/classifications, diff of inputs, and evidenceId And updates API/UI consumers within 2.0 seconds (event-to-UI latency p95) And if the new classification is Emergency, a risk.escalated event is emitted immediately; if Safe to Wait, sets nextDayEligible=true in the payload
External Data Integration and Fallbacks
Given external data providers are responsive and data freshness <= 10 minutes When scoring requests that depend on external conditions Then the engine incorporates current weather alerts and temperature and logs provider source and timestamps Given external data is unavailable, slow (>800 ms), or stale (>60 minutes) When scoring proceeds Then the engine uses last-known-good values if <= 60 minutes old; otherwise flags externalDataStatus=missing And includes fallback details in rationale And does not classify as Safe to Wait for issue types marked externalDataCritical unless externalDataStatus=ok
Human-Readable Rationale and Explainability
Given a completed risk assessment Then the rationale includes: top 5 factors with qualitative influence (↑/↓) and weights or confidence, triggered rules with names, salient ML feature contributions, photo analysis findings, tenant severity, property criticality, and referenced external conditions with units And readability score <= Grade 9 (Flesch-Kincaid), length <= 800 characters And content is deterministic for identical inputs and localizable (i18n keys present) And for Safe to Wait, includes 1–3 mitigation steps appropriate to issue type
API Contract and Performance for Risk Decisions
Given an authenticated client with scope risk:read When requesting GET /risk-decisions/{requestId} Then the service responds 200 with: requestId, score, classification, rationale, confidence, modelVersion, rulesetVersion, evidenceSummary, externalDataStatus, assessedAt, nextDayEligible And responds 401 for unauthenticated, 403 for insufficient scope, 404 for unknown requestId And p95 response time <= 300 ms, p99 <= 800 ms; availability >= 99.9% monthly And emits webhook risk.assessed to subscribed endpoints within 1.0 second of decision commit (p95)
UI Display, Accessibility, and Auditability
Given a property manager viewing a request in FixFlow When a risk decision exists Then the UI shows classification badge (green/yellow/red), numeric score, rationale, confidence, external data status, and last updated timestamp And updates the display within 2.0 seconds after a new assessment event And provides an accessible experience meeting WCAG 2.1 AA (color contrast >= 4.5:1, focus order, ARIA labels) And exposes an audit log listing all assessments with timestamp, score, classification, and evidence link
Accuracy and Safety Outcome Targets
Given a labeled validation dataset covering leaks, electrical, HVAC, and top issue types across seasons When evaluated offline prior to launch Then Emergency recall >= 0.95 and precision >= 0.90; Safe to Wait false-negative rate <= 1.0%; ROC AUC >= 0.95 And Borderline bucket contains 5–20% of cases and includes uncertainty drivers in the rationale Given the first 90 days of production with human review on a 10% stratified sample When measuring outcomes Then emergency dispatches are reduced by >= 20% vs baseline with 0 critical safety incidents attributable to misclassification, and human agreement with engine classification >= 95%
Cost-and-Risk Comparison View
"As a landlord, I want a simple side-by-side comparison of emergency vs next-day options so that I can make a confident, cost-aware decision quickly."
Description

Present a concise side-by-side view contrasting Emergency Now versus Next‑Day options. Surfaces estimated total cost (labor premiums, after-hours surcharges, travel), potential mitigation costs, and risk level derived from the assessment engine. Pulls vendor rate cards, historical job durations, and materials assumptions from FixFlow. Displays earliest daylight slot availability, potential savings, and confidence indicators. Accessible in manager/owner dashboard and mobile, with clear labels and color cues to support rapid, trustworthy decisions.

Acceptance Criteria
After‑Hours Water Leak: Emergency vs Next‑Day Cost Breakdown
Given a maintenance request with a vendor rate card containing standard and after‑hours labor rates, travel fees, and materials assumptions, and historical average job duration is available When the manager opens the Cost‑and‑Risk Comparison View after business hours Then Emergency Now estimated total equals (after‑hours labor rate × expected duration) + after‑hours surcharge(s) + travel fee + materials estimate And Next‑Day estimated total equals (standard day labor rate × expected duration) + standard surcharge(s) + travel fee + materials estimate And both totals display as currency with symbol and two decimals And both options appear side‑by‑side labeled "Emergency Now" and "Next‑Day" with line‑item breakdowns available via expand/collapse
Earliest Daylight Slot Display When Safe to Wait
Given vendor availability calendar data in FixFlow and the property’s local timezone And a daylight window configured in FixFlow (default 07:00–19:00 local if unset) When the assessment engine marks the issue safe to wait Then the Next‑Day card displays the earliest available vendor start time within the daylight window in the property’s local time, formatted as "Tue, 24 Oct, 07:30" And if no qualifying slot exists within 72 hours, the label reads "Next daylight slot: >72h" and the card shows a warning badge
Potential Savings and Percent Savings Calculation
Given Emergency Now and Next‑Day estimated totals are computed When the comparison view renders Then Potential Savings equals max(Emergency Now − Next‑Day, 0) and is displayed as currency with two decimals And Percent Savings equals round((Potential Savings / Emergency Now) × 100)%, capped at 100% And if Emergency Now ≤ Next‑Day, Percent Savings displays "0%" and the savings badge uses a neutral color
Confidence Indicator Derived from Data Completeness and Variability
Given the following inputs: vendor rate card presence, materials assumptions presence, historical duration sample size (n), and coefficient of variation (CV) When computing confidence for the estimates Then Confidence = High if rate card present AND materials assumptions present AND n ≥ 10 AND CV ≤ 30% And Confidence = Medium if exactly one of the above conditions fails OR 3 ≤ n ≤ 9 And Confidence = Low if two or more conditions fail OR n < 3 And Confidence = N/A if historical duration data is absent AND no rate card is present And the UI displays a colored indicator (High=green, Medium=amber, Low=red, N/A=gray) with a tooltip listing which data sources were used
Risk Level Integration and Clear Labeling from Assessment Engine
Given the assessment engine returns a risk level for waiting (Low, Moderate, High, Critical) and a Safe‑to‑Wait boolean When the comparison view loads Then the Next‑Day card displays the risk level text, icon, and color cue supplied by the engine And if risk is High or Critical OR Safe‑to‑Wait = false, the Next‑Day card shows a "Not recommended" badge and the savings section is visually de‑emphasized And the Emergency Now card displays "Risk mitigation: immediate response" with a neutral icon
Dashboard and Mobile Accessibility, Performance, and Layout
Given manager/owner access on web dashboard and mobile app When navigating to a maintenance request detail Then the Cost‑and‑Risk Comparison View opens within 2 taps from both web and mobile And initial content renders within 2.0s on broadband (≥25 Mbps) and within 4.0s on 4G (≥10 Mbps) on reference devices And the view is fully usable from 320 px to 1440 px widths without horizontal scroll; primary tap targets are ≥44 px; body text is ≥14 px on mobile
Accessible Labels and Color Cues Meet WCAG 2.1 AA
Given WCAG 2.1 AA accessibility requirements When viewing the comparison on web or mobile Then all color cues are accompanied by text labels and/or icons; no information is conveyed by color alone And text and interactive elements meet contrast ratio ≥ 4.5:1 against their backgrounds And screen readers announce card titles, estimated totals, risk level, potential savings, and earliest daylight slot in a logical reading order using clear labels: "Estimated Total", "Potential Savings", "Risk Level", "Earliest Daylight Slot"
Earliest Daylight Auto-Booking
"As a tenant, I want the repair scheduled for the earliest daylight time so that my issue is resolved promptly without overnight disruption."
Description

Automatically identifies and books the earliest qualifying daylight appointment once waiting is approved. Queries preferred vendor pools by trade, SLA, geography, and certifications; respects tenant availability windows and building access constraints; optimizes for travel time and first-available slots (e.g., 7–10am). Places holds or books directly via vendor integrations, writes to technician calendars, and confirms with all parties. Includes fallback vendor cascades and soft-cancel logic if conditions change before the appointment.

Acceptance Criteria
Auto-Booking Gated by Wait Approval
Given a maintenance request is marked safe-to-wait And a Wait Approved flag is present on the work order When the auto-booking service runs Then the system initiates vendor search and selection And if the Wait Approved flag is missing or revoked before booking Then the system does not create holds or bookings and logs the reason
Earliest Daylight Slot Selection Across Vendor Pool
Given a request has a required trade, property location, SLA targets, and certification requirements And preferred vendors are filtered by trade, SLA compliance, service geography, and certifications And vendor APIs provide availability windows and travel-time estimates And the next local sunrise time for the property timezone is known When the system evaluates available slots for the next 3 business days Then the chosen slot has the earliest start time on or after next local sunrise and within vendor working hours And if multiple vendors offer the same earliest start time Then the slot with the lowest travel-time score is selected; if tied, choose the vendor with the highest SLA score; if still tied, choose the vendor with the fewest open orders And if availability is provided as windows (e.g., 7–10am), the earliest window on the earliest day is selected And the selection rationale (vendor list, scores, chosen slot) is logged for audit
Tenant Availability and Building Access Compliance
Given tenant availability windows and building access constraints are stored on the work order When selecting an appointment Then the appointment start and end occur fully within a tenant availability window And comply with building access constraints (e.g., doorman hours, access codes, elevator reservations) And if no slot satisfies both constraints within the next 3 business days Then the system does not book and requests updated availability from the tenant while notifying the manager
Calendar Booking and Confirmation Notifications
Given a slot has been selected and the vendor integration supports booking or hold placement When booking is executed Then a hold or confirmed booking is created on the vendor/technician calendar with property address, contact info, access notes, and work order ID And the FixFlow property timeline shows the appointment within 60 seconds And confirmation messages with mitigation steps and a live status link are sent to tenant, vendor, and manager within 60 seconds And if the vendor API call fails transiently Then the system retries up to 3 times with exponential backoff and surfaces a clear error if all retries fail
Fallback Vendor Cascade on No Availability
Given no preferred vendor can meet daylight and constraint requirements in the target window When the fallback cascade runs Then the system queries secondary vendor tiers in priority order until a qualifying slot is found or tiers are exhausted And each attempt logs timestamp, vendor, and rejection reason And if all tiers are exhausted Then the system escalates to the manager with the best next non-daylight option and does not auto-book
Soft-Cancel and Replan on Condition Change
Given an appointment hold or booking exists And the issue severity, tenant availability, or access conditions change before the appointment start When the change invalidates the current plan (e.g., escalated to emergency, tenant unavailable, access revoked, issue resolved) Then the system issues a soft-cancel through the vendor integration when supported to avoid fees And notifies tenant, vendor, and manager of the change with updated next steps And automatically re-runs slot selection if work is still required and waiting remains approved; otherwise closes the work order And all actions and state changes are recorded in the audit log
Tenant Mitigation Guidance Messaging
"As a tenant, I want clear steps to minimize damage while I wait so that I feel safe and the property is protected."
Description

Deliver clear, issue-specific mitigation instructions to tenants during the wait period. Generates step-by-step actions (e.g., close main water valve, switch off breaker, place bucket, open windows) with safety warnings and rich media snippets. Sends via SMS, email, and in‑app with multilingual templates and read/acknowledge tracking. Captures tenant confirmations and photos as proof of completion, feeding back into risk reassessment. Falls back to an automated call flow when messages are not acknowledged.

Acceptance Criteria
Issue-Specific Mitigation Instructions with Safety and Media
Given a tenant-submitted maintenance request classified by Next‑Day Saver as safe to wait until daylight and an identified issue type When mitigation guidance is generated Then the guidance contains 3–7 ordered steps tailored to the issue type And at least one explicit safety warning relevant to the issue is included And each step uses plain language at or below 8th-grade reading level And at least one rich media snippet (image, short video, or annotated diagram) is attached and mapped to the appropriate step And the guidance includes an estimated time to complete and required materials, if any
Multi-Channel Delivery and Consistency
Given the tenant has at least one verified contact channel (SMS, email, in‑app) When mitigation guidance is dispatched Then the same content (steps, safety warnings, media links) is delivered across all available channels within 60 seconds And each outbound message includes a unique tracking ID and deep link to the in‑app flow And delivery success or failure is logged per channel with timestamp and reason And if any channel fails, the system retries once and falls back to remaining channels automatically
Multilingual Templates and Locale Handling
Given a tenant language preference or locale can be determined from profile or device When mitigation guidance is generated Then the system selects the matching language template if available And if unavailable, defaults to English and indicates the language used at the top of the message And tenants can switch language in one tap or click from the message link, which reloads the guidance in the chosen language And right‑to‑left languages render correctly for text and media captions
Read and Acknowledge Tracking with Reminders
Given mitigation guidance has been delivered When the tenant opens the guidance link or views it in‑app Then the system records a read receipt with timestamp and channel And when the tenant taps Acknowledge after reviewing safety warnings and steps Then the system records an acknowledgment event with timestamp and device And if no acknowledgment is received within 10 minutes, the system sends up to 2 reminders spaced 10 minutes apart And all reminder outcomes are logged
Proof Capture: Step Confirmations and Photos
Given the guidance requires tenant actions When the tenant submits confirmations and photos for required steps Then the system enforces at least one photo for steps marked Photo Required And accepts uploads via MMS and in‑app with minimum resolution 720x720 and max file size 10 MB per asset And timestamps and step associations are stored with each asset And the tenant receives immediate feedback on successful upload or clear error messages on failure
Risk Reassessment on Tenant Updates
Given tenant acknowledgments and/or proof assets are received When the risk engine reevaluates the issue Then the risk score is updated within 2 minutes And if the score exceeds the emergency threshold, the job is escalated to emergency dispatch and the tenant is notified immediately And if the score remains within safe-to-wait bounds, the next‑day plan and ETA are confirmed back to the tenant
Automated Call Flow Fallback on Non‑Acknowledgment
Given mitigation guidance was sent and no acknowledgment is recorded after all reminders When 30 minutes have elapsed since initial dispatch Then the system initiates an automated TTS call to the tenant in their preferred language And the IVR collects keypad confirmation that safety warnings were heard and mitigation steps will be followed And if the call is unanswered or fails twice, the system escalates to on‑call technician or live operator and records the outcome And all call attempts, responses, and escalation actions are logged
Dynamic Live Status and Escalation
"As a property manager, I want live updates and automatic escalation when conditions worsen so that issues never slip through and risk is controlled."
Description

Provide real-time status to tenants and managers with a timeline of events, risk score changes, and technician ETA once booked. Continuously monitor new signals (tenant updates, weather shifts, sensor data where available) to re-evaluate risk. Auto-escalate to emergency dispatch and notify stakeholders if thresholds are crossed or if vendor cancellations threaten timelines. Supports rebooking logic, SLA breach alerts, and push/SMS/email notifications with suppression rules to prevent spam.

Acceptance Criteria
Tenant/Manager Timeline With Real‑Time ETA
Given an active work order with a booked technician and at least one status source, When a new status event or ETA is received, Then update both tenant and manager timelines within 60 seconds showing current status, technician ETA in property local time, last event with timestamp, and current risk score; And record an immutable audit entry with event source and received_at; And display a Last updated timestamp derived from server time; And ensure time zone is the property’s locale.
Continuous Risk Re‑Evaluation From New Signals
Given a job is in Next‑Day Saver monitoring with a baseline risk score (0–100), When a tenant submits new media/text, a severe weather alert covers the property geo, or a connected sensor crosses its threshold, Then recompute risk within 60 seconds; And if risk delta ≥ 15 points or risk ≥ 70, mark risk_state = High and write a risk_evaluation event to the timeline including old_score, new_score, and signal_type; Else write risk_evaluation with state = Unchanged.
Auto‑Escalation to Emergency Dispatch
Given a job scheduled for next‑day service, When risk ≥ 70 OR the booked vendor cancels and the earliest rebookable ETA exceeds safe-by time or SLA, Then initiate emergency dispatch within 2 minutes; And notify tenant and manager via push and SMS (email fallback) immediately including mitigation instructions; And log escalation_reason, dispatch_request_id, and contacted_vendors; And suppress non‑critical notifications for 30 minutes post‑escalation to prevent spam.
Automatic Rebooking After Cancellation
Given a booked job with SLA targets and a preferred vendor list, When the current vendor cancels or fails to confirm within the confirmation window, Then auto-search and rebook to the earliest daylight slot (08:00–18:00 local) meeting SLA and current risk constraints within 5 minutes; And prefer preferred vendors before marketplace vendors; And send tenant a rebooking confirmation (non‑emergency requires explicit confirm, emergency auto-confirms); And if no slot meets SLA, create an escalation_candidate event and notify manager.
SLA Breach Prediction and Alerting
Given SLA targets are configured (acknowledge ≤ 15 min, onsite ≤ 24 h unless emergency), When predicted arrival or countdown indicates a breach or a breach occurs, Then emit an SLA_breach alert within 60 seconds; And notify manager via push/SMS/email with breach type, variance, and recommended actions; And flag the job in dashboard; And limit to one alert per breach type per 6 hours unless status improves and degrades again.
Notification Suppression, Quiet Hours, and Digesting
Given notification channels are enabled, When multiple non‑critical updates occur within 10 minutes, Then coalesce into a single digest; And enforce quiet hours 21:00–07:00 local allowing only critical alerts (risk ≥ 70 or emergency dispatch) to bypass; And apply rate limits: max 1 non‑critical per 6 hours per recipient; And provide per‑tenant unsubscribe for non‑critical channels; And surface all suppressed updates in the in‑app timeline to maintain transparency.
Owner/Manager Approval with Guardrails
"As an owner, I want to set approval rules and review borderline cases so that we balance cost savings with acceptable risk."
Description

Implement configurable policy thresholds that auto-approve Next‑Day decisions under defined risk and cost limits. For borderline cases, generate an approval request containing the risk/cost summary, mitigation steps, and proposed daylight slot. Support single- or multi-approver flows, response timeouts with safe defaults, and capture explicit approvals/denials with timestamps. Integrates with email/mobile push and in‑app approvals to minimize decision latency.

Acceptance Criteria
Auto-Approval Within Policy Thresholds
Given a Next‑Day Saver decision candidate with calculated risk_score and estimated_cost And an active approval policy with risk_threshold and cost_threshold for auto-approval When risk_score <= risk_threshold and estimated_cost <= cost_threshold Then the system auto-approves the Next‑Day plan without human approval And schedules the earliest available daylight slot within the policy’s allowed window And records an auto-approval decision with reason "WithinPolicyThresholds" and all input parameters And completes the decision in ≤ 5 seconds from case assessment
Borderline Approval Request Content
Given a decision candidate that violates at least one auto-approval threshold but is not classified as emergency by policy When generating an approval request Then the request payload includes: risk_score, risk_factors, cost_estimate, cost_breakdown, mitigation_steps, proposed_daylight_slot, media_references, and tenant_impact_summary And the payload includes policy identifiers and links to policy change logs And all monetary values include currency code and are rounded to two decimals And the request presents one-tap Approve and Deny actions with a deep link to the in-app decision view
Multi-Approver Policies (Any vs All)
Given a policy configured for multi-approver flow with mode = "ANY" or "ALL" And approvers A and B are active and reachable When an approval is requested Then for mode "ANY", the first explicit approval finalizes the decision; any subsequent approvals/denials are recorded as "Superseded" And for mode "ALL", approvals from all required approvers must be received before finalizing; any single denial finalizes as Denied And the decision status updates in real time for all approvers And the final outcome and approver list are persisted in the audit log
Response Timeout Safe Default
Given a policy with response_timeout_minutes and timeout_action configured When no required approver action is received within response_timeout_minutes Then the system applies timeout_action And if timeout_action = "ProceedNextDay", it is only applied when current risk_score <= safe_wait_max_risk; otherwise escalate to "EmergencyNow" And the system documents the timeout event with timestamps, action taken, and current risk/cost snapshot And all pending approval links are expired and marked "TimedOut"
Explicit Approval/Denial Capture and Audit
Given an approver interacts via email link, mobile push, or in-app approval UI When they select Approve or Deny Then the system records a decision including: approver_id, channel, decision, decision_reason_code (required for Deny), optional comment, and ISO-8601 UTC timestamp And the record is immutable and append-only; any corrections are appended as new entries And the decision history shows precise ordering by timestamp with stable event IDs
Notification Delivery and Latency
Given an approval request is created When notifications are sent via email, mobile push, and in-app Then at least one channel is delivered successfully within 60 seconds, or the system retries per backoff policy and escalates to SMS (if enabled) after 2 failed attempts And delivery and read receipts are tracked and visible in the case timeline And duplicate notifications are suppressed within a 10-minute dedup window per channel And opt-out or unreachable channels are flagged and excluded from further retries
Idempotent and Concurrent Decision Handling
Given multiple approval events (clicks or API callbacks) are received concurrently for the same request When processing decisions Then the system enforces idempotency using a request_id and detects duplicates within a 5-minute window And only the first policy-valid decision is applied; subsequent events are recorded as "Duplicate" or "Superseded" without changing the outcome And conflicting inputs in "ALL" mode result in Denied; in "ANY" mode the first decision stands And the final state is consistent across all clients within 2 seconds
Decision Audit Trail and Compliance Logging
"As a portfolio manager, I want a complete audit trail of Next‑Day Saver decisions so that I can justify actions to tenants, insurers, and regulators."
Description

Record a comprehensive, immutable log for each Next‑Day Saver decision: incoming evidence (photos, messages), risk scores and versions, comparisons shown, approvals, bookings, and notifications. Link logs to the maintenance ticket and allow export for insurance or legal purposes. Enforce data retention and access controls aligned with GDPR/CCPA and company policies. Provide a query interface for support and portfolio analytics.

Acceptance Criteria
Comprehensive Decision Event Capture
Given a Next‑Day Saver decision session is initiated for a maintenance ticket When the user progresses from intake through comparison, approval, booking, and notifications Then the audit trail records timestamped entries for: incoming evidence (photos, messages) with source and SHA‑256 hash; calculated riskScore with modelVersion; comparison options shown (emergency vs next‑day) including cost, risk, and rationale; explicit approval actor and timestamp; booking details (vendor, date, time window, confirmationId); notifications sent (channel, recipient, templateId and variables or content snapshot) And each entry includes ticketId, decisionSessionId, actor (userId/serviceId), IP/service origin, timezone, and sequence number And the audit entries are durably persisted within 2 seconds of the action and retrievable via API/UI within 5 seconds
Tamper Evidence and Immutability
Given any attempt to update or delete an existing audit entry When the operation is executed Then the system prevents in‑place modification and instead records a non‑destructive correction event referencing the original entryId and reasonCode And every entry contains contentHash and previousHash to form an append‑only chain verifiable end‑to‑end And a daily integrity job recomputes hashes for 100% of entries and raises a P1 alert on any mismatch And administrators can view lastIntegrityCheck timestamp and result status per portfolio
Access Control and PII Protection
Given role‑based permissions and portfolio scoping are configured When a user requests to view or export an audit trail Then access is allowed only if the user has scope to the ticket’s portfolio and the 'Audit.View' or 'Audit.Export' permission, else a 403 with reasonCode is returned And PII fields (e.g., tenant name, phone, email, photo content) are masked or excluded for roles without 'PII.View' And all access events are logged with userId, ticketId, action, fieldsExposure, timestamp, IP, retained per policy And consent/lawful basis metadata is stored and surfaced for each tenant data element
Retention, Erasure, and Legal Hold
Given a configurable retention policy (policyRetentionYears default 7) and legal hold support When an audit record reaches retention threshold without an active legal hold Then PII is anonymized and non‑essential artifacts are purged via append‑only tombstone events preserving chain validity And when a legal hold is active, no erasure/anonymization occurs until the hold is cleared and actions are deferred with justification And data subject erasure requests are fulfilled within 30 days by removing/anonymizing PII unless a documented lawful basis to retain exists, with decisions logged And a retention job produces a signed deletion/anonymization report with counts and hashes
Export Package for Insurance/Legal
Given an authorized user requests an export of a decision session When the export is generated Then the package includes: JSONL of all audit entries; attachments or secure links with checksums for photos/messages; a human‑readable PDF timeline; and a chain‑verification report And the package is cryptographically signed and includes export metadata (requestedBy, requestedAt, reasonCode) And redaction level is selectable (Full, PII‑Masked) based on user permissions And the export completes within 60 seconds for sessions up to 500 entries and 100 MB of artifacts
Query Interface for Support and Analytics
Given a user with 'Audit.Query' permission opens the query interface When they filter by date range, portfolio/property, riskScore range, decision outcome, approver, technician, modelVersion, or perform keyword search Then matching audit sessions are returned with pagination within 2 seconds for up to 100k sessions And users can export result sets to CSV or Parquet respecting access and redaction rules And aggregate metrics (counts, averages, next‑day adoption, estimated cost savings) are displayed with drill‑through to individual sessions
Ticket Linkage and Merge/Split Resilience
Given a maintenance ticket with one or more Next‑Day Saver decisions exists When an authorized user views the ticket Then the UI shows the latest audit status (e.g., Approved, Booked, Notified) and a deep link to the full audit trail And merges/splits preserve references to all related decisionSessionIds with origin and current ticketIds recorded And idempotency ensures only one active decision session is created per ticket unless explicitly overridden with an idempotencyKey And the audit view opens scoped to the selected decision session and enforces the same access controls

Batch Rescue

When an after‑hours visit is unavoidable, auto‑surfaces nearby, compatible jobs to piggyback within the same run. Offers tenants immediate openings, consolidates trips, and improves vendor utilization. Turns one emergency fee into multiple resolved tickets to dilute cost per job.

Requirements

Geospatial Compatibility Matching
"As a property manager, I want the system to automatically surface nearby, compatible tickets when an after-hours emergency is confirmed so that I can add more jobs to the same visit and reduce costs."
Description

Real-time clustering and ranking of open maintenance tickets within a configurable radius of a confirmed after-hours anchor job, applying compatibility rules (trade/skill match, tools/parts availability, building access windows, tenant contactability, SLA priority, estimated duration). Produces a scored list of piggyback candidates with constraint summaries and conflict reasons. Integrates with FixFlow tickets, vendor profiles, and inventory to ensure only actionable jobs are surfaced. Runs automatically upon anchor confirmation and supports manual refresh. Honors tenant preferences (quiet hours, communication channels) and property policies.

Acceptance Criteria
Auto-Trigger on Anchor Confirmation
Given an after-hours anchor ticket is confirmed with an assigned vendor When the confirmation event is saved Then geospatial compatibility matching starts within 2 seconds and completes within 5 seconds for up to 1,000 open tickets And the search radius used equals the configured value on the anchor or its portfolio (default 10 miles if unset) And a timestamped run record with run_id is attached to the anchor
Compatibility Filtering Rules Enforcement
Given an open ticket is within the search radius When the vendor's trade or skill set does not satisfy the ticket requirements Then exclude the ticket with conflict code "skill_mismatch" Given an open ticket is within the search radius When required tools or parts are unavailable for the anchor time window Then exclude the ticket with conflict code "inventory_unavailable" Given an open ticket is within the search radius When the building access window does not overlap the anchor window Then exclude the ticket with conflict code "access_window_conflict" Given an open ticket is within the search radius When the tenant cannot be contacted via allowed channels in the evaluation window Then exclude the ticket with conflict code "tenant_uncontactable" Given an open ticket is within the search radius When property policies disallow after-hours piggybacking for the unit or building Then exclude the ticket with conflict code "policy_block" Given an open ticket is within the search radius When estimated duration would exceed the available after-hours window for the vendor Then exclude the ticket with conflict code "duration_overrun"
Scoring, Ranking, and Constraint Summaries
Given a set of actionable candidates that passed all mandatory filters When scoring is executed Then each candidate is assigned a numeric score between 0 and 100 based on at least distance, SLA priority, and duration fit, with weights configurable and totaling 100 And the candidate list is sorted by score descending; ties are broken by distance ascending, SLA due time ascending, then created_at ascending And each candidate includes a constraint summary listing each rule evaluated with pass/fail and its contribution to the score (percentage or points)
Conflict Reasons for Excluded Tickets
Given a ticket is evaluated and excluded When results are returned via API and UI Then the excluded ticket appears with a non-empty conflict_codes array and human-readable reasons And if multiple rules failed, all corresponding conflict codes are included And every conflict code uses snake_case and is documented in the API schema
Data Integration Freshness and Fallbacks
Given FixFlow tickets, vendor profiles, and inventory data sources are online When matching runs Then it reads the latest committed data with maximum staleness of 60 seconds And if a required data fetch fails or required fields are missing, affected tickets are excluded with conflict code "data_missing" and the run is marked partial with error detail And when data becomes available and a rerun occurs, previously excluded tickets due to "data_missing" are re-evaluated
Manual Refresh and Idempotency
Given a dispatcher clicks Manual Refresh for an anchor When the refresh is requested Then a new matching run executes and atomically replaces the prior candidate list, including updates to scores and summaries And if no underlying data changed since the last run, the returned list, scores, and ordering are identical And the UI/API reflects the new run_id and updated_at timestamp
Tenant Preferences and Quiet Hours Compliance
Given a tenant has quiet hours set and preferred communication channels specified When evaluating candidates for an after-hours anchor occurring within the tenant’s quiet hours Then the ticket is excluded with conflict code "quiet_hours" unless an explicit tenant pre-authorization override flag is present And contact attempts are limited to the tenant’s preferred channels; if none are usable for the timeframe, the ticket is excluded with conflict code "tenant_uncontactable" And the constraint summary for included candidates marks contactability as true and lists the channel to be used
After-Hours Trigger & Eligibility Detection
"As an operations lead, I want the platform to determine when an after-hours visit is unavoidable and eligible for batching so that we only trigger Batch Rescue when it will add value and comply with policies."
Description

Automated detection that an on-site after-hours visit is unavoidable based on triage outcomes, escalation matrix, and approval status. Marks the ticket as an anchor job, defines the batching eligibility window and service radius, and constrains by trade type and policy rules per landlord/portfolio. Provides manual override with justification, audit logs of trigger reasons, and safeguards to prevent triggering when conditions are not met (e.g., safety concerns, legal restrictions).

Acceptance Criteria
Auto Anchor Tagging After-Hours
Given a ticket’s triage outcome requires an on-site visit before the next business day And the escalation matrix for the property authorizes after-hours dispatch And the ticket’s approval status meets or exceeds the matrix threshold And the current time is outside the property’s business hours in the property’s local timezone When the decision engine evaluates the ticket Then the ticket is marked as an Anchor Job with an after-hours flag And the decision payload includes eligibility=true with reason codes: triage_outcome, escalation_matrix, approval_status, timezone_out_of_hours And the anchor creation timestamp is recorded And if any precondition is not satisfied, eligibility=false is returned with corresponding reason codes and no anchor is created
Eligibility Window & Radius Calculation
Given an Anchor Job has been created And a batching policy is defined at property, portfolio, or global default level When the system computes the batching window and service radius Then window_start and window_end are set per policy relative to the anchor’s dispatch/ETA timestamp And service_radius_meters is set per policy And more restrictive child policy overrides parent policy And computed values are persisted on the anchor, exposed via API/UI, and included in the decision payload And values are clamped to system minimums/maximums
Trade and Policy Constraints for Candidate Jobs
Given an Anchor Job with trade_type set and a portfolio policy defining compatible_trades and constraints When the system searches for candidate tickets to batch Then only tickets with trade_type equal to the anchor’s trade or listed in compatible_trades are considered And tickets violating landlord/portfolio do-not-batch rules are excluded And candidates must be within the computed time window and service radius And candidates must meet the required approval status for after-hours per policy And excluded tickets are returned with exclusion_reason codes
Manual Override with Justification & Governance
Given a user with role in {Owner, Portfolio Admin, Dispatcher} views a ticket’s eligibility decision When the user applies a Force Anchor or Suppress After-Hours override Then the UI requires a justification of at least 20 characters And the system records override_type, user_id, role, timestamp, prior_decision, justification in the audit log And eligibility and anchor status update immediately across API and UI And an auto-expiry duration is applied per policy unless set to permanent And a revert action restores the prior automated state and is logged
Safeguards: Do-Not-Trigger Conditions
Given a ticket is flagged with any of: safety_concern=true, legal_restriction=true, tenant_access_denied=true, vulnerable_occupant_unattended=true When the decision engine evaluates the ticket Then after-hours triggering is blocked And eligibility=false with reason codes for each triggered safeguard And a Force Anchor override requires elevated role and dual-approval per policy And the UI surfaces the block and required next steps
Audit Logging & Traceability
Given any trigger, non-trigger, or override decision occurs When the audit log entry is created Then it includes ticket_id, property_id, portfolio_id, decision_version, inputs_snapshot, rules_evaluated, reason_codes, actor (system/user), timestamp, timezone_used, and prior_state And entries are immutable and tamper-evident And logs are queryable by date range, ticket_id, and property_id and exportable as CSV via API And redacted fields comply with PII policy
No Trigger During Business Hours or Next-Day SLA
Given a ticket meets triage and approval conditions And the current time is within the property’s business hours Or a qualified vendor has a next-business-day slot within the policy SLA When the decision engine evaluates the ticket Then eligibility=false with reason codes business_hours or within_sla_next_day And no Anchor Job is created
Route Optimization & Dynamic Batch Scheduler
"As a dispatcher, I want an optimized multi-stop route that adapts as tenants accept offers so that technicians minimize drive time and maximize completed jobs in one run."
Description

Generation of an optimized multi-stop route that sequences the anchor job and selected piggyback tickets to minimize travel time and overtime while honoring access windows, parking/loading constraints, building elevator/service hours, and estimated job durations. Supports real-time re-optimization as tenants accept or decline offers, traffic conditions change, or jobs are added/removed. Outputs ETAs, arrival windows, and technician instructions; writes to technician calendars and updates tenant portals. Integrates with mapping APIs with offline fallback and failure handling.

Acceptance Criteria
Initial After‑Hours Batch Route Generation
Given an after‑hours anchor job and eligible piggyback tickets within 10 km with compatible skill/access requirements When the scheduler runs initial optimization Then it outputs a single multi‑stop route in ≤30 seconds for up to 12 stops And the anchor job remains within its SLA window And the sequence minimizes total travel time + projected overtime cost by ≥10% versus a nearest‑neighbor baseline And no stop violates access windows, building elevator/service hours, parking/loading constraints, or estimated job durations And each stop has an ETA (±5 minutes vs mapping estimate) and a 60‑minute arrival window
Re‑Optimization on Tenant Response Changes
Given offers are pending and tenant responses arrive (accept/decline/reschedule) When any response is received Then the route is re‑optimized in ≤20 seconds reflecting the updated stop set And no surviving stop’s access window or service hours are violated And unaffected stops’ ETAs shift by ≤5 minutes And a change event with a new route version ID is emitted
Traffic‑Aware Re‑Optimization during Active Run
Given the technician is en route and live traffic increases segment travel time by ≥10% for ≥2 minutes When the system detects the sustained travel time delta Then it re‑optimizes the remaining route in ≤60 seconds And any stop at risk of missing its arrival window is moved or swapped to satisfy all constraints And tenants whose windows shift by >10 minutes are notified within 60 seconds And the technician receives the updated sequence and ETAs on mobile
Mid‑Run Job Add/Remove Handling
Given a dispatcher adds a compatible job or removes a stop from an active batch When the change is submitted Then re‑optimization completes in ≤30 seconds And the anchor job remains scheduled within its SLA window And projected overtime does not increase by >15% unless the dispatcher confirms an override And if infeasible, the system returns the top 3 feasible alternatives with reason codes
Calendar and Tenant Portal Sync
Given any route creation or update When calendar and portal synchronization runs Then the technician calendar contains one event per stop with start time, 60‑minute arrival window, address map link, notes, and stop ID; the calendar API returns 2xx for all events And the tenant portal displays ETA, arrival window, technician name, and building‑specific instructions within 15 seconds of route finalization And on any sync failure, the system retries with exponential backoff up to 3 attempts within 2 minutes; unresolved failures raise an alert and are logged with error codes
Offline Routing Fallback and Recovery
Given the mapping API times out, rate‑limits, or returns 5xx errors When an optimization is requested Then the system switches to the offline routing engine within 5 seconds and computes a route And the offline route’s total travel time is within +15% of the last cached online estimate for the same geography, or the deviation is explicitly flagged to the dispatcher And failover start/end and cause are logged with timestamps And upon API recovery, the route is re‑evaluated and updates are pushed only if the total ETA improves by ≥5 minutes
Technician Instructions and Constraint Compliance
Given a route is finalized When technician instructions are generated Then each stop includes parking/loading guidance, access codes, elevator/service hours, required tools/parts, and on‑site contact; ≥95% of fields present on the ticket are populated And constraints per stop are visible and no stop is scheduled outside its access window or building service hours And instructions are available offline and open in ≤2 seconds on a mid‑range device
Tenant Instant Offer & Consent Workflow
"As a tenant, I want to receive an immediate offer for a sooner time slot with clear details so that I can get my issue resolved quickly and consent to after-hours access."
Description

Immediate, multi-channel outreach (SMS, email, in-app) to eligible tenants with proposed time windows tied to the batch run, including clear value messaging (faster resolution, shared emergency fee benefits). Supports one-tap accept/decline, alternative time suggestions, access instructions, and special notes. Captures explicit consent with timestamp and stores it on the ticket. Handles expirations, rate limits, localization, accessibility, and retries. Automatically updates ticket states and informs scheduling upon response.

Acceptance Criteria
Multi-Channel Instant Offer Dispatch
Given a Batch Rescue run with defined window(s) and eligible tenants When the Tenant Instant Offer is triggered Then the system sends SMS, email, and in-app notifications per tenant within the configured SLA (default 30s) And each message includes the proposed window tied to the run, clear value messaging, a unique secure response link, and an explicit expiration timestamp And tenant communication preferences and legal opt-outs are honored And duplicate offers within the same run are prevented across channels And delivery provider message IDs and timestamps are recorded for each channel
One-Tap Accept/Decline & Alternative Suggestion
Given a tenant opens the offer When they tap Accept Then the system records the response in ≤2s, displays a confirmation with the proposed window, and disables repeat submissions (idempotency key) And the slot is tentatively held for the configured hold period (default 10 min) When they tap Decline Then the system records the decline in ≤2s and optionally captures a reason When they choose Suggest Times Then the UI allows up to 3 alternative time windows within the configured horizon (default 72h) and submits in ≤2s
Explicit Consent Capture and Audit Trail
Given a tenant accepts When consent text is presented Then the tenant must explicitly confirm consent before submission And the system stores consent text/version, timestamp (UTC), tenant ID, channel, IP/device/user-agent on the ticket And generates an immutable audit record with a cryptographic hash of the consent payload And consent can be exported and viewed by staff with appropriate permissions
Offer Expiration, Rate Limiting, and Retry Logic
Given an offer with configured TTL (default 2h) When the TTL elapses without response Then the offer link shows an expired state and no further actions are accepted And the ticket reflects "Offer Expired" Given outbound send failures When the error is transient Then the system retries with exponential backoff up to 3 attempts per channel and records outcomes And if all channels fail, a fallback channel is attempted if available and an alert is logged Given per-tenant rate limits When offers are generated Then no more than 2 offers per 24h are sent per tenant across runs
Ticket State Auto-Update & Scheduling Handoff
Given a tenant response is received When Accept Then the ticket state updates to "Offer Accepted – Pending Scheduling", the proposed window is attached, and a scheduling event is published within 5s When Decline Then the ticket state updates to "Offer Declined" and the batch run may continue filling capacity When Alternative Proposed Then the ticket state updates to "Alternative Proposed" with suggested windows attached And all updates are atomic, audit-logged, and idempotent on duplicate callbacks
Localization, Time Zone, and Accessibility Compliance
Given tenant locale and time zone are known When offers are generated Then content is localized to the tenant's locale with fallback to English, and all times are rendered in the tenant's time zone with clear offsets And RTL languages are correctly displayed when applicable Given in-app and web consent flows When rendered Then pages meet WCAG 2.1 AA for contrast, focus order, and keyboard navigation, and support screen readers And SMS bodies are ≤320 GSM-7 chars or segmented with UDH; links use branded short domains
Access Instructions and Special Notes Collection
Given the offer response UI is displayed When the tenant provides access instructions, special notes, and permission-to-enter Then the UI accepts up to 500 characters of instructions and captures a boolean "Permission to Enter" flag And the data is stored on the ticket, time-stamped, and visible to scheduling and vendor dispatch And sensitive tokens (e.g., door codes) are masked in downstream notifications unless explicit share consent is given
Vendor Capacity & Constraint Enforcement
"As a vendor manager, I want the system to enforce technician capacity, skills, and parts constraints so that we don’t overload techs or schedule jobs they can’t complete."
Description

Validation that the assigned technician and vehicle can absorb additional stops without violating shift limits, overtime policies, maximum stop counts, required certifications, safety guidelines, or parts/tools availability. Checks live utilization, on-call rosters, service area boundaries, and inventory readiness. Blocks incompatible additions and provides actionable reasons with manager override (with justification). Updates utilization metrics and prevents schedule overcommitment.

Acceptance Criteria
After-Hours Piggyback Within Shift Limits
Given an after-hours emergency job is scheduled and Batch Rescue proposes additional stops When the system evaluates the assigned technician’s remaining shift time, mandated breaks, and max stop count per policy Then only stops that keep total labor hours within shift limits and total stops within the max are eligible And ineligible stops are blocked with explicit reason codes (e.g., SHIFT_LIMIT_BREACH, MAX_STOPS_EXCEEDED) And selection is prevented unless a manager override with justification is entered And all decisions and inputs are recorded in the audit log with timestamp, user, and policy versions
Technician Certification and License Compliance
Given a candidate job has required certifications or licenses (e.g., electrical, gas, HVAC) When matching the job to the assigned technician for piggybacking Then only technicians with valid and unexpired certifications covering the job type are eligible And if requirements are not met, the addition is blocked and the UI displays the missing credential(s) and soonest eligible on-call alternatives And overrides are allowed only if the credential category is marked overridable by policy; otherwise the override control is disabled And any override captures justification and is logged to compliance reports
Vehicle Capacity, Tools, and Parts Availability
Given the technician’s vehicle inventory, weight/volume capacity, and tool list are synced in real time When adding a piggyback job that specifies required parts/tools and estimated materials volume/weight Then the system confirms all required parts/tools are available and total load remains within capacity constraints And required parts are reserved on assignment to prevent double booking and inventory counts are decremented atomically And if parts/tools are unavailable or capacity would be exceeded, the addition is blocked with actionable reasons and nearest restock/transfer options And reservation rollbacks occur automatically if the addition is canceled or times out
Service Area Boundary and Travel Time Validation
Given the route is being optimized to include additional stops When calculating travel times with current traffic and validating against service area polygons and tenant time windows Then each added stop must be within the technician’s allowed service area and maintain arrival within each tenant’s committed window And total route duration must remain within the configured max for after-hours runs And violations block the addition with reason codes (e.g., OUT_OF_SERVICE_AREA, WINDOW_SLIP) And the schedule is updated without overlaps and vendor utilization recalculated upon successful addition
Overtime Policy Compliance and Cost Exposure
Given company overtime rules (daily/weekly thresholds, hard vs soft limits) and the technician’s accrued hours When evaluating an additional stop for piggybacking Then the system forecasts resulting overtime and displays projected cost impact And if a hard limit would be breached, the addition is blocked without override And if a soft limit would be exceeded, manager approval with justification is required before proceeding And the decision and cost delta are logged for payroll/export and policy audit
On-Call Roster and Availability Enforcement
Given an after-hours slot requires on-call coverage and rest-period rules When selecting technicians for additional stops Then only technicians currently on the on-call roster and not marked unavailable (PTO, fatigue lockout, safety hold) are eligible And additions that violate mandatory rest periods or create overlapping assignments are blocked with reasons And successful assignments update live utilization and prevent double booking within the same time window
Manager Override Workflow and Safeguards
Given an addition was blocked due to one or more constraints When a manager initiates an override Then the system requires a structured justification (reason category and minimum 20-character note) and optional attachment And overrides are disallowed for non-overridable categories per policy and trigger compliance notifications if attempted And upon successful override, utilization and inventory metrics are recalculated and must pass remaining constraints; otherwise the override is rejected And override indicators are visible on the schedule, dispatch, and audit trail
Fee Apportionment & Transparent Billing
"As a landlord, I want the emergency fee fairly apportioned and transparently itemized across batched jobs so that billing is accurate and savings are clear."
Description

Allocation of a single emergency call-out fee across all batched tickets using configurable strategies (fixed per stop, duration-based, distance-weighted, or custom per landlord). Generates clear, itemized line items on invoices and estimates projected savings during tenant offers and manager approval. Ensures no double-billing, supports tax handling, and exports to accounting systems. Provides dispute resolution artifacts with full breakdowns.

Acceptance Criteria
Fixed Per-Stop Fee Allocation on Batched Emergency Run
Given an after-hours batched run with a single emergency call-out fee F and M distinct stops When the fee allocation strategy is set to "Fixed per stop" Then each stop is assigned F/M as its share rounded to 2 decimals And any rounding remainder cents are assigned to the earliest stop by scheduled time to ensure the sum equals F And if a stop contains multiple tickets, the stop’s share is split evenly across its tickets using the same rounding rule And invoice preview and final invoice display a labeled line item "Shared emergency call-out fee (fixed per stop)" on each ticket for the allocated amount
Duration-Based Fee Allocation Using Technician Time Logs
Given a batched run with total logged on-site minutes S across all tickets and a call-out fee F When the fee allocation strategy is set to "Duration-based" Then each ticket i with minutes mi is assigned F * (mi / S) rounded to 2 decimals, with remainder cents assigned to the ticket with the largest mi (tie broken by ascending Ticket ID) so totals equal F And tickets missing on-site minutes are flagged and prevent invoicing under this strategy until completed or an alternate strategy is selected And invoice preview and final invoice display each ticket’s allocated amount and the strategy label
Distance-Weighted Fee Allocation Using Route Segment Distances
Given a batched run with M stops and per-inbound segment road distances di (i=1..M) from technician start/prior stop, and a call-out fee F When the fee allocation strategy is set to "Distance-weighted" Then each stop i is assigned F * (di / sum(d1..dM)) rounded to 2 decimals, with remainder cents assigned to the stop with the largest di (tie broken by earliest stop sequence) so totals equal F And if any di are unavailable, the strategy is blocked until distances are captured or another strategy is chosen And invoice preview and final invoice display each ticket’s allocated amount and the strategy label
Landlord Default Strategy Configuration and Historical Immutability
Given a landlord profile with a default fee allocation strategy (fixed, duration-based, or distance-weighted) and optional parameters (rounding method, primary-stop minimum share) When a new batched run is created for that landlord Then the default strategy and parameters are preselected and applied And the selected strategy and parameters are snapshotted on the run and referenced on all resulting invoices And subsequent changes to landlord defaults do not alter existing invoices or historical allocations And only users with Owner or Manager role for the landlord can change these defaults And all changes are audit-logged with timestamp, user, and old/new values
Itemized Invoices and Pre-Approval Savings Estimates
Given a pending after-hours batched run with a proposed set of tickets and a selected allocation strategy When presenting tenant offer options and manager approval screens Then the UI shows each ticket’s projected share of the call-out fee, the standalone (unbatched) baseline, and the projected savings amount and percentage And projections update in real time if batch composition changes prior to dispatch, with changes logged And after completion, final invoices include an itemized line per ticket labeled with the strategy used and the allocated amount And managers see actual savings computed post-run with variance from projections if any
Idempotency and No Double-Billing Across Batches and Invoices
Given a batched run with a unique Run ID and tickets with unique Ticket IDs When generating, regenerating, or exporting invoices Then the emergency call-out fee is apportioned exactly once per Run ID and Ticket ID combination And re-generation or re-export is idempotent and does not duplicate call-out fee line items And a ticket can be associated for emergency fee allocation with only one Run ID; attempts to allocate again result in a blocking error requiring resolution And system reports for the period show zero duplicate call-out fee line items
Tax Handling, Accounting Export, and Dispute Artifacts
Given jurisdictional tax settings and landlord/property tax codes When generating invoices containing shared call-out fee line items Then taxes are calculated per line according to taxable/non-taxable configuration and correct tax codes are applied, with subtotal, tax, and total matching the sum of lines And when exporting to QuickBooks Online and Xero, invoices map line items to configured products/services and tax codes, include the Batch Run ID and strategy in memo, and monetary amounts match the source invoice within $0.01 And the system generates a downloadable PDF and JSON artifact per run containing the strategy, inputs (times/distances), formulas, rounding decisions, timestamps, approvals, and per-ticket breakdowns And once issued, artifacts and invoices are immutable and versioned; corrections create a new version with a complete audit trail
Batch Run Audit Trail & Performance Reporting
"As a product owner, I want a complete audit trail and performance metrics for each batch run so that we can prove ROI and continuously improve the feature."
Description

Comprehensive record of each batch run including trigger conditions, candidate discovery, tenant communications and responses, route versions, approvals/overrides, execution timeline, completion outcomes, and billing allocations. Powers dashboards for cost-per-job, vendor utilization uplift, response time reduction, first-time-fix rate, and savings versus baseline. Supports CSV/export APIs and adheres to data retention policies with role-based access controls.

Acceptance Criteria
Audit Trail Completeness for Batch Runs
- For each batch run, the audit record contains: runId; triggeredAt (ISO 8601 with timezone); triggerType; triggerSourceId(s); triggeringActor (userId or system); discoveryQuery (filters/constraints); candidateJobs[] with jobId, compatibilityScore, includeReason/excludeReason; tenantOutreach[] with messageId, channel, templateId, sentAt, deliveryStatus; tenantResponses[] with receivedAt, value, decisionSnapshotId; routeVersions[] with versionId, createdAt, optimizerParams, distanceKm, durationMin, stops[]; approvals[] with actorId, action, rationale, timestamp; executionTimeline per stop (dispatchAt, arriveAt, startAt, completeAt); outcomeCode; resolutionNotesHash; billingAllocations[] with allocationId, costType, amount, currency, splitRuleId; vendorId; technicianId; exceptions[]; systemVersion. - 100% of timestamps are recorded in ISO 8601 with explicit timezone; numeric fields declare units (km, minutes, currency). - Audit events are persisted within 5 seconds of occurrence; failures are retried 3 times with exponential backoff and surfaced as operational alerts if still failing. - Replaying a batch run from its audit record reproduces the same candidate set and final executed route (deterministic mode) with 100% fidelity.
Immutable Route Versioning and Overrides Capture
- Any route change creates a new immutable version; prior versions remain read-only. Attempts to modify a prior version return 403 and no data is altered. - Each version stores both a full snapshot and a computable diff from the previous version, including stop order, ETAs, distances, and optimizer parameters. - User/system overrides are captured with actorId, role, reason, timestamp, and affected fields; overrides are traceable to resulting versionId. - Given an execution timestamp, the active route version used for dispatch is unambiguously identifiable. - A content hash/signature for each version verifies integrity; recomputing the hash from stored payload matches 100% of the time.
Tenant Communication Logging and Response Capture
- Every outbound message related to a batch run is logged with channel, templateId, locale, contentHash, sentAt, and deliveryStatus; inbound responses are logged with receivedAt, value, and metadata; all entries link to jobId and runId. - Communication events are persisted within 2 seconds of send/receive and appear in the audit trail in chronological order. - Opt-out and quiet-hours suppressions are enforced and logged with suppressionReason; no suppressed message is sent. - Decision snapshots link communications and tenant responses to include/exclude decisions for candidate jobs; auditors can view the exact decision path. - PII in communications (phone, email) is masked in the audit view for non-privileged roles according to org policy.
Performance Dashboards Accuracy and Refresh
- The system computes and displays: costPerJob, vendorUtilizationUplift, responseTimeReduction, firstTimeFixRate, and savingsVsBaseline with documented formulas applied consistently across UI and API. - Given a seeded validation dataset with known expected values, each metric matches within ±0.1% of the expected result. - Metric filters (date range, property, vendor, technician, job type) can be combined; queries over 12 months and up to 50k jobs return within 3 seconds at P95. - Data freshness: metrics reflect completed batch runs within 15 minutes; lastRefreshedAt timestamp is visible and accurate. - Drill-through from any metric to underlying audit records preserves counts and totals (±1 row tolerance due to in-flight updates).
CSV and API Export Fidelity and Limits
- Exports are available via UI and API with identical filters; formats include CSV and JSONL. - CSVs include a header row, stable column order, RFC 4180-compliant quoting/escaping, ISO 8601 timestamps, and dot decimal separators. - Async export jobs support up to 1,000,000 rows per request, complete within 15 minutes at P95 for max-size datasets, and support chunked downloads with resumable links. - Field-level redaction is applied per role; unauthorized fields are omitted (not blanked) and the schema descriptor enumerates omitted fields. - Exported row counts match the corresponding UI/query counts within ±1 row; API responses are idempotent and include standard error codes and rate limit headers.
Role-Based Access Controls and Data Retention Compliance
- RBAC enforcement: Owners/Managers have full audit access; Technicians see only assigned runs/stops without billing allocations or tenant PII; Vendors see only their assigned jobs and billing subtotals; Auditors have read-only org-wide access; Tenants have no access. - Unauthorized access attempts return 403 and are logged with actorId, resource, and reason; security logs are queryable. - The system enforces the organization-configured retention policy: after the retention interval, eligible audit records are purged or anonymized per configuration; records under legal hold are exempt. - A daily purge job reports counts of purged/anonymized items and ensures no orphaned references remain; referential integrity checks pass 100%. - Data exports and dashboards respect retention windows; requests outside the window return empty results with explanatory metadata.

Score Tuner

Customize how vendors are ranked by setting per‑trade, per‑issue, or per‑building weights for KPIs like first‑time‑fix, cost per job, on‑time rate, CSAT, and compliance. Shows real‑time score impact and plain‑English “why assigned” explanations so coordinators and approvers trust the pick. Aligns vendor selection with your goals (speed, cost control, quality) and reduces second‑guessing.

Requirements

Per-Context KPI Weighting
"As a property manager, I want to set different KPI weights per trade, issue type, and building so that vendor selection reflects my priorities for each context and improves outcomes without manual intervention."
Description

Enable managers to create and apply KPI weighting profiles at multiple scopes—account default, trade (e.g., plumbing, electrical), issue type (e.g., leak, no-heat), and building/property—so vendor rankings align with local goals (speed, cost control, quality). Provide inheritance and override rules (more specific context takes precedence), guardrails to ensure weights sum to 100%, and the ability to enable/disable individual KPIs (first-time-fix, cost/job, on-time, CSAT, compliance). Include effective start dates/times, profile cloning, and import/export for quick setup. Persist profiles per account with strict tenant isolation, and integrate with the existing vendor assignment engine so every assignment call uses the correct resolved profile. Expected outcome: consistent, policy-aligned vendor picks that reduce second-guessing and manual overrides.

Acceptance Criteria
Create Account-Default KPI Profile
- Given a manager with Score Tuner permission is on the KPI Profiles page - When they create an Account Default profile with at least one KPI enabled and weights across enabled KPIs summing to 100% - Then the profile saves successfully and is available for resolution as the account default - Given the Account Default profile exists - When a work order has no effective Building, Issue Type, or Trade profile - Then the Account Default profile is the resolved profile for vendor ranking - Given the manager enters any non-numeric, negative, or >100 weight value - When they attempt to save the profile - Then the save is blocked and inline validation identifies each invalid KPI weight field
Context Resolution and Override Precedence
- Given profiles exist at Account Default, Trade=Plumbing, IssueType=Leak, and Building=123 Main - When a work order is for Building=123 Main, IssueType=Leak, Trade=Plumbing - Then the Building profile is resolved and fully replaces less-specific profiles - Given profiles exist at IssueType=Leak and Trade=Plumbing but not at Building level - When a work order is IssueType=Leak and Trade=Plumbing - Then the Issue Type profile is resolved over the Trade profile - Given only a Trade=Plumbing profile exists - When a work order is for Trade=Plumbing - Then the Trade profile is resolved over the Account Default profile - Given multiple profiles exist for the same scope and key with different start times - When resolving after the latest start time - Then the profile with the most recent effective start time not in the future is selected
Effective Start Scheduling and Time Zone
- Given the account time zone is configured - When a manager sets a profile effective start to a future date/time in the account time zone - Then the profile remains Scheduled until that exact moment and becomes effective at that time in the account time zone - Given a manager sets a profile effective start to a past date/time - When they attempt to save - Then the save is rejected with the message "Start time must be now or future" - Given a profile’s start time is reached - When subsequent assignments are requested - Then the newly effective profile is used without requiring any manual refresh or deployment
Enable/Disable KPIs and 100% Weight Guardrails
- Given the profile editor lists KPIs: first-time-fix, cost per job, on-time rate, CSAT, compliance - When the manager disables any KPI - Then its weight input is disabled and its weight is excluded from the 100% total - Given at least one KPI must be enabled - When the manager disables all KPIs - Then save is blocked with an error "At least one KPI must be enabled" - Given enabled KPIs have editable weights - When the sum of weights across enabled KPIs is not exactly 100% - Then save is blocked and the total indicator shows the current sum and error state - Given a manager enters decimal weights - When weights have more than two decimal places - Then save is blocked with an error specifying the offending fields
Clone Profile for Quick Setup
- Given a profile exists at any scope - When the manager selects Clone - Then a new profile draft is created with copied KPI enablement and weights, a new identifier, and a default effective start of now (editable before save) - Given a cloned draft exists - When the manager edits its scope and key (e.g., from Trade to Building or changes Building) - Then the changes are allowed and validated like a new profile - Given the original profile exists - When the cloned profile is saved - Then the original profile remains unchanged
Import/Export with Validation and Tenant Isolation
- Given an account has one or more profiles - When the manager exports profiles - Then a machine-readable file containing scope, key, KPI enabled flags, KPI weights, profile name, and effective start time is generated for that account only - Given a properly formatted import file for the current account with valid KPI sums (100% across enabled KPIs) and future-or-now start times - When the manager imports the file - Then profiles are created or updated within the same account and are available for resolution per their scopes - Given the import file contains any invalid row (e.g., bad scope/key, weights not summing to 100%, non-numeric weights, past start time) - When the manager imports the file - Then the import is rejected with a report of row-level errors and no partial profiles are created or updated - Given an import file references profile identifiers from another tenant - When import is attempted - Then the import is rejected and no cross-tenant data is created or modified
Assignment Engine Scoring and Why-Assigned Explanation
- Given a work order has Building, Issue Type, and Trade attributes - When the vendor assignment engine is invoked - Then it resolves the applicable profile using precedence (Building > Issue Type > Trade > Account Default) and the most recent effective start time - Given the resolved profile has a set of enabled KPIs and weights - When vendor scores are computed - Then only enabled KPIs contribute and each KPI is weighted exactly per the profile, producing a deterministic ranking for identical inputs - Given a vendor is selected - When the assignment result is returned to the UI - Then a plain-English explanation includes the resolved profile scope/key, the weights applied, and the top contributing KPIs with their weighted impacts - Given the Score Tuner preview is open for a sample work order - When the manager adjusts weights before saving - Then the real-time score impact updates within 1 second to reflect the tentative weights without persisting them
Live Score Impact Preview
"As a maintenance coordinator, I want to see how changing KPI weights affects vendor rankings in real time so that I can tune confidently before saving changes."
Description

Provide an interactive UI that recalculates vendor scores and top-N rankings in real time as users adjust KPI weights, showing diffs versus the current baseline and highlighting which vendors move up or down. Support previewing for a specific example job (trade, issue, building) and a generic context. Target sub-250 ms recompute using cached KPI aggregates and incremental calculation; gracefully degrade to asynchronous mode with a spinner if inputs are complex. Display visual breakdowns of score contribution per KPI and the net impact, without persisting changes until saved. Integrate directly with the ranking service used in production to ensure previews match live outcomes.

Acceptance Criteria
Real-time re-score within 250 ms
Given a baseline vendor ranking and KPI weights are loaded for a selected context (or generic) and top N is set When the user adjusts any single KPI weight value Then the preview recalculates and renders updated vendor scores and the top-N list within 250 ms at the 95th percentile, measured from input change to visible DOM update And no loading spinner is shown for these interactions And the displayed top-N count matches the configured N
Diffs vs baseline and movement highlighting
Given the baseline ranking and scores are captured for the current context When the preview recalculates with modified weights Then each vendor row shows the score delta versus baseline in the format +x.xx or -x.xx And rank movement is indicated with up/down/no-change icons and a signed position delta (e.g., ↑ +3, ↓ -1, — 0) And the list order reflects the recomputed ranks And all deltas are computed against the persisted baseline, not prior preview states
Context switch: specific job vs generic
Given a user can select trade, issue, and building for a preview context When the user switches between Generic and Specific Job modes Then scores and top-N ranking update to reflect the active mode and selected dimensions And the active mode and dimensions are clearly labeled in the header And when no dimensions are selected, Generic mode is the default and produces a valid ranking
Asynchronous fallback with spinner and cancel
Given a recomputation exceeds the 250 ms threshold (e.g., multiple inputs changed rapidly) When the preview is triggered Then a loading spinner and "Calculating…" state appear within 100 ms of the input change And a Cancel action restores the last rendered results without applying the pending changes And when computation completes, the new results replace the loading state without page refresh And the operation succeeds even if total compute time exceeds 250 ms
KPI contribution breakdown correctness
Given a vendor row in the preview When the user views the score breakdown Then each KPI shows its weighted contribution (weight × KPI value) and the sum equals the total score within ±0.01 And a baseline vs preview per-KPI delta is displayed and the summed deltas equal the total score delta within ±0.01 And breakdowns are available for every vendor displayed in the top-N list
No persistence until save
Given the user modifies KPI weights and previews results When the user reloads the page or navigates away without saving Then the persisted baseline weights remain unchanged and reopening the tuner shows the original baseline And live production rankings outside the preview remain unchanged during preview interactions
Parity with production ranking service
Given identical KPI weights and context are provided to both the preview and the production ranking service When comparing the preview’s top-N vendor list to the production service output Then vendor ordering and per-vendor scores match within ±0.01 for all vendors in top N And any mismatch causes the test to fail
Plain-English Why Assigned
"As an approver, I want a clear explanation of why a vendor was chosen so that I can trust the selection and approve faster."
Description

Generate transparent, human-readable explanations for each assignment that describe the main factors driving the selection, including top contributing KPIs, any compliance gating, and relevant comparisons to the next-best vendor. Explanations must be deterministic and auditable (rule- and template-based), include timestamps and data freshness notes, and link to the active profile used. Provide concise and expanded views, with language suitable for sharing with approvers and tenants. Integrate with the assignment event log so teams can review the exact rationale later. Expected outcome: higher trust in automated picks and fewer escalations.

Acceptance Criteria
Concise Explanation for Assignment Confirmation
Given an assignment is generated by Score Tuner for a work order When a user opens the “Why assigned” concise view in the coordinator UI Then the explanation shows vendor name, top 3 contributing KPIs with plain-English labels, each KPI’s current value for the selected vendor, and its weight label (High/Medium/Low) And the explanation includes a one-sentence summary of why this vendor was chosen And the explanation includes a one-sentence comparison to the next-best vendor with the score delta (e.g., +4.2) And the explanation displays compliance gating outcome (Passed/Failed with reason; if Failed, states exclusion) And the text meets Flesch-Kincaid grade level ≤ 9 And the total length is ≤ 400 characters And numeric values are rounded to 0–1 decimal place as appropriate And a control to “See details” is visible to open the expanded view
Expanded Explanation With Scoring Breakdown
Given an assignment is generated by Score Tuner for a work order When a user opens the expanded explanation view Then the view lists all KPIs considered with their weights (as percentages), the selected vendor’s actual KPI values, and each KPI’s weighted contribution to the final score And the view shows any hard compliance gates evaluated with pass/fail and the specific rule names And the view includes a comparison section for the next-best vendor showing per-KPI differences and overall score delta And the calculation method (sum of weighted KPIs + penalties/bonuses) is described in plain English without formulas And a “Copy explanation” control copies the expanded text exactly as displayed
Deterministic, Template-Based Output
Given identical inputs (assignment ID, Score Tuner profile version, KPI snapshot timestamps, vendor eligibility set) When the explanation is generated multiple times Then the produced text is byte-for-byte identical across runs And the explanation payload includes template ID and rule version IDs used And any randomized phrasing is disabled And a stable explanation ID is produced as a function of assignment ID + template ID + rule versions
Timestamps, Data Freshness, and Active Profile Link
Given an explanation is displayed in either concise or expanded view When the user inspects metadata Then the explanation shows a generation timestamp in ISO 8601 with local property timezone offset And it shows data freshness notes for KPI metrics and compliance checks with “as of” timestamps And a freshness badge displays “Fresh” if all data is ≤ 24h old, otherwise “Stale” with the oldest age And a link to the active Score Tuner profile version used is present and returns HTTP 200 And if the current profile differs from the one used at assignment time, a notice states “Profile updated since assignment”
Event Log Entry of Explanation
Given an assignment is created (auto or manual) When the assignment event is written to the event log Then the concise explanation text and the expanded explanation payload (or a deterministic regeneration reference containing template/rule IDs and data snapshot pointers) are recorded And the log entry includes the actor (system/user), timestamps, template ID, rule version IDs, and data snapshot IDs And the log entry is immutable and retrievable for at least 24 months And when a user opens the historical event, the explanation displayed matches the originally recorded content
Tenant-Safe Explanation Variant
Given a tenant views the work order assignment in the tenant portal When “Why this vendor” is displayed Then the text uses the tenant-safe template that excludes internal weights, sensitive compliance details, and any PII And the text meets Flesch-Kincaid grade level ≤ 8 and length ≤ 280 characters And the message remains positive, avoids blame, and includes at most two plain-English reasons (e.g., on-time rate, nearby availability) And internal links (e.g., profile links) are not shown; only public-safe wording appears And localization uses the tenant’s preferred language when available, otherwise defaults to en-US
Override and Tie Handling Explanation
Given either (a) a coordinator manually overrides the selected vendor or (b) a tie occurs between top vendors When the explanation is generated Then the text explicitly states “Manual override by <user> at <timestamp> with reason: <reason>” or “Tie resolved by rule: <rule name>” And the original auto-selection rationale is preserved and viewable And for ties, the explanation lists the tie-break criteria applied in order and the first criterion that differentiated the vendors And the event log captures the override/tie context and user attribution
Role-Based Control & Audit Trail
"As an operations lead, I want controlled editing and full auditing of score profiles so that we can change policies safely and meet compliance requirements."
Description

Introduce permissions for viewing, creating, editing, approving, and deploying KPI profiles (e.g., Owner, Approver, Coordinator roles). Support maker-checker workflow where changes require approval before going live, with notifications and in-app change diffs. Maintain a complete audit log (who, what, when, before/after values, reason) with search and export, and allow rollback to any prior version. Enforce effective-dated deployments and prevent mid-job drift by pinning the profile used at assignment time. Expected outcome: safe governance of scoring rules with accountability and reversible changes.

Acceptance Criteria
Role Permission Matrix Enforcement
Given user role = Coordinator, when attempting to create or edit a KPI profile, then the action is blocked with HTTP 403 and UI message "Insufficient permissions" and no draft is created. Given user role = Coordinator, when viewing a KPI profile, then read-only view is available and Approve/Deploy controls are hidden. Given user role = Approver, when attempting to approve a submitted change they authored, then approval is blocked with message "Self-approval not allowed" and the request remains Pending. Given user role = Approver, when attempting to create or edit a KPI profile, then the action is blocked with HTTP 403. Given user role = Owner, when creating or editing a KPI profile, then a draft is created; and when submitting, then the request enters Pending Approval state and awaits a distinct Approver. Given any user without Owner/Approver/Coordinator role, when accessing KPI profile UI or APIs, then access is denied with HTTP 403 and no data is leaked.
Maker-Checker Approval Workflow
Given a KPI profile draft with all required fields and a non-empty Reason for change (min 10 chars), when the maker submits for approval, then a Pending Approval request is created and the draft becomes read-only for the maker. Given a Pending Approval request, when an Approver opens it, then they see a side-by-side diff listing every changed field with Before and After values and computed score-weight deltas. Given a Pending Approval request, when an Approver (not the submitter) approves it, then the system records approver identity and timestamp, and prompts for an effective date/time; upon confirmation it moves to Scheduled state. Given a Pending Approval request, when an Approver rejects it with a required comment, then the draft returns to Editable, the maker is notified, and no deployment is scheduled. Given any user, when attempting to approve a change they submitted, then the system prevents approval and records the attempt in the audit log.
Effective-Dated Deployment
Given an Approved change scheduled for effective datetime T (stored in UTC), when now < T, then the current version remains Active and the new version displays status Scheduled with T shown in the viewer's local timezone. When system time reaches T, then the scheduled version becomes Active and the previously Active version becomes Superseded; the transition is recorded in the audit log. Given an attempt to schedule a second version for the same scope (trade/issue/building) at the same effective instant as an existing Scheduled version, then the system blocks it and prompts for a different time. Given an attempt to schedule a version with T in the past, then the system blocks it with message "Effective time must be in the future". Given a scope, when querying versions, then at most one version is Active at any instant.
Profile Pinning on Job Assignment
Given a work order assignment at timestamp ta, when a vendor is selected using Score Tuner, then the exact KPI profile version (ID and hash) effective at ta is pinned to the work order. Given a work order with a pinned profile, when a new profile version becomes Active later, then the work order continues to use the pinned version for all recalculations and explanations. Given a user with Owner role, when they initiate a re-score mid-job, then the UI requires explicit confirmation and reason; upon confirm a new assignment event uses the then-current Active profile and pins it, and the change is logged. Given a work order details view, when opened, then it displays the pinned profile version, effective time, and the "why assigned" explanation calculated with that version's weights.
Audit Log Completeness, Search, and Export
Given any action on KPI profiles (create, edit, submit, approve, reject, schedule, deploy, rollback, permission change), then an audit record is created with actor ID, role, IP, UTC timestamp, object ID, action type, reason/comment, and before/after values for each changed field, plus a correlation ID. Given audit records, when attempting to modify or delete them via UI or API, then the system returns HTTP 405 and no changes occur. Given the audit UI, when filtering by date range, actor, action type, object ID, role, or free text (reason/comments), then matching results are returned within 2 seconds for datasets up to 10k records. Given selected audit results up to 50k records, when exporting to CSV, then the file downloads within 10 seconds, includes headers for all displayed columns, and the export event is itself recorded in the audit log.
Rollback to Prior Version
Given a user with Owner role, when selecting a prior version and initiating rollback, then a new draft is created that exactly matches the prior version's fields and is labeled "Rollback from version <id>", requiring approval before deployment. Given a rollback draft that is approved and scheduled, when it becomes Active, then it takes effect prospectively at its effective time and does not alter historical assignments or pinned profiles. Given a rollback action, then the audit log records the target version, the approver, the effective time, and a diff from current Active to rollback values. Given a rollback to values identical to the current Active version, then the system warns "No effective change" but still allows creating a draft; upon approval it creates a new version ID with identical values.
Notifications and Escalations for Approvals
Given a draft is submitted for approval, then all designated Approvers for the profile's scope receive email and in-app notifications containing profile name, scope, submitter, reason, and count of fields changed, with a deep link to the approval screen. Given a Pending Approval, when it is approved or rejected, then all prior notifications are marked resolved and no further reminders are sent. Given a Pending Approval with no action for 24 hours, then a reminder is sent to Approvers; if still no action by 72 hours, then an escalation notification is sent to Owner(s); both intervals are configurable. Given a notification delivery failure, then the system retries with exponential backoff up to 3 times; persistent failures are recorded in the audit log with error details.
Simulation & Backtesting Sandbox
"As a portfolio manager, I want to test weight changes on past jobs so that I can see the impact on cost, speed, and quality before deploying them."
Description

Allow users to run simulations on historical work orders to compare outcomes across different KPI weight profiles (current vs. candidate). Compute projected impacts on metrics like average cost/job, on-time rate, first-time-fix rate, and CSAT, with confidence indicators and sample sizes. Support selecting date ranges, trades, buildings, and vendor inclusion rules. Execute as an asynchronous job with progress, notifications, and persisted results that can be shared. Simulations are read-only and never affect live routing. Expected outcome: data-driven tuning that aligns selections with business goals and reduces unintended consequences.

Acceptance Criteria
Filterable Historical Scope Selection
Given I am on the Score Tuner Simulation & Backtesting Sandbox When I select a valid date range, one or more trades, one or more buildings, and vendor inclusion rules Then the system validates inputs, displays the count of matching historical work orders (sample size), and enables "Run Simulation" Given the date range is invalid or the filters produce zero matches When I attempt to proceed Then the system shows a validation error, disables "Run Simulation", and displays a "No matching historical work orders" message
Asynchronous Simulation Execution with Progress
Given I click "Run Simulation" with a valid setup Then an asynchronous job is created with a Job ID and initial status of "Queued" or "Running" When the job is executing Then progress updates periodically showing processed/total work orders and percent complete, and the status persists across page refresh or navigation If the job fails Then the status is set to "Failed", an error summary is displayed, and a "Retry" action is available; no partial results are published
Metrics Computation and Comparison
Given the job completes successfully Then the results show baseline (current weights) and candidate (proposed weights) for average cost per job, on-time rate, first-time-fix rate, and CSAT And each metric displays baseline value, candidate value, absolute delta, and percent delta with consistent rounding And each metric shows the sample size used and a confidence indicator (High/Medium/Low) with an info tooltip explaining the basis And metrics with insufficient data are labeled "Insufficient data" and excluded from aggregate deltas
Vendor Inclusion Rules Enforcement
Given specific vendor inclusion/exclusion rules are selected When the simulation scores and selects vendors for each historical work order Then only eligible vendors per the rules are considered And if no eligible vendor exists for a work order, the work order is flagged, excluded from metrics, and counted in a "No eligible vendor" summary And the results summary shows the count and percentage of work orders whose assigned vendor differs between baseline and candidate
Result Persistence and Sharing
Given a simulation completes Then its parameters (filters, profiles, inclusion rules), timestamps, initiating user, and software version are persisted and listed under "Saved Simulations" When I open a saved simulation Then I see the original results exactly as computed, with a unique, shareable link And recipients within my organization who open the link can view results read-only after authentication; permissions are enforced And I can duplicate a saved simulation to re-run with modifications without altering the original
Read-Only Isolation From Live Routing
During simulation execution and after completion Then no changes are made to live work orders, assignments, approvals, notifications, or vendor scores And the UI and API do not expose actions to push simulated assignments to production And audit logs reflect simulation activities only (created, started, completed, failed) with no live routing side effects
Completion and Failure Notifications
Given I started a simulation When it completes successfully Then I receive an in-app notification and, if enabled, an email or chat notification with a summary of key deltas and a link to the results When it fails Then I receive a failure notification with an error summary and a link to details
KPI Data Quality & Fallbacks
"As a vendor manager, I want safeguards for KPI data quality and fair fallbacks so that rankings remain reliable even when data is sparse or delayed."
Description

Define data freshness and completeness thresholds for each KPI used in scoring and surface health indicators in the UI. Apply smoothing and minimum-sample-size rules (e.g., Bayesian smoothing, recency decay) to reduce volatility, and implement fair fallbacks for new or data-sparse vendors (e.g., use market baseline or neutral priors). Provide per-KPI enablement when data health is low, and document the fallback chosen in explanations. Ensure nightly recomputation of aggregates with incremental updates after job completion. Expected outcome: stable, reliable scores that avoid penalizing vendors due to missing or stale data.

Acceptance Criteria
Per-KPI Freshness and Completeness Thresholds Enforced
Given admin config sets KPI=On-Time Rate with freshness_days=30 and min_sample=15 And vendor V has last_updated=45 days ago and sample_size=12 for On-Time Rate When scores are computed for V Then On-Time Rate health=Red and its raw value is not used directly in scoring And a configured fallback value is used for On-Time Rate in scoring for V And the health state, last_updated timestamp, and sample_size are persisted in the KPI health log
UI Health Indicators Reflect Data Status
Given vendor V has KPI health states: CSAT=Green, Cost per Job=Red, Compliance=Green When a coordinator views the Score Tuner for building B Then each KPI shows a colored indicator (Green/Red) with a tooltip containing: last_updated timestamp, sample_size vs thresholds, and whether a fallback is active And switching between vendors updates indicators without page reload And the indicators match backend health states for the same timestamp
Smoothing and Recency Decay Stabilize Scores
Given KPI CSAT prior_mean=4.2, prior_weight=20 jobs, min_sample=20, recency_half_life_days=90 And vendor V has 3 CSAT surveys in the last 30 days with average=5.0 When CSAT is computed Then smoothed_CSAT = (4.2*20 + 5.0*3)/(20+3) = 4.33 is stored and used in scoring And each survey within 90 days has weight=1.0 Given vendor V has one survey at age=90 days and one today, both 5.0 When weighting is applied Then the 90-day-old survey weight=0.5 and today's survey weight=1.0
Fair Fallbacks for New or Data-Sparse Vendors
Given KPI=Cost per Job fallback is configured as market_median for trade=Plumbing in region=North over the last 180 days And vendor V has sample_size=0 for Cost per Job When scores are computed Then V's Cost per Job value used for scoring equals the configured market_median And V is not penalized relative to peers solely due to missing data (weights remain normalized)
Per-KPI Enablement Controls Under Low Data Health
Given KPI=Compliance for vendor V is health=Red for building B When a coordinator opens Score Tuner for B and disables Compliance for scoring Then total scores recalculate within 2 seconds excluding Compliance and normalizing remaining KPI weights to sum to 100% And the UI shows a banner: "Compliance excluded due to low data health" with a control to re-enable And the action is audit-logged with user, timestamp, scope (building/trade), and reason
Explanations Document Fallbacks and Health
Given an assignment where vendor V was selected using Score Tuner for a Plumbing issue And KPI Cost per Job used a fallback baseline and KPI CSAT used Bayesian smoothing When the user opens "Why assigned" Then the explanation lists, per KPI: value used, method (raw/smoothed/fallback), sample_size, last_updated, and contribution to total score in points And the text includes: "Cost per Job used market median due to insufficient recent data" And the explanation values match backend computation for the same timestamp
Nightly Recompute and Post-Job Incremental Updates
Given a nightly aggregation job is scheduled at 02:00 local time When the job runs Then all KPI aggregates per vendor/trade/building are recomputed and timestamped by 02:30 And if the job fails, a failure event is logged and an alert is sent to operations Given a work order is marked complete at time T When the incremental update process runs Then affected KPIs and vendor scores reflect the new data within 5 minutes of T And UI health indicators and explanations reflect the updated computation
Ranking API & Webhooks
"As an integration engineer, I want stable APIs and change webhooks for score profiles and rankings so that I can embed tuned selections into our workflows reliably."
Description

Expose versioned APIs to read/write weighting profiles and to request ranked vendor lists given a job context (trade, issue, building), returning scores, contributing KPI breakdowns, and explanation text. Support optimistic concurrency, idempotency keys, pagination, and rate limiting. Secure with OAuth2 and per-account scoping. Emit webhooks on profile creation/update/approval/deployment so downstream systems (e.g., scheduling) can react, and ensure backward compatibility via API versioning. Expected outcome: seamless integration of Score Tuner with FixFlow’s assignment engine and external tools.

Acceptance Criteria
Create/Update Weighting Profile with Optimistic Concurrency and Idempotency
Given a valid OAuth2 token with scope profiles:write for account A, When the client POSTs /v1/weighting-profiles with a unique Idempotency-Key and a valid profile body, Then the API responds 201 Created with profile_id, account_id=A, version=1, etag, and created_at. Given the same Idempotency-Key and an identical request body within 24h, When retried, Then the API responds 200 OK with Idempotency-Replayed: true and an identical response body. Given an existing profile with version N, When the client PATCHes /v1/weighting-profiles/{id} with If-Match: etag/version=N and valid changes, Then the API responds 200 OK with version=N+1 and a new etag. Given an existing profile with version N, When the client PATCHes without If-Match or with a stale etag/version, Then the API responds 409 Conflict and returns the latest etag/version in the response body. When the request body includes unknown fields or out-of-range weights per schema, Then the API responds 400 Bad Request with field-level errors. All write operations deduplicate by Idempotency-Key per account for 24h and do not create duplicate resources.
Rank Vendors by Job Context with Scores, KPI Breakdown, and Explanation
Given a valid OAuth2 token with scope ranking:read for account A and a job context {trade, issue, building}, When the client POSTs /v1/rankings with the context, Then the API responds 200 OK within 800ms p95 with an ordered vendors array. Each vendor item includes vendor_id, total_score (0.0–100.0), kpi_breakdown {kpi: {weight, value, contribution}}, explanation (plain English), used_profile_id, and profile_version. The vendors array is sorted by descending total_score; ties are broken deterministically by vendor_id ascending. When no eligible vendors match the context, Then the API responds 200 OK with vendors=[] and reason="no_eligible_vendors". When required context fields are missing/invalid, Then the API responds 400 Bad Request with field-level errors. When the caller lacks scope or accesses another account, Then the API responds 403 Forbidden.
OAuth2 Authorization and Per-Account Scoping
Given requests with missing, invalid, or expired Bearer tokens, When calling any /v1 endpoint, Then the API responds 401 Unauthorized with a WWW-Authenticate header containing an appropriate error code. Given a token scoped to account A, When accessing resources belonging to account B, Then the API responds 403 Forbidden. Given a token without the required scope for the operation (profiles:read, profiles:write, ranking:read, webhooks:manage), When the client calls the endpoint, Then the API responds 403 Forbidden and no state changes occur. Given a valid token with correct scopes for account A, When the client calls the endpoint, Then the API authorizes the request and includes account_id=A in resource responses.
API Versioning and Backward Compatibility (v1)
All endpoints are exposed under /v1; requests to unversioned paths are rejected with 400 Bad Request directing clients to use versioned paths. Minor, non-breaking fields may be added in v1 responses; existing fields, types, and semantics are not removed or altered. Responses include X-API-Version: 1; requests may include Accept-Version: 1 (optional) and must continue to succeed without it. When breaking changes are required, Then new endpoints are exposed under /v2; v1 continues to operate unchanged for at least 180 days after /v2 GA with Deprecation and Sunset headers emitted on v1. Webhook payloads include api_version=1 and follow the same backward-compatibility guarantees as REST responses.
Pagination and Rate Limiting for Read Endpoints
Given a list endpoint (e.g., GET /v1/weighting-profiles), When page[limit] (1–100) and page[cursor] are provided, Then the API returns up to page[limit] records and pagination.cursors.next/prev in the response. When additional pages exist, Then pagination.cursors.next is non-null; when at the end, next is null. When page[limit] exceeds the maximum, Then the API responds 400 Bad Request with a descriptive error. All responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. When the caller exceeds the rate limit, Then the API responds 429 Too Many Requests with Retry-After and performs no partial writes.
Webhooks for Profile Lifecycle Events
Given an active webhook endpoint configured for account A with secret S and subscriptions {profile.created, profile.updated, profile.approved, profile.deployed}, When any such event occurs, Then an HTTPS POST is sent within 30s including id, type, api_version=1, account_id, profile_id, profile_version, occurred_at, and data. Each delivery includes X-FixFlow-Signature (HMAC-SHA256 over the raw body with secret S) and X-FixFlow-Timestamp; deliveries older than 5 minutes must be rejected by receivers. Deliveries that receive non-2xx responses are retried with exponential backoff for up to 24 hours or 9 attempts; a 410 Gone permanently disables the endpoint and stops retries. Duplicate deliveries for the same event id may occur; receivers can deduplicate using the event id; FixFlow does not generate new event ids for retries. A test delivery can be triggered via POST /v1/webhooks/endpoints/{id}/test; successful processing returns 204 No Content.

SLA Forecaster

Predicts each vendor’s likelihood to meet the ticket’s SLA using historical performance, travel time, access risk, time‑of‑day, and seasonality. Highlights breach risk before assignment and recommends a primary and backup vendor with confidence bands. Cuts missed SLAs and keeps high‑priority work on track without constant manual monitoring.

Requirements

Historical Vendor Performance Data Pipeline
"As a property manager, I want the system to learn from past vendor outcomes so that future SLA predictions are accurate and tailored to each vendor and job type."
Description

Implement robust data pipelines to ingest, normalize, and persist historical vendor performance signals (e.g., on-time arrival, SLA meet/miss, job duration, first-time-fix rate, cancellations, communication latency, cost variance) from FixFlow tickets, scheduling, and integrated sources. Standardize vendor identities, handle missing and outlier data, align time zones, and compute contextual features (job category, property type, priority, service area) over rolling windows. Expose a clean feature store optimized for forecasting and reporting, with data freshness SLAs and auditability.

Acceptance Criteria
Multi-Source Ingestion & Schema Normalization
Given configured connectors for FixFlow tickets, scheduling, and external integrations, When the daily pipeline runs for the past 24h, Then ≥99.9% of records are ingested with zero duplicate primary keys. Given schema mapping specs v1.0, When normalization executes, Then all fields conform to the canonical model with <0.5% type coercion errors and required units converted. Given late-arriving events up to T+72h, When they arrive, Then the pipeline upserts idempotently without double-counting and emits a late_data metric. Given a re-run for the same window, When executed, Then outputs are bitwise-identical and row counts match previous run (tolerance 0).
Vendor Identity Resolution & Deduplication
Given raw vendor entities from all sources, When identity resolution runs, Then each record is assigned a stable vendor_uid via deterministic rules plus probabilistic matching with confidence ≥0.92. Given candidate matches with confidence <0.92, When evaluated, Then they are not auto-merged and are queued for review with match features logged. Given previously merged vendors with new contradictory evidence, When detected, Then a reversible split is performed and downstream features are re-attributed within 24h. Given the same vendor across systems with matching Tax ID and phone hash, When processed, Then they are merged automatically with an audit trail including rule path and scores.
Missing Data, Outlier Handling & Quality Checks
Given metric fields with nulls, When feature computation runs, Then imputation uses the approved strategy per metric (e.g., median by vendor×category over 90d) and sets quality_flag=imputed. Given values beyond the 99.5th percentile per metric and segment, When detected, Then they are winsorized to the cap and raw_value is preserved in a shadow column. Given any daily job, When quality gates execute, Then completeness ≥99%, freshness within SLA, and anomaly score <3σ; otherwise the run fails and sends alerts to #data-ops within 5 minutes.
Timezone Alignment & Temporal Integrity
Given source timestamps in varying time zones, When ingestion completes, Then event_time_utc is computed correctly with source_tz captured and DST transitions yield no negative durations. Given a ticket lifecycle, When events are ordered by event_time_utc, Then causality holds (created_at < scheduled_at < arrival_at < complete_at) for ≥99.9% of records; violations are quarantined with reasons. Given duration calculations using travel and work times, When computed, Then timezone offsets are applied and median absolute error ≤1 minute vs ground truth sample.
Contextual Feature Computation over Rolling Windows
Given the canonical dataset, When the feature job runs daily at 02:00 UTC, Then it publishes vendor metrics (on-time rate, SLA meet rate, median job duration, first-time-fix rate, cancellation rate, comms latency p50/p90, cost variance p50/p90) segmented by job_category, property_type, priority, and service_area for 7/30/90-day windows. Given any segment with sample_size <20 in a window, When features are computed, Then hierarchical backoff is applied (segment → vendor overall → market baseline) and sample_size is included. Given an event update within a window, When recomputation is triggered, Then affected feature rows are updated within 2 hours with as_of and feature_version incremented. Given unit tests of feature definitions, When validation runs, Then 100% of definition tests pass.
Feature Store Exposure & Point-in-Time Correctness
Given a consumer requests features for ticket_id X as_of timestamp T, When the query executes, Then returned features use only data available at T with 0 detected leakage on a 1000-ticket backtest. Given standard segment queries, When executed, Then P95 read latency ≤150 ms and P99 ≤300 ms for hot partitions with ≥200 QPS sustained for 10 minutes. Given daily ingestion completion, When freshness checks run, Then feature tables are updated by 04:00 UTC with freshness_lag ≤2h for ≥99% of partitions. Given schema evolution, When a breaking change is required, Then a new feature_version is published with backward-compatible view and a changelog entry; no silent breaking changes occur.
Auditability, Lineage & Reproducibility
Given a forecast request reference, When provided ticket_id and as_of timestamp, Then the system reproduces the exact feature vector and underlying raw records within 5 minutes via lineage queries. Given any pipeline execution, When it completes, Then run metadata (code version, config hash, input dataset checksums, row counts) is written to audit logs and linked in the lineage graph. Given an upstream schema change or data incident, When detected by monitors, Then alerts fire within 5 minutes, impacted ranges are identified, and rollback/forward-fix restores correctness within 4 hours.
Travel Time and Access Risk Estimator
"As a dispatcher, I want reliable travel time and access risk estimates so that I can assign vendors who can realistically arrive within the SLA."
Description

Integrate a routing service to estimate vendor-to-property travel time and variability for requested appointment windows, factoring traffic patterns, time-of-day, weekdays vs. weekends, and seasonality. Incorporate property-specific access constraints (parking, gate codes, elevator, doorman hours) to compute an access risk score and uncertainty band. Provide graceful degradation on API failures, caching for common routes, and guardrails for rate limits.

Acceptance Criteria
ETA Estimation for Appointment Window
Given vendor_origin, property_location, and an appointment_window [start, end], When the estimator is invoked, Then it returns travel_time_secs_p50, travel_time_secs_p75, and travel_time_secs_p90 for that window. Given valid inputs, When the estimator returns, Then travel_time_secs_p50 >= 0 and travel_time_secs_p50 <= travel_time_secs_p75 <= travel_time_secs_p90. Given end <= start or malformed coordinates, When invoked, Then it returns HTTP 400 with validation errors and makes zero calls to the routing provider.
Time-of-Day, Weekday/Weekend, and Seasonality Factors Applied
Given identical origin/destination and two appointment windows: a weekday 08:00–09:00 and a weekend 14:00–15:00 on a congested-route test fixture, When the estimator runs, Then p50_weekday >= p50_weekend by at least 10%. Given identical origin/destination and windows in January vs July at 17:00–18:00 on a winter-surge test fixture, When the estimator runs, Then p90_January >= p90_July by at least 10%. Given any request, When the estimator calls its routing model/provider, Then it includes time-of-day, day-of-week, and month (season) parameters and marks factors_applied=true in response metadata.
Property Access Risk Score Computation
Rule: access_risk_score is in [0,100] and equals min(100, sum of factor weights). Rule: factor weights — parking: on_site=0; street_only=10; restricted_permit=15; unknown=10. Rule: factor weights — gate_code: not_required=0; required_provided=0; required_missing=40. Rule: factor weights — elevator: not_required=0; required_available=0; required_booking_missing_or_oos=20; unknown=10. Rule: factor weights — doorman_hours: covers_window=0; does_not_cover_window=25; unknown=10. Given parking=street_only, gate_code=required_missing, elevator=unknown, doorman_hours=does_not_cover_window, When computed, Then access_risk_score=85 and access_risk_factors lists the four contributing factors with their weights.
Access Risk Uncertainty Band Behavior
Rule: access_risk_ci_width = 10 + 10 per unknown factor among {parking, gate_code, elevator, doorman_hours}, capped at 30. Given zero unknowns and access_risk_score=30, When computed, Then access_risk_ci_low=20 and access_risk_ci_high=40. Given two unknowns and access_risk_score=55, When computed, Then access_risk_ci_low=40 and access_risk_ci_high=85. Given any combination, When computed, Then 0 <= access_risk_ci_low <= access_risk_ci_high <= 100.
Graceful Degradation on Routing API Failure and Timeouts
Given the routing provider times out (>800 ms) or returns 5xx/429, When the estimator runs, Then it retries up to 2 times with exponential backoff (total provider wait <= 800 ms) and falls back to: cached ETA if cache_age <= TTL; else distance-based ETA using calibrated speed tables for the time bucket; sets degraded=true and source='fallback'. Given provider failures, When the estimator returns, Then HTTP status is 200 with valid results, provider_error_code is populated, and no unhandled exceptions occur. Given sustained provider failures (>60 s), When subsequent requests arrive, Then a circuit breaker opens for 60 s and external provider calls are not attempted during the open period. Given inbound demand would exceed provider max_rps, When scheduling outbound calls, Then token-bucket rate limiting with jitter ensures outbound calls per second do not exceed max_rps and overflow requests use cache/fallback.
Caching of Common Routes and Bucketing
Rule: cache key = vendor_origin + property_location + day_type(weekday|weekend|holiday) + 15-minute time_bucket. Rule: TTL = 15 minutes; eviction = LRU; capacity >= 10,000 route-time_bucket entries. Given two identical requests within TTL, When the second request arrives, Then cache_hit=true and zero external routing calls are made. Given cache entry expired or time_bucket changed, When a request arrives, Then a fresh provider call is made and cache is updated. Given 1000 identical requests over 5 minutes, When measured, Then cache hit ratio >= 85% and external routing calls <= initial cold misses.
Estimator Output JSON Schema Contract
Rule: response includes fields with types — travel_time_secs_p50|p75|p90: integer>=0; travel_time_variance: integer>=0; access_risk_score: integer in [0,100]; access_risk_ci_low|access_risk_ci_high: integer in [0,100]; access_risk_factors: array of {factor:string, weight:integer}; requested_window_start|requested_window_end: RFC3339 strings; evaluated_time_bucket: string 'YYYY-MM-DDTHH:MM:00Z/15m'; provider:string; source:enum['primary','fallback']; degraded:boolean; cache_hit:boolean. Given a valid request, When the estimator responds, Then the payload validates against the schema and access_risk_ci_low <= access_risk_score <= access_risk_ci_high. Given any response failing schema validation, When contract tests run, Then the build fails.
SLA Breach Risk Scoring Service
"As a property manager, I want a clear breach risk score for each potential assignment so that I can choose the vendor most likely to meet the SLA."
Description

Deliver a low-latency scoring service that predicts the probability of SLA breach for each vendor-ticket pair using features such as historical performance, travel time estimates, access risk, ticket priority, job category, vendor capacity signals, time-of-day, and seasonality. Ensure calibrated outputs with confidence levels, support both real-time and batch scoring, provide versioned models and feature schemas, and include observability (latency, error rate) and audit logs for decisions.

Acceptance Criteria
Real-time API scoring for vendor-ticket pair
Given a valid vendor_id and ticket_id with a complete, valid feature payload (<= 50 features, JSON size <= 16KB) When POST /score is called under 200 RPS in staging for 10,000 requests Then p95 latency <= 200 ms and p99 latency <= 400 ms and success rate >= 99.5% Given a successful response When parsed Then it contains fields: vendor_id, ticket_id, probability, confidence_low_95, confidence_high_95, model_version, feature_schema_version, request_id, generated_at (RFC3339) Given any successful response When validating bounds Then 0.0 <= confidence_low_95 <= probability <= confidence_high_95 <= 1.0 and numeric fields have at least 4 decimal places Given missing required keys or invalid types per feature schema When POST /score is called Then HTTP 400 is returned with error_code, message, and list of invalid_fields
Batch scoring for nightly SLA risk snapshot
Given a Parquet or CSV input file with feature_schema_version matching a supported version and up to 10,000 vendor-ticket rows When POST /score/batch is triggered Then the job completes within 3 minutes p95 and produces an output file with one row per input containing vendor_id, ticket_id, probability, confidence_low_95, confidence_high_95, model_version, feature_schema_version Given row-level validation failures affecting <= 0.5% of rows When the job finishes Then failed rows are emitted to a companion errors file with error_code and field details and the API returns HTTP 207 Multi-Status Given a retry with the same idempotency_key within 24 hours When /score/batch is called again Then exactly-once semantics are preserved and duplicate outputs are not produced
Model calibration and discrimination quality
Given a time-split holdout of >= 10,000 historical vendor-ticket outcomes from the last 12 months with any single vendor weighted <= 5% When the model is evaluated Then Expected Calibration Error (10 equal-frequency bins) <= 0.03, Brier score <= 0.18, and AUROC >= 0.75 Given predictions in the interval [0.7, 0.8] When observed breach outcomes are aggregated on the holdout set Then the empirical breach rate lies within [0.67, 0.83] Given calibration by ticket priority and job category When reliability curves are fit Then calibration slope is in [0.9, 1.1] and intercept is in [-0.05, 0.05]
Uncertainty bands and confidence signaling in API responses
Given a successful score with feature_completeness >= 0.9 When confidence bounds are computed Then (confidence_high_95 - confidence_low_95) <= 0.40 and 0.0 <= confidence_low_95 <= probability <= confidence_high_95 <= 1.0 Given feature_completeness < 0.9 or model_variant = "baseline" When the response is returned Then low_confidence = true and confidence_reason is populated with at least one code from the allowed set {"MISSING_FEATURES","SPARSE_HISTORY","OUT_OF_DISTRIBUTION"} Given the same input payload and model_version When scored 5 times Then the coefficient of variation of probability <= 1e-6
Feature validation and missing-data handling
Given non-critical feature missingness <= 20% and no critical features missing per schema When POST /score is called Then the service imputes per schema defaults, sets imputation_applied = true, and returns 200 Given any critical feature missing or out-of-range per schema When POST /score is called Then the service either (a) falls back to a baseline model with model_variant = "baseline" and low_confidence = true, or (b) returns 422 Unprocessable Entity if fallback=false is specified; behavior is controlled by request flag fallback Given a feature_schema_version major mismatch When POST /score is called Then the service returns 409 Conflict with supported_versions listed
Model and feature schema versioning
Given a request specifies model_version equal to a supported version When POST /score is called Then that model version is used for inference and echoed in the response; otherwise the latest stable version is used Given a model version outside the published support window When it is requested Then the service responds with HTTP 299 Warning header including deprecation metadata while still serving Given a feature_schema_version X.Y.Z When a breaking change (major bump) occurs Then requests using older major versions are rejected with 409 Conflict; minor/patch bumps remain backward compatible
Observability metrics and audit logging
Given production traffic When metrics are scraped Then the service exposes request_count, error_rate, p50/p95/p99 latency, and output_probability_histogram to Prometheus at 15s intervals and a dashboard displays these metrics Given error_rate > 1% for 5 consecutive minutes or p95 latency > 300 ms for 5 consecutive minutes When alerting is evaluated Then a PagerDuty alert is triggered within 1 minute Given any scored request When queried by request_id or ticket_id Then an immutable audit record is retrievable within 2 seconds containing timestamp, vendor_id, ticket_id, input feature hash, model_version, feature_schema_version, probability, confidence_low_95, confidence_high_95, decision flags; logs are retained for 365 days
Vendor Recommendation with Confidence Bands
"As a coordinator, I want the system to recommend a primary and backup vendor with confidence so that I can assign quickly and reduce missed SLAs."
Description

Generate a ranked list of recommended vendors with predicted meet-probabilities and confidence intervals, highlighting a primary and backup choice. Respect operational constraints such as preferred vendor lists, certifications, pricing caps, service areas, blackout dates, and tenant availability. Provide reason codes (top contributing factors) and ensure deterministic results for the same inputs. Persist chosen recommendations back to the ticket record for traceability.

Acceptance Criteria
Ranked Vendor List With Probabilities and Confidence Bands
Given a ticket with at least two eligible vendors When recommendations are generated Then a list of vendors is returned sorted by predicted meet-probability in descending order And exactly one vendor is labeled "Primary" and the next highest is labeled "Backup" And each vendor item includes predicted_meet_probability ∈ [0,1] with at least two decimal places And each vendor item includes confidence_interval {lower, upper} where 0 ≤ lower ≤ predicted_meet_probability ≤ upper ≤ 1
Constraint Compliance Filtering
Given a ticket with defined operational constraints (preferred vendor list, required certifications, pricing caps, service areas, blackout dates, tenant availability windows) When recommendations are generated Then only vendors satisfying all active constraints are included in the ranked list And if a preferred vendor list is present, vendors not on the list are excluded And vendors without the required certifications are excluded And vendors whose pricing exceeds the ticket’s cap are excluded And vendors whose service area does not include the property location are excluded And vendors with blackout dates that prevent service before the SLA deadline are excluded And vendors that cannot service within tenant availability windows before the SLA deadline are excluded
Explanatory Reason Codes Per Vendor
Given recommendations are generated When viewing any recommended vendor Then 3–5 reason codes are displayed per vendor, sorted by absolute contribution to the meet-probability And each reason code indicates positive or negative impact direction And each reason code shows a normalized weight (0–1) with two decimals And reason codes map to human-readable factor names derived from actual model inputs
Deterministic Output For Identical Inputs
Given two or more recommendation requests with identical ticket data, vendor data, user/org configuration, model version, and real-time data snapshot timestamp When the recommendations are generated repeatedly Then the ranked order, predicted probabilities, confidence intervals, Primary/Backup labels, and reason codes are identical across runs And ties in predicted probability are broken deterministically using a stable key (e.g., vendor_id ascending)
Persistence And Traceability On Ticket
Given a user accepts the Primary recommendation or selects an alternative eligible vendor and saves the ticket When the ticket is retrieved later via UI or API Then the ticket record contains a persisted recommendation snapshot including vendor_id, predicted_meet_probability, confidence_interval, reason codes, generation timestamp, and model version for all recommended vendors at decision time And the ticket record stores selected_primary_vendor_id and selected_backup_vendor_id (if any) and an override_flag indicating whether the user changed from the recommended Primary And the persisted snapshot is retrievable unchanged for audit purposes
Graceful Handling When No Vendors Are Eligible
Given no vendors satisfy all active constraints for the ticket When recommendations are requested Then the system returns an empty recommendation list And a machine-readable outcome flag (no_eligible_vendors = true) and a human-readable summary explaining top exclusion reasons and counts per constraint And no Primary or Backup labels are set
Risk-Forward Assignment UI
"As a manager on the go, I want an at-a-glance risk indicator and one-click assignment so that I can keep high-priority work on track without deep analysis."
Description

Enhance the assignment interface to surface breach risk prominently per vendor, including meet probability, confidence band, and color-coded risk badges. Offer one-click assignment for the primary recommendation, quick-select for backup, and inline explanations (e.g., peak traffic, historic lateness). Ensure responsive design, keyboard navigation, and WCAG AA compliance. Reflect selections in schedules and trigger the standard FixFlow tenant/vendor notifications without extra steps.

Acceptance Criteria
Risk Badges & Confidence Bands Visible per Vendor
Given a ticket with an SLA target and at least three candidate vendors with valid forecast data When the assignment UI loads Then each vendor row displays the SLA meet probability as a whole percentage (0–100%) And each vendor row displays a 95% confidence band as a range (e.g., 62–79%) And each vendor row shows a color-coded risk badge with labels: Green (≥85%), Amber (60–84%), Red (<60%) And the badge includes a text label and aria-label conveying the risk level And risk probability, confidence band, and badge are visible above the fold at 1366×768 and 375×667 without horizontal scrolling
Primary & Backup One-Click Assignment
Given the forecaster provides a primary and a backup vendor recommendation When the assignment UI renders Then an Assign Primary button is enabled and labeled with the primary vendor name and probability And a Quick-Select Backup control is visible and labeled with the backup vendor name and probability When the user activates Assign Primary Then the ticket is assigned to the primary vendor within 2 seconds and a success toast appears And the Assign Primary control becomes Assigned and is disabled And repeated activation within 5 seconds does not create duplicate assignments When the user activates Assign Backup instead Then the ticket is assigned to the backup vendor within 2 seconds and a success toast appears
Inline Risk Explanations per Vendor
Given a vendor row in the assignment UI When the user activates the Why? explanation via click, tap, or keyboard Then an inline panel opens within 200 ms listing the top three risk drivers with weights (e.g., Travel time 35%, Historic lateness 25%, Access risk 20%) And the panel shows the data freshness timestamp (e.g., Updated 12m ago) And the panel is dismissible via Esc and Close, and returns focus to the triggering control And the explanation content fits within the viewport without causing horizontal scroll on 375 px width
Responsive Layout Across Devices
Given viewport widths of 320, 375, 768, 1024, 1366, and 1440 pixels When the assignment UI loads Then no horizontal scrollbar appears on any viewport And risk badges and probabilities remain visible on each vendor row And tap targets are at least 44×44 px on touch viewports (≤768 px) And initial content renders in under 2.0 seconds on a 4G connection (≈150 ms RTT, 1.6 Mbps) And layout reflows without overlap when rotating between portrait and landscape
Keyboard Navigation & Focus Management
Given a keyboard-only user on the assignment UI When tabbing through interactive elements Then the focus order is: Filters → Vendor list header → Vendor rows → Explanation triggers → Assignment controls → Footer actions And a visible focus indicator with at least 3:1 contrast is shown on the focused element And Enter/Space activates the focused control; Arrow Up/Down moves between vendor rows; Esc closes popovers/tooltips And viewing risk details, reading explanations, and assigning primary/backup are all achievable without a pointing device
WCAG 2.1 AA Accessibility Compliance
Rule: Text contrast ≥4.5:1 and large text/UI components ≥3:1 Rule: Risk is not conveyed by color alone; each badge includes a text label (High/Medium/Low) and icon Rule: All interactive elements have accessible names/roles; tooltips are keyboard accessible via aria-describedby Rule: Status messages announce via aria-live (polite for success, assertive for errors) Rule: Content reflows and remains functional at 200% zoom without loss of information Rule: Screen reader operation verified on NVDA, JAWS, and VoiceOver on latest Chrome/Edge/Safari
Schedule Update & Standard Notifications on Assignment
Given a ticket is assigned via Assign Primary or Assign Backup When the assignment succeeds Then the vendor calendar blocks the scheduled window immediately And the tenant and vendor receive standard FixFlow notifications (email, SMS, push) within 30 seconds using existing templates And an audit log entry captures actor, timestamp, selected vendor, recommendation type, predicted meet probability, and confidence band And the UI updates to show the assigned vendor and disables assignment controls When any step fails Then no partial state persists (no calendar block, no notifications) and a descriptive error with retry is shown
Early Warning Alerts and Escalations
"As a property manager, I want proactive alerts and suggested actions when an SLA is at risk so that I can intervene before it breaches."
Description

Provide proactive alerts when pre- or post-assignment breach risk exceeds configurable thresholds due to factors like vendor delays, traffic spikes, or weather. Support in-app, email, and SMS channels with noise controls (throttling, deduplication, quiet hours). Suggest mitigation actions (reassign to backup, reschedule, notify tenant) and track all actions in an audit trail. Allow different thresholds by ticket priority to focus attention on high-impact work.

Acceptance Criteria
Pre-Assignment Breach Risk Alert Trigger
Given a new maintenance ticket is created and risk forecasts are available And a priority-specific pre-assignment breach-risk threshold is configured When the predicted breach probability for the currently selected vendor exceeds the configured threshold Then a pre-assignment breach-risk alert is generated within 60 seconds And the alert contains ticket ID, priority, current breach-risk percentage, applied threshold, top three risk drivers, and recommended primary and backup vendors with confidence bands And the alert is recorded in the audit trail with timestamp and source "pre-assignment"
Post-Assignment Breach Risk Monitoring and Alerting
Given a ticket is assigned to a vendor and is actively monitored And a priority-specific post-assignment breach-risk threshold is configured When the breach risk crosses from below to above the threshold or the predicted ETA exceeds the SLA minus the configured buffer Then a post-assignment breach-risk alert is generated within 60 seconds of detection And the alert includes updated ETA, current risk percentage, applied threshold, and top three risk drivers And the alert suggests at least one mitigation: reassign to backup, reschedule window, or notify tenant And the alert is recorded in the audit trail with timestamp and source "post-assignment"
Multi-Channel Delivery with Noise Controls
Given a user has enabled any of in-app, email, or SMS channels and configured quiet hours, throttling limits, and deduplication When an alert is generated for a ticket Then the alert is delivered via all enabled channels within 60 seconds (in-app) and 2 minutes (email/SMS) And during quiet hours, only priority P1 alerts bypass quiet hours; non-P1 alerts are queued and delivered within 2 minutes after quiet hours end And deduplication suppresses alerts with identical type, ticket, and risk band (±2%) within a 30-minute window And throttling limits alerts to a maximum of 2 per ticket per hour per recipient; additional alerts are summarized into a single consolidated digest at the end of the hour And suppression and consolidation actions are logged in the audit trail
Priority-Based Threshold Configuration
Given an administrator configures breach-risk thresholds per ticket priority (e.g., P1, P2, P3, P4) When a ticket’s breach risk is evaluated Then the system applies the threshold corresponding to the ticket’s current priority And if a priority lacks a custom value, the global default threshold is applied And threshold values are displayed in the alert configuration UI and are editable with validation (0–100%) And any threshold change is versioned and captured in the audit trail with editor, old value, new value, and timestamp
Mitigation Recommendations and One-Click Actions
Given an alert is presented to a user When the user opens the alert details Then the system shows recommended primary and backup vendors with probability to meet SLA and confidence bands And provides one-click actions: Reassign to backup, Reschedule appointment, Notify tenant And selecting Reassign updates the assignment, sends confirmations to vendor and tenant, and recalculates risk within 60 seconds And selecting Reschedule proposes the next three time windows with lower predicted risk; upon confirmation, schedules the selected window and notifies stakeholders And selecting Notify tenant sends a templated message including updated ETA/reason and logs delivery status And all actions and outcomes (success/failure) are recorded in the audit trail
Audit Trail for Alerts and Actions
Given alerts are generated and mitigation actions are taken When any alert or action event occurs Then the system creates an immutable audit entry with timestamp, actor (user or system), ticket ID, alert type, source (pre/post), risk percentage, applied threshold, top drivers, channels sent, delivery outcomes, action selected (if any), outcome, and before/after assignment details And audit entries are viewable within the ticket timeline and exportable to CSV within 5 seconds for a 90-day window And tampering is prevented; edits create new entries rather than modifying existing ones
Escalation on Unacknowledged High-Priority Alerts
Given an alert for a P1 or P2 ticket requires acknowledgement When no user with the designated role acknowledges the alert within 10 minutes (P1) or 30 minutes (P2) Then the system escalates to the next-level recipients via all enabled channels and marks the alert as Escalated And if still unacknowledged after an additional 10 minutes (P1) or 30 minutes (P2), the system auto-creates a task to reassign to the recommended backup vendor and sends a tenant notification And escalation respects quiet hours for P2; P1 bypasses quiet hours And all escalation steps, acknowledgements, and auto-actions are captured in the audit trail
Model Explainability, Monitoring, and Retraining
"As an operations lead, I want transparency into why predictions are made and assurance the model stays accurate so that I can trust and improve the system over time."
Description

Offer per-decision explanations indicating the top features influencing each prediction and their direction. Provide dashboards for model health (calibration, precision/recall by category and vendor, data/feature drift, coverage) and enforce a retraining cadence with versioning, rollback, and pinning. Ensure PII-safe logs, adherence to data retention policies, and access controls. Define performance SLOs for prediction accuracy and service reliability and trigger alerts when thresholds are not met.

Acceptance Criteria
Per-Decision Explainability for SLA Prediction (API and UI)
Given an SLA prediction is generated for a ticket and candidate vendor When the client requests the explanation via API or UI Then the response contains the top 5 contributing features with name, contribution score, and direction (positive/negative) And the sum of feature contributions plus model bias reconstructs the predicted probability within ±1 percentage point (or the logit within ±0.02) And the payload includes model_version, prediction_timestamp, confidence_band, and attribution_method And the response excludes PII fields per policy And the explanation is returned within 300 ms at p95 for API and renders within 1 s in UI
Explanation Availability for Primary and Backup Vendor Recommendations
Given a ticket with recommended primary and backup vendors When viewing the recommendations in API or UI Then each vendor’s prediction includes a separate explanation object and confidence band And selecting a vendor in the UI reveals the same top 5 features and directions as the API And the API response contains explanation objects keyed by vendor_id And re-scoring with the same feature snapshot and model_version reproduces the explanations within tolerance
Model Health Dashboard Coverage and Segmentation
Given I open the Model Health dashboard When I select a time range (last 24h, 7d, 30d, or custom) Then I can view calibration (reliability curve and ECE), precision, recall, F1, AUC, and coverage overall, by ticket category, and by vendor And I can filter by geography, time-of-day bucket, and season (month or quarter) And metrics refresh at least hourly with last_refresh_timestamp displayed And chart tooltips show metric definitions and sample sizes And users can export metrics as CSV and via a documented metrics API endpoint
Data and Feature Drift Detection with Alerting
Given a daily drift job compares production scoring data to the training baseline When PSI for any monitored feature is ≥ 0.2 or KL divergence exceeds configured threshold Then a Drift Warning alert is sent to Slack and email within 15 minutes, including affected features, directionality, and charts And when PSI is ≥ 0.3 or target prior shift exceeds 10% Then a Drift Critical incident is created in PagerDuty with a link to the runbook And the dashboard displays drift history, current status badges, and threshold configurations And drift thresholds are configurable per feature with versioned configs
Scheduled Retraining, Versioning, Rollback, and Pinning
Given a retraining cadence of every 4 weeks or upon a Drift Critical event When a new model candidate is trained Then it is assigned a semantic version, lineage metadata, and a human-readable change log And it must meet or exceed baseline on predefined offline metrics and a 7-day shadow test before promotion And promotion requires explicit approver sign-off with an auditable record And rollback to the previous version can be executed within 15 minutes and restores 100% of traffic And tenants or markets can be pinned to a specific model_version for both online and batch scoring; pinning persists across deployments and is visible in config UI and API
PII-Safe Logging, Retention, and Access Controls
Given prediction and explanation logs are written to centralized storage Then logs exclude PII fields (name, phone, email, free text) or store them as irreversible hashes per policy And logs include request_id, model_version, feature_vector_hash, prediction, confidence_band, and top features And logs are encrypted at rest (AES-256) and in transit (TLS 1.2+) And access is restricted via RBAC; only Data-Scientist and SRE roles can read explanations; all access attempts are audited And default retention is 180 days with automatic purge; tenant-level retention overrides are supported And data subject deletion requests remove associated logs within 30 days of receipt
Prediction Accuracy and Service Reliability SLOs with Alerts
Given SLOs for accuracy and reliability are defined and published When computing rolling 30-day metrics Then Brier score is ≤ 0.18 overall and ≤ 0.22 for each of the top 10 vendors And recall@breach is ≥ 0.75 and precision@breach is ≥ 0.60 overall; no monitored segment is below 0.70 recall or 0.55 precision And online scoring uptime is ≥ 99.9% monthly; p95 latency ≤ 250 ms and p99 latency ≤ 500 ms And any SLO breach pages on-call within 5 minutes and posts a status page entry within 30 minutes And SLOs, current compliance, and error budgets are visible on the dashboard

Capacity Guard

Continuously tracks vendor workload, active routes, and blackout dates to prevent over‑assignment. Throttles dispatches when utilization is high, auto‑bundles nearby tickets, and suggests alternative vendors to keep schedules realistic. Protects on‑time arrival rates, avoids burnout, and improves first‑time‑fix by ensuring techs have enough time per job.

Requirements

Real-time Capacity Engine
"As a dispatch coordinator, I want a real-time view of vendor capacity so that I can avoid over-assigning and maintain on-time arrivals."
Description

Continuously computes per-vendor and per-technician utilization using active work orders, planned jobs, travel times, service windows, skill requirements, and blackout dates. Aggregates capacity at hourly, daily, and weekly horizons and exposes it to scheduling, dispatch, and approval flows. Incorporates route-aware travel estimates and dynamic buffers to produce an accurate, forward-looking view of availability. Updates in near real time as tickets are created, rescheduled, or completed, preventing over-assignment and enabling data-driven scheduling decisions within FixFlow.

Acceptance Criteria
Per-Tech Utilization Computation With Constraints
Given vendor V and technician T have a 480-minute service window on 2025-10-01 and assigned jobs J1 (120 minutes) and J2 (60 minutes) And the routing service returns travel times: origin->J1 10 minutes, J1->J2 20 minutes, J2->origin 10 minutes And the dynamic buffer rule is 10 minutes per job When the capacity engine computes utilization for T on 2025-10-01 Then occupied_minutes equals 240 and remaining_available_minutes equals 240 And utilization_percent equals 50%
Hourly/Daily/Weekly Capacity Aggregation
Given the data from AC1 for 2025-10-01 and no jobs on 2025-10-02 with a 480-minute service window When the engine aggregates capacity by hourly, daily, and weekly horizons Then the sum of hourly occupied_minutes for 2025-10-01 equals 240 And daily utilization_percent equals 50% for 2025-10-01 and 0% for 2025-10-02 And weekly_available_minutes for ISO week 2025-W40 equals 960 And weekly_utilization_percent equals 25%
Route-Aware Travel Estimates With Dynamic Buffers
Given technician T has jobs A (60 minutes), B (45 minutes), and C (30 minutes) on 2025-10-03 at locations L1, L2, L3 And the routing service returns travel times: origin->L1 5 minutes, L1->L2 18 minutes, L2->L3 12 minutes, L3->origin 5 minutes And the dynamic buffer rule is 8 minutes per job plus 5% of total travel time When the engine computes capacity for T on 2025-10-03 Then used_travel_minutes equals 40 And applied_buffer_minutes equals 26 And daily occupied_minutes equals 201
Near Real-Time Capacity Updates On Ticket Changes
Given an initial capacity snapshot for vendor V and technician T at time T0 And routing returns 0 travel minutes and the buffer rule is 0 minutes per job for this test When a new ticket J3 (60 minutes) is created for T on 2025-10-01 at time T0+1s Then the engine updates capacity for T and V by T0+6s (<= 5 seconds from ticket creation) And a capacity.updated event is published including identifiers for T and V And a GET capacity query at T0+6s shows occupied_minutes increased by 60 for 2025-10-01
Skill and Blackout Respect In Availability
Given a job requires skill HVAC and technician T lacks skill HVAC and has a blackout on 2025-10-04 When querying capacity for T for the job on 2025-10-04 Then eligible_for_job equals false And available_minutes_for_job equals 0 And vendor-level capacity excludes T from eligible_technicians for that job
Capacity API Contract For Scheduling/Dispatch/Approvals
Given a request GET /capacity?vendor_id=V&horizon=daily&start=2025-10-01&end=2025-10-07 with valid auth When the request is executed Then the response status is 200 and Content-Type is application/json And each item contains fields: vendor_id, technician_id, date, available_minutes, occupied_minutes, utilization_percent, next_available_slot_start, last_updated_at, timezone And the response validates against the published JSON schema with no additional properties And P95 latency is <= 300 ms under 100 concurrent requests in staging
Over-Assignment Guardrail Signal
Given vendor V has a daily utilization threshold of 85% and technician T has a 480-minute window with current occupied_minutes of 384 (80%) And routing returns 0 travel minutes and the buffer rule is 0 minutes per job for this test When evaluating addition of a new 60-minute job on 2025-10-01 Then can_accept equals false and over_assignment_risk equals true for T on 2025-10-01 And recommended_slots includes at least one slot within the next 3 days that keeps utilization <= 85% And suggested_alternative_vendors includes at least one vendor with >= 60 available minutes on 2025-10-01
Dispatch Throttling & Queue
"As a property manager, I want dispatch throttling when a vendor is overloaded so that critical jobs are not crowded out and technicians can arrive on time."
Description

Applies configurable utilization and workload thresholds to automatically throttle new dispatches to vendors approaching or exceeding capacity. Places excess tickets into a prioritized queue that respects SLAs, emergency status, tenant availability, and aging. Provides override controls with reason capture, audit logs, and automatic re-evaluation as capacity frees up. Integrates with FixFlow’s approval and notification systems to keep stakeholders informed while preserving realistic schedules.

Acceptance Criteria
Throttle on Utilization Threshold Exceedance
Given vendor V has a configured daily capacity of 8 hours and a throttle threshold of 85% And V currently has 6.9 hours assigned today (86% utilization) And ticket T is approved with an estimated duration of 1.0 hour and skill match for V When the dispatch engine evaluates T for assignment to V Then T is not dispatched to V And T is placed in the Dispatch Queue with reason "throttled_capacity" And an audit event is recorded with vendor_id, ticket_id, utilization=86%, threshold=85%, and timestamp
Prioritized Queue Respects SLA, Emergency, Availability, Aging
Given the Dispatch Queue contains tickets T1 (emergency=false, time_to_SLA_breach=2h, availability=10:00–12:00 today, age=3h), T2 (emergency=true, time_to_SLA_breach=5h, availability=12:00–14:00 today, age=1h), T3 (emergency=false, time_to_SLA_breach=1h, availability=tomorrow 09:00–12:00, age=6h), and T4 (emergency=false, time_to_SLA_breach=2h, availability=09:00–11:00 today, age=1h) When the system orders the queue Then the order is T2, T3, T4, T1 based on the rules: emergencies first; then ascending time_to_SLA_breach; then earlier same-day availability start; then older age
Authorized Override Dispatch With Reason Capture
Given ticket T is in the Dispatch Queue for vendor V with reason "throttled_capacity" And user U has permission DISPATCH_OVERRIDE When U selects Override Dispatch on T and enters reason "Emergency water leak" Then T is dispatched to V immediately And an audit log is created with user_id, vendor_id, ticket_id, action="override_dispatch", reason, and timestamp And notifications are sent to tenant, property manager, and vendor indicating override assignment And vendor V's utilization is updated to include T Given user U2 lacks permission DISPATCH_OVERRIDE When U2 attempts to override dispatch on T Then the action is blocked with error "Insufficient permission" and no assignment or audit entry is created
Automatic Re-evaluation and Dispatch on Capacity Change
Given vendor V has queued tickets in the Dispatch Queue and is over the 85% throttle threshold And job J for V is completed or canceled, reducing utilization to 80% When capacity changes are detected Then within 60 seconds the system re-evaluates the queue for V And the highest-priority eligible ticket is dispatched to V respecting blackout dates and route constraints And an audit event records the re-evaluation trigger, selected ticket_id, and resulting utilization
Suggest Alternative Vendors When Primary Is Throttled
Given ticket T's primary vendor P is over the throttle threshold And T requires skill S in location L within availability window W and must meet SLA S_deadline When the dispatch engine evaluates T Then the system suggests at least two alternative vendors ranked by ETA to W start, on-time probability, and travel distance And each suggested vendor has skill S, services L, utilization at or below the configured threshold, and can meet S_deadline within W And selecting an alternative vendor assigns T accordingly and logs reason "alternative_vendor_due_to_throttle"
Auto-bundle Nearby Tickets When Dispatching From Queue
Given queued tickets T1 and T2 are within 0.5 miles (or same building), share compatible skills, and have overlapping availability windows today And vendor V's route has sufficient remaining time for duration(T1)+duration(T2)+15 minutes travel buffer When capacity opens for V and the queue is processed Then the system creates a bundle B including T1 and T2 and sequences them on V's route And both tickets are dispatched with a consolidated notification to V And tenant communications for T1 and T2 are updated with a shared 2-hour arrival window And an audit entry records bundle_id, tickets included, and estimated travel time saved
Notifications for Throttle, Queue, Dispatch, and Override
Given ticket T transitions to Queued due to "throttled_capacity" When the transition occurs Then the property manager receives an in-app and email notification within 2 minutes including reason and next review time And the tenant receives an SMS/email stating scheduling is pending and an ETA will be provided upon assignment Given T is dispatched (standard or override) When the dispatch occurs Then the vendor receives a push/in-app notification with job details and arrival window And the tenant receives an updated ETA notification And duplicate notifications are suppressed within a 30-minute window for the same event type
Auto-Bundle Nearby Tickets
"As a scheduler, I want the system to auto-bundle nearby tickets so that technicians spend less time driving and complete more jobs without overtime."
Description

Identifies and groups eligible work orders by geographic proximity, compatible time windows, skill match, parts readiness, and vendor preference to minimize drive time and increase job throughput. Suggests bundle candidates with estimated travel savings and impact on SLAs, allowing one-click application and rollback. Updates routes and tenant notifications automatically while honoring tenant access constraints and first-time-fix requirements.

Acceptance Criteria
Proximity and Time-Window Compatible Bundle Suggestion
Given vendor V has an active route on date D and open work orders exist within 3 miles of each other And each work order’s time window overlaps or is back-to-back within a 15-minute buffer When Auto-Bundle runs for vendor V on date D Then the system proposes at least one bundle containing 2 to 5 tickets And the estimated drive time between any consecutive stops in the bundle is <= 12 minutes And no work order outside the 3-mile proximity threshold is included And bundles are ordered by highest total estimated drive-time savings
Eligibility Filter: Skills, Vendor Preference, and Parts Readiness
Given vendor V has skill tags and truck inventory defined And there are open work orders with required skill tags and partsReady = true And property/vendor preference rules allow vendor V for those properties When Auto-Bundle evaluates eligibility Then 100% of tickets in any suggested bundle have requiredSkills ⊆ V.skills And required parts are available on-truck or reserved for the visit And no ticket violating vendor preference rules is included
Display Estimated Travel Savings and SLA Impact
Given at least one bundle is proposed When the user opens the bundle details view Then each bundle displays total estimated travel time saved (in minutes) And each ticket shows projected arrival time vs. its SLA window (on-time / at-risk) And any ticket projected at risk is visibly flagged And metrics are computed by the same routing engine used for dispatch And values recalculate within 2 seconds when vendor or date filters change
One-Click Apply Updates Route and Enforces Capacity Guard
Given the user has selected a suggested bundle When the user clicks Apply Bundle Then the vendor’s route is updated with the bundled sequence and updated ETAs within 2 seconds And the vendor’s utilization for affected time blocks does not exceed the configured maximum (e.g., 85%); otherwise application is blocked with an explanatory message And all bundled tickets move to Scheduled with the assigned time window and route position And an audit log records bundleId, userId, timestamp, before/after route metrics, and utilization
One-Click Rollback Restores Prior State
Given a bundle was applied within the last 24 hours and none of its tickets are In Progress or Completed When the user clicks Rollback Bundle Then the vendor’s route order and ETAs revert to the pre-bundle state And ticket statuses and appointment times revert to their prior values And notifications triggered by the bundle are canceled or superseded with correction notices And no duplicate or orphaned assignments remain And an audit log entry captures the rollback with reason and userId
Tenant Notifications Respect Access Constraints
Given bundled tickets involve different tenants with specified access windows and preferred contact hours When the bundle is applied Then each tenant receives a single notification with the scheduled arrival window And notifications are sent only within each tenant’s permitted contact hours And minimum notice requirements (e.g., 24 hours unless emergency) are enforced; otherwise application is blocked or requires manager override And building/access instructions are included for the technician
First-Time-Fix Time Allocation and Buffer Protection
Given each work order includes an estimated duration and a required diagnostic buffer When Auto-Bundle constructs a bundle Then the sum of durations + buffers + drive time fits within the vendor’s shift and break rules And each ticket in the bundle includes at least its required buffer (e.g., 15 minutes) And bundles projected to reduce first-time-fix probability below the configured threshold (e.g., 75%) are not proposed
Alternate Vendor Suggestions
"As a property manager, I want recommended alternate vendors when the primary is at capacity so that work continues without delays."
Description

Generates a ranked list of alternative vendors when the preferred vendor is at or above capacity, considering availability, utilization, skills, certifications, historical performance, proximity, pricing, and blackout dates. Supports one-click reassignment with automatic stakeholder notifications, SLA recalculation, and approval routing when costs or policy thresholds are exceeded. Provides transparent scoring factors to support confident decision-making.

Acceptance Criteria
Trigger Suggestions at Capacity Breach
Given a work order with required skills and a requested service window And the preferred vendor’s projected utilization during that window is >= the configured capacity threshold OR the date falls within the vendor’s blackout dates When the dispatcher opens the Assign/Schedule panel or attempts assignment to the preferred vendor Then the system generates a ranked list of eligible alternative vendors within 2 seconds And the list includes at least 3 vendors when available And all listed vendors meet required skills/certifications and have available capacity in the requested window And ineligible vendors are excluded with machine-readable reason codes (e.g., blackout, over-capacity, missing certification) And no suggested assignment would breach the vendor’s configured maximum daily jobs or route constraints
Transparent Vendor Scoring Display
Given the alternative vendor list is displayed When the user views score details for any vendor Then the system shows a breakdown with sub-scores for availability, utilization, skills/certifications match, historical first-time-fix, on-time arrival, proximity/travel time, pricing, and blackout compliance And each sub-score displays its weight and raw value And the total score equals the weighted sum of sub-scores within a tolerance of 0.1 And vendors are sorted descending by total score And tapping a factor reveals the underlying data source and timestamp
One-Click Reassignment and Notifications
Given the dispatcher selects an alternative vendor from the list When the dispatcher confirms via a single Reassign action Then the work order reassignment completes and a new dispatch is created for the selected vendor within 1 second of API confirmation And notifications are sent automatically to the vendor (work order details), tenant (updated ETA window), and property manager (reassignment summary) via configured channels (email/SMS/in-app) And the event is audit-logged with user, timestamp, old/new vendor, and reason And if vendor auto-accept is disabled, the vendor receives an actionable accept/decline; on decline, the system returns to the suggestions list and records the decline reason And no duplicate notifications are sent for the same event
SLA Recalculation After Reassignment
Given a reassignment or schedule change is initiated When an alternative vendor and time window are selected Then response and resolution SLAs are recalculated using property policy and vendor availability And the new due times and deltas versus prior plan are displayed before confirmation And any projected SLA breach is flagged and requires explicit user acknowledgement to proceed And upon confirmation, SLAs on the work order are updated and an audit record captures previous and new values with timestamps
Approval Routing for Cost and Policy Thresholds
Given the selected vendor’s estimated cost or rate exceeds policy thresholds OR the vendor requires manager approval per policy When the dispatcher attempts to reassign Then an approval request is created and routed to the correct approver(s) per the policy matrix And the reassignment enters Pending Approval and notifications are sent to requester and approver And upon approval within the configured SLA, the reassignment proceeds automatically with notifications and SLA recalculation And upon rejection, the work order remains with the original vendor and the suggestions list is re-presented with the rejection reason And if no action occurs before the approval timeout, the request auto-escalates to the next approver and the dispatcher is notified
Availability, Proximity, and Blackout Compliance
Given the system generates alternative vendor suggestions When computing eligibility and ranking Then vendors with blackout dates overlapping the requested window are excluded And travel time is estimated using the vendor’s active route and commitments; suggested windows must keep on-time arrival probability >= 90% And nearby tickets within a 3-mile radius and 2-hour window for the same vendor are auto-bundled when doing so maintains on-time probability >= 90% and does not violate capacity limits And each bundle displays combined ETA, stop order, and predicted time saved And if no vendor can meet constraints, the system suggests the earliest next-available window and marks the reason as capacity constraints
Time Guardrails & Travel Buffers
"As a technician lead, I want guardrails that ensure adequate time per job and travel buffers so that first-time-fix rates improve and burnout is avoided."
Description

Enforces minimum appointment durations and dynamic travel buffers per job type based on historical task durations, complexity, and distance between stops. Prevents back-to-back scheduling that violates configured thresholds and flags risky itineraries before dispatch. Offers intelligent time suggestions to rebalance routes, improving first-time-fix rates and reducing technician burnout while maintaining tenant communication accuracy.

Acceptance Criteria
Enforce Minimum Appointment Durations by Job Type
Given minimum appointment durations are configured per job type And a dispatcher attempts to schedule an appointment shorter than the configured minimum for its job type When the dispatcher saves the appointment Then the system blocks the save and displays an error that states the required minimum duration And the system offers the nearest time slots that satisfy the minimum duration And the attempt is recorded in the audit log with user, job type, attempted duration, and required minimum Given an import (CSV/API) contains appointments below the minimum duration When the import runs Then the system rejects offending rows with error details per row unless auto-adjust is enabled And if auto-adjust is enabled, durations are expanded to the minimum and the change is reported in the import summary
Dynamic Travel Buffers Between Consecutive Jobs
Given travel buffer rules are configured based on distance, traffic profile, and property constraints (e.g., parking/setup) And a technician has two consecutive appointments at different addresses When the route is planned or edited Then the system calculates the required travel buffer using current addresses and target times And enforces at least that buffer between the prior job end and the next job start And prevents saving if the buffer is violated, showing the required buffer minutes And recalculates the buffer immediately when addresses or times change And exposes the computed buffer value in the UI and API for each job transition
Prevent Back-to-Back Scheduling That Violates Guardrails
Given a technician has an existing appointment ending at time T And minimum appointment duration and travel buffer guardrails are configured When a dispatcher attempts to schedule or move another appointment that would start before T plus the required buffer or compress the first below its minimum duration Then the system blocks the change and lists each violated guardrail (minimum duration, travel buffer) And provides a one-click action to move the new appointment to the next valid time that satisfies all guardrails And the conflict is logged with the proposed resolution time
Pre-Dispatch Risk Flagging for Itineraries
Given historical task durations and risk thresholds are configured And a technician’s route for the day is composed When the predicted on-time arrival probability for any stop falls below 90% or total route overtime risk exceeds the configured threshold Then the system flags the itinerary as High Risk prior to dispatch And blocks dispatch until a user either applies a suggested adjustment or provides an override reason And the system proposes adjustments (reordering, travel buffer increases, vendor alternative) that raise predicted on-time probability to at least the threshold And the final decision, risk score, and any override reason are stored in the audit log
Intelligent Time Suggestions to Rebalance Routes
Given a scheduling action results in guardrail violations When the user requests suggestions Then the system returns at least 3 valid schedule options that satisfy minimum duration and travel buffer rules and respect vendor blackout dates And at least one option includes auto-bundling nearby tickets when it reduces total travel time without creating new violations And no option widens any tenant’s promised window by more than 60 minutes unless the user explicitly approves an exception And applying an option updates all affected appointments and buffers atomically and confirms success to the user
Tenant Communication Accuracy After Time Adjustments
Given an appointment time is adjusted due to guardrail enforcement or route rebalancing When the change is saved Then the tenant receives a single consolidated notification with the updated window and reason within 1 minute And any previously scheduled conflicting notifications are canceled or replaced And the tenant portal/app reflects the new ETA/window within 1 minute And the communication log shows the before/after times, notification status, timestamp, and actor
Configuration Scope and Propagation of Guardrails
Given an admin edits minimum durations by job type and travel buffer rules And scopes the rules to a vendor, region, or property portfolio When the admin saves the changes Then the new rules apply to appointments created or edited after save and do not change already confirmed appointments unless "retroactive apply" is selected And changes propagate system-wide within 2 minutes And default global rules apply if no scoped rule exists for a given context And an audit entry records who changed what, when, and the scope affected
Blackout & Holiday Sync
"As a vendor, I want my blackout dates and holidays respected automatically so that I’m never assigned when unavailable."
Description

Synchronizes vendor and technician blackout dates, PTO, and holidays from connected calendars and vendor profiles, with timezone awareness and conflict detection. Automatically blocks scheduling during unavailable periods and proposes compliant alternatives when conflicts arise. Provides visibility into upcoming capacity constraints to inform triage and vendor selection within FixFlow.

Acceptance Criteria
Calendar Sync Ingests Blackouts, PTO, and Holidays
Given a vendor has a connected calendar and a timezone set in their profile When an all-day holiday event is created for 2025-07-04 on the connected calendar Then FixFlow creates a blackout from 2025-07-04 00:00 to 23:59 in the vendor’s local timezone and marks the vendor unavailable for scheduling Given a recurring PTO event (Wednesdays 13:00–17:00 local) exists with an exception on the last Wednesday of the month When the sync runs Then FixFlow creates blackouts for each occurrence in the next 60 days and skips the exception date Given a calendar event is updated or deleted When the next sync occurs Then the corresponding blackout in FixFlow is updated or removed within 5 minutes and the vendor’s last sync timestamp is refreshed Given overlapping blackouts from profile settings and calendar events When synchronization completes Then FixFlow merges them into a single continuous unavailable window without gaps Given a vendor has no connected calendar but has a holiday region set to US in the profile When the annual holiday list is refreshed Then US public holidays for the year are created as blackouts in the vendor’s local timezone
Scheduling Blocked with Compliant Alternatives
Given a dispatcher selects a vendor and time that overlaps a blackout/PTO/holiday When attempting to schedule a job Then the booking is blocked and a conflict message displays the blackout source (Calendar, Profile, Holiday) and the local start/end times Given a blocked attempt When alternatives are requested Then FixFlow proposes at least 3 next available time windows within the vendor’s SLA window, respecting travel buffers and service hours Given a blocked attempt When viewing alternative vendors Then FixFlow lists up to 5 vendors who match skill, zone, and capacity and have no overlapping blackouts, with ETA windows and utilization shown Given the user selects an alternative time or vendor When confirming the booking Then the appointment is created without conflicts and the resolution is logged to the ticket timeline
Full Timezone and DST Awareness
Given a vendor in America/Los_Angeles and a tenant in America/New_York When scheduling across a DST change weekend Then blackout enforcement uses the vendor’s timezone boundaries, and all UI times are labeled with their timezone abbreviations Given an all-day event on the connected calendar When imported Then FixFlow treats it as 00:00–23:59 in the vendor’s local timezone, not 24h UTC, and blocks that local day Given the DST spring-forward day with a missing hour When offering time slots Then no slot is offered in the non-existent hour, and adjacent slots do not overlap blackouts Given API exports of availability When generated Then all timestamps include ISO 8601 offset (e.g., 2025-03-10T09:00:00-07:00) and a vendorTimezone field
Capacity Constraint Visibility in Triage and Vendor Selection
Given a manager opens Capacity Guard When viewing the next 14 days Then a heatmap shows daily blocked hours and percent unavailable per vendor/team sourced from blackouts/PTO/holidays Given vendors with elevated constraints When thresholds are exceeded Then vendors with >30% blocked capacity in the next 7 days are flagged and filterable by region and trade Given a ticket is being triaged When the preferred vendor has >50% blocked during the target window Then the ticket is tagged “At Risk: Limited Capacity” and alternative vendors are highlighted Given blackout data changes from a sync When the dashboard refreshes Then metrics update within 10 minutes
Controlled Overrides and Conflict Audit Trail
Given a user without the “Override Blackouts” permission When attempting to schedule within a blackout window Then the action is denied and no appointment is created Given a user with the “Override Blackouts” permission When overriding a blackout to book an emergency job Then a reason is required, a warning is displayed, and the booking is created with a conflict-override flag Given an override booking is created When reviewing audits Then an audit entry records user, timestamp, reason, blackout source, and affected window, and a notification is sent to the vendor Given a source calendar event changes after an override When the next sync runs Then the original blackout remains intact and the one-time override persists only for the booked slot
Sync Failure Handling and Status Alerts
Given the calendar provider returns errors or rate limits When syncing blackout events Then FixFlow retries with exponential backoff up to 5 attempts and surfaces a “Sync Degraded” status with error codes Given the last successful sync is older than 60 minutes When users access vendor availability Then a banner warns of stale data, an email notification is sent to vendor and manager, and scheduling defaults to safe mode treating time as unavailable Given access tokens are revoked or permissions reduced When the next sync runs Then the connection status changes to “Action Required,” sync halts, and users are prompted to re-authenticate Given administrators review sync health When opening the Sync Status page Then they see last sync time, items processed, failures count, and current status per vendor
Capacity Risk Alerts & KPIs
"As an operations manager, I want proactive alerts and KPIs on capacity risk so that I can intervene before SLAs are missed."
Description

Monitors key indicators such as utilization trends, on-time arrival rates, travel time per job, queue age, and throttle frequency to detect rising capacity risk. Sends proactive alerts to managers and suggests remedial actions like reassigning, bundling, or adjusting buffers. Offers dashboards and weekly summaries to quantify impact on SLA attainment and first-time-fix, enabling continuous optimization of dispatch policies.

Acceptance Criteria
Real-time Utilization Threshold Alert
Given a vendor’s rolling 2-hour utilization exceeds 85% and remains above threshold for 15 minutes And the vendor has at least 3 active jobs scheduled in the next 4 hours When the condition is detected Then a Capacity Risk alert is created within 60 seconds tagged "Utilization" And it is delivered via in-app banner and email to manager(s) of affected portfolio(s) And the alert contains: vendor name, current utilization %, impacted time window, number of jobs affected, recommended actions (throttle, reassign, bundle) with one-click CTAs And duplicate alerts for the same vendor and time window are suppressed for 30 minutes And accepting a recommended action updates the dispatch plan within 2 minutes and logs the change under Audit > Capacity
On-Time Arrival Rate Decline Alert
Given the portfolio’s trailing 7-day on-time arrival rate falls below 90% or drops by ≥5 percentage points week-over-week And at least 30 completed jobs exist in the measurement window When the condition is detected at the top of the hour Then an "On-Time Risk" alert is sent to managers with the top 10 routes/vendors contributing to the decline and their OTR And the alert suggests increasing travel buffers by 5–15 minutes and/or reslotting late-day jobs And clicking "Apply buffers" applies the selected buffer policy to future jobs starting next scheduling cycle and records changes And the alert auto-closes once OTR returns above 90% for 24 hours
Excessive Travel Time Per Job Alert & Auto-Bundling
Given a route’s median travel time per job over the last 24 hours exceeds its baseline by ≥30% or is >25 minutes absolute And at least 5 jobs contributed to the median When detected Then an alert proposes up to 3 proximity-based bundles (≤2 km radius, same vendor, same day) with projected time savings And selecting "Bundle" merges jobs accordingly, updates ETAs, and notifies impacted tenants within 5 minutes And a sandbox preview shows route impact before commit And if bundling would breach SLA for any job, the system blocks and explains the reason
Aging Queue Escalation
Given Priority P1 tickets older than 2 hours or P2 tickets older than 24 hours exceed configured thresholds (P1>0, P2>10) Or backlog growth rate >20% over 48 hours When the condition occurs Then a "Queue Age" alert lists offending tickets with age, priority, vendor, and proposed reassignment targets ranked by capacity score And clicking "Reassign" moves selected tickets and updates vendor workloads immediately And the alert remains open until offending counts are below thresholds for 4 consecutive hours
Throttle Frequency Spike Alarm
Given dispatch throttling auto-triggers ≥5 times in any 2-hour window for a vendor or region When the spike is detected Then a "Throttle Spike" alert is issued with a time series of throttle events and estimated missed SLA risk And the alert offers actions: enable alternate vendor pool, raise daily job cap by 10–20%, or extend service hours by 1 hour And the chosen action takes effect next dispatch cycle and is auditable And throttle logs are exportable as CSV from the alert
Weekly Capacity Risk Summary
Given it is Monday 08:00 local time When the report job runs Then managers receive a summary email and in-app report covering last 7 days: utilization trend, OTR, median travel time per job, queue age, throttle frequency, SLA attainment, first-time-fix rate, and top 3 recommendations And the report includes quantified deltas week-over-week and estimated hours saved and missed SLA avoided And links open the dashboard filtered to the relevant period/vendor And email delivery success ≥99% and open tracking enabled
Dashboard KPI Integrity & Drill-Down
Given a manager opens the Capacity Risk dashboard When the dashboard loads Then KPI cards display ≤15-minute-latency values and 7/30-day trends for utilization, OTR, travel time per job, queue age, throttle frequency, SLA attainment, and first-time-fix And each KPI has a definition tooltip and a "View details" drill-down listing underlying jobs/events with timestamps and IDs And filters (date range, vendor, region, priority) apply within 2 seconds and persist to drill-down And data exported via CSV matches underlying job/event tables within ±1% for counts and ±0.5 minutes for time metrics

Compliance Gate

Verifies licensing, insurance/COI, W‑9, and required certifications in the background and factors status into the score. Auto‑penalizes expiring docs, blocks assignments when out of policy, and sends magic links for vendors to update paperwork instantly. Lowers legal and audit risk while keeping only compliant vendors at the top of the board.

Requirements

Real-time Document Ingestion & Verification
"As a property manager, I want vendor documents verified automatically in the background so that I can trust compliance status without manual review and avoid risky assignments."
Description

Background service that ingests vendor licensing, COI/insurance, W-9, and trade certifications from multiple sources (magic link uploads, manager upload, and API). Performs OCR and structured parsing (e.g., ACORD forms), extracts policy limits, effective/expiration dates, carrier, license numbers, and certification details, and validates against configurable thresholds and external registries where available. Runs asynchronously with retriable jobs, writes normalized compliance status to the vendor profile, and emits events/webhooks for downstream updates. Stores documents securely with encryption, access controls, and retention policies. Reduces manual review time and eliminates assignment risk by ensuring the compliance signal is accurate and up to date within FixFlow’s vendor model.

Acceptance Criteria
Multi-Source Document Ingestion Pipeline
- Given a vendor opens a valid magic link, when they upload a supported file (PDF, JPEG, PNG) up to 25 MB, then the file is virus-scanned, stored, and a verification job is queued within 10 seconds. - Given a manager uploads a document via the dashboard, when they submit a supported file up to 25 MB, then the file is virus-scanned, stored, and a verification job is queued within 10 seconds. - Given an authorized partner calls the ingestion API with a valid bearer token and a supported file, when the request is accepted, then the API returns 201 with job_id and the file is stored and queued within 10 seconds. - Then the vendor profile shows verification_status = "pending" for the relevant document type within 10 seconds of ingestion.
ACORD COI OCR and Structured Parsing
- Given a valid ACORD 25 certificate PDF, when OCR and parsing run, then the system extracts insured name, producer, carrier and NAIC, policy number(s), effective date, expiration date, coverage types, per-occurrence and aggregate limits, and additional insured endorsements. - Then extracted dates are normalized to ISO 8601 (YYYY-MM-DD) and limits to numeric USD values. - Then across a 100-sample reference set, at least 95% of required fields are correctly populated. - Given non-ACORD COIs, when processed, then at minimum policy number, effective/expiration dates, and liability limits are extracted or the document is flagged non-parseable with reason code.
Policy and License Validation Against Configurable Thresholds and External Registries
- Given configured minimum GL per-occurrence = 1,000,000 USD and aggregate = 2,000,000 USD, when a COI is parsed, then the document is marked compliant if extracted limits meet or exceed thresholds; otherwise non_compliant with reason code GL_LIMIT_BELOW_THRESHOLD. - Given a license number and issuing state, when the state registry API is available, then active status and expiration date are verified; mismatches set non_compliant with LICENSE_INACTIVE or LICENSE_EXPIRED. - Given the registry API is unavailable, when validation runs, then the document is marked unverified and a retry job is scheduled; no compliance pass is granted solely on OCR results. - Given any required document will expire within 30 days, when validation runs, then the vendor is marked expiring_soon and a configurable score penalty is applied. - Then all validation outcomes persist source, timestamp, and rule_id for auditability.
Asynchronous Processing, Retries, and Idempotency
- Given a transient error (HTTP 5xx, timeout, or rate limit), when a job fails, then it is retried up to 5 times with exponential backoff starting at 30 seconds and capping at 30 minutes. - Given the same document content is uploaded multiple times for the same vendor, when ingestion runs, then deduplication by SHA-256(content)+vendor_id prevents duplicate records and updates metadata and audit trail instead. - Given a job succeeds after retries, then only a single compliance status update and a single event are emitted using an idempotency key. - Then 95th percentile end-to-end processing time from upload to status update is <= 3 minutes under normal load.
Normalized Compliance Status on Vendor Profile
- Given all required document categories are evaluated, when validation completes, then vendor.compliance aggregates per-category statuses (coi, license, w9, certifications) with values in {ok, expiring_soon, non_compliant, unverified, missing}. - Then vendor.compliance includes updated_at, next_action, and reason_codes[] for any non-ok status. - Then an out_of_policy flag is true if any required category is non_compliant or missing; false otherwise. - Then changes are versioned so previous and current snapshots are accessible for audit.
Event and Webhook Emission for Downstream Updates
- Given a compliance status change, when it is persisted, then an event vendor.compliance.updated is published within 10 seconds with payload {event_id, vendor_id, previous, current, changed_fields, occurred_at, correlation_id}. - Then registered webhooks receive an HTTPS POST with HMAC-SHA256 signature header (X-FixFlow-Signature) and idempotency key; 2xx responses mark delivery complete. - Then failed webhook deliveries are retried with exponential backoff for up to 24 hours before moving to a dead-letter queue, and subscribers can request redelivery by event_id. - Then per-vendor event ordering is preserved.
Secure Storage, Access Control, and Retention
- Then all stored documents are encrypted at rest with AES-256 and in transit with TLS 1.2+. - Given an authenticated user, when they request a document, then access is granted only if their role is property_manager_admin, compliance_reviewer, or the vendor owner of the document; otherwise a 403 is returned. - Given a magic link is issued to a vendor, when it is used, then it expires after a single successful upload or 24 hours, whichever comes first. - Then all document access, downloads, and deletions are logged with user_id, timestamp, action, doc_id, and outcome. - Then default retention is active plus 7 years, after which documents are purged by a daily job; purge events are logged and irreversible.
Compliance Scoring & Weighting Engine
"As an operations lead, I want compliance to influence vendor ranking so that the system prioritizes safe, policy-adherent vendors automatically."
Description

Calculates a compliance score that feeds into FixFlow’s vendor ranking and auto-assignment logic. Applies configurable weights and penalties for missing documents, expired items, and items nearing expiration (e.g., decay within 30 days). Supports policy-based minimums (coverage amounts, endorsements, license types) and provides a transparent breakdown explaining how the score was derived. Exposes score via API and UI and updates in real time as documents change, ensuring compliant vendors surface at the top of the board.

Acceptance Criteria
Real-Time Score Recalculation on Document Update
Given a vendor has an existing compliance score visible in the UI and API And the vendor's document status changes (e.g., COI approved, license expired, W-9 uploaded) When the document status change is saved Then the vendor's compliance score is recalculated within 2 seconds And the updated score is reflected in the vendor board, vendor detail, and API within 2 seconds And the score's lastUpdated timestamp reflects the recalculation time in ISO 8601 And an audit entry records the old score, new score, changed inputs, and actor/source
Configurable Weights and Penalties Application
Given a policy profile defines weights for required items (e.g., License, COI, W-9, Certifications) that sum to 100% And penalties are configured for missing, expired, and near-expiration states When an admin updates any weight or penalty in the profile Then all affected vendor scores are recomputed using the new configuration within 60 seconds And missing items apply the configured missing penalty, expired items apply the configured expired penalty, and near-expiration items apply the configured decay penalty And computed scores are bounded between 0 and 100 and rounded to one decimal place deterministically And the active configuration version and profile ID are stamped on each score and exposed in the breakdown
Policy Minimums Enforcement and Assignment Block
Given a policy sets minimums (e.g., coverage >= specified amount, required endorsements present, specific license type/class) When a vendor fails any policy minimum Then the vendor's policyCompliance flag is false And a policyFailure penalty is applied to the score according to configuration And the auto-assignment engine excludes the vendor from assignment while out of policy And the UI displays an Out of Policy badge with reason codes And the API returns policyFailures with codes, required threshold, and observed value
Expiring Document Decay Penalty Window
Given a configurable decay window (default 30 days, configurable 7-90 days) When a required document is within the decay window prior to expiration Then a decay penalty is applied that increases monotonically as days remaining decrease And when days remaining = 0 the item is treated as expired and the expired penalty is applied instead of the decay penalty And when the document is outside the decay window no decay penalty is applied And changing the decay window takes effect for new calculations within 60 seconds
Transparent Score Breakdown in UI and API
Given a vendor compliance score is available When a manager or auditor views the score breakdown in the UI or retrieves it via API Then the breakdown lists each component with its weight, raw contribution, applied penalties, document status, effective dates, and policy check results And the sum of component contributions minus penalties equals the total score within 0.1 And each breakdown item includes a reference to the source document ID and verification status And the breakdown response includes configuration profile ID, configuration version, and lastUpdated timestamp And access is restricted to authorized roles
Compliance Score API Contract and Performance
Given the endpoint GET /vendors/{id}/compliance-score requires scope vendors:read When called with a valid token and vendor ID Then it returns 200 with JSON containing vendorId, score (0-100), lastUpdated, configurationProfileId, configurationVersion, components[], and policyFailures[] And when called without proper authorization it returns 401/403 with a standardized error payload And the endpoint supports ETag and If-None-Match returning 304 when not modified And p95 latency <= 300 ms and p99 latency <= 800 ms under 100 requests/second per region And GET /vendors/compliance-scores?ids=... returns a list with the same schema per vendor with p95 latency <= 400 ms
UI Ranking and Vendor Board Ordering by Compliance Score
Given the vendor board is configured to sort by compliance score When vendors are listed for a work category Then vendors are ordered descending by compliance score And vendors failing policy minimums are displayed at the bottom with an Out of Policy indicator regardless of numeric score And ties on score are broken deterministically by ascending vendor ID And the UI ordering matches the order returned by the list API for the same filters and sort
Assignment Blocker for Non‑Compliant Vendors
"As a dispatcher, I want the system to block assigning jobs to non-compliant vendors so that we avoid legal exposure and tenant risk."
Description

Hard gate in scheduling and dispatch flows that prevents work order assignment to vendors who are out of policy (missing, invalid, or expired documentation). Presents clear blocking reasons and remediation steps with an inline action to send a compliance update magic link. Supports role-based, time-bound overrides with required justification and automatic logging. Enforces the gate at the API and UI layers to prevent bypass, ensuring only compliant vendors receive jobs.

Acceptance Criteria
UI Block on Assignment to Non‑Compliant Vendor
Given a work order and a vendor whose compliance status is Non‑Compliant (missing, invalid, or expired documentation) When a user attempts to assign the work order via any UI assignment control (single assign, drag‑and‑drop calendar, or reschedule modal) Then the assignment action is prevented (no status change, no vendor notification triggered) And the Assign/Confirm controls are disabled And a blocking message is shown indicating the vendor is out of policy
Blocking Reasons and Inline Remediation CTA
Given an assignment attempt is blocked due to vendor non‑compliance When the blocking message is shown Then the UI lists each failing document with its type, status (missing/invalid/expired), and expiry date (if applicable) And the UI provides a visible "Send compliance update link" action When the action is clicked Then a magic link is sent to the vendor’s primary contact via at least one available channel (email or SMS) And the user receives a success/failure notification And failures include a reason (e.g., no contact on file) and no assignment proceeds
API Enforcement for Assignment Attempts
Given an API client attempts to assign a work order to a non‑compliant vendor When the assignment request is submitted to the assignments endpoint Then the API responds with HTTP 403 Forbidden and error code VENDOR_NON_COMPLIANT And the response includes an array of failing documents with type and status And no assignment, state change, or notification side effects occur And the same request with a compliant vendor returns 2xx and creates the assignment
Role‑Based, Time‑Bound Override
Given a vendor is non‑compliant and an assignment is blocked When a user with the ComplianceOverride permission initiates an override Then the system requires a justification (minimum 10 characters) and an override scope (single work order) with an expiry (max 24 hours) And upon confirmation, the assignment is created and labeled as "Override" And users without the permission cannot initiate or approve an override And after expiry or work order completion, further assignments to the vendor are blocked again until compliance is restored
Audit Logging of Blocks and Overrides
Given any blocked assignment or approved override occurs When the event is processed Then an audit log entry is created capturing timestamp, actor (user or API client), work order ID, vendor ID, failing documents, action (blocked or override), justification (if override), and IP/user agent And the entry is immutable and visible in the audit trail within 60 seconds And the audit trail can be filtered by vendor and work order
Real‑Time Unblock After Compliance Update
Given a vendor was previously blocked for missing/expired/invalid documentation When the vendor updates documentation via the magic link and the compliance engine marks them Compliant Then a subsequent assignment attempt to that vendor succeeds without override And the UI badge updates to Compliant within 60 seconds of status change And previously shown blocking reasons no longer appear for that vendor
Gate Coverage Across Bulk, Auto‑Dispatch, and Reschedule Flows
Given multiple scheduling and dispatch pathways exist (bulk assign, auto‑dispatch rules, calendar drag‑and‑drop, reschedule) When any pathway attempts to assign a work order to a non‑compliant vendor Then the assignment is prevented consistently across all pathways And bulk operations report per‑item failures with the VENDOR_NON_COMPLIANT reason and failing documents listed And auto‑dispatch skips non‑compliant vendors and selects the next eligible compliant vendor, or returns no assignment if none are eligible
Expiration Monitoring & Proactive Notifications
"As a vendor coordinator, I want proactive reminders before documents expire so that vendors update paperwork on time and work isn’t disrupted."
Description

Continuous monitoring of document expiration dates with configurable reminder cadences (e.g., 30/14/7/1 days). Sends email/SMS/in-app notifications to vendors and alerts managers, automatically degrades compliance score as expiration nears, and schedules a hard block at the moment of expiration. Provides a dashboard of upcoming expirations and bulk nudging actions to minimize last-minute scrambling and reduce audit risk.

Acceptance Criteria
Configurable Reminder Cadence Schedules Correctly
Given a manager configures a reminder cadence of 30, 14, 7, and 1 days before a document’s expiration in the vendor’s local timezone, And a vendor’s COI expires at T, When the cadence is saved, Then the system schedules reminders at T-30d, T-14d, T-7d, and T-1d at 09:00 vendor local time. Given the document’s expiration date is changed by the vendor or manager, When the new date is saved, Then previously scheduled reminders are canceled and re-scheduled within 5 minutes based on the new date. Given a document is renewed and approved before the next reminder, When approval status becomes Active, Then all future reminders for that document are canceled within 2 minutes. Given a reminder is scheduled for a channel that is disabled at send time, When the send job executes, Then no message is sent for that channel and other enabled channels still send.
Multi-Channel Notifications and Manager Alerts
Given a vendor has verified email and SMS and an active in-app account, And the manager has enabled email, SMS, and in-app reminders, When a reminder window triggers, Then the vendor receives one notification per enabled channel within 1 minute containing the document type, days until expiration, and a magic link to update. Given multiple documents for the same vendor trigger on the same day, When notifications are sent, Then messages are batched per document type and cadence to avoid duplicates while preserving clarity (max 1 notification per channel per document per day). Given a notification fails to deliver (e.g., hard bounce or SMS error), When the failure is detected, Then the system retries up to 3 times over 24 hours and logs the outcome; if still failing, Then an alert is created for the manager. Given a manager is responsible for the property/vendor, When a 7-day or 1-day reminder triggers, Then the manager receives an alert (in-app and email summary) listing vendors at those thresholds.
Compliance Score Auto-Degradation by Proximity to Expiration
Given the default penalty curve is configured as: 30+ days: 0 points, 15–29 days: −5, 8–14 days: −10, 2–7 days: −20, 1 day: −30, expired: set score impact to maximum and mark non-compliant, When time passes each threshold for a document, Then the vendor’s compliance score reflects the penalty within 10 minutes. Given a custom penalty curve is configured for W-9 documents, When thresholds are crossed, Then the penalties applied follow the custom curve for W-9 only and the default for other documents. Given a document is renewed and approved, When approval is recorded, Then the penalty is removed and the score recalculated within 10 minutes. Given multiple documents influence the score, When penalties apply concurrently, Then the score combines penalties per the configured aggregation rule (e.g., sum capped at minimum score) and the audit log shows each contributing penalty.
Hard Block at Exact Expiration and Auto-Unblock on Update
Given a vendor has an approved document that expires at T, When the current time reaches T, Then the vendor is hard-blocked from new assignments governed by the policy, the Assign action is disabled in UI, and the API returns 403 with reason "Document expired". Given the vendor is blocked due to expiration, When the vendor uploads a valid replacement that passes validation and is approved, Then the block is lifted within 5 minutes and assignment actions are re-enabled. Given a manager attempts to override the block, When policy prohibits overrides, Then the system prevents the override and displays the specific policy rule; When policy allows overrides, Then an override requires justification and is logged with user, timestamp, and duration. Given a work order was already assigned before expiration, When the expiration occurs, Then the existing assignment is not auto-canceled, but the vendor remains blocked from new assignments until compliant.
Expirations Dashboard with Filters, Sorting, and Bulk Nudging
Given a manager opens the Expirations Dashboard, When the page loads, Then it lists all documents expiring within the next 60 days by default, sorted ascending by days to expire, and shows counts by document type and status (Active, Expiring, Expired). Given the manager applies filters (document type, portfolio/property, vendor status, days-to-expire range), When filters are applied, Then the list updates within 2 seconds and the filter state is reflected in the URL for shareability. Given the manager selects multiple rows, When Bulk Nudge is clicked, Then notifications are sent according to the active cadence and channels, deduplicated per vendor/document/day, and a success/failure summary is shown; failures provide retry options. Given the manager exports the view, When Export CSV is clicked, Then a CSV containing the current filtered set with vendor, document type, expiration date, days to expire, last nudged, and compliance score is generated and downloaded within 10 seconds. Given user permissions, When a user without compliance permissions opens the dashboard, Then no data is shown and an access request CTA appears.
Magic Link Document Update Flow and Security
Given a vendor receives a reminder containing a magic link, When the vendor opens the link, Then a secure upload form for the specific document type is shown without login, with vendor and document pre-filled and non-editable. Given security requirements, When the magic link is created, Then it is single-use and expires in 72 hours or upon successful upload (whichever happens first), and is bound to the vendor and document type. Given the vendor uploads files and metadata, When the upload occurs, Then the system validates required fields (e.g., expiration date present and in the future), permitted file types, and size limits; on success, Then the document status becomes Pending Review or Auto-Approved per policy. Given a successful upload and approval, When status becomes Active, Then all future reminders for that document are canceled, the compliance score is recalculated, and any active block for that document type is lifted within 5 minutes. Given an invalid or expired magic link, When accessed, Then the system returns a 401/invalid link message and offers a flow to request a fresh link.
Magic Link Self‑Service Compliance Portal
"As a vendor, I want a secure one-click link to update my compliance documents so that I can get approved quickly without creating an account."
Description

Single-use, time-limited links delivered via email/SMS that open a mobile-optimized portal where vendors can securely upload documents, e-sign W‑9, capture photos, and complete a checklist of required items. Prefills known data, provides instant validation feedback (format, coverage minimums, date ranges), supports saving progress, and updates FixFlow in real time. Eliminates login friction and shortens the time-to-compliance with a streamlined, secure flow.

Acceptance Criteria
Magic Link Delivery via Email and SMS
Given a vendor with a verified email and/or mobile number on file, When a compliance update is requested by a manager or triggered by an expiring document, Then a unique, single-use magic link is sent via both email and SMS within 30 seconds including issuer name, property, expiration timestamp, and support contact. Given a vendor record missing email and mobile, When attempting to send a magic link, Then the system prevents send and surfaces a blocking error with code ML-001 and remediation steps to collect contact info. Given multiple send attempts within a short window, When more than 3 requests occur within 15 minutes for the same vendor, Then messages are rate-limited and a single consolidated message is sent with the most recent link and prior links invalidated.
Single-Use and Time-Limited Link Security
Given a magic link with a TTL of 72 hours (configurable 1–168 hours), When the link is opened within TTL and not previously redeemed, Then grant access to the portal after token verification and log the redemption. Given a magic link that is expired or already redeemed, When it is clicked again or forwarded, Then deny access, display an expiration page, and offer a request-new-link action that sends a fresh link to the verified contact(s). Given any magic link token, Then it must be signed, unguessable (≥128 bits entropy), bound to vendor and required-item scope, and all requests must be over HTTPS; all validations and failures are recorded in the audit log with IP, UA, and timestamp.
Mobile-Optimized Portal and Prefill of Known Data
Given a modern mobile device on 4G, When the vendor opens the portal, Then the initial view loads with LCP ≤ 2.5s and all controls are accessible and usable at 320–414 px widths (WCAG 2.1 AA for contrast and focus states). Given vendor profile data exists in FixFlow, When the portal loads, Then business name, legal entity type, address, policy numbers, and known expiry dates are prefilled and marked as verified where applicable. Given the vendor edits prefilled data, When changes are submitted, Then the portal re-validates dependent fields, flags changed items for review, and updates the audit trail with before/after values.
Document Uploads with Instant Validation
Given the vendor selects files, When uploading documents, Then PDF/JPG/PNG up to 25 MB each (max 100 MB per session) are accepted; non-conforming files are rejected with error code DOC-415. Given an insurance COI is uploaded, When validation runs, Then coverage minimums are checked (GL ≥ $1M per occurrence and ≥ $2M aggregate; Auto ≥ $300k; Workers’ Comp statutory), named insured matches vendor legal name, and effective/expiry dates cover the policy requirement window; the result is displayed inline within 2 seconds. Given a license number is entered, When validation runs, Then the system verifies format, state, and expiry; where an integration exists, the license status is confirmed against the issuing authority and status returned as Active/Inactive/Unknown.
W-9 E-Sign and TIN Match
Given the vendor chooses to complete W-9, When they proceed, Then a guided e-sign flow collects legal name, TIN, federal tax classification, and address with required field validation. Given the vendor submits W-9, When IRS TIN/Name matching is performed, Then a Pass result allows e-signature completion; a Fail result blocks submission with error code TIN-400 and prompts correction. Given the W-9 is completed, When the vendor signs, Then a tamper-evident PDF is generated with signature, IP, timestamp, and consent captured, stored in FixFlow, and emailed to the vendor.
Save Progress and Resume via Fresh Link
Given the vendor has partially completed the checklist, When they close the portal or lose connectivity, Then progress is auto-saved within 1 second of each step and preserved for 14 days. Given the previous magic link expired, When the vendor requests a new link, Then the new link resumes the session at the last incomplete item with previously validated items marked complete and read-only. Given the vendor chooses to start over, When they confirm reset, Then previous in-progress data is archived and a fresh checklist is initialized without deleting already approved documents in FixFlow.
Real-Time Sync to FixFlow and Compliance Impact
Given a vendor completes all required items, When the final validation passes, Then FixFlow updates the vendor’s compliance status to Compliant and recalculates the compliance score within 5 seconds. Given any required item is expired, missing, or invalid, When sync occurs, Then the vendor status remains Out of Policy, assignment eligibility is blocked per policy, and reasons are listed in the portal with remediation steps. Given any change to compliance-affecting data, When it is saved, Then an immutable audit log entry is written including actor, action, timestamp, and affected fields; notifications are sent to the manager per subscription settings.
Audit Trail & Compliance Reporting
"As a compliance auditor, I want an exportable trail of compliance checks and overrides so that I can verify policy adherence during audits."
Description

Immutable, searchable log of all verification events, policy checks, overrides, notifications, and assignments with timestamps, actor identity, and document snapshots. Provides exportable reports (CSV/PDF) and dashboards showing compliance posture by portfolio, property, and vendor. Enables auditors and stakeholders to trace decisions and demonstrate that only compliant vendors were eligible at assignment time, lowering legal and audit risk.

Acceptance Criteria
Search and Filter Verification Events
Given an auditor is on the Audit Trail view with at least 10,000 events in the last 12 months When they apply filters for vendor name (partial), vendor ID (exact), property, portfolio, event type, actor type, outcome, and date range Then the result set reflects all applied filters using AND logic And the first page of results returns in  2 seconds for queries matching  10,000 events And each row displays: ISO 8601 UTC timestamp, event type, actor ID and display name, actor role, entity references (vendor, property, portfolio), outcome, reason/message, and a link to the related document snapshot (if any) And results are sortable by timestamp (default desc), actor, event type, and outcome
Immutable Audit Log Integrity and Tamper Evidence
Given the audit log stores verification events When a new event is written Then it is append-only and cannot be updated or deleted And the event contains a content hash and the hash of the previous event (per-tenant chain) And an integrity check over any selected date range returns Pass if all hashes validate, otherwise Fail identifying the first broken link And any redaction creates a new Redaction event referencing the original, preserving timestamps and actor identity And attempted edits/deletes by any role are blocked and logged as Denied actions
Scoped CSV/PDF Export with Required Fields
Given a user selects scope (portfolio(s), property(ies), vendor(s)) and a date range up to 12 months When they export CSV Then the file generates within  30 seconds for up to 100,000 events and includes the columns: event_id, timestamp_utc, event_type, actor_id, actor_name, actor_role, vendor_id, vendor_name, property_id, property_name, portfolio_id, outcome, policy_rule_id (if applicable), reason, document_snapshot_id, previous_event_id, event_hash When they export PDF Summary Then a PDF generates within  60 seconds for up to 1,000 vendors and includes: compliance rate %, noncompliant vendors count, expiring documents by type (30/60/90 days), blocked assignments count, overrides count, and a dated certification footer And all exports reflect the applied scope and filters exactly
Compliance Posture Dashboard and Drill‑downs
Given a portfolio with active vendors and properties When a user opens the Compliance Dashboard Then the dashboard displays: overall compliance rate %, vendors with expiring docs by type windows (30/60/90 days), missing doc counts, blocked assignments (last 30 days), overrides active, average verification SLA And metrics refresh automatically at least every 15 minutes, and a manual Refresh updates data within  30 seconds And clicking any metric opens a pre-filtered list view that matches the metric count within 1 item
Assignment Eligibility Traceability at Decision Time
Given a work order is being assigned When the assignment decision is computed Then the system records a Compliance Snapshot event capturing: vendor eligibility (pass/fail), each policy check result with rule IDs, document versions used, and current expiration statuses And if the vendor is noncompliant, the assignment is blocked and a Blocked Assignment event records the policy rule ID and message And if an override is applied, the snapshot links to the Override event with approver identity, reason, scope, and expiry And an Assignment Trace report for a given work order shows the snapshot, candidate list with their eligibility at that timestamp, and the selected vendor
Override Workflow Logging and Expiry Enforcement
Given a manager with override permission initiates an override When they submit the override Then reason (min 10 characters), scope (vendor, property/portfolio), policy_rule_id, and expiry date/time (UTC) are required And an Override event is written with before/after compliance status, actor identity, and affected entities And after expiry, the system auto-revokes, writes an Override Expired event, and blocks further assignments unless compliance is restored And Overrides are reportable with status (active/expired/revoked) and can be filtered by actor, scope, and date
Document Snapshot Versioning, Access Control, and Retention
Given a verification of a license, COI, W‑9, or certification occurs When the event is logged Then a read-only document snapshot with version_id and content hash is stored and linked And access to snapshots is role-restricted; W‑9 TINs are masked by default, with unmask access limited to Finance role and logged And retrieving a snapshot by event_id returns the exact version referenced by the event And integrity scans flag missing or corrupted snapshots; failures appear in the integrity report And document snapshots are retained for  7 years; any deletion/redaction writes a corresponding event with reason and approver
Policy Configuration & Templates
"As an admin, I want to configure compliance rules and templates per portfolio so that enforcement aligns with our legal requirements and markets."
Description

Admin UI and APIs to define compliance policies by organization, portfolio, market, or property: required document types, minimum insurance limits, additional insured/endorsements, accepted license types, grace periods, reminder cadences, and scoring weights. Supports versioning, effective dates, change history, test mode with impact preview, and bulk application to vendor groups. Ensures consistent enforcement while allowing local variations and rapid policy updates without code changes.

Acceptance Criteria
Create Policy by Hierarchy and Apply Overrides
Given an admin with organization-level permissions and a hierarchy containing an organization, a portfolio, and a property And no existing policies in that hierarchy When the admin creates an organization-level policy P1 with: requiredDocs=["COI","W-9"], minInsurance.GLPerOccurrence>=1000000, additionalInsured=["Acme RE"], endorsements=["Waiver of Subrogation"], acceptedLicenseTypes=["HVAC","Electrical"], gracePeriodDays=14, reminderCadenceDays=[30,14,7,1], scoringWeights={docs:40,insurance:40,licenses:20} And the admin creates a property-level policy P1P that overrides minInsurance.GLPerOccurrence>=2000000 and gracePeriodDays=7 Then the effective policy for that property equals: requiredDocs=["COI","W-9"], minInsurance.GLPerOccurrence>=2000000, additionalInsured=["Acme RE"], endorsements=["Waiver of Subrogation"], acceptedLicenseTypes=["HVAC","Electrical"], gracePeriodDays=7, reminderCadenceDays=[30,14,7,1], scoringWeights={docs:40,insurance:40,licenses:20} And the UI displays the source per field as Inherited or Overridden accordingly And the Policies API returns 200 with the effective policy and a field-level source map for that property
Policy Versioning, Effective Dates, and Rollback
Given an active policy P1 version v1 with effectiveStart<=today and no effectiveEnd When an admin creates version v2 of P1 with effectiveStart set to a future date and modifies acceptedLicenseTypes to include "Plumbing" Then vendor evaluations before the future date use v1 and on/after the future date use v2 And the change history records actor, timestamp, ip, version, and a structured diff of changed fields And the system allows setting v2 to inactive (rollback) which reactivates v1 within 5 minutes and logs the rollback reason And API GET /policies/{id}/versions returns both versions with correct effective windows and diffs
Policy Management API Validation and Idempotency
Given a client with OAuth2 scope policy.write When the client PUTs /policies/{scopeId} with Idempotency-Key=K and a valid payload whose scoringWeights sum to 100 and all enums are recognized Then the response is 201 (create) or 200 (update) with a stable policyId and ETag, and the stored policy matches the payload exactly And a repeated PUT with the same Idempotency-Key K within 24h does not create a new version and returns the same policyId and ETag And invalid payloads (e.g., unknown license type, negative limits, weights not summing to 100) return 422 with machine-readable error codes and JSON pointers to offending fields And GET /policies/{id} supports If-None-Match and returns 304 when unchanged
Grace Periods and Reminder Cadences Execution
Given a policy with gracePeriodDays=7 and reminderCadenceDays=[30,14,7,1] And a vendor whose COI expires on 2025-10-31 in the property's local timezone When the system schedules reminders Then reminders are queued at 30, 14, 7, and 1 days before 2025-10-31 in local time at 09:00 And during the 7-day grace period after expiry the vendor status is At Risk (not Non-Compliant) and receives daily reminders And after the grace period ends the status flips to Non-Compliant and no further pre-expiry reminders are sent under this policy And all reminder events are logged with timestamp, channel, recipient, and outcome
Scoring Weights Affect Vendor Compliance Score
Given a policy with scoringWeights={docs:40,insurance:40,licenses:20} and requiredDocs=["COI","W-9"] And a vendor that has a valid W-9, a missing COI, and an accepted active license When the compliance score is calculated Then the vendor loses the full 40 points for missing docs (COI missing), retains 40 for insurance only if COI present and limits met, and retains 20 for licenses And changing the policy's docs weight to 50 immediately recalculates the vendor score to reflect a 50-point loss for missing docs And the score response includes component breakdowns and the policy version used
Test Mode Impact Preview with No Side Effects
Given a draft policy version vNext set to Test Mode and scoped to portfolio "North" And the current active policy is vCurrent When the admin runs an impact preview Then the system returns counts of affected vendors, list of vendors whose status would change, and per-vendor score deltas under vNext vs vCurrent And no assignments are blocked, no notifications are sent, and no vendor statuses change as a result of the preview And the admin can promote vNext to Active, at which point enforcement switches within 5 minutes and the preview results are archived with the promotion record
Bulk Apply Policy to Vendor Groups with Progress and Errors
Given a vendor group of 500 vendors and a selected policy version When the admin applies the policy in bulk to that vendor group Then the system enqueues a bulk evaluation job and displays real-time progress with counts of processed, succeeded, and failed vendors And the job completes within 10 minutes for 500 vendors under normal load And per-vendor results include compliance status, score, and any validation errors; failures are retryable individually or in batch without duplicating results And an audit record is created linking the bulk action, actor, scope, policy version, timing, and outcome summary

Smart Trials

Onboards new or recovering vendors with controlled micro‑assignments capped by issue type, price, and distance. Generates trial scorecards and auto‑promotes, pauses, or rotates based on early KPIs (first‑time‑fix, on‑time, CSAT). Safely grows your bench without jeopardizing SLAs or costs.

Requirements

Rule-Based Trial Eligibility
"As a property manager, I want to set eligibility criteria for trial vendors so that only compliant, properly scoped vendors receive micro‑assignments."
Description

Define and enforce eligibility criteria for trial vendors, including required documents (licenses, insurance, W‑9), service categories, coverage radius, availability windows, and tax/compliance status. Integrates with FixFlow’s vendor profiles to validate inputs and block assignment until criteria are met. Supports invitations, self-onboarding, and admin approval, with configurable caps per category and per market. Reduces risk by ensuring only compliant vendors enter Smart Trials and that trial parameters are aligned to portfolio needs.

Acceptance Criteria
Eligibility Gate Blocks Assignment Until Required Documents Verified
- Given a vendor profile lacking any required document (license, insurance, W-9), when the system evaluates eligibility for Smart Trials, then the vendor is marked Ineligible with all missing/invalid items listed. - Given an ineligible vendor, when dispatch or auto-assignment attempts to assign a trial job, then the assignment is blocked, an actionable error is shown to the assigner, and the event is audit-logged with timestamp, user/system actor, and reason codes. - Given the vendor uploads required documents, when the documents pass validation checks (file type, expiration date, name match, minimum coverage), then eligibility status flips to Eligible within 5 minutes and the vendor becomes assignable. - Given documents fail validation, when re-evaluated, then the vendor remains Ineligible and the vendor receives a notification with specific remediation guidance.
Service Category and Certification Matching for Trial Scope
- Given a trial configured for specific service categories, when evaluating a vendor, then the vendor is eligible only if their profile includes all selected categories. - Given a category that requires a license class/subtype, when evaluating eligibility, then the vendor must have an active matching license for that category; otherwise they are ineligible for that category’s assignments. - Given a multi-category work order, when auto-assignment runs, then only vendors eligible for all categories in the work order are considered.
Coverage Radius and Market Caps Enforcement
- Given a vendor coverage radius and home base location, when a trial job is evaluated for assignment, then the vendor is eligible only if the property distance is within the configured radius (using the organization’s configured distance method). - Given per-market and per-category trial caps (by count and by total dollar amount), when evaluating assignment, then the system excludes vendors who have reached a cap and surfaces the cap reason in the assignment response. - Given a vendor drops below a cap after a job is closed or canceled, when re-evaluated, then the vendor becomes eligible for new assignments within 5 minutes.
Availability Windows Applied to Auto-Assignment
- Given vendor availability windows and blackout dates in their profile, when auto-assignment evaluates a job with a target service window, then only vendors with at least one matching availability window are eligible. - Given differing time zones, when evaluating availability, then comparisons use the property’s local time zone. - Given no vendor matches availability, when auto-assignment completes, then no assignment is made and a reason code is logged for each skipped vendor.
Tax and Compliance Status Validation During Onboarding
- Given a vendor starts self-onboarding, when they submit tax forms, then the system requires a valid W-9 (or W-8 for non-US) and performs TIN/name format checks before allowing submission. - Given tax/compliance verification fails, when the vendor attempts to complete onboarding, then submission is blocked with specific failure reasons and remediation steps. - Given verification succeeds, when the admin reviews the application, then the vendor’s compliance status is set to Active and they become eligible for Smart Trials subject to other rules.
Admin Invitation, Self-Onboarding, and Approval Workflow
- Given an admin sends a trial invitation, when the vendor accepts, then the vendor can complete a guided checklist capturing required documents, categories, radius, and availability before submission. - Given the checklist is incomplete or contains invalid data, when the vendor attempts to submit, then submission is blocked with inline errors and field-level highlights until corrected. - Given an admin review, when all requirements are met, then the admin can approve the vendor and activation time is audit-logged; when requirements are not met, then the admin can reject with reasons that notify the vendor.
Auto-Pause on Document Expiry with Notifications and Re-Evaluation
- Given a required document has 14 days or fewer until expiry, when nightly compliance checks run, then the vendor and account admin receive notifications with upload links and the vendor’s status shows Expiring. - Given a required document expires, when eligibility is re-evaluated, then the vendor is auto-paused from Smart Trials, excluded from assignment, and the pause event is audit-logged with the expired document details. - Given the vendor uploads a renewed document that passes validation, when re-evaluated, then the vendor is auto-unpaused and becomes eligible within 5 minutes.
Micro-Assignment Capping Engine
"As an operations manager, I want small, low-risk jobs automatically assigned to trial vendors within preset caps so that I can evaluate them safely without risking service quality."
Description

Automatically routes low-risk work orders to trial vendors using configurable caps by issue type, price ceiling, distance/travel time, and concurrent job limit. Honors tenant availability, urgency classification, and blackout rules (e.g., no gas leaks). Integrates with FixFlow scheduling and dispatch to reserve slots and avoid double-booking. Provides fallback routing if caps are exceeded or no eligible vendor is available. Ensures controlled exposure while collecting early performance signals.

Acceptance Criteria
Auto-Routing Under Configured Caps
Given a new work order is classified as low-risk and matches an allowed issue type And the estimated price is at or below the configured price ceiling for that issue type And at least one trial vendor is within the configured distance or travel time limit And each candidate trial vendor has concurrent assigned trial jobs below their configured cap When the capping engine evaluates routing Then it assigns the work order to an eligible trial vendor And the assignment record includes the applied caps (issue type, price ceiling, distance/travel time, concurrent cap) and selected vendor ID
Blackout and Urgency Rule Enforcement
Given a work order is flagged with a blackout issue type (e.g., gas leak) or urgency level classified as emergency When the capping engine evaluates routing Then it does not assign the work order to any trial vendor And it routes according to the standard emergency workflow to a qualified non-trial vendor pool And the decision is logged with reason code "blackout_rule" or "emergency_bypass"
Tenant Availability Alignment and Slot Reservation
Given a low-risk work order has tenant availability windows And an eligible trial vendor has open scheduling slots When the capping engine assigns the work order Then it selects a vendor slot that overlaps a tenant availability window And reserves the slot in FixFlow scheduling to prevent double-booking And the reservation is visible on both the vendor and tenant calendars
Fallback Routing When No Eligible Trial Vendor
Given a low-risk work order requires assignment And no trial vendor meets the configured caps or all eligible vendors are at their concurrent job limit When the capping engine evaluates routing Then it routes the work order to the configured fallback vendor pool according to priority rules And marks the assignment with a "fallback" indicator and reason code And if fallback routing also fails, it raises an alert to the dispatcher/property manager
Concurrent Job Limit Enforcement and Fair Rotation
Given multiple trial vendors are eligible for micro-assignments And each has a configured concurrent job limit When the capping engine assigns successive eligible work orders Then no vendor exceeds their concurrent job limit at any time And assignments are distributed according to the configured rotation strategy (e.g., round-robin or weighted) And the distribution results are auditable per vendor for the time period
Early Performance Signal Capture
Given a trial work order has reached completion When the completion event is processed Then the engine records first-time-fix, on-time arrival, and CSAT metrics to the vendor's trial scorecard And the metrics are persisted and available for reporting and future routing decisions And incomplete or missing metrics are flagged for follow-up data collection
Routing Decision Audit Trail
Given the capping engine evaluates a work order When a routing decision (assignment, bypass, or fallback) is made Then an audit entry is created capturing timestamp, candidate vendors considered, caps evaluated, eligibility outcomes, final decision, and reason codes And the audit entry is retrievable via API and in the work order activity log
Trial Scorecard & KPI Tracking
"As a vendor manager, I want a live scorecard of trial vendor KPIs so that I can assess performance objectively and make data-driven decisions."
Description

Generates real-time scorecards for each trial vendor, calculating first-time fix rate, on-time arrival %, CSAT, rework rate, cancellation rate, and average resolution time. Supports per-issue-type weighting, minimum sample sizes, and time windows. Ingests signals from tenant app, scheduler, and work order outcomes; updates after every job. Exposes dashboards, drill-down to job details, and CSV/BI export. Establishes an objective, transparent view of early performance.

Acceptance Criteria
Real-Time KPI Recalculation After Job Update
Given a trial vendor has an active scorecard and job J is associated with that vendor And system parameters time_window_days, on_time_threshold_minutes, and rework_window_days are configured When job J transitions to Completed, Cancelled, or Rework Requested and all relevant source events for J are received Then the vendor’s scorecard recalculates within 60 seconds of the final required event And the KPIs (first-time fix, on-time arrival %, CSAT, rework rate, cancellation rate, average resolution time) include or exclude job J according to definitions And the scorecard “Last updated” timestamp reflects the recalculation time And recalculation is idempotent such that replaying identical events does not change KPI values And concurrent updates for multiple jobs do not produce inconsistent intermediate KPI totals
KPI Formulas, Weighting, and Sample Thresholds
Rule: First-time fix rate = weighted count of jobs resolved with no additional visit within rework_window_days ÷ weighted count of eligible jobs in time_window_days Rule: On-time arrival % = weighted count of jobs where arrival_time ≤ scheduled_window_start + on_time_threshold_minutes ÷ weighted count of eligible attended jobs Rule: CSAT = weighted average of tenant ratings from completed jobs in time_window_days, respecting per-issue-type weights Rule: Rework rate = weighted count of jobs with a follow-up visit within rework_window_days ÷ weighted count of eligible jobs Rule: Cancellation rate = weighted count of vendor-initiated cancellations ÷ weighted count of assigned jobs in time_window_days Rule: Average resolution time = weighted average of (completed_at − assigned_at) for completed jobs in time_window_days Rule: Per-issue-type weights are configurable (default 1.0) and applied consistently across all KPI computations after save Rule: If a metric’s sample size < min_sample_size, display “Insufficient sample” in UI and emit null for that metric in exports Rule: All formulas apply the selected time window (rolling N days or fixed date range) and include only jobs intersecting that window
Multi-Source Signal Ingestion and Deduplication
Given tenant app, scheduler, and work order systems emit events containing event_id, job_id, vendor_id, and timestamps When multiple events for the same job arrive out of order or are retried Then the ingestion pipeline deduplicates on event_id and applies events in timestamp order to derive the latest valid state And events missing critical fields (job_id, vendor_id, status, or timestamp) are rejected with an error logged and the job excluded from KPI updates until corrected And late-arriving events cause automatic backfill recalculation within 5 minutes of receipt And transient outages under 30 minutes result in no data loss and the backlog is fully processed within 10 minutes after recovery And source-to-metric field mappings are documented and consistent across environments
Scorecard Dashboard with Filters and Drill-Down
Given a manager opens a vendor’s scorecard When they apply filters for time window, issue type(s), and service area Then the dashboard refreshes within 2 seconds and displays KPI cards showing value, numerator, and denominator And clicking a KPI opens a drill-down list of included jobs with columns: job_id, issue_type, assigned_at, arrival_time, completed_at, first_time_fix_flag, on_time_flag, rating, rework_flag, cancellation_flag, resolution_time_minutes And selecting a job opens a job detail view containing the raw timestamps and source references used to compute each KPI flag And the aggregate counts from the drill-down list exactly match the numerator/denominator shown on the KPI card for the same filters
CSV and BI Export for Scorecards
Given a manager requests an export for a vendor with a specified date range and optional filters When exporting KPI Summary Then a CSV is generated containing one row per KPI with columns: vendor_id, kpi_name, value, numerator, denominator, time_window_start, time_window_end, generated_at (UTC), and per-issue-type weights snapshot And the KPI Summary values match the dashboard values for the same filters When exporting Job Detail Then a CSV is generated with one row per job and columns: job_id, vendor_id, issue_type, assigned_at, scheduled_window_start, arrival_time, completed_at, first_time_fix_flag, on_time_flag, rating, rework_flag, cancellation_flag, resolution_time_minutes (all timestamps ISO 8601 UTC) And exports up to 10,000 jobs complete within 60 seconds And a BI API endpoint provides the same data with pagination and schema documentation, returning identical values to the CSV for the same query
Transparency and Auditability of KPI Calculations
Given a KPI value is displayed on the scorecard When the user selects “View calculation” Then the system presents the formula, numerator, denominator, applied per-issue-type weights, time window, sample size, and the list of included/excluded job_ids with reasons And configuration changes to weights, thresholds, or time window are recorded with user, timestamp, old_value, and new_value And historical views respect effective-dated configurations so that viewing a past period reproduces the KPI values as of that date
Auto-Promote, Pause, and Rotate Logic
"As a property manager, I want vendors to be promoted or paused automatically based on KPI thresholds so that my bench grows without constant manual oversight."
Description

Implements policy-driven state transitions for trial vendors: auto-promote to approved/preferred when KPIs meet thresholds and sample sizes; auto-pause when metrics fall below floor; rotate through a candidate pool to distribute trials. Supports cool-down periods, grace windows, manual override with justification, and instant notifications to stakeholders and vendors. Maintains an auditable history of state changes for compliance and review.

Acceptance Criteria
Auto-Promote Trial Vendor on KPI Thresholds and Sample Size
Given a vendor is in Trial state and has completed assignments >= policy.min_samples And the trailing performance window meets or exceeds policy thresholds for first-time-fix, on-time, and CSAT When a KPI evaluation is triggered by assignment completion or the scheduled evaluator Then the vendor is promoted to the configured target state (Approved or Preferred) and the effective UTC timestamp is recorded And the promotion record stores sample size, KPI values, and thresholds used And eligibility caches reflect the new state within 5 minutes And stakeholders and the vendor receive promotion notifications within 60 seconds
Auto-Pause Trial Vendor Below KPI Floor After Grace Window
Given a vendor is in Trial state and has exited the policy-configured grace window (by time or count) And any KPI in the trailing window falls below the policy floor When the evaluator runs Then the vendor state changes to Paused and becomes ineligible for new trials immediately And the pause record stores KPI values, floor thresholds, window details, and reason code And a cool-down period is started with an end timestamp per policy And stakeholders and the vendor receive pause notifications within 60 seconds
Fair Rotation of Eligible Trial Vendors for New Assignments
Given a new trial-eligible work order with defined issue type, price cap, and distance cap And a pool of eligible trial vendors satisfying policy caps, availability, and cool-down/paused exclusions When vendor selection is executed Then the selected vendor follows the configured rotation algorithm (round-robin or weighted) without assigning the same vendor more than policy.max_consecutive for that category And the selected vendor’s distance and quoted price are within caps And if the selected vendor declines or times out, the system offers the job to the next eligible vendor within policy.response_timeout And over the last 50 eligible assignments per category, each vendor’s share is within policy.fairness_tolerance of its configured weight
Cool-Down Enforcement and Re-Eligibility
Given a vendor is in Paused state with an active cool-down timer When assignment routing runs Then the vendor is excluded from eligibility until the cool-down end timestamp And upon cool-down expiry, the vendor is reintroduced to the rotation only if no new blocking conditions exist and KPI re-evaluation passes policy re-entry rules And any attempt to bypass the cool-down without override permission is rejected with a clear error
Manual Override of Vendor State with Justification and Constraints
Given a user with override permissions initiates a vendor state change When the user selects the target state and submits Then the system requires a justification of at least 20 characters and captures user ID, role, timestamp, and policy snapshot And disallowed transitions are blocked with a clear error message And on success, the state changes immediately, notifications are sent within 60 seconds, and the audit log records old/new state and reason
Instant Notifications on State Transitions
Given any vendor state transition (auto or manual) occurs When the transition completes Then notifications are dispatched to configured recipients via in-app and email (and SMS if enabled) within 60 seconds And the message includes old/new state, trigger type (auto/manual), reason summary, and next steps And delivery status is tracked; failures retry up to 3 times with exponential backoff And unrecoverable failures create a task for the account owner within 2 minutes
Auditable, Immutable State-Change History
Given the system processes KPI evaluations and manual overrides When any vendor state transition is executed Then an immutable audit entry is created including vendor ID, old/new state, actor (system/user), reason, KPI values, thresholds, sample size, grace window/cool-down data, related assignment IDs, and UTC timestamps And audit entries are viewable via UI and API with filters by vendor, date range, actor, and state And audit export to CSV/JSON reproduces all fields and passes checksum validation And attempts to modify or delete existing audit records are rejected and logged
SLA and Cost Guardrails
"As an operations lead, I want SLA and cost guardrails during trials so that service levels and budgets are protected while we test new vendors."
Description

Enforces guardrails to prevent SLA breaches and budget overruns during trials: blocks assignment to emergencies unless explicitly allowed, validates time-to-respond/arrive targets, applies price caps by issue type, and enforces max travel time. Provides pre-dispatch risk checks, warnings, and automatic fallback to trusted vendors when risk is high. Logs guardrail decisions for auditability and continuous tuning.

Acceptance Criteria
Block Emergency Assignments for Trial Vendors
Given a work order with priority "Emergency" and a vendor in "Trial" status where emergency_allowed is false, When the dispatcher attempts assignment via Smart Trials, Then the assignment is blocked, an error code "EMERGENCY_BLOCK" and message are shown, and no dispatch is created. Given the same work order and vendor but emergency_allowed is true, When the dispatcher attempts assignment, Then the emergency guardrail passes and evaluation continues to other guardrails. Given the emergency guardrail blocks assignment, When the dispatcher views alternatives, Then the system displays at least three eligible non-trial trusted vendors ranked by SLA compliance likelihood.
Validate Time-to-Respond and Time-to-Arrive Targets
Given a job with SLA targets respond_by=30m and arrive_by=2h and the predicted vendor response=45m or arrival=2h15m, When assignment is attempted, Then the system blocks with code "SLA_TIME_FAIL" and indicates which target(s) would be breached. Given predicted response and arrival are within targets, When assignment is attempted, Then the SLA time guardrail passes and evaluation continues. Given a user with role "Ops Admin" provides an override reason and selects a time-limited override (<=24h), When they confirm override, Then the assignment is allowed, the override expires as configured, and an audit record with code "SLA_TIME_OVERRIDE" is created.
Enforce Price Caps by Issue Type
Given issue_type "Water Heater Repair" with price_cap=$250 and vendor estimated_trip_plus_labor=$275, When assignment is attempted, Then the system blocks with code "PRICE_CAP_EXCEEDED" and shows cap and estimate values. Given the estimate is <= price_cap, When assignment is attempted, Then the price cap guardrail passes and evaluation continues. Given no estimate is available, When assignment is attempted, Then the system requires a bound estimate and prevents dispatch until provided or an admin override is recorded with code "PRICE_CAP_OVERRIDE" including justification.
Enforce Maximum Travel Time for Trial Vendors
Given max_travel_time=30 minutes for trial vendors and computed vendor travel_time=42 minutes, When assignment is attempted, Then the system blocks with code "TRAVEL_TIME_EXCEEDED" and displays vendor travel_time and max_travel_time. Given computed travel_time <= max_travel_time, When assignment is attempted, Then the travel time guardrail passes and evaluation continues. Given a user with role "Ops Admin" applies a one-time override with reason, When confirmed, Then the assignment proceeds and an audit record with code "TRAVEL_TIME_OVERRIDE" is created.
Pre-Dispatch Risk Scoring and Auto-Fallback
Given a trial vendor candidate with risk_score >= 0.8 and risk_threshold_high=0.7, When assignment is attempted, Then the system auto-selects the next best trusted vendor meeting guardrails, assigns the job, and notifies the dispatcher with code "RISK_AUTO_FALLBACK". Given no trusted vendor meets guardrails, When auto-fallback is attempted, Then the assignment is not created, and the dispatcher is presented with a prioritized list of alternative actions and vendors with reason codes. Given a user with role "Ops Admin" explicitly chooses "Proceed with trial vendor" and enters justification, When confirmed, Then the trial vendor is assigned and an audit record with code "RISK_OVERRIDE" captures the risk_score and justification.
Guardrail Decision Logging and Auditability
Given any guardrail evaluation occurs (pass, block, override, auto-fallback), When the assignment attempt completes, Then an audit record is written within 1 second containing: timestamp, work_order_id, vendor_id, guardrail_type, input_parameters snapshot, outcome, reason_code, actor_id (or system), and correlation_id. Given an auditor queries guardrail logs by work_order_id or date range, When results are requested, Then records are returned within 2 seconds and are immutable (append-only) with tamper-evident checksums. Given metrics aggregation is enabled, When guardrail events occur, Then counters for blocks, overrides, and auto-fallbacks are emitted with labels (guardrail_type, issue_type, market) for continuous tuning.
Vendor Feedback and Coaching Loop
"As a vendor manager, I want to send structured feedback to trial vendors so that they can improve quickly and meet our standards."
Description

Delivers timely feedback to trial vendors via in-app messages and email: post-job summaries (arrival, FTF, tenant comments), weekly progress digests against targets, and actionable recommendations. Supports acknowledgement tracking, knowledge base links, and requests for updated availability or documentation. Encourages rapid improvement and alignment with expectations during the trial window.

Acceptance Criteria
Post-Job Summary Delivery
Given a vendor is in trial and a work order assigned to them moves to Completed When the job completion event is emitted or 30 minutes elapse after the technician marks "Work finished," whichever comes first Then the system sends a post-job summary via in-app message and email to the vendor within 10 minutes including job ID, property address (city/state only), scheduled arrival window, actual arrival timestamp, on-time status, first-time-fix flag, tenant comments (max 300 chars), and SLA adherence And the message contains an "Acknowledge" CTA and a "View job details" deep link And delivery, open, click, and acknowledge events are logged with timestamps And if tenant feedback is not yet available, the system sends an initial summary and appends tenant comments within 24 hours when received And if the email hard-bounces, the system suppresses further emails for that message, shows an in-app banner, and logs status "Email Bounced" And duplicate summaries for the same job are prevented
Weekly Progress Digest Distribution
Given the vendor is in a trial period with at least one job in the last 7 days When the weekly digest scheduler runs at 08:00 in the vendor’s saved timezone each Monday Then the system sends a digest via in-app and email summarizing count of jobs, FTF rate, on-time %, average CSAT, each compared to target thresholds, with trend vs prior 7 days And the digest includes days remaining in trial, current trial tier, and next-step recommendations And if no jobs occurred, the digest indicates "No activity" and omits KPIs And links to the detailed scorecard and KB articles are included with click tracking And each digest has a unique idempotency key to avoid duplicates And failures are retried up to 3 times with exponential backoff and surfaced in ops logs
Acknowledgement Tracking and Reminders
Given a post-job summary or weekly digest is delivered When the vendor has not acknowledged the message within 24 hours Then the system sends an in-app reminder and an email reminder And if still unacknowledged at 48 hours, a second reminder is sent And if still unacknowledged at 72 hours, the vendor’s manager is notified and the vendor scorecard logs "Unacknowledged Feedback" And when the vendor clicks "Acknowledge," the system records user id, timestamp, channel, and origin message id and suppresses further reminders for that message
Actionable Recommendations with KB Links
Given a KPI is below its target threshold (FTF < 85%, On-time < 90%, or CSAT < 4.2) When generating a post-job summary or weekly digest Then the message includes 1–3 specific, data-driven recommendations mapped to the failing KPI(s) And each recommendation links to a relevant KB article with UTM parameters and opens in a new tab And clicking a recommendation records a "Recommendation Viewed" event; clicking "Mark as applied" records a "Recommendation Applied" event And if the mapped KB article is unavailable, a fallback generic article is inserted and flagged in logs
Availability and Documentation Update Request Flow
Given the vendor’s required documentation (COI, license, W‑9) is expired or expiring within 14 days, or availability data is older than 30 days When the nightly compliance job runs Then the system sends an update request via in-app and email with prefilled forms and secure upload links And uploads accept PDF/JPG/PNG up to 10 MB and are virus-scanned; invalid files are rejected with reasons And upon successful submission, the vendor profile is updated immediately and a confirmation message is sent And if documentation remains missing after 7 days, new trial assignments are paused and the manager is notified
Localization, Accessibility, and Channel Preferences
Given the vendor has set language, timezone, and channel preferences When composing any feedback or digest message Then all dates/times are rendered in the vendor’s timezone and content is localized to the selected language (currently en, es) And email and in-app content meet WCAG 2.1 AA contrast and include alt text for images; templates render responsively on screens 320–1200 px And operational messages ignore marketing email opt-outs but respect channel preferences; if email is disabled by preference, in-app delivery still occurs And all preferences are auditable and can be changed by the vendor at any time
Scorecard Update and Auto-Action Integration
Given new job outcomes or acknowledgements are recorded When the trial scorecard is recalculated nightly and on new event ingestion Then the vendor’s FTF, on-time, and CSAT metrics are updated and visible in the scorecard within 5 minutes of event receipt And if for the last 10 eligible jobs FTF ≥ 90%, on-time ≥ 95%, and CSAT ≥ 4.5, the vendor is auto-promoted to Active Trial Tier 2 and notified And if for 2 consecutive weeks any KPI is ≥ 20% below target, the vendor is auto-paused and the manager is notified with an override link And all auto-actions are logged with reason codes and are reversible by a manager within 72 hours

Quality Pulse

Captures quick post‑visit tenant feedback (CSAT, communication, cleanliness) and merges it with rework/callback data into a normalized quality signal. Feeds directly into rankings and flags coaching opportunities with pattern notes. Elevates service quality—not just speed and price—so tenants feel cared for and complaints drop.

Requirements

Instant Post-Visit Survey Delivery
"As a tenant, I want a simple survey right after the visit so that I can quickly share how satisfied I am with service, communication, and cleanliness without friction."
Description

Automatically triggers a mobile-friendly post-visit survey when a work order is marked completed, delivering via SMS, email, or in-app push. Captures 3-tap CSAT plus communication and cleanliness ratings, with optional free-text comments and photo attachments. Supports multi-language templates, configurable branding, and property-specific question sets. Includes retry logic, link expiration, rate limiting, and deduplication to prevent multiple surveys per visit. Handles tenant consent, opt-out, and data retention policies to meet GDPR/CCPA. Provides secure deep links tied to the work order and technician, and records completion timestamps and metadata for downstream analytics.

Acceptance Criteria
Auto-Trigger on Work Order Completion
Given a work order is transitioned to Completed When the completion event is persisted with a timestamp Then the system enqueues a survey send task within the configured SLA And the task is idempotent so duplicate completion events do not create multiple tasks And the task payload includes work order ID, tenant ID, technician ID, and property ID And if the work order is reverted from Completed before send execution, the pending task is canceled
Channel Delivery and Preference Enforcement
Given a tenant has contact methods and channel preferences on file When a post-visit survey is sent Then the system selects SMS, email, and/or in-app push according to configured preference order And channels the tenant has opted out of are never used And in-app push is used only when a valid, active device token exists And the same unique survey link is reused across chosen channels without generating duplicates And sender identities and messaging comply with regional regulations for each channel
Survey Content and UX Capture
Given a tenant opens the survey deep link on a mobile device When the survey loads Then the tenant can submit a 3-tap CSAT rating, communication rating, and cleanliness rating And free-text comments are optional with a configurable character limit And up to the configured number and size of photo attachments can be uploaded with file type validation And the submission is accepted only once per link and returns a confirmation state And expired links display an expiration message without allowing submission
Localization, Branding, and Property-Specific Question Sets
Given the tenant has a preferred language and the work order belongs to a specific property When the survey content is generated Then the system uses the matching language template; if unavailable, it falls back to the default language And property/company branding (logo, colors, sender name) is applied as configured And the property’s question set is used; if not configured, the global default question set is used And right-to-left languages render correctly when applicable
Retry Logic, Deduplication, Rate Limiting, and Link Expiration
Given transient delivery failures may occur When a survey send attempt fails with a retryable error Then the system retries with exponential backoff up to the configured maximum attempts per channel And non-retryable failures (e.g., hard bounce, invalid number) are not retried and are logged And only one active survey is created per work order visit; subsequent triggers do not create duplicates And survey sends adhere to configured rate limits per tenant and per property within a time window And each survey link expires after the configured duration and cannot be used after expiration
Consent, Opt-Out, and Data Retention Compliance
Given GDPR/CCPA compliance is required When determining eligibility to send a survey Then the system sends only to tenants with documented consent or permissible basis for the selected channel And any opt-out received before send suppresses the survey for that channel immediately And opt-outs received after send prevent retries or future sends for that channel And survey responses and metadata are retained only for the configured retention period, then deleted or anonymized And data subject access and deletion requests can retrieve or erase survey data within the configured SLA
Secure Deep Link, Access Control, and Analytics Logging
Given a survey deep link is generated for a completed work order When the link is created Then it contains a non-guessable, single-use token bound to tenant ID, work order ID, technician ID, and expiration And accessing the survey validates the token and denies access if the token is invalid, expired, or bound to a different tenant And subsequent access after successful submission is blocked and displays an already-completed state And completion timestamp, channel of first successful delivery, device type, locale, and response metadata are recorded without storing PII in URLs or logs And analytics events are emitted to the data pipeline within the configured SLA for downstream reporting
Feedback Normalization Engine
"As a property manager, I want a single quality score that blends feedback and callbacks so that I can fairly compare technicians and vendors across properties."
Description

Combines tenant survey results with rework/callback events to generate a normalized quality score per job, technician, and vendor. Applies configurable weights, time decay, and minimum-sample thresholds; detects and mitigates outliers and fraudulent responses. Supports both real-time updates on survey submission and nightly batch recomputation for consistency. Ingests callback data from FixFlow work orders and external systems via API/webhooks. Maintains versioned scoring formulas with audit trails and rollback. Exposes normalized scores via internal API for use by rankings, dashboards, and alerts.

Acceptance Criteria
Real-time score update on survey submission
Given a closed work order with assigned technician and vendor and a valid tenant survey submission And an active scoring formula with configured weights, time decay, and minimum sample thresholds When the survey is submitted via Quality Pulse Then the engine ingests the survey and updates the job-level normalized score within 5 seconds And recomputes affected technician and vendor normalized scores within 10 seconds And persists score records with formula version, sample size, timestamp, and input references And publishes updated scores to the internal scores API within 10 seconds And writes an audit record linking survey id to score version changes
Nightly batch recomputation consistency with real-time
Given the nightly batch window of 01:00–03:00 UTC and real-time updates during the prior day When the batch recomputation runs for the last 30 days of data Then recalculated scores must match real-time scores within an absolute difference ≤ 0.5 on a 0–100 scale or relative difference ≤ 1%, whichever is larger And discrepancies outside tolerance are logged as anomalies with job/technician/vendor identifiers and prior/current values And the batch writes authoritative scores and marks superseded real-time versions as superseded while preserving audit lineage And the batch processes up to 100,000 jobs and completes within 2 hours
Configurable weights, time decay, and version rollback
Given an active scoring formula version V with weights, time-decay half-life, and callback penalties And an admin creates and activates a new version V+1 When V+1 is made active Then subsequent real-time calculations must use V+1 immediately and that night's batch must use V+1 And all score records store the applied formula version And a configuration endpoint returns the active version and parameters And when an admin triggers a rollback to version V via the rollback API Then subsequent calculations switch to V immediately and the next batch uses V And an audit entry records actor, timestamp, prior version, new version, and reason
Minimum sample threshold enforcement and score suppression
Given configurable thresholds of at least N survey responses or M completed jobs in the last 90 days And a technician or vendor below either threshold When a consumer requests the normalized score via the internal API Then the engine returns null for score with suppression_reason="insufficient_sample" and includes sample counts And the entity is excluded from ranking outputs but remains visible in detail endpoints And once thresholds are met, the engine produces a score within 10 seconds of the satisfying event
Outlier and fraud detection and mitigation
Given survey submissions with metadata (IP, user agent, completion time, role, tenant id, property id) and historical distributions per technician/vendor When rules detect anomalies (e.g., z-score ≥ 3, duplicate IP bursts > 3 within 5 minutes, completion time < 5 seconds, non-tenant role) Then the submission is flagged and excluded or downweighted per configured policy with reason codes And affected scores are recomputed and persisted with references to the flagged inputs And an audit entry is created capturing detection signals and final decision And on a labeled test dataset of 1,000 records with 100 fraudulent examples, the detector flags at least 95 fraudulent and no more than 50 legitimate records (recall ≥ 95%, FPR ≤ 5%)
Callback ingestion from FixFlow and external systems
Given a closed work order and a callback/rework event occurring within 14 days of job completion When FixFlow emits a callback event or an external system posts to the /callbacks webhook with a signed request Then the engine ingests the event idempotently (deduplicates by source_event_id) and links it to the original job id with reason code And applies the configured callback penalty to job, technician, and vendor scores in real time And stores late callbacks (> 14 days) but excludes them from scoring by default unless override=true is provided And the ingestion endpoint responds within 500 ms and is retry-safe with signature verification and 24-hour replay protection
Internal API exposure for normalized scores
Given a consumer requests scores via GET /internal/scores with entity_type=job|technician|vendor, id(s), and optional date_range When the request is valid Then the API returns 200 with normalized_score (0–100), sample_size, version_id, updated_at, suppression_reason (if any), and input_window And supports pagination (limit, cursor) and sorting by score and updated_at And returns 400 for invalid parameters and 404 for unknown ids And responds within p95 ≤ 300 ms for cached reads and p95 ≤ 700 ms for uncached reads at 100 RPS And maintains 99.9% monthly availability with a /health endpoint returning dependency status
Technician & Vendor Quality Rankings
"As a dispatcher, I want quality-based rankings in the assignment workflow so that I can route jobs to the best-performing technicians and vendors."
Description

Generates ranked lists of technicians and vendors based on the normalized quality score, with configurable weighting by property, issue type, and recency. Integrates with scheduling/assignment to boost higher-quality options while honoring availability, location, and cost constraints. Suppresses rankings when data is insufficient and shows confidence indicators. Provides leaderboards, filters, and drill-down to underlying jobs and comments, with role-based access controls to protect sensitive data.

Acceptance Criteria
Weighted Quality Rankings by Property, Issue Type, and Recency
Given property-specific and issue-type-specific weights are configured and saved When rankings are generated for a work order matching that property and issue type Then the displayed order reflects the weighted normalized quality scores with recency decay applied Given weights are updated by an authorized user When the rankings are regenerated via UI or API Then the new weights are persisted and used, and the previous configuration is no longer applied Given an invalid weight configuration is entered (e.g., negative values or sum outside allowed range) When the user attempts to save Then the system blocks the change with inline validation errors and retains the last valid configuration Given two providers have identical weighted scores When the ranking is displayed Then a deterministic tie-breaker using confidence score (descending) is applied
Scheduling Suggestions Prioritize Quality Within Constraints
Given a work order with defined location, time window, required skill, and budget constraints When the scheduler requests provider suggestions Then candidates violating availability, service radius, or budget constraints are excluded from the list Given eligible candidates remain after constraint filtering When suggestions are shown Then they are ordered by weighted quality score in descending order and display availability and estimated travel time Given no candidates meet the constraints When suggestions are requested Then the UI shows a clear "No eligible providers" message and offers next-available time options or broaden-filters prompts
Data Sufficiency and Confidence Indicators
Given a provider has fewer than the configured minimum completed jobs or CSAT responses within the selected window When rankings are generated Then the provider is suppressed from leaderboards and labeled "Insufficient data" where referenced Given a provider meets the sufficiency threshold When the ranking is displayed Then a confidence score (0–100) and an explainer tooltip indicating sample size and recency are shown alongside their score Given the user changes the date range filter When rankings refresh Then sufficiency and confidence values recalculate to match the new window
Leaderboards with Filters and Drill-down
Given property, entity type (technician/vendor), issue type, and date range filters are available When a filter is applied or cleared Then the leaderboard refreshes within 2 seconds for datasets up to 10,000 records and reflects the selected filters Given a user clicks a ranked provider When the drill-down view opens Then it lists underlying jobs with dates, issue types, CSAT rating, rework/callback flags, and tenant comments with timestamps Given the current leaderboard view is filtered When the user exports data Then a CSV is downloaded that includes only the filtered rows and columns, with scores and confidence values Given a user navigates from leaderboard to drill-down and back When returning to the leaderboard Then previously selected filters and scroll position are preserved
Role-Based Access Control for Quality Rankings
Given a user with role Admin or Manager accesses rankings for properties in their organization When viewing leaderboards and drill-downs Then full rankings and underlying job details are visible for those properties Given a user with role Technician accesses the module When viewing their information Then only their own aggregated score, confidence, and anonymized tenant comments are visible; peer rankings are hidden Given a user with role Vendor accesses the module When viewing rankings Then only providers within their organization and assigned properties are visible, and tenant PII is masked in drill-down Given a user without permission attempts access via UI or API When the request is made Then a 403 is returned and the attempt is logged with user, endpoint, timestamp, and reason
API and Auditability of Ranking Inputs and Changes
Given an authenticated client with rankings:read scope calls the rankings API with property, issueType, and dateRange parameters When the response is returned Then it includes ranked entities with weighted scores, confidence, and data sufficiency flags that match the UI for the same filters Given an authorized user updates weighting configurations When the change is saved Then an audit log entry is created with user identity, timestamp, before/after values, and affected scope (property/issueType) Given Quality Pulse data is delayed beyond 15 minutes When rankings are requested via UI or API Then a dataFreshness timestamp is included and a stale-data warning indicator is shown to the user
Coaching Insights & Pattern Notes
"As an operations lead, I want automatic insights that highlight coaching opportunities so that I can target training and improve overall service quality."
Description

Identifies recurring patterns such as low cleanliness scores or high callbacks by issue type, building, or technician. Surfaces actionable insights with example jobs, tenant comments, and suggested coaching actions. Allows managers to add notes, assign coaching tasks, and track outcomes over time to measure impact on quality scores. Supports tagging of root causes (parts, training, communication) and generates monthly summary digests for review.

Acceptance Criteria
Detect recurring quality patterns by entity
Given at least 5 completed jobs for a technician, building, or issue type in the last 30 days When the average cleanliness CSAT for those jobs falls below 3.0 OR the callback/rework rate exceeds 15% Then the system creates a quality pattern for that entity within 15 minutes And the pattern includes the metric type, time window, sample size, and entity identifier And duplicate patterns for the same entity, metric, and time window are not created
View actionable insight card with examples and suggestions
Given a quality pattern exists When a manager opens the pattern insight Then the insight displays at least 3 example jobs from the last 60 days (or all if fewer), each linking to job details And shows up to 3 representative tenant comment excerpts with timestamps and sources And lists 1–3 suggested coaching actions mapped to the pattern type And shows current metric vs threshold and a 30-day trend sparkline
Create and assign coaching task from insight
Given a manager is viewing a pattern insight When they create a coaching task Then they must set an assignee, due date, and at least one root cause tag before saving And the task is linked to the originating pattern and entity And task statuses include Open, In Progress, Blocked, and Done with timestamps for status changes And the assignee receives an in-app notification and email within 5 minutes of creation
Compute and display coaching impact over time
Given a coaching task linked to a pattern is marked Done When 14 days have elapsed OR at least 5 new jobs for the entity have completed post-close (whichever comes first) Then the system computes pre vs post using a 30-day window ending at close and a 30-day window starting after close And displays absolute and percentage change for cleanliness CSAT and callback rate with sample sizes And stores an immutable outcome snapshot on the task and updates the pattern timeline
Root cause tagging and filterable analytics
Given the root cause taxonomy includes Parts, Training, Communication, Process, Vendor, Misdiagnosis, Scheduling, and Other When managers tag a pattern or a coaching task Then multiple tags can be applied and must be validated against the controlled list And selecting Other requires a free-text note of at least 10 characters And insights and reports can be filtered by root cause tags And tag frequency and co-occurrence counts are available for the selected period
Monthly coaching summary digest generation and delivery
Given it is the first business day of the month at 09:00 in the manager’s timezone When the system generates the Quality Coaching digest Then it includes the top 5 patterns by risk score with metric values, deltas vs prior month, and links to insights And lists all open and overdue coaching tasks with owners and due dates And includes tag distribution and top recurring root causes for the prior month And sends an email with a link to the in-app report to subscribed managers; if zero patterns and zero open tasks, send a no-action-required notice or honor opt-out settings
Real-Time Quality Alerts & Service Recovery
"As a property manager, I want immediate alerts for poor post-visit feedback so that I can intervene quickly and recover the tenant experience."
Description

Triggers alerts when a submission falls below thresholds or includes concerning keywords, sending notifications via email, SMS, or Slack. Automatically creates follow-up tasks and provides templated outreach to the tenant for rapid recovery. Supports escalation paths, snoozing, deduplication, and configurable quiet hours. Logs alert responses and resolutions to feed back into the normalization engine and coaching insights.

Acceptance Criteria
CSAT Threshold Breach Triggers Multi-Channel Alert
Given Quality Pulse is enabled and CSAT/category thresholds are configured per property And email, SMS, and/or Slack channels are enabled for alerts When a tenant submits post-visit feedback with overall CSAT below the configured threshold Or any category (communication, cleanliness, workmanship) falls below its threshold Then the system generates a single alert within 120 seconds of submission And sends the alert to all enabled channels with tenant name (masked per privacy rules), unit, work order ID, technician, scores, and direct links to the feedback and recovery task And sets the alert status to Pending Acknowledgement And records delivery receipts (success/fail) per channel with up to 3 retries spaced 60 seconds apart on transient failures
Concerning Keyword Detection Initiates Alert
Given a configurable keyword list exists with severity levels and per-property enablement And keyword matching is case-insensitive and supports exact phrase and whole-word matches When tenant comments contain one or more configured keywords (e.g., “unsafe”, “no-show”, “rude”, “water leak”) Then an alert is generated within 120 seconds and marked with the highest detected severity And the triggering keywords/phrases are highlighted in the alert payload And a false-positive control allows users to mark the alert as Not Actionable, which updates keyword precision metrics and suppresses re-alerting on the same feedback
Automatic Service Recovery Task Creation with SLA
Given an alert is generated by threshold breach or keyword detection When the alert is created Then a linked Service Recovery task is auto-created with default assignee based on routing rules (e.g., property manager on duty) And the task contains prefilled context (feedback details, tenant contact preferences, technician, timestamps) and a severity-based SLA (e.g., Critical=2h, High=8h, Normal=24h) And SLA timers start at alert creation and pause during approved snooze periods And task creation is idempotent such that reprocessing the same feedback does not create duplicates And all task changes are auditable (creator, timestamp, previous values)
Templated Tenant Outreach for Rapid Recovery
Given templated outreach messages exist for apology, clarification, and follow-up scheduling across email and SMS And templates support placeholders for tenant name, appointment date, technician name, and property contact When an assignee opens the Service Recovery task Then the system presents recommended templates based on severity and issue type And allows preview, personalization (free text up to 500 chars), and channel selection (email/SMS) with opt-out checks And sends the message within 30 seconds of confirmation and logs send status and delivery receipts And records tenant replies in the task timeline and notifies the assignee
Escalation on Unacknowledged Alerts
Given an alert is in Pending Acknowledgement And an escalation policy is configured with at least two tiers (e.g., Property Manager -> Ops Lead -> Owner) When the alert is not acknowledged within 10 minutes Then the system escalates to Tier 2 via all enabled channels and logs the escalation event And if still unacknowledged after 30 minutes, escalates to Tier 3 and marks the alert as Overdue And any acknowledgement or resolution at any tier stops further escalations And all escalations respect quiet hours unless severity is Critical
Snoozing, Deduplication, and Quiet Hours Controls
Given property-level quiet hours are configured (e.g., 9:00 PM–7:00 AM local) When an alert is generated during quiet hours and severity is not Critical Then notifications are deferred until quiet hours end, while the Service Recovery task is created immediately And the system supports user-initiated snooze (15 min, 1 h, custom up to 24 h) that pauses SLA timers and escalations And deduplication suppresses identical alerts for the same feedback ID or for the same tenant+unit+issue type within 24 hours And suppressed alerts are logged with a reference to the primary alert
Resolution Logging and Feedback Loop to Quality Signal
Given a Service Recovery task is resolved When the assignee marks the task Complete with a required resolution code (e.g., Apology Issued, Revisit Scheduled, Technician Coached) Then the system logs resolution timestamp, actor, code, and free-text notes And updates the normalized quality signal for the technician and vendor, including recovery time and outcome And exposes the event to the coaching insights module and normalization engine via internal API within 5 minutes And provides exportable audit trails (CSV and API) for alerts, actions, and outcomes
Quality Analytics Dashboard & Export
"As an owner, I want to see quality trends and benchmarks across my portfolio so that I can track improvements and make informed vendor decisions."
Description

Provides dashboards for CSAT, communication, cleanliness, callbacks, and normalized scores over time with filters by property, vendor, technician, and issue type. Includes trend lines, distributions, confidence intervals, and cohort analysis (pre/post coaching). Supports CSV export and a read-only API for BI tools. Enforces role-based permissions and masking of tenant PII in comments, with configurable retention windows.

Acceptance Criteria
Quality Metrics Visualization and Trends
Given a date range up to 12 months and available CSAT, communication, cleanliness, callbacks, and normalized score data When the user loads the Quality Analytics dashboard Then trend lines for each metric render within 2 seconds for up to 50,000 events And 95% confidence intervals display when per-bucket sample size >= 30, otherwise a "Low n" badge is shown and CIs are hidden And a distribution view (histogram or boxplot) can be toggled and reflects the same filters/date range And normalized score equals the backend reference implementation within ±0.1 on a 100-sample validation set
Multi-Dimensional Filtering and Segmentation
Given filters for property, vendor, technician, issue type, and a date range When the user applies any combination of up to four filters Then visualizations, counts, and summaries update within 1 second for <=10,000 events and within 3 seconds for up to 200,000 events And applied filters persist in the URL and are restored on refresh and when the link is shared And zero-result states show "No quality data for selected filters" with a clear reset action And the active filters propagate to CSV exports and API query parameters
Pre/Post Coaching Cohort Analysis
Given a coaching intervention date recorded for a technician or vendor When the user selects a pre/post window of 30, 60, or 90 days Then the cohort chart displays mean deltas for each metric with 95% confidence intervals and sample sizes for pre and post And a significance badge appears when |delta| > 0.2 and p < 0.05 (two-proportion z for CSAT/callbacks, t-test for continuous scores) with n >= 30 per cohort And if either cohort has n < 30, a message "Insufficient sample" is shown and significance is disabled And the selected cohort entity and window persist across navigation within the dashboard session
CSV Export of Filtered Quality Data
Given a user with export permission and active dashboard filters/date range When the user clicks Export CSV Then a CSV reflecting current filters and org default timezone is delivered within 60 seconds for up to 200,000 rows or queued with an emailed link if larger And columns include: timestamp, property_id, vendor_id, technician_id, issue_type, csat, communication_score, cleanliness_score, callback_flag, normalized_score, comment_masked And tenant PII (names, emails, phones, unit numbers) within comments is masked as [REDACTED] And the file is UTF-8 with BOM, comma-delimited, LF line endings, and includes a header row And an audit log entry records user, timestamp, filter summary, and row count
Read-Only BI API Access
Given an API consumer with read-only permissions and a valid API key When the consumer issues GET requests to /quality/metrics and /quality/events with filter parameters Then responses return 200 with data scoped to the consumer's permissions and retention window And POST/PUT/PATCH/DELETE requests return 405 Method Not Allowed And tenant PII in comment_text fields is masked identically to CSV exports And rate limiting enforces 60 requests per minute per key with 429 on exceed and Retry-After header And ETag headers support caching with 304 responses when data unchanged And an OpenAPI spec is published at /api-docs with example requests/responses
Role-Based Permissions and Data Scoping
Given users with roles Admin, Manager, Vendor, and Technician When each user accesses the dashboard, exports, or API Then data visibility is scoped: Admin = all org data; Manager = assigned properties; Vendor = their jobs; Technician = their visits And attempts to access data outside scope return 403 and are captured in audit logs And tenant PII in comments is masked for all roles; Admins may temporarily unmask via explicit toggle with justification, and all unmask actions are logged And role or assignment changes take effect within 5 minutes
Configurable Retention and PII Masking Window
Given an organization retention policy (e.g., comments retained 90 days; raw events 18 months) When data exceeds its retention window Then tenant comments are permanently purged or irreversibly masked while aggregated metrics remain available And exports and API exclude purged data and display masked comments as [REDACTED] And updates to retention settings apply within 24 hours to historical data and are recorded in an audit trail And a legal hold flag prevents purge until removed, with status visible to Admins
Multi-Language & Accessibility Support
"As a tenant with language or accessibility needs, I want an easy, accessible survey so that I can provide accurate feedback without barriers."
Description

Localizes survey content, notifications, and dashboards with locale detection and fallback language. Ensures WCAG 2.1 AA compliance with screen reader support, high-contrast mode, large tap targets, and keyboard navigation. Supports RTL scripts and culturally appropriate phrasing. Provides translation management workflows and supports A/B testing of copy to improve response rates.

Acceptance Criteria
Auto-Locale Detection with Fallback Chain
Given a visitor is not logged in and the Accept-Language header is "es-MX,es;q=0.9,en;q=0.8" When they open a Quality Pulse survey Then UI resources load in es-MX; if a key is missing it falls back to es, then en-US, and no raw i18n keys are shown Given a user profile language is set to fr-CA When they view the Quality Pulse dashboard Then content renders in fr-CA and formats dates, numbers, and currency per CLDR for fr-CA Given notification templates exist for multiple locales When the system sends an email and SMS to a tenant with locale pt-BR Then both notifications use pt-BR templates; if unavailable they use pt, else en-US Given a malformed or unsupported locale is detected When the app initializes Then it defaults to en-US and logs a telemetry event for missing locale resolution
WCAG 2.1 AA Screen Reader Announcements
Given a screen reader (NVDA/JAWS/VoiceOver/TalkBack) is active When a user navigates survey questions Then each control exposes correct role, name, and state; labels are programmatically associated and announced Given client-side validation fails on submit When the error summary appears Then focus moves to the summary and errors are announced via aria-live polite with field-level descriptions and links back to fields Given dynamic status changes (loading, success, failure) When content updates Then updates are announced once via appropriate live regions without repetition Given decorative icons exist When rendered Then they are aria-hidden; actionable icons have meaningful aria-label or alt text
High-Contrast Mode and Large Tap Targets
Given a user enables High Contrast in-app or via OS When viewing Quality Pulse interfaces Then text/background contrast meets WCAG AA (>=4.5:1 normal text, >=3:1 large text) and non-text contrast >=3:1 Given interactive elements are displayed on touch devices When a user attempts to tap Then each hit area is at least 44x44 px and spacing prevents overlap taps Given an element is focused or disabled When states change Then state is conveyed by more than color; a visible focus indicator with >=3:1 contrast is present Given page zoom is set to 200% and 400% When navigating surveys and dashboards at 320px width Then content reflows without loss of functionality and avoids horizontal scrolling
Keyboard-Only Navigation
Given a keyboard-only user accesses Quality Pulse When tabbing through surveys and dashboards Then all interactive elements are reachable in a logical order with a persistent visible focus outline Given a modal dialog opens When interacting via keyboard Then focus is trapped within the modal, Escape closes it, and focus returns to the trigger on close Given custom components (toggles, dropdowns, sliders) When operated with a keyboard Then they support standard patterns (Space/Enter to activate, Arrow keys to navigate, Home/End, Tab/Shift+Tab for movement) Given a skip link is provided When the page loads Then "Skip to main content" is focusable on Tab and moves focus to the main region
Right-to-Left (RTL) Script Support
Given a tenant's locale is RTL (e.g., ar, he, fa) When they view surveys, notifications, and dashboards Then layout direction switches to RTL, text aligns appropriately, and mixed LTR content (numbers, codes) remains readable Given directional icons and navigation controls are used When rendered in RTL Then icons and chevrons are mirrored where appropriate; nondirectional icons remain unchanged Given text inputs and placeholders in RTL locales When typing and selecting text Then cursor movement, selection, and punctuation behave correctly; validation and helper text respect RTL direction Given charts and progress components are displayed in RTL When viewed Then axes/legends align for RTL while numeric scales maintain correct orientation
Translation Management Workflow
Given a user with Translator role When accessing the Translation Console Then they can search keys, view source strings, edit translations, and submit for review with placeholder validation Given a user with Reviewer role When reviewing submitted translations Then they can approve or reject with comments; approved strings move to Ready state Given import/export is initiated When handling locale files Then CSV and XLIFF are supported, key IDs are preserved, and variable placeholders are validated before import completes Given versioning and audit are required When a translation changes Then the system records actor, timestamp, diff, and allows rollback to any prior version Given minimum coverage is enforced When releasing a locale Then release blocks if coverage <95% or critical keys missing; missing strings fall back per chain and emit telemetry
A/B Testing of Survey Copy by Locale
Given two variants (A and B) exist for a locale When an eligible tenant opens the survey Then they are randomly assigned 50/50 within that locale and the assignment persists until completion Given experiment guardrails When configuring tests Then a maximum of 2 active variants per locale is allowed and a control is always present; conflicting experiments cannot be scheduled concurrently Given minimum sample and duration thresholds When a test is running Then it auto-pauses if respondents <200 after 14 days or if significance is reached at 95% confidence Given metrics collection When responses are submitted Then response rate, completion time, and CSAT deltas are recorded per variant and locale Given reporting is viewed When analyzing results Then the dashboard shows uplift, p-value, confidence interval, and allows one-click promotion of the winning copy to default

Scope Builder

Auto-generates a unit-specific turnover scope in one click using move-in photos, prior tickets, and your property standards. Ensures nothing is missed—paint touch-ups, filter swaps, caulk lines, deep clean—so you start with a complete, prioritized punch list and shave hours off planning.

Requirements

One-click Scope Generation
"As a property manager, I want to generate a complete turnover scope in one click so that I can save time and ensure nothing critical is missed."
Description

Generate a complete, unit-specific turnover scope from a single action on the unit record by combining move-in photos, prior maintenance history, and property standards. Output a structured, editable punch list with trade tags, quantities, materials, and suggested due dates. Ensure generation completes in under 10 seconds for 95th percentile, supports versioning (draft/final), and preserves an immutable snapshot for audit. Provide idempotency per unit-turn event to avoid duplicate scopes and allow regeneration on updated inputs with clear change diffs. Integrate with FixFlow’s unit, ticket, and vendor data models via internal APIs.

Acceptance Criteria
One-Click Scope Creation from Unit Record
Given an authenticated property manager is viewing a unit record with move-in photos, configured property standards, and prior maintenance tickets When the user clicks "Generate Scope" Then the system creates a new Draft scope linked to the active unit-turn event with a prioritized punch list And each line item includes trade_tag, title, description, quantity with unit, materials (SKU or free text), suggested_due_date, priority, and source (standards|ticket|photo) And the scope is associated to the correct unit and property And the Draft scope is editable immediately after generation
Scope Generation Performance p95 <= 10s
Given representative unit data exists in the system (photos, tickets, standards) When 200 scope generations are executed under up to 5 concurrent requests Then the measured end-to-end time from request to scope ready is <= 10 seconds at the 95th percentile And the measurement is captured in logs/metrics with latency percentiles for the run
Idempotent Generation per Unit-Turn Event
Given a unit-turn event has an existing generated scope and no inputs have changed When "Generate Scope" is invoked again with the same unit_turn_id Then the system returns the same scope_id and does not create a duplicate scope And an idempotency_hit is recorded for the invocation And the scope content returned is identical to the existing scope
Versioning: Draft/Final and Immutable Snapshot
Given a Draft scope exists When the user marks the scope as Final Then a Final version is created and locked against edits And an immutable snapshot of the Final (including all line items and metadata) is stored with content hash, version number, author, and timestamp And any subsequent changes require creating a new Draft version referencing the Final as its parent And an audit log entry records actor, action, timestamp, and version identifiers
Regeneration on Updated Inputs with Change Diff
Given new inputs are available (e.g., new photos, updated ticket statuses, or revised property standards) When the user selects "Regenerate from inputs" on the latest scope Then the system creates a new Draft version And a change diff is presented showing Added, Removed, and Modified line items And for each Modified item, field-level changes (e.g., quantity, materials, suggested_due_date) and the detected change reason are displayed And the user can accept or revert individual changes before saving the Draft
Structured, Editable Punch List and Validation
Given a Draft scope is open When the user edits trade_tag, quantity, materials, or suggested_due_date; or adds/removes line items Then required fields are validated and inline errors shown for missing/invalid values And quantities accept positive numeric values with units; suggested_due_date accepts a valid ISO-8601 date And upon save, changes persist and are visible on reload And aggregate counts and totals update consistently with the edits
Internal API Integration and Error Handling
Given internal APIs for units, tickets, standards, and vendors are reachable When a scope is generated Then the generator retrieves unit metadata, prior maintenance tickets, move-in photos, and property standards via internal APIs And trade tags are mapped to vendor categories so each line item includes vendor_category_id where applicable And if any upstream API fails, the system generates a Draft with available data, flags incomplete sections, surfaces a non-blocking error message with correlation_id, and logs the failure for observability
Computer Vision Defect Detection
"As a landlord, I want the system to auto-detect visible issues from photos so that I don’t have to manually inspect every image to build the punch list."
Description

Ingest and analyze move-in and recent unit photos to automatically identify common turnover issues (e.g., paint scuffs, stains, cracked tiles, missing caulk, damaged fixtures, dirty appliances, filter presence/condition). Produce itemized findings with confidence scores, bounding boxes, and mapped scope templates. Support uploads from web and mobile with EXIF handling, image deduplication, and secure storage. Provide configurable confidence thresholds, a manual review queue for low-confidence detections, and human-in-the-loop validation to improve models over time. Ensure privacy by redacting PII in images and comply with retention policies.

Acceptance Criteria
Auto-Detect Turnover Defects from Move‑In Photos
Given a unit has 12 recent or move-in photos uploaded to FixFlow When the Computer Vision service analyzes the images using the default confidence threshold of 0.60 Then each image response includes zero or more detections with fields: image_id, unit_id, label, confidence (0.00–1.00), bbox [x,y,width,height] in pixels, and detection_id And control images known to contain no defects produce zero detections above threshold And detections are returned in a consistent JSON schema consumable by Scope Builder
Configurable Confidence Thresholds per Defect Type
Given an admin configures per-label thresholds (e.g., paint_scuff=0.75, missing_caulk=0.50) and a global default of 0.60 When images are analyzed Then only detections with confidence >= the configured threshold for their label are included in the results And detections below their label threshold are routed to the Manual Review queue with reason "below_threshold" and are excluded from immediate scope mapping And changes to thresholds take effect for subsequent analyses without service downtime
Manual Review Queue and Human-in-the-Loop Validation
Given detections enter the Manual Review queue with reason in {below_threshold, duplicate_conflict, model_disagreement} When a reviewer marks a detection as Confirm or Reject and optionally adds a comment Then the detection status, reviewer_id, timestamp, and comment are persisted And Confirmed detections are immediately eligible for scope mapping; Rejected detections are suppressed from downstream workflows And the cropped image region, label, and decision are added to a feedback dataset for future model training And an audit log entry is created for each action
Upload Ingestion with EXIF Handling (Web and Mobile)
Given a user uploads images from web or mobile in JPG, PNG, or HEIC up to 25 MB each When the upload completes Then images are stored with corrected orientation using EXIF data, and capture_time and device_orientation are recorded And if EXIF orientation is missing or corrupt, a safe default orientation is applied without failing the upload And the system records uploader_id, unit_id, and source_channel {web,mobile} for each image And a normalized analysis rendition (max long edge 4096 px, JPEG) is generated while preserving the original
Image Deduplication Within Unit Uploads
Given a batch contains near-duplicate images of the same scene for the same unit When images are processed Then perceptual hashing identifies near-duplicates below the configured distance threshold within the same unit/session And only the earliest image is retained as primary; duplicates are linked to the primary with reason "near_duplicate" And duplicates are not re-analyzed; their results reference the primary image’s detections And a dedup summary is available listing the count of merged duplicates for the batch
Privacy, Security, and Retention Compliance
Given an uploaded image may include faces, license plates, or sensitive documents When images are stored or displayed in FixFlow UIs Then detected PII regions are automatically redacted (blurred) on all derived renditions prior to reviewer or tenant access And all images and detection data are encrypted in transit and at rest, and access is restricted to users within the property’s organization with appropriate roles And images are retained for the configured period (default 24 months) and hard-deleted within 30 days after expiration or explicit delete request And access, redaction, and deletion events are captured in an immutable audit log
Map Detections to Scope Templates and Prioritize
Given there are confirmed detections for a unit When the mapping service runs Then each detection is mapped to a standardized scope line item including: scope_item_id, trade, location, action, priority, estimated_effort_range, and required_materials (when applicable) And overlapping detections (IoU >= configured threshold) are consolidated into a single scope line with aggregated evidence And the resulting scope is emitted as a JSON payload attached to the unit’s turnover plan and is visible in the Scope Builder UI
Standards & Rule Engine Mapping
"As a portfolio owner, I want my property standards automatically applied to each unit so that scopes are consistent and compliant without manual setup."
Description

Model property standards as reusable rules that map unit attributes (unit type, finishes, flooring, paint color, appliance models) to required turnover tasks (e.g., filter size, paint code, caulk type, cleaning level). Allow property- and portfolio-level overrides and effective dates. Generate parameterized scope items with SKUs, materials, and trade assignments derived from the rules. Provide a rules editor with validation, versioning, and test cases against sample units. Ensure deterministic rule resolution with clear precedence and conflict reporting. Expose a decision log per scope for transparency.

Acceptance Criteria
Unit Attribute-to-Task Rule Mapping
Given a unit with attributes (unit type, finishes, flooring, paint color, appliance models) and an active property standard And a published ruleset mapping attributes to turnover tasks When the Scope Builder generates a scope for this unit Then all tasks whose rule conditions evaluate true are included in the scope And no tasks whose rule conditions evaluate false are included And default rules apply only when no more-specific rule matches And the resulting task set matches the expected fixture for this unit in automated tests And rules evaluation for a single unit with up to 1,000 active rules completes in ≤ 500 ms
Portfolio and Property Overrides with Effective Dates
Given a portfolio-level rule R1 and a property-level override rule R2 with defined effective date ranges And the system date used for evaluation is configurable per scope build When the scope is generated for a date within R2’s effective range Then R2 supersedes R1 for overlapping task domains When generated for a date outside R2 but within R1’s range Then R1 applies And expired rules (end date < evaluation date) do not apply And future-dated rules (start date > evaluation date) do not apply And in overlapping windows, precedence is Property > Portfolio > Default And the decision log records the selected rule, the discarded rule, and the effective-date rationale
Parameterized Scope Item Generation (SKUs, Materials, Trades)
Given matched rules define parameterized outputs (SKU, materials with quantities/units, trade assignment, labor estimates) When the scope is generated Then each scope item contains a resolvable SKU ID referencing the active catalog And each material line has a quantity and unit, and price extensions are computed from catalog pricing And each item has a trade assignment from the allowed trade list And labor and material totals are computed deterministically from inputs And any unresolved catalog reference (SKU or material) causes the item to be flagged "Needs Resolution" and listed in validation results And for the standard fixture set, 100% of items resolve to valid SKUs and trades (0 unresolved references)
Deterministic Rule Resolution and Precedence with Conflict Reporting
Given multiple rules may target the same task domain for a unit When evaluating applicable rules Then precedence is applied in this order: Unit-specific > Property > Portfolio > Default And within the same level, higher specificity (exact model/code) outranks wildcard And remaining ties are broken by numeric priority, then by most recent published version And repeated runs with identical inputs select the same rule and produce identical outputs And if a tie cannot be resolved by precedence, a Conflict is raised listing rule IDs, levels, priorities, and fields in contention, and only one result is emitted per task domain while the conflict count > 0 is recorded
Rules Editor: Validation, Versioning, and Test Cases
Given a user edits or creates a rule in the rules editor When the user attempts to save Then validation enforces required fields (level, conditions, outputs, effective dates) and valid syntax And catalog references (SKUs, materials, trades) must exist and be active And effective date ranges cannot overlap conflicting rules at the same level unless marked as an explicit override target And save is blocked if any attached test case fails against its sample unit and expected outputs And publishing creates a new immutable version with audit metadata (author, timestamp, change note) and preserves prior versions read-only And users can run attached test cases on-demand and see Pass/Fail with diffs of expected vs. actual
Per-Scope Decision Log Transparency
Given a scope has been generated for a unit When a user or API client requests the decision log Then the log includes the unit attribute snapshot, evaluated rules count, matched rule IDs with level/priority/version, applied overrides, and effective-date evaluations And for each scope item, the selected rule, discarded alternatives, parameter calculations, and pricing derivations are recorded And conflicts and validations (including unresolved references) are listed with severity And the log is accessible via UI and API, exportable as JSON and CSV And response time for retrieving a decision log with up to 200 scope items is ≤ 200 ms in the test environment And logs are retained for at least 13 months
Prior Tickets & History Merge
"As an assistant property manager, I want past issues to be auto-included or flagged in the turnover scope so that recurring problems aren’t missed during turnover."
Description

Pull prior tickets and inspections for the unit (configurable lookback, default 12 months) to include unresolved, recurring, or deferred items in the turnover scope. Deduplicate against already-completed work and CV-detected items using normalized categories and item fingerprints. Flag warranty or vendor callback candidates and link original ticket IDs for traceability. Provide filters to include/exclude items by status, severity, or cost impact. Sync via internal services with resilience to partial outages and clear user-facing error messages.

Acceptance Criteria
Default 12-Month Lookback Pull
Given a unit with tickets and inspections created in the last 24 months and no custom lookback is set When the user generates a Scope Builder turnover plan for that unit Then the system retrieves all prior tickets and inspections dated within the last 12 months And excludes items older than 12 months from inclusion And includes items with status Unresolved, Recurring, or Deferred in the draft scope And completes the history retrieval within 3 seconds for up to 500 historical records And records the retrieval window (start/end dates) in the scope metadata
Configurable Lookback Override
Given a property-level lookback preference of N months or a custom date range is configured And a unit has historical tickets and inspections When the user generates the scope Then the retrieval window respects the configured preference (N months or date range) And the active window is displayed to the user in the scope header And values are validated: N must be an integer between 1 and 36; date ranges must not exceed 36 months and start <= end And if validation fails, the user sees an inline error and the default 12-month window is applied
Deduplication via Normalized Categories and Item Fingerprints
Given prior tickets/inspections and CV-detected items may refer to the same underlying issue When the system merges history into the scope Then items are deduplicated using normalized categories and item fingerprints with a similarity score threshold >= 0.9 And duplicates collapse into a single scope line item And the merged item retains highest severity, latest status, and most recent note timestamp And the merged item stores all source references and shows a "View sources" detail with at least ticket IDs and detection type And no duplicate line items appear in the scope list
Warranty and Vendor Callback Flagging
Given a prior ticket was completed by a vendor and is within the applicable warranty window defined by policy Or a repeat issue occurs within the policy-configured recurrence window after completion by the same vendor When the history is merged Then the related scope item is flagged as "Warranty/Callback Candidate" And links to the original ticket ID(s) and shows vendor name and completion date And displays the warranty window end date and reason code (e.g., within warranty, early recurrence) And exposes a direct "Initiate Callback" action that references the original ticket
Status, Severity, and Cost Impact Filters
Given the scope contains items with varying status, severity, and estimated cost impact When the user adjusts filters for Status, Severity, or Cost Impact Then the scope list updates immediately to include/exclude matching items And the visible item count and summary chips reflect the current filters And exporting or saving the scope respects the active filters And resetting filters returns to the default view including unresolved, recurring, and deferred items
Resilient Sync with Partial Outage and Error Messaging
Given internal ticket and inspection services may experience timeouts or partial outages When generating the scope Then the system returns available results and clearly indicates which sources are incomplete via a non-blocking banner And shows actionable messaging with a "Retry" control and a timestamp of last successful fetch And retries failed sources with exponential backoff up to 2 additional attempts per source And per-source timeouts do not exceed 4 seconds; total generation time does not exceed 8 seconds for partial results And errors are logged with correlation IDs without exposing internal stack traces to the user
Traceability and Audit Log for Merge Operation
Given a scope item created from merged history When viewing the item details or exporting the scope Then the item shows its source lineage including original ticket IDs, inspection IDs, detection types, and fingerprint hash And the API payload includes a sources[] array with type, id, category, fingerprint, and timestamps And an audit log entry records deduplication decisions (kept vs. suppressed IDs) and the criteria used And a "Removed as duplicate" counter is shown for transparency at the scope level
Priority Scoring & Categorization
"As a maintenance coordinator, I want items prioritized automatically so that I can schedule the most critical work first and hit move-in dates."
Description

Assign each scope item a priority based on safety, habitability, regulatory compliance, recurrence, seasonality, estimated time/cost, and tenant move-in date. Produce a recommended execution order and group items into categories (Critical, Preventive, Cosmetic). Allow manual reprioritization with required reason capture and audit trail. Expose scoring inputs to users for transparency and enable property-level weighting configurations. Provide API fields for downstream scheduling and vendor allocation logic.

Acceptance Criteria
Priority Score Calculation Across Required Factors
Given a generated turnover scope for a unit with populated factor inputs (safety, habitability, regulatory compliance, recurrence, seasonality, estimated time, estimated cost, tenant move-in date) When the system computes the priority score for each scope item Then the score calculation uses all eight factors and the current property-level weights And the same inputs always produce the same score (deterministic) And items with identical inputs except for an earlier tenant move-in date receive a higher score than those with a later move-in date And the computed score and each factor’s raw value and weighted contribution are stored and retrievable
Recommended Execution Order Generation
Given a scope containing N items with computed priority scores When the system generates the recommended execution order Then items are ordered from highest to lowest priority score And ties are resolved deterministically and the resulting order is stable for identical inputs And each item receives a unique recommended_order_index starting at 1 with no gaps And regenerating the order without input changes preserves the same order
Category Assignment Rules (Critical, Preventive, Cosmetic)
Given a scope item with a computed priority score and factor flags When the system assigns a category Then the item is assigned to exactly one category among Critical, Preventive, or Cosmetic per the configured category rules And items meeting configured safety, habitability, or regulatory violation triggers are categorized per configuration (e.g., Critical) regardless of other factors And category assignment is displayed in the UI and included in exports and API responses
Manual Reprioritization with Required Reason and Audit Trail
Given a user viewing a generated scope item When the user manually changes the item’s priority score, category, or recommended order Then the system requires a non-empty reason before saving And the item is marked as overridden while preserving the system-generated score and category for reference And an audit record is created capturing before/after values, user, timestamp, and reason And the audit history is viewable in the UI and accessible via API
Scoring Transparency in UI
Given a user opens the details drawer for a scope item When viewing the scoring breakdown Then the UI displays each factor’s raw value, applied weight (from the active property configuration), weighted contribution, and the total score And the UI indicates whether the current values are system-generated or user-overridden And the timestamp and version of the weights used are displayed
Property-Level Weighting Configuration
Given a property manager opens Property Settings for Scope Builder When they set or update weights for the eight scoring factors Then the system validates, saves, and activates the configuration for that property And newly generated scopes use the updated weights by default And existing scopes can be recalculated on demand to apply the new weights, with recalculation logged in the audit trail
API Fields for Scheduling and Vendor Allocation
Given an authenticated API client requests scope items When retrieving items by property_id and scope_id Then each item includes fields: priority_score, priority_category, recommended_order_index, scoring_factors (raw values), applied_weights, is_overridden, override_reason, audit_trail_uri, and weights_version_id And responses support pagination, sorting by priority_score and recommended_order_index, and filtering by category and override status And the API contract is documented with field definitions and example payloads
Cost Estimation & Budget Guardrails
"As a property owner, I want an estimated cost for the turnover scope with alerts when we exceed budget so that I can control expenses before authorizing work."
Description

Calculate per-item and total scope costs using property price books, labor rates, and material SKUs, with support for regional modifiers, taxes, and surcharges. Show low/medium/high cost ranges with confidence using historicals and model-based estimates. Compare totals against property or unit-level budget caps and require approval for overages. Provide exportable cost breakdowns and fields for purchase orders. Support multi-currency and rounding rules where applicable.

Acceptance Criteria
Price Book–Driven Itemized and Total Cost Calculation
Given a property has an active price book with material SKUs, unit costs, labor rates, and standard labor hours per task And a generated scope contains line items referencing those SKUs with specified quantities When the scope cost is calculated Then each line item shows material cost = unit cost × quantity And labor cost = labor rate × standard hours × quantity And line item total = material cost + labor cost And scope subtotal equals the sum of all line item totals within a tolerance of ±0.01 in the scope currency And any line item missing a required price book value is flagged with a validation error and excluded from subtotal until resolved
Application of Regional Modifiers, Taxes, and Surcharges
Given a property has configured regional modifiers, tax rates, and applicable surcharges for its location And modifier applicability (labor/material/both) is defined When the scope cost is calculated Then the regional modifier is applied to eligible costs as configured and displayed as a separate row per line item and in totals And tax is calculated on the correct base (pre- or post-modifier) per configuration and shown as a separate field And configured surcharges (e.g., trip fee, disposal) are added when their conditions are met and itemized in totals And grand total = subtotal + modifiers + taxes + surcharges within a tolerance of ±0.01
Low/Medium/High Cost Ranges with Confidence Scores
Given historical job data and/or model estimates are enabled for the property And at least 10 matching historical records exist for an item category or a model fallback is available When cost ranges are generated Then each line item displays Low, Median, and High estimates and a 0–100 confidence score And the confidence score is ≥70 when historical sample size ≥50 and variance is low, else it is calculated proportionally and may be labeled Low Confidence when <50 And the total scope shows aggregated Low/Median/High ranges consistent with line items within ±1% aggregation error And a details panel shows basis: data source (historical/model), sample count, and date range
Budget Cap Enforcement and Overage Approval Workflow
Given a unit-level or property-level budget cap is configured and a scope total has been calculated When the total is ≤ the budget cap Then exporting, PO generation, and scheduling are allowed without additional approval When the total is > the budget cap by up to 10% Then manager approval is required and recorded (approver, timestamp, amount) before exporting, PO generation, or scheduling When the total exceeds the budget cap by more than 10% Then owner approval is required and recorded before proceeding And until required approvals are granted, blocked actions are disabled with a visible overage banner and reason
Exportable Cost Breakdown (CSV and PDF)
Given a calculated scope with itemized costs, modifiers, taxes, surcharges, and totals When the user exports the cost breakdown as CSV or PDF Then the export includes columns: Item ID/SKU, Description, Quantity, Unit Cost, Labor Cost, Modifier Amounts, Tax, Surcharges, Line Total, Currency, Vendor (if mapped) And document totals match on-screen totals within rounding rules And the file name follows the convention Property_Unit_ScopeCost_YYYYMMDDHHmm.ext And exports for scopes up to 200 line items complete within 5 seconds And PDF renders page headers with property, unit, scope ID, and page numbers
Purchase Order Fields and Draft Generation
Given vendor mappings exist for SKUs and a calculated scope is approved (if required) When the user generates purchase orders from the scope Then line items are grouped by vendor into draft POs And each PO contains: Vendor, Ship-To, Bill-To, Currency, Line Items (SKU, Description, Qty, Unit Price, Tax, Line Total), Expected Delivery Date, and Notes And PO numbers follow the configured schema (e.g., {PROPERTY}-{YYYY}-{SEQ}) without duplication And editing a PO draft updates PO totals and keeps scope cost totals synchronized in real time And PO totals adhere to the property’s rounding rules
Multi-Currency Support and Rounding Rules
Given a property currency is set and an exchange rate source is configured And some SKUs or labor rates may be priced in other currencies When the scope is calculated and displayed Then all amounts are converted to the property currency using the exchange rate effective on the calculation date And currency symbols and number formats match the locale settings (e.g., €1.234,56 for de-DE) And rounding follows the property rule (e.g., round half up to the nearest 0.05) consistently across line items, totals, and exports And any variance introduced by rounding is ≤0.01 in the property currency and is accounted for in a rounding adjustment line item
Approvals Workflow & Change Tracking
"As a property manager, I want a clear approval process with tracked changes so that scopes are controlled and auditable before work is scheduled."
Description

Enable configurable approval flows for draft scopes with thresholds by total cost, category, or trade. Support single and multi-step approvers, comment threads, and change requests. Track all edits with version diffs, author, timestamp, and reason codes. Notify stakeholders on submission, approval, rejection, and material changes. Lock approved scopes for execution while allowing controlled change orders with re-approval as needed. Maintain a complete audit trail for compliance and reporting.

Acceptance Criteria
Auto-routing by Cost Threshold
Given an approval policy configured with cost thresholds per property and owner And threshold bands: 0–999 = Property Manager; 1,000–4,999 = Property Manager then Owner; >=5,000 = Property Manager then Owner then Compliance And a draft scope total is automatically calculated from line items When a user submits a draft scope totaling $4,200 for Property A Then the system assigns approvers in sequence: Property Manager, then Owner And does not include Compliance in the route And displays the route with step numbers, due-by timestamps, and SLA per step And records the routing decision, threshold band, and total in the audit log
Category/Trade-based Routing Rules
Given an approval policy with category and trade rules (e.g., Electrical requires Compliance; Life-Safety requires 24h SLA) And scope line items are tagged with categories/trades per property standards When a draft scope containing at least one Electrical item is submitted Then Compliance is added as an approver per rule regardless of total cost And when a scope contains only Cleaning and Paint with total $800 Then the route follows the cost-only rule without Compliance And the audit log captures rule IDs that triggered inclusion/exclusion
Single- and Multi-step Approvals with SLAs and Escalation
Given sequential approval steps are defined with SLA hours per step and escalation paths And approvers have contact methods configured When step 1 approver does not act within the SLA Then the system escalates to the designated backup approver and notifies stakeholders And timestamps the escalation in the audit log When an approver approves Then the workflow advances to the next step immediately And when any approver rejects Then the workflow status becomes Rejected and all pending steps are canceled
Comment Threads and Change Requests on Draft Scopes
Given each draft scope supports threaded comments at scope and line-item levels And approvers can Request Changes with required reason codes When an approver requests changes on a specific line item Then the requester must select a reason code and may add a comment And the scope status becomes Changes Requested And the drafter receives a notification with the items to revise When the drafter edits the scope and resubmits with a re-submission note Then prior approvals reset and the workflow restarts at step 1 And the audit trail links the change request, edits, and re-submission note
Version Diffs and Audit Trail
Given versioning is enabled for draft scopes, approved scopes, and change orders When any field in the scope is edited and saved Then a new version is created with author, timestamp, changed fields, before/after values, and a required reason code And a human-readable diff view and a machine-readable JSON diff are available And versions are immutable and exportable in a compliance report with pagination and filters by date, author, and reason code
Notifications on Submission, Approval, Rejection, and Material Changes
Given notification templates exist for submission, approval, rejection, escalation, and material change events And stakeholders have delivery preferences (email, SMS, push, in-app) When a scope is submitted Then the drafter and first-step approver receive notifications with a link to the scope, total cost, SLA, and next actions When a scope is approved or rejected at any step Then all stakeholders receive status updates with actor, timestamp, and comments When a material change is made (delta cost ≥ configured threshold or category change) Then re-approval is triggered and a material change notification is sent And duplicate notifications for the same event within 5 minutes are suppressed
Locking Approved Scopes and Controlled Change Orders with Re-approval
Given a scope is fully approved When the scope is marked Approved Then the scope is locked against edits except permitted operational fields (schedule dates, assigned technician) that do not alter cost or scope content And any attempt to edit protected fields prompts creating a Change Order with a required reason code When a Change Order increases total cost by ≥ the configured threshold or adds restricted categories Then the change order requires re-approval per routing rules And upon final approval, the original scope remains immutable and the change order is linked in the audit trail

Photo Compare AI

Lines up move-in vs. move-out photos side by side, highlights differences, and labels likely tenant-caused damage with confidence notes. Produces dispute-ready evidence packets and suggested line items, speeding chargebacks while staying fair and transparent.

Requirements

Smart Photo Pairing & Alignment
"As a property manager, I want the system to automatically pair and align move-in and move-out photos of the same area so that I can review differences quickly without manual sorting or adjustments."
Description

Automatically ingests move‑in and move‑out photos from uploads, mobile capture, and the FixFlow media library, then pairs and aligns them for like‑for‑like comparison. Normalizes EXIF orientation, resolution, and color/exposure, corrects lens distortion, and uses feature matching to suggest top candidate pairs with confidence scores while supporting manual overrides and locks. Handles partial crops and different vantage points via perspective adjustment and padding. De‑duplicates via perceptual hashing, validates timestamps, and flags unmatched photos for user review. Associates each pair with property, unit, room/fixture tags, and the lease period using existing FixFlow data. Persists aligned outputs as derived assets in the media store and preps them for difference analysis. Targets batch pairing of 1,000 photos within 15 minutes with resumable, queued processing and full audit logging.

Acceptance Criteria
Multi-Source Ingestion and Batch Pairing Performance
Given a batch of 1,000 photos from uploads, mobile capture, and the FixFlow media library across multiple properties When batch pairing is started Then 95th-percentile completion time is <= 15 minutes and no single job exceeds 20 minutes And at least 99% of supported files are successfully ingested and queued on first attempt And processing resumes within 60 seconds after a service restart without reprocessing completed items And per-property FIFO ordering is preserved in the queue And failed items are retried up to 3 times with exponential backoff and surfaced as actionable errors
EXIF Normalization and Lens Correction
Given move-in and move-out photos with varying EXIF orientations, resolutions, and lens distortion When normalization runs Then output images are stored with EXIF Orientation=1 (upright) for both assets And aligned canvases share identical pixel dimensions (width and height match within 1 px) And lens distortion correction yields straight-line deviation <= 2% over at least 80% of the frame And exposure/color normalization results in mean luminance difference <= 10% between the pair
Perspective and Crop Handling for Misaligned Shots
Given move-in and move-out photos of the same fixture taken from different vantage points or with partial crops When alignment is computed Then a perspective transform (homography) is applied to maximize overlap with overlap area >= 60% And padding is applied to non-overlapping regions using neutral fill so no original content is cropped And alignment quality meets an inlier ratio >= 0.5 with at least 50 matched keypoints; otherwise the pair is flagged as low alignment confidence
Confidence Scoring and Manual Override/Lock
Given a selected move-in photo and a candidate set of move-out photos for the same unit/room/fixture When pairing suggestions are generated Then the top 3 candidate pairs are returned with confidence scores in [0,1] And the top suggestion has confidence >= 0.7 in at least 90% of correctly paired validation cases And a user can manually select a different match and lock the pair And locked pairs are not altered by subsequent auto-reprocessing unless explicitly unlocked by a user
Perceptual De-duplication and Timestamp Validation
Given a batch containing duplicates and near-duplicates When perceptual hashing is applied Then images with Hamming distance <= 8 are marked as duplicates and only the highest-quality instance is retained for pairing And duplicate relationships are logged with source asset IDs and suppression reasons And photo capture timestamps are validated against the lease period and assets outside the window are flagged for review
Unmatched Photo Flagging and Review Workflow
Given photos that cannot be confidently paired When pairing completes Then all unmatched photos appear in an Unmatched queue with reason codes (low confidence, missing counterpart, invalid timestamp, or insufficient tags) And users can edit tags (property/unit/room/fixture) and re-run pairing on selected items And successfully re-paired items leave the Unmatched queue and the action is captured in the audit log
Pair Association, Derived Asset Persistence, and Audit Logging for Diff Prep
Given a successfully paired and aligned photo set When results are persisted Then the pair is associated to property, unit, room/fixture tags, and lease period using existing FixFlow IDs And aligned outputs are stored as derived assets with links to source asset IDs and alignment metadata (transform matrix, padding, normalization parameters) And assets are marked diff-ready and are discoverable by the difference analysis service within 2 seconds of persistence And an audit log entry records creator/updater identity, timestamps, input asset IDs, confidence score, override/lock status, and processing duration
Visual Difference Detection & Highlights
"As a landlord, I want clear visual highlights of differences between move-in and move-out photos so that I can quickly spot potential damage without scrutinizing every pixel."
Description

Computes objective before/after changes using lighting‑robust comparison (e.g., exposure normalization, SSIM/LPIPS, and edge/texture differencing) to detect material changes between aligned photo pairs. Produces heatmap overlays and bounding boxes for changed regions, assigns severity levels, and provides a sensitivity slider to tune noise vs. signal. Uses semantic segmentation to ignore transient objects (people, pets, cleaning supplies) and reflections/shadows. Offers interactive before/after slider, zoom, and toggleable overlays on web and mobile. Stores annotated results as layers linked to the case in FixFlow and supports queued, retryable processing with progress indicators. Aims for <5 seconds average processing per pair at standard resolutions and graceful degradation for very large images.

Acceptance Criteria
Lighting-robust aligned photo difference detection
Given two aligned photos with only a global exposure shift up to +/- 1.5 EV, When processed at default sensitivity, Then no Medium/High severity regions are returned and total changed area < 2% of image. Given two aligned photos with a material change occupying >= 1% of image area, When processed at default sensitivity, Then at least one bounding box has IoU >= 0.8 with the changed region and severity >= Medium. Given two identical images, When processed, Then zero change regions and an empty overlay are returned.
Heatmap and bounding boxes with severity labeling
Given a pair with detected changes, When results are generated, Then a heatmap overlay (same dimensions as input) and vector bounding boxes (x,y,width,height) are produced. Given bounding boxes, When viewed, Then each box displays severity in {Low, Medium, High} and confidence between 0.00 and 1.00 with two decimal places. Given multiple adjacent changes, When results are generated, Then overlapping boxes are merged such that duplicated area across boxes is <= 10% of the total changed area.
Sensitivity slider effect on noise vs. signal
Given sensitivity at minimum, When processing a pair with only lighting/shadow differences, Then change pixels are reduced by >= 70% compared to default sensitivity and no Medium/High regions are returned. Given sensitivity at maximum, When processing a validated test pair with ground-truth changes, Then recall of ground-truth changed pixels is >= 90% and at least one box per ground-truth region is produced. Given the user adjusts the slider by any amount, When reprocessing completes, Then updated overlays render within 2 seconds without a full page reload.
Semantic masking of transient objects, reflections, and shadows
Given people, pets, cleaning supplies, reflections, or shadows are present, When processed, Then these classes are masked and do not produce bounding boxes or increase severity. Given a mirror or glass surface shows movement in reflection only with no surface change, When processed, Then no change region is emitted for the reflection. Given a transient object partially occludes damage, When processed, Then detected damage boxes maintain IoU >= 0.5 with ground truth for the visible portion.
Interactive before/after viewer on web and mobile
Given supported browsers (Chrome, Edge, Safari latest) and mobile apps (iOS/Android latest 2 versions), When a result is opened, Then the before/after slider is draggable, overlays can be toggled on/off, and zoom up to 4x with pan is supported. Given a user toggles heatmap or bounding boxes, When toggled, Then the UI updates within 100 ms and the toggle state persists during the session and across zoom/pan. Given a zoomed-in view at 3x, When panning, Then overlay alignment error remains <= 2 pixels relative to the underlying image.
Processing performance, queueing, retries, and progress indicators
Given 100 standard-resolution pairs (longest side <= 2048 px), When processed under normal load, Then average processing time per pair < 5 s and p95 < 7 s. Given a submitted job, When the queue is non-empty, Then the UI shows status transitions {Queued, Processing, Complete, Failed} with progress updates at least every 2 s. Given a transient processing error (e.g., timeout or HTTP 5xx), When it occurs, Then the job is retried automatically up to 3 times with exponential backoff; upon final failure, status is Failed with an error code and retry count. Given very large images (e.g., > 24 MP or > 6000 px longest side), When processed, Then the system downscales automatically, displays a notice to the user, and completes without out-of-memory errors; p95 processing time < 15 s.
Storage of annotated layers linked to FixFlow case
Given a successfully processed pair, When saving results, Then heatmap raster, bounding boxes (x,y,width,height), severity, confidence, and sensitivity settings are stored as versioned layers linked to the FixFlow case and photo pair IDs. Given a case is reopened, When retrieving results, Then the exact saved overlays and metadata are returned and rendered identically to when saved without recomputation unless the user changes sensitivity. Given reprocessing is triggered with a new sensitivity, When saved, Then a new layer version with timestamp and user ID is created and prior versions remain accessible read-only.
Damage Classification & Confidence Scoring
"As a small property manager, I want the system to label likely tenant-caused damage with confidence notes so that I can make fair, defensible decisions faster."
Description

Classifies detected change regions into configurable damage categories (e.g., wall scuffs, holes, stains, appliance dents, flooring scratches, pet-related) and differentiates likely tenant-caused issues from normal wear-and-tear using policy rules (tenancy length, asset age from the register, prior condition notes). Outputs per-region labels with confidence scores, rationale notes (e.g., pattern match, texture break, abnormal discoloration), and a conservative default when confidence is low. Supports manager overrides, captures feedback for model improvement, and version-locks model outputs for auditability. Provides adjustable thresholds to control when labels are suggested vs. withheld. Integrates with FixFlow’s unit records, move-in reports, and asset registry to enrich decisions while ensuring transparent explanations to users.

Acceptance Criteria
Per-Region Damage Classification with Confidence Scores and Rationale
Given aligned move-in and move-out photos with N detected change regions When the classifier runs Then for each region the system outputs a label from the active damage category configuration, a confidence score in [0.00, 1.00] with two-decimal precision, 1–3 rationale notes from a controlled list (e.g., pattern match, texture break, abnormal discoloration), and region bounding coordinates Given the active damage categories have been configured When classification completes Then no output label falls outside the configured categories Given a region where no category meets the model confidence floor When results are returned Then the label is Uncertain with confidence ≤ 0.40 and the rationale notes indicate low-evidence causes
Tenant-Caused vs Wear-and-Tear Decision Using Policy Rules
Given an active policy configuration with rules using tenancy length, asset age from the asset registry, and prior condition notes from the move-in report When a region receives a damage label Then the system computes causeClassification as one of [Likely Tenant-Caused, Wear-and-Tear, Indeterminate] and attaches a rulesFired list referencing rule IDs and input values used Given any required policy input is missing or stale When the decision is computed Then causeClassification is Indeterminate and missingData is populated with the absent fields Given policy rules are updated to a new version When the next classification run executes Then the decision references the new policyConfigVersion and rulesFired align to that version
Conservative Default Behavior When Confidence is Low
Given a configured suggestionThreshold in [0.00, 1.00] When a region's confidence is below suggestionThreshold Then the label is withheld from suggestion UI, the region is marked Needs Review, and no chargeback line item is generated Given a region's confidence is equal to or above suggestionThreshold When results are presented Then the suggested label and causeClassification are displayed to the user along with rationale notes
Manager Override, Reason Capture, and Feedback Loop
Given a user with Manager role views a suggested label When they override the label and/or causeClassification Then the system records userId, timestamp, reason (1–250 chars), previous values, and new values, and the original modelSuggestion remains immutable Given a manager submits an override When the session ends Then a feedback record is queued containing imagePairId, regionId, modelVersion, policyConfigVersion, original label/confidence, override label/causeClassification, and reason Given a region has a manager override When the model is re-run on the same image pair Then the override remains the authoritative value and any new model suggestion is shown as secondary without overwriting the override
Version-Locked Outputs and Reproducible Audit Trail
Given a classification run completes When results are persisted Then each region record stores modelVersion (name and hash), policyConfigVersion, and dataSourceVersions (moveInReportId, assetRegistrySnapshotId) Given the same inputs and versions are supplied When an audit replay is requested Then the system reproduces identical labels, confidences, causeClassification, and rationale notes or returns the persisted snapshot with matching version metadata Given a dispute evidence packet is generated When the export is created Then it includes per-region outputs and the associated modelVersion, policyConfigVersion, and dataSourceVersions
Configurable Suggestion Thresholds and Behavior
Given a property-level configuration is available When a manager sets suggestionThreshold to a value within [0.00, 1.00] Then the value is saved with userId and timestamp and applied to subsequent classifications for that property Given both a global default and a property-specific suggestionThreshold exist When classifications run for that property Then the property-specific value overrides the global default
Data Enrichment via Unit Records, Move-In Reports, and Asset Registry
Given a classification request for unit U and tenancy T When context is retrieved Then the system attaches tenancyLength (months) for T, assetAgeYears from the asset registry, and priorConditionStatus from the move-in report to each region decision Given multiple assets could match the region When enrichment occurs Then the system matches by exact asset tag or serial; if ambiguous, enrichmentStatus is set to Ambiguous and the ambiguous asset IDs are listed Given enrichment fails or context data are unavailable When causeClassification is computed Then the result is Indeterminate and a dataEnrichmentWarning is recorded
Dispute-ready Evidence Packet Generation
"As a landlord, I want a dispute-ready packet that clearly documents evidence and reasoning so that I can communicate charges transparently and stand up to challenges."
Description

Generates a branded, shareable evidence packet (PDF/ZIP) that includes side-by-side images, highlight overlays, labels, confidence scores, timestamps, GPS when available, unit/lease details, chain-of-custody log, and applied policy rules. Supports page numbers, watermarking, cryptographic hash for integrity, and size-optimized media to keep downloads efficient. Allows selective redaction (faces, docs on counters) and inclusion/exclusion of items, while preserving an internal unredacted original for audit. Provides multi-language support and accessibility-compliant text. Exposes one-click sharing via expiring links to the tenant portal and email, and stores the packet with the lease closeout workflow in FixFlow.

Acceptance Criteria
Packet Generation: Required Contents and Branding
Given a completed move-out inspection with AI photo diffs and unit/lease metadata When a property manager clicks "Generate Evidence Packet" Then a branded PDF is produced including for each item: side-by-side move-in vs. move-out images, highlight overlays, damage labels, and confidence scores And the packet includes timestamps (capture and upload), GPS coordinates when available, unit and lease identifiers, chain-of-custody log, and list of applied policy rules And every page displays page numbers (Page X of Y) and a semi-transparent watermark with the property brand And a ZIP variant is also available containing the PDF plus original media and a JSON manifest And the file(s) are assigned and display a SHA-256 hash and creation timestamp And total packet size is optimized to <= 25 MB by downscaling images to a maximum of 2048px on the longest edge without reducing text readability And generation completes within 60 seconds for packets with up to 50 photo pairs on a standard dataset
Selective Redaction With Audit-Preserved Original
Given faces or sensitive documents are detected or manually tagged for redaction When the manager enables redaction and selects items to include/exclude Then the shareable packet masks selected regions with a visible 'REDACTED' overlay and excludes chosen items from public distribution And the unredacted original packet and original media are preserved internally and linked to the redacted version And the audit log captures who performed redactions, what areas/items were redacted or excluded, timestamps, and reasons And only users with Role = Admin or Compliance can view or export the unredacted packet And cryptographic hashes for both redacted and unredacted variants are stored and differ as expected
Expiring Link Sharing to Tenant Portal and Email
Given a generated packet exists When the manager clicks "Share" and selects tenant email and portal Then an expiring link is created with default expiry 7 days (configurable 1–30 days) And the link requires tenant authentication via the portal or a one-time email code And access attempts, downloads, and expirations are logged with IP, user, and timestamp And the link is immediately revoked when the manager selects "Revoke Access" And downloads verify file integrity against the stored SHA-256 hash And the share is delivered within 60 seconds and is retried up to 3 times on transient failures
Multilingual and Accessibility-Compliant Packet
Given the manager selects a target language or the tenant has a preferred language on file When the packet is generated Then all system-generated text, labels, and policy notes render in the selected language with English fallback per string And the PDF complies with PDF/UA and includes tagged structure, proper reading order, searchable text, embedded fonts, alt text for images, and minimum 4.5:1 contrast for text And any ZIP includes an accessible HTML summary (WCAG 2.1 AA) mirroring packet content And screen readers correctly announce headings, lists, tables, and image alt text in a test run
Lease Closeout Storage, Versioning, and Immutability
Given a packet is generated When the lease closeout record is viewed Then the packet (current redacted variant) is attached and visible with version number and status And prior versions (including unredacted) are immutable and accessible per permissions And the packet is retrievable via API GET /leases/{id}/evidence-packets with ETag = SHA-256 hash And the audit trail records generator, time, version, and share events And the record is backed up nightly with 35-day retention
Policy Rules and Charge Line-Items Transparency
Given policy rules are configured for damage charges When the packet is generated Then each suggested charge lists: item, quantity/units, rate, calculation formula, subtotal, tax if applicable, and rule reference ID And confidence scores below a configurable threshold (default 0.6) are flagged for review and excluded from auto-suggested totals And the packet shows a summary total and separates landlord-policy vs. statute-required items And rounding is to 2 decimals using bankers' rounding And each line links back to the corresponding photo pair and overlay region
Suggested Chargeback Line Items & Rate Cards
"As a property manager, I want the system to suggest chargeback line items from detected damages so that I can finalize end-of-lease billing quickly and consistently."
Description

Transforms classified damages into suggested, editable chargeback line items mapped to configurable rate cards (labor, materials, trip fees) and jurisdiction-aware policies (depreciation schedules, caps, fair wear-and-tear). Auto-calculates subtotals, taxes, and deposit offsets, and attaches annotated photos to each line. Routes for internal approval and then pushes approved charges to FixFlow invoicing and generates repair work orders for vendors with relevant imagery. Maintains a full audit trail of edits, overrides, and approvals, and supports templates per property owner/portfolio to standardize outcomes while remaining fair and transparent.

Acceptance Criteria
Damage Mapping, Template Application, and Rate Card Selection
Given a property belongs to portfolio "Alpha" with default rate card RC-Alpha and a portfolio template enabled And the system receives classified damages with trades and severities When suggestions are generated Then each damage is mapped to the RC-Alpha trade and labor tier per trade And portfolio template default descriptions, unit measures, and standard quantities are applied to the line items And unmapped damages are flagged "Mapping required" with a prompt to select a rate card/trade before proceeding And switching the rate card at the property or line-item level recalculates unit prices immediately and records the change in the audit trail
Jurisdiction-Aware Depreciation, Caps, and Wear-and-Tear
Given the property jurisdiction policy defines taxability, straight-line depreciation schedules by category, tenant-charge caps, and fair wear-and-tear thresholds And an item "Carpet replacement" with age 4 years on a 5-year schedule and material cost $1,000 When the line items are calculated Then the chargeable material amount for carpet equals $200 (remaining useful life 1/5 of $1,000) and is displayed as a depreciation adjustment And if a policy cap is lower than the calculated amount, the tenant charge is limited to the cap and the cap reason is shown And if wear-and-tear rules exempt the item (e.g., paint beyond policy age), the tenant charge is set to $0 with an explanatory note
Auto-Calculation of Subtotals, Taxes, and Deposit Offsets
Given a line item with labor 2 hours at $90/hour, materials $120 (post-depreciation), trip fee $45, and jurisdiction tax 8% applied to labor and materials only And a tenant security deposit balance of $300 with $50 on hold When totals are computed Then the subtotal equals $345.00, tax equals $24.00, and grand total equals $369.00 And the deposit offset applied equals $250.00, leaving a net tenant balance of $119.00 and remaining deposit of $0.00 And all totals update within 1 second of any edit to quantity, price, fees, or tax settings
Line Item Photo Evidence Attachment and Annotations
Given each suggested line item originates from detected damage with before/after imagery When the line item is created Then at least one annotated image pair is attached to the line item with highlighted difference regions and callouts And removing all photos blocks submission with the error "Photo evidence required" And the exported packet (PDF/ZIP) contains the images and annotations per line item
Editable Line Items with Full Audit Trail and Overrides
Given a preparer edits quantity, unit price, taxability, or applies a policy override with a reason When the changes are saved Then the audit trail captures user ID, timestamp, field changed, old value, new value, and reason And overridden fields display an "Override" badge with a tooltip explaining the policy impact And reverting a field restores the previous value and appends a new audit entry
Internal Approval Routing and Controls
Given roles Preparer and Approver and a packet in Draft status When the Preparer submits for approval Then the packet status changes to Pending Approval, the Approver is notified, and line items become read-only except comments And when the Approver approves, status changes to Approved and a timestamped approval record is added And when the Approver rejects, at least one rejection reason is required and the packet returns to Draft with comments visible to the Preparer
Push to Invoicing and Vendor Work Order Generation
Given an Approved packet When charges are pushed to FixFlow invoicing Then an invoice is created with line items, taxes, and totals exactly matching the approved packet, and an external invoice ID is stored in the audit trail And for each trade represented, a vendor work order is generated with the relevant line items and attached annotated photos And if no default vendor is configured for a trade, the user is prompted to select one before completion
Tenant-facing Transparency & Dispute Flow
"As a tenant, I want a clear, interactive explanation of alleged damages with a way to contest them so that the process feels fair and transparent."
Description

Provides tenants with a portal view of side-by-side images, highlights, labels, and plain-language explanations of how decisions were made, including confidence notes and applicable policy rules. Enables tenants to acknowledge, comment, or contest specific items, upload counter-evidence, and track resolution status against clear timelines. Supports secure, expiring share links, notifications (email/SMS/in-app), multi-language text, and accessibility standards. Summarizes accepted vs. disputed line items for mediation or escalation and records outcomes in the lease closeout and accounting flows within FixFlow.

Acceptance Criteria
Tenant Views Side-by-Side Evidence and Explanations
Given a tenant has an active move-out review case and a valid authenticated session or share link When the tenant opens the case in the portal Then for each chargeable line item the move-in and move-out photos are displayed side by side with synchronized zoom/pan, highlight overlays, and labels And a plain-language explanation, cited policy rule identifiers/titles, and an AI confidence value (percentage plus descriptor) are visible for the item And above-the-fold content renders within 2 seconds on a 5 Mbps connection and the full image set within 5 seconds or shows loading placeholders until ready And each photo shows timestamp and room/area tags
Item-Level Acknowledge, Comment, or Contest
Given a line item is displayed in the tenant portal When the tenant selects Acknowledge and confirms Then the item is marked Acknowledged with a timestamp and user ID and included in accepted totals When the tenant selects Comment and submits up to 1000 characters Then the comment is saved with timestamp and visible in the item thread to both parties When the tenant selects Contest, chooses a reason from a configurable list, and submits an optional comment Then the item is marked Contested with a timestamp and reason captured And all actions (acknowledge, comment, contest) are recorded in an immutable audit log and reflected in the case summary
Upload and Manage Counter-Evidence
Given a tenant is contesting a specific line item When the tenant uploads files Then the system accepts JPEG, PNG, HEIC, MP4, and PDF files up to 10 files total, each ≤ 25 MB and ≤ 100 MB combined, and scans for malware And upload progress is shown, previews are generated for images/video, and files can be removed before submission And files are associated to the specific line item and stored with a web-optimized rendition while preserving originals And disallowed types or oversize files are blocked with clear error messages without losing other entered data
Status Tracking, Timelines, and Notifications
Given a case contains one or more items When the tenant views the case Then each item displays a status badge (Pending Review, Under Review, Resolved—Accepted, Resolved—Rejected, Escalated) and a target response date per configured SLA And the case timeline shows key events (submitted, updated, mediation requested, final decision) with timestamps When an item status or target date changes Then the tenant receives a notification via email and SMS and in-app (if logged in) within 60 seconds containing a summary and deep link And notification delivery outcomes (sent, failed, retried) are logged per channel with up to 3 retry attempts on transient failures
Secure Expiring Share Links
Given a landlord generates a share link for a tenant case When the link is created Then it contains a signed, scoped token limited to that case and expires per configuration (default 7 days; configurable 1–30 days) When an expired or revoked link is accessed Then access is denied with a clear message and an option to request a new link And all link accesses (success/denied), IP, user agent, and timestamps are logged, with rate limiting enforced at 10 attempts per minute
Localization and Accessibility Compliance
Given a tenant language preference is set or detected When the tenant views the portal and receives notifications Then UI text, explanations, and system messages render in the selected language from the supported set and a language switcher is available; untranslated strings do not appear as raw keys And the portal meets WCAG 2.1 AA: full keyboard navigation, visible focus indicators, text contrast ≥ 4.5:1, images have alt text, controls have accessible names/roles, and dynamic updates announce via ARIA live regions And all form inputs have labels, errors are programmatically associated, and any time limits can be extended by the tenant
Summary and System Recording of Outcomes
Given a case includes acknowledged and contested items When the tenant submits responses or the review window closes Then the system generates a summary showing counts and dollar totals of accepted vs. disputed items, item statuses, and links to evidence And a downloadable, shareable PDF packet is produced for mediation containing side-by-side images, highlights, explanations, tenant comments, decisions, and timestamps And upon final resolution, outcomes are recorded in the lease closeout workflow and corresponding accounting entries for charges/credits are created or updated with audit log references
Privacy, Security, and Retention Controls
"As a portfolio owner, I want strong privacy and retention controls on photo evidence so that we meet legal obligations and protect tenant and owner data."
Description

Implements role-based access controls and least-privilege defaults for viewing, annotating, and sharing photos and packets. Automatically detects and blurs faces, license plates, and sensitive documents before sharing externally, with reversible redaction controls for internal auditors. Captures upload consent/attestations, applies configurable retention rules per jurisdiction and owner, supports legal holds, and honors deletion requests. Encrypts data in transit and at rest, logs access and changes for audits, and aligns with SOC 2-style operational controls to protect all parties during evidence handling.

Acceptance Criteria
Least-Privilege Role Access to Evidence
- Given a new user is created, When they log in, Then they cannot view, annotate, download, or share any photos or packets until explicitly granted permissions. - Given a user has 'Evidence.View' for Property A only, When they attempt to access photos from Property B, Then the system returns 403 and logs the attempt with userId, propertyId, timestamp, and IP. - Given a user lacks 'Share.External', When they attempt to create an external link for a packet, Then the Share action is disabled and a permission error is shown and logged. - Given a manager grants 'Annotate' permission to a user, When the user annotates a photo, Then the action succeeds and the log records who, what, and when. - Rule: Default role templates implement least-privilege (no 'ViewOriginals' or 'Share.External' by default) and permissions are property-scoped.
Automatic PII Redaction on External Share
- Given a packet contains photos with faces or license plates, When a user generates an external share link, Then faces and license plates are automatically blurred at ≥20×20 px with ≥0.90 detection confidence, and no unredacted versions are accessible via the link. - Given the detector yields <0.90 confidence on any suspected PII region, When generating the share, Then the UI flags the photo for review and blocks sharing until the user confirms manual masks are applied. - Given a photo contains a visible document (e.g., ID, lease page), When sharing externally, Then detected document regions are blurred and the packet footer includes the notice "Personal data redacted". - Rule: All external exports (PDF/ZIP/URL) must include only redacted assets and a redaction manifest listing image IDs, masked region counts, and detector confidence ranges.
Reversible Redaction for Internal Audit
- Given a user with 'Auditor.ViewOriginals', When viewing a redacted photo internally, Then they can toggle "Show Original" for session-only viewing and the image is watermarked "Internal Use Only—Not for Distribution". - Given an auditor toggles "Show Original", Then the system logs userId, required reason text, timestamp, photoId, and duration of exposure. - Given an auditor attempts to download an original, Then the download is blocked unless they also have 'Auditor.DownloadOriginals' and provide an approval code issued within the last 15 minutes. - Rule: Redaction toggles do not alter the stored redacted asset used for any external sharing or export.
Upload Consent and Attestation
- Given a landlord or tenant uploads photos to Photo Compare AI, When they submit, Then they must affirm an attestation that they have rights/consent to upload and share for maintenance and move-out evaluation; submission is blocked until checked. - Then the system records attestation text version, timestamp (UTC), userId, IP address, propertyId, and jurisdiction, and binds these to the uploaded asset IDs. - Given an auditor opens a packet, When they export evidence, Then the consent records are included in an appendix (summary plus per-asset references). - Rule: The attestation checkbox defaults to unchecked and links to the privacy policy are displayed adjacent to the control.
Retention Rules and Legal Holds
- Given an owner with jurisdiction X has a retention policy of 24 months for evidence, When any photo/annotation/packet exceeds 24 months age, Then it is purged automatically within 24 hours unless on legal hold or marked as an active dispute. - Given a legal hold is placed on a packet, When the nightly purge job runs, Then held items are skipped and the hold reason, placer, and timestamp are logged. - Given a retention policy is changed, When saved, Then the policy is versioned, the change is logged, and the admin must choose whether to grandfather existing items or re-evaluate immediately; the chosen mode is enforced. - Rule: Every retention-driven purge generates a deletion manifest with item IDs and hashes, stored in immutable audit storage.
Deletion Requests (Right to Erasure)
- Given a verified data subject requests deletion, When the request is approved, Then their personal data across photos, annotations (e.g., names), and packets is queued within 24 hours and hard-deleted from active storage and search indices within 30 days, unless a legal hold applies. - Then thumbnails, redacted derivatives, and cached previews are also removed; backups are purged in the next scheduled rotation (≤35 days); all actions are logged. - Given deletion is blocked by a legal hold, When processing the request, Then the requester is notified with a non-sensitive hold explanation and the request remains pending until release. - Rule: Security/audit logs are retained but pseudonymized; a signed deletion certificate is generated and made available to the requester/owner.
Encryption & Audit-Ready Logging
- Rule: All data in transit uses TLS 1.2+ (TLS 1.3 preferred) with HSTS enabled and weak ciphers disabled; external share links are HTTPS-only, signed, and expire after 7 days by default (configurable). - Rule: All evidence data at rest is encrypted with AES-256; keys are managed by cloud KMS with rotation at least every 90 days; key access is restricted to the service and is fully logged. - Given any create/read/update/delete of photos, annotations, redactions, shares, or retention/hold changes, When it occurs, Then an append-only, tamper-evident log entry is written capturing userId, action, objectId, timestamp (UTC), IP, and before/after hashes. - Given an admin requests an audit export, When generated, Then logs for a specified date range and property export as signed JSON/CSV within 2 minutes and verify against a chain-of-custody hash. - Given an anomalous access pattern (e.g., >50 original-view toggles by one user in 10 minutes), When detected, Then a security alert is sent to designated admins within 5 minutes.

Parts Auto‑Cart

Rolls all punch list items into a single parts cart across units, checks on-hand inventory, and recommends bulk orders to hit quantity price breaks. Reserves stock for scheduled jobs and flags lead-time risks so make-ready never stalls waiting on supplies.

Requirements

Unified Punch List Aggregation
"As a property manager, I want to roll all pending parts needs across my units into one cart so that I can purchase everything in one go and avoid missed items and extra trips."
Description

Consolidate material line items from open maintenance tickets, make-ready checklists, and scheduled jobs across all units into a single normalized parts cart. Normalize SKUs and units of measure, deduplicate items, and roll up quantities while preserving line-level traceability back to the originating work order and unit. Allow filters by property, target date window, and job status, plus include/exclude toggles per line. Auto-refresh the cart as new work orders are created or updated, and maintain an audit trail of changes. Expose a summary view (by item and by job) and provide exportable reports for stakeholders.

Acceptance Criteria
Aggregate Open Work Sources Into Single Parts Cart
Given there are open maintenance tickets, active make-ready checklists, and scheduled jobs across multiple units When the parts cart is loaded Then all material line items from those sources appear in a single consolidated cart without omission Given a source work order is closed When the cart refreshes Then items from that work order are removed unless referenced by another still-eligible source Given there are no eligible sources When the cart is loaded Then the cart shows zero items and an empty-state message
Normalization of SKUs and Units of Measure
Given source lines reference equivalent items with different vendor SKUs and UoM When the cart is built Then they are normalized to a single internal SKU and base UoM using configured mappings Given quantities are in mixed UoM (e.g., pack vs each; ft vs m) When the cart is built Then quantities are converted to the base UoM using defined conversion rates with ≤1% rounding variance Given a source line lacks a mapping to an internal SKU or UoM When the cart is built Then the line is flagged Unmapped and excluded from roll-up totals until mapping is resolved
Deduplication with Quantity Roll-up and Traceability
Given multiple normalized lines for the same internal SKU exist across sources When the cart is built Then a single item row is shown with the summed quantity across those sources Given I expand a normalized item row When viewing details Then each contributing source line shows work order/checklist ID, property, unit, status, required date, requested quantity, and any UoM conversion applied Given I click a contributing source link When the link is activated Then I am navigated to the originating work item in a new tab
Filter by Property, Date Window, and Job Status
Given I select one or more properties When filters are applied Then only items from units within those properties are included in the cart and totals Given I set a target date window When filters are applied Then only items with required dates within that window are included Given I choose one or more job statuses (e.g., Open, Scheduled, Pending Approval) When filters are applied Then only items from sources with matching statuses are included Given I clear all filters When filters are reset Then the cart returns to showing all eligible sources
Include/Exclude Toggles and Line-Level Overrides
Given a normalized item row is displayed When I toggle Exclude Then the item's quantity is removed from totals and exports while traceability remains available in details Given I exclude an item When I return later or reload the page Then the exclusion state persists for my user and is timestamped in the audit trail Given I expand an item and deselect a specific contributing source line When I save Then the rolled-up quantity updates accordingly and the source line shows as Excluded in its detail
Auto-Refresh and Audit Trail of Cart Changes
Given a new work order with material lines is created or an existing one is updated When the event occurs Then the cart updates within 60 seconds without page reload and displays a refreshed timestamp Given a user changes include/exclude state or modifies filters When the change is saved Then an audit entry records user, timestamp, action, affected SKU/source, old value, and new value Given I open the audit log When I apply filters (user, action type, date range, SKU) Then the log results reflect those filters
Summary Views and Exportable Reports
Given I switch to the By Item view When the view loads Then one row per normalized SKU is shown with description, base UoM, total included quantity, properties count, jobs count, and include/exclude indicator Given I switch to the By Job view When the view loads Then one row per work order/checklist job is shown with property, unit, job status, required date, items count, and total included quantity Given I export the cart When I choose CSV or XLSX Then a file is generated within 10 seconds containing included items with traceability fields and totals that match on-screen values within 0.1%
Inventory Availability & Reservation Engine
"As a maintenance coordinator, I want the system to reserve in‑stock items for scheduled jobs so that techs have what they need on the day of service."
Description

Check real-time on-hand inventory across all stock locations (warehouse, technician trucks, on-site closets) with configurable safety stock buffers. Allocate and reserve inventory against scheduled jobs, supporting partial allocation, soft reservations at cart creation, and hard reservations upon approval/PO creation. Prevent double-booking with concurrency-safe holds, and automatically deallocate on cancellation or reschedule. Surface availability badges (in stock, partially allocated, out of stock) and ETAs within the cart, and sync reservations to technician mobile apps and work orders.

Acceptance Criteria
Real-Time Multi-Location Inventory Check with Safety Stock
Given multiple stock locations (warehouse, truck, on-site closet) with per-SKU safety stock configured When a user opens the Parts Auto-Cart or refreshes inventory Then the system displays available-to-allocate = sum(max(on-hand − safety stock, 0)) across locations and shows a last-updated timestamp per location Given a SKU with on-hand below its safety stock at any location When availability is calculated Then that location contributes 0 to available-to-allocate and is flagged as “below safety stock” Given inventory receipts, picks, or returns are posted When the cart is refreshed Then quantities reflect changes within 2 seconds and no location shows negative on-hand or available-to-allocate Given no record exists for a SKU at a location When availability is requested Then that location contributes 0 and is omitted from the breakdown
Soft Reservation at Cart Creation with Partial Allocation
Given a user creates/saves a parts cart for one or more jobs When soft reservation is applied Then the system soft-reserves up to the available-to-allocate quantity per SKU and records any remainder as backordered Given soft reservations exist When other users view availability Then soft-held quantities are deducted from available-to-allocate and labeled as “soft hold” Given a configurable soft-hold timeout (default 30 minutes; min 5, max 120) When the timeout elapses without approval/PO Then all soft reservations on the cart auto-release and availability is recalculated Given new stock arrives before approval When inventory is updated Then soft reservations automatically increase up to the requested quantity without exceeding available-to-allocate
Hard Reservation on Approval/PO Creation
Given a cart is approved or a PO is created for the associated job(s) When approval/PO creation is confirmed Then all related soft reservations convert to hard reservations in a single atomic transaction Given hard reservations exist for SKUs When other carts attempt to reserve those SKUs Then hard-reserved quantities are excluded from available-to-allocate until picked or explicitly released Given only partial stock is available at approval time When conversion occurs Then the available portion is hard-reserved and the remainder is marked backordered with an ETA sourced from supplier lead times or expected receipts Given a job has a scheduled date and assigned technician When hard reservations are created Then the reservation is linked to the work order and technician assignment for downstream pick/consumption
Concurrency-Safe Double-Booking Prevention
Given two or more users/processes try to reserve the same SKU simultaneously When reservation requests arrive within 100 ms of each other Then only one reservation succeeds per unit of available stock and others receive adjusted remaining quantity or a clear insufficiency message; no oversell occurs Given idempotency keys are passed with cart actions When the same reservation request is retried within 60 seconds Then the original result is returned without duplicating reservations Given high contention on a SKU When 50 concurrent reservation attempts are executed in test Then no negative inventory is recorded and average reservation response time remains under 300 ms Given transactional conflicts occur When a deadlock is detected Then the system retries up to 3 times and records an audit entry with a correlation ID
Automatic Deallocation on Cancel or Reschedule with Sync
Given a job has soft or hard reservations When the job is cancelled Then all associated reservations are immediately released, availability is updated, and an audit entry is logged Given a job is rescheduled When a new date is saved Then reservations are retained but need-by/pick dates are updated; if beyond the configurable reservation window (default 14 days), soft holds auto-expire and hard holds prompt for extend or release Given reservation changes occur (release, extend, reassign) When changes are saved Then technician mobile apps and the work order reflect updated reserved quantities within 30 seconds and a push notification is sent Given parts are picked and the work order is completed When consumption is recorded Then reservations decrement on-hand and any unused reserved quantity is returned to available-to-allocate
Availability Badges and ETAs in Parts Auto-Cart
Given items are present in the cart When availability is evaluated Then each line shows a badge: In Stock (100% allocated), Partially Allocated (1–99%), or Out of Stock (0%), with safety stock buffers respected Given a line is partially allocated or out of stock When viewing the cart Then an ETA is displayed based on supplier lead times, existing POs, or next transfer date, rendered in the user’s local timezone Given inventory or PO status changes When updates occur Then badges and ETAs refresh within 2 seconds and display consistently on web and technician mobile apps Given the same SKU is demanded across multiple units in a punch-list rollup When presented in the cart Then demand is aggregated, a single badge is shown per SKU, and a breakdown by job/unit is available in the line detail
Bulk Order Optimization with Price Breaks
"As a small landlord, I want recommendations that hit vendor price breaks so that I lower total spend without unnecessary surplus."
Description

Ingest vendor catalogs with tiered pricing, MOQs, and shipping thresholds to compute recommended order quantities that achieve price breaks while minimizing overstock. Present side-by-side comparisons of current vs. optimized cost, including savings and impact on inventory levels. Respect vendor preferences and split recommendations across vendors as needed. Allow user overrides and lock-ins per line, then apply chosen recommendations back to the cart in one step. Store rationale and calculations with the order for auditability.

Acceptance Criteria
Vendor Catalog Ingestion with Tiered Pricing, MOQs, and Shipping Thresholds
Given a vendor catalog containing SKUs with tiered pricing breaks, minimum order quantities (MOQs), and shipping thresholds When the catalog is uploaded or synced via API Then the system parses and stores each SKU's price tiers (break qty and unit price), MOQ, and vendor-level shipping thresholds without data loss And invalid or conflicting rows are rejected with a per-row error report showing row number and reason And imported tiers are normalized to non-overlapping, ascending quantity breaks per SKU/vendor And the import summary displays total rows processed, imported, and rejected
Optimized Quantity Computation Respecting Price Breaks and Overstock Tolerance
Given a consolidated parts cart with per-SKU net requirement calculated as max(0, demand − on-hand − incoming reserved) And vendor price tiers and MOQs are available And an overstock tolerance is configured (e.g., % or days-of-supply) When the user runs Bulk Order Optimization Then for each SKU, the system recommends a quantity per vendor that meets MOQ and net requirement And selects the tier that minimizes total landed cost within the overstock tolerance And if no feasible quantity within tolerance satisfies MOQ, the system recommends the lowest-cost feasible quantity and flags the exception reason on that line
Shipping Threshold and Free-Shipping Optimization
Given vendor-specific shipping thresholds and fees are known And a draft set of per-vendor recommendations exists When the optimizer evaluates shipping economics Then it may increase one or more line quantities for a vendor to reach a shipping threshold only if the net landed cost decreases or remains equal and the change stays within the overstock tolerance And the adjusted lines, threshold reached, and marginal savings are shown per vendor
Vendor Preferences and Multi-Vendor Split Recommendations
Given each SKU may have an allowed vendor list and hard preferred vendor When the user runs Bulk Order Optimization Then no recommendation uses a vendor outside the allowed list And if multiple allowed vendors exist, the system may split recommendations across vendors to minimize per-vendor landed cost (including shipping and price breaks) computed independently per vendor And each vendor's recommendation forms a separable purchase order group
Side-by-Side Cost Comparison with Savings and Inventory Impact
Given a current cart state and an optimized recommendation set When results are displayed Then each line shows side-by-side current vs optimized: quantity, unit price tier, extended cost, shipping allocation, and landed cost And totals show overall landed cost, absolute savings, and percent savings And each line shows projected post-order on-hand and days-of-supply impact based on the recommended quantity
User Overrides and Lock-Ins with One-Step Apply to Cart
Given the optimization results are visible When a user edits a line to override quantity and/or vendor and locks the line Then re-optimization preserves locked lines and re-optimizes only unlocked lines And when the user clicks Apply Recommendations, all selected recommendations are written back to the cart in a single transaction with version conflict detection And if a conflict is detected (cart changed since optimization), the user is prompted to refresh and re-apply
Audit Trail of Optimization Rationale and Calculations
Given a recommendation set is applied or an order is placed When the system finalizes the cart or purchase orders Then it stores, per line, the selected vendor, recommended quantity, chosen price tier, MOQ considered, shipping threshold decisions, overstock tolerance used, and net requirement inputs (demand, on-hand, incoming reserved) And it stores alternative tiers/quantities considered with reasons for rejection (e.g., exceeds tolerance, higher landed cost) And an immutable audit report can be retrieved later showing these details unchanged
Lead-Time Risk Detection & Alerts
"As a property manager, I want items with long lead times flagged against my schedules so that I can switch vendors or adjust plans before make‑ready slips."
Description

Track standard and dynamic vendor lead times for each SKU and evaluate them against required-by dates derived from job schedules and make-ready timelines. Flag items where expected arrival exceeds available slack, and surface risk levels with clear icons and explanations. Provide actionable options: propose alternate vendors, in-store pickup, approved substitutes, or expediting. Trigger notifications to coordinators and offer one-click schedule adjustments when unavoidable delays are detected. Maintain an "at risk" list and include lead-time considerations in bulk optimization.

Acceptance Criteria
Lead-Time Risk Flag on Cart Items vs Required-By Date
Given a cart contains SKU(s) with known vendor lead times and a job with a required-by date And the system computes an expected arrival date per SKU (including dynamic lead-time adjustments and shipping days) And available slack is computed from the job schedule and make-ready timeline When expected arrival exceeds available slack relative to the required-by date Then the SKU is flagged with a lead-time risk badge and level (Low/Medium/High) using distinct icons And the explanation includes expected arrival date, required-by date, and days late/early And the risk badge is shown consistently on Auto-Cart, Job Detail, and Make-Ready Timeline views And risk is recalculated within seconds when lead time or schedule data changes
Actionable Alternatives for At-Risk Items
Given an item in the cart is marked at risk due to lead time And at least one fulfillment alternative exists (alternate vendor, in-store pickup, approved substitute SKU, or expediting option) When the user opens Lead-Time Options Then the system lists each option with ETA, price impact, and whether the option resolves the risk And only approved substitutes are shown for substitute SKU options And selecting an option applies it in one click, updates the cart, reserves stock if applicable, and recalculates risk And the UI records the chosen mitigation on the order and job activity log
Coordinator Notifications for Lead-Time Risks
Given a coordinator is assigned to the property/job When a cart item transitions into an at-risk state (or risk level increases) Then a notification is sent to the coordinator with SKU, job, required-by date, expected arrival, risk level, and suggested actions And duplicate notifications for the same item and level are suppressed for 60 minutes And a resolution notification is sent when the item is no longer at risk And all notifications are timestamped and visible in the job activity feed
One-Click Schedule Adjustment for Unavoidable Delays
Given an at-risk item has no available alternative that can meet the required-by date When the user selects Adjust Schedule Then the system proposes the earliest new schedule that satisfies the item’s expected arrival and obeys task dependencies And applying the proposal updates the job schedule in one click and recalculates required-by dates and risk And a changelog entry is created with before/after dates and the reason (Lead-time delay)
At-Risk List Maintenance and Resolution
Given one or more cart items across jobs are at risk When the user opens the At Risk list Then the list shows each item with property/unit, job, SKU, risk level, expected arrival, required-by date, days late, and age of risk And items drop off the list automatically when risk is cleared (ETA meets required-by or job rescheduled/canceled) And the list updates within 30 seconds of underlying data changes And users can filter by risk level, property, vendor, and required-by window
Bulk Optimization Considers Lead-Time and Price Breaks
Given multiple SKUs with varying required-by dates are eligible for bulk ordering And vendors offer quantity price breaks with different lead times When the system generates a bulk order proposal Then no SKU with a required-by date earlier than a vendor’s ETA is assigned to that vendor in the proposal And the system may split quantities across vendors to both meet deadlines for urgent items and achieve price breaks for non-urgent quantities And the proposal displays per-SKU vendor, ETA, price, and whether lead-time risk remains And any items that cannot be fulfilled in time are explicitly flagged with risk and suggested mitigations
Substitution & Equivalent SKU Mapping
"As a technician, I want approved substitutes suggested when a part isn’t available so that I can finish the job without compromising quality or standards."
Description

Maintain a catalog of approved equivalent parts and substitution rules with compatibility constraints by brand, model, and property standards. When an item is unavailable or risky, suggest compliant substitutes with live pricing, availability, and lead times across vendors. Support manager approval workflows for substitutions, log decisions for compliance, and remember preferences for future recommendations. Integrate substitution logic into cart aggregation, inventory reservation, and bulk optimization to ensure end-to-end consistency.

Acceptance Criteria
Auto-suggest compliant substitutes when primary SKU unavailable or risky
Given a cart line item with SKU S and a scheduled job date D And live vendor data shows S is out of stock or has ETA arrival after D minus a configurable buffer (default 2 business days) When the user views the cart or opens substitution suggestions for S Then the system displays up to 5 approved equivalent SKUs that satisfy compatibility and property standards And each suggestion shows vendor, live unit price, available quantity, earliest arrival/lead time, bulk price tiers, and compliance badges And suggestions are ranked first by ability to meet D (no lead-time risk), then by lowest landed cost (including shipping and bulk pricing) And selecting a substitute immediately updates the line’s price, ETA, and risk status
Block non-compliant or incompatible substitutes
Given the substitution rules catalog defines equivalence groups and constraints by brand, model, dimensions/specs, and property standards When generating suggestions or validating a user-selected substitute Then the system excludes any SKU that violates a constraint (e.g., brand disallowed at property, model incompatibility, spec mismatch) And if a user attempts to add a blocked substitute, the system prevents it and shows a reason code and linked rule And an override option is available only to users with the required role, which triggers the approval workflow
Manager approval workflow for substitutions
Given property P requires approval for substitutions (policy-level, property-level, or per-SKU rule) When a user selects a substitute that requires approval Then the system creates an approval request containing original SKU, chosen substitute, reason (OOS/risk/cost), price delta, ETA delta, vendor, job ID, and property And notifies designated approvers via in-app and email with approve/decline actions And records timestamps for request, decision, and acting user IDs And on approval, the substitution is applied, stock is reserved (if available), and the cart/PO updates And on rejection, the cart reverts to the original item and remains flagged until an alternative is chosen And approvals exceeding the SLA (default 24 hours) surface as overdue in the approvals queue
Substitution decision audit logging
Given any substitution suggestion viewed, selection made, approval/decline, or override When the action completes Then an immutable audit record is written with action type, original SKU, substitute SKU, property, job ID, user, timestamp, vendor, price delta, ETA/lead-time delta, reason code, and approval reference (if applicable) And audit records are queryable by date range, property, SKU, user, and action type And audit records are exportable to CSV
Learn and apply manager substitution preferences
Given a manager at property P approves or selects substitute X for original Y When a future cart at property P contains Y Then the system ranks X first among suggestions if it meets current availability and compliance constraints And displays a "Preferred at P" indicator with last-approved date And provides controls to override or clear the preference And authorized users can promote a preference from property-level to portfolio-level And preferences auto-downgrade if X becomes non-compliant or shows persistent lead-time risk beyond the buffer for 3 consecutive checks
Cart aggregation and bulk optimization reflect substitutions
Given multiple line items across units include originals that have chosen substitutes When the cart aggregates quantities Then quantities consolidate by final chosen SKU and vendor (not the original SKU) And bulk pricing tiers recalculate using consolidated quantities and vendor breaks And the optimizer selects vendor(s) that minimize total landed cost while meeting lead-time constraints And the system avoids splitting an equivalence set across vendors unless the split reduces total landed cost by at least 3% or eliminates a lead-time risk And a summary shows before/after cost, savings from bulk, and lead-time risk status
Inventory reservation and lead-time propagation for substitutes
Given on-hand inventory exists for a chosen substitute When the job is scheduled or the cart is checked out Then available units are reserved to the job and property, reducing on-hand counts And any shortfall creates/updates a purchase order for the remaining quantity with live ETA And the job timeline and risk indicators update based on reserved quantities and vendor ETA And items that cannot meet D minus buffer remain flagged "At Risk" with escalation notes and suggested alternates
Approval Workflow & PO Generation
"As a property manager, I want a streamlined approval and PO process so that orders are controlled and traceable without slowing down jobs."
Description

Provide role-based approval thresholds and routing rules based on total spend, vendor, and property. Show approvers an itemized diff from the last version, line-level allocations to work orders/units, and bulk-optimization justifications. Support partial approvals and comments. Upon approval, split the cart into vendor-specific purchase orders with correct taxes, ship-to locations, and required-by dates, and dispatch via email/EDI/CSV. Update inventory reservations and order statuses, and synchronize PO status back to work orders and technician schedules.

Acceptance Criteria
Spend-Threshold Routing by Role, Vendor, and Property
Given routing rules that define approval thresholds per role, vendor category, and property, When a cart totaling $7,500 for Property A with Vendor XYZ (category: Standard) is submitted, Then the system routes sequentially to all roles whose cumulative thresholds must be met until a role with threshold >= $7,500 approves, and no roles below their thresholds are skipped. Given vendor override rules that escalate Non-Preferred vendors, When a cart totaling $2,000 uses a Non-Preferred vendor for Property B, Then the route includes the escalated approver level regardless of spend under base threshold. Given property-specific thresholds, When the same $7,500 cart is reassigned from Property A to Property C with a higher threshold, Then the route recalculates and reduces required approver levels accordingly. Given approval routing rules are versioned, When rules are updated after submission but before decision, Then the originally-evaluated route remains stable for that submission and the system logs the rule version used.
Approver Sees Itemized Diff on Resubmission
Given a previously rejected cart v1 and a revised cart v2, When an approver opens v2, Then the UI displays an itemized diff showing line additions, removals, quantity changes, unit price changes, and extended price deltas with totals (v1 vs v2) and net change. Given lines with changes, When the approver views the list, Then changed fields are highlighted and sortable by change magnitude and change type. Given attachments or justifications changed, When viewing v2, Then the approver sees a diff summary of justification text and attachment list changes. Given multiple revisions, When opening vN, Then the approver can toggle diffs between vN vs vN-1 and vN vs original submission.
Line-Level Allocation Visibility and Validation
Given a cart with multiple lines linked to work orders and units, When an approver opens the cart, Then each line displays allocations (quantity or cost) to work orders/units with percentages summing to 100% or quantities equaling the line total. Given incomplete allocations, When the requester attempts submission, Then the system blocks submission and indicates lines with missing or inconsistent allocations. Given allocations across multiple properties, When reviewing, Then the system flags cross-property allocations if not permitted and prevents submission until corrected. Given a change to allocations post-approval of some lines, When resubmitting, Then the diff highlights allocation changes per line.
Partial Approval with Comments and Reroute
Given a cart with 10 lines, When an approver approves 7, rejects 2 with comments, and requests changes on 1, Then the system records line-level decisions and comments, recalculates the remaining approval total for pending lines, and advances only approved lines to the next stage. Given partial approval, When the requester addresses requested changes and resubmits the modified lines, Then the routing engine re-evaluates thresholds based on the modified subtotal and routes only the changed/rejected lines as a new version while preserving approvals for previously approved lines. Given comments entered, When the requester views the cart, Then comments are visible at both header and line levels with timestamps and author identity. Given a line is rejected at any level, When resubmitted without addressing mandatory comment-tagged issues, Then submission is blocked with specific validation referencing the unresolved comments.
PO Split, Tax, Ship-To, Required-By, and Dispatch
Given a fully approved cart with lines for three vendors, When converting to purchase orders, Then the system creates one PO per vendor with correct lines, quantities, unit prices, and per-line required-by dates inherited from scheduled jobs or lead-time calculations. Given property and vendor tax rules, When POs are generated, Then taxes are calculated per jurisdiction and vendor tax profile and included on the PO totals with a breakdown by tax component. Given properties with multiple ship-to locations, When generating POs, Then each PO uses the ship-to tied to the line’s property/site and includes contact and delivery instructions. Given vendor dispatch preferences, When dispatching, Then POs are sent via Email (with PDF/CSV attachments), EDI (with 200-level acknowledgment captured), or CSV export (download logged) and the system records a dispatch status per PO with timestamp and outcome. Given a dispatch failure, When email bounces or EDI fails, Then the PO status is set to Dispatch Failed, an alert is created, and retry options are available with error details.
Inventory Reservation and Order Status Updates
Given lines fulfilled from on-hand stock, When the cart is approved, Then the system reserves the required quantities against inventory locations and decreases available-to-promise while keeping on-hand unchanged until pick. Given lines exceeding on-hand, When generating POs, Then only the shortfall is placed on the vendor PO and the reservation reflects split fulfillment (reserved + on-order) per line. Given a line is rejected after reservation, When the decision is recorded, Then the system releases any associated reservations and updates available-to-promise immediately with an audit log entry. Given PO dispatch completion, When dispatch succeeds, Then the order status per line updates to Ordered and links to the created PO number(s).
PO Status Sync to Work Orders and Technician Schedules with Lead-Time Risk Flags
Given PO status transitions (Ordered, Backordered, Shipped, Partially Received, Received), When a PO line status changes, Then the linked work order parts status updates within 1 minute and displays the current fulfillment state and ETA. Given required-by dates earlier than vendor ETA, When the variance exceeds the configurable threshold (e.g., >2 days), Then the system flags a lead-time risk on the work order and shows a warning badge on affected technician schedules. Given a lead-time risk is flagged, When a scheduler opens the affected job, Then reschedule suggestions are displayed based on soonest ETA across all required parts and technician availability. Given all parts are received, When inventory receipt is posted, Then the risk flag clears, the work order is updated to Parts Ready, and schedule holds are removed automatically.

Trade Sequencer

Builds the optimal task order—cleaners, painters, repairs, inspection—with dependencies and buffers, then auto-books vendors using your calendars. If anything slips, it reflows the sequence, updates everyone, and protects the move-in date without coordinator scramble.

Requirements

Dependency Graph Builder
"As a property manager, I want to define and visualize task dependencies for a unit turn so that work happens in the right order and I can spot bottlenecks early."
Description

Enable creation of a task dependency graph for turn tasks (e.g., cleaning, painting, repairs, inspection) with hard/soft predecessors, minimum gaps, and task durations. Validate for cycles, detect parallelizable work, and compute critical path and float. Pull tasks from FixFlow work orders and unit turnover templates; store graph per unit or project. Expose APIs/UI to add constraints (e.g., “paint after patch cure 24h”), required access, materials readiness, and permit gates. Output a baseline schedule with earliest start/finish times and resource placeholders for vendor assignment.

Acceptance Criteria
Graph Build from Template and Work Orders
Given a unit turnover template "Standard Turn v3" with tasks (Clean, Paint, Repair, Inspect) and existing FixFlow work orders for the same unit When the user clicks "Build Dependency Graph" for the unit Then the system ingests tasks from both the template and the unit's open work orders into a single task set And creates nodes with duration, identifiers, and task metadata for each task And creates edges for all defined hard/soft predecessors and minimum gaps And persists the graph under the unit's turnover project with a unique graph ID And returns a build summary including counts of tasks, edges, and any missing metadata warnings
Cycle Detection and Prevention on Save
Given a user adds dependencies that create a cycle (e.g., Clean -> Paint -> Repair -> Clean) via the UI or API When the user attempts to save the graph Then the save is blocked And an error is displayed listing the cycle path(s) by task IDs/names And the API returns HTTP 422 with a machine-readable list of nodes/edges involved And no partial graph changes are persisted
Parallelizable Work Identification
Given a valid acyclic graph with tasks that have no direct or indirect dependencies between them When the graph analysis runs Then the system identifies sets of tasks that can run in parallel And exposes these sets via API endpoint /graphs/{id}/parallelizable as arrays of task IDs And highlights parallel groups in the UI for the selected date range And excludes tasks gated by access/materials/permit conditions from parallel sets until gates are satisfied
Critical Path and Float Calculation
Given a valid graph with task durations and minimum gaps defined When the user requests the baseline schedule Then the system computes earliest_start and earliest_finish for all tasks And identifies the critical path where total float equals 0 And computes total float for non-critical tasks in hours And exposes critical path and float per task via API and UI And results match a reference CPM calculation within ±1 minute for all tasks
Constraint Capture: Min Gaps, Hard/Soft Predecessors, and Gates
Given the user inputs constraints including: hard predecessor (Paint after Repair), soft predecessor (Inspection after Cleaning), minimum gap (Patch cure 24h before Paint), required access, materials readiness, and permit approval When the constraints are saved Then each constraint is stored with type, value, and associated tasks or gate condition And hard constraints are enforced in scheduling And soft constraints are preferred; if relaxed, the schedule flags the exception on affected tasks And gates prevent earliest_start until their conditions are marked satisfied And validation ensures minimum gaps are respected as time offsets between finish and start
API and UI for Constraint Management
Given a project graph exists When a client adds, updates, or deletes a constraint via POST/PATCH/DELETE to /graphs/{id}/constraints Then the request is validated and either applied or rejected with clear errors And successful changes are versioned with user/service ID, timestamp, and diff And the graph's updated_at changes and the UI reflects updates within 5 seconds without page reload And invalid requests return HTTP 400/422 with error codes and field-level messages
Baseline Schedule Output with Resource Placeholders
Given a validated dependency graph When the baseline schedule is generated Then each task includes earliest_start, earliest_finish, and resource_placeholder fields And resource_placeholder contains trade type and required skill tags for later vendor assignment And the baseline respects all hard/soft predecessors, minimum gaps, and gate conditions And the baseline is stored as version 1 and retrievable via /graphs/{id}/baseline And regenerating creates version N+1 with a diff of changes since the prior baseline
Calendar & Vendor Auto-Booking
"As a coordinator, I want the system to automatically book qualified vendors into available slots so that I spend less time calling around and vendors get clear, confirmed work orders."
Description

Integrate two-way with vendor calendars (Google, Microsoft 365/Outlook, ICS) and FixFlow availability to auto-book time slots that satisfy task windows, travel buffers, working hours, and service-area constraints. Place provisional holds, request confirmations, and auto-promote to confirmed on acceptance. Respect vendor preferences, capacity limits, and cost tiers; support backup vendors and waitlists. Sync confirmed bookings back to all calendars with job details, access instructions, and attachments. Provide failure fallbacks (manual selection, reschedule suggestions).

Acceptance Criteria
Two-Way Calendar Sync (Google, Microsoft 365/Outlook, ICS)
Given a vendor has connected Google or Microsoft 365/Outlook, When FixFlow creates, updates, or cancels a booking, Then the corresponding calendar event is created/updated/canceled within 60 seconds and mirrors title, start/end, location, and description fields. Given a vendor edits or deletes a FixFlow-authored event in Google/Microsoft 365 that contains a FixFlow eventId, When the change is saved, Then FixFlow ingests the change within 2 minutes and updates booking status/availability accordingly. Given a vendor provides an inbound ICS feed URL, When FixFlow polls the feed (<=10-minute interval), Then imported busy times block auto-booking during overlapping periods. Given FixFlow publishes an outbound ICS feed for a vendor, When a booking is confirmed, Then the event appears in the vendor’s calendar via the feed within 10 minutes with up-to-date timing. Given a sync error (4xx/5xx/timeout), When 3 retries with exponential backoff fail, Then FixFlow flags the booking, logs the error, and notifies the coordinator with remediation options.
Auto-Booking Within Constraints (Task Windows, Travel Buffers, Working Hours, Service-Area)
Given a task has an allowed window [start,end], When auto-booking selects a slot, Then the slot’s start and end occur within the window. Given a vendor has a travel buffer of N minutes and sequential jobs, When scheduling adjacent bookings, Then FixFlow ensures at least N minutes between end and next start including estimated drive time. Given vendor working hours and blackout dates, When proposing times, Then FixFlow excludes times outside working hours and any blackout dates. Given a vendor service area radius R from a base address, When evaluating eligibility, Then FixFlow only considers jobs whose road-network distance is <= R. Given no eligible slot exists, When auto-booking completes search, Then FixFlow returns at least 3 alternative suggestions ordered by earliest completion and offers manual vendor/time selection.
Provisional Holds, Confirmations, and Auto-Promotion
Given a candidate slot is identified, When FixFlow places a hold, Then the event is created as tentative with holdExpiresAt per vendor SLA and vendor receives a confirmation request. Given a tentative hold, When the vendor accepts via calendar RSVP or FixFlow link, Then the booking status changes to confirmed within 60 seconds and the calendar event is updated to confirmed. Given a tentative hold, When the vendor declines or holdExpiresAt elapses without response, Then the hold is released, status is set to declined/expired, and next-step selection logic is invoked. Given a booking becomes confirmed, When notifications are dispatched, Then tenant and coordinator receive confirmation with time, vendor, and access instructions.
Vendor Selection Honors Preferences, Capacity, and Cost Tiers
Given vendor preferences (e.g., mornings only, no Fridays), When evaluating candidates, Then FixFlow excludes any time slots violating stated preferences. Given a vendor capacity C concurrent jobs, When scheduling, Then FixFlow never exceeds C overlapping tentative or confirmed bookings for that vendor. Given cost tiers and SLA constraints across eligible vendors, When selecting a primary, Then FixFlow minimizes total cost while meeting the task window; ties are broken by on-time rate and proximity. Given a vendor is marked do-not-use for a property or task type, When building the candidate list, Then that vendor is not included.
Backup Vendors and Waitlist Escalation
Given a ranked list of vendors [primary, backup1, backup2…], When the primary declines or times out, Then FixFlow places a provisional hold with backup1 within 5 minutes and sends updated notifications. Given all ranked vendors decline or time out, When escalation completes, Then the job enters waitlist state and the coordinator is prompted to add vendors or choose manual scheduling. Given a waitlisted vendor gains availability before task cutoff, When capacity opens, Then FixFlow places a hold and cancels conflicting holds to avoid double-bookings. Given multiple backups accept overlapping holds, When conflicting acceptances arrive, Then the earliest acceptance by timestamp wins and all others are auto-canceled with clear messaging.
Booking Detail Sync to Calendars With Access and Attachments
Given a booking is confirmed, When syncing to calendars, Then the event includes job title, property address/unit, arrival window, expected duration, point of contact, and access instructions (masked from tenants) plus a secure link to attachments/photos. Given attachments are shared, When using Google/Microsoft 365, Then files are delivered as links with time-limited permission tokens accessible to the assigned vendor. Given a booking time or instruction changes, When the update is saved, Then all linked calendar events update within 2 minutes and affected parties receive change notifications. Given a booking is canceled by the coordinator, When cancellation is confirmed, Then calendar events are canceled and include a cancellation reason.
Reflow and Rebook on Upstream Task Slippage
Given a dependency chain (e.g., cleaning → painting → inspection) and a fixed move-in date, When an upstream task is delayed by D hours, Then FixFlow recalculates the sequence within 5 minutes and attempts to preserve the move-in date by rebooking downstream tasks within constraints. Given a reflow causes a conflict with a confirmed booking, When alternatives exist, Then FixFlow proposes at least 2 viable slots per affected vendor and issues re-confirmation requests automatically. Given rebooking fails after two escalation attempts, When remaining options cannot satisfy constraints, Then the coordinator is alerted with a prioritized action list and one-click manual reschedule options.
Dynamic Reflow Engine
"As a property manager, I want the schedule to automatically reflow when something slips so that the move-in date is protected without me rebuilding the plan."
Description

Continuously monitor events (vendor declines, lateness, early finishes, weather, parts delays) and re-optimize the schedule to protect the target move-in date and SLAs. Recompute sequencing within constraints, preserve locked tasks, and minimize objective functions (lateness penalties, cost, vendor changes). Generate alternatives with tradeoffs (e.g., swap vendor, overtime, compress buffers) and require approval based on policy. Version schedules, track deltas, and maintain an audit of what changed and why.

Acceptance Criteria
Vendor Decline Triggers Reflow
Given a confirmed task assigned to a vendor in an active sequence And the move-in date and SLA windows are defined When the assigned vendor declines the task via API or portal Then the engine recomputes the schedule within 30 seconds And preserves all locked tasks unchanged And respects task dependencies, buffers, vendor calendars, and tenant notice policies And selects the feasible plan with the lowest objective score And auto-books the replacement vendor per policy if no approval is required And updates integrated calendars and sends notifications to affected parties within 60 seconds And versions the schedule (v+1) with a delta and audit entry citing "vendor_decline" And flags "move-in at risk" if no feasible plan meets the target date
Locked Tasks Preservation During Re-Optimization
Given one or more tasks are marked Locked When any reflow is triggered by an event Then the start time, end time, assigned vendor, and buffers of locked tasks remain identical to the prior version And no candidate plan that alters locked tasks is considered feasible And if locks cause infeasibility with the target move-in date, the engine marks risk with a constraint-violation report and leaves locked tasks unchanged
Early Finish Pull-Forward Optimization
Given a task reports actual completion earlier than planned When the completion timestamp is recorded Then the engine recomputes within 30 seconds And attempts to advance downstream tasks within vendor and tenant notice policies and availability constraints And never reduces any buffer below the configured minimum without approval And updates bookings and notifications for any tasks moved earlier And produces a new version and delta summarizing time savings realized
Weather Delay Risk Mitigation
Given policy.weather.risk_threshold is configured And an outdoor task is scheduled within the forecasted impact window When the weather feed indicates risk >= threshold for the task time window Then the engine generates at least two feasible alternatives (e.g., reschedule within window, swap vendor, insert buffer) And selects the best plan automatically unless any alternative triggers an approval rule And updates calendars, notifications, version, delta, and audit with reason "weather_risk" And flags move-in risk if no feasible alternative preserves the target date
Alternative Generation and Policy-Based Approval
Given approval rules exist for overtime, buffer compression below minimum, vendor swap, or after-hours work When a reflow requires any action governed by these rules Then the engine outputs at least two ranked alternatives with objective scores and explicit tradeoffs And requests approval from the configured role before committing governed actions And blocks auto-booking until approval is granted or a non-governed alternative is selected And records approver, decision, timestamp, and rationale in the audit And applies the approved or policy-compliant alternative and versions the schedule
Objective Function Minimization and Explainability
Given weight configuration for lateness penalties, cost, and vendor changes When the engine evaluates feasible plans during reflow Then the selected plan’s objective score is less than or equal to all other evaluated plans within epsilon 1e-6 And the engine returns the objective score breakdown (lateness, cost, changes) for the selected plan and top 3 alternatives And the audit contains the weight configuration used and the primary drivers of selection
Versioning, Delta Tracking, and Auditability
Given any schedule change is applied by the engine When a reflow results in modifications to tasks Then a new immutable version identifier (v+1) is created And the delta includes for each changed task: old/new start, old/new end, old/new assignee, buffer change, and cause And the audit record includes triggering event type, constraints considered, objective scores, approvals, and notifications sent And users can retrieve any prior version and diff it against current within 2 seconds for schedules up to 200 tasks
Buffer Management & Move-in Protection
"As a leasing lead, I want built-in buffers and guardrails around the move-in date so that quality and timelines aren’t compromised when schedules tighten."
Description

Allow configuration of default and per-task buffers (drying/cure times, QA windows) and a protection zone before tenant move-in. Auto-insert buffers based on trade templates; visualize buffer consumption and warn on erosion. When buffers are threatened, propose mitigations (scope split, vendor swap, overtime) and escalate per policy. Block changes that violate the move-in protection window unless override permissions are met and impacts are acknowledged.

Acceptance Criteria
Configure Default and Per-Task Buffers
Given organization-level default buffers are configured per trade with allowed range 0–168 hours and a property timezone is set When a new turnover sequence is generated from trade templates Then each task is pre-populated with its trade’s default buffer and unit of measure in the property timezone Given a coordinator edits a task’s buffer value within the allowed range When the sequence is saved and later reflowed due to schedule changes Then the per-task override persists and supersedes the template default Given a coordinator enters an invalid buffer (e.g., negative, over 168h, non-numeric) When saving Then validation prevents save and displays a specific error indicating the allowed range and format
Auto-Insert Buffers from Trade Templates
Given a trade template defines task dependencies and buffer requirements between tasks (e.g., Paint -> 24h cure -> Clean) When a coordinator builds a sequence using that template Then the system automatically inserts buffer tasks/durations between dependent tasks according to the template Given a dependency lacks an explicit buffer in the template When the sequence is generated Then the system applies the organization default buffer for that trade/dependency Given buffers are auto-inserted When vendors are auto-booked Then vendor calendar holds respect the buffers so downstream tasks do not start until buffers complete
Move-in Protection Zone Enforcement
Given a property has a configured move-in protection window (e.g., 48 hours before tenant move-in) When a user attempts to schedule, move, or extend any task or buffer that would occupy time within the protection window Then the action is blocked with a clear message unless the user has the "Override Move-In Protection" permission Given a user with the required permission attempts the same action When proceeding Then the user must enter an impact acknowledgment note and select a reason code, and the system records an audit entry with user, timestamp, change details, and reason Given an override would shift the move-in date When confirmed Then the system requires explicit confirmation and updates the move-in date, notifying stakeholders per notification policy
Buffer Consumption Visualization and Erosion Alerts
Given a sequence with buffers in place When viewing the schedule Then remaining buffer for each dependency is displayed as time/delta and color-coded by status (healthy, at-risk, depleted) Given a delay reduces a buffer below 50% of its original value When the buffer crosses the threshold Then an "at-risk" alert is generated and sent to the assigned coordinator via in-app and email Given a delay fully consumes a buffer (<= 0h remaining) When this occurs Then a "buffer depleted" alert is sent to the coordinator and escalations are triggered per policy, and affected tasks are flagged in the UI
Mitigation Proposals When Buffers Are Threatened
Given a buffer is at-risk or depleted and downstream tasks risk entering the protection window When the coordinator opens mitigation suggestions Then the system proposes options such as scope split, vendor swap, and overtime, each with estimated cost, schedule impact, confidence, and required approvals Given the coordinator selects a mitigation option When applying it in preview mode Then the system simulates the schedule, shows the new finish times vs. move-in, and highlights any remaining risks Given a mitigation requires approval per policy (e.g., overtime > 10% budget) When the coordinator submits Then the system routes the request to the appropriate approver and blocks changes until approved or rejected, logging all decisions
Automatic Reflow on Task Slips with Notifications
Given a scheduled task or buffer is delayed or its duration increases When the delay is recorded (status update, vendor push, or calendar conflict) Then the system reflows all dependent tasks while respecting buffers, constraints, business hours, and the move-in protection window Given reflow causes conflicts with vendor availability When conflicts are detected Then the system proposes alternate slots or vendor swaps and indicates the earliest feasible schedule that preserves the move-in date Given reflow alters booked times When changes are finalized Then updated calendar invites are sent to vendors and confirmations requested; coordinators receive a summary of changes within 5 minutes
Policy-Based Escalation and Acknowledgments
Given an escalation policy defines triggers (e.g., buffer depleted, move-in risk score > 0, mitigation awaiting approval > 2 hours) When a trigger condition is met Then the system escalates to the configured roles via in-app, email, and SMS with a concise summary and required next actions Given the first-level escalation is not acknowledged within the SLA (e.g., 2 hours) When the SLA expires Then the system auto-escalates to the next level and logs the missed acknowledgment Given any escalation or override occurs When reviewing the sequence audit trail Then all events show actor, timestamp, before/after schedule deltas, notes/reasons, and notification recipients
Stakeholder Notifications & Acknowledgements
"As a vendor and tenant, I want timely, clear notifications with confirmations so that I always know what’s expected and when."
Description

Send targeted, event-driven updates to vendors, tenants, and owners across SMS, email, and push when bookings are created, changed, or canceled. Include updated ICS invites, prep checklists, access codes, and photo requirements. Require acknowledgements where needed (e.g., vendor accepts slot, tenant confirms access window) and track read receipts. Provide throttling, quiet hours, localization, and templating. Log all communications in the job timeline for compliance and dispute resolution.

Acceptance Criteria
Vendor Booking Created Notification & Acknowledgement
Given a vendor task is auto-booked by Trade Sequencer, When the booking is created, Then FixFlow sends notifications via the vendor’s configured channels (SMS, email, push) within 30 seconds and logs delivery attempts per channel. Then the message includes: ICS invite (RFC 5545, correct timezone, start/end/buffers, consistent UID), prep checklist, access codes, and photo requirements when present. Then the vendor can Accept or Decline from any channel via a secure link or in-app; acceptance state is recorded; a reminder is sent at 2 hours if no response (respecting quiet hours) and an escalation at 12 hours. Then read receipts are captured where supported; link-click is used as read proxy for SMS; all events are recorded in the job timeline with timestamps and actor.
Reschedule Reflow Update with ICS Regeneration
Given an existing booking with an issued ICS UID, When the sequence reflows and the time/date changes, Then an update is sent to all affected stakeholders within 60 seconds highlighting changed fields. Then the ICS is reissued with the same UID and incremented SEQUENCE so Google, Outlook, and Apple Calendar update the existing event. Then prior reminders are canceled and new reminders scheduled; the vendor is required to re-acknowledge the new time; a reminder is sent at 2 hours and escalation at 12 hours (respecting quiet hours). Then the job timeline shows the before/after times, update notifications sent, delivery/read statuses, and the new acknowledgment outcome.
Cancellation Notification with ICS Cancel
Given a booking exists, When it is canceled by coordinator or automation, Then cancellation notifications are sent to vendor, tenant, and owner within 30 seconds including cancellation reason and next steps. Then an ICS CANCEL with the original UID is delivered so calendar entries are removed across major clients. Then acknowledgements are required from vendor and tenant if access was previously confirmed; non-response is reminded at 2 hours and escalated at 12 hours (respecting quiet hours). Then all communications and acknowledgements are logged in the job timeline with timestamps and actors.
Tenant Access Window Confirmation & Quiet Hours
Given a job requires tenant access, When the access window request is issued, Then the message is delivered outside tenant quiet hours; if triggered during quiet hours, it queues and sends at the next permitted time in the tenant’s timezone. Then the message contains proposed windows, an option to suggest alternatives, building access codes (masked if configured), and photo requirements if applicable. Then the tenant can confirm via secure link or in-app; success state updates the job; if no response within 24 hours, reminders are sent per throttling rules and the coordinator is notified. Then read receipts and confirmation timestamps are tracked and logged in the job timeline.
Owner Approval Request with Templates and Localization
Given a job meets approval criteria (cost threshold or schedule impact), When approval is requested, Then the owner receives a templated, localized message (locale from profile with English fallback) with job summary, estimate, proposed schedule, and photos. Then currency, dates, times, and units are formatted per locale; links and UI render in the owner’s language. Then the owner can Approve or Decline via secure link or in-app; the signed decision and timestamp are stored; non-response at 24 hours triggers reminders and escalation to protect the move-in date. Then all messages, reads, and decisions are logged in the job timeline and available via API.
Throttling and Burst Control
Given multiple schedule changes occur for a job or property, When 10 or more notifications would be generated within 60 minutes per stakeholder, Then the system batches them into a digest every 15 minutes containing the latest state and diffs, except acknowledgement-required messages which are sent immediately. Then per-stakeholder max messages per hour and per day are enforced; overflow items are deferred and logged with next send time. Then queued messages respect quiet hours; upon send, timestamps reflect actual send time and content reflects the current state. Then acceptance tests verify no stakeholder receives more than configured limits while acknowledgement-required items are not delayed.
Communication Logging, Read Receipts, and Compliance
Given any notification is sent, When delivery attempts, reads, and acknowledgements occur, Then the job timeline records for each event: channel, locale, template version, content hash, attachments (ICS, checklist, access codes, photo requirements), delivery status, read status, acknowledgement status, timestamps, and actor. Then records are immutable, uniquely identified, filterable in UI, and exportable via API for compliance and dispute resolution; PII fields are masked per policy with secure reveal controls. Then audit reports can be generated for a job showing complete communication history with outcomes within 5 seconds of request.
Manual Overrides & Conflict Resolution
"As an operations manager, I want to override the sequencer when business realities demand it while clearly seeing the impacts and retaining an audit trail."
Description

Allow authorized users to lock tasks, pin vendors, or override constraints with visibility into downstream impacts (critical path shifts, buffer loss, added cost). Present conflict summaries and require reason codes before committing. Support one-click rollback to prior schedule versions. Enforce role-based permissions and maintain a tamper-evident audit trail for all overrides and conflict resolutions.

Acceptance Criteria
Lock Task Respected in Reflow
Given an authorized user is viewing a scheduled turnover with sequenced tasks When the user locks Task X and a reflow is triggered by a slip or manual change Then Task X start and end times remain unchanged until explicitly unlocked And the sequencer reflows other tasks around Task X without violating hard dependencies And a lock indicator is displayed on Task X in all schedule views And an impact panel shows critical path changes, buffer changes (in hours), and affected tasks within 5 seconds And affected vendors/tenants receive updated notifications if their tasks moved
Vendor Pin Override with Availability Check
Given an authorized user is editing Task T When the user pins Vendor V and selects a time window Then the system checks Vendor V's connected calendar for conflicts in real time And if a conflict exists, a conflict summary with alternate time options is displayed and the user cannot commit without choosing a resolution And if no conflict exists, the assignment is committed, Vendor V is invited, and Task T shows a pinned vendor indicator And cost delta relative to the baseline vendor (if any) is calculated and displayed before commit And a reason code selection is required to complete the override
Constraint Override with Impact Preview and Reason Code
Given dependencies and buffers are defined for the schedule When the user proposes to change or remove a dependency or buffer on Task T Then a preview panel displays projected critical path shift, buffer loss/gain (in hours), and estimated cost impact before commit And users without the "Override Constraints" permission are blocked with an error and no changes are saved And users with permission must select a reason code and optionally add a note before the "Commit" button is enabled And upon commit, a new schedule version is created and all impacted stakeholders are notified of changes
Conflict Summary and Guided Resolution
Given an override introduces double-bookings, dependency violations, or resource overallocations When the user opens the conflict summary Then conflicts are listed by type and affected task/vendor with counts and severity And one-click recommended fixes are offered (e.g., shift start, swap vendor, insert buffer) And the user can accept selected fixes and reflow in a single action And after commit, there are zero unresolved conflicts; if any remain, a "Move-In Risk" banner is displayed and commit is blocked
One-Click Rollback to Prior Schedule Version
Given at least one prior schedule version exists When an authorized user selects a prior version and clicks "Rollback" Then the current schedule is snapshotted, and the selected version becomes the active schedule within 5 seconds And all tasks, vendor assignments, dependencies, and buffers match the selected version And affected vendors/tenants receive update notifications for any changed times And a reason code is required, and an audit entry is created linking from the new version to the rolled-back version And if any task in the current version is in-progress (status started), rollback is blocked unless the user has "Force Rollback" permission
Role-Based Permissions for Overrides
Given RBAC is configured with roles and permissions When a user without appropriate permission attempts to lock/unlock a task, pin a vendor, override a constraint, resolve conflicts, or rollback Then the action controls are disabled or the attempt is blocked with an explanatory message And the blocked attempt is recorded in the audit trail with user, timestamp, action, and reason "Insufficient Permission" And users with permission can complete the action without escalation
Tamper-Evident Audit Trail of Overrides
Given any override, conflict resolution, or rollback is committed When the operation completes Then an immutable audit log entry is appended with timestamp (UTC), user ID, role, action type, affected objects, before/after values, reason code, version ID And entries are hash-chained to be tamper-evident and cannot be edited or deleted via the UI or API And the audit log is filterable by date range, user, action type, task, and exportable to CSV And a daily integrity check verifies the hash chain and raises an alert if a mismatch is detected

Deposit Deductor

Calculates compliant security-deposit deductions from approved damage items, auto-attaches photo proof, and generates jurisdiction-ready notices with itemization and timelines. Reduces disputes, standardizes decisions, and closes out turnovers cleanly.

Requirements

Jurisdiction Rules Engine
"As a property manager operating across multiple jurisdictions, I want deposit rules applied automatically by location so that every deduction and deadline is compliant without manual research."
Description

A configurable, versioned rules engine that applies location-specific security-deposit regulations (state/province/country and municipality) to each case. Encodes allowable deduction categories, depreciation methods, caps, wear-and-tear exclusions, mandatory notice language, and return timelines with effective dates and audit trails. Supports property-level overrides, rule precedence, and fallback defaults for unknown jurisdictions. Exposes a validation layer that blocks non-compliant deductions and assembles per-line compliance rationale used by calculations and notices. Integrates with FixFlow property profiles, itemization, deadline tracker, and notice generation.

Acceptance Criteria
Jurisdiction and Rule Version Selection by Address and Effective Date
Given a property profile with country/state/province/county/municipality codes, a lease end date, and a processing date P When the Jurisdiction Rules Engine is invoked for validation Then it selects the most specific matching jurisdiction rule set and version whose effective_start <= P < effective_end (or no end), and returns rule_set_id and version Given overlapping rule versions for the same jurisdiction When selecting the applicable version Then the engine chooses the latest published version whose effective dates include P Given multiple jurisdiction levels match (country, state/province, municipality) When resolving precedence Then municipal rules override state/province, which override country, which override platform default Then rule resolution completes within 300 ms at p95
Enforce Allowable Categories and Wear-and-Tear Exclusions
Given a line item category not in the jurisdiction's allowed deduction categories When validation runs Then the deduction is blocked with code RULE_CATEGORY_DISALLOWED, deductible_amount=0, and line_status="Blocked", and the statute_reference is included in the rationale Given an item meets the rule's wear-and-tear exclusion When validation runs Then deductible_amount=0 and rationale contains exclusion_id and text excerpt Given an allowed category and no other violations When validation runs Then line_status="Compliant"
Apply Depreciation Methods and Category/Deposit Caps
Given jurisdiction method=straight_line, useful_life=L years, item age=A years, original_cost=C, salvage=jurisdiction.salvage When calculating the allowable deduction Then allowable_depreciated_value = max(0, C - ((C - salvage) * min(A, L)/L)), and deduction <= category_cap and <= remaining_deposit_balance Given jurisdiction method="no_depreciation" (or equivalent) When calculating Then calculation follows jurisdiction directive (full cost or zero) and records method in rationale Then calculation response includes fields: method, inputs, intermediate_values, cap_applied, final_amount; results match expected within +/- $0.01 per rounding rule
Generate Mandatory Notice Language with Itemized Rationale
Given a jurisdiction with mandatory notice template version V and required clauses When generating the deposit deduction notice Then the notice includes template V without alteration, populated placeholders (dates, amounts, addresses), per-line compliance rationale snippets, photo evidence references, and the statutory return deadline Then the template_version_hash embedded in the notice equals the stored rule template hash
Compute Return Timelines and Sync to Deadline Tracker
Given a move-out date M and rule timeline T with an anchor defined by jurisdiction (e.g., M, receipt_of_forwarding_address, or lease_end) When the case is opened Then deadline_date = anchor + T (calendar or business days per rule), stored on the case and pushed to the Deadline Tracker with tags ["Deposit", jurisdiction_code] Given now >= (deadline_date - reminder_window from rule or 3 days default) When validation or notice generation runs Then raise warning RULE_DEADLINE_IMMINENT Given now > deadline_date When attempting to approve deductions Then block with RULE_DEADLINE_PASSED unless rule permits an exception; if permitted, require reason and record in audit trail
Property-Level Overrides, Precedence, and Audit Trail
Given property-level overrides exist for specific rule keys When resolving a rule Then the override value is used and recorded with source="property_override" and override_id; otherwise apply precedence municipality > state/province > country > platform_default When any rule value influences a calculation or validation outcome Then write an immutable audit entry capturing property_id, case_id, rule_scope, rule_key, rule_id, version, source, old_value, new_value, user/session_id, timestamp, correlation_id Given an override is updated with a future effective_start When validating cases with processing dates before that start Then the previous override version is used; cases on/after that start use the new version
Fallback Defaults for Unknown or Unmapped Jurisdictions
Given the jurisdiction cannot be resolved or has no published rules When validation runs Then apply platform_default rule set with fallback=true, block high-risk deductions defined by default rules, and create a task "Map Jurisdiction Rules" When generating a notice under fallback Then insert generic compliance text, display a "Jurisdiction Unknown" banner, and require explicit user acknowledgement before sending Then log all fallback events with reason, detection method, and suggested next steps
Itemization & Cost Calculation
"As a landlord, I want accurate, defensible per-line deduction amounts with clear rationale so that charges are fair, consistent, and easy to explain."
Description

Automates per-line deduction calculations by importing approved damage items from FixFlow, applying jurisdictional rules, depreciation schedules (age and expected lifespan), betterment, proration, tax, and fee logic. Distinguishes normal wear-and-tear from chargeable damage and calculates tenant-responsible amounts versus landlord costs. Provides editable line items, rationale notes, and templates for common issues to standardize decisions. Supports multi-currency, rounding rules, and snapshotting of calculation states for auditability and regeneration of notices.

Acceptance Criteria
Import Approved Damage Items with Metadata
Given a property has approved damage items in FixFlow with status "Approved for Deposit Deduction" and associated photos When the user opens Deposit Deductor for that property and selects an import date range Then only items approved within the selected date range are imported, preserving item IDs, categories, timestamps, quantities, unit costs, and photo links And Then unapproved or denied items are excluded and reported with counts And Then duplicate import attempts within the same session do not create duplicate line items And Then an import summary displays totals for imported, excluded, and errored items
Depreciation, Expected Lifespan, Betterment, and Proration
Given the jurisdiction ruleset defines expected lifespans and depreciation method for item categories And Given an item with replacement cost 1200, category Carpet, expected lifespan 10 years, age 7 years, and damage scope 25% When the tenant-responsible amount is calculated Then the base cost is prorated by scope to 300 And Then depreciation by remaining-life ratio (3/10) yields 90 And Then a 20% betterment flag reduces the tenant-responsible amount to 72 And Then if a vendor repair invoice exists for 60, the tenant-responsible amount is min(72, 60) = 60 And Then intermediate values and the formula sequence are displayed in the line item audit
Wear-and-Tear Exclusion and Chargeable Damage Classification
Given the input includes items tagged Normal Wear (e.g., minor paint scuffs, small nail holes) and items tagged Damage (e.g., broken window latch) When classification runs for the selected jurisdiction Then Normal Wear items are set to non-chargeable with Tenant Amount = 0 and Landlord Amount = calculated cost, with rationale citing wear-and-tear policy And Then Damage items remain chargeable and progress through calculation And Then overriding a classification requires a mandatory rationale note and records user, timestamp, and previous value in the audit trail
Jurisdictional Rules, Caps, and Timelines Enforcement
Given the property jurisdiction is determined from the unit address and mapped to a ruleset version When calculations run Then item/category prohibitions and caps from the ruleset are applied before taxes/fees And Then total tenant charges are capped at the remaining security deposit balance And Then a statutory deadline date for delivering the itemized notice is calculated from the move-out date per jurisdiction and stored on the calculation And Then if any required rule for an item is missing, notice generation is blocked and the missing rules are listed
Tax, Fees, Multi-Currency, and Rounding Rules
Given the property base currency is EUR and a vendor invoice for a line item is in USD with FX rate 1.10 on the calculation snapshot date When taxes, fees, and currency conversion are applied Then taxable components are taxed per the jurisdiction’s tax matrix and non-taxable components are excluded from tax And Then currency conversion converts USD components to EUR using the snapshot FX rate and displays both source and converted amounts And Then rounding follows the configured rule (e.g., round half up to 2 decimals) applied after taxes per line and on totals, with no more than ±0.01 discrepancy between the sum of lines and totals And Then any administrative fee configured as a flat 25 EUR or 10% of the tenant-responsible subtotal is calculated per jurisdictional allowance and included as its own line
Tenant vs Landlord Cost Allocation Summary
Given multiple line items are calculated When the summary is generated Then each line displays Tenant Amount and Landlord Amount, with subtotals and grand totals computed And Then the grand total Tenant Amount does not exceed the deposit balance; any excess is reassigned to Landlord Amount And Then the summary includes a category breakdown and a downloadable CSV/JSON that matches the on-screen values
Editable Line Items, Templates, Rationale Notes, Snapshots, and Notice Regeneration
Given a user opens the calculation When they edit allowed fields on a line item (description, quantity, unit cost, classification, taxability, age, expected lifespan, scope %, currency, FX rate) Then the system recomputes amounts and updates the audit trail with before/after values and user/timestamp And Then applying a predefined template (e.g., Carpet Stain, Repaint Wall) pre-fills fields, rationale text, lifespan, depreciation method, and taxability per jurisdiction And Then saving creates an immutable snapshot storing inputs, ruleset version, FX rate, and computed outputs with a snapshot ID And Then regenerating a notice from a given snapshot reproduces identical line items, amounts, and totals to the cent in the stored currency
Evidence Auto-Attach & Redaction
"As a property manager, I want photo and document evidence auto-attached and sanitized so that tenants see credible proof without exposing sensitive information."
Description

Automatically links photo and document evidence from inspections and maintenance intake to each itemized deduction line. Embeds timestamps, unit/room tags, and metadata in the generated packet while enabling optional PII redaction (face blurring, GPS/EXIF stripping) and document redaction. Supports additional uploads such as invoices and receipts, preserves chain-of-custody metadata, and enforces role-based access. Produces a consolidated evidence appendix (PDF/HTML) with thumbnails and deep links for tenant viewing.

Acceptance Criteria
Auto-Attach Evidence from Inspections and Intake
Given approved deduction line items exist and evidence from inspections/intake is tagged with unit and room, When a deduction line is created or updated, Then the system automatically links all evidence with matching unit and room tags captured within ±30 days of the line’s associated inspection/incident date to that line item. Given multiple matching evidence items, When auto-attach completes, Then items are ordered chronologically and the attachment count is displayed on the line item. Given a deduction line has no matching evidence, When auto-attach runs, Then the system displays "No evidence found" and provides a control to add uploads manually. Given a user manually unlinks an evidence item from a deduction line, When auto-attach runs subsequently, Then the unlinked item is not re-attached automatically.
Metadata Embedding in Evidence Packet
Given a deposit deduction packet is generated, When building each deduction line’s evidence section, Then the packet embeds for each evidence item: capture timestamp (UTC and local), unit number, room tag, evidence source (inspection/intake/upload), uploader role, file hash (SHA-256), original filename, and version ID. Given redaction is enabled for the packet, When embedding metadata, Then GPS coordinates, device serial numbers, and any PII fields are omitted from tenant-facing packets. Given a tenant-facing packet is generated, When rendering metadata, Then internal user IDs are not shown and roles are displayed as labels only (e.g., "Manager").
Photo PII Redaction (Faces and EXIF/GPS)
Given redaction is enabled at the packet or evidence-item level, When processing images for distribution, Then all detected human faces are blurred with a minimum 25-pixel radius at ≥95% detection confidence, and no unblurred faces are visible at 1x–3x zoom in tenant-facing copies. Given images are exported to tenant or shared via public links, When delivering copies, Then all EXIF metadata, including GPS coordinates, is stripped from those copies. Given an image has been redacted, When viewing in Manager or Owner role, Then the original unredacted file remains accessible; Tenant and Vendor roles see only the redacted copy.
Document Redaction for PDFs and Images
Given a PDF or image document (e.g., invoice, receipt) is selected for redaction, When a user applies rectangle redactions or term-based redaction (e.g., SSN, email), Then the distributed copy irreversibly removes the pixels and associated OCR text within the redacted regions and logs the redaction with user, timestamp, and reason. Given a redacted document is exported or viewed, When attempting to copy/paste or text-search, Then redacted content cannot be recovered or discovered. Given a document receives additional redactions, When saving, Then a new version with a new checksum is created; prior versions remain read-only and are restricted to Manager/Owner roles.
Additional Uploads and Deduplication
Given a manager uploads additional evidence (invoices, receipts, photos), When selecting files, Then the system accepts PDF/JPG/PNG/HEIC up to 25 MB per file and rejects unsupported types with a clear error message. Given duplicate files are submitted, When uploading, Then exact duplicates (matching SHA-256 checksum) are flagged and not stored twice; the existing file is referenced instead. Given uploads complete successfully, When associating files to a deduction line item, Then the files are linked to that line and are included automatically in the evidence appendix under that line’s section.
Chain-of-Custody and Audit Trail Preservation
Given any evidence item exists, When viewing its audit log, Then the log includes: createdAt (UTC), uploadedBy (role and masked ID), source (inspection/intake/upload), file hash (SHA-256), prior hash (if versioned), actions (attach/unlink/redact/export), actor, timestamp, and reason. Given a packet is generated for export, When completing the export, Then an audit summary per evidence item is included and a packet-level SHA-256 checksum is computed and displayed on the cover page. Given any edit or redaction is applied to evidence or its metadata, When saving, Then an immutable new version is created and appended to the audit trail; the previous version remains read-only according to role-based access.
Consolidated Evidence Appendix, Deep Links, and RBAC
Given a deduction packet is generated, When assembling the evidence appendix, Then both PDF and HTML versions are produced with a table of contents, itemized sections per deduction line, thumbnails (≥200 px on the shortest side) with captions, and deep links to view full-resolution copies in the portal. Given tenant access is required, When opening a deep link from the tenant-facing appendix, Then the link is tokenized, scoped to the specific tenant and case, expires after 30 days by default, and all accesses are logged; expired links return HTTP 410. Given role-based access controls are enforced, When accessing evidence, Then Manager/Owner roles can view originals; Vendor role can view only items tied to their assigned work orders; Tenant role can view only redacted copies; unauthorized requests return HTTP 403 and are logged. Given an appendix contains up to 100 evidence items, When generating the appendix, Then 95th-percentile generation time is ≤ 8 seconds for PDF, and HTML first contentful paint is ≤ 2 seconds on a 5 Mbps connection.
Compliant Notice Generation & Delivery
"As a property manager, I want compliant notices generated and delivered through approved channels so that I can meet legal obligations and close turnovers efficiently."
Description

Generates jurisdiction-ready, itemized security-deposit notices using dynamic templates that include required statutory language, deadlines, totals, refund amounts, and manager/tenant details. Outputs accessible HTML and signed PDF versions, maintains version history, and watermarks superseded notices. Supports multi-language content and branding. Delivers notices via email and integrates with postal services for certified mail, tracking delivery status and bounce events. Regenerates notices automatically upon calculation changes and logs all sends for audit.

Acceptance Criteria
Jurisdiction-Ready Itemized Notice Generation
Given a finalized deposit deduction calculation, a known jurisdiction, and move-out and notice dates When the manager generates a security-deposit notice Then the system uses the jurisdiction’s mapped template and required clauses And includes itemized deductions with descriptions, quantities, unit costs, line totals, and a grand total And shows original deposit amount, total deductions, refund amount (or amount due), and statutory deadline computed from jurisdiction rules And displays manager and tenant names, mailing addresses, unit identifier, lease dates, and notice date And validates presence of all mandatory fields; if any are missing, generation is blocked with explicit error messages And ensures monetary totals equal the sum of items within ±0.01 of currency precision and are formatted per locale And assigns a unique notice ID and embeds jurisdiction reference code
Accessible HTML and Digitally Signed PDF Outputs
Given a generated notice When outputs are created Then an HTML version is produced that meets WCAG 2.1 AA (semantic structure, labeled tables, language tag, contrast ≥ 4.5:1, accessible logo alt text) And a PDF is produced as a tagged, text-selectable, PDF/UA-compliant file with correct reading order And the PDF is digitally signed with the organization’s certificate; opening the file shows a valid signature status And HTML and PDF content (text and monetary values) are content-equivalent byte-for-byte after normalization And both files include the unique notice ID and version number
Versioning and Watermarking of Superseded Notices
Given a prior version of a notice exists When a new version is generated Then the system increments the version number and records an immutable version entry with timestamp, actor, and change reason And the immediately previous version is watermarked "Superseded" on all pages without obstructing legibility and remains accessible read-only And only the latest version is marked Current in the UI and is eligible for delivery actions And attempting to send a non-current version is blocked with guidance to send the latest version
Multi-Language Content and Property Branding
Given a property default language and optional tenant-preferred language When a notice is generated Then all statutory clauses, labels, and system text render in the selected language using the jurisdiction’s approved translations And if the selected language is unavailable, the system falls back to the property default language and alerts the manager before delivery And bilingual output is automatically added where the jurisdiction mandates it And property branding (logo, colors, legal name, address) appears per branding settings and remains legible in grayscale And dates, numbers, and currency format according to the output language locale
Email Delivery with Bounce and Status Tracking
Given a current notice version and at least one valid tenant email When the manager sends the notice via email Then the tenant receives an email with the HTML notice link and the signed PDF attached And the system records provider message ID, recipient(s), send timestamp, and delivery status updates (queued, delivered, bounced) with reasons And hard bounces and permanent failures are flagged and surfaced to the manager with a recommended postal fallback And all email send events are appended to the audit log with notice ID and version number And sending to multiple recipients is supported and tracked individually
Certified Mail Integration and Tracking
Given a current notice version and a validated postal address When the manager selects certified mail delivery Then the system creates a certified mail job with the postal vendor, generates a shipping label, and stores the tracking number And address validation errors block label creation with actionable messages And delivery events from the vendor (accepted, in transit, delivered, delivery attempt, return to sender) are ingested via webhook and time-stamped And the latest tracking status is visible to the manager and linked in the audit log And proof of mailing (receipt image or PDF) is stored and associated with the notice version
Automatic Regeneration on Calculation Changes with Audit Logging
Given a notice has been generated When deposit deductions, tenant/manager details, jurisdiction, or deadlines change Then the system automatically generates a new notice version within 30 seconds And marks prior versions as superseded with watermarking and preserves them read-only And prompts the manager to review and re-send; no automatic re-delivery occurs without explicit confirmation And all regenerations and all delivery attempts (channel, recipients, timestamps, statuses, version IDs) are logged immutably for audit
Deadline & Timeline Tracker
"As a small property manager, I want automatic deadline tracking and alerts so that I never miss refund windows and avoid penalties."
Description

Calculates and monitors statutory deadlines from key events (move-out date, possession return, forwarding address received), adjusts for weekends/holidays per jurisdiction, and surfaces countdowns and risk indicators. Automates reminders, escalation alerts, and task creation, with optional pauses where permitted while awaiting vendor invoices or estimates. Blocks sending after deadlines and proposes remediation steps. Provides a portfolio dashboard and exports for compliance reporting, with webhook and Slack/Email notifications.

Acceptance Criteria
Accurate Deadline Computation by Jurisdiction and Key Events
Given a property with a defined jurisdiction ruleset specifying anchor events and duration When move-out, possession return, and/or forwarding address dates are recorded or updated Then the system computes a statutory deadline using the jurisdiction ruleset and displays the anchor event used, the rule name/version, and whether days are business or calendar Given the computed deadline falls on a weekend or jurisdiction holiday When the rule requires business days Then the deadline is rolled to the next business day in the property’s timezone at 11:59 PM Given any anchor event date is changed When the system recalculates Then the new deadline is computed within 60 seconds, prior values are preserved in an audit log (before/after, user, timestamp), and scheduled notifications are rescheduled accordingly
Dynamic Countdown and Risk Indicators at Unit and Portfolio Levels
Given any record with a computed deadline When viewed in the unit and portfolio dashboards Then a countdown of days remaining is shown and color-coded: Green (>=10), Amber (5-9), Red (0-4), and Overdue (<0) labeled "Missed" Given multiple records across the portfolio When the portfolio dashboard is loaded Then aggregate counts are shown by severity and by jurisdiction, and rows are sorted ascending by days remaining by default Given the day changes or a relevant event is updated When the system refreshes data Then countdowns and severities update at least daily at 00:05 local time and immediately upon relevant changes
Automated Reminders and Escalation via Slack/Email
Given a record with deadline D and notifications enabled When scheduling reminders Then emails are sent to the assigned manager at D-14, D-7, D-3, and D-1 at 09:00 in the property timezone including property, unit, tenant, anchor event, ruleset, days remaining, severity, and an action link Given Slack is configured for the property When reminders are due Then Slack messages are posted to the configured channel at the same offsets with equivalent content Given the record is completed or the notice is sent before D When evaluating pending reminders Then all future reminders are canceled Given severity transitions to Red When evaluating escalations Then one-time escalation notifications are sent via email and Slack with high-priority markers Then all notifications are logged with timestamp, recipients, medium, and delivery status
Jurisdiction-Governed Pause Handling for Vendor Invoices/Estimates
Given jurisdiction PauseAllowed = true and PauseMaxDays = 10 When a user starts a pause with reason "Awaiting vendor invoice" and attaches at least one file or link Then the countdown freezes, a pause banner displays start time, reason, and days paused, and "Paused" status is visible on dashboards When paused days reach PauseMaxDays Then the system auto-unpauses, resumes the countdown, and sends an escalation notification Given jurisdiction PauseAllowed = false When a user attempts to start a pause Then the system blocks the action and shows a message citing the applicable rule When a pause ends (manual or invoice received) Then countdown resumes and elapsed time excludes paused days only if PauseAllowed = true, and the audit log records user, timestamps, reason, and attachments
Post-Deadline Blocking with Remediation Suggestions
Given current time is after the computed deadline When a user attempts to send a deduction notice Then the action is blocked, the control is disabled and labeled "Deadline missed", and a tooltip explains the compliance risk When blocked Then the system presents remediation options: generate missed-deadline report, notify tenant, issue goodwill refund, request tenant acknowledgment, or mark for legal review; the user can select and track one or more actions Then no admin override exists to send after deadline Then selecting a remediation option creates corresponding tasks, sends any configured notifications, and records the decision in the audit log
Compliance Dashboard and Export for Reporting
Given a portfolio up to 150 units When loading the dashboard Then totals by severity and jurisdiction are displayed and a table shows: Property, Unit, Tenant, Jurisdiction, Anchor Event, Deadline (with timezone), Days Remaining, Severity, Paused Days, Status Given filters for jurisdiction, severity, property, and deadline date range When a filter is applied Then results update within 2 seconds and counts reflect the filtered set When the user requests CSV export with current filters Then a CSV is generated within 5 seconds containing the table columns plus counts of notifications sent and pause events; values match on-screen data exactly
Webhook Delivery for Deadline Lifecycle Events
Given webhooks are enabled for a property When a deadline is created, updated, paused, unpaused, enters Red severity, or is missed Then a webhook is sent with event type, entity identifiers, timestamps, anchor event, deadline, severity, and an idempotency key Then each webhook is HMAC-SHA256 signed with a shared secret and includes a timestamp header When delivery fails with non-2xx Then retries occur with exponential backoff for up to 24 hours; any 2xx response marks success Given duplicate deliveries with the same idempotency key When the consumer processes the event Then duplicates can be safely ignored Given a test delivery is requested When invoked Then a sample event is sent to the configured endpoint; per-property enable/disable and endpoint configuration is supported
Dispute & Reconciliation Workflow
"As a landlord, I want a structured dispute process with audit trails so that disagreements are resolved faster and with less risk."
Description

Provides a tenant-facing portal to dispute specific line items with comments and counter-evidence, plus an internal review queue for managers to assess, adjust amounts, or withdraw items. Tracks SLA timers, decision rationales, and outcomes, generating revised, versioned notices where needed. Supports configurable resolution policies, structured settlement offers, and a final evidence/export packet suitable for mediation or small-claims court. Maintains a complete audit trail across all actions.

Acceptance Criteria
Tenant Disputes Specific Line Items With Evidence
Given a tenant receives an itemized deposit deduction notice and is within the dispute window (default 7 calendar days, configurable) When the tenant opens the Dispute portal and selects specific line items to contest Then the tenant must provide a comment per disputed item (minimum 10 characters) and may upload up to 10 files per item (jpg, png, heic, pdf, mp4; max 25 MB each) And invalid file types/sizes are blocked with inline error messages and no upload occurs And the submission cannot proceed unless at least one item is selected and all required fields are valid And upon submission the dispute is timestamped, associated to the exact line items, and a review SLA timer starts And confirmation is sent via email and portal notification to the tenant
Manager Reviews Disputed Items and Adjusts Amounts
Given a tenant dispute exists When a manager opens the Review Queue Then the case is visible with filters for age (SLA), property, unit, amount at risk, and status And opening the case shows side-by-side original evidence and tenant evidence per line item And the manager can Uphold, Adjust (enter new amount <= original; non-negative), Withdraw, or Request More Info per item And each action requires selecting a policy reason code and entering a rationale (minimum 20 characters) where applicable And Request More Info pauses the review SLA and notifies the tenant And all decisions are saved, audit logged (user, timestamp, before/after), and the case status updates accordingly
SLA Timers and Escalations
Given SLA policies are configured (e.g., tenant response 7 calendar days; manager review 5 business days) When a notice is sent, a dispute is submitted, or more info is requested Then the relevant SLA timers start, pause, or resume accordingly and display countdowns to the appropriate party And at 80% of SLA and at breach, the system triggers escalations: dashboard alert, email to assignee and fallback, optional SMS And breached cases are flagged and appear in an "At Risk" filter And all SLA state transitions are captured in the audit log
Versioned Notice Regeneration After Decisions
Given one or more disputed items are adjusted or withdrawn or the dispute window expires When the manager finalizes decisions for all disputed items Then a revised, versioned deposit deduction notice (version n+1) is generated using the jurisdiction-specific template And the notice includes updated itemization, amounts, timelines, and linked evidence thumbnails And a change log highlights differences from the prior version And the notice is delivered via portal and email with read receipt tracking And prior versions remain immutable and accessible, with statutory timelines (e.g., 21 days) recalculated and displayed
Structured Settlement Offers and Counteroffers
Given a disputed case is in review When the manager proposes a settlement (lump-sum or per-item) with an expiration (default 72 hours) and within policy caps Then the tenant can accept, reject, or submit a single counteroffer within configured limits And acceptance auto-updates item amounts, marks the case as Settled, and triggers notice regeneration And rejection returns the case to Review state And counteroffers require a rationale (minimum 10 characters) and are validated against policy; out-of-bounds counters are blocked with errors And all offers and counters show countdown timers and are fully audit logged
Final Evidence and Export Packet
Given a case reaches closure (Settled, Upheld, Withdrawn, or Expired) When the manager requests an export Then the system generates a downloadable packet within 60 seconds for cases up to 500 MB total evidence And the packet includes: original notice, all versions, itemized decisions, comments, all evidence files, decision rationales, SLA timeline, and full audit log And a cover sheet shows case ID, property/unit, parties, dates, and outcome summary And a SHA-256 manifest validates file integrity And PII is redacted per policy in the PDF while originals are retained in the ZIP
Complete Audit Trail and Immutable Logging
Given any action occurs in the dispute workflow When the action is saved Then an immutable audit entry records user ID, role, action type, entity, before/after values, UTC timestamp, IP/device fingerprint, and related SLA/notice version And entries are append-only and hash-chained to detect tampering And audit logs are filterable by case, user, action, and date range, and exportable to CSV And RBAC limits visibility: tenants see only their case; managers/admins per property scope
Accounting & Refund Disbursement Integration
"As a property manager, I want deductions and refunds to post automatically to my books and pay out to tenants so that closeout is accurate and hands-off."
Description

Calculates net refund after deductions, posts entries to FixFlow ledgers and external accounting systems (e.g., QuickBooks/Xero), and initiates refund disbursement via ACH/check through payment partners. Handles trust accounting separation, remittance advice to tenants, and reconciliation when payments clear. Supports partial refunds, chargebacks, and re-issues, ensuring books stay aligned with notice versions and maintaining a complete financial audit log.

Acceptance Criteria
Net Refund Calculation and Ledger Posting
Given a finalized itemized deduction notice is approved for a tenancy deposit When the refund is calculated Then the net refund equals the original deposit received minus the sum of approved deductions, to the cent, using property currency and defined rounding rules And a balanced journal entry is posted to FixFlow ledgers with unique Notice ID, tenancy, property, accounts, amounts, and posting date And the journal entry is locked against edit and linked to the notice version used for calculation Given a notice is amended after posting When a new version is finalized Then the system posts delta adjustment entries that bring books in line with the latest version and marks prior entries as superseded without deletion And any pending disbursements based on superseded versions are automatically blocked until recalculated
External Accounting Sync (QuickBooks/Xero)
Given a successful FixFlow ledger posting exists When external sync runs Then a matching balanced transaction is created in the connected accounting system within 2 minutes And account mappings follow the configured chart-of-accounts map for the property/portfolio And the external document/memo references the FixFlow Notice ID and posting date Given a transient sync failure occurs When retry policy applies Then the system retries up to 3 times with exponential backoff and surfaces an error after final failure And the sync is idempotent using a deterministic external document key to prevent duplicates
ACH Refund Disbursement Initiation and Settlement
Given the tenant’s bank details are verified and the net refund is greater than zero When the manager initiates refund via ACH through the payment partner Then a disbursement is created with status "Pending", amount equals the net refund, and an estimated settlement date is recorded And a remittance advice is generated and sent to the tenant via email and portal with itemization, payment method, amount, and expected timeline Given the payment partner sends a settlement webhook When the event is received and validated Then the disbursement status updates to "Paid", the settlement date and reference ID are stored, and the payment is reconciled against the FixFlow ledger posting
Trust Accounting Separation and Safeguards
Given the portfolio is configured for trust accounting When a refund disbursement is initiated Then disbursement funds are sourced from the designated trust account and any platform/processing fees are posted to the operating account per configuration And the system blocks disbursement if the trust account ledger balance is insufficient, showing a clear error and required next steps And all entries reflect correct entity, bank account, and fund separation for audit compliance
Partial Refunds, Re-Issues, and Check Management
Given a manager chooses to issue a partial refund now and remaining later When the first payment is created Then the system records the outstanding refundable balance and prevents overpayment beyond the net refund total Given a check is lost or voided before clearing When the manager marks the check as void/stop-payment Then reversing entries are posted, the payment status changes to "Voided", and the outstanding balance is restored for re-issue And a re-issued payment is linked to the original with a clear audit relationship And books remain aligned with the current notice version without duplicate expense or liability impact
Chargeback/Return Handling and Reconciliation
Given an ACH refund is returned or charged back by the bank When a return/chargeback webhook is received and validated Then the disbursement status updates to "Returned", reversing ledger entries are posted, and the tenant balance reflects the returned funds And the manager is notified with reason code and recommended actions, and re-issue is enabled once banking issues are resolved Given a payment clears When bank settlement is confirmed Then the corresponding payment is marked "Cleared" and appears matched in reconciliation reports with bank reference IDs
Complete Financial Audit Log and Traceability
Given any calculation, posting, sync, or disbursement event occurs When the event is committed Then an immutable audit log entry is recorded with actor/service, timestamp (UTC), entity IDs (tenancy, property, notice version), amounts before/after, accounts, external IDs, and IP/request metadata And audit entries are read-only, exportable (CSV/JSON), filterable by date range and entity, and retained per data retention policy And every remittance advice and notice links back to the exact ledger posting and payment records used

Make‑Ready Board

A live command board showing every unit’s readiness stage, blockers, and SLA timers. Offers one-click nudges, reassignment, or slot swaps to keep the blitz moving—and gives owners and PMs real-time clarity on what’s done, what’s next, and what’s at risk.

Requirements

Real-Time Make‑Ready Board
"As a property manager, I want a live board summarizing each unit’s readiness status so that I can quickly spot risks and coordinate actions without navigating multiple screens."
Description

Deliver a live, auto-updating board that presents every unit’s make‑ready status at a glance. Each unit appears as a card with current stage, assignee(s), due/SLA timers, blockers, and next actions. Support column-based layout by stage with drag-and-drop card movement subject to workflow rules. Provide fast filters (property, stage, assignee, risk level, date window) and saved views. Use push updates (WebSockets) with offline-safe fallbacks and UI virtualization for large portfolios (1–150 units). Ensure color-coded risk states, badge indicators for blockers, and accessibility-compliant contrasts. Integrate directly with FixFlow work orders, approvals, vendor assignments, and tenant communications so board changes persist across the platform.

Acceptance Criteria
Real-Time Push Updates with Offline Fallback
Given the board is open with a stable network When another user updates a unit's stage or fields Then the corresponding card updates within 2 seconds without page refresh via WebSockets. Given the WebSocket disconnects When connectivity is lost Then the app displays an offline banner within 1 second, queues user actions locally, and marks affected cards with a queued badge. Given connectivity is restored When the WebSocket reconnects Then queued actions sync within 5 seconds; successful sync clears queued badges; conflicts are surfaced with a toast describing the server state and the user can retry. Given the backend is unreachable for more than 10 seconds When the app is offline Then a polling fallback runs every 15 seconds until the WebSocket connection is re-established.
Card Content Completeness, Risk, and Accessibility
Given a unit has a defined make-ready stage, assigned user(s)/vendor(s), due date/SLA, blockers, and next actions When the card renders Then all fields are present and reflect backend values on first paint. Given SLA times are active When the board is open for at least 2 minutes Then countdown timers decrement in real time with accuracy within ±1 minute and update color based on risk threshold: green (>24h), amber (6–24h), red (<6h or overdue). Given a card has blockers When the card is visible Then a blockers badge shows the count; selecting it opens a panel listing blocker titles and owners. Given color is not perceivable When inspected with contrast tooling Then text and essential iconography meet WCAG AA contrast (>=4.5:1 for normal text, >=3:1 for large text/icons), and risk state also has a text label.
Column-Based Layout by Stage
Given a property workflow with N stages When the board loads Then N columns render in the configured stage order, each with a header displaying stage name and card count. Given the user scrolls horizontally When there are more columns than viewport width Then columns remain virtualized and sticky headers remain readable without layout shift. Given a unit changes stage from any source When the change is applied Then the card moves to the corresponding column within 2 seconds.
Drag-and-Drop Movement with Workflow Rules Enforcement
Given a card is draggable and a target stage allows transition per workflow rules When the user drags and drops the card onto that column Then the card lands, the new stage is persisted to the backend, and the change is visible to all clients within 2 seconds. Given a transition is disallowed by workflow rules When the user attempts the drop Then the card snaps back to its original column and an inline message explains the blocked rule. Given the backend write fails When the user drops a card Then the UI reverts the move within 1 second and displays an error toast with a retry option. Given two users move the same card concurrently When both writes reach the server Then the first successful write persists; the second client sees a conflict toast and the card reflects server state.
Fast Filters and Saved Views
Given units span multiple properties, stages, assignees, risk levels, and date windows When the user applies any combination of these filters Then results appear within 300 ms for up to 150 units and filter chips reflect the active set. Given a filter set is active When the user saves it as a view with a unique name Then the view is persisted per user, appears in the Saved Views list, and can be set as default. Given a saved view exists When the user renames, updates, or deletes it Then changes persist immediately and are reflected on reload. Given a direct URL to a saved view When opened in a new session Then the board loads with identical filters and sort as the saved view.
Scalability and UI Virtualization for 1–150 Units
Given a portfolio of 150 units distributed across up to 10 stages When the board first loads on a 10 Mbps connection Then first contentful paint occurs within 2 seconds and interactive within 3 seconds. Given the user scrolls through a column with 150 cards When measuring performance Then scroll remains smooth at >=55 FPS on a mid-tier laptop and the DOM contains only cards within the viewport ± one screen. Given frequent updates (>=5 updates/sec) When the board is open Then the UI remains responsive with main-thread long tasks under 50 ms and no missed updates.
Cross-Platform Persistence with FixFlow Workflows
Given a user changes a card’s stage, assignee, or next action on the board When the change is saved Then linked FixFlow work orders, approvals, vendor assignments, and tenant communications reflect the change within 2 seconds. Given a related entity changes outside the board (e.g., work order closed) When the system emits the event Then the board updates the card to the corresponding stage and clears/computes next actions within 2 seconds. Given an audit is required When a card field changes Then an audit log entry is created with user, timestamp (UTC), old value, new value, and source (Board/API/Other).
Stage & Workflow Configuration
"As an admin for a property management team, I want to define make‑ready stages and rules so that the board reflects our process and drives consistent execution."
Description

Provide an admin console to define make‑ready stages, entry/exit criteria, required tasks/checklists, SLAs, and custom fields. Allow per-portfolio templates and property-level overrides. Enable rules for automatic stage advancement based on task completion, approvals, or data signals (e.g., keys returned, cleaning complete). Support stage policies such as pause reasons, required attachments (photos), and handoff validations. Expose versioned workflows to change processes without disrupting in-flight units; migrating units must follow safe transitions with audit logs.

Acceptance Criteria
Create Portfolio-Level Workflow Template
Given I am an admin with Workflow:Manage permission in Portfolio A When I create a new Make‑Ready workflow template named "Standard Turn" Then I can add ordered stages with unique names within the template And for each stage I can define entry criteria, exit criteria, required tasks/checklists, an SLA duration, and custom fields (text, number, date, dropdown, attachment) And saving validates required fields and blocks publish if any stage lacks a name, exit criteria, or SLA And the template is persisted and retrievable by ID and portfolio via API and UI And created/updated metadata (user, timestamp, version) is recorded
Property-Level Overrides of Template
Given Property P is linked to Portfolio A's workflow template v1 When I override the "Cleaning" stage SLA and add a custom field at the property level Then the property shows effective values reflecting overrides while all other stages inherit from the template And I can revert an override to inherit again And publishing the override does not modify the portfolio template And API responses include both base values and override indicators for each field
Auto-Advance on Task Completion, Approval, and Data Signals
Given a stage has an auto-advance rule: all required tasks complete AND ManagerApproval = Approved AND DataSignal.KeysReturned = true When those conditions become true for Unit U Then Unit U automatically advances to the next stage within 2 minutes And the previous stage SLA timer stops, the next stage SLA timer starts, and the transition is written to the audit log with the rule ID And assigned users of the next stage are notified via in-app and email notifications And if any condition is not satisfied, the unit does not advance and no partial transition is recorded
Enforce Required Attachments and Handoff Validations
Given the "Make‑Ready Inspection" stage requires at least 3 photos and a signed checklist for exit When a user attempts to advance the unit without the required attachments or validations Then the system blocks advancement and displays specific validation errors identifying missing items And once all required attachments and validations are provided, advancement succeeds without admin intervention And all attachments are stored, linked to the unit and stage, and visible in the audit trail and API
Pause Reasons and SLA Suspension Policy
Given a stage policy defines pause reasons {Awaiting Parts, Tenant Delay} that suspend SLAs When a user with permission pauses a unit selecting an allowed reason and adds a note Then the stage SLA timer is suspended from the pause timestamp And the unit shows status Paused with reason and note And upon resume the SLA timer resumes and total elapsed excludes paused duration And pause/resume events are recorded in audit logs and exposed via API
Versioned Workflow Publishing Without Disruption
Given workflow template v1 is Published and in use and v2 is in Draft When I publish v2 Then new units created after publish are associated with v2 by default And existing in‑flight units remain on v1 unless explicitly migrated And both v1 and v2 remain retrievable with full definitions, version numbers, and effective dates And publishing is blocked if v2 contains breaking changes that would invalidate existing property-level overrides until resolved
Safe Migration of In‑Flight Units with Full Audit Logging
Given a migration plan maps stages from v1 to v2 When I select 10 in‑flight units on v1 and execute migration to v2 Then the system validates each unit's current stage has a mapped destination and blocks migration for any unmapped stages, listing the affected units And for migrated units the new stage is set per mapping, the previous stage SLA timer stops, the new stage SLA timer starts, and entry/exit validations are re‑evaluated And no historical tasks, attachments, or logs are lost; all events are recorded with who, when, fromVersion→toVersion, fromStage→toStage And assigned users are notified of the migration outcome for their units
SLA Timers & Risk Scoring
"As a leasing lead, I want SLA timers and risk flags so that I can prioritize units at risk of missing move-in dates."
Description

Implement configurable SLAs per stage with business-hour calendars and holidays, start/stop/pause logic (e.g., paused when awaiting tenant access), and pre-breach alerts. Display countdowns and elapsed time on cards with color thresholds. Compute a unit-level risk score using factors like SLA proximity, blocker severity, vendor availability, and move-in date alignment. Surface risk badges and sort/group by risk. Allow managers to adjust SLAs by template, property, or unit and export SLA metrics to reporting APIs.

Acceptance Criteria
Stage SLA Configuration with Business-Hour Calendars and Holidays
Given a manager sets an SLA duration per stage at template, property, and unit scopes, When the configuration is saved, Then the effective SLA for a unit resolves by specificity: unit > property > template. Given a business-hours calendar and holiday list are assigned to a property or unit, When SLA time is calculated, Then only business hours are counted and holidays are excluded. Given multiple calendars exist, When determining the effective calendar for a unit, Then the system applies the most-specific assigned calendar and records the applied calendar ID in the audit log. Given an SLA definition is edited, When the manager selects "Apply to in-progress" and saves, Then in-progress timers recalculate remaining time using the new SLA and the change is audited; otherwise, existing timers remain unchanged.
SLA Timer Start, Stop, and Pause Logic
Given a stage transitions to In Progress, When entry criteria are met, Then the SLA timer starts at the transition timestamp in the property's timezone. Given a stage is set to Blocked with a pause reason (e.g., Awaiting Tenant Access, Vendor Parts), When the status changes to Blocked, Then the SLA timer pauses and records pause reason and duration. Given a stage is marked Done, When the status changes to Done, Then the SLA timer stops and final business elapsed time is persisted. Given a manager performs a manual timer adjustment, When they save the change, Then the audit trail records user, before/after values, reason, and timestamp. Given calculations span timezones, When computing SLA time, Then all calculations normalize to the property's timezone and store timestamps in ISO 8601.
Pre-Breach Alerts and Escalations
Given an SLA has a pre-breach threshold X minutes, When remaining business time ≤ X and the timer is not paused, Then send a pre-breach alert via in-app and email to the assignee and property manager. Given a pre-breach alert has already been sent for the current state, When the threshold condition remains true, Then suppress duplicate alerts until state changes (resume, unblocked, or breach). Given an SLA breach occurs, When remaining time reaches zero during business hours, Then send a breach alert, update the card badge to Breached, and log the event. Given escalation rules specify a secondary recipient and delay Y minutes, When a breach persists > Y minutes, Then send an escalation alert to the configured recipient. Given quiet hours are configured, When an alert is triggered during quiet hours and severity ≠ Critical, Then defer notifications until the next business hour start.
Countdown and Elapsed Time Display with Color Thresholds
Given an active SLA timer, When viewing the Make‑Ready board, Then each card shows remaining business time (countdown) and an elapsed time chip. Given color thresholds are configured as Green (>50% remaining), Amber (10–50%), and Red (<10% or breached), When rendering a card, Then the timer background reflects the correct color threshold. Given a timer is paused, When rendering the card, Then the countdown is frozen, a Paused indicator with pause reason is shown, and no time decrements. Given a user hovers over the timer, When the tooltip opens, Then it displays start time, pauses with durations, effective calendar, target SLA, and final/remaining times. Given accessibility standards, When displaying colors, Then ARIA labels convey status and contrast ratios meet WCAG AA (≥4.5:1).
Risk Score Computation and Refresh Cadence
Given a unit has factors SLA proximity, blocker severity, vendor availability, and move‑in date alignment, When computing risk, Then produce a numeric score 0–100 using the configured weights and persist score, inputs, and calculation timestamp. Given any factor value changes (timer tick crossing a threshold, blocker added/cleared, vendor ETA updated, move‑in date changed), When the change is saved, Then recalculate the risk score within 60 seconds. Given risk tiers Low (<40), Medium (40–69), High (≥70), When a score updates, Then the unit badge reflects the correct tier immediately on the board. Given a factor is missing, When computing risk, Then apply a neutral default for that factor and flag imputation in the audit record.
Risk Badges, Sorting, and Grouping
Given units have risk scores, When viewing the Make‑Ready board, Then the user can sort by risk ascending/descending and group units by risk tier (Low/Medium/High). Given a risk badge is rendered on a card, When viewing, Then it displays the tier color and the numeric score. Given a user clicks Nudge on a high‑risk card, When confirmed, Then send a templated reminder to the current assignee and write an activity log entry referencing the unit and stage. Given board filters are applied, When sorting and grouping, Then counts and aggregates reflect only filtered units.
SLA Metrics Export to Reporting APIs
Given an authenticated client with scope reporting.read, When calling the SLA metrics endpoint with a date range, Then return per‑stage records including target SLA, business elapsed, pause durations with reasons, breach status, pre‑breach alerts sent, breach timestamp, and applied calendar ID in JSON. Given pagination parameters limit and cursor, When provided, Then the API returns a stable, paginated result set with next cursor until exhaustion. Given property, unit, and stage filters, When specified, Then only matching records are returned and totals reflect the filtered set. Given invalid parameters, When validation fails, Then respond 400 with machine‑readable error codes; when unauthorized, respond 401; when forbidden scope, respond 403. Given time fields, When serializing, Then return ISO 8601 timestamps with timezone offsets and include the property's timezone identifier.
Blocker Detection & Dependency Tracking
"As a maintenance coordinator, I want to see blockers and dependencies for each unit so that I can remove bottlenecks quickly and keep work flowing."
Description

Automatically detect and label blockers such as pending approvals, parts on order, vendor confirmations, tenant access, or inspection outcomes. Model dependencies between tasks (predecessor/successor) and visualize them on the card and detail pane. Provide standard blocker reason codes with notes and attachments. Enable unblock actions (e.g., request approval, order part) directly from the board. Allow authorized users to override/waive a blocker with a required reason and audit log. Update dependencies in real time as upstream tasks complete.

Acceptance Criteria
Auto-Detect Common Blockers and Label on Card
Given a work order task has an associated approval with status "Pending" When the Make‑Ready Board loads or the task state changes Then the task card and detail pane show a blocker badge "Pending Approval" with reason code "APPROVAL_PENDING" And Start/Complete actions for the task are disabled Given a task has a linked part with order_status = "ORDERED" and delivery_eta is in the future When the board renders Then a blocker badge "Parts on Order" with reason code "PARTS_ON_ORDER" is displayed with the ETA on the card and detail pane And the task remains blocked Given a task requires tenant access and access_status = "UNCONFIRMED" When the board renders Then a blocker badge "Tenant Access Needed" with reason code "TENANT_ACCESS" is displayed Given a task awaits vendor acceptance and vendor_status = "PENDING_CONFIRMATION" When the board renders Then a blocker badge "Vendor Confirmation" with reason code "VENDOR_CONFIRMATION" is displayed Given a task is contingent on inspection results and inspection_status is "SCHEDULED" or "PENDING_RESULTS" When the board renders Then a blocker badge "Inspection Outcome" with reason code "INSPECTION_OUTCOME" is displayed
Visualize Task Dependencies on Card and Detail Pane
Given Task B has predecessor Task A recorded in the system When viewing the Make‑Ready Board Then Task B's card shows a dependency badge "Blocked by A" And the Task B detail pane lists Task A in a Dependencies section with its current status Given Task A transitions to "Done" When the change is saved Then within 2 seconds Task B's dependency indicator updates in real time to "Resolved" And the dependency entry in the detail pane reflects the completion timestamp of Task A Given Task B has no remaining blockers after Task A completes When the dependency is resolved Then Task B's Start action becomes enabled automatically
Capture Standard Blocker Codes, Notes, and Attachments
Given an authorized user opens Blocker details for a task When adding or editing a blocker Then the user can select one of the standard reason codes: APPROVAL_PENDING, PARTS_ON_ORDER, VENDOR_CONFIRMATION, TENANT_ACCESS, INSPECTION_OUTCOME Given a blocker is being saved When the user includes notes and file attachments Then the notes text is stored and displayed in the detail pane And attachments can be uploaded, listed with filename and size, viewed/downloaded successfully, and removed And disallowed file types (e.g., .exe) are rejected with an error message Given a task has one or more blockers saved When viewing the task card Then the card shows a blocker badge per active blocker and a total blocker count And the detail pane shows the full blocker list with reason code, notes, and attachments
Trigger Unblock Actions Directly from the Board
Given a task has a "Pending Approval" blocker When the user clicks "Request Approval" from the card quick actions or detail pane Then an approval request modal opens to select approver and message And on successful submission the blocker status updates to "Approval Requested" with a timestamp and request ID visible in the detail pane Given a task has a "Parts on Order" need and no purchase order on file When the user clicks "Order Part" Then a part order modal opens to capture vendor, SKU, quantity, and ETA And on successful submission the blocker updates to "Parts on Order" with the PO number and ETA displayed Given a task has a "Vendor Confirmation" blocker When the user clicks "Request Vendor Confirmation" Then the system sends a confirmation request to the assigned vendor and records the attempt And the blocker shows last requested timestamp and method (email/SMS/app) Given a task has a "Tenant Access Needed" blocker When the user clicks "Request Access" Then the tenant is prompted for access windows and confirmation And the blocker updates with the latest tenant response status
Override or Waive a Blocker with Required Reason and Audit Log
Given a user has the permission "BLOCKER_OVERRIDE" When the user selects "Override/Waive" on a blocker Then a confirmation modal requires a non-empty override reason And on confirmation the blocker state becomes "Waived" and the task is unblocked if no other blockers remain Given a user lacks the permission "BLOCKER_OVERRIDE" When viewing the task Then the Override/Waive control is not shown on the card or detail pane Given a blocker is overridden When the action completes Then an audit log entry is created capturing task ID, blocker code, previous state, new state, actor, timestamp, and reason And the audit entry is visible under the task's Audit section
Real-Time Dependency Updates Across Sessions
Given two users are viewing the same Make‑Ready Board When User A marks a predecessor task as "Done" Then within 2 seconds User B sees the downstream task's dependency badge resolve and any related blocker removed without manual refresh Given a predecessor task is reopened from "Done" to an active state When the status change is saved Then downstream tasks immediately regain the dependency blocker in both users' sessions Given network connectivity is lost on a client When it is restored Then the board resynchronizes and reflects the current dependency and blocker states within 5 seconds
One‑Click Nudges, Reassignment, and Slot Swaps
"As a scheduler, I want to nudge, reassign, or swap time slots with one click so that I can keep make‑ready tasks moving without opening multiple tools."
Description

Offer inline actions on unit cards: send templated nudges to technicians/vendors/tenants via SMS, email, or app push; reassign tasks to a different technician or vendor; and swap scheduled time slots to resolve conflicts. Provide safe-guard confirmations, undo windows, and rate-limiting on nudges. Respect user permissions and record every action to an audit log. Pre-fill messages with context (unit, task, SLA state) and allow quick edits. Ensure actions are idempotent and immediately reflected on the board and in related FixFlow records.

Acceptance Criteria
Send Templated Nudge From Unit Card
Given I am on a unit card on the Make‑Ready Board with an open task And I have permission to send nudges When I click Nudge and select a recipient and channel (SMS, Email, or Push) Then the compose window is pre‑filled with unit number, task name, SLA state, scheduled slot (if any), and a link to the task And I can edit the message body before sending And I can preview the final rendered message with placeholders resolved When I click Send Then the nudge is delivered via the selected channel And a delivery attempt record with status (Queued, Sent, Failed) is created And the unit card shows a "Nudged" indicator with timestamp within 3 seconds
Nudge Rate Limiting and Undo Window
Given a recipient has received a nudge for a specific task within the last 10 minutes When I attempt to send another nudge to the same recipient for the same task Then the system blocks the send and shows a rate‑limit message stating the next available send time And per recipient per task no more than 3 nudges can be sent within a rolling 24‑hour window Given I send a nudge When I confirm the send Then an Undo option is shown for 15 seconds And if I click Undo within 15 seconds, the message is not delivered (or is recalled if still queued), UI rolls back, and audit log records a reverted action
Safe‑Guard Confirmations for Inline Actions
Given I initiate a nudge, reassignment, or slot swap from a unit card When I click to perform the action Then a confirmation dialog summarizes the action (target(s), time(s), impact on SLA) And the primary action is disabled until I acknowledge the summary When I confirm Then the action executes And when I cancel Then no changes are made and no notifications are sent
Reassign Task to Different Technician or Vendor
Given a task is assigned to Assignee A And I have permission to reassign When I open Reassign and select Assignee B Then the system validates Assignee B’s availability and permissions And shows any conflicts before confirming When I confirm Then the task’s assignee changes to Assignee B And Assignee A is unassigned and notified And Assignee B is notified with task context And the change is reflected on the Make‑Ready Board within 2 seconds And related FixFlow records (work order, calendar) are updated consistently
Swap Scheduled Time Slots to Resolve Conflict
Given two scheduled tasks with time slots that conflict with resource availability And I have permission to manage schedules When I select both tasks and choose Swap Slots Then the system validates that the swap resolves the conflict and does not violate constraints (business hours, blackout dates, resource capacity) And it previews new start/end times and SLA impact When I confirm Then the two tasks exchange their scheduled slots And all affected calendars and notifications are updated And SLA timers and risk flags recalculate within 5 seconds And the Make‑Ready Board reflects the new order within 2 seconds
Permissions Enforcement for Inline Actions
Given my role lacks permission for nudges, reassignment, or slot swaps When I view unit cards Then restricted actions are hidden or disabled with an explanatory tooltip When I attempt the action via direct API or deep link Then the request is rejected with HTTP 403 and an error code indicating missing scope And no state changes occur Given my role has permission When I perform the action Then it succeeds subject to all other validations
Audit Logging, Idempotency, and Real‑Time Sync
Given any inline action (nudge, reassignment, slot swap) is initiated with a client‑generated request_id When the action completes Then an immutable audit log entry is written with action_type, request_id, actor_id, target_id(s), timestamp (UTC ISO‑8601), before/after summary, channel (if applicable), and outcome And the same request_id within 5 minutes results in a no‑op response with the original outcome (idempotent) And the Make‑Ready Board updates the unit card state within 2 seconds of completion And underlying FixFlow records (task assignment, schedule, communication log, SLA state) are consistent with the board view
Technician Scheduling & Calendar Sync
"As a field operations lead, I want the board tied to technician schedules so that assignments and rearrangements are fast and conflict-free."
Description

Integrate the board with FixFlow scheduling to display technician availability, travel buffers, and conflicts. Provide suggestions for earliest feasible slots based on skills, location, and SLA urgency. Support bulk scheduling from the board and drag-and-drop rescheduling that updates all dependent tasks. Sync with Google Workspace and Microsoft 365 calendars (ICS/webhook) and prevent double-booking across systems. Handle time zones, business hours, and daylight-saving changes gracefully.

Acceptance Criteria
Technician Availability with Travel Buffers and Conflict Indicators
Given a user opens the Make‑Ready Board and selects a unit task requiring a technician When the availability panel loads for one or more technicians Then the board displays free, busy, travel buffer, and hold times on a timeline for the next 14 days And travel buffer between consecutive jobs equals estimated drive time plus the org-defined padding (default 10 minutes), rounded to 5-minute increments And conflicts (overlap, insufficient buffer, outside business hours) are highlighted with icons and tooltips stating the reason And availability reflects external calendars and FixFlow jobs updated within 60 seconds of a change And the availability panel renders within 2 seconds for up to 50 technicians
Smart Slot Suggestions by Skill, Location, and SLA Urgency
Given an unscheduled task with required skill tags, estimated duration, property location, and an SLA deadline When the user requests suggested slots Then the system returns at least 3 earliest feasible slots before the SLA that satisfy skill match, technician business hours, travel buffer constraints, and no conflicts across FixFlow or external calendars And each suggestion displays technician, start–end time in the viewer’s locale, travel time, and an SLA risk indicator And if no slot exists before the SLA, the next 3 feasible slots after the SLA are shown with a “misses SLA” badge And suggestions compute within 2 seconds for up to 100 candidate technicians
Bulk Scheduling from the Make‑Ready Board
Given the user multi-selects 5–50 unscheduled tasks and chooses a scheduling window, technician pool, and max jobs per day per technician When the user clicks Schedule Then tasks are placed into earliest feasible slots honoring duration, technician business hours, travel buffers, dependencies, and SLAs And tasks at the same property are clustered where feasible without increasing SLA risk And a preview shows assigned technician, time, and any conflicts before confirmation And upon confirmation, at least 95% of non-conflicting tasks are scheduled within 5 seconds; remaining complete within 30 seconds And any failures are reported per task with actionable reasons; successful schedules are not rolled back if others fail
Drag‑and‑Drop Rescheduling with Dependency Propagation
Given a scheduled task with downstream dependencies on the Make‑Ready Board When the user drags the task to a new time or reassigns it to a different technician Then the task updates and all dependent tasks shift by the same delta while respecting business hours, travel buffers, and SLAs And if a dependent task would violate constraints, the system prompts: auto-adjust to next feasible slot, proceed with override, or cancel And slot swaps between two tasks (same or different technicians) complete atomically so neither is left unscheduled And notifications to affected technicians and tenants are sent within 1 minute of confirmation And an audit log records user, timestamp, before/after times, technician changes, and affected task IDs
Two‑Way Calendar Sync with Google Workspace and Microsoft 365
Given an organization has connected Google Workspace and/or Microsoft 365 and enabled calendar sync When FixFlow creates, updates, or cancels a scheduled task Then a corresponding external calendar event is created, updated, or canceled within 60 seconds, including title, job ID, property address, description, and the assigned technician as an attendee And inbound updates received via webhook from external calendars are reflected in FixFlow within 60 seconds; ICS feeds are polled at least every 15 minutes And external events use the technician’s calendar time zone and respect daylight-saving rules And authentication tokens auto-refresh; transient failures retry up to 3 times with exponential backoff and are surfaced in an error log
Cross‑System Double‑Booking Prevention
Given a user attempts to schedule or move a task for a technician When the selected slot overlaps any FixFlow job, external calendar event, or required travel buffer Then the action is blocked with a clear conflict message naming the source and offering one-click alternative slots And concurrent scheduling attempts for the same technician are serialized so that only one succeeds; the other receives a conflict error within 2 seconds And administrators may apply an explicit override that requires a reason and is recorded in the audit log; the external event is still created with a “conflict” tag
Time Zones, Business Hours, and Daylight‑Saving Handling
Given users, properties, and technicians may be in different time zones and observe DST When viewing, scheduling, or syncing tasks Then the board displays times in the viewer’s locale while storing timestamps in UTC and creating external events in the technician’s calendar time zone And scheduling respects each technician’s configured business hours and holidays; attempts outside hours require explicit override confirmation And tasks spanning a DST change preserve their intended duration and correct wall-clock times before and after the transition And all date/time inputs and API payloads include explicit time zones or UTC offsets
Audit Trail, Permissions, and Notifications
"As a portfolio owner, I want auditable changes and role-based controls so that we maintain accountability and security across our make‑ready operations."
Description

Enforce role-based access controls for viewing, editing, and performing quick actions on the board. Capture a detailed audit trail for stage changes, nudges, reassignments, and swaps with who, what, when, and why. Provide real-time notifications and daily digests for SLA risks, newly blocked units, and critical changes, with per-user preferences. Ensure data retention and export capabilities for compliance and owner reporting.

Acceptance Criteria
RBAC: Board Visibility and Edit Rights by Role
Given an Owner user assigned to Portfolio A When they open the Make‑Ready Board Then they can view all units in Portfolio A and no units outside it And they cannot edit stages or perform quick actions Given a Property Manager for Properties P1–P3 When they open the Make‑Ready Board Then they can view all units in P1–P3 And can edit stages and perform quick actions (nudge, reassign, slot swap) on those units Given a Maintenance Coordinator assigned to Property P2 When they open the Make‑Ready Board Then they can view all units in P2 And can perform quick actions on those units Given a Technician assigned to unit U1 When they open the Make‑Ready Board Then they can view U1 and any units where they are the assigned technician And they can update the readiness stage and task status on those units only And they cannot nudge, reassign, or slot swap Given a Vendor user When they open the Make‑Ready Board Then they can view only the units with an active work order assigned to their organization And they cannot edit stages or perform quick actions Given a Read‑Only user When they open the Make‑Ready Board Then they can view units per their portfolio permissions And cannot edit or perform quick actions Given any user When they attempt an action on a unit outside their permission scope via UI or API Then the system blocks the action with a 403 permission error And no state change occurs
Quick Actions: Authorization, Reason Capture, and Validation
Given a Property Manager or Maintenance Coordinator initiates a nudge, reassignment, or slot swap on an in‑scope unit When they confirm the action Then the system requires a Reason field with a minimum of 5 characters And validates that target assignees/slots are available And executes the action atomically Given the quick action completes Then the Make‑Ready Board reflects the update within 1 second And any affected SLA timers are recalculated immediately Given validation fails due to invalid assignee, conflicting slot, or missing reason When the user submits Then the action is rejected with a specific error message And no partial changes occur Given two users attempt conflicting quick actions on the same unit within 2 seconds When both submit Then only the first committed action succeeds And the others receive a conflict error and no changes are applied
Audit Trail: Stage Changes and Quick Actions
Given a stage change, nudge, reassignment, or slot swap is executed When the action is confirmed Then an audit entry is recorded with fields: action type, unit ID, property ID, previous value, new value, actor user ID and role, timestamp (UTC), reason text, and source (UI or API) Given an audit entry exists Then it is immutable and append‑only; updates are not permitted Given a user with audit‑view permission filters by action type and date range for a unit When they apply the filter Then only matching entries are returned within 500 ms for up to 5,000 records Given the user’s timezone preference is set When they view audit timestamps Then times display in that timezone and also show the UTC offset Given an unauthorized action is attempted When it is blocked by permissions Then no state‑change audit entry is created And a security event is logged separately outside the standard audit trail
Audit Trail Export and Filtering
Given a user with export permission selects a date range and filters (property, unit, action types) When they request export Then CSV and JSON files are generated with the filtered audit entries And are available for secure download within 60 seconds for up to 100,000 records Given an export completes Then files include headers/keys, ISO‑8601 UTC timestamps, and a SHA‑256 checksum manifest for integrity verification Given an export exceeds 100,000 records When the user requests it Then processing is asynchronous And the user receives a notification with a download link when ready And the link expires after 7 days
Real‑Time Notifications: SLA Risks, Blockers, Critical Changes
Given a unit’s SLA timer crosses a risk threshold or breach, the unit becomes newly blocked, or a critical change occurs (stage moved backward or reassigned) When the event occurs Then subscribed users receive an in‑app notification within 5 seconds And receive email/SMS within 60 seconds if those channels are enabled Given per‑user notification preferences for categories (SLA risk, newly blocked, critical changes) and channels (in‑app, email, SMS) exist When a user updates their preferences Then subsequent notifications honor the new settings immediately without logout Given a user has muted a unit or property When qualifying events occur on that scope Then no notifications are sent to that user Given multiple events of the same type occur within 5 minutes for the same unit When notifications are sent Then they are bundled into a single message indicating the count
Daily Digest: Configuration and Content
Given a user has enabled a daily digest with a selected local time (default 7:00 AM) and timezone When the digest time occurs Then the user receives one email summarizing the last 24 hours: units at SLA risk or breached, newly blocked units, and critical changes, grouped by property with counts and links Given a user has opted out of any category When the digest is generated Then that category is omitted from their digest Given no qualifying events occurred in the last 24 hours for the user’s scope When the digest is generated Then no digest is sent Given a digest is sent Then delivery success or failure is logged with a provider message ID for audit
Data Retention and Compliance
Given an organization‑level retention policy is set to N years (1–10, default 7) When audit entries exceed N years Then they are automatically archived and hidden from the UI And remain exportable by admins for 90 days before permanent deletion Given a legal hold is applied to a property or unit When retention would purge matching audit entries Then those entries are preserved until the hold is removed Given a user account is deactivated or a role changes When historical audit entries are viewed Then entries remain attributed to the historical user and visible based on the current viewer’s permissions Given an admin requests a compliance export for a date range When processed Then the package includes the audit data, a manifest of retention policy in effect, and a hash of contents for non‑repudiation

ChainSeal Ledger

Cryptographically chains every photo, note, status change, and approval so the entire maintenance record is tamper‑evident. One‑click integrity checks verify hashes and highlight exactly where any change occurred. Provides indisputable chain‑of‑custody for audits, insurance claims, and disputes.

Requirements

Immutable Event Hashing & Chain Linking
"As a property manager, I want every action and attachment in a work order to be cryptographically chained so that I can prove the record hasn’t been tampered with."
Description

Compute a cryptographic hash for every maintenance event (photo upload, note, status change, approval) using deterministic serialization and include a reference to the previous event’s hash to form an append‑only, tamper‑evident chain per work order. Hash all attached media and include their digests in the event’s Merkle root to localize discrepancies. Persist the chain in an append-only event store with replication and integrity checks, and expose APIs to append and read events. Integrate seamlessly with FixFlow’s intake, triage, scheduling, and communication flows so all actions are automatically chained without user effort. Expected outcome: any modification to historic data becomes immediately detectable and provable.

Acceptance Criteria
Deterministic Event Hashing Across Environments
- Given two maintenance events with identical logical content but different JSON key orders, When canonicalized using JSON Canonicalization Scheme (RFC 8785) and hashed with SHA-256, Then the resulting 64-character lowercase hex digest is identical across platforms and runs. - Given any single-bit change in the event payload (including whitespace, encoding, or field value), When re-hashed, Then the digest differs from the baseline digest. - Given a published fixture event and its expected digest, When hashed, Then the computed digest matches the published fixture digest exactly. - Given two machines with different OS/CPU architectures, When hashing the same canonical payload, Then the computed digests are byte-for-byte identical.
Append-Only Chain Linking and Genesis Handling
- Given a work order with head hash H at index N, When appending event E with previous_hash=H, Then E is committed at index N+1 and the new head becomes hash(E). - Given an append request where previous_hash != current head, When processed, Then the append is rejected with 409 Conflict and no event is written. - Given the first event for a work order, When appended, Then previous_hash must equal the 64-hex-zero genesis value and is validated by the API. - Given a committed event, When attempting update or delete via any API, Then the request is rejected (405/403) and the chain remains unchanged.
Media Merkle Root and Discrepancy Localization
- Given N attachments for an event, When each is hashed with SHA-256 and a binary Merkle tree is constructed using SHA-256 for internal nodes, Then the stored merkle_root equals the computed root. - Given a single altered attachment, When integrity verification runs, Then the response identifies the mismatching attachment by attachment_id/index and includes a minimal Merkle proof showing the path to the root. - Given an event with zero attachments, When building the Merkle root, Then the root equals the SHA-256 of the empty byte array: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855. - Given multiple attachments, When ordering leaves, Then ordering is deterministic by ascending attachment_id to ensure consistent Merkle roots across runs.
Append-Only Event Store, Replication, and Background Verification
- Given an append write, When committed, Then the write is durably replicated to at least two independent nodes/availability zones before acknowledgment. - Given any attempt to update or delete stored events directly, When attempted, Then the store denies the operation and records an audit log entry; no data is modified. - Given the hourly background verifier, When it scans chains, Then it recomputes event hashes, previous_hash links, and Merkle roots and flags any inconsistency with chain_id, first_bad_event_index, and timestamp. - Given a crash or power loss during append, When recovering, Then partial writes are not visible and the chain remains valid (event is either fully committed or absent).
One-Click Integrity Check and Tamper Highlighting
- Given a work order chain, When the user clicks "Verify Integrity" or calls GET /chains/{id}/verify, Then the system validates all event hashes, linkages, and Merkle roots and returns {valid, head_hash, event_count, verified_at}. - Given a tampered historical event, When verification runs, Then valid=false and the response includes first_bad_event_index with expected_prev_hash and actual_prev_hash for diagnosis. - Given an altered attachment, When verification runs, Then the response includes attachment_id/index and a Merkle proof demonstrating the mismatch. - Given a chain with ≤1000 events and ≤500 total attachments, When verification runs, Then it completes in ≤2 seconds at p95 latency.
Automatic Chaining Across FixFlow Workflows
- Given tenant intake with photo-first submission, When the request is completed, Then a chained event is created that includes photo digests in its Merkle root without any extra user action. - Given triage decisions, scheduling assignments, approvals, and tenant communications, When performed through FixFlow, Then each action emits a chained event with correct previous_hash linkage. - Given a temporary network outage during actions, When connectivity is restored, Then queued events are appended in causal order within 5 minutes and without gaps in previous_hash values. - Given repeated append failures after 3 retries, When they occur, Then the system alerts operators and surfaces an error banner within the affected work order UI.
Append and Read APIs with Idempotency and Pagination
- Given POST /chains/{work_order_id}/events with body {canonical_payload, previous_hash, merkle_root, idempotency_key}, When first submitted, Then the API returns 201 Created with {event_hash, index, head_hash}; when resubmitted with the same idempotency_key, Then it returns 200 OK with the identical result and no duplicate event. - Given GET /chains/{work_order_id}/events?limit=L&cursor=C, When called, Then events are returned in ascending index order with pagination cursors, and the response includes head_hash and total_count. - Given a client requesting JSON, When responses are returned, Then event payloads are emitted in canonical JSON (RFC 8785) and include their event_hash. - Given an unauthorized or unauthenticated caller, When invoking append or read APIs, Then the request is rejected with 401/403 and is recorded in access logs.
Trusted Timestamping & External Anchoring
"As a landlord, I want trusted timestamps and external anchors for my maintenance records so that insurers and auditors accept the evidence without question."
Description

Attach trusted timestamps to each chained event using multi-source time validation (e.g., NTP quorum) and periodically anchor chain roots (e.g., daily per account) to an external, verifiable ledger (public blockchain or RFC 3161 TSA) to provide independent proof of existence and ordering. Handle clock drift and offline scenarios with queued anchors and retries. Store anchor receipts and verification metadata alongside chain state, and surface anchor status in the UI and API. Outcome: third parties can independently verify when records existed, strengthening audit, insurance, and dispute outcomes.

Acceptance Criteria
Event Timestamp Quorum Assignment
Given a new maintenance chain event is created When the system queries at least 3 distinct trusted time sources Then the event timestamp is set to the median of responding sources within ±2 seconds tolerance in RFC 3339 UTC And the decision metadata records each source, response latency, offset, included/excluded status, and quorum size And if fewer than 3 sources respond, the event is tagged "degraded-quorum" and queued for post-hoc validation within 15 minutes And any source deviating >5 seconds from the median is excluded and flagged in metadata
Clock Drift Detection and Adjustment
Given the host clock deviates by more than 2 seconds from the current time quorum When an event is captured Then the event timestamp uses quorum time and is flagged "drift-adjusted" with recorded offset And a drift audit entry is created including local time, quorum time, offset, and detection method And a monitoring alert is emitted within 60 seconds And event ordering is preserved using monotonic sequence numbers even under drift
Daily External Anchor to Public Ledger
Given an account has events within a UTC day When the day ends at 00:00:00 UTC Then a deterministic Merkle root for that day's events is computed within 15 minutes And an anchor is submitted to the configured external ledger (public blockchain or RFC 3161 TSA) within 2 hours And upon success the receipt (e.g., txid/block hash or TSA token) and verification metadata are stored immutably And on failure the system retries with exponential backoff for up to 72 hours total with idempotent submissions And anchor status transitions to "Anchored" on success or "Failed" after retries exhausted
Offline Capture and Queued Anchoring
Given the service is offline when events are recorded When connectivity is restored Then events are reconciled against a fresh time quorum and assigned adjusted timestamps that preserve original monotonic order And missed daily Merkle roots are computed and anchored sequentially without gaps And all pending anchors are submitted in order and receipts are stored and linked to their day roots And events captured offline are flagged with an "offline-reconciled" indicator in metadata
UI and API Anchor Status Surfacing
Given an account with events and day roots When a user opens the dashboard or calls the /anchors API Then each day root shows status in [Pending, Anchored, Failed, Degraded] with last-updated time And each event shows timestamp status in [Trusted, Drift-Adjusted, Degraded-Quorum, Offline-Reconciled] And API responses include anchor receipts (txid/block hash or TSA token), verification endpoints, and root hash And users can filter by status and export CSV/JSON including these fields
Independent Verification Bundle and Validation
Given a verifier downloads a verification bundle for a date range When they run the provided CLI or call the /verify endpoint with public ledger access Then the tool reconstructs Merkle proofs, validates event ordering and timestamps, and verifies anchors against the external ledger And on success returns PASS with anchored block height or TSA serial and verification timestamps And on tampering returns FAIL and pinpoints the first mismatching hash and affected event IDs And the verification process completes within 60 seconds for up to 10,000 events
One-Click Integrity Check Including Anchors
Given a user runs Integrity Check for an account When the system executes the check Then it validates hash-chain continuity across selected events and verifies each day's Merkle root against stored receipts and the external ledger And the report lists any missing anchors, mismatched receipts, or timestamp quorum failures with severity levels and remediation hints And the check produces a signed report artifact including inputs, versions, and summary result (PASS/FAIL)
One‑Click Integrity Check & Verification Report
"As a property manager, I want a one‑click integrity check with a shareable report so that I can quickly validate a record during disputes or audits."
Description

Provide a single‑action control in each work order that recomputes all hashes, verifies the chain’s continuity, checks timestamp anchors, and returns a clear pass/fail with the earliest point of divergence. Generate a downloadable, human‑readable report (and machine‑readable JSON) detailing verification steps, algorithms, versions, anchor receipts, and results. Ensure checks complete quickly on typical ticket sizes and scale for larger chains via incremental verification. Outcome: users can instantly validate integrity and share defensible evidence.

Acceptance Criteria
Single-Click Integrity Check on Work Order
Given a user views a work order containing up to 200 chained entries and the chain has no tampering When the user clicks the "Verify Integrity" control Then the system recomputes all entry hashes, verifies chain continuity, and validates all configured timestamp anchors And the verification returns a Pass result within 2 seconds at p95 for N ≤ 200 entries And the UI shows a Pass badge with verification duration, ChainSeal version, hash algorithm, and total entries
Earliest Divergence Identification on Failure
Given a work order where any entry’s content, order, or linkage hash has been altered since the last valid write When the user runs the integrity check Then the result is Fail And the earliest point of divergence is displayed with position (1-based), entry ID, entry type, timestamp, expected cumulative hash, and actual cumulative hash And the divergent entry is highlighted in the UI and all downstream entries are marked Untrusted And for structured entries, changed field names are listed; for binary attachments, the filename and content digest mismatch are shown
Human-Readable Verification Report Download
Given a completed verification (Pass or Fail) When the user clicks "Download Report" Then a PDF is generated within 3 seconds containing: work order ID, verification ID, run timestamp (UTC), ChainSeal version, hash algorithm, entry count, root hash, anchor receipts and results, overall result, earliest divergence (if any), and step-by-step checks performed with timings And the PDF filename includes the work order ID and run timestamp And the PDF content matches the on-screen result exactly
Machine-Readable JSON Verification Report
Given a completed verification When the user requests the JSON report Then a JSON file conforming to the public JSON Schema version X.Y is returned with properties: verificationId, workOrderId, runAtUtc, chainSealVersion, hashAlgorithm, entryCount, rootHash, result, earliestDivergence (nullable), anchors[], entries[] And anchors[].status is one of Valid, Invalid, Unreachable; each anchor includes provider, receiptId, anchoredAt, proofHash And entries[] includes position, entryId, type, timestamp, cumulativeHash, verificationStatus And the JSON validates against the schema and is byte-for-byte identical for the same input across runs
Timestamp Anchor Validation
Given a work order with one or more external timestamp anchors configured for the chain or checkpoints When the integrity check runs Then each anchor receipt is cryptographically verified against its provider and the local chain state And any Invalid anchor causes the overall result to be Fail with code ANCHOR_INVALID; Unreachable anchors cause Fail with code ANCHOR_UNVERIFIED And the report lists each anchor with provider, receiptId, verification status, and diagnostic message
Incremental Verification for Large Chains
Given a previously verified chain with a stored checkpoint at position K and cumulativeHashK And M new entries have been appended after K When the integrity check runs Then the system reuses the checkpoint to verify only the boundary at K and the M new entries And the verification completes within 3 seconds at p95 for M ≤ 1,000 entries And the reported root hash and result are identical to a full from-scratch verification
Concurrency, Idempotency, and Audit Logging
Given a user clicks "Verify Integrity" multiple times while a verification is in progress When subsequent clicks occur within the running window Then no duplicate jobs are started; the UI shows the existing run’s progress And rerunning verification on an unchanged chain yields the same result and root hash And the verification action is logged with user ID, work order ID, start/end timestamps, result, and runtime
Granular Tamper Localization & Diff Highlighting
"As an auditor, I want the system to highlight exactly which field or file changed so that I can assess the scope of tampering quickly."
Description

Use canonical field ordering and per‑field Merkle leaves to pinpoint exactly which field or attachment breaks integrity when a mismatch is found. For structured data, present field‑level diffs; for attachments, show which file’s digest diverges and provide metadata (filename, size, MIME). Present discrepancies inline in the work order timeline and in exported reports, minimizing investigation time. Outcome: users can see precisely where a change occurred without manual forensics.

Acceptance Criteria
One-Click Check Localizes Tampered Field
Given a sealed work order with stored Merkle root and per-field leaves When exactly one structured field (e.g., details.priority) is altered post-seal and the user clicks "Verify Integrity" Then the system returns "Integrity Mismatch", identifies the specific field by key path, and displays a field-level diff with old and new values And shows expected vs computed leaf hash for the changed field and confirms all other fields as "Unchanged" And completes the verification within 2 seconds for records up to 100 fields and 20 attachments
Structured Field-Level Diff Rendering
Given structured fields of type string, number, date/time, enum, array, and object When differences are detected Then diffs render as: strings show character-level highlights with 10-character context; numbers/dates show before→after with normalized comparisons; enums show from→to labels; arrays list added/removed/reordered items with indices; objects surface nested key paths And masked/redacted fields only indicate "value changed (redacted)" without exposing sensitive data And diffs are visible in both the timeline inline view and the modal diff view with identical content
Attachment Digest Mismatch With Metadata
Given sealed attachments A.jpg and B.pdf on a work order When A.jpg is modified or replaced and integrity verification runs Then A.jpg is flagged "Digest Mismatch" and displays filename, MIME type, stored size vs current size, and expected vs computed digest with algorithm label And B.pdf is marked "Unchanged" And the corresponding timeline event where A.jpg was added/modified also displays the mismatch summary inline
Inline Timeline Discrepancy Indicators
Given a work order timeline with multiple events When any tamper is detected Then an inline discrepancy banner appears at the exact event(s) where divergence begins And clicking the banner expands the per-field and per-attachment diffs inline within one interaction And badges display counts of affected fields and attachments And all interactive elements meet WCAG 2.1 AA for focus order, labels, and keyboard operation
Exported Reports Highlight Tamper Points
Given the user exports the work order as PDF and CSV When discrepancies exist Then the export includes a Tamper Summary listing each affected field key path and/or attachment filename with before/after values or metadata and links to the corresponding event page/row And cryptographic metadata (hash algorithm, Merkle root, verification timestamp, signer) is included And the export output is deterministic for identical inputs and completes within 5 seconds for up to 100 timeline events and 20 attachments
Canonical Ordering and Cross-Environment Determinism
Given the same logical work order serialized across different locales and runtimes When Merkle leaves and the root are computed Then leaf ordering follows canonical key-path lexicographic order (UTF-8, case-sensitive) with stable array index ordering And value serialization is canonical (ISO 8601 for timestamps; exact byte sequence for strings/binaries; explicit number formatting) without mutating stored values And the same record yields identical per-field leaf hashes and Merkle root across environments And changing one field alters only its leaf hash and the hashes along its Merkle path; unaffected leaves remain unchanged
Clean Integrity Pass Without False Positives
Given an unmodified sealed work order When the user runs "Verify Integrity" Then the system returns "Integrity Verified" with zero flagged fields and zero flagged attachments And a green verification indicator and timestamp are shown in the timeline and can be exported And the result is cached and reused for 24 hours unless the record changes
Cryptographic Approvals & Chain‑of‑Custody Signatures
"As an approver, I want my approvals to be cryptographically signed and bound to the ledger so that no one can dispute my authorization later."
Description

Require digital signatures for approvals and critical state transitions using managed key pairs (e.g., ECDSA). Bind signer identity, role, and context metadata (IP, user agent, timestamp) to the event prior to hashing, enabling non‑repudiation and chain‑of‑custody. Provide secure key storage for system keys and support user‑held keys or OTP‑based ephemeral signing. Expose signature verification in UI, reports, and APIs. Outcome: approvals are provably made by specific users, strengthening dispute resilience.

Acceptance Criteria
Manager Approval ECDSA Signature and Metadata Binding
Given a pending approval for a work order and a manager with an active managed ECDSA P-256 key pair, When the manager clicks Approve, Then the system constructs a deterministic canonical payload that includes event_id, event_type, workflow_transition_from, workflow_transition_to, actor_id, actor_display_name, actor_role, actor_email_hash (SHA-256), actor_ip, actor_user_agent, tenant_id, unit_id, request_id, approval_reason, server_timestamp_utc (ISO 8601), app_version, and a cryptographic nonce. Given the canonical payload, When hashing, Then the system computes event_hash = SHA-256(canonical_payload) and generates an ECDSA P-256 signature over event_hash using the manager’s private key. Given signature generation succeeds, Then the system persists signature, signer_public_key_fingerprint (SHA-256), key_id, and verification_materials alongside the event, and chains event_hash into the ledger as the next link. Given the approval is saved, Then the UI displays "Signed by <name> (<role>) at <UTC timestamp>" with a Verify action that returns verified=true and matches signer identity and role to the account used.
Technician Critical Status Transition Requires Valid Signature
Given critical transitions are configured as [Work Started, Completed, Cost Override], When a technician attempts any critical transition without providing a signature, Then the API rejects the request with HTTP 403 and error_code=SIGNATURE_REQUIRED and the workflow state remains unchanged. Given a technician attempts a critical transition with a signature over the canonical payload (including nonce and server_timestamp_utc), When the signature is valid and within a 5-minute TTL, Then the transition is accepted, the event is chained, and the response returns verified=true with the resulting event_id and event_hash. Given replay attempts, When the same nonce is reused within TTL, Then the API rejects the request with HTTP 409 and error_code=REPLAY_DETECTED and no event is written.
Tenant OTP Ephemeral Signing for One-Time Consent
Given a tenant elects OTP-based signing, When the tenant requests an OTP, Then the system sends a 6-digit OTP via the selected channel (SMS or Email), marks the request with otp_channel and masked_destination, and prepares an ephemeral ECDSA P-256 key inside a non-exportable KMS context. Given the tenant enters the correct OTP within 10 minutes and ≤5 attempts, When the tenant confirms consent, Then the server computes event_hash for the canonical payload with tenant context and signs with the ephemeral key; the private key is destroyed immediately after signing; the record stores signer_public_key_fingerprint, key_id_ephemeral=true, otp_channel, masked_destination, and verified=true. Given OTP is expired or attempts exceeded, When the tenant submits, Then the API returns HTTP 401 with error_code=OTP_INVALID_OR_EXPIRED and no event is chained.
Signature Verification in UI, Reports, and APIs
Given a signed event exists, When a user clicks Verify in the UI, Then the system recomputes the canonical payload and event_hash, verifies the signature with the stored public key material, and returns verified=true within 300 ms P95; on failure, verified=false with reason in [INVALID_SIGNATURE, KEY_MISMATCH, PAYLOAD_TAMPERED, UNSIGNED_EVENT]. Given report generation (PDF/CSV), When a report includes signed events, Then each event row contains verification_status, signer_display, role, timestamp_utc, fingerprint, and a deep link; the deep link returns JSON {verified, reason, signer_id, role, fingerprint, timestamp_utc, event_hash} via GET /v1/events/{id}/verify. Given an unsigned event, When a verify request is made, Then the API returns HTTP 400 with error_code=UNSIGNED_EVENT and verified=false.
Tamper Detection with Field-Level Delta Highlight
Given any signed event, When any bound field (e.g., approval_reason, workflow_transition_to, actor_role, server_timestamp_utc, actor_ip, actor_user_agent) is modified after signing, Then verification fails (verified=false, reason=PAYLOAD_TAMPERED) and the UI Verify view highlights field-level differences using JSON Pointers with before_value and after_value for each changed path. Given a valid, unmodified event, When Verify is run, Then no differences are shown and verified=true; chain position and previous_hash match the ledger sequence.
Secure Managed Key Storage, Rotation, and Audit Controls
Given system-managed signing keys, Then private keys are generated and stored in a FIPS 140-2/3 validated HSM/KMS with non-exportable policy; key access requires least-privilege service identity and MFA for administrative actions, all accesses are auditable with actor_id, action, and server_timestamp_utc. Given key rotation is initiated by a Security Admin, When rotation completes, Then new signatures use the new key_id and prior signatures remain verifiable with retained public keys; an immutable audit event records who rotated, when, and why. Given steady-state signing load, When approvals are signed, Then signing latency added by the platform is ≤150 ms P95 and ≤300 ms P99 as measured at the API boundary.
Audit Package Export & Offline Verifier
"As an insurance adjuster, I want a portable audit package I can verify offline so that I can validate evidence without relying on FixFlow’s systems."
Description

Generate a self‑contained export (ZIP) for any work order or date range including the ledger events, attachment digests, chain manifest, anchor receipts, verification report, and a lightweight offline verifier tool with instructions. Support redaction options for PII and configurable retention of media vs. hashes. Ensure package integrity with a top‑level manifest signature and versioned schema for forward compatibility. Outcome: third parties can independently verify integrity without FixFlow access.

Acceptance Criteria
One-Click Export for Single Work Order
Given a work order with ChainSeal Ledger data and attachments And the user selects "Export Audit Package" When the export completes Then the ZIP contains: /manifest.json, /manifest.sig, /ledger/events.jsonl, /attachments/digests.json, /anchors/receipts.json, /reports/verification.json, /verifier/*, /README.txt And all file digests listed in manifest.json match contents using SHA-256 And verification.json status is "PASS" And the package can be verified end-to-end by running the included verifier without network access
Date-Range Export at Scale
Given a date range selection producing ≥10,000 events and ≥2,000 attachments totaling ≥3 GB When export is initiated Then the export completes within 15 minutes on reference hardware (8 vCPU, 16 GB RAM, SSD) And peak exporter memory usage remains ≤2 GB And the ZIP structure matches the single-work-order layout with batched event files (events-*.jsonl) And the UI displays progress percentage and emits an error if disk space is insufficient
PII Redaction Controls and Audit Log
Given the user enables "Redact PII" When the export runs Then resident full name, phone numbers, email addresses, exact unit numbers, PII entities in free-text notes, and photo EXIF GPS are removed or masked in all outputs And a redaction_report.json lists each redacted field type with counts and file paths And unredacted values are absent from all files in the ZIP And when "Redact PII" is disabled, no fields are redacted
Media Retention Configuration (Full, Thumbnails, Hashes-Only)
Given the user selects a retention mode: Full media, Thumbnails, or Hashes-only When the export runs Then Full media includes all original attachments under /attachments/media with matching SHA-256 digests And Thumbnails includes only derived images ≤1280px longest side and ≤200 KB each, with source digests preserved in digests.json and media_excluded=true And Hashes-only omits all media files and includes only digests.json; verifier passes by validating recorded digests against the ledger And verification.json records retention_mode and media_counts consistent with the selection
Top-Level Manifest Signature and Schema Versioning
Given an export is generated Then manifest.json includes schema_version (SemVer), package_id, created_at, and retention_mode And manifest.sig contains an Ed25519 signature over manifest.json And verifier/public_keys.json contains the required public key(s) and key_id When the verifier runs Then a valid manifest signature passes; tampering with manifest.json causes signature verification to fail with exit code 2 And an unknown minor schema version yields a warning while proceeding; an unknown major version yields failure with exit code 3
Offline Verifier Usability and Outputs
Given the ZIP is extracted on Windows 11, macOS 13+, or Ubuntu 22.04 without internet access When the user runs the included verifier via the provided script/binary Then the tool executes without additional installation, performs all checks offline, and exits 0 on success And it writes a human-readable summary to stdout and a JSON report to /reports/verification.json And README.txt provides ≤12-step instructions with OS-specific commands and expected exit codes
Tamper Detection and Precise Diff Reporting
Given any file in the package is altered, removed, or added after export When the verifier is run Then it fails with a non-zero exit code and identifies the exact artifact path and mismatch type And for ledger event tampering, the report includes the event index and a JSON pointer to the changed field And for media tampering, the report includes the attachment id and expected vs actual SHA-256 digest And the report highlights the earliest broken link in the cryptographic chain
Legacy Record Backfill & Migration Markers
"As a small property manager, I want my existing tickets added to the tamper‑evident ledger so that I have a single, consistent audit trail going forward."
Description

Provide a migration utility to ledgerize existing maintenance records by creating a genesis event from the current snapshot, clearly marking it as a backfill without historical anchors. Chain all subsequent events normally. Display migration markers in UI and reports to avoid implying pre‑migration immutability. Throttle and schedule backfill jobs to avoid impact on live operations, and expose progress and error handling. Outcome: customers get a unified, honest chain for historic and new records.

Acceptance Criteria
Genesis Backfill Event Without Historical Anchors
Given a legacy maintenance record with a current snapshot S; When the migration utility runs; Then a single genesis ledger event is created with backfill=true, parentHash=null, migrationBatchId present, recordId preserved, and a deterministic hash derived from S. And Then the genesis event timestamp equals the migration execution time in UTC ISO 8601 and does not reuse any historical timestamps. And Given the same record is presented for migration again; When the utility detects an existing genesis for that record; Then it performs no-op and returns idempotent=true without creating a duplicate genesis.
Normal Chaining of Post-Migration Events
Given a migrated record with genesis event G; When a new maintenance event E is recorded post-migration; Then E.parentHash equals G.hash (or the hash of the latest prior event), backfill=false is set, and the chain validates end-to-end. And When multiple subsequent events E1..En are appended; Then one-click integrity check passes with a contiguous chain from G through En. And When an event is submitted with a timestamp earlier than G; Then the system rejects it with validation error code LEDGER_ORD_001 and no event is written.
UI and Report Migration Markers
Given a migrated record; When viewing the timeline UI; Then the genesis event displays a visible Backfilled badge and a tooltip stating "Created by migration; pre-migration history not available". And When exporting reports (PDF/CSV/JSON) or retrieving via API; Then genesis events include backfill=true and migrationBatchId fields, and reports include a header note "Chain begins at migration" for those records. And When running one-click integrity check in the UI; Then the result banner explicitly states "Chain begins with migrated genesis" and does not imply verification prior to genesis.
Backfill Scheduler and Throttling
Given an admin schedules a backfill batch for N records with maxConcurrency=C and blackout windows configured; When the job runs; Then effective concurrency never exceeds C and the job pauses during blackout windows and resumes automatically afterward. And While the job is active; Then p95 latency for live tenant operations increases by no more than 10% compared to the 15-minute pre-job baseline, otherwise the job self-throttles until within threshold. And When pause or cancel is requested; Then the job halts within 60 seconds and persists checkpoint state to enable exact-once resume.
Progress Visibility and Error Handling
Given a running backfill batch; When querying job status via UI or API; Then it returns totals, processed, succeeded, failed, in-progress counts; percent complete; ETA; throughput (records/min); last-updated timestamp (<=15s old). And When a record fails with a recoverable error; Then the system retries up to 3 times with exponential backoff starting at 2s; on final failure, it quarantines the record with error code, redacted message, and attachable diagnostics. And When an operator selects Retry failed; Then only quarantined items are retried idempotently without duplicating successful genesis events.
Auditability and Integrity Checks for Migrated Records
Given a completed migration; When exporting an audit trail for a migrated record; Then it includes operator identity, timestamp, tool version, migrationBatchId, genesis hash, and a signed job summary. And Given any event in the chain is tampered post-write; When one-click integrity check runs; Then it fails, identifies the first failing index, and highlights the exact fields whose hashes differ. And For migrated records; When integrity check runs; Then it verifies continuity from genesis forward and explicitly excludes any claim of verification before genesis.

MetaProof Capture

Auto‑locks trusted metadata to each upload—server timestamp, user ID, device fingerprint, and optional geofence match. Flags stripped EXIF or stale photos and requests a live retake via magic link. Proves when and where evidence was captured, reducing back‑and‑forth and challenge risk.

Requirements

Immutable Metadata Binding
"As a property manager, I want each tenant upload to be automatically sealed with trusted metadata so that I can act on evidence confidently without disputes about when or by whom it was captured."
Description

Automatically bind trusted metadata to every media upload at receipt: server-side timestamp, authenticated user ID, account and work-order IDs, device fingerprint, content hash, and signature. Preserve raw client EXIF but never trust it over server truth. Store the metadata and cryptographic hash in an append-only store with versioning to ensure immutability and tamper-evidence. Expose this metadata via internal APIs and the UI, link it to the originating maintenance request, and prevent post-hoc modification while allowing additive annotations. Handle failure modes (clock skew, partial uploads, retries) and ensure idempotency for duplicate submissions. This provides non-repudiation, shortens disputes, and underpins downstream verification flows.

Acceptance Criteria
Canonical metadata bound at upload receipt
Given an authenticated user uploads media to a specific work order and the upload completes successfully When the server receives the final byte and commits the file Then the system creates a metadata record with canonical fields: upload_id, server_received_at (UTC, ms), authenticated_user_id, account_id, work_order_id, device_fingerprint, content_sha256, content_signature, key_id, storage_location, version=1 And no client-supplied timestamp or EXIF value is written to any canonical field And content_sha256 is computed over the exact stored bytes and is reproducible by re-download and re-hash And content_signature is computed over {upload_id, content_sha256, server_received_at, authenticated_user_id, account_id, work_order_id, device_fingerprint} using the current server signing key and recorded with key_id And the metadata record and file are persisted atomically; a failure to persist either results in no record and a non-2xx response
Immutable append-only store with versioning
Given a metadata record exists When a client attempts to update any canonical field via API Then the request is rejected with HTTP 403 and no mutation occurs When an authorized user adds an annotation {key, value} Then a new version is created (version = previous + 1) with an append-only diff containing the annotation and a new chain_hash referencing the prior version; canonical fields remain unchanged When fetching the record Then the API returns the latest version plus an ordered version_history with checksums/chain_hash for each version When any stored version fails chain verification Then the record is flagged tampered=true on read and the API returns HTTP 409 on attempts to modify
EXIF preserved but not trusted
Given a photo with EXIF metadata including DateTimeOriginal and GPS When the upload completes Then exif_raw is stored verbatim and exif_status='present' and no EXIF field populates canonical timestamp or location Given EXIF DateTimeOriginal differs from server_received_at by any amount When the metadata is viewed via API or UI Then server_received_at remains unchanged and EXIF values are labeled untrusted Given a photo with no EXIF or stripped EXIF When the upload completes Then exif_status='missing' is recorded and the upload is accepted
Idempotent handling of duplicates and retries
Given the same client retries the same upload with identical content and the same idempotency key When the server processes the request Then the response is 200 and returns the original upload_id; no new record is created and version remains 1 Given identical content is uploaded to a different work_order_id When the server processes the request Then a new upload_id is created and both records link to their respective work orders Given two identical requests arrive concurrently for the same work_order_id and idempotency key When processed by separate nodes Then exactly one metadata record is created and all responses reference that record
Metadata exposure via API and UI
Given a metadata record exists When GET /internal/uploads/{upload_id}/metadata is called by an authorized service Then the response is 200 JSON including: upload_id, server_received_at, authenticated_user_id, account_id, work_order_id, device_fingerprint, content_sha256, content_signature, key_id, exif_raw, version, version_history, chain_hash, annotations[], tampered, links.self, links.work_order And all date-times are ISO 8601 UTC with millisecond precision Given a user opens the associated work order in the UI and selects the media item When the Metadata panel is displayed Then canonical fields are visible and read-only with an "Immutable" badge; a copy action is available for content_sha256; and a link navigates to the work order
Additive annotations with audit and access control
Given an authorized user adds an annotation to a metadata record When they submit {key, value} Then annotation_id, key, value, added_by_user_id, added_at (UTC, ms) are recorded and version increments by 1 Given an unauthorized user attempts to add or delete an annotation When the request is made Then the API returns HTTP 403 and no changes occur Given a user attempts to edit or delete an existing annotation When the request is made Then the API rejects the request with HTTP 405 and only additive annotations are permitted Given annotations are fetched When GET /internal/uploads/{upload_id}/metadata is called Then annotations are returned in chronological order with full audit fields
Failure handling: partial uploads, clock skew, hashing errors
Given a partial upload fails before final byte commit When the client abandons or retries Then no finalized metadata record is exposed via API/UI and temporary state is cleaned; upon successful resume exactly one record is finalized and bound Given server clock skew across nodes When uploads are processed on different nodes Then server_received_at is monotonic per upload and cross-node skew is ≤50ms in tests Given content hashing or signature creation fails during finalization When the server processes the upload Then the operation fails with a non-2xx response, the file is not linked to a work order, and no metadata record is created Given a network retry occurs after the server persisted the record but the client missed the response When the request is retried with the same idempotency key Then the server returns the existing upload_id without creating a duplicate
Device Fingerprint and Trust Scoring
"As a landlord, I want to know whether a submission comes from a known device for this unit so that I can detect anomalies and prioritize credible evidence."
Description

Generate a privacy-preserving device fingerprint on capture (browser characteristics, OS, SDK identifiers where permitted) to associate uploads with a pseudonymous device ID. Maintain a device history and compute a simple trust score based on consistency signals (repeat use on the same property, low anomaly rate, successful geofence matches) while minimizing personal data collection. Surface device continuity and trust indicators in the work-order view and expose them via API for automation rules. Provide controls to reset or revoke device trust and handle users with multiple devices gracefully. Degrade functionality when fingerprinting is unavailable without blocking uploads.

Acceptance Criteria
Pseudonymous Device Fingerprint on Capture
- Given a user initiates a capture or upload, When the capture screen loads, Then the system generates a device fingerprint from allowed signals (UA, platform, rendering engine, timezone, screen metrics, language, and permitted SDK identifiers) and stores only a pseudonymous device_id with no raw PII. - Given the same physical device with unchanged primary signals, When two uploads occur 1–90 days apart, Then the computed device_id is identical. - Given two different physical devices, When both perform uploads in the same time window, Then their device_id values differ. - Given a minor browser/OS version update on the same device, When the next upload occurs, Then the device_id remains stable with a stability rate ≥ 95% in the test cohort.
Device Trust Score Computation and Update
- Rule: Trust score ranges 0–100 and is recalculated on each upload. - Rule: +15 for successful geofence match; +10 for repeat use on the same property within 60 days (max +20); −25 for an anomaly (e.g., timezone shift > 6 hours and locale change within 24 hours); floor 0, cap 100. - Given a device with no history, When its first upload is recorded, Then trust_score=50 and trust_level="Medium". - Given three uploads to the same property within 60 days with at least two geofence matches and no anomalies, When the third upload is saved, Then trust_score ≥ 85 and trust_level="High". - Given an anomaly is detected on the latest upload, When the upload completes, Then trust_score decreases by ≥ 25 points and trust_level updates within 1 second.
Work-Order UI Shows Device Continuity and Trust
- Given a work order with associated uploads, When the work-order view is opened, Then each upload displays device_id (short hash), trust_level badge (Low/Medium/High), trust_score, last_seen_at, geofence_match indicator, and anomaly flag. - Given ≥ 2 uploads from the same device_id, When the view loads, Then the UI groups them under a "Same device" label and shows the count. - Given an upload from a new device_id, When the view loads, Then the UI labels it "New device" for 24 hours. - Performance: When loading the work-order view, Then device indicators render within 300 ms after data fetch at the 95th percentile. - Accessibility: When navigating via screen reader, Then all indicators have accessible names and color contrast meeting WCAG AA.
API Exposes Device Indicators for Automation
- Given a client requests GET /api/v1/work_orders/{id}/uploads, When the response returns, Then each upload object includes device_id, trust_score, trust_level, device_use_count, last_seen_at, geofence_match (boolean), anomaly_count, device_revoked (boolean), and fingerprint_available (boolean). - Given an automation rule filters for trust_level="High" and geofence_match=true, When applied to the API response, Then the correct subset of uploads is selectable deterministically. - Given a trust update occurs after an upload, When the next API call is made, Then updated trust_score and trust_level are reflected within 2 seconds. - Security: When inspecting API payloads, Then no raw fingerprint attributes or PII are present; device_id is non-reversible.
Admin Reset/Revoke Trust and Multi-Device Handling
- Given an admin opens a device detail from a work order, When "Reset trust" is confirmed, Then trust_score resets to 50, trust_level="Medium", and an audit log entry records actor, timestamp, and reason. - Given an admin confirms "Revoke device", When future uploads from that device occur, Then device_revoked=true and trust_level="Low" until reinstated. - Given a user uploads from multiple devices under the same account, When the work-order view loads, Then each device is listed separately with its own device_id and trust indicators, and no upload is blocked. - Given a revoked device is reinstated by an admin, When the next upload occurs, Then device_revoked=false and trust_score recalculates from baseline on that upload.
Graceful Degradation Without Fingerprinting
- Given the environment blocks fingerprinting (e.g., strict privacy mode), When a user uploads, Then the upload completes and fingerprint_available=false is stored for that upload. - Given fingerprint_available=false on an upload, When trust indicators are shown, Then trust_level="Medium" and trust_score=50 with a visible "Unknown device" label. - Given fingerprint is unavailable for some uploads and later becomes available, When a new upload occurs with fingerprinting, Then a device_id is assigned starting with that upload without retroactively inferring prior device_ids. - Given intermittent fingerprint availability in a session, When multiple uploads occur, Then no inconsistent device_id is assigned to any upload; missing fingerprints remain null.
Privacy and Data Minimization Compliance
- Rule: No storage of raw hardware identifiers (IMEI, MAC); only hashed/peppered aggregates of non-PII signals and permitted SDK IDs with explicit consent. - Rule: Pepper rotation is supported; rotating secrets does not expose prior device_ids and prior hashes remain referenceable for 90 days. - Given a user requests a data export, When device-related data is generated, Then only device_id, trust history (score, level), timestamps, and event types are included—no raw signals or PII. - Given a retention policy of 18 months, When the retention job runs, Then any cached raw signal fragments are purged while preserving device_id references and audit logs per policy.
Geofence Verification and Policy Rules
"As a property manager, I want each upload to indicate whether it was captured at the property so that I can prioritize on-site evidence and reduce site visits triggered by off-site photos."
Description

Allow admins to define geofences per property (address-based radius and optional polygon). On upload, request location permission and perform server-side geofence matching; when not granted, fall back to coarse IP-based estimation and mark confidence. Tag uploads as inside, outside, or unknown with distance and confidence, and store the raw reading with time. Provide policy settings to warn, auto-request retake, or allow with note based on results. Display geofence status in the request UI and include it in notifications and APIs. Implement spoofing checks and rate limits, and ensure secure storage of coordinates with least-privilege access.

Acceptance Criteria
Admin defines geofence per property
Given an admin with Manage Properties permission When they create or edit a property's geofence with an address, a radius, and optionally a polygon Then the address is geocoded to lat/long and saved; radius must be between 25 and 2000 meters; polygons must be 3–50 vertices, WGS84, non-self-intersecting And if both radius and polygon exist, the system stores both and uses the polygon for containment checks by default And invalid inputs show field-level errors and are not persisted And the save is versioned with createdBy/updatedBy, timestamps, and an audit log entry
GPS-granted upload tagged inside/outside with confidence
Given a tenant starts an upload and grants precise location When the server receives a location reading with lat/long, accuracy (meters), method=GPS, and readingTime Then the system computes distance to the nearest geofence boundary and tags status as inside, outside, or unknown using the configured geofence And it assigns confidence as High (GPS; accuracy ≤ 50m; readingTime ≤ 2 minutes), Medium (GPS; accuracy 51–200m or readingTime ≤ 5 minutes), or Low (otherwise) And raw reading (lat, long, accuracy, method, readingTime) is stored with the upload And the tag includes fields: status, distanceMeters (integer), confidence, matchedGeofenceId, computedAt
Location denied fallback IP estimation
Given a tenant starts an upload and denies location permission When the server performs IP-based geolocation Then the system tags the upload with method=IP, confidence=Low, and status inside/outside/unknown based on IP centroid vs geofence And the tag includes distanceMeters when resolvable, else null, and reason=LocationDenied And the UI presents a location warning to the tenant indicating low confidence and suggesting a live retake
Policy-driven actions for geofence outcomes
Given a property's geofence policy is configured with actions for inside, outside, and unknown When an upload is tagged by geofence matching Then if status=outside and distanceMeters ≥ 100, the system auto-requests a live retake via magic link and blocks submission until a retake is completed or an authorized admin overrides And if status=unknown, the system allows submission but requires a tenant note and flags the request for manager review And if status=inside (any confidence), the system allows submission without interruption And all actions are recorded in the request timeline and reflected in notifications
Geofence status shown in UI, notifications, and APIs
Given an upload has completed geofence processing When a manager or tenant views the maintenance request UI Then a status pill shows Inside/Outside/Unknown, distance in meters (if available), and confidence (High/Medium/Low) And outbound email/SMS notifications include the same status summary And the APIs expose: geofenceStatus, distanceMeters, confidence, method, readingTime, matchedGeofenceId, rawLocation (role-gated), and policyActionTaken
Spoofing detection and rate limiting
Given an upload includes or purports to include location data When the system detects mock-location providers, unrealistically high speed (>150 km/h between readings), or GPS jump anomalies (>500m in <5s) Then the upload is tagged spoofingSuspected=true, confidence is set to Low, and a retake is requested per policy And repeated retake attempts are rate-limited to 5 attempts per 10 minutes per user and per device; excess attempts return HTTP 429 and are logged with userId, deviceId, and IP
Secure storage and least-privilege access for coordinates
Given location data is persisted for geofence and uploads When the platform stores or serves this data Then coordinates are encrypted at rest and in transit, and only principals with Location.Read scope can access raw coordinates And all access is audited (who, when, purpose); non-production environments use redacted or fuzzed coordinates by default And PII exports include location fields only when explicitly requested by an authorized admin, and exports are watermarked and audited
EXIF Integrity and Staleness Detection
"As a maintenance coordinator, I want the system to flag old or edited photos so that I avoid scheduling work based on outdated or manipulated evidence."
Description

Inspect media headers on ingestion to detect stripped EXIF, mismatched creation timestamps, editing software markers, and camera-to-server time deltas. Compare capture time against request creation and configurable freshness thresholds to flag stale media. Attach machine-readable flags and human-readable warnings to the upload, and trigger downstream actions such as auto-sending a retake link. Log reasons and confidence scores for each flag to aid auditing and tuning. Never block uploads solely on client EXIF; use server truth for decisions.

Acceptance Criteria
Missing EXIF on Tenant Upload
Given a tenant uploads a photo or video with EXIF metadata fully missing or stripped When the file is ingested by MetaProof Capture Then the system sets exif_missing=true on the upload's machine flags And attaches a human-readable warning "Source metadata missing; using server timestamp" And logs reason_code="EXIF_MISSING", confidence>=0.95, and detector_version And the upload is accepted and stored; no client-side EXIF is required to complete ingestion And capture_time_source="server" with server_received_at recorded to millisecond precision And a retake magic link is generated and sent to the submitter within 60 seconds, unless disabled by policy And all actions are idempotent per upload_id
Stale Photo vs Request Creation Threshold
Given a maintenance request exists with creation_time=T_request and freshness_threshold_hours configured And an upload with resolved_capture_time=T_capture is ingested When T_request - T_capture > freshness_threshold_hours Then the system sets stale_media=true and staleness_hours=T_request - T_capture (rounded to 0.1h) And attaches a human-readable warning stating the photo is stale by staleness_hours and the configured threshold And logs reason_code="STALE_VS_REQUEST", confidence>=0.9, and threshold_hours And triggers a retake magic link within 60 seconds And the upload remains accepted and visible in the request thread And no stale_media flag is created when T_request - T_capture <= freshness_threshold_hours
Edited Image Software Marker Found
Given an image upload whose EXIF/XMP contains Software tags matching a configurable list of editing tools or composite markers When the file is ingested Then the system sets edited_marker_detected=true and records matched_software[] And logs reason_code="EDIT_SOFTWARE_MARKER", confidence>=0.9, matches, and detector_version And attaches a human-readable warning naming the detected software And the upload is accepted; server timestamp is used for subsequent decisions And if policy.edit_marker_requires_retake=true, a retake magic link is sent within 60 seconds
Camera-to-Server Time Delta Exceeds Threshold
Given an upload includes EXIF DateTimeOriginal=T_camera and server_received_at=T_server And max_camera_server_skew_minutes=M is configured When |T_server - T_camera| > M minutes Then the system sets time_delta_exceeds_threshold=true and records delta_minutes And logs reason_code="CAMERA_SERVER_DELTA", confidence>=0.95, M, and detector_version And capture_time_source="server" is enforced for all downstream comparisons And a human-readable warning explains the clock skew and that server time is used And the upload is accepted; no blocking occurs
Flags and Warnings Attached to Upload Record
Given an upload triggers one or more integrity conditions When the upload record is retrieved via API GET /uploads/{id} Then the response includes machine_flags[] with keys, reason_code, confidence (0..1), detector_version, and created_at And includes human_warnings[] with localized message keys and parameters And includes capture_time_source, resolved_capture_time, and all thresholds used And the same information is visible in the manager UI within 5 seconds of ingestion
Retake Magic Link Triggered by Policy
Given retake_trigger_rules evaluate true when any of [exif_missing, stale_media, time_delta_exceeds_threshold, edited_marker_detected] are present And a preferred delivery channel (SMS or email) is known for the submitter When an upload meets any trigger rule Then a unique magic link is generated with a single-use token valid for 24 hours And the link is delivered via the preferred channel within 60 seconds with delivery_status tracked (delivered, bounced, failed) And retake notifications are idempotent: at most one send per upload per 24 hours And an audit trail entry links upload_id, request_id, trigger_flags, and notification_id
Do Not Block; Server Truth Precedence
Given any integrity anomaly is detected from client-provided EXIF When the file is ingested Then the upload is accepted and stored regardless of client EXIF validity And all decisions requiring capture time use server_received_at unless a trusted capture time is independently verified And no workflow step is blocked solely due to client EXIF; flags and warnings guide downstream actions
Live Retake via Magic Link
"As a tenant, I want a simple link to retake photos live so that I can quickly provide valid evidence and move my request forward without back-and-forth."
Description

When verification flags occur or on-demand by staff, send a one-time, time-limited magic link via SMS/email that opens a focused capture flow enforcing live camera use (no gallery), with inline consent for location and metadata. Support retry windows, expiration, rate limiting, and device compatibility (mobile web and native). Embed request context to auto-associate the retake, prefill geofence, and capture device fingerprint and server timestamp. Provide localized UI, accessibility compliance, offline-aware fallback with delayed upload, and clear success/failure states. Track completion and update the work order in real time.

Acceptance Criteria
Auto-Triggered Magic Link on Verification Flag
Given an upload is flagged by MetaProof Capture for stripped EXIF or a stale timestamp beyond the configured threshold When the flag event is emitted Then a one-time magic link is generated and sent to the tenant via preferred channel(s) within 15 seconds And the link token is signed and encodes work order ID, contact ID, retake reason, and geofence parameters And the work order status updates to "Retake Requested" with an audit entry including server timestamp and event ID And no duplicate link is issued if an active, unexpired request already exists for the same work order-contact pair
Staff-Initiated Retake Request from Work Order
Given a staff user with permission opens a work order and clicks "Request Live Retake" When they select SMS and/or email and confirm Then the system generates and dispatches a magic link to the chosen channel(s) And the confirmation displays delivery status (queued/sent/failed) and a tracking ID And an audit log records staff ID, timestamp, channels, recipient, and token ID And messaging respects opt-out and channel availability; if SMS is unavailable, fallback to email is used
One-Time Redemption, Expiration, and Retry Limits
Given a magic link with TTL configured (default 30 minutes) and single-use policy When the recipient opens the link within TTL for the first time Then the token is marked redeemed and the capture flow loads And subsequent opens show "Link expired or already used" with an option to request a new link And if the link is opened after TTL, an expiration screen is shown and the token is invalidated And resends are limited to 3 per 24 hours per work order-contact pair; attempts beyond the limit are blocked and surfaced to staff
Live Camera Enforcement and Device Compatibility
Given a valid magic link is opened on a device When the capture flow initializes Then only live camera input is allowed; gallery/file upload is disabled and hidden And if camera permission is denied or unavailable, the user is guided to enable permissions or switch devices; upload cannot proceed without live capture And supported environments include iOS Safari 16+, Android Chrome 108+, and latest native apps; unsupported environments display guidance and do not allow capture And any attempt to inject pre-existing media is rejected with "Live photo required" and no file is stored
Inline Consent and Verified Metadata Binding with Geofence
Given the capture flow is active When the user proceeds to capture Then inline consent for location and metadata use is displayed in the user's locale and must be acknowledged before capture And on capture, server timestamp, device fingerprint, user/contact ID, and approximate location are recorded and immutably bound to the upload And geofence match is evaluated against the prefilled property geofence; result stored as pass/fail with observed distance And if location permission is denied, capture proceeds but is flagged "geofence_unverified" and staff are notified
Offline Capture with Delayed Upload
Given the user has intermittent or no connectivity during capture When the upload attempt fails Then the capture is stored encrypted on-device with a retry policy (exponential backoff up to 24 hours) And the UI shows a "Pending upload" state with last attempt time and a manual retry action And upon reconnection, the upload resumes automatically; on success the on-device cache is cleared
Real-Time Completion Update, Localized and Accessible States
Given a successful live capture upload When the server validates and stores the evidence Then the associated work order is updated to "Evidence Received" within 5 seconds and the photo plus metadata appear in the timeline And tenant and staff receive confirmation in their preferred channels; messages and UI are localized to the user's language and time zone And success and failure states meet WCAG 2.1 AA (contrast, focus order, screen reader labels) and provide clear next steps
Evidence Timeline and Audit Export
"As a property manager, I want an auditable evidence timeline I can export so that I can resolve disputes with vendors or tenants using verifiable records."
Description

Present an immutable, chronological timeline per work order showing each media event with metadata snapshot, geofence result, device trust indicators, flags raised, retake requests sent, delivery status, and user acknowledgments. Support role-based access, redaction of personal data, and export to signed PDF and JSON with embedded hash and signature for external sharing. Provide search and filter across requests by flag type, location match, and freshness. Ensure exports are reproducible and verifiable against stored hashes.

Acceptance Criteria
Immutable Chronological Timeline Display
Given a work order with multiple media events captured at different times When a user views the Evidence Timeline Then events are ordered strictly by server timestamp (UTC) ascending; ties resolve by upload ID ascending And each event displays: server timestamp (UTC), uploader user ID, device fingerprint ID, geofence result (match/no-match/unknown), device trust indicators, flags raised, retake requests sent (count), delivery status, and user acknowledgments (timestamp and user) And timeline entries are read-only; any edit attempt is blocked with a visible error and an audit log entry is recorded
Role-Based Access and PII Redaction
Given a user without PII_View permission When viewing a timeline or generating an export Then personal data (email, phone, exact GPS beyond 3-decimal precision, device fingerprint value) is redacted or truncated, while non-PII remains visible And API access to redacted fields returns 403 and is audit-logged And a user with PII_View permission sees the full unredacted data under the same conditions And redaction settings are applied consistently to both on-screen timeline and exported files
Signed PDF Export of Evidence Timeline
Given a work order timeline and a user with Export permission When the user requests a PDF export Then the system generates a PDF containing all timeline entries with required metadata and a unique export_id And the PDF is digitally signed with the platform certificate; standard PDF readers report the signature as valid and the document as unmodified And the export record stores the SHA-256 hash of the normalized payload and links the file to the work order And the export job status transitions from Queued to Completed on success, or to Failed with a retriable error message on error
Signed JSON Export of Evidence Timeline
Given a work order timeline and a user with API_Export permission When the user requests a JSON export Then the system returns a JSON document including timeline entries and metadata snapshots with top-level fields: export_id, hash (SHA-256 of canonicalized payload), and signature (detached signature over the hash) And verification of the signature using the platform public key succeeds And the export record is stored and retrievable by export_id
Verifiability and Reproducibility of Exports
Given two exports generated from the same work order with identical filters and redaction settings When the exports are created at different times Then their computed payload hashes are identical and match the stored hash for that work order state And modifying any byte of either file causes the recomputed hash to differ and signature validation to fail And re-running the export with different filters or redaction settings produces a different hash
Search and Filter Across Requests
Given multiple work orders with various flags, geofence results, and capture times When a user filters by flag type (e.g., Stripped EXIF, Stale Photo), location match (match/no-match/unknown), and freshness window (e.g., last 24h/7d/custom) Then the result set includes only work orders meeting all selected filters and displays counts per filter And the results update within 2 seconds for datasets up to 10,000 requests at the 95th percentile And the applied filters are reflected in the URL/query parameters for shareable views
Geofence and Device Trust Visibility and Flagging
Given a media upload with a configured geofence and available device trust signals When the upload is processed Then the timeline records the geofence result (match/no-match/unknown), geofence ID, and match radius And device trust indicators (e.g., known device fingerprint match and integrity check pass/fail) are displayed And no-match or integrity-fail conditions raise a flag and, if enabled, automatically create a retake request entry with delivery status And the same fields appear identically in both PDF and JSON exports
Privacy, Consent, and Data Retention Controls
"As an admin, I want clear consent flows and retention controls for captured evidence so that we comply with privacy regulations without losing necessary operational data."
Description

Implement granular consent prompts and just-in-time explanations for location and device fingerprinting, with graceful degradation when declined. Provide organization-level settings for retention periods of media and metadata, automatic deletion schedules, and export/self-service data requests. Encrypt media and metadata in transit and at rest, implement key rotation, and restrict access via RBAC and auditing of all access events. Document legal bases and provide configurable notices to meet GDPR/CCPA requirements. Include admin dashboards for consent rates, retention status, and deletion queues.

Acceptance Criteria
Just-in-Time Consent for Location and Device Fingerprinting with Graceful Degradation
Given a tenant initiates a media upload in MetaProof Capture and the organization has enabled location and device fingerprinting When the app requests location permission Then a just-in-time modal explains purpose, legal basis, data usage, and retention, and provides Accept and Decline options And the consent prompt records the notice template/version displayed Given the tenant declines location and/or device fingerprint consent When they proceed to upload Then the upload completes without blocking And the system records consent status=declined with timestamp and template version And geofence match and device fingerprint trust scoring are disabled for this upload And the metadata is marked "unverified" without exposing PII in tenant view And the manager UI shows a non-blocking "unverified metadata" indicator Given the tenant accepts consent When the upload completes Then the system stores consent status=granted with timestamp, template version, and pseudonymous device identifier Given notice copy or legal basis configuration changes When a tenant is prompted after the change Then the new notice version is shown and any consent recorded references the new version
Configurable Regional Notices and Legal Basis Documentation (GDPR/CCPA)
Given an admin selects operating regions and legal bases in Admin > Compliance When a tenant in an EU jurisdiction opens the consent prompt Then the modal displays GDPR-compliant notices including controller identity, purposes, legal basis, DPO/contact, data rights, and policy links And the system stores the region context and legal basis used in the consent record Given a California tenant opens the consent prompt When the org has CCPA/CPRA notices configured Then the prompt includes categories of personal information, purposes, a "Do Not Sell/Share" control if applicable, and an opt-out link Given an admin customizes and publishes notice templates When published Then the template version increments, previous versions are retained for audit, and all subsequent consent records reference the new version Given an auditor requests legal basis mapping When the admin exports compliance documentation Then the system generates a machine-readable export mapping data elements (location, device fingerprint, media metadata) to purposes, legal bases, retention periods, and processors
Organization-Level Retention Policies for Media and Metadata
Given an admin sets retention periods separately for media and for metadata (e.g., media=180 days, metadata=365 days) When the policy is saved Then policies apply to new and existing records unless a legal hold exists And policy changes are versioned and logged with editor, timestamp, and diffs Given items reach end-of-retention When the daily retention job runs Then eligible media are securely deleted and metadata is deleted or minimized per policy And each deletion event is logged with item ID, type, timestamp, actor=system, and policy reference Given a legal hold is applied to a case/property/tenant When retention would otherwise purge related items Then deletion is deferred and items are tagged with hold reason and next review date Given an admin views retention status When opening Admin > Data Lifecycle Then counts by retention bucket, next purge dates, and exceptions (holds, failures) are displayed and exportable
Deletion Queue Workflow and Proof of Erasure
Given a deletion job is scheduled by policy or initiated manually When the job runs Then items are placed into a pending deletion queue with a configurable grace period (default 7 days) And notifications are sent to designated admins for review Given the grace period elapses without cancellation When final deletion executes Then the system performs cryptographic erasure for cloud objects and secure deletion/minimization for database records And a deletion certificate per batch is generated including a hash list of object IDs, timestamps, policy reference, and operator identity Given a deletion operation fails When retries occur up to the configured limit Then failures are surfaced with error codes, remediation guidance, and webhook alerts to the org And unresolved failures remain visible in the deletion queue with next retry time
Self-Service Data Export and Deletion Requests (DSAR)
Given a tenant verifies identity via magic link When they request a data export Then within 24 hours a downloadable archive is prepared containing their media, associated metadata, consent records, and access logs relevant to their items And the archive is protected with a one-time token, encrypted at rest, and expires after 7 days Given a tenant submits a deletion request When the org approves the request in the Admin queue Then the system deletes personal data not required for legal obligations, replaces references with anonymized IDs, and emails a confirmation summarizing items removed and exceptions with legal basis Given regional SLA timelines (e.g., GDPR 30 days) When a DSAR is opened Then SLA countdowns and reminders are shown in the Admin dashboard until completion And overdue DSARs are flagged and included in weekly compliance reports
Encryption in Transit and at Rest with Managed Key Rotation
Given media or metadata is captured or uploaded When data is transmitted Then TLS 1.2+ with strong ciphers is enforced end-to-end, HSTS is enabled, and mobile clients use certificate pinning Given data is stored at rest When written to storage Then media objects are encrypted with AES-256 via cloud KMS and database metadata uses TDE with a per-tenant key hierarchy And backups and search indexes are encrypted Given a key rotation policy of 90 days is configured When the rotation job runs Then new data uses new keys and existing data is re-encrypted asynchronously without downtime And rotation events are logged with wrapped key IDs, time, initiator, and status Given a key is compromised or disabled When incident response is initiated Then immediate rekeying is performed, access to affected data is blocked until re-encryption completes, and alerts are sent to security contacts
RBAC Access Controls and Comprehensive Access Auditing
Given roles (Owner, Manager, Technician, Support) and scopes (organization, portfolio, property) are defined When permissions are configured Then least-privilege defaults apply and only authorized roles can view/download media, metadata, or consent records within their scope Given a user attempts access outside their scope or permission set When they request a resource Then access is denied and the event is logged with user ID, role, resource ID, action, time, IP/device fingerprint, and denial reason And repeated anomalous attempts are flagged for review Given any access to sensitive items occurs When a user views or downloads Then immutable audit logs capture who accessed what, when, from where, and a required justification note for PII views And logs are queryable and exportable in CSV and JSON with retention independent from operational data Given SSO/SCIM provisioning is enabled When users are deprovisioned or roles change in the IdP Then access changes propagate within 15 minutes and audit logs reflect the changes

RoleRoute Approvals

Configurable approval matrix by cost, trade, and funding source with e‑sign and required attestations. Auto‑applies policy language and blocks work until compliant sign‑off is complete. Speeds decisions while keeping spend, grants, and reimbursements within program rules.

Requirements

Approval Matrix Configuration UI
"As a property manager, I want to configure approval rules by cost, trade, and funding source so that requests route to the right approvers without manual triage."
Description

Provide an admin UI to define and manage approval rules by cost thresholds, trade categories (e.g., plumbing, electrical), and funding sources (e.g., owner-paid, grant, insurance). Support multiple approval tiers, conditional approvers, role-based routing, and per-portfolio overrides. Include versioning, rule previews, simulation (test a sample work order to see routed approvers), and safe publishing with change logs. Integrate with FixFlow entities (work orders, vendors, budgets, properties) so rules are evaluated in context. Enforce RBAC for who can create, edit, and publish rules.

Acceptance Criteria
Define Multi-Tier Approval Rule by Cost, Trade, and Funding Source
Given a user with Rule Admin permission, when they create a draft rule scoped to selected trade categories and funding sources with cost thresholds, then the UI supports adding 1–5 ordered tiers with approvers defined by role and/or specific users. Given thresholds overlap within the same trade/funding scope, when the user attempts to save, then the save is blocked and validation displays conflicting ranges and locations. Given a rule contains conditional approvers (e.g., add Finance if vendor_rating < 3), when configured via the condition builder, then the UI validates fields against available FixFlow entity attributes and data types. Given a valid configuration, when saved, then a draft version with a unique version ID and timestamp is created and visible in version history.
RBAC for Rule Creation, Editing, and Publishing
Given a user without Rule Admin permission, when they access the Approval Matrix Configuration UI, then create/edit controls are hidden/disabled and direct API attempts return 403 with an audit log entry. Given a user with Rule Admin but without Publisher permission, when viewing a draft, then they can edit and save drafts but cannot publish (Publish control disabled with tooltip explaining required permission). Given a user with Publisher permission, when publishing a draft, then publication succeeds only if validation passes and an audit record captures user, timestamp, version, and scope (global/portfolio). Given any permission change, when applied by an Org Admin, then the new permissions take effect on next action without requiring logout.
Versioning, Validation, and Safe Publishing with Change Logs
Given a published rule exists, when a new draft is created, then the published version remains active until the draft is successfully published. Given a draft contains structural errors (e.g., missing approver for a tier, unreachable conditions), when Publish is attempted, then publish is blocked and the error list identifies the exact fields. Given a draft is saved, when changes are made, then a change log entry records field-level diffs, user, timestamp, and optional reason, and is viewable in the version history. Given a draft is published, then the new version is marked Published, the prior version is marked Superseded and remains read-only and available for simulation; evaluations created after the publish timestamp use the new version.
Rule Preview (Read-Only Summary)
Given a draft or published rule, when Preview is opened, then a read-only summary displays scope (trades, funding sources, cost ranges), tiers with approvers, conditional logic, and targeted portfolios. Given the rule includes required attestations for any approver tier, when Preview is shown, then the attestations are listed in the sequence approvers will encounter with links to attestation text. Given any tier is unreachable due to prior conditions, when Preview is generated, then a warning banner enumerates unreachable tiers and conditions causing them.
Simulation of Work Order Routing and Blocking
Given a user provides a sample work order (trade, estimated cost, funding source, property, vendor, budget), when Simulate is run, then the system returns the ordered approver path by tier, the resolved approver identities (via roles), and required attestations. Given any approver cannot be resolved (e.g., empty role assignment), when Simulate is run, then the simulation flags the missing assignment and indicates that publishing would be blocked until resolved. Given the sample exceeds the property’s remaining budget or invokes funding-program rules, when Simulate is run, then the simulation includes the additional approval steps configured for budget/funding and indicates that work would be blocked until compliant sign-offs are complete. Given simulation results are shown, then they display the matched rule name, version ID, and whether a portfolio override was applied.
Per-Portfolio Overrides and Inheritance
Given a global rule exists, when an admin creates a portfolio override, then the override inherits the global rule’s fields for that portfolio and is labeled as Override with its own versioning. Given both a global rule and a portfolio override exist, when simulating or evaluating a work order for a property in that portfolio, then the override takes precedence over the global rule. Given a user attempts to publish an override without specifying a portfolio scope, when Publish is attempted, then validation blocks the publish and indicates the missing scope. Given the global rule is later updated and published, then existing overrides remain unchanged and display an Out of Sync indicator comparing override to the updated global rule.
Rules Engine for Auto-Routing
"As a system admin, I want approvals to be routed automatically based on cost, trade, and funding source so that decisions are consistent with policy and do not require manual review."
Description

Implement a deterministic, auditable rules engine that evaluates each request against defined thresholds, trade classifications, and funding attributes to compute the required approval path. Support chaining of rules, fallbacks, and conflict resolution. Expose an API and service that can be invoked at intake, estimate updates, and change orders. Log decisions with rule IDs, inputs, and outputs for traceability, and persist the computed approval path on the work order.

Acceptance Criteria
Auto-route by Cost, Trade, and Funding
Given a maintenance request with costEstimate, tradeClassification, and fundingSource present And a published ruleset with thresholds, trade, and funding conditions exists When the rules engine evaluates the request Then it selects the approval path whose rule conditions are satisfied by all three attributes And the output includes approvalPathId, orderedApprovers with roleIds, and blocking=true|false as configured And the matchedRuleIds list contains the IDs of all rules that directly determined the path
Rule Chaining and Fallback Behavior
Given a ruleset where some rules produce partial segments of an approval path and define nextRule or chain keys And a request matches multiple partial rules needed to form a complete approval path When the rules engine evaluates the request Then it composes a single approval path by chaining matched rules in defined precedence order And if the composed path remains incomplete, then the engine applies the configured default fallback path And the decision output includes chainApplied=true|false and fallbackApplied=true|false indicators
Conflict Resolution and Precedence
Given two or more rules match the same request and yield conflicting approvers or order And each rule carries priority (integer) and specificity (number of predicates) metadata When the rules engine evaluates the request Then it resolves conflicts deterministically by precedence: higher priority wins; if tied, higher specificity wins; if still tied, most recent publishedAt wins And the decision output includes resolution.reasonCode and resolution.appliedOrder showing the steps taken And exactly one approval path is returned
API Invocation at Intake, Estimate Update, and Change Order
Given the engine is exposed at POST /v1/approvals/route And the request payload includes requestId, eventType in {INTAKE, ESTIMATE_UPDATED, CHANGE_ORDER}, propertyId, trade, costEstimate, fundingSource, and optional rulesetVersion When the API is invoked for each of the three event types with valid inputs Then it returns 200 with approvalPath, matchedRuleIds, rulesetVersion, and decisionId And for invalid payloads it returns 422 with field-level errors; for concurrent evaluation of the same requestId+eventRevision it returns 409 And repeated calls with the same requestId+eventRevision are idempotent and return the same decisionId and approvalPath
Deterministic Evaluation and Idempotence
Given a fixed rulesetVersion and identical request attributes When the rules engine evaluates the same request multiple times Then the computed approvalPathId, orderedApprovers, and matchedRuleIds are identical across evaluations And the response and logs include the rulesetVersion used for the decision
Decision Logging and Audit Traceability
Given a routing decision is produced When the engine completes evaluation Then it persists an audit record containing decisionId, timestamp, requestId, eventType, rulesetVersion, matchedRuleIds (ordered), inputAttributes snapshot, computed approvalPath, and any resolution.reasonCode And audit records are immutable and retrievable by requestId or decisionId via an internal query endpoint And failures to log cause the decision to be retried or flagged as failed with no state changes persisted to the work order
Persist Computed Approval Path on Work Order
Given a work order exists or will be created for the maintenance request When a new approval path is computed by the engine Then the system persists the approvalPath to the work order with version, effectiveFrom timestamp, and source eventType And prior approval paths remain in history with supersededBy linking to the latest And the work order reflects blockedUntilApproval per the computed path configuration
Work Block & Exception Controls
"As an operations lead, I want work to be blocked until approvals are complete, with controlled overrides, so that we stay compliant while still handling emergencies."
Description

Block technician dispatch, PO issuance, and work start until required approvals are complete. Provide clear UI states on tickets (e.g., Awaiting Approval) and API guards to prevent noncompliant actions. Support emergency override with configurable roles, required justification, and automatic post-facto review tasks. Track all block/unblock events with timestamps and user IDs for auditability.

Acceptance Criteria
Block Dispatch Pre-Approval
Given a ticket configured to require approvals based on cost, trade, or funding source When a user attempts to dispatch a technician before all required approvals are complete Then the dispatch action is blocked in UI and mobile, the ticket shows state "Awaiting Approval", and the API returns HTTP 409 with errorCode "APPROVAL_REQUIRED" And an audit event "DISPATCH_BLOCKED" with userId, timestamp, and ticketId is recorded When all required approvals are complete Then the dispatch action becomes enabled and the API returns HTTP 200 for the same request payload
Block PO Issuance Pre-Approval
Given a ticket has pending required approvals When a user attempts to issue a purchase order for that ticket Then PO creation is blocked, a non-dismissible banner indicates "Awaiting Approval", and the API returns HTTP 409 with errorCode "APPROVAL_REQUIRED" And an audit event "PO_BLOCKED" is recorded with ticketId, userId, and timestamp When approvals are complete Then PO issuance succeeds and the PO record links to the approval artifacts
Block Work Start & UI State Visibility
Given a ticket is awaiting approval When a technician opens the job in the mobile app Then the "Start Work" button is disabled with reason "Awaiting Approval" and shows pending approver roles And the ticket list and detail pages display an "Awaiting Approval" badge and the blocking approval(s) When final approval is recorded Then "Start Work" becomes enabled and the badge updates to "Approved"
Emergency Override with Required Justification
Given an emergency exists and the user holds a role authorized for emergency override When the user selects "Emergency Override" on a blocked ticket Then the system requires a mandatory free-text justification (minimum 20 characters) and a reason code selection before proceeding And upon confirmation, the ticket state changes to "Emergency Override", dispatch/PO/start actions are unblocked, and an audit event "OVERRIDE_APPLIED" is recorded with userId, justification, reason code, and timestamp When the override is revoked or after a configurable window expires Then if approvals remain incomplete the block is reinstated and an "OVERRIDE_EXPIRED" audit event is recorded
API Guards for Noncompliant Actions
Given the dispatch, issuePO, and startWork API endpoints When they are called for a ticket that is noncompliant with approval requirements and not under active override Then the service returns HTTP 409 with a body containing errorCode "APPROVAL_REQUIRED", blockedActions, and pendingApprovals, and performs no side effects When called while an active override exists Then the service returns HTTP 200 and includes overrideId in the response metadata
Audit Trail of Block/Unblock/Override Events
Given any block, unblock, or override action occurs on a ticket Then the system records an immutable audit entry with eventType, ticketId, userId, userRole, timestamp (UTC ISO 8601), actionTarget (dispatch|po|start), reason, and preState/postState And audit entries are visible in the ticket timeline and retrievable via an Audit API with filters by date range, userId, eventType, and ticketId And audit entries cannot be edited or deleted; only append operations are allowed
Post-Facto Review Task & Escalation After Override
Given an emergency override is applied Then the system automatically creates a post-facto review task assigned to the approver group with a due SLA of 48 hours If the task is not completed by SLA Then the system escalates via email and in-app to the property manager and marks the ticket with a "Review Overdue" badge Ticket closure is permitted but requires explicit acknowledgment of the outstanding review; the review task remains open until completed and all events are auditable
E‑Signature & Audit Trail
"As an approver, I want to sign electronically with a verifiable audit trail so that approvals are legally traceable and easy to audit."
Description

Integrate e‑signature for approval packets, capturing signer identity, authentication method, timestamp, and document hash. Support signing on web and mobile, and attach signed artifacts to the work order. Maintain an immutable audit trail of approval requests, actions, and outcomes, with exportable logs for auditors. Ensure signatures meet common e‑sign standards and can be verified independently.

Acceptance Criteria
Capture Signer Identity and Authentication Details
Given an approval packet is sent for e-signature, When a recipient completes signing, Then the system records signer full name, unique signer ID, email, IP address, and device user agent. Given authentication is required, When the signer authenticates via email link, SMS one-time passcode, or SSO, Then the method type, factor details (masked where applicable), and authentication success timestamp are stored. Given a signature is applied, Then an audit entry links signer UUID, authentication method, UTC timestamp, work order ID, and approval packet ID. Given a signer declines or fails authentication, Then the event, error/reason code, and timestamp are logged and no signature artifact is attached.
Timestamping and SHA-256 Document Hashing
Given a signature packet is finalized, Then the system computes a SHA-256 hash of the exact signed PDF byte stream and stores it in the audit trail and file metadata. Given an auditor downloads the signed PDF, When they compute the SHA-256 hash locally, Then it matches the stored hash value. Then all signature and action timestamps are recorded in UTC using ISO 8601 (Z suffix) and the UI displays local time with timezone for viewers. Then any subsequent download of the signed PDF returns the identical byte stream that produced the stored hash.
Web and Mobile Signing Coverage
Given a recipient opens the signing link, When using the latest two major versions of Chrome, Firefox, Edge, or Safari on desktop, Then the signing flow completes without functional errors. Given a recipient opens the signing link on mobile, When using iOS Safari (latest two major iOS versions) or Android Chrome (latest two major versions), Then the signing flow completes without functional errors. Given screen widths from 320px to 1440px, Then all signing controls are visible, tappable/clickable, and no horizontal scrolling is required to complete signing. Given a temporary network interruption during signing, When connectivity resumes within 5 minutes, Then the session recovers or resumes without data loss and allows completion.
Attach Signed Artifacts to Work Order
Given a work order approval is completed, When the approval packet is signed, Then the final signed PDF, a certificate of completion/audit report, and the SHA-256 hash are automatically attached to the associated work order. Then attached signed artifacts are marked read-only, versioned, and cannot be edited or overwritten via UI or API. Then authorized users of the work order can view and download the attachments; unauthorized users cannot access them and receive a 403 error. Then removal of signed artifacts via UI/API is blocked by default; any permitted administrative purge (if enabled by policy) requires dual authorization and is fully logged in the audit trail.
Immutable Audit Trail with Export
Given any approval-related event occurs (request sent, viewed, authenticated, signed, declined, revoked, exported), Then an append-only audit entry is written with event type, actor (user/service) ID, actor role, target record IDs, UTC timestamp, and cryptographic prev_entry_hash and entry_hash. Then the system disallows in-place edits or deletions of audit entries via UI or API; corrections are represented as compensating entries that reference the original. Given an auditor requests logs, When exporting by date range and work order ID, Then the system produces CSV and JSON files containing all relevant entries, including the hash chain needed for tamper-evidence verification. Then exports of up to 10,000 entries complete within 60 seconds and include a manifest with overall dataset hash.
Independent Signature Verification and Crypto Standards
Given a signed PDF is generated, Then it is digitally signed using a PKCS#7/PAdES-compliant signature with SHA-256 and an X.509 certificate (RSA ≥ 2048-bit or ECDSA P-256) embedded in the document. Then verification using Adobe Acrobat or an independent open-source PDF signature verifier reports the signature as valid and the document as unmodified. Then the signed PDF or its accompanying audit certificate includes certificate chain details and OCSP/CRL status sufficient for independent validation at least 24 months post-signing. Given a verification request, When the auditor compares the published SHA-256 hash and certificate fingerprint, Then they match the values stored in the audit trail.
Block Work Until Compliant E‑Sign Complete
Given the approval matrix requires electronic sign-off, When required signatures are missing or fail identity/authentication requirements, Then the system blocks technician scheduling, purchase order issuance, and work order status transition to Approved, returning a 409 error in API and showing an in-app banner. Then once all required compliant signatures are recorded, the blocks are lifted immediately and an audit entry is created noting the unblock event. Then attempts to bypass the block via direct API updates are rejected with a 403 and are logged with actor ID and reason code.
Attestation Templates & Enforcement
"As a compliance officer, I want required attestations collected during approval so that funding and program rules are enforced at the point of decision."
Description

Provide configurable attestation forms (e.g., grant eligibility, procurement method, multiple-bid confirmation) that can be attached to approval steps and must be completed before submission. Support conditional fields based on funding source or trade, required attachments (e.g., photos, bids), and validation rules. Store attestations with the approval record and include them in the audit export.

Acceptance Criteria
Attach Attestation to Approval Step and Enforce Completion
Given an approval workflow step has an attestation template assigned When a requester opens the step Then the attestation form is rendered inline and marked Required Given at least one required attestation field is blank or invalid When the user attempts to submit the approval step Then the submission action is blocked, the missing fields are highlighted, and a message lists what must be completed Given all mandatory attestation fields and attachments are valid When the user submits Then the step transitions per workflow rules and the attestation is saved with the approval record
Conditional Fields by Funding Source and Trade
Given funding source equals Municipal Grant A When the form loads Then fields tagged to Grant A display and non-applicable conditional fields remain hidden Given trade equals HVAC and funding source is not grant-restricted When the form loads Then HVAC-specific attestation fields display and grant eligibility fields remain hidden Given the user changes the funding source selection When dependencies change Then conditional field visibility and requiredness update in real time without a page reload
Required Attachments Enforcement (Photos and Bids)
Given the template requires Photos: minimum 2 and Bids: minimum 3 PDFs When the user submits with fewer than the required counts Then submission is blocked and errors indicate the exact missing counts for each attachment type Given a required attachment rule allows only JPG, PNG, or PDF and maximum file size 25 MB When a disallowed type or oversize file is uploaded Then the upload is rejected with an inline error explaining the rule violated Given all required attachment rules are satisfied When the step is submitted Then each attachment is stored and linked to the attestation record with filename, type, size, and upload timestamp
Field-Level Validation Rules and Blocking Submission
Given a numeric field Estimated Cost has rules >= 0 and <= 5000 for self-approval When the value is outside the allowed range Then a validation error is shown and submission is blocked Given a checkbox Multiple bids obtained is checked When Number of bids is less than 2 Then the form shows an error requiring correction before submission Given a date field Procurement date must be on or after Request date When the user enters an earlier date Then an error is shown and submission is blocked
Persist Attestations with Approval Record (Immutable Snapshot)
Given an approval step is submitted successfully When the system saves the record Then a snapshot of the attestation (field values, conditional visibility states, validation outcomes, and attachment references) is stored immutably with approval step ID, submitter ID, and timestamp Given a supervisor edits non-attestation fields of the approval later When viewing the attestation snapshot Then the original attestation values remain unchanged and read-only Given the approval step is rejected and resubmitted When the attestation is updated Then a new version is saved with version number incremented and prior versions remain accessible in history
Audit Export Includes Attestations and Attachments Metadata
Given an audit export is generated for a date range When approvals with attestations exist Then the export includes each attestation version with template ID/name, template version, field labels and values, attester identity, timestamps, and validation pass/fail at submit Given required attachments were provided When exported Then the export includes attachment metadata (filename, type, size, checksum, storage URI) and referential links to approval and attestation IDs without embedding binary files Given some fields were conditionally hidden at submit When exported Then those fields are omitted or represented as null with a visibility flag to indicate not applicable
Template Management: Create, Version, and Assign Attestation Templates
Given an admin creates a new attestation template When they define fields, conditional rules by funding source and trade, validation rules, and attachment requirements Then the template can be saved as Draft and previewed with a selected funding source/trade context Given a Draft template is Published When it is assigned to an approval step configuration Then new approvals use the published version and in-flight approvals continue using their originally bound version Given an admin deprecates a template version When creating new assignments Then deprecated versions are not selectable and remain read-only for historical records
Policy Language Auto‑Apply
"As a property manager, I want applicable policy language auto‑included in approvals so that approvers see the correct terms without manual copy‑paste."
Description

Automatically insert relevant policy clauses and program terms into the approval packet based on detected attributes (cost tier, trade, funding). Support templating with variables (property, vendor, estimate amount) and dynamic sections. Maintain a library of policy texts with version control and effective dates. Display applied clauses to approvers and include them in signed artifacts.

Acceptance Criteria
Rules-Based Auto-Selection of Policy Clauses
Given a request with attributes cost tier, trade, and funding source, When an approval packet is generated, Then all clauses whose rules match the attributes are included and all non-matching clauses are excluded. Given multiple funding sources, When rules match across sources, Then clauses from all applicable sources are included without duplicates. Given clause priorities are configured, When multiple rules target the same clause, Then the highest-priority rule determines inclusion once. Given selection completes, When logging is written, Then clause IDs, rule IDs, and selected version IDs are recorded.
Effective Date and Version Resolution
Given a policy has versions with effective start and end dates, When generating a packet for a specific request timestamp, Then the version effective at that timestamp is selected. Given no version is effective for the request date, When generating the packet, Then the system blocks approval and surfaces an actionable error identifying the missing policy. Given a policy version is scheduled to start in the future, When generating a packet before the start date, Then the future version is not used.
Variable Substitution and Formatting
Given a template contains variables such as {property.*}, {vendor.*}, and {estimate.amount}, When a packet is generated, Then all variables are resolved with current values and no unresolved placeholders remain. Given a currency field, When rendered, Then it is formatted to two decimals with the correct currency symbol per portfolio locale. Given a required variable is missing, When generating the packet, Then generation fails with a clear error listing the missing variables and the approval is blocked. Given optional variables have defaults, When source data is absent, Then configured defaults are used.
Dynamic Section Rendering
Given a dynamic section with condition expressions, When the condition evaluates true for the request, Then the section is included; When false, Then it is omitted without leaving dangling headers or extra whitespace. Given nested and multiple conditions, When evaluated, Then precedence and grouping are respected per configuration specifications. Given a rendered packet, When pagination occurs, Then dynamic sections do not split mid-sentence and preserve readability standards.
Approver Visibility and Required Attestations
Given an approver opens the approval view, When clauses are applied, Then the approver can view all applied clauses and program terms in full before e-sign. Given required attestations are configured, When viewing the packet, Then checkboxes with attestation text are displayed and must be checked to enable e-sign. Given clauses change due to attribute updates before signing, When the approver returns, Then the UI highlights changes since the last view and requires re-attestation. Given approval prerequisites are unmet (e.g., attestations unchecked), When attempting to approve or schedule work, Then approval actions and downstream scheduling are blocked and an explanatory message is shown.
Signed Artifact and Audit Trail Integrity
Given e-sign is completed, When generating the signed artifact, Then the artifact embeds the exact applied clauses text, filled variables, clause IDs, version IDs, and a cryptographic hash in the document metadata or appendix. Given an audit query, When retrieving approval records, Then the system returns the list of applied clause IDs, version IDs, rule IDs, timestamps, and the associated artifact hash. Given a download of the signed packet, When compared to the audit record, Then the text and metadata match exactly.
Immutable Policy Snapshot Post-Approval
Given a policy clause is updated after an approval is signed, When viewing or downloading the previously signed packet, Then the original clause text and version remain unchanged. Given an approval is re-opened prior to re-signing, When attributes change, Then the packet is regenerated with the currently effective policies and prior signatures are invalidated. Given a historical report is generated, When listing approvals over a date range, Then it reflects the policy versions effective at the time of each approval, not the current versions.
Notifications & Escalations
"As a landlord, I want timely reminders and escalations when approvals stall so that repair decisions happen quickly and tenants are not left waiting."
Description

Send targeted notifications to approvers when actions are required, with reminders and time‑based escalations to alternates or managers. Support email, SMS, and in‑app channels, quiet hours, and digest options. Include deep links to approve/deny and a status dashboard showing bottlenecks. Record delivery and action metrics to optimize turnaround time.

Acceptance Criteria
Targeted Action-Required Notification with Deep Links
Given an approval step becomes assigned to an approver and the approver has at least one notification channel enabled When the step transitions to Pending Approval Then the system sends a notification within 60 seconds via the approver’s selected channels (email, SMS, in-app) including request ID, property, trade, amount, funding source, SLA due date, and Approve/Deny deep links And the notification is delivered only once per event per channel And the send event is logged with unique message ID, channel, and UTC timestamp
Reminder Scheduling and Auto-Suppression
Given an approval is pending and a reminder policy is configured (e.g., every 24 hours up to 3 times) When the approver has not acted by the reminder interval Then a reminder notification is sent via the approver’s preferred channels with the original context and deep links And reminders stop immediately if the approver approves, denies, or the item is reassigned And the system records reminder count and last reminder time on the approval And no more than the configured maximum number of reminders are sent
Time-Based Escalation to Alternate or Manager
Given an approval remains pending beyond the configured escalation threshold (e.g., 48 hours) When an alternate approver and/or manager is defined in the approval matrix Then the system escalates to the next level with a notification containing full context and deep links And escalation is not triggered if the original approver has taken action And each escalation level is triggered at most once and logged with level, recipient(s), and timestamp And the dashboard marks the item as Escalated and shows the current responsible party
Quiet Hours and Digest Delivery
Given an approver has quiet hours configured or a digest preference enabled and the notification is non-emergency When a notification would occur during the approver’s quiet hours in their time zone Then the notification is deferred to the next allowable send window And non-urgent notifications during the day are aggregated into a single daily digest delivered at the configured digest time And emergency notifications marked as such bypass quiet hours for SMS and in-app only And the system prevents duplicate sends by deduplicating items included in the digest
Status Dashboard Bottleneck Visualization
Given a portfolio with active approvals When a manager views the Approvals Status dashboard Then the dashboard displays counts by stage, average age, items past SLA, and top bottlenecks by approver And each item shows current assignee, time in state, escalation status, and next SLA deadline And filters for property, trade, funding source, approver, and date range are available and persist for the session And data refreshes at least every 60 seconds or on manual refresh
Delivery and Action Metrics Capture
Given notifications and escalations are sent When delivery providers return status and users interact Then the system records per-message metrics: send time, queued time, delivery status (delivered, bounced, failed), error code, and provider message ID And per-user action metrics: first open timestamp (where supported), first deep-link click timestamp, decision timestamp, and time-to-decision And metrics are stored for at least 365 days, normalized to UTC, and available via dashboard and export API
Secure Deep Link and Authentication
Given a notification contains Approve/Deny deep links When the link is opened Then the link validates a single-use, time-limited token bound to the intended approver and approval ID And if the user is not authenticated or the token is expired/invalid, the user is prompted to log in and then redirected to the approval page without losing context And multiple uses of the same token are rejected and logged And all approval decisions are attributed to the authenticated user in the audit log

Audit Pack Builder

One click compiles an audit‑ready packet by unit, building, or program: indexed timeline, hashed media manifest, work orders, COIs, invoices, and approvals. Exports as digitally signed PDF/ZIP with templates for HUD/HQS, LIHTC, and insurance requirements. Saves hours and prevents missing‑document findings.

Requirements

One-Click Builder Orchestration
"As a property manager, I want to generate an audit pack with one click after selecting scope and template so that I can meet audit deadlines without manually assembling documents."
Description

Provides a single-action flow to compile an audit pack by unit, building, or program with optional date range and template selection. Invokes a background job that orchestrates data aggregation, validation, timeline generation, manifest creation, signing, and export. Includes progress tracking, cancellation, retries with backoff, and idempotent rebuilds. Captures a point-in-time snapshot of source records and stores build metadata (scope, template versions, ruleset, builder, timestamps) to ensure reproducibility. Integrates with FixFlow’s job queue, notifications, and permissions layer to respect user roles and record visibility.

Acceptance Criteria
One-Click Build Initiation by Scope and Template
Given an authorized user (Owner, Property Manager, or Auditor) is viewing a Unit, Building, or Program context When they click "Build Audit Pack", select a template (HUD/HQS, LIHTC, or Insurance) and optionally choose a date range Then a build job is enqueued within 2 seconds and a job UUID is created And the job parameters are persisted: scope type and ID(s), selected template ID and version, ruleset version, date range (or "all available"), requester user ID, and request timestamp And the user is redirected to the job detail page showing status "Queued" with the job UUID And if scope or template is missing or invalid, field-level errors are shown and no job is created
Background Job Orchestration and Step Validation
Given a queued build job When a worker starts processing Then it runs steps in order: capture snapshot → aggregate data → validate completeness → generate timeline → build hashed media manifest → assemble documents (work orders, COIs, invoices, approvals) → apply selected template → produce signed PDF and ZIP → persist artifacts → mark completed And each step emits structured logs with step name, start/end timestamps, record counts, and any errors And if validation fails due to missing required items per template, the job status is "Failed", a machine-readable error report (JSON) is attached, and no export artifacts are published And on success, the PDF and ZIP are digitally signed with the organization key (RSA-2048/SHA-256), and the manifest lists each file with SHA-256 hash and byte size And the timeline output includes chronologically indexed events with ISO-8601 timestamps and source record IDs
Progress Tracking UI and Notifications
Given a running build job When the requester views the job detail page Then progress percentage, current step, and ETA update at least every 5 seconds via SSE/WebSocket or polling And each pipeline step displays a status badge: Queued, Running, Succeeded, Failed, or Skipped And when the job reaches Completed, Failed, or Canceled, an in-app notification is sent and an email is delivered if the user has email notifications enabled, including job ID, outcome, duration, and links to artifacts or error report And notification delivery attempts and outcomes are logged in job metadata
Cancellation and Safe Cleanup
Given a job is in Queued or Running state When the requester clicks "Cancel build" and confirms Then the worker receives a cancellation signal and stops within 30 seconds at the next safe step boundary And partial artifacts and temporary files are deleted, the job status becomes "Canceled", and cancellation reason with timestamp is recorded And if the job is Completed or Failed, the cancel action is disabled and cannot be invoked via API And cancellation is audited with actor user ID and IP address
Automatic Retries with Exponential Backoff
Given a transient error occurs during a job step (e.g., network timeout, 5xx from storage, queue preemption) When the step fails Then the system retries up to 3 times with exponential backoff of 30s, 60s, and 120s, maintaining idempotency of side effects And attempt count, backoff schedule, and last error are recorded per step And if the error is classified as permanent (e.g., validation failure, permission denied), the job is marked "Failed" with no retries and an error report attached And after the final failed retry, the job status is "Failed" with a consolidated error summary and guidance to retry manually
Permissions Enforcement and Record Visibility
Given the requester has role-based access to a subset of units, programs, and documents When the build runs Then only records visible to the requester at trigger time are included in the snapshot and exports And templates not permitted for the requester are not shown in the UI and are rejected server-side if forced via API And exported artifacts exclude fields beyond the requester’s clearance (e.g., redacted PII), consistent with FixFlow’s permissions layer And an access evaluation summary (roles, scopes considered, policy version, and visibility checksum) is attached to job metadata
Idempotent Rebuilds and Snapshot Metadata
Given a prior successful build exists for a scope, template, date range, and ruleset version When a rebuild is initiated with identical parameters or via "Rebuild from snapshot" Then the resulting artifact-level hashes (ZIP and contained files) match the prior build, excluding job ID and timestamps, proving idempotency And job metadata persists: scope type and IDs, template ID and version, ruleset version, requester user ID, evaluated permissions checksum, snapshot timestamp, job start/end timestamps, artifact checksums, and environment And a downloadable metadata.json is provided alongside the exports And when source records have changed since the snapshot, a rebuild from snapshot yields original content, while a rebuild from live data captures changes and records a new snapshot with versioning
Data Aggregation & Normalization
"As a property manager, I want the system to automatically pull and normalize all related records for a unit/building/program so that the audit packet is complete and consistent."
Description

Collects all relevant artifacts from FixFlow (work orders, technician notes, photos/videos, invoices, approvals, COIs, communications) for the chosen scope and timeframe. Normalizes fields (dates, IDs, vendor info, costs, statuses), deduplicates attachments, and resolves versions to the latest approved state. Applies filters by program and jurisdiction, performs light OCR on invoices/COIs for missing structured fields, and flags gaps (e.g., missing COI or unsigned approval) for the validator. Produces a canonical data model consumed by the timeline, templating, and export steps.

Acceptance Criteria
Scope-Based Artifact Aggregation by Date Range
Given a selected scope type (Unit, Building, or Program) and a date range When aggregation is executed Then only artifacts linked to the selected scope and within the date range (or linked to in-range work orders) are included: work orders, technician notes, photos, videos, invoices, approvals, COIs, communications And artifacts outside the scope or timeframe are excluded And the job produces a summary count by artifact type that matches the canonical payload totals And the run completes within 120 seconds for datasets <= 5,000 artifacts And rerunning with identical inputs produces an identical canonical payload hash
Canonical Field Normalization Across Entities
Given raw artifacts collected for the selection When normalization runs Then all date/time fields are stored as ISO 8601 UTC in field "timestamp_utc" with "original_timezone" preserved And all entities have a stable "canonical_id" (UUIDv4) with a mapping from source system IDs And vendor fields are normalized: "vendor_name" trimmed/title-cased, "contact_email" lowercased, and "tax_id" normalized if present And monetary fields are normalized to USD: "amount_usd" (decimal, 2dp) and "amount_cents" (integer) with currency source recorded And status fields are mapped to canonical enumeration {open, in_progress, scheduled, completed, approved, rejected, canceled} And the canonical dataset validates against JSON Schema "audit_canonical_v1" with zero schema errors
Attachment Deduplication and Version Resolution
Given attachments across all artifacts When deduplication runs Then duplicates are identified by SHA-256 content hash and merged into a single attachment record And the manifest contains one record per unique content hash with a references list to all parent artifacts And the count of unique attachments equals the count of distinct hashes When version resolution runs Then for each document with versions, the primary version is the latest with approval_status=approved; if none approved, the latest by modified_at And superseded versions are linked via a "supersedes" chain and excluded from the primary export set
Program and Jurisdiction Rule Filtering
Given selected program(s) (HUD/HQS, LIHTC, Insurance) and jurisdiction(s) When filters are applied Then only artifacts required by the selected program rules and belonging to properties in the selected jurisdictions remain in the canonical set And each kept or excluded artifact records evaluated rule_ids and decision (kept|excluded) And applying the same filters twice yields identical results And the filter step completes within 30 seconds for datasets <= 5,000 artifacts
OCR Enrichment for Invoices and COIs
Given invoices and COIs missing any of: vendor_name, invoice_number/policy_number, total_amount, expiration_date When light OCR is executed Then available fields are extracted with confidence >= 0.90 and populated into the canonical model with source="ocr" And any field with confidence < 0.90 remains null and generates a gap flag of type "ocr.low_confidence" And OCR execution time is <= 10 minutes for 1,000 pages on the standard queue
Gap Detection and Validator Flagging
Given the canonical dataset after normalization and filtering When validation runs Then missing COI for any vendor referenced by an in-scope work order produces flag "gap.missing_coi" with severity=high And any approval record without a signature produces flag "gap.missing_signature" with severity=high And any invoice missing total_amount produces flag "gap.missing_total" with severity=medium And each flag includes artifact_id, field, severity, message, and remediation_hint And the total number of flags equals the number of detected issues in the dataset
Canonical Model Contract for Downstream Consumers
Given the canonical dataset for the selection When consumer contract tests are executed for Timeline, Templating, and Export modules Then all required fields per consumer contract are present and correctly typed with 0 contract failures And Timeline can derive a chronologically ordered event list without missing required event types And Templating can populate HUD/HQS, LIHTC, and Insurance templates without placeholder fallbacks And Export can produce a signed PDF/ZIP using only the canonical dataset and unique attachments
Compliance Templates & Validation Rules
"As a compliance officer, I want the audit pack to follow HUD/HQS, LIHTC, and insurance templates with automated checks so that submissions pass audits on the first attempt."
Description

Maintains a versioned library of packet templates and checklists for HUD/HQS, LIHTC, and common insurance requirements. Maps normalized data into the correct sections, tables, and cover sheets. A rules engine validates completeness and compliance (required fields, date windows, photo count, COI coverage, signature presence) and emits blocking errors or warnings with remediation hints and deep links to fix issues in FixFlow. Supports jurisdiction/carrier overrides, conditional sections, localization, and justification notes for permissible waivers.

Acceptance Criteria
Template Version Selection by Program, Jurisdiction, and Carrier
Given a user initiates an Audit Pack for a property with program = HUD/HQS, jurisdiction = NY, and carrier = Travelers When the system selects a template Then it chooses the latest Published template version whose effective_date <= packet_export_date using precedence: jurisdiction override > carrier override > program default And the selected template_version_id is recorded in packet metadata And previously generated packets remain pinned to their original template_version_id upon re-open And if no override exists at a higher precedence, the system falls back deterministically to the next level And selection is deterministic given the same inputs (repeat selection returns identical template_version_id)
Data Mapping to Sections, Tables, and Cover Sheets
Given a selected template version and normalized FixFlow data (unit, building, inspections, work orders, approvals, COIs, photos, notes) When the packet is compiled Then 100% of required placeholders and tokens in the template are resolved (no {{...}} remains) And tables are populated with the correct rows filtered per template rules, with column formats applied (dates, currency, percentages) And cover sheets display program, jurisdiction, template version, and packet metadata accurately And any optional/unmapped non-required tokens are removed or replaced with empty-state text defined by the template And mapping errors are logged and surfaced as validation items referencing the exact field and section
Validation Engine: Required Fields and Blocking Errors with Deep Links
Given the rules engine runs against the compiled packet When a required field is missing or invalid per template rules Then a Blocking Error is created with severity = ERROR and a machine-readable rule_id And the message includes at least one remediation hint and a deep link that opens the exact FixFlow screen to correct the data And exporting the packet is disabled while any Blocking Errors remain (unless a permissible waiver is applied) And non-critical issues are emitted as Warnings that do not block export And clearing the underlying data removes or downgrades the corresponding validation item on re-validate
Validation Rules: Date Windows, Photo Count, and COI Coverage
Given rules are configured for date windows, minimum photo counts, and COI coverage thresholds When the packet is validated Then any inspection or certification date outside its configured window relative to the audit period emits a Blocking Error naming the field, required window, and actual date And any section requiring a minimum photo count emits a Blocking Error or Warning (per rule severity) if count < required, naming the section and counts (actual vs required) And COI validations ensure coverage dates span all work order service dates, required endorsements are present, and minimum liability limits are met; any missing element is enumerated in a single validation item with sub-points And all three rule categories support per-program and per-jurisdiction overrides used in evaluation
Conditional Sections and Rules-Based Visibility
Given template sections have visibility conditions (e.g., program = LIHTC and recertification_due = true) When the packet is compiled Then only sections whose conditions evaluate true are rendered and subjected to validation And hidden sections do not appear in the export and do not generate validation items And condition evaluation is deterministic and based solely on packet data and template rules, producing the same result across repeated runs And visibility decisions are recorded in packet metadata with section_id and evaluated condition outcome
Localization of Labels, Content, and Formats
Given the user selects locale = es-US in FixFlow settings and a localized template is available When the packet is compiled Then 100% of localized keys referenced by the template resolve in the selected locale; any missing key falls back to template default without displaying placeholder or [MISSING] And dates, numbers, and currencies are formatted per locale rules And the locale used and fallback count (if any) are recorded in packet metadata And switching to en-US and recompiling produces English content with identical data values and section structure
Permissible Waivers with Justification Notes
Given a validation item is marked waivable in the template rules When an authorized user applies a waiver Then the system requires a reason code selection and a justification note of at least 20 characters before saving And the waiver captures user_id, timestamp, rule_id, and scope (this packet only) in the audit log And the waived item no longer blocks export but remains visible as a Warning with a Waived tag And non-waivable items cannot be waived (action disabled) And a Waiver Summary section is included in the exported packet listing each waived rule with reason and author
Media Hashing & Manifest
"As an auditor, I want a hashed media manifest for all photos, videos, and documents so that I can verify evidence integrity and chain of custody."
Description

Generates a cryptographic hash per media file and document, producing a manifest that lists filename, size, hash, capture/upload timestamps, author, and key EXIF fields. Detects duplicates and post-capture alterations by comparing stored hashes. Links each media item to its timeline event and source work order. Includes chain-of-custody notes and stores the manifest inside the ZIP and as an appendix in the PDF for third-party verification.

Acceptance Criteria
SHA-256 Hashing and Manifest Field Completeness on Upload
Given a supported media file or document is uploaded to a specific work order When the upload completes successfully Then the system computes a SHA-256 hash over the exact file bytes and persists it as sha256_hex And the manifest entry includes: filename, byte_size, sha256_hex, capture_timestamp, upload_timestamp, author_id, author_display_name, mime_type, and key EXIF fields (DateTimeOriginal, GPSLatitude, GPSLongitude, Make, Model, Orientation) when available And all timestamps are stored in ISO 8601 UTC format And the manifest entry is assigned a unique manifest_id and is immutable post-creation
Duplicate Detection Within Work Order and Audit Pack Scope
Given a user uploads a media file to a work order that already contains media When the newly computed sha256_hex matches an existing manifest entry within the same work order or current audit pack scope Then the system flags the upload as a duplicate without storing redundant file bytes And links the duplicate record to the original manifest_id and increments duplicate_count on the original And returns a success response with duplicate=true and the original manifest_id within 1 second of upload completion And the duplicate relationship appears in the manifest and PDF appendix
Post-Capture Alteration Detection and Audit Trail
Given a media item already exists with a stored sha256_hex When a user attempts to replace the file or a newer version is uploaded for the same timeline event Then the system recomputes the SHA-256 and compares to the original And if the hash differs, marks the prior manifest entry as superseded, creates a new manifest entry, and appends a chain-of-custody note "hash mismatch detected" And both entries are linked via previous_manifest_id/new_manifest_id and a "Media altered" timeline event is recorded
Linkage to Timeline Events and Source Work Orders
Given a media file is uploaded from a timeline event within a work order When the manifest entry is created Then the entry includes timeline_event_id and work_order_id fields referencing existing records And the audit pack export fails validation with a descriptive error if any manifest entry lacks these references And the PDF appendix renders each media row with the visible work order number and timeline event reference
Chain-of-Custody Notes Recording and Immutability
Given any of the following actions occur: upload, duplicate detected, replacement, export, vendor transfer When the action completes Then a chain_of_custody entry is appended with: actor_id, actor_display_name, action, timestamp_utc, and optional note And chain_of_custody entries are append-only and cannot be edited or deleted And the manifest embeds the full chain_of_custody array per item, mirrored in the PDF appendix
Manifest Embedded in ZIP and PDF Appendix; Digital Signatures Verifiable
Given an audit pack is generated as ZIP and PDF When the export completes Then the ZIP contains manifest.json (UTF-8) at the root and the same content rendered as an appendix in the PDF And both the ZIP and the PDF are digitally signed; the signatures validate with standard tools (e.g., Adobe Acrobat for PDF; CMS/PKCS#7 verification for ZIP) using the included certificate chain And third parties can verify file integrity by recomputing sha256_hex for any included media and matching it to manifest.json
EXIF Extraction, Normalization, and Fallback Rules
Given a photo or video is uploaded When metadata is processed Then the system extracts key EXIF/metadata fields (DateTimeOriginal or equivalent, GPS lat/long, Orientation, Make, Model) when present And capture_timestamp is set by priority: EXIF DateTimeOriginal > client_provided_capture_time > server_receipt_time, with the chosen source recorded in capture_source And timestamps without timezone are normalized using the property time zone and stored in UTC And if EXIF is missing, exif_extracted=false is recorded and fields are left null without blocking upload
Indexed Timeline & Cross-References
"As a reviewer, I want an indexed, cross-referenced timeline of events so that I can quickly trace what happened, when, and which evidence supports it."
Description

Builds a chronological, scoped timeline of maintenance events, communications, approvals, and financials with normalized timestamps and time zone handling. Produces a paginated index and PDF bookmarks that reference page numbers and sections (e.g., work order details, invoices, COIs, approvals). Embeds bidirectional links between the index, timeline entries, and artifacts to speed review. Includes author/role attribution and outcome summaries for each event.

Acceptance Criteria
Chronological Ordering with Time Zone Normalization
Given a set of maintenance events with timestamps across multiple IANA time zones, including DST transitions And a base time zone is set for the packet (property time zone or user-selected) When the timeline is generated Then all event timestamps are normalized and displayed in the base time zone with offset and abbreviation (e.g., 2025-03-10 14:05 -05:00 EST) And events are strictly ordered ascending by the normalized timestamp; ties are resolved deterministically by event_id ascending And no event displays a "floating" time (offset missing) And events spanning a DST boundary remain correctly ordered across the offset change
Paginated Index Accuracy and Section Mapping
Given the packet includes a timeline and artifacts (Work Orders, Invoices, COIs, Approvals, Communications, Media) When the paginated index is generated Then each timeline event appears as an index entry with a section label and the exact page number where its detailed section begins And 100% of index entries have valid page numbers and section labels from the allowed set And clicking the page number in the index navigates to that exact page in the PDF And the index itself is paginated and labeled "Index" in the PDF headers/footers
PDF Bookmark Hierarchy and Navigation
Given the exported PDF is opened in Adobe Acrobat and a Chromium-based viewer When the bookmarks panel is expanded Then top-level bookmarks exist for Index, Timeline, Work Orders, Invoices, COIs, Approvals, Communications, and Media And each timeline event has a child bookmark titled "<ISO date time tz> — <event summary>" And selecting any bookmark navigates to the correct destination page/anchor with 0 mismatches in automated verification And the total bookmark count equals the number of timeline events plus the defined top-level bookmarks
Bidirectional Links Between Index, Timeline, and Artifacts
Given the exported PDF contains the index, timeline, and artifact sections When a user clicks an index entry link Then the viewer navigates to the corresponding artifact's first page And when a user clicks "Back to Index" on that artifact page, the viewer returns to the originating index entry anchor And when a user clicks a timeline entry link, the viewer navigates to the artifact page for that event And an automated link check reports 0 broken or external links; all links are internal destinations
Author/Role Attribution and Event Outcome Summary Visibility
Given each event includes author metadata and an outcome field When the timeline is rendered Then every event row displays Author Full Name, Role (Tenant|Manager|Vendor|System), and an Outcome Summary capped at 280 characters And if author is missing, Author = "FixFlow Automated" and Role = System And if the outcome is system-generated, the summary is prefixed with "[Auto]" And the same fields are repeated in the corresponding artifact section header
Fallback Rules for Missing or Ambiguous Time Zone Data
Given one or more events lack an explicit time zone offset When timestamps are normalized Then the property’s configured IANA time zone is applied as a default and marked "(assumed TZ)" next to the displayed time And all such assumptions are listed in an "Assumptions" appendix with counts by event type And timeline generation completes with 0 errors and 0 omitted events due to time zone issues And assumed-TZ events display a warning icon in the timeline
Digital Signing & Trusted Timestamping
"As an owner, I want the exported packet to be digitally signed and timestamped so that recipients can trust its authenticity and integrity."
Description

Applies organization-backed digital signatures to the PDF and ZIP exports and optionally adds a trusted timestamp to prove existence at a specific time. Embeds signature metadata (creator, certificate info, build ID) and a visible signature block on the PDF cover. Supports key rotation, revocation checks, and signature verification on download. Fails closed if signing cannot be completed and logs all signature operations for auditability.

Acceptance Criteria
Org-Signed PDF and ZIP Exports
Given an organization has a valid signing certificate configured And a user with Export Audit Pack permission initiates an export When the system generates the PDF and ZIP artifacts Then the PDF is signed as PAdES-B-B using the org certificate with SHA-256 or stronger And the ZIP includes a detached CMS (PKCS#7) signature file (.p7s) verifiable with standard tools (e.g., OpenSSL) And the full certificate chain (intermediate(s)) is embedded to enable trust path validation And validation succeeds in Adobe Acrobat (PDF) and OpenSSL (ZIP) without warnings
Optional Trusted Timestamp Application
Given a trusted TSA endpoint is configured and timestamping is enabled by policy or per-export selection When an export is signed with timestamping required Then an RFC 3161 timestamp token is applied (PAdES-B-T for PDF; timestamp on CMS signature for ZIP) And timestamp verification succeeds against the TSA certificate chain And if the TSA is unavailable or returns an invalid token, the export is aborted and no files are delivered
Embedded Signature Metadata
Given an export is being signed by user U for organization O with build identifier B When signing completes Then the signatures embed metadata fields: creator_user_id, creator_email, org_id, certificate_subject, certificate_serial, key_id (fingerprint), build_id, signing_software, signing_version, signing_time_utc And the PDF exposes these via signature properties and the ZIP includes a signature-metadata.json containing the same fields And API GET /exports/{id}/signatures returns the metadata for each artifact
Visible Signature Block on PDF Cover
Given a PDF export is generated When the PDF is signed Then the cover page displays a visible signature block showing: organization name, signer identifier, signing date/time (UTC), certificate issuer CN, and signature status placeholder And the block meets WCAG 2.1 AA contrast requirements And opening the PDF in Adobe Acrobat shows the signature as valid with the visible block intact
Key Rotation and Revocation Enforcement
Given multiple organization signing keys exist with defined validity windows and possible revocation states When an export is signed at time T Then the system selects the active key whose validity covers T according to policy And expired or revoked certificates are never used for signing And revocation status is checked via OCSP with CRL fallback at signing time, proceeding only on a 'good' status And key rotation events (activation/deactivation) are recorded including key_id and effective timestamps
Signature Verification on Download
Given a signed export exists When a user initiates a download via UI or API Then server-side verification runs prior to transfer, including chain building, revocation checks, and timestamp validation (if present) And if verification passes, the download proceeds and the response includes header X-Signature-Verification: pass with algorithm and key_id details And if verification fails (e.g., invalid chain, tampering, revoked cert), the download is blocked, UI shows 'Signature invalid' with reason, and API returns 409 with code SIGNATURE_INVALID And a verification report is retrievable at GET /exports/{id}/verification
Fail-Closed and Audit Logging
Given any signing or timestamping step fails or exceeds the configured timeout When an export is requested Then the export is aborted and no unsigned or partially signed artifacts are delivered And the user receives an error message with a correlation_id and retry guidance And an immutable audit log entry is written capturing: export_id, user_id, org_id, action, start_time_utc, end_time_utc, key_id, certificate_serial, tsa_endpoint, ocsp_result, crl_result, outcome, error_code, error_message (redacted) And audit logs are queryable via API and retained per the organization's retention policy
Export & Secure Delivery
"As a property manager, I want to export and securely share the audit pack via expiring links or cloud destinations with optional redaction so that I can control access and protect tenant privacy."
Description

Exports the packet as a single PDF and a ZIP containing the PDF, manifest, and original artifacts. Supports large-file handling (chunked ZIP, multi-part downloads), password protection, expiring access links, and optional watermarking. Delivers to configured destinations (email link, S3, Google Drive, OneDrive) and enforces role-based access with download logging. Offers PII redaction toggles and recipient-specific views to protect tenant privacy while meeting audit needs. Uses consistent, human-readable file naming and includes a cover sheet with a contents index.

Acceptance Criteria
One-Click Export: PDF and ZIP with Manifest
Given a user selects a unit/building/program and clicks "Export Audit Pack" When the export completes Then a single PDF is generated that includes a cover sheet and a contents index And a ZIP is generated that contains the PDF, a media manifest listing every artifact with file path, size, and SHA-256 hash, and all original artifacts in their original formats And the ZIP preserves the original folder structure by category (e.g., Work Orders, COIs, Invoices, Approvals, Media) And the export reports success with links/locations to both outputs
Large File Handling: Chunked ZIP and Resumable Multi-part Downloads
Given the total export size exceeds the configured chunk threshold (default 250 MB) When the ZIP is created Then the ZIP is split into sequential parts not exceeding the configured size and named with .zip, .z01, .z02… extensions And the system provides a checksum (SHA-256) for each part and the whole set Given a recipient downloads via the email link When a network interruption occurs Then the download resumes via HTTP Range requests without re-downloading completed bytes And a completed set is verified against checksums before marked as "Downloaded"
Secure Access: Password Protection and Expiring Links
Given password protection is enabled for the export When a recipient opens the PDF from any destination Then the PDF requires the configured password to open Given an access link with a 7-day expiry is issued When the link is accessed after expiry Then access is denied with an "Expired" message and no file bytes are served And the owner can regenerate a new link without regenerating the files
Delivery Destinations: Email Link, S3, Google Drive, OneDrive
Given destinations Email Link, S3, Google Drive, and OneDrive are configured When the export completes Then an email is sent to specified recipients containing a secure download link And the ZIP and PDF are uploaded to the specified S3 bucket/prefix and their URLs are returned And the ZIP and PDF are uploaded to the specified Google Drive and OneDrive folders and item links are returned And each destination reflects the expected file sizes and names And if any destination fails, the others still complete, and the user sees per-destination success/failure with a retry option
Role-Based Access Control (RBAC) Enforcement and Download Logging
Given RBAC is configured so only Owners and Managers can generate exports and only designated recipients can download When an unauthorized user attempts to access an export link or cloud-stored item Then access is denied with a 403/Access Denied response And all successful and failed access attempts are logged with timestamp, user/email, IP, user agent, file name, and outcome And the export owner can view and export the download log for that packet
PII Redaction Toggles and Recipient-Specific Views
Given PII redaction is toggled on and recipients are assigned profiles (e.g., Auditor, Insurer, Internal) When the export is generated Then the PDF and any included artifact copies match the recipient’s profile: PII fields (e.g., tenant name, phone, email, SSN/Tax ID if present) are masked or omitted per profile And artifacts flagged as containing PII are replaced with redacted copies in the recipient’s ZIP, with the manifest noting redaction And two different recipients receive distinct links that deliver their respective views, and a link for one profile cannot access another profile’s files And if watermarking is enabled, each page of the PDF displays a recipient-specific watermark (recipient email and link ID)
File Naming Consistency and Cover Sheet Contents Index
Given an export is created for Building "Maple Court", Unit "A-3", on 2025-09-18 When files are produced Then filenames are human-readable and follow the pattern: FixFlow_AuditPack_[Scope]_[Identifier]_[YYYYMMDD]_[vNN].pdf and .zip And names use only letters, numbers, underscores, and dashes, with spaces normalized to underscores And if a name collision occurs, the version number increments (v01, v02, ...) And the PDF cover sheet includes property/program, unit/building, date generated, date range covered, exporter identity, recipient profile, and a contents index listing each section with starting page and item counts

Privacy Redactor

Automatically detects and redacts faces, license plates, mail labels, and other PII in photos and documents before sharing. Preserves an access‑restricted original and logs who viewed or exported each version. Lets teams share evidence confidently without privacy violations.

Requirements

Real-time PII Detection & Redaction
"As a property manager, I want uploaded photos and documents to be automatically redacted for PII so that I can share evidence with tenants and vendors without risking privacy violations."
Description

Implement an automated pipeline that detects and redacts personally identifiable information (faces, license plates, mail labels, phone numbers, emails, street addresses, ID numbers) in images and multi-page PDFs at upload and prior to any share action within FixFlow. Use a hybrid approach combining computer vision models, OCR, and NER with configurable confidence thresholds and redaction styles (blur, pixelate, solid fill). Support common formats (JPEG/PNG/HEIC/PDF), multilingual OCR, rotation handling, and low-light/noisy images. Process single images in ≤2 seconds and 10-page PDFs in ≤15 seconds on average, with graceful degradation and background processing for larger files. Persist detection metadata (bounding boxes, classes, confidence) with each asset to enable review, versioning, and audit. Provide failure handling, retries, and fallback to manual redaction when confidence is below policy thresholds.

Acceptance Criteria
Auto Redaction on Upload
Given a user uploads a supported image (<=10MB) or PDF (<=50MB, <=20 pages) to FixFlow When the asset enters the intake pipeline Then the system creates an access-restricted original and a redacted derivative before the asset is available for sharing or external visibility And all detected PII regions (faces, license plates, mail labels, phone numbers, emails, street addresses, ID numbers) are obscured per the current workspace policy And the original is viewable only by users with the "View Original" permission And detections with confidence below the configured per-class threshold are not auto-redacted and are flagged for manual review
Pre-Share Redaction Enforcement
Given a user initiates a share action on an asset When the asset’s redaction status is Pending or Manual Review Required Then the share action is blocked and the user is shown a clear message indicating redaction must complete before sharing And no external link or export is generated Given a user initiates a share action on an asset with Completed redaction When the share is confirmed Then only the redacted derivative is shared by default And sharing the original is allowed only if the user has the "Share Original" entitlement and the workspace policy permits it
Detection Coverage & Confidence Thresholds
Given workspace thresholds are configured (e.g., faces=0.85, plates=0.90, text PII=0.92) When processing assets containing those PII classes Then detections with confidence >= their per-class threshold are auto-redacted And detections with confidence < their threshold are included in metadata and routed to manual review without auto-redaction And each detection record includes class label, bounding box coordinates, page/frame index, confidence score, model identifier, and timestamp
Performance SLAs & Background Processing
Given 100 sample single images (<=10MB) processed under normal load When measuring from upload to redacted derivative ready Then average processing time <= 2 seconds and 95th percentile <= 4 seconds Given 100 sample 10-page PDFs (<=50MB) When processed end-to-end Then average processing time <= 15 seconds and 95th percentile <= 25 seconds Given an asset exceeds 20 pages or 50MB When uploaded Then processing occurs in the background with a visible Processing status and user notification on completion And transient failures are retried up to 3 times with exponential backoff; on final failure the status is Processing Failed and the asset is routed to manual redaction
File Support, Rotation, and Multilingual OCR
Given uploads of JPEG, PNG, HEIC, and PDF files When validated Then supported files are accepted and queued; unsupported formats are rejected with a clear error message Given images/pages are rotated or skewed up to ±45° or captured in low-light/noisy conditions When processed Then orientation is auto-corrected and detection/OCR still executes; detected PII is handled per policy Given assets contain text in English and Spanish When OCR runs with automatic language detection Then phone numbers, emails, and street addresses present in either language are detected and redacted per policy
Redaction Style Configuration
Given a workspace admin opens Privacy Redactor settings When selecting redaction style = blur | pixelate | solid fill and adjusting parameters (e.g., blur radius, pixel size) Then subsequent processing applies the chosen style and parameters consistently across all PII classes And per-class overrides can be configured and are respected (e.g., solid fill for plates, blur for faces) And the redacted derivative uses irreversible transformations such that PII cannot be reconstructed by simple image processing And a preview reflects the configured style before saving changes
Metadata Persistence, Review, and Versioning
Given an asset has completed automated processing When retrieving its metadata Then detection records include bounding boxes (x, y, width, height), page number, class, confidence, model version, processing duration, redaction style, and processing timestamps And a redacted derivative version is created with a unique version ID linked to the original Given a reviewer opens the asset in review mode When they accept/reject detections or add manual redactions Then a new redacted derivative is generated as a new version, metadata is updated with reviewer ID, decision, and timestamp, and prior versions remain immutable
Originals Access Vault
"As an operations lead, I want originals kept in a secure vault with tightly controlled access so that sensitive information is protected while still available for legitimate investigations or insurance claims."
Description

Store unredacted originals in an encrypted, access-restricted vault separate from redacted derivatives, using KMS-managed keys and per-tenant isolation. Enforce role- and asset-level permissions with least privilege, time-bound access grants, reason capture on access, and optional manager approval. Provide a secure, watermarked, non-downloadable viewer for originals with click-to-request access flow. All access to originals must be logged and linked to the associated work order/case. Support data residency constraints and automated purge according to retention policies.

Acceptance Criteria
KMS Encryption and Per-Tenant Isolation of Originals
Given a tenant uploads an original file, When the system stores it, Then the object is encrypted at rest with the tenant’s KMS-managed key and storage metadata shows the correct KeyId. Given an original exists for tenant A, When an authenticated user from tenant B attempts direct or indirect access, Then the request is denied with 403 and no object metadata or path is revealed. Given the tenant’s KMS key is disabled or inaccessible, When an original upload is attempted, Then no plaintext is stored, the operation fails with a retriable error, and an alert is emitted.
Separation of Originals from Redacted Derivatives
Given an original and its redacted derivative exist, When a user with access to the derivative retrieves it, Then no access to the original is implicitly granted and original URLs remain undiscoverable. Given an API client holds a derivative URL, When it attempts path or identifier manipulation to access the original, Then the system returns 404/403 and does not expose the original’s location. Given storage listing permissions for derivatives, When a listing is performed, Then originals do not appear in the results and cannot be enumerated by any derivative-level principal.
Least-Privilege Role and Asset-Level Permissions
Given a user lacks Original:View for a specific work order asset, When they attempt to open the original, Then access is denied with 403 and an access-denied audit event is recorded. Given a user holds Original:View and is assigned to the same work order and property, When they access the original, Then access is granted within their role’s scope and tenant boundary. Given tenant policy restricts originals to asset-assigned users only, When a global admin not assigned to the work order attempts access, Then access is denied unless a valid time-bound grant exists.
Time-Bound Access Grants, Reason Capture, and Manager Approval
Given a user requests access to an original, When submitting the request, Then a non-empty reason of at least 10 characters is required or the request is rejected. Given tenant setting manager_approval_required=true, When a non-manager requests access, Then an approval task is created for a manager and the requester is notified of pending status. Given a manager approves within the SLA window, When approval is recorded, Then a time-bound grant is issued with the specified TTL and both reason and approver are captured. Given the grant TTL expires or the grant is revoked, When the user attempts to view the original, Then access is denied until a new grant is issued. Given a grant renewal is requested, When processed, Then a new reason must be supplied and a new TTL is applied.
Secure Watermarked Non-Downloadable Viewer
Given a user has a valid grant to view an original, When the viewer renders, Then a dynamic watermark overlays the content with user name, timestamp, tenant, and work order/case ID on all pages/frames. Given the viewer is open, When the user attempts to download, print, or save the original via UI controls, Then such controls are absent or disabled and the operation is blocked. Given a user attempts to fetch the original via direct URL or developer tools outside the viewer session, When the request lacks a valid short-lived viewer token, Then the response is 403 and no file bytes are returned. Given the viewer is interacted with via right-click or drag actions, When default save actions are triggered, Then they are prevented by the application.
Audit Logging Linked to Work Orders/Cases
Given any view, export attempt, or access request for an original, When the event completes (success or denial), Then an immutable audit record is stored with user, role, action, outcome, reason, approver (if any), grant ID, object version ID, timestamp, IP, and work order/case ID. Given an admin queries the audit log for a work order, When results are returned, Then all original access events are present and filterable by action, user, and date range. Given user roles or permissions change after an event, When historic audit records are viewed, Then they reflect the captured role and permissions at event time and are not altered retroactively.
Data Residency Enforcement and Retention-Based Purge
Given a tenant is configured with a residency region (e.g., EU), When originals and derivatives are stored, Then data at rest resides only in the configured region and object location metadata confirms regional storage. Given cross-region access occurs, When a user outside the region requests to view an original, Then the content is served without moving the stored object out of region and at-rest location remains unchanged. Given a tenant retention policy (e.g., 365 days after work order closure) is set, When the retention date passes, Then originals and derivatives are automatically purged and subsequent access attempts return 404 with a purge audit event recorded. Given a purge operation fails transiently, When the next scheduled job runs, Then the purge is retried until successful and completion is logged.
Redaction Audit Trail & Versioning
"As a compliance officer, I want a complete audit trail and version history of redactions and accesses so that I can demonstrate compliance and investigate incidents if needed."
Description

Maintain an immutable, tamper-evident audit trail of all detections, redaction actions, policy evaluations, views, downloads, and exports for both originals and redacted derivatives. Track who did what, when, from where (IP/device), and why (captured reason), and hash-chain entries for integrity. Support version history per asset, allowing multiple redaction passes and export variants with lineage back to the original. Provide exportable audit reports (PDF/CSV) for a given case, asset, time window, or recipient. Enforce retention policies and data minimization for logs while preserving integrity proofs.

Acceptance Criteria
Comprehensive Audit Entry Capture for All Actions
Given any of the following actions occurs on an asset (original or derivative): detection, redaction, policy_evaluation, view, download, export When the action is committed Then exactly one audit entry is appended containing: action_type, asset_id, asset_kind (original|derivative), version_id, actor_id or system_actor, actor_type (user|service), timestamp_utc (ISO 8601 with ms), source_ip, device_info/user_agent, reason (required for redaction, export, or policy override), request_id/correlation_id, result (success|failure), prev_hash, hash And the entry references the correct asset/version and passes schema validation And the action fails if the audit entry cannot be written (no operation without a log)
Tamper-Evident Hash Chain and Verification
Given a sequence of retained audit entries for a tenant When the verification API is called for a time/window or full range Then the service recomputes the chain from the first retained entry to the last and returns status=valid with head_hash matching the latest entry When any retained entry is modified, removed, or re-ordered Then the verification API returns status=invalid and identifies the first invalid entry_id and position And a downloadable integrity proof (range start/end, head_hash, signature) is produced for offline verification
Per-Asset Version History and Lineage
Given an original asset exists When multiple redaction passes and export variants are created Then version history records a monotonic version sequence per lineage and each derivative includes a parent_version_id linking back to its source And a lineage query from any derivative/export returns the full chain back to the original And attempts to create a derivative without a valid parent are rejected and logged as policy_evaluation result=failure
Scoped Audit Report Export (PDF/CSV)
Given a user with Audit.Export permission selects a scope (by case_id OR asset_id list OR time window OR recipient) and a format (PDF|CSV) When the report is requested Then the system generates a file containing only scoped entries with columns: timestamp_utc, action_type, asset_id, asset_kind, version_id, actor_id, source_ip, device_info, reason, result, entry_id, hash, prev_hash, head_hash And the report includes an integrity proof for the scoped range (head_hash and signed checkpoint) And the export action itself is logged with parameters used (scope, format, requester) And requesting a range beyond retention returns only available entries with a retention notice
Access Controls and View/Export Logging for Originals vs Redactions
Given the original asset is access-restricted When an unauthorized user attempts to view or export it Then access is denied with 403 and a policy_evaluation audit entry is appended including actor_id, asset_id, result=failure When an authorized user views or exports a redacted derivative Then a view or export audit entry is appended with actor_id, asset_id, version_id, timestamp_utc, source_ip, device_info, and reason if required by policy And no view/export proceeds if the corresponding audit entry cannot be written
Retention and Data Minimization with Integrity Preservation
Given a tenant-specific retention policy is configured When the retention job executes Then raw audit entries older than the retention window are purged or compacted, while daily signed checkpoints (anchor hashes) are retained for the purged periods And the verification API validates continuity across checkpoints and retained entries as valid And audit reports and queries exclude purged entries and display a retention notice when applicable And stored fields comply with tenant minimization settings (e.g., truncated IP or hashed device_id), verified by schema and automated tests
Write-Path Reliability and Atomicity
Given the audit datastore becomes unavailable during an action (e.g., redaction or export) When the system attempts to append the audit entry Then the system retries a bounded number of times; if still unsuccessful, the user-facing action is aborted/rolled back and an error is returned And an alert is emitted and the incident is logged for operations And background reconciliation confirms there are no state changes without corresponding audit entries for the affected interval
Policy-Based Redaction Rules
"As an account admin, I want configurable redaction policies per sharing scenario so that our team can balance privacy with operational needs without manual steps."
Description

Enable administrators to configure redaction policies by portfolio, property, team, or sharing context (e.g., tenant share, vendor dispatch, insurer submission). Allow toggling entity types to redact, setting confidence thresholds, choosing redaction styles, and defining exceptions based on consent or contractual requirements. Provide presets for common scenarios and sensible defaults to meet privacy regulations. Policies must be evaluated consistently in backend services and surfaced in UI for transparency, with change history and effective-dates. Validate policies before activation and support staged rollout with analytics on detection outcomes and false positives.

Acceptance Criteria
Multi-Scope Policy Configuration and Validation Before Activation
Given I am an administrator with Privacy Redactor permissions When I create a new policy and select a scope of Portfolio, Property, Team, or Sharing Context Then the system requires a unique name, scope, at least one entity type selection, and an effective start date Given I configure entity-type toggles (Faces, License Plates, Mail Labels, IDs, Documents, Other PII) with per-entity confidence thresholds and redaction style (blur, pixelate, box, mask) When I attempt to save the policy Then server-side validation ensures thresholds are numeric within 0–100, styles are supported, and no conflicting rules exist within the same scope and effective period Given policy validation passes When I activate the policy Then the policy is marked Active with version 1 and effective start date set; otherwise activation is blocked with field-level error messages Given sensible defaults are enabled When I omit a value for an optional threshold or style Then the system fills default values from the selected preset or global defaults and records the source of each default in the audit trail
Entity Type Toggles and Confidence Thresholds by Context
Given a policy scoped to Tenant Share, Vendor Dispatch, and Insurer Submission contexts When I set different toggles and thresholds per context Then each context persists independent settings and is applied only when matching that share context Given detection results contain entities with confidence scores When the policy is evaluated Then only entities at or above the configured threshold for that entity type and context are redacted; those below are left unredacted and logged Given a threshold outside 0–100 is entered When I save Then the save is rejected with a validation message and no changes are applied Given a context has no explicit threshold When policy is evaluated Then the global default threshold for that entity type is used and recorded in the evaluation log
Exceptions Based on Consent and Contract
Given a tenant or vendor consent record or contractual clause is attached to a unit, property, or work order with effective dates When I define an exception rule referencing that consent or contract Then the exception can be scoped to entity type, context, and duration and must include a justification reference Given an exception is active and matches the evaluation scope When redaction is evaluated Then the specified entities are not redacted and the exception ID and reason are attached to the audit log and UI explanation Given consent is revoked or expires When the same media is reprocessed or shared again Then the exception no longer applies and entities are redacted per policy Given a user attempts to add an exception without linked consent or contractual evidence When saving Then the system blocks the change and displays a required-evidence error
Presets and Sensible Defaults
Given system-provided presets for Tenant Share, Vendor Dispatch, and Insurer Submission When an admin applies a preset to a policy Then the policy prepopulates recommended entity toggles, thresholds, and styles that meet the configured privacy baseline Given global sensible defaults are enabled When a setting is not explicitly set in a policy Then the default from the selected preset or system baseline is applied at evaluation time and captured in the evaluation log Given an admin attempts to disable a baseline-required protection in a preset-based policy (e.g., faces for Tenant Share) When saving Then the change is rejected unless the admin provides an approved justification and role-based override, which is logged Given a preset is updated by the system in a new release When an admin edits an existing policy based on that preset Then they are prompted to adopt changes with a diff view and can accept all, some, or none; acceptance results in a new policy version
Consistent Backend Evaluation and UI Transparency
Given any media is processed for sharing When the backend evaluates redaction Then the same policy engine and policy version are used across all services, and the evaluation returns a deterministic decision with a policyVersionId Given a user is about to share redacted media When the UI renders the preview Then it displays the active policy name, scope, version, and a summary of entity types that will be redacted, with tooltips citing the specific rules Given a user requests an explanation for a redaction When viewing details Then the UI shows the detected entity type, detection confidence, threshold, rule identifier, and whether any exception applied
Change History and Effective Dates
Given an admin edits a policy When they save changes Then a new immutable version is created with who, when, change summary, and diffs of each field Given a future-dated policy version exists for a scope When the effective start time is reached Then the new version automatically becomes active and supersedes the prior version without reprocessing historical shares Given a past policy version is selected in the audit UI When viewing details Then all settings and rules are visible exactly as they were and can be restored to create a new version Given overlapping effective dates are configured within the same scope When saving Then the system blocks the save and highlights the overlap
Staged Rollout and Detection Analytics
Given an admin enables staged rollout for a policy When configuring cohorts Then they can target by percentage, property list, team, or user group, and select Shadow Mode (evaluate only) or Active Mode (apply redaction) Given Shadow Mode is enabled When media is processed Then detection outcomes are logged without redaction, including counts by entity type, confidence distributions, and policy decisions Given users provide feedback marking a redaction as correct or false positive When analytics are computed Then the system reports redaction rate, exception rate, and a false positive rate per entity type and context for the cohort period Given false positive rate exceeds a configured threshold during rollout When monitoring jobs run Then the system automatically pauses expansion and notifies admins with links to affected rules and suggested threshold adjustments Given rollout metrics meet success thresholds When an admin approves promotion Then the policy moves from staged to fully active for the selected scope and the promotion event is logged
Batch Redaction & Workflow Hooks
"As a property manager handling many units, I want batch redaction tied into my existing workflows so that large volumes of media are processed automatically without slowing me down."
Description

Integrate redaction into FixFlow’s intake and scheduling workflows with bulk processing for queues (e.g., new maintenance requests, technician uploads). Provide asynchronous jobs, progress indicators, and notifications/webhooks when redaction completes or requires manual review. Ensure redaction gates all external shares (emails, links, vendor portal posts) and approvals, with clear UI states and retry logic on failures. Expose API endpoints/events so partner systems can submit assets for redaction and retrieve results at scale.

Acceptance Criteria
Bulk Redaction for New Intake Queue
- Given new maintenance requests with attached images or documents enter the New Intake queue, When the queue auto-runs every 1 minute or a user clicks Redact All, Then all unprocessed assets are batched into a redaction job with a unique job_id. - Given a batch includes N assets, When processing begins, Then each asset is analyzed for faces, license plates, mail labels, and other PII and a redacted derivative is produced where PII is found. - Given an asset has no detectable PII, When processing completes, Then the asset is marked Not Needed and the original is retained as internal-only. - Given an asset was already redacted earlier, When the batch runs again, Then processing is idempotent and the asset is skipped without creating duplicate derivatives. - Given processing completes for the batch, When the user views the intake queue, Then each item shows the correct redaction status: Pending, Processing, Completed, Not Needed, Needs Review, Failed.
Bulk Redaction for Technician Uploads in Scheduling
- Given a technician uploads photos to a scheduled work order, When the upload finalizes, Then the assets are auto-enqueued into a redaction batch linked to that work order. - Given the work order is awaiting manager review, When redaction is Pending or Processing, Then the work order cannot be marked Ready to Share or Approved. - Given redaction completes, When the manager opens the work order gallery, Then only redacted derivatives are shown by default, with an internal-only indicator on originals. - Given additional uploads are added later, When they arrive, Then they trigger an incremental redaction batch without blocking already completed assets.
Asynchronous Job Processing and Progress Indicators
- Given a redaction job is created, When the job runs server-side, Then it continues if the user navigates away or logs out. - Given a redaction job with N total assets, When X assets have finished (success, not needed, needs review, or failed), Then the UI progress shows floor((X/N)*100)% and the current state (Queued, Processing, Completed, Completed with issues). - Given the user is viewing a queue or work order, When a job is running, Then progress updates at least every 5 seconds via push or polling without requiring manual refresh. - Given the application is reloaded, When returning to the job page, Then the last known progress and state are restored and continue to update.
Notifications and Webhooks for Completion and Manual Review
- Given a user has redaction notifications enabled, When a batch completes with all assets successful or not needed, Then the user receives an in-app notification with job_id, counts by status, and links to the results. - Given a batch contains assets requiring manual review, When analysis finishes, Then an in-app notification is sent and a webhook event redaction.needs_review is delivered to all active endpoints with signed payload including job_id and the asset_ids needing review. - Given a batch fails partially or fully, When processing stops or completes with failures, Then a webhook event redaction.failed is sent and the job state is set to Completed with issues. - Given webhooks are configured, When any event is sent, Then delivery is at-least-once with retries for 24 hours on non-2xx responses and events include a monotonically increasing sequence_id for ordering.
External Sharing and Approvals Gated by Redaction State
- Given a user attempts to email, generate a share link, or post to the vendor portal, When any included asset has redaction status Pending, Processing, Needs Review, or Failed, Then the action is blocked and the UI shows the blocking assets and resolution actions. - Given a work order approval is initiated, When any attached asset is not in Completed or Not Needed state, Then approval cannot be finalized and the approver sees a requirement to complete redaction. - Given sharing proceeds, When assets are included, Then only redacted derivatives are shared; originals are never distributed externally. - Given a share is generated, When the recipient accesses it, Then the content contains only redacted files and watermarks reflect that redaction has been applied.
Retry Logic and Failure Recovery
- Given a redaction task encounters a transient error, When the error is retryable, Then the system auto-retries up to 3 times with exponential backoff and logs each attempt. - Given a task exhausts retries, When it remains failed, Then the asset status is Failed and a user can click Retry to requeue just that asset without reprocessing the entire batch. - Given a batch contains a mix of successful and failed assets, When processing ends, Then successful derivatives remain available and only failed assets require retry or review. - Given the redaction service is temporarily unavailable, When it recovers, Then queued jobs resume without data loss or duplication.
Partner API for Batch Submission and Result Retrieval
- Given a partner system has a valid API token, When it POSTs a batch with up to 1,000 asset URLs to /api/redactions with an Idempotency-Key, Then the API returns 202 Accepted with job_id and initial state Queued. - Given a job is in progress, When the partner GETs /api/redactions/{job_id}, Then the API returns counts by status, overall state, and percent_complete. - Given assets are processed, When the partner GETs /api/redactions/{job_id}/assets, Then results include each asset_id, status, and URLs to redacted derivatives for completed assets, with pagination and cursor support. - Given the partner registers a webhook endpoint, When events occur (completed, needs_review, failed), Then events are delivered with HMAC-SHA256 signatures and include job_id, sequence_id, and per-asset statuses.
Secure Redacted Export & Sharing
"As a coordinator, I want to share redacted files securely with vendors and insurers so that they get the information they need without exposing PII."
Description

Generate redacted export artifacts for email, share links, and vendor/insurer portals while preserving readability and necessary metadata. Apply recipient-specific watermarks (name, timestamp, case ID), expiring links, optional passwords, and download rate limits. Maintain a cryptographic linkage between each export and its source version for auditability. Preserve original page order, DPI, and orientation; enforce file size and format constraints; and include a machine-readable manifest of redactions applied. Track delivery, access events, and revocation status.

Acceptance Criteria
Email Attachment Export Compliance
Given a case with redacted assets and a recipient name and case ID When the user selects Export as Email Attachment Then the generated attachment preserves original page order, orientation, and per-page DPI (>= original up to configured max) And the attachment format is one of the allowed types per configuration (e.g., PDF or ZIP) And total file size is ≤ the configured limit; if exceeded, the export is blocked with a clear error and remediation options (compress/split) And all redacted regions are irreversibly obscured with no recoverable underlying pixels And a recipient-specific watermark containing recipient name, case ID, and UTC timestamp appears on every page/image And a machine-readable redaction manifest is embedded and also attached alongside the artifact And a cryptographic linkage to the source version is embedded in the artifact metadata And an export event is logged with initiator, recipients, timestamp, artifact checksum, and outcome
Share Link Security Controls
Given a redacted export and a designated recipient When the user creates a share link with expiry, optional password, and download rate limits Then the link expires at the configured date/time and returns an explicit expired status thereafter And if a password is set, access requires the correct password meeting the configured complexity policy And download attempts are limited to the configured rate/quantity (e.g., N downloads per 24h); excess attempts are throttled/blocked and logged And recipient-specific watermarking is applied to all viewed/downloaded content And access events record timestamp, actor (if authenticated), IP, user agent, and outcome And upon revocation by the owner, subsequent accesses are blocked within 60 seconds and any active sessions are terminated
Vendor/Insurer Portal Submission Conformance
Given portal constraints (accepted formats, maximum file size, minimum DPI, page order) are configured for a vendor/insurer When the user submits a redacted package to that portal Then the generated package conforms to the portal’s constraints and preserves page order and orientation And images meet or exceed the configured minimum DPI without degrading readability And the redaction manifest is included per the portal’s required attachment or metadata field And submission succeeds with a returned confirmation/tracking ID captured in the audit log And on pre-validation failure, the system prevents submission and surfaces specific remediation guidance
Redaction Manifest Content and Schema
Given redacted assets exist When an export is generated Then a manifest.json is produced and embedded/attached with the export And it lists, per page/image, each redaction with type (face, plate, label, other PII), coordinates, size, confidence, and redaction method And it includes export ID, source asset IDs, source content hash, algorithm version, and generation timestamp (UTC) And the manifest validates against the published JSON schema and is signed for tamper-evidence And its checksum matches the embedded reference in the export
Cryptographic Linkage and Auditability
Given an immutable source version is stored When any export is created Then the export embeds the source content hash and a unique export ID signed with the system private key And the audit log stores the mapping {export ID ↔ source version ID/hash} with timestamp and initiator And verification tooling can validate the signature and detect any mismatch between export and source And tampering with the export or manifest causes verification to fail with a clear error
Recipient-Specific Watermark Rendering
Given recipient identity, case ID, and current UTC timestamp When any export (email attachment, share link, portal package) is generated or viewed Then a visible watermark containing recipient name/email, case ID, and UTC timestamp appears on every page/image And watermark opacity, size, and placement comply with configuration (e.g., 20–30% opacity, diagonal across center or footer) and avoid covering critical content And automated overlap detection verifies the watermark does not obscure key text/annotations; otherwise placement adjusts automatically And an embedded identifier tied to the export ID enables detection of attempted watermark removal/cropping
Delivery and Access Event Tracking
Given an export is sent via email or accessed via share link When delivery occurs or an access attempt is made Then email delivery status (accepted, deferred, bounced) is recorded with provider metadata (message ID, SMTP code) And open/click events (where allowed) are recorded with timestamps And share link access/download events are recorded with IP, user agent, success/failure, and bytes served And revocation status and timestamp are recorded and displayed alongside the export’s event history And an audit report can be generated filtered by case ID, recipient, export ID, and date range
Manual Redaction Review Tools
"As a support agent, I want quick manual review tools to fix edge cases so that I can ensure accuracy when automated redaction is uncertain."
Description

Provide an in-app review workspace to verify detections, adjust masks, add/remove redactions, and tune blur intensity before sharing. Show overlays with labels and confidences, keyboard shortcuts for speed, zoom/pan, before/after comparison, undo/redo, and accessibility-compliant controls. Support multi-page navigation, quick approve-all for high-confidence runs, and save as new version with notes. Surface common false positives for training feedback loops.

Acceptance Criteria
Verify and Adjust Auto-Detected Redactions
Given an image with auto-detected PII regions, when the review workspace opens, then each detection displays a mask overlay with its label and a numeric confidence between 0.00 and 1.00 (two decimals). Given a detection overlay is selected, when the reviewer drags its edges or corners, then the mask resizes within image bounds and the change persists immediately. Given a detection overlay is selected, when the reviewer drags inside the mask, then the mask moves within image bounds and the change persists immediately. Given a detection overlay, when the reviewer toggles its visibility, then the mask hides/shows without deleting the detection. Given a mask is adjusted, when the preview renders, then the redacted preview updates within 200 ms of mouse release.
Manual Add/Remove with Undo/Redo
Given the reviewer selects Add Mask: Rectangle or Polygon, when a shape is drawn on the image, then a new mask is created and listed in the detections panel. Given an existing mask, when Delete/Remove is invoked, then the mask is removed from the current page and the detections count updates accordingly. Given at least one prior action, when Ctrl+Z/Cmd+Z is pressed, then the most recent action is undone; and pressing Ctrl+Y/Cmd+Shift+Z redoes it. Given a sequence of edits, when undo/redo is used repeatedly, then the system maintains a history of the last 50 actions for the current document session. Given the reviewer navigates away from and back to a page within the session, when undo/redo is used, then that page’s action history is preserved.
Blur Intensity Tuning with Live Preview
Given a selected mask or the All Masks scope, when the blur intensity slider (0–100) is adjusted or a numeric value is entered, then the preview updates within 150 ms and the displayed value matches the applied intensity. Given blur intensity is set to 100, when the preview renders, then the underlying content within the mask is fully obscured with no recognizable facial features or readable text at normal viewing size. Given a document is saved and reopened, when a previously saved mask is selected, then its stored blur intensity value is restored and displayed.
Zoom/Pan and Before/After Comparison
Given an image is loaded, when the reviewer zooms via Ctrl+=/Ctrl+-, trackpad pinch, or UI controls, then the zoom level changes between 10% and 800% in 10% increments and remains centered on the cursor/focus point. Given the image is zoomed, when the reviewer holds Space and drags, then the canvas pans without changing the zoom level and remains within scrollable bounds. Given masks exist, when Before/After is toggled via the UI or the B shortcut, then the view switches between original and redacted; in split view both are visible with synchronized zoom and pan.
Keyboard and Accessibility Coverage
Given the review workspace has focus, when documented shortcuts are used (Undo Ctrl/Cmd+Z; Redo Ctrl+Y/Cmd+Shift+Z; Zoom In Ctrl+=; Zoom Out Ctrl+-; Next Page ] ; Prev Page [ ; Toggle Before/After B; Toggle Mask Visibility V; Add Rectangle R; Add Polygon P; Approve-All A; Help ?), then each shortcut performs its action and does not trigger a conflicting browser default. Given the reviewer presses ?, when the shortcuts overlay opens, then all available shortcuts are listed with their actions. Given the workspace is navigated via keyboard only, when Tab/Shift+Tab is used, then all interactive controls and overlays are reachable in a logical order with visible focus indicators. Given a screen reader is active, when a detection overlay receives focus, then its label and confidence are announced via accessible name/description. Given any text or control is displayed, when measured, then color contrast ratios meet or exceed WCAG 2.2 AA (4.5:1 for text, 3:1 for large text and UI components).
Multi-Page Navigation and Approve-All High-Confidence
Given a multi-page document, when the reviewer navigates via Next/Previous, keyboard ([/]), or thumbnails, then the selected page loads and is indicated as active; cached page transitions complete within 300 ms. Given detections with confidence ≥ 0.95 exist, when Approve All High-Confidence is applied to the current page or entire document, then those detections are marked approved and locked, and the action is undoable. Given a page has no remaining unreviewed detections, when the page list renders, then that page is marked Reviewed; pages with pending items display a Pending indicator with a count.
Save New Version with Notes and Training Feedback
Given edits have been made, when Save as New Version is confirmed, then a new redacted version is created while preserving the original; metadata includes version ID, timestamp, editor ID, optional note (3–200 chars), total masks, and per-mask blur intensity. Given the save completes, when the Versions list is viewed, then the new version appears at the top with its note; access to the original remains restricted to authorized roles only. Given the reviewer marks any detections as False Positive/False Negative and checks Submit Feedback on save, when the save completes, then those items are queued to the training feedback log with label and count. Given a save occurs, when the audit log is inspected, then an entry exists for the action including editor ID, version ID, timestamp, and summary of changes.

Retention Guard

Applies retention schedules per jurisdiction or funding source, with legal holds for disputes or claims. Warns before purge, offers one‑click export with hash manifest, and issues deletion certificates. Controls storage bloat while staying compliant with records policies.

Requirements

Jurisdiction-Aware Retention Policy Engine
"As a property manager operating across multiple cities, I want FixFlow to automatically apply the correct retention period for each record based on the property's jurisdiction and funding source so that I remain compliant without manual tracking."
Description

Implements an automated policy engine that applies retention schedules based on property jurisdiction, funding source, and record type (e.g., work orders, photos, invoices, tenant communications). Maps each record to governing rules using property location and funding metadata, supports effective dates, triggers (job completion, payment posted, lease end), exceptions, and rule precedence. Calculates purge eligibility and executes defensible, scheduled deletions. Includes versioned policies, simulation mode, and full auditability. Integrates with FixFlow’s data model and event bus to react to lifecycle events and ensures consistent enforcement across web, mobile, and APIs.

Acceptance Criteria
Map and Apply Retention on Work Order Completion
Given property P with jurisdiction=CA-US and fundingSource=HUD, and a policy rule R exists: recordType=work_order, trigger=JobCompleted, retentionPeriod=3 years, effectiveFrom=2025-01-01, version=1.0 When work order WO123 for P transitions to status=Completed at 2025-10-05T10:00:00Z via the event bus Then the engine selects R based on P.jurisdiction and P.fundingSource and recordType=work_order And it writes retention metadata on WO123: policyVersion=1.0, ruleId=R.id, trigger=JobCompleted, retentionUntil=2028-10-05T10:00:00Z, jurisdiction=CA-US, fundingSource=HUD And it emits an audit event RetentionApplied with WO123 id, ruleId, policyVersion, appliedAt timestamp, actor=system And the operation is idempotent: repeating the same Completed event does not create duplicate metadata or audit entries And the metadata is visible via web, mobile, and API GET /work-orders/WO123 with identical values
Precedence and Exceptions Resolution for Conflicting Policies
Given active rules for recordType=photo: Rg (global, retention=1 year), Rj (jurisdiction=CA-US, retention=2 years), Rf (fundingSource=HUD, retention=5 years), and a property-level exception Rp (propertyId=P1, retention=7 years) When photo PH1 associated to property P1 is created and its trigger event occurs Then the engine resolves precedence as property_exception > funding_source > jurisdiction > global And it applies Rp to PH1 and sets retentionUntil accordingly And the audit record includes resolvedPrecedence=property_exception and candidateRules=[Rp,Rf,Rj,Rg] with their reasons And if a required dimension is missing on the record (e.g., fundingSource=null), the engine applies the highest-precedence rule determinable and records missingDimensions=[fundingSource] in the audit
Legal Hold Prevents Purge and Resumes on Release
Given invoice INV9 has retentionUntil=2025-10-01T00:00:00Z and is eligible for purge And a legal hold HOLD-22 is placed on INV9 at 2025-09-20T12:00:00Z with reason="Tenant dispute" When the nightly purge job runs on 2025-10-02T02:00:00Z Then INV9 is not deleted and is marked purgeDeferredDueToHold=true And an audit event PurgeSkippedHold is recorded with holdId, invoiceId, scheduledRunId When HOLD-22 is released on 2025-11-01T09:00:00Z Then INV9 becomes purge-eligible on the next scheduled run and is deleted if no other holds exist And the deletion audit links to the prior hold and includes holdReleasedAt timestamp
Versioned Policies with Effective Dates and Simulation Mode
Given policy v1 for recordType=tenant_communication (trigger=LeaseEnd, retention=2 years) effectiveFrom=2024-01-01 and policy v2 (retention=3 years) effectiveFrom=2025-12-01 When a lease ends at 2025-11-15T00:00:00Z Then v1 is applied and retentionUntil=2027-11-15T00:00:00Z When a lease ends at 2025-12-02T00:00:00Z Then v2 is applied and retentionUntil=2028-12-02T00:00:00Z And existing records under v1 are not recalculated on v2 activation unless setting recalcOnPolicyUpdate=true When simulation mode is run for date=2025-12-10 across tenant_communication Then the system produces a report with counts, example record IDs, and predicted retentionUntil deltas by policy version without persisting changes or deleting data And the simulation run is auditable with inputs (asOfDate, policies compared) and outputs (candidate count, by-version breakdown)
Scheduled Purge with Warning, Export, and Deletion Certificate
Given a policy for recordType=work_order has warningWindowDays=14 and purgeSchedule=02:00 server time daily And work order WO55 has retentionUntil=2025-10-20T02:00:00Z When the system time reaches 2025-10-06T02:00:00Z Then a pre-purge warning is issued to the assigned manager via email and in-app alert, and WO55 appears in the Upcoming Purges list with D-14 When the manager clicks Export for the batch containing WO55 before purge Then the system produces a single downloadable package with all records/files and a manifest.json including batchId, recordIds, policyVersion, retentionUntil, file SHA-256 hashes, and a manifest SHA-256 When the purge job runs at 2025-10-20T02:00:00Z Then WO55 and associated files are deleted And a deletion certificate is generated with batchId, deletedCount, startedAt, completedAt, manifestHash, and is available read-only via UI and API
Event-Driven Triggers Across Record Types
Given the engine subscribes to lifecycle events on the event bus: WorkOrderCompleted, PaymentPosted, LeaseEnded When events arrive for records with matching property jurisdiction and funding metadata Then retention mapping and metadata application occur within 60 seconds of event receipt And duplicate events with the same idempotency key are ignored without side effects And events with missing required context are dead-lettered with reason codes and visible in monitoring
Cross-Channel Enforcement and Full Audit Trail
Given records are created and updated via web app, mobile app, and public API When a retention rule is applied, updated due to metadata change, held, simulated, or purged Then an immutable audit entry is recorded for each action with fields: actionType, recordId, recordType, policyVersion, ruleId, actor (userId or system), sourceChannel (web/mobile/api/event), timestamp (UTC), before/after retentionUntil, reason And all channels surface the same retention metadata and audit history for a given record And after purge, attempts to access the record via any channel return 404 NotFound with reason=Purged and a link to the deletion certificate batchId
Legal Hold Management
"As a compliance officer, I want to place and manage legal holds on relevant maintenance records so that they are preserved during disputes or investigations."
Description

Provides a secure workflow to create, scope, and manage legal holds that suspend retention and purge actions. Allows authorized users to place holds by case ID with reasons, notes, attachments, and optional expiration, scoped to entities (properties, tenants, work orders, vendors) and their related records. Displays hold indicators in UI, blocks deletions, and notifies stakeholders of expiring holds. Offers API endpoints for external legal systems, role-based access control, and immutable auditing of all hold activities.

Acceptance Criteria
Create legal hold with scope and metadata
Given I have the Hold Creator permission and am logged into FixFlow When I create a new legal hold via the UI providing a unique Case ID, Reason, optional Expiration Date (empty or a future date), Notes, up to 10 attachments (PDF/JPG/PNG, each <= 25 MB), and select a scope of one or more entities (properties, tenants, work orders, vendors) Then the system validates required fields and rejects missing Case ID or Reason with inline errors And rejects Expiration Date in the past and oversized/unsupported attachments And prevents duplicate Active holds with the same Case ID within the account And saves the hold with Status = Active and stores attachments securely with AV scan pass And applies the selected scope immediately And displays a success confirmation with the Case ID And writes an immutable audit log entry capturing actor, timestamp, channel=UI, IP, action=Create, and the hold metadata hash
External API endpoints for legal hold lifecycle
Given I possess a valid API token with scope hold:manage When I call POST /v1/legal-holds with payload including caseId, reason, optional expirationDate, notes, attachments (pre-signed upload), and scope (entityType + IDs) Then I receive 201 Created with holdId, status=Active, and echo of normalized fields And if I retry the same request with the same Idempotency-Key, I receive 200 OK with the original resource And invalid payloads return 400 with field-level errors; unauthorized requests return 401; insufficient permissions return 403 And GET /v1/legal-holds?status=Active&caseId=... supports pagination (limit, cursor) and filtering by entityType and date And PATCH /v1/legal-holds/{holdId} supports updating notes and expirationDate only (not caseId or scope) with validation And POST /v1/legal-holds/{holdId}:release changes status to Released and requires a releaseReason And all API actions emit webhooks (legal_hold.created|updated|released) with delivery retries and signatures And every API action writes an immutable audit entry with actor=clientId and channel=API
Scope cascade and retention suspension across related records
Given an Active legal hold scoped to a Tenant entity and its ID(s) When the system evaluates retention for records linked to that tenant Then the hold is applied to the tenant and all directly related records, including work orders, messages, documents, photos, invoices, and vendor assignments And any scheduled retention purge for those records is suspended while the hold remains Active And nightly retention jobs report the count of records skipped due to holds and reference the Case ID And the cascade respects additional scopes if multiple holds overlap without duplication of flags And an immutable audit entry is recorded for the cascade application with counts per record type
Deletion and purge prevention for held records (UI, API, and background jobs)
Given a record is under at least one Active legal hold When a user attempts to delete it via the UI Then the delete action is blocked with a clear message referencing the Case ID(s) And when a client calls DELETE via API for the same record Then the API responds 423 Locked with a machine-readable error code legal_hold_active and the Case ID(s) And when a scheduled purge job encounters the record Then the job skips deletion, logs the skip with the Case ID(s), and continues without failure And bulk-delete operations skip held records and report per-item results And each blocked attempt is captured as an immutable audit event with actor, channel, and target record ID
UI indicators and discoverability of active legal holds
Given I have permission to view legal holds When I open any entity detail page affected by an Active hold Then I see a prominent hold indicator (icon/banner) showing Case ID, Reason, Status, and a link to view hold details And list views display a hold badge on affected rows and support filtering by Has Legal Hold = true and by Case ID And hovering the indicator shows tooltip text summarizing scope and expiration (if set) And the hold details panel masks attachments until explicitly opened by an authorized viewer And viewing hold details creates an immutable audit trail entry (action=View, redacted content)
Expiring hold notifications and renewal
Given an Active legal hold has an Expiration Date set When the hold reaches T-30, T-7, and T-1 days before expiration Then the system sends deduplicated notifications to the hold owner and configured stakeholders via email and in-app, including Case ID, scope summary, and renewal link And if the owner renews, they can extend the Expiration Date to a future date with a required Renewal Reason And successful renewal updates the hold without changing Case ID or scope and triggers a confirmation notification And if the hold expires without renewal, its status changes to Released-Expired and retention resumes on the next cycle And all events (notify, renew, expire) are written to the immutable audit log with timestamps and recipients
Hold release and resumption of retention
Given I am the hold owner or an administrator with Hold Manager permission When I release an Active legal hold by providing a Release Reason Then the hold status changes to Released and the hold indicator is removed from affected records within 5 minutes And retention clocks resume, making eligible records available for purge on the next scheduled run per their original retention policies And stakeholders receive a release notification containing Case ID and a summary of impacted records counts And attempts to modify a Released hold’s historical data are blocked; only notes may be appended And an immutable audit entry is recorded capturing before/after status, actor, and counts of records transitioning from held to releasable
Pre-Purge Review & Notifications
"As a small property manager, I want advance warnings and an easy way to review and approve deletions so that I can prevent accidental loss of needed records."
Description

Generates a time-bounded queue of records eligible for deletion and sends email/in-app warnings to designated reviewers ahead of purge. Provides a review workspace with filters, sampling, CSV export, approval history, and optional dual-approval. Supports snooze/override for specific items, blackout windows to avoid business hours, and dry-run estimates of storage savings. Logs reviewer decisions and integrates with FixFlow’s notification and scheduling services for reliable execution and retry handling.

Acceptance Criteria
Time-Bounded Eligible Deletion Queue Creation
Given retention schedules, jurisdictions/funding sources, and a lookahead period are configured When the eligibility job runs on schedule or is manually triggered by an authorized user Then a queue is created containing only records whose purge date falls within the lookahead period And records under legal hold are excluded And records with active snoozes that end after the purge date are excluded And each queued item includes policy ID, jurisdiction, source, purge date, size, and eligibility reason And the job is idempotent so re-running within the same cycle produces identical queue membership And the queue summary shows total count, per-policy counts, and the next scheduled run time
Pre-Purge Notifications to Designated Reviewers
Given designated reviewers and notification lead time are configured with email and in-app channels When new items enter the eligible queue Then an email and an in-app notification are sent to each reviewer within 15 minutes unless within a blackout window And if within a blackout window, notifications are deferred and dispatched at the first permitted time And notifications include scope (portfolio/property), item counts, direct link to the review workspace, and CSV export link And delivery uses FixFlow Notification Service with at least 3 retries over 24 hours and recipient delivery status is recorded
Review Workspace: Filters, Sampling, and CSV Export
Given eligible items exist and a reviewer opens the review workspace When the reviewer applies filters by property, jurisdiction, policy, age range, size range, decision status (new/approved/rejected/snoozed), and reviewer Then the visible list updates within 2 seconds for up to 50,000 items and matches the filters And random sampling by percentage or count returns a uniformly random subset seeded for reproducibility And exporting the current filtered set to CSV completes within 30 seconds for up to 100,000 rows and includes visible columns plus internal IDs
Optional Dual-Approval with Approval History
Given dual-approval is enabled at the portfolio or policy level When Reviewer A approves an item Then the item moves to Awaiting Second Approval and cannot be purged until a different Reviewer B approves And the same user cannot provide both approvals And a rejection by either reviewer removes the item from the current purge batch And approval history records timestamps, user IDs, decisions, reasons, and item version immutably
Snooze and Override Controls
Given an eligible item is visible in the review workspace When a reviewer snoozes the item with a reason and a duration or until-date Then the item is excluded from the current purge batch and will not trigger notifications until the snooze expires And reviewers can edit or cancel the snooze with a complete audit trail And attempting to override an item under legal hold is blocked with an explanatory message And overriding an item marks it Do Not Purge This Cycle and excludes it from execution
Dry-Run Storage Savings Estimate
Given a reviewer selects a filtered set or the entire queue and requests a dry-run When the estimate is computed Then no data is deleted And the system returns total projected storage reclaimed, per-storage-bucket breakdown, and item count And results are saved with timestamp and parameters for later comparison And for queues up to 100,000 items the estimate completes within 60 seconds or shows progress until completion
Scheduling, Execution, Retry, and Audit Logging
Given purge execution time and blackout windows are configured When the scheduled purge job runs Then only items with required approvals and without active snooze or legal hold are executed And if the start time falls within a blackout window, the job is deferred to the next permitted window And operations are idempotent so re-running a job does not purge already deleted items And transient failures are retried per FixFlow Scheduling Service policy and partial failures are reported to reviewers And an immutable audit log records reviewer decisions, notifications, job runs, per-item outcomes, and errors with export available
One-Click Export with Hash Manifest
"As a landlord facing an audit, I want to export all records subject to a hold or upcoming deletion with a verifiable hash manifest so that I can provide tamper-evident evidence to regulators."
Description

Enables export of any purge batch or selected records as a packaged archive containing normalized data (JSON/CSV) and associated binaries (photos, attachments), accompanied by a cryptographic SHA-256 manifest per file and an overall manifest. Includes metadata such as policy versions applied, requester identity, timestamps, and scope. Supports secure download links, optional delivery to customer S3, encryption at rest and in transit, resumable downloads, chunking for large exports, and an API for automation. All exports are fully audited.

Acceptance Criteria
UI One-Click Export to Secure Download with Hash Manifest
Given an authorized Records Admin views a purge batch in Retention Guard When they click "Export" and select JSON and/or CSV formats Then a single archive supporting files >4 GB is generated containing: normalized data in the selected formats, all associated binaries (photos, attachments), a per-file SHA-256 manifest, and an overall manifest at the archive root Given the archive and manifests are produced When SHA-256 is computed for each file listed and for the archive itself Then each value matches the corresponding entry in the manifests exactly Given the export completes When the system issues the download link Then the link requires authentication or a time-limited signed URL, uses TLS 1.2+ on access, and expires at the configured TTL (default >= 24 hours) Given the export completes When reviewing the audit trail Then an entry exists with export_id, actor_id, scope, policy_versions, requested_at, completed_at (UTC ISO 8601), and outcome=success
Deliver Export to Customer S3 with Server-Side Encryption
Given the customer has configured an S3 destination (bucket, prefix) and server-side encryption settings (SSE-S3 or SSE-KMS) When a user selects "Deliver to S3" for an export Then the system uploads the archive and all manifest files to the specified bucket/prefix using the configured encryption and returns the S3 object key(s) Given the upload completes When verifying the S3 object properties Then encryption is enabled (SSE-S3 or SSE-KMS), ACLs are private, and no public access is granted by the exporter Given the export resides in S3 When the archive is downloaded from S3 and its SHA-256 is computed Then it matches the overall manifest value included in the package Given the export is delivered to S3 When reviewing the audit log Then an entry exists capturing destination bucket and prefix (with no secrets), actor_id, timestamps, and outcome=success
Resumable Chunked Download for Large Exports
Given an export archive >= 50 GB is available via secure link When a download is interrupted after at least 10 GB is transferred Then resuming the download uses HTTP Range (206 Partial Content) and completes without restarting from byte 0 Given the resumed download completes When computing the archive SHA-256 Then it matches the overall manifest value Given the download link has a 48-hour TTL When resuming within the TTL Then the resume succeeds; and when attempting after expiry, Then access is denied with 403 and the attempt is logged
API-Triggered Export with Idempotency and Job Status
Given a client with export permissions and Idempotency-Key X sends a POST request to create an export with defined scope and delivery option When the request is accepted Then the API returns 202 with a job_id and status=pending, and an audit log entry is created with requester_identity=client_id Given the same client retries the same request within 24 hours with Idempotency-Key X When processed Then the API returns 200 with the original job_id and does not create a new export job Given the job completes When the client retrieves job status Then the response includes completed_at (UTC ISO 8601), policy_versions applied, export_scope, counts of records and files, delivery details (link or S3), and a pointer to the manifest file(s)
Manifest and Metadata Completeness and Accuracy
Given an export is completed When inspecting the overall manifest file Then it includes: algorithm="SHA-256", policy_version identifiers applied, requester_identity (user_id or client_id), requested_at and completed_at in UTC ISO 8601, export_scope description, counts of records/files, and a list of each file path with its size and sha256 Given any required metadata field is missing or invalid When validation runs before delivery Then the export fails with a descriptive error, no link or S3 delivery is issued, and the audit log records outcome=failed with reason Given records under legal hold are in the export scope When the manifest is generated Then those records are flagged with legal_hold=true in the metadata
Access Control, Authorization, and Encryption at Rest
Given a user without the Records Admin or Data Exporter role attempts to initiate an export via UI or API When they try to proceed Then the UI disables the action or the API returns 403 Forbidden, and an audit entry is recorded Given an export is staged for download When inspecting the storage configuration Then the archive and manifests are encrypted at rest (AES-256 or equivalent) with keys managed by the platform, and access to keys is restricted to the export service Given a secure download link is shared with another authenticated user in the same organization When that user attempts access Then access is granted only if they have export download permission; otherwise 403 is returned and the attempt is logged
Deletion Certificates & Immutable Audit Trail
"As an operations lead, I want an official deletion certificate and immutable audit trail for each purge so that I can demonstrate compliant records disposition."
Description

Automatically issues signed deletion certificates after each purge, detailing record counts, identifiers, policy references, approvers, timestamps, and a hash summary of items deleted. Stores certificates and related job logs in an append-only audit store to ensure non-repudiation. Provides certificate download, email distribution, and an audit API/UI for regulatory inquiries. Retains certificates according to configurable compliance timelines independent of content retention.

Acceptance Criteria
Auto-issuance of signed deletion certificate post-purge
Given a retention purge job completes successfully When the job finalizes Then a deletion certificate is generated within 2 minutes and assigned a unique certificateId linked to the jobId And the certificate is cryptographically signed with the platform signing key and includes a trusted timestamp And the signature verifies successfully against the published public key And the certificate record is persisted to the append-only audit store And the purge job summary exposes a link to the certificate download endpoint And any certificate generation failure flags the job as "Attention Required" and retries up to 3 times with error details logged
Certificate content completeness and hash summary integrity
Given a purge job targets items governed by a retention policy and includes an approver When the certificate is generated Then the certificate includes: total deleted count, list or range of identifiers, policy reference(s), approver identity, jobId, started/ended timestamps, and environment/tenant identifiers And the certificate includes a SHA-256 hash summary computed over the lexicographically sorted list of deleted identifiers And recomputing the hash from the identifiers reproduces the certificate's hash value And the certificate conforms to schema version X.Y validated against the JSON Schema with no errors And the total deleted count equals the number of items confirmed deleted by the job logs
Append-only audit storage with tamper-evident controls
Given certificates and purge job logs are stored in the audit store When any API or UI attempts to update or delete an existing certificate or job log Then the operation is rejected with HTTP 403 and no mutation occurs And each stored record includes a previous-record hash to create a verifiable chain And requesting an integrity check returns a chain hash that validates all records since the last checkpoint And all read and write access to audit records is captured with actor, timestamp, and purpose code
Certificate download and email distribution
Given an authorized user views a completed purge job When they click Download Certificate Then a bundle containing the certificate (JSON/PDF), detached signature file, and hash manifest is delivered within 5 seconds And verifying the signature of the downloaded certificate succeeds And the download event is logged in the audit store with actor and timestamp When the user sends the certificate via email to specified recipients Then recipients receive an email within 2 minutes containing a secure link that expires after 7 days And access to the link requires authentication or possession of a one-time token, and each access is audited
Audit API and UI retrieval for regulatory inquiries
Given an auditor with the Regulatory Auditor role and scoped tenant access When they query certificates by date range, policy reference, property, or jobId via API/UI Then the system returns matching results within 3 seconds for up to 10,000 records with pagination And each result includes certificateId, jobId, policy reference, counts, approver, timestamps, and download links And requesting a specific certificate returns the full certificate payload and related job logs And access controls enforce least privilege; unauthorized requests receive HTTP 403; nonexistent resources return HTTP 404 And the audit query itself is recorded with query parameters (redacted where sensitive) and response metadata
Independent retention schedule for certificates and logs
Given certificate retention is configured to 7 years and content retention for the same items is 3 years When the content reaches end-of-life and is purged at 3 years Then the corresponding deletion certificate remains accessible until 7 years elapse And changing certificate retention to 8 years updates future purge scheduling for certificates without altering content retention And when the certificate retention period expires with no legal holds, the system appends a purge-marker record and removes access to the certificate and related logs while preserving chain integrity And attempts to purge a certificate under an active legal hold are blocked and logged with reason
Storage Bloat Controls & Policy Impact Dashboard
"As a portfolio manager, I want visibility into storage consumption and the impact of retention policies so that I can optimize costs while staying compliant."
Description

Offers a dashboard showing storage consumption by entity type (photos, videos, messages, documents), growth trends, largest contributors, and projected savings from upcoming purges. Includes policy simulation to model the impact of proposed retention changes before deployment and provides recommendations within compliance bounds. Supports per-property and portfolio views, thresholds with alerts, and exportable reports. Integrates with FixFlow media storage and analytics to drive cost optimization while maintaining compliance.

Acceptance Criteria
Portfolio Storage Overview Dashboard
Given a user with View Storage permission and a portfolio up to 150 units, When they open the Storage Dashboard for the last 30 days, Then total storage and breakdown by entity type (photos, videos, messages, documents) display with percentages summing to 100% and totals matching the authoritative backend within max(1%, 100 MB). Given the user applies a property filter and changes time range (7/30/90/365 days), When filters are applied, Then charts and tables update to reflect the selection within 2s P95 for above-the-fold and 5s P95 full page. Given the Largest Contributors widget, When rendered, Then it lists the top 50 items with size, entity type, property, last activity date, and a link to the source, and sorting by size desc is default. Given analytics aggregation, When data is shown, Then a freshness timestamp is visible and the snapshot age is ≤ 24 hours.
Projected Purge Savings View
Given active retention schedules and legal holds, When the user opens Upcoming Purges with a 30-day lookahead, Then projected reclaimed storage (GB) and cost savings are displayed by entity type and property, excluding all items under legal hold. Given the same configuration, When compared to a system dry-run purge for the same window, Then item counts and sizes differ by ≤ 2%. Given the user changes the lookahead to 7/30/90 days, When applied, Then projections recalculate within 2s P95. Given the user clicks Export, When the export completes, Then CSV and JSON files download including item IDs, sizes, and a SHA-256 hash manifest.
Retention Policy Simulation
Given a user with Manage Retention permission, When they propose changes to retention (per property, jurisdiction, or funding source), Then the simulator returns estimated storage impact (GB), affected item counts, projected monthly savings, and earliest purge dates. Given governing minimums and active legal holds, When a proposed change violates a rule, Then the simulator blocks execution and shows a clear error citing the blocking rule(s). Given a valid proposal, When Run Simulation is clicked, Then results return within 10s P95 for portfolios ≤ 150 units and can be saved with name, timestamp, and author. Given saved simulations, When Compare is used, Then the proposal and current policy are shown side-by-side with deltas for GB, item counts, and cost.
Compliance-Bounded Recommendations
Given current storage patterns and active policies, When the user opens Recommendations, Then up to 5 policy adjustments are suggested, each with expected GB reduction, monthly cost savings, risk score, and cited governing rules proving compliance. Given a recommendation, When Simulate This is clicked, Then the simulator opens pre-populated and returns results within 10s P95 with ≤ 2% variance from the recommendation estimates. Given user action, When a recommendation is dismissed, Then it is hidden for 30 days and logged to audit with user, timestamp, and reason (optional). Given daily analytics refresh, When recommendations are regenerated, Then they are based on the latest snapshot and the snapshot ID is shown.
Threshold-Based Alerts
Given an admin defines thresholds (e.g., MoM storage growth > 10%, property total > 500 GB, single item > 2 GB), When saved, Then monitoring begins within 15 minutes at an hourly cadence. Given a threshold breach, When detected, Then an in-app banner appears within 15 minutes and an email is sent to subscribed users; duplicate alerts are suppressed for 24 hours per threshold-scope pair. Given an alert, When viewed, Then it shows current value, threshold, scope (property/portfolio), time of breach, and deep links to relevant dashboard sections. Given user permissions and preferences, When alerts are delivered, Then only users with View Storage receive in-app and only users opted into Email Alerts receive emails; acknowledgements clear banners but are retained in alert history for 90 days.
Exportable Storage Reports
Given a selected scope (portfolio or property) and date range, When the user clicks Export Report, Then PDF and CSV are generated containing storage by entity type, growth trends, largest contributors, projected purge savings, alert summary, and any selected simulation results. Given export generation, When processing exceeds 30s, Then a progress indicator is shown and, upon completion within 60s P95, files are provided for download and via email link. Given a completed export, When inspected, Then metadata includes generated timestamp, scope, filters, data snapshot ID, and SHA-256 hash manifest for CSV files; included data respects user permissions. Given audit requirements, When an export is generated, Then an audit log entry is created with export type, scope, user, and hash values.

Auditor Portal

Generates a time‑boxed, read‑only portal scoped to selected units, dates, or programs. Auditors can search, filter, and leave clarifying comments without altering records; all access is logged. Eliminates email back‑and‑forth and shortens audit cycles with transparent, self‑serve review.

Requirements

Time-Boxed Read-Only Auditor Access
"As an auditor, I want time-limited, read-only access so that I can review records without risk of altering data."
Description

Provide an auditor role that enforces read-only behavior across UI and API, constrained to a defined start/end date. When a portal is active, all mutating actions (create, update, delete, file overwrite) are disabled, action buttons are hidden or disabled, and server-side authorization blocks writes. Access automatically expires at the configured end time or can be revoked immediately by an owner. Integrates with existing authentication/authorization layers, adds a portal context and expiry enforcement middleware, and presents a clear countdown/status banner within the portal. Outcome: auditors can safely review data without any risk of altering records, and access cleanly terminates on schedule.

Acceptance Criteria
UI Enforces Read-Only State During Active Auditor Portal
Given an authenticated auditor is using an active portal within its valid time window And is viewing records within the portal scope When any list, detail, or file view is rendered Then all mutating controls (Create, Edit, Delete, Approve, Schedule, Assign, Upload/Replace File, Comment, Bulk Actions) are hidden or disabled with a 'Read-only auditor access' indicator And any direct navigation to a mutating route (e.g., /edit, /new, /delete, /upload) within the portal returns a 403 page in the portal shell And no client-side action results in a persisted change on the server
Server-Side Authorization Blocks All Writes for Auditor Role
Given a request is made by an authenticated auditor during an active portal session When the request uses POST, PUT, PATCH, or DELETE to any API endpoint (including file overwrite endpoints) Then the server responds 403 Forbidden with error code 'auditor_read_only' and a correlation id And the response body contains no sensitive data beyond the error And the operation produces no data changes or file mutations And the attempt is recorded in the audit log with method, route, status, and portal_id
Scoped Data Visibility and Search Within Portal Context
Given a portal is created with a defined scope of Units, Date Range, and Programs And an auditor is authenticated to that portal When the auditor searches, filters, paginates, exports, or opens deep links Then only records within the defined scope are returned or visible And attempts to access out-of-scope resources return 404 or 403 without leaking existence metadata And aggregate counts, totals, and exports reflect only the scoped data
Automatic Expiration and Countdown Banner
Given a portal has a configured end time in the future When an auditor uses the portal prior to expiry Then a status banner displays the remaining time and updates at least every 60 seconds And the banner state changes to 'Access expired' when the end time is reached When any request is made at or after the end time Then the server responds 403 with error code 'portal_expired' And all active sessions/tokens for the portal are invalidated within 60 seconds of expiry
Immediate Owner Revocation of Active Portal
Given an owner chooses to revoke an active portal When the owner confirms revocation Then all auditor sessions for that portal are invalidated within 30 seconds And subsequent requests return 403 with error code 'portal_revoked' And the portal UI displays 'Access revoked by owner' on the next refresh or navigation And an audit log entry records revocation with timestamp, owner_id, and portal_id
Auth Integration and Middleware Enforcement
Given the platform's existing authentication and authorization are in place When an auditor signs in via the standard auth flow Then the issued session/JWT contains role=auditor, portal_id, scope, and expiry claims And middleware enforces read-only and scope checks on every request (UI and API) And requests missing or invalid claims receive 401 with a WWW-Authenticate header And routes outside the portal context (owner/manager app) return 404 or 403 to the auditor
Scoped Portal Creation (Units/Dates/Programs)
"As a property manager, I want to generate a portal scoped to specific units, dates, or programs so that auditors only see relevant records."
Description

Enable property managers to create a portal scoped to specific units, date ranges, and program tags (e.g., subsidy type, compliance program). The scope applies uniformly to work orders, approvals, messages, photos, invoices, SLAs, and technician schedules so auditors only see relevant records. The scope is enforced server-side at the query layer using a portal ID and is summarized at the top of the portal so auditors understand the boundaries. Integrates with FixFlow’s existing data models and tagging; includes validation to prevent empty or overly broad scopes. Outcome: focused, least-privilege access that speeds review and protects unrelated data.

Acceptance Criteria
Create Scoped Auditor Portal
Given I am a property manager with access to FixFlow And I select at least one of Units, Date Range, or Program Tags in the portal creation form When I click "Create Portal" Then a portalId is generated and persisted with the exact selected scope And the system returns a shareable portal URL containing a non-guessable token And the portal opens with a scope summary at the top showing Units, Date Range, and Program Tags exactly as selected
Server-Side Scope Enforcement
Given an auditor accesses any portal page or API using the portal URL When the backend queries data with the provided portalId Then only records whose Unit is in scope AND the entity's scoped date intersects the Date Range AND Program Tags intersect the scoped tags are returned And any request for an out-of-scope record by ID returns 403 Forbidden And requests missing a valid portalId return 401 Unauthorized And aggregate counts and totals reflect only scoped records
Uniform Scope Across Entities
Given a portal is created with a specific scope When the auditor views Work Orders, Approvals, Messages, Photos, Invoices, SLAs, or Technician Schedules Then each list and detail view shows only items associated to in-scope Units and Programs with activity dates within the scoped Date Range per entity date mapping And cross-links between entities never escape the scope (following links stays within the portal and only shows in-scope data) And exports or downloads initiated from the portal include only scoped data
Scope Validation and Guardrails
Given the portal creation form is displayed When the manager attempts to create a portal with no Units, no Date Range, and no Program Tags Then the Create action is disabled with an inline message: "Select at least one scope dimension (Unit, Date Range, or Program)" And when a selection would exceed configured MaxAuditScope thresholds (e.g., Units > MaxUnits OR EstimatedRecords > MaxRecords OR DateRangeDays > MaxDays) Then the system blocks creation and shows a specific error listing which threshold(s) would be exceeded And thresholds are read from configuration and can be overridden per environment for testability
Scope Summary Banner
Given an auditor is in the portal When any page within the portal loads Then a persistent summary banner is visible at the top And it displays: the count of Units and their identifiers (truncated with "View all" if >5), the Date Range in ISO format, and the list of Program Tags And the summary values exactly match the persisted scope for the portalId And the banner meets accessibility requirements (ARIA landmarks, keyboard navigable, contrast AA)
Program Tag and Data Model Integration
Given scope includes one or more Program Tags When records are fetched for the portal Then inclusion is determined via existing FixFlow associations: Work Orders, Messages, Photos, Approvals, SLAs, and Technician Schedules inherit Program Tags from their associated Unit or parent Work Order; Invoices inherit from the originating Work Order or Unit And records with no Program Tag association are excluded when Program Tag filters are present And records are included when any of their tags intersect the scoped Program Tags
Advanced Search & Filters
"As an auditor, I want to search and filter across scoped records so that I can quickly find items that require review."
Description

Provide full-text search and facet filtering across all scoped records, including tickets, events, communications, attachments, vendors, costs, and SLA statuses. Support filters such as status, category, emergency vs. scheduled, cost range, vendor, created/closed dates, and SLA breach flags, with sort options and saved views per portal. Leverage indexing for performance and paginate to maintain responsiveness. Integrates with the portal scope so results never leak outside authorized data. Outcome: auditors can quickly locate evidence and exceptions without manual requests.

Acceptance Criteria
Full-Text Search Across Scoped Records
Given an auditor is signed in to a time-boxed Auditor Portal scoped to specific units, dates, or programs And the scoped dataset contains tickets, events, communications, attachments, vendors, costs, and SLA statuses containing the term "leak" When the auditor enters "leak" in the global search and submits Then the first page of results (25 items by default) returns within 2 seconds at the 95th percentile And results include only records within the portal scope And results include any record type where the term matches ticket titles/descriptions, event notes, communication subjects/bodies, attachment file names, vendor names, cost descriptions, or SLA status notes And records outside the portal scope are not returned
Facet Filtering by Status, Category, and Emergency
Given a result set with various statuses, categories, and emergency vs scheduled flags When the auditor selects Status in {Open, Closed}, Category = Plumbing, and Emergency = Emergency Then only records matching (Status is Open OR Closed) AND Category is Plumbing AND Emergency is true are returned And removing any one filter expands results accordingly And the filter state persists when navigating pages or changing sort options
Cost Range, Vendor, and Date Filters
Given tickets with associated total costs, linked vendors, and created/closed dates within the portal scope When the auditor sets Cost Range Min = 250 and Max = 1000, Vendor = "Acme Plumbing", Created Date = 2025-01-01 to 2025-03-31 Then results include only records where ticket total cost is between 250 and 1000 inclusive, vendor matches "Acme Plumbing", and created_at is between 2025-01-01 and 2025-03-31 inclusive in the portal’s timezone And records lacking a total cost are excluded when a cost range is applied And clearing the Vendor filter allows records from any vendor
SLA Breach and Emergency Filters
Given some tickets have SLA breach flags set and some are marked Emergency or Scheduled When the auditor applies filters SLA Breach = True and Emergency = Scheduled Then only records with breached SLA and Emergency = Scheduled are returned And toggling SLA Breach = False returns only records with no breach And removing the SLA Breach filter returns both breached and non-breached as constrained by other active filters
Sorting, Pagination, and Responsiveness
Given a result set of at least 200 records within scope When the auditor selects Sort by Relevance (default), Created Date Desc, Closed Date Asc, Cost Desc, and Vendor Name Asc sequentially Then the order updates accordingly without changing the total result count And pagination is available with page sizes of 25, 50, and 100 (default 25) And navigating between pages returns consistent, non-duplicated records for the same query and filter state And first-page responses return within 2 seconds p95 and subsequent page fetches within 1.5 seconds p95
Saved Views Per Portal and Scope Enforcement
Given an auditor has configured a query with search text, selected facets, sort, and page size When the auditor saves this as a named view within the Auditor Portal Then the saved view is available only within the same portal instance for the duration of the portal’s active time window And applying the saved view restores the exact search text, filters, sort, and page size And if the portal scope is reduced after saving, applying the view returns only records within the new scope without error And sharing a saved view URL cannot expose records outside the portal scope
Inline Clarifying Comments (Non-Mutating)
"As an auditor, I want to leave clarifying comments on records so that managers can provide explanations without changing the original data."
Description

Allow auditors to add clarifying comments anchored to specific records (e.g., a work order, approval step, photo) without changing the underlying data. Comments are stored in a separate, immutable comment stream marked as auditor_comment, support threading and mentions, and include status (open/resolved) to drive closure. Notify record owners/managers of new comments via email/in-app, and surface resolution history. Exports include comments with timestamps and authors. Outcome: questions are handled in context, eliminating email chains while preserving record integrity.

Acceptance Criteria
Anchor Auditor Comment to Record Without Mutation
Given an auditor is authenticated in a time‑boxed, read‑only Auditor Portal with access to a specific record (work order, approval step, or photo) When the auditor submits a new clarifying comment with body text and selects the record anchor Then the system creates a new comment entry flagged auditor_comment with anchor_type, anchor_id, author_id, created_at (UTC), and unique comment_id And the comment is stored in a separate, immutable comment stream and immediately visible in the record’s comment pane And the underlying record’s domain fields and updated_at/updated_by remain unchanged
Immutability and Access Logging of Auditor Comments
Given an auditor_comment exists When any user attempts to edit or delete the comment via UI or API Then the system rejects the action with a 403/permission error and no changes are persisted And the audit log records comment creation, views, and failed modification attempts with user_id, timestamp (UTC), and action And reportable logs can be filtered by auditor_comment events within the portal scope
Threaded Replies and Mentions with Scoped Notifications
Given an auditor_comment exists on a record When a user posts a reply in the same thread Then the reply is saved with parent_comment_id, author_id, and created_at (UTC) and displayed nested under the parent in chronological order And when the reply text contains an @mention of an eligible user with access to the record Then the system validates the mention, links it to the user_id, and queues in‑app and email notifications with a deep link to the thread within 5 minutes And mentions of users without access are disallowed and omitted from the saved text
Comment Status Lifecycle and Resolution History
Given a top‑level auditor_comment is Open When a record owner or manager marks the comment Resolved with an optional resolution note Then the comment status changes to Resolved and a resolution event is appended with resolver_id, timestamp (UTC), and note And if any participant posts a new reply after resolution Then the thread status automatically reopens to Open and a reopen event is appended And the UI displays a chronological status history for the thread
Owner and Manager Notifications for New Auditor Comments
Given a new top‑level auditor_comment is created on a record When the comment is saved Then all record owners and managers receive an in‑app notification immediately and an email notification within 5 minutes containing the record identifier, comment author, snippet, and a deep link And duplicate notifications for the same event are not sent to the same recipient And notification delivery status (queued, sent, failed) is recorded per recipient
Scoped Export Includes Auditor Comments and Resolution History
Given an auditor generates an export within the Auditor Portal scope (selected units, dates, or programs) When the export is generated Then each record in the export includes an Auditor Comments section containing for each comment: comment_id, anchor_type, anchor_id, author_name, author_role, created_at (UTC), status, parent_comment_id/thread_id, and full text And the export includes status transition events (resolved/reopened) with actor and timestamp (UTC) And only comments within the selected scope are included And all timestamps in the export are formatted as ISO 8601 UTC
Contextual Rendering and Anchor Navigation
Given a comment is anchored to a photo or approval step When a user opens the thread from the comment list or a notification deep link Then the UI scrolls to and highlights the anchored entity (photo or step) in read‑only mode and shows the comment thread in a side panel And the anchor metadata (e.g., photo filename or step name) is displayed with the comment header And no edit controls for the underlying record are shown in the Auditor Portal
Comprehensive Access Logging & Export
"As a compliance officer, I want complete access logs and exports so that I can demonstrate audit trail integrity."
Description

Capture and persist an immutable audit trail of all portal activity, including successful/failed logins, resource views, searches, downloads, and comments, with timestamps, user identity, IP, and user agent. Provide an exportable log (CSV/JSON) per portal with filters by date and event type. Include retention settings aligned to organization policy and safeguards against tampering. Integrates with existing logging pipeline and is visible to portal owners. Outcome: transparent, defensible evidence of who accessed what and when.

Acceptance Criteria
Authentication Events Logged
Given an auditor attempts to access a portal When a login succeeds Then an event "auth.login.success" is recorded with portal_id, auditor_id, timestamp (UTC ISO 8601), ip, user_agent, and session_id Given an auditor attempts a login with invalid credentials When the attempt fails Then an event "auth.login.failure" is recorded with portal_id, attempted_identifier, timestamp, ip, user_agent, failure_reason, and no session_id Given an authenticated auditor logs out or their session expires When the session ends Then an event "auth.logout" is recorded with portal_id, auditor_id, timestamp, ip, user_agent, and session_id
Event Coverage: Views, Searches, Downloads, Comments
Given an auditor views a resource (e.g., work order, unit, attachment) When the detail view loads Then a "resource.view" event is recorded with resource_type, resource_id, portal_id, auditor_id, timestamp, ip, user_agent, and view_context Given an auditor performs a search in the portal When results are returned Then a "search.run" event is recorded with portal_id, auditor_id, timestamp, ip, user_agent, query_string (PII-redacted), applied_filters, and result_count Given an auditor downloads a file or export When the download begins Then a "download.start" event is recorded with portal_id, auditor_id, timestamp, ip, user_agent, artifact_type, artifact_id/export_id, filename, and bytes_expected And when the download completes Then a "download.complete" event is recorded with bytes_sent and status=success/failure Given an auditor posts a clarifying comment When the comment is saved Then a "comment.create" event is recorded with portal_id, auditor_id, timestamp, ip, user_agent, comment_id, resource_id, and content_hash (not plaintext)
Per‑Portal Scope and Owner Visibility
Given a portal owner opens the access logs for a specific portal When the logs page loads Then only events with that portal_id are displayed with columns: timestamp (UTC), event_type, portal_id, actor (name/email), resource_type/id, ip, user_agent Given the owner needs to analyze activity When they apply filters by date range, event type, actor, resource, or IP Then the results are constrained accordingly and pagination reflects the filtered set Given a non-owner user attempts to access the logs When they navigate to the logs URL or API Then access is denied with HTTP 403 and the attempt is logged as "auth.authorization.failure" with portal_id, timestamp, ip, and user_agent
Per‑Portal Export: CSV and JSON with Filters
Given a portal owner selects a date range and event types for a portal When they request an export Then an export job scoped to that portal_id and filters is created and an "export.created" event is logged Given the export completes When the owner downloads the files Then both CSV and JSON formats are available and each includes a header with portal_id, generated_at (UTC), filter_summary, record_count, and SHA-256 checksum And each record contains timestamp, event_type, actor_id/email, portal_id, resource_type/id, ip, user_agent, and event-specific metadata And the download is logged as "export.downloaded" with portal_id, actor, timestamp, and file_format Given date range and event type filters are applied When the export is generated Then only events within the inclusive date range and selected event types are included
Immutability and Tamper Safeguards
Given a log entry exists When any user or admin attempts to edit or delete it via UI or API Then the operation is rejected (HTTP 405/403) and no modification occurs Given a log segment is written When the integrity verification endpoint is called Then the system returns a cryptographic checksum or hash-chain proof that validates the segment is unchanged since write Given privileged storage access is misused to alter underlying data When nightly integrity verification runs Then discrepancies are detected, a security alert is issued to the configured contacts, and a "log.integrity.alert" event is recorded with affected range and portal_id Given new events are appended over time When queried sequentially Then event identifiers are monotonically increasing and no unexpected gaps indicate deletion beyond enforced retention
Retention Policy and Legal Hold
Given an organization configures a retention period (e.g., 90/180/365 days) for portal logs When the policy is saved Then it is applied to new and existing entries and a "retention.updated" event is recorded with prior and new settings Given a log entry exceeds the retention period and is not on legal hold When the retention job runs Then the entry is purged from primary storage and a "retention.purge" summary event is recorded with count and date range Given a legal hold is placed on a portal's logs When retention jobs run Then entries under hold are preserved until the hold is removed Given a user queries or exports logs beyond the retention window When results are returned Then no entries older than the configured TTL are included
Logging Pipeline Integration
Given the system commits a portal activity event When forwarding to the existing logging pipeline Then the event is delivered with schema fields (tenant_id/org_id, portal_id, event_type, timestamp, actor, ip, user_agent, metadata) within 120 seconds of commit Given the external pipeline is unavailable When forwarding fails Then the system retries with exponential backoff for at least 24 hours and records internal "pipeline.forward.failure" events; upon success, a "pipeline.forward.success" event references the retried batch Given a SIEM user compares counts When they query the pipeline and the portal export for the same portal_id and time window after delivery has completed Then event counts and types match across both systems for that window Given schema evolution occurs When a new event version is forwarded Then a version field is present and events remain backward-compatible with no data loss
Secure Invite & Access Controls
"As a property manager, I want to invite auditors with secure, expiring links and optional SSO so that only authorized individuals can access the portal."
Description

Offer a portal invitation flow that sends tokenized, expiring access links to auditors, with optional SSO (SAML/OIDC) and configurable 2FA. Support immediate revoke, resend, and role changes by portal owners; enforce rate limiting, CAPTCHA on repeated failures, and optional IP allowlists. Display clear portal state (Active/Expired/Revoked) and allow regeneration of tokens without changing scope. Server-side verification ensures all requests carry a valid portal context and unexpired token/identity. Outcome: only authorized individuals gain access, and owners maintain tight control.

Acceptance Criteria
Tokenized Invite Link Generation, Delivery, Redemption, and Expiration
Given a portal owner creates an invite with defined scope (units, date range, programs) and expiry T, When the invite is saved, Then a single-use, tokenized link is generated with expiry T and an email is sent to the auditor within 60 seconds. Given the auditor clicks the invite link before T and the token is unused, When the link is opened, Then the server validates the token signature, expiry, and scope and grants a read-only session bound to the portal context, and logs the access event (timestamp, IP, user agent, user id). Given the token has already been redeemed, When the same link is used again, Then access is denied and a 410 invalid-token error is shown and logged; no data is returned. Given current time is after T, When the link is used, Then access is denied as Expired, the portal state is shown as Expired to the owner, and the attempt is logged. Given the owner regenerates the token, When the new link is issued, Then the scope remains unchanged and all prior tokens are immediately invalidated.
SSO (SAML/OIDC) and Configurable 2FA Enforcement
Given SSO (SAML or OIDC) is configured and required for the invite, When the auditor clicks the invite link, Then they are redirected to the configured IdP and upon a valid assertion the portal session is established without FixFlow credentials prompt. Given SSO is not configured and portal 2FA policy = Required, When the auditor authenticates with email and password, Then a second factor (TOTP/SMS) is required and only a correct code within 5 attempts completes login. Given portal 2FA policy = Optional and the auditor has 2FA enabled, When they authenticate, Then a second factor challenge is required; if the auditor has not enabled 2FA, no challenge is presented. Given a new device or 30 days have elapsed since trusting a device, When the auditor authenticates, Then a 2FA challenge is required. Given SSO is configured with IdP-enforced MFA, When the IdP assertion includes an MFA context, Then the portal does not require an additional 2FA step.
Immediate Revoke, Resend, and Role Change Controls
Given the portal owner clicks Revoke, When the action is confirmed, Then all active auditor sessions for that portal are terminated within 10 seconds, new requests return 403 Revoked, tokens are invalidated, and the portal state is set to Revoked. Given the portal owner clicks Resend, When the action is confirmed, Then a new tokenized link is generated and emailed to the auditor, and all prior tokens are invalidated; scope remains unchanged. Given the portal owner changes the auditor’s role, When the change is saved, Then permissions update server-side immediately and apply on the next request. Given a revoked portal is later reactivated, When the owner regenerates a token, Then the auditor can access with the original scope and updated role.
Rate Limiting and CAPTCHA on Repeated Failures
Given 5 failed authentication or token redemption attempts occur from the same IP within 15 minutes, When the next attempt is made, Then a CAPTCHA is required before processing and failures return 429 with no authentication processing. Given 20 failed attempts occur from the same IP within 15 minutes, When additional attempts are made, Then requests are blocked for 15 minutes with 429 Too Many Requests. Given an auditor successfully completes authentication, When the session is established, Then identity-based failure counters reset and IP-based counters decay per a sliding window. Given automated or suspicious traffic does not complete the CAPTCHA, When requests are made, Then authentication is not attempted and the events are logged.
Optional IP Allowlist Enforcement
Given the portal owner enables an IP allowlist with valid CIDRs, When the auditor attempts access from a non-allowed IP, Then access is denied with 403 and the event is logged; no portal data is returned. Given the auditor attempts access from an allowed IP and all other checks pass, When they use a valid token, Then access is granted. Given the allowlist is edited, When changes are saved, Then the new rules take effect within 60 seconds for new and existing sessions. Given an invalid CIDR is entered, When the owner saves the allowlist, Then validation prevents saving and an error is shown.
Portal State Visibility and Token Regeneration
Given a portal invite exists, When the owner views the portal management page, Then the portal state (Active, Expired, Revoked) is displayed and reflects real-time state. Given the invite reaches its expiration time, When no user action is taken, Then the portal automatically transitions to Expired and auditors cannot access without a regenerated token. Given the owner clicks Regenerate Token, When the new token is issued, Then the scope (units, dates, programs) is unchanged and all previous tokens are invalidated; portal state remains unchanged. Given an auditor uses an older token after regeneration, When the link is opened, Then access is denied and a replaced-token message is shown and logged.
Server-Side Portal Context and Token Integrity Enforcement
Given any request to the auditor portal, When the portal context ID or token is missing, invalid, expired, or revoked, Then the server returns 401 or 403 and no portal data is included in the response. Given a request attempts to access resources outside the invite scope (units, dates, programs), When the server processes the request, Then the request is denied with 403 and the scope violation is logged. Given client-side parameters are modified to elevate permissions or change scope, When the request is sent, Then the server ignores client parameters and enforces permissions solely from the server-side portal context. Given a session is idle for the configured timeout, When a new request is received, Then the session is invalidated and re-authentication is required via a valid token and identity.
PII Redaction & Role-Based Field Masking
"As a landlord, I want sensitive tenant data masked for auditors so that we meet privacy requirements while enabling effective review."
Description

Mask sensitive tenant and payment information by default for auditor sessions, including phone numbers, email addresses, payment details, exact GPS locations, and other regulated fields. Provide configurable masking rules per portal and ensure all exports, screenshots, and comment notifications respect masking. Log any unmask actions (if permitted) with justification and time limit. Support basic image redaction for photos (e.g., auto-blur faces/identifiers) when enabled. Integrates with the presentation layer and download services to prevent leakage. Outcome: privacy is preserved while enabling effective audit review.

Acceptance Criteria
Default PII masking in auditor portal UI
Given an auditor is authenticated via an Auditor Portal link When they view any list, detail, search results, or activity screen within the portal scope Then all regulated PII fields (tenant phone numbers, tenant email addresses, payment details, exact GPS coordinates, government IDs) render fully masked as placeholders and contain no original characters And when the auditor uses hover, tooltip, title, print view, or accessible labels on masked elements Then no raw PII is revealed and screen readers announce a non-PII label (e.g., "masked") And when the auditor copies masked values or their container Then the clipboard contains only the masked placeholder And then masking is consistent across pagination, infinite scroll, and client-side caching
Per-portal configurable masking rules
Given an owner creates or edits an Auditor Portal with a masking configuration (fields to mask, fields eligible for unmask with justification, image redaction on/off) When the portal token is used to access records Then the configured rules are enforced across UI, API, and downloads for that portal only And when the configuration is updated and saved Then new sessions and subsequent requests in existing sessions apply the updated rules without exposing previously masked data And when a field is configured as "always masked" Then no portal user can unmask it regardless of other permissions And when no custom configuration is provided Then the default policy masks all regulated fields defined by the platform
Masked exports and notifications
Given an auditor triggers an export (CSV, PDF, JSON) or uses in-app Download, Print, or Generate Report within the portal When the file is generated or the print preview is shown Then all regulated fields are masked identically to the UI and no raw PII appears in the output And then filenames, HTTP headers, and document metadata contain no raw PII And given an auditor posts a comment that references record fields When email or webhook notifications are sent Then masked values are preserved in subject and body and no raw PII is included And then links in notifications honor portal scope and masking rules on open
Justified, time-boxed unmasking and immutable audit log
Given the portal configuration permits unmasking for specific fields When an auditor requests to unmask a field Then the UI requires a justification text of at least 20 characters and sets a time-box per field+record to the configured duration (e.g., 15 minutes) And then the unmask applies only to the requesting user, that record and field, and only within the current portal And then an immutable audit log entry is created capturing portal ID, user ID, IP, record ID, field, justification, start and end timestamps, and scope And when the time limit expires or the user navigates away Then the value is re-masked automatically And when concurrent unmask session limits are exceeded (configurable) Then the request is denied with a clear message and no PII is revealed And given an admin audit view When the unmask log is exported Then entries exactly match events and include no raw PII beyond the temporarily unmasked values
Automatic image redaction for photos
Given image redaction is enabled for the portal When an auditor views or downloads a photo or PDF attachment through the portal Then faces and machine-detected identifiers (e.g., license plates, government IDs, payment cards) are blurred so that text is unreadable at 200% zoom and faces are unrecognizable And then redacted derivatives are served; originals remain unchanged in storage and are never delivered to the auditor And then EXIF/XMP metadata including GPS coordinates is stripped from delivered files And when viewing thumbnails, galleries, fullscreen, or zoom Then redaction persists consistently at all zoom levels and resolutions And when processing images <= 1920x1080 Then p95 redaction latency per image is <= 1s for up to 10 concurrent images
End-to-end leakage prevention in UI, API, and metadata
Given any API or service used by the Auditor Portal When records are fetched Then responses return masked values and never include raw PII in JSON bodies, HTML, headers, URLs, query strings, or cookies And when inspecting the DOM and page source Then no raw PII appears in hidden elements, data-* attributes, preloaded caches, or client logs And when copying, printing, or using browser accessibility tools Then only masked values are exposed And then server/application logs for portal requests do not contain raw PII in request/response payloads or structured logs
Fail-closed behavior on masking/redaction errors
Given the masking or redaction subsystem fails or times out When the auditor attempts to view or export affected content Then no unmasked data is displayed or downloaded; a non-PII error placeholder is shown and the operation is blocked And then the event is logged with portal ID, user ID, correlation ID, and error details without including raw PII And when the subsystem recovers Then normal masked behavior resumes without requiring a new portal link And then automated retries (up to 3 with exponential backoff) occur before showing the error

ThreadSense

Transforms messy SMS/WhatsApp threads into clean, structured tickets automatically. Extracts unit/address, issue type, urgency, availability, and attached media from free‑form text—even emojis and shorthand—then asks only for what’s missing. Eliminates manual data entry, reduces back‑and‑forth, and standardizes intake the moment messages arrive.

Requirements

SMS & WhatsApp Channel Ingestion
"As a property manager, I want tenant messages from SMS and WhatsApp to flow into FixFlow automatically so that every inquiry becomes trackable without manual copy-paste."
Description

Implement robust connectors for SMS and WhatsApp that receive inbound messages and media via webhooks, validate signatures, normalize payloads, and reliably enqueue them for processing. Support tenant opt-in/opt-out management, contact matching, idempotent message de-duplication, and message threading across channels. Ensure resilience with retry/backoff, message ordering by provider timestamps, and multi-tenant routing to the correct FixFlow account and property portfolio based on phone numbers and business profiles.

Acceptance Criteria
Inbound SMS/WhatsApp Webhook Reception & Signature Validation
Given an inbound SMS or WhatsApp webhook with a valid signature and timestamp When the request is received Then the signature is verified using the configured secret for that provider and destination number And the request is accepted with HTTP 200 within 500 ms And the raw payload and headers are persisted with a request_id for traceability Given an inbound webhook with an invalid or missing signature or outside ±5 minutes timestamp skew When the request is received Then respond with HTTP 401 within 500 ms And do not enqueue any message content And record an audit log entry with reason=invalid_signature Given a provider webhook verification/health check request per provider spec When the provider performs the check Then the endpoint returns the expected verification response (e.g., 200 with challenge echo)
Payload Normalization and Durable Enqueue
Given a valid inbound message containing text and/or media When normalization occurs Then the normalized record includes: tenant_id, portfolio_id (if known), channel (sms|whatsapp), provider, from_e164, to_e164, provider_message_id, provider_timestamp (UTC ISO‑8601), received_at (UTC), text, media[], correlation_id, routing_key And media[] entries include url, content_type, size (if provided), filename (if provided) And phone numbers are normalized to E.164 and validated Given successful publish to the durable queue When an ack is received from the broker Then the system logs enqueue_success with normalized_id and returns HTTP 200 to the provider Given the queue is temporarily unavailable When publish is attempted Then no normalized record is lost And the attempt is retried per retry policy And no 2xx is returned until a durable ack is recorded
Idempotent Message De-duplication by Provider Message ID
Given duplicate webhook deliveries with the same provider_message_id for the same channel and tenant within 48 hours When ingestion is processed concurrently or sequentially Then only one normalized message is stored and enqueued And subsequent duplicates return HTTP 200 and are logged as deduplicated=true And a race condition under parallel requests does not create duplicates Given a provider resend after a 5xx response When the resend arrives Then idempotency prevents duplicate enqueue while still returning 200 on success
Cross-channel Threading and Ordering by Provider Timestamps
Given inbound messages from the same contact across SMS and WhatsApp where the WhatsApp user’s phone matches an existing contact E.164 When threading is computed Then messages are assigned the same thread_id and linked to the existing contact; otherwise a new contact is created in pending state Given messages for the same thread arrive out of order When ordering is applied Then an ordering_index is assigned so downstream consumers observe ascending provider_timestamp within the thread And ties are broken deterministically by provider sequence or webhook receipt time Given multiple messages arrive within a 10-minute window When consumed Then the observed order for that thread is non-decreasing by ordering_index
Multi-tenant Routing via Destination Numbers and Business Profiles
Given an inbound message addressed to a phone number or WhatsApp business profile configured for a FixFlow account When routing is performed Then the normalized message is assigned to the correct tenant_id and portfolio_id according to the configuration And no message is routed to a different tenant’s queue Given an inbound message to an unrecognized destination When routing is performed Then respond with HTTP 404 or provider-expected error without enqueue And create an audit log with reason=unmapped_destination Given multiple tenants share the same provider (e.g., Twilio/WhatsApp) under different numbers/profiles When routing is performed Then routing is based solely on the destination number/profile and business profile id, not on source IP or headers
Tenant Opt-in/Opt-out Management and Compliance
Given a contact sends a STOP/UNSUBSCRIBE keyword on SMS or a provider-specific opt-out on WhatsApp When the message is ingested Then the contact’s consent_status for that channel is set to opted_out with timestamp and source recorded And an opt-out confirmation message is queued only where compliant And future outbound on that channel is blocked Given a contact sends START/UNSTOP or explicit opt-in When the message is ingested Then consent_status is set to opted_in with timestamp and evidence stored Given an inbound message from an opted-out contact When ingested Then the message is accepted for record-keeping but downstream outbound actions are blocked and flagged in metadata
Resilience: Retry/Backoff and Dead-letter Handling
Given transient failures (HTTP 5xx from internal services, network timeouts, queue publish failures) When processing the webhook Then retries use exponential backoff starting at 1s with jitter for up to 5 attempts within 10 minutes And each retry is idempotent via provider_message_id And metrics and alerts are emitted on retry and final failure Given repeated failure beyond the retry budget When enqueue still fails Then the normalized message and context are written to a dead-letter queue with failure_reason and replay_token And the provider receives non-2xx so that it may retry per its policy Given a burst of 100 RPS per tenant sustained for 60 seconds When webhooks are received Then p99 webhook handling latency remains ≤ 1s and no messages are dropped or reordered within threads
Entity Extraction & Normalization Engine
"As an intake specialist, I want key details automatically parsed from messy texts so that I get consistent, structured data without manual interpretation."
Description

Build an NLU pipeline that parses free-form texts and emojis to extract unit/address, issue type, urgency, preferred availability, and attached media references. Normalize entities to FixFlow’s canonical taxonomies (property, unit, category, severity) and validate addresses against existing portfolios. Provide locale-aware parsing (dates/times), emoji and shorthand interpretation, and confidence scoring per field with explainability metadata for downstream decisions.

Acceptance Criteria
Mixed Emoji and Shorthand Extraction in Tenant SMS
Given a tenant SMS contains free-form text with emojis and shorthand (e.g., "🚰 leak in 2B, asap pls") When the NLU engine processes the message Then it extracts unit/address, issue type, urgency, preferred availability (if present), and media references And it interprets emojis and shorthand into canonical meanings And it returns normalized values mapped to FixFlow taxonomies And it outputs per-field confidence scores (0.00–1.00) and explainability metadata (token spans, emoji/shorthand expansions) And fields not present are returned as null with a missing_fields list detailing which are absent and why
Portfolio Address and Unit Validation
Given the message mentions a property address and/or unit identifier And the portfolio contains matching properties/units When the engine validates the address/unit Then it returns propertyId and unitId from the portfolio with match_confidence >= 0.90 And if multiple candidates score within 0.05 of the best, it returns a ranked candidate list with reasons (e.g., street alias, unit format) And if no match >= 0.80, it flags address_invalid with the top 3 suggestions and rationale And unit normalization handles formats like "2B", "Unit 2-B", "#2B" equivalently
Locale-Aware Availability and Date/Time Parsing
Given tenant availability is expressed using locale-specific formats or relative terms (e.g., "tomorrow 6–8pm", "31/10 at 09:00", "next Fri morning") And the tenant locale/time zone are known from profile or phone country code When the engine parses availability Then it outputs ISO 8601 start/end times in the property time zone And it explains the locale and rules applied And it disambiguates DD/MM vs MM/DD based on locale And it resolves relative terms to concrete dates using the current processing timestamp And if confidence < 0.80 for availability, it adds availability to missing_fields with a clarification_hint
Issue Type and Severity Normalization
Given a description of a maintenance problem using synonyms, slang, or abbreviations (e.g., "no hot H2O", "AC dead", "toilet overflowing") When the engine classifies the issue Then it maps to canonical category and subcategory in FixFlow (e.g., Plumbing > Hot Water) And it assigns severity mapped to FixFlow's severity scale And it returns top_3 candidates with scores; selects a primary only if top_score >= 0.85 and (top_score - second_score) >= 0.10; otherwise sets needs_clarification with candidates And it provides rationale features in explainability (keywords, emojis, patterns) used for classification
Media Attachment Detection and Thread Linking
Given a conversation thread includes media attachments and textual references (e.g., "see pic", "video attached") possibly across multiple messages When the engine processes the full thread Then it enumerates media with stable IDs, types, sizes, and message indices And it links textual references to the corresponding attachments And it deduplicates identical media by content hash And it includes media_presence with count and URIs And if media is referenced textually but missing, it adds to missing_fields with a request_hint
Output Contract, Schema Compliance, and Performance
Given any supported input thread up to 30 messages and 5 MB total media metadata When processing completes Then the engine returns a JSON payload conforming to the published schema (fields, types, required/optional) And all extracted fields include confidence scores and explainability blocks And processing latency is <= 1500 ms at p95 under nominal load And errors are returned with machine-readable codes and do not crash the pipeline And PII is redacted in explainability while preserving reference spans
Targeted Missing-Info Prompts
"As a tenant, I want the system to only ask me for the information that’s missing so that I can submit my issue quickly without repetitive questions."
Description

Automatically ask concise follow-up questions only for fields that are missing or below confidence thresholds, using the tenant’s original channel. Maintain conversation context, support multilingual prompts, and apply throttling and timeout rules. Stop prompting once required fields are satisfied and record explicit confirmations. Respect quiet hours and escalation rules, and log all interactions to the ticket timeline in FixFlow.

Acceptance Criteria
Prompt Only Missing or Low-Confidence Fields
Given a parsed tenant message with required fields {unit/address, issue type, urgency, availability window} And confidence threshold = 0.85 And detected values: unit/address=present@0.96, issue type=missing, urgency=present@0.62, availability=missing When the system generates follow-up prompts Then it sends prompts only for issue type, urgency, and availability And it does not prompt for unit/address And the number of prompts equals the count of required fields with value missing or confidence < 0.85 And no prompt requests information already explicitly confirmed in this conversation
Maintain Conversation Context and Field Updates
Given the conversation contains prior answers for unit/address and urgency marked confirmed When the tenant sends a new message "Actually it's unit 10A, not 12A" Then the system updates unit/address to 10A and marks the previous value as superseded And it does not re-ask for urgency or other already confirmed fields And any subsequent prompt references the updated unit/address value And a final summary reflects the latest values only
Multilingual Prompts on Original Channel
Given the tenant’s latest inbound message is in Spanish (language code = es) on WhatsApp When the system sends missing-info prompts Then the prompts are delivered via the same WhatsApp thread And the prompt content is in Spanish using the approved Spanish templates And if translation assets for the tenant’s language are unavailable, the system falls back to English and records the fallback And the tenant’s next reply in either Spanish or English is correctly associated with the same ticket context
Throttling, Reminder, and Escalation Rules
Given required fields remain incomplete after the first prompt When 15 minutes pass without a tenant reply Then send exactly one reminder message And if 60 minutes pass after the reminder without reply, escalate to the designated manager queue and stop further prompts And enforce a maximum of 3 prompt messages (including the reminder) per rolling 24 hours per conversation And enforce a minimum spacing of 2 minutes between any two system-sent prompts in the same conversation
Respect Quiet Hours
Given property quiet hours are set to 21:00–08:00 in the property’s local timezone When a prompt or reminder would be sent during quiet hours Then the message is queued and scheduled for delivery at 08:00 local time And escalation and reminder timers pause during quiet hours and resume at 08:00 And no system messages are delivered during quiet hours except for configured emergency overrides (which are explicitly flagged)
Stop Prompting and Capture Explicit Confirmation
Given all required fields are present and each field’s confidence is ≥ 0.85 When the system presents a final summary to the tenant Then it asks for explicit confirmation (e.g., YES to confirm, NO to change) And if the tenant replies YES, the ticket is marked confirmed with a timestamp and no further prompts are sent And if the tenant replies NO, the system asks targeted follow-ups only for the fields the tenant indicates are incorrect, preserving all other confirmed fields
Comprehensive Timeline Logging
Given any system prompt, tenant reply, reminder, escalation, throttle block, or quiet-hours deferment occurs When the event is processed Then an immutable entry is appended to the FixFlow ticket timeline including: timestamp (ISO 8601), channel, actor (system/tenant), event type, message snippet, fields affected, values and confidence scores (where applicable), and rules applied And timeline entries are ordered by event time and are retrievable via the ticket view And redactions are applied per PII policy to message snippets before storage
Auto Ticket Creation & Thread Deduplication
"As a property manager, I want tickets automatically created and de-duplicated from message threads so that I don’t waste time on redundant entries or miss critical issues."
Description

Create a structured maintenance ticket in FixFlow once required fields reach acceptable confidence or are confirmed. Link tickets to the correct tenant/contact and property/unit, set initial priority/severity, and attach source transcripts and media. Detect and merge duplicate threads from the same tenant within a configurable window, preserving conversation history and preventing double-dispatch.

Acceptance Criteria
Auto-create ticket on confidence threshold
Given an inbound SMS or WhatsApp message thread from a recognized contact And extracted fields include tenant/contact, property/unit, issue type, and urgency with confidence scores When all required fields meet thresholds: tenant/contact ≥ 0.95, property/unit ≥ 0.90, issue type ≥ 0.85, urgency ≥ 0.80, or are explicitly confirmed by the sender Then a FixFlow ticket is created within 5 seconds of the last qualifying message And structured fields are populated; ticket status = New; intake queue = Default; source channel and timestamps recorded And an audit entry records field values, confidences, and creation trigger
Clarify missing or low-confidence fields
Given one or more required fields are below threshold or missing When processing the thread Then the system sends one concise clarification prompt per field (combining prompts when possible) in the thread's language And accepts confirmations via text, yes/no, emoji, or quick-reply buttons And retries up to 2 times per field within a 10-minute window And upon threshold satisfaction, proceeds to auto-creation; otherwise routes to Manual Review with reason codes
Correct tenant/contact and property/unit linking
Given the sender maps to exactly one active tenant/contact When the ticket is created Then link the ticket to that tenant/contact and their active lease's property/unit Given multiple matches (≥2) or no active lease When ambiguity exists Then send a disambiguation prompt listing up to 3 options and do not auto-create until confirmed And if unresolved after 10 minutes or 3 prompts, route to Manual Review and log ambiguity details
Initial priority/severity assignment
Given extracted issue type and urgency with confidence scores When creating the ticket Then assign priority per ruleset: P1 for life/safety or active water leak + urgent; P2 for critical utility outage; P3 default And if computed priority confidence < 0.70, set P3 and flag Needs Review And log rule ID, input signals, and computed priority in the audit trail
Attach transcripts and media
Given a ticket created from a message thread When viewing the ticket Then the full transcript up to creation time is available inline and downloadable as a single artifact with timestamps and sender labels And all received media (images, videos, voice notes) are attached with thumbnails and metadata; failed attachments are retried 3 times then logged And PII masking settings (if enabled) are applied to transcript previews
Duplicate detection and merge within window
Given a new thread from the same tenant/contact within the configured window (default 72 hours) When similarity to an open ticket for the same property/unit ≥ 0.85 by the dedup model Then merge the new thread into the existing ticket and do not create a new ticket And preserve chronological order of all messages and media; add a Merge event with similarity score and matched ticket ID And prevent duplicate vendor dispatches or notifications on merged threads And allow authorized users to unmerge within 24 hours, restoring original threads
Review Queue with Human-in-the-Loop
"As an operations lead, I want a review queue for uncertain cases so that we maintain accuracy without blocking the automated flow."
Description

Provide a moderation UI listing low-confidence or conflicting extractions with side-by-side raw thread and parsed fields. Allow quick accept/edit of entities, confidence overrides, and merge/split of threads. Capture reviewer feedback as labeled data for continuous model improvement and audit trails, with SLAs and prioritization for urgent items.

Acceptance Criteria
Queue Intake of Low-Confidence Extractions
Given an incoming thread has any extracted field below the confidence threshold or contains conflicting entities, When extraction completes, Then the thread is added to the Review Queue within 5 seconds and marked "Needs Review". Given a thread already in the queue, When additional messages from the same conversation arrive, Then they are aggregated into the same queue item without duplication. Given the Review Queue list, When filters (status, priority, property, age, assignee) are applied, Then results update within 200 ms for up to 5,000 items and counts are accurate. Given the Review Queue list, When no items match filters, Then an empty state is shown with zero results and no errors. Given a queue item is resolved, When it is removed, Then it no longer appears in any filtered or unfiltered view.
Side-by-Side Review and Quick Edit
Given a queue item is opened, When the detail view loads, Then the raw message thread and parsed fields render side-by-side with media previews and timestamps. Given parsed fields (unit/address, issue type, urgency, availability, media), When a reviewer clicks a field, Then it becomes inline-editable with model suggestions and validation. Given a media attachment, When clicked, Then it opens a zoomable viewer in-context without page navigation. Given the detail view, When displayed on desktop (≥1280px), Then a two-column layout is used; on smaller screens the layout stacks while preserving all functions. Given a queue item is opened, When data is requested, Then first meaningful paint completes within 1.5 seconds at p95.
Accept/Reject Actions and Confidence Overrides
Given an item has been reviewed, When the reviewer clicks "Accept", Then confirmed fields are finalized and the ticket is created/updated with those values. Given a field is incorrect, When the reviewer clicks "Reject" for that field, Then the correction replaces the value and a labeled data record with a reason code is saved. Given a confidence override is set, When the reviewer saves, Then the manual confidence value, user, timestamp, field, previous confidence, and justification (if lowering below threshold) are recorded. Given accept/reject/override actions are submitted, When processing completes, Then the item status updates to Resolved and leaves the queue within 2 seconds, and an audit event is logged.
Merge and Split Threads
Given two or more queue items belong to the same incident, When the reviewer selects "Merge", Then messages, media, and fields are combined into one ticket with message order preserved by timestamp and a merge mapping recorded. Given a single queue item contains multiple incidents, When the reviewer selects "Split" and defines message ranges, Then separate tickets are created per range with inherited metadata and independent field sets. Given a merge or split operation completes, When the audit trail is queried, Then parent/child relationships and the acting user, time, and rationale are visible and the action is reversible via Undo within 10 minutes. Given conflicting fields exist during merge/split (e.g., different unit), When the reviewer proceeds, Then a conflict resolution prompt requires explicit selection before completion.
Feedback Capture for Model Training and Audit
Given any edit, accept/reject, or override occurs, When the reviewer saves, Then a structured label record is stored including raw text span, normalized value, field type, pre/post values, confidence, reason code, reviewer ID, and timestamp. Given stored feedback, When the daily export runs at 02:00 UTC, Then a dataset (NDJSON or Parquet) is written to the training bucket with ≥99.9% success over a rolling 30 days, otherwise an alert is sent to Ops. Given an audit search by ticket ID or date range, When executed for the last 90 days, Then the full action history returns within 2 seconds; events older than 90 days are retrievable from cold storage up to 1 year. Given PII policies, When labels are persisted, Then sensitive fields are encrypted at rest (AES-256) and redacted in analytics unless the user has the Data Steward role.
SLA Prioritization and Urgent Escalation
Given an item is classified as Emergency or matches escalation keywords (e.g., "gas leak", "flooding"), When it enters the queue, Then it is auto-prioritized as P0, visually highlighted, and pinned to the top for all reviewers. Given SLA policies (P0=5m, P1=30m, P2=4h), When an item reaches 80% of its SLA time, Then it escalates with notifications to the on-call channel and visible badges until acknowledged. Given an item is resolved, When within SLA, Then response time is recorded and marked SLA Met; when over SLA, Then marked SLA Breached and a reason code is required. Given the queue is open, When a new P0 item arrives, Then the list updates in real time with <1 second latency at p95. Given auto-assign is enabled, When a reviewer becomes available, Then the highest-priority item is assigned respecting capacity and required skills (e.g., language).
Secure Media Handling & Enrichment
"As a technician scheduler, I want reliable access to tenant photos and videos so that I can assess issues and plan visits without delays."
Description

Download and store inbound photos/videos from SMS/WhatsApp securely with anti-malware scanning, expiration-aware retrieval, and signed URLs. Generate thumbnails and lightweight previews, extract metadata (timestamps, device orientation), and attach media to the corresponding ticket. Enforce size/type limits and graceful fallbacks when media cannot be retrieved.

Acceptance Criteria
Inbound Media Malware Scan and Quarantine
Given an inbound MMS/WhatsApp media URL is received When the file is downloaded to temporary storage Then it is scanned by the configured anti-malware engine before any persistence And if malware is detected, the file is not persisted, is quarantined, a blocked-media placeholder is added to the ticket, and the tenant is prompted to re-upload via the secure link And if clean, the file is persisted to encrypted object storage (AES-256) and removed from temp storage And the scan decision, file SHA-256 hash, and engine version are logged And for files ≤ 25 MB the scan completes within 5 seconds in 95% of cases
Expiration-Aware Retrieval and Retry for Provider Media
Given a provider media URL with a known expiration window When initial download fails due to expiration or a 4xx related to expiration Then the system requests a fresh URL using the provider media ID and retries up to 3 times with exponential backoff within 10 minutes of message receipt And on success the normal processing pipeline resumes; on final failure a placeholder is attached to the ticket and the tenant is asked to re-upload via the secure link And all attempts and outcomes are logged with timestamps and error codes
Time-Limited Signed URLs for Authorized Access
Given a stored media asset When an authorized user requests to view or download it Then the system issues a time-limited signed URL scoped to that asset with a TTL of 10 minutes And unsigned or expired URLs return HTTP 403 without leaking asset existence And revoking the user’s access to the ticket prevents future URL issuance And every access via a signed URL is audit-logged with user ID, ticket ID, IP, and timestamp
Thumbnail and Lightweight Preview Generation
Given a clean supported image or video asset within size limits When media processing completes Then for images a thumbnail (max 256x256, aspect preserved) is generated with orientation corrected per EXIF And for videos a poster frame thumbnail and a 5-second 480p H.264 MP4 preview are generated And previews are stored alongside the original and linked to the ticket And if preview generation fails the original is still attached, a generic thumbnail is shown, and the error is logged And 95% of preview generations for assets ≤ 50 MB complete within 15 seconds
EXIF/Metadata Extraction and Normalization
Given a clean image or video asset is persisted When metadata is extracted Then capture timestamp, device model, orientation, dimensions, and (for video) duration are captured when available And timestamps are normalized to UTC with original offset retained if present And extracted metadata is stored with the media record and surfaced on the ticket And if metadata is missing or unreadable fields are null without failing the attachment
Media Type and Size Validation with Tenant Fallback
Given an inbound media message When the file type is unsupported or the size exceeds 50 MB for MMS/SMS or 100 MB for WhatsApp Then the system rejects the download, records the reason, and attaches a placeholder to the ticket And the tenant is automatically sent instructions to resend a smaller/supported file or use a secure upload link And supported types include JPEG, PNG, HEIC (converted to JPEG), MP4, and MOV; all others are rejected And HEIC images are converted to JPEG on ingest; conversion failures are treated as unsupported
Correct Ticket Association and Idempotent Attachment
Given an inbound message that maps to an existing conversation or a new ticket When media is processed Then the media is attached to the correct ticket based on thread-to-ticket mapping rules And duplicate webhooks or retries do not create duplicate attachments by deduplicating on provider media ID and content hash And if ticket creation is pending, the media is queued and attached within 2 minutes of ticket creation; on timeout a placeholder is added and staff are alerted And the ticket timeline reflects success or failure states for each media item
Privacy, Compliance, and Consent Guardrails
"As a compliance-conscious manager, I want privacy protections and consent tracking built in so that our communications stay compliant and tenant data is safeguarded."
Description

Detect and minimize PII exposure by redacting sensitive data in transcripts and media via OCR where applicable. Track consent and opt-in status per channel, honor opt-out keywords, and log consent events. Apply configurable data retention policies, role-based access to raw messages, and audit logging to support regulations such as GDPR and TCPA.

Acceptance Criteria
Automatic PII Redaction in Message Transcripts
Given an inbound SMS/WhatsApp message contains PII such as emails, phone numbers, credit card numbers, SSNs, or DOB When the message is ingested by ThreadSense Then the transcript stored for general access is redacted for all detected PII tokens using masking (e.g., ****) and tagged with redaction types And the structured ticket fields are populated from the unredacted parse without exposing PII in free text And redaction completes within 2 seconds for messages up to 1,000 characters And the redaction does not remove non-sensitive operational data (e.g., unit, issue type) from structured fields
OCR-Based PII Detection in Attachments
Given a tenant sends image or PDF attachments containing visible PII (e.g., IDs, emails, credit card numbers) When the attachments are processed Then OCR is applied to extract text and PII patterns are detected and masked in previews and derived transcripts And original binaries are stored encrypted and marked sensitive And OCR processing completes within 10 seconds per file up to 5 MB under nominal load And a failure to OCR results in a flag for manual review and prevents unredacted preview from being shown to unauthorized roles
Channel-Specific Consent Capture and Enforcement
Given a contact has no recorded opt-in for a specific channel (SMS or WhatsApp) When an outbound message is attempted on that channel Then the system blocks the send and triggers an automated opt-in request flow And upon receiving explicit opt-in keywords or button confirmation, the system records a consent event with tenant ID, channel, timestamp (UTC ISO 8601), source, and operator (if any) And outbound messaging is enabled only after consent is recorded for that channel And revocation of consent immediately disables outbound messaging on that channel until re-opt-in
Opt-Out Keyword Handling and Compliance Response
Given an inbound message contains a recognized opt-out keyword (e.g., STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT), case-insensitive and punctuation-tolerant When the message is received on SMS or WhatsApp Then the system immediately marks the contact as opted-out for that channel And sends a single compliance confirmation message indicating opt-out success and how to re-subscribe And blocks all further outbound messages on that channel except the compliance confirmation And logs the opt-out event with tenant ID, channel, timestamp, source message ID, and processing outcome
Configurable Data Retention and Secure Purge
Given an admin configures retention policies for raw messages, transcripts, previews, and attachments (e.g., 90/180/365 days) When stored items reach their retention limits Then a scheduled job purges the expired items irreversibly while preserving structured ticket data and audit logs And legal hold flags, when present, prevent purge until removed and are auditable And each purge run produces an audit record with counts deleted by type, time window, initiator (system/admin), and any exceptions And retention settings changes are versioned with who, when, and before/after values
Role-Based Access to Unredacted Content
Given organization roles have permissions defined for viewing raw, unredacted messages and media When a user without the permission requests raw content via UI or API Then the system returns only redacted transcripts and masked previews and denies raw access with 403 on API And exports initiated by such users include only redacted content And a break-glass workflow allows temporary raw access only with required reason, approver, and auto-expiry within 60 minutes, all logged
Comprehensive Audit Logging and Integrity
Given privacy and consent actions occur (access to raw content, consent changes, retention changes, purges, exports) When any such action is performed Then an immutable audit entry is created capturing actor, role, action, resource ID, channel (if applicable), IP or client ID, timestamp (UTC ISO 8601), and outcome And audit logs are append-only, retained for at least 2 years, and can be exported in CSV and JSON formats by authorized roles And daily integrity hashes are computed over new entries and stored to detect tampering, with integrity check results visible to admins

AutoLocale

Detects the renter’s language and tone from the first message and continues the conversation in their preferred language with clear, housing‑friendly phrasing. Seamlessly switches if they change languages mid‑thread and supports bilingual households. Boosts response rates, reduces misunderstandings, and cuts coordinator intervention.

Requirements

Real-time Language Detection
"As a renter, I want the system to recognize my language from my first message so that I can communicate without changing settings or repeating myself."
Description

Detects the renter’s language from the first inbound message across all channels (SMS, email, portal, WhatsApp) using per-message identification with confidence scoring. Automatically sets and updates the user’s preferred language on their profile and thread, applies safe fallbacks when confidence is low, and logs detection decisions for auditability. Handles closely related languages and code-switching, supports a broad set of locales, and exposes detection results to downstream flows (triage, templates, scheduling) to ensure end-to-end localized experiences. Improves response rates and reduces coordinator handoffs by eliminating manual language setup.

Acceptance Criteria
First Inbound Message Detection Across Channels
Given an inbound first message arrives via SMS, Email, Portal, or WhatsApp When the message is accepted into the system Then the system performs per-message language identification and returns a BCP 47 locale code (e.g., "es-MX") and confidence score in [0,1] within 400 ms at p95 per channel And then no outbound automated reply is sent until detection completes And then the detected locale is applied to that message’s reply context And then measured accuracy on a held-out validation set across the top 20 locales is >= 97% macro-accuracy And then at least 60 locales with region/script variants are supported and advertised via GET /v1/locales
Confidence Thresholds and Safe Fallbacks
Given a detection result with confidence >= 0.85 When applying language settings Then set the user profile preferredLanguage and thread.language to the detected locale and continue the conversation in that locale Given confidence in [0.60, 0.85) When applying language settings Then do not change profile or thread defaults And then generate replies in the last known preferred language or English if none And then include a lightweight language confirmation prompt referencing the top-1 candidate locale And then log fallback_reason = "medium_confidence" Given confidence < 0.60 or the message length < 5 characters after normalization When applying language settings Then do not change profile or thread defaults And then respond in English And then attach language confirmation options for the top-2 candidate locales And then log fallback_reason = "low_confidence"
Profile and Thread Auto-Update Rules
Given a renter with no preferredLanguage set When a message is detected with confidence >= 0.85 Then set profile.preferredLanguage and thread.language to the detected locale and record updated_at and actor = "AutoLocale" Given a renter with an existing preferredLanguage set manually (locked) When a message is detected Then do not override the locked preference and record attempt with action = "skipped_manual_lock" Given repeated messages in the same detected locale with confidence >= 0.85 When updates are applied Then operations are idempotent and do not create duplicate audit entries
Mid-Thread Code-Switch Handling
Given an active thread with preferredLanguage = L1 When an inbound message is detected as locale L2 != L1 with confidence >= 0.85 Then generate the immediate reply in L2 for that message And then update profile.preferredLanguage to L2 only after two consecutive high-confidence messages in L2 within 24 hours or upon explicit user confirmation (e.g., "please use Portuguese") And then log event_type = "language_switch" with from = L1, to = L2 Given code-switching where the subsequent message returns to L1 with confidence >= 0.85 before the update rule is satisfied When evaluating update Then keep preferredLanguage = L1 and clear any pending switch state
Disambiguation of Closely Related Languages
Given a curated test set covering es vs pt, nb vs da vs sv, hr vs sr-Latn/sr-Cyrl, zh-CN vs zh-TW When running batch detection Then achieve >= 95% accuracy per pair/group and >= 0.90 average confidence on correct predictions And then for ambiguous samples where the model cannot disambiguate, return confidence < 0.60 to trigger safe fallback And then ensure script detection correctly distinguishes sr-Cyrl vs sr-Latn with >= 99% script accuracy
Audit Logging and Retrieval of Detection Decisions
Given any detection decision When persisting logs Then store message_id, tenant_id, channel, timestamp (UTC), detected_locale (BCP47), confidence, top_3_candidates with scores, model_version, action_taken, fallback_reason (if any), actor, and request_id And then make logs queryable by date range, tenant_id, channel, and detected_locale with p95 query latency <= 1.5 s for 30-day windows And then retain logs for at least 18 months and redact message content per data policy And then expose log entries via Admin UI and API endpoint GET /v1/language-detections/{message_id}
Downstream Localization Propagation
Given a detected locale for a message/thread When invoking triage classification, template rendering, and scheduling flows Then each downstream component receives the locale via event payloads and internal context And then 100% of automated outbound messages (triage follow-ups, appointment invites, status updates) use templates matching the effective locale, unless a safe fallback rule is active And then external webhooks include detected_locale and confidence fields And then end-to-end tests across SMS, Email, Portal, and WhatsApp verify localized outputs within one minute of the inbound message
Dynamic Mid-thread Language Switching
"As a renter, I want the conversation to switch to the language I’m using mid-thread so that I can mix languages naturally without confusion."
Description

Performs message-level language detection throughout a conversation and seamlessly switches reply language when the renter changes languages mid-thread. Preserves context, maintains consistent terminology, and optionally confirms the switch to avoid confusion. Updates per-contact and per-thread preferences without interrupting the flow, synchronizes across channels, and ensures prompts, templates, and UI elements are regenerated in the new locale. Reduces duplicate messages and clarifies instructions for bilingual interactions.

Acceptance Criteria
Mid-thread Auto-Switch on Language Change
Given an active conversation with current reply language L1 And the renter sends a message written in language L2 != L1 with detection confidence >= 0.85 When the system generates the next reply Then the reply language is L2 and the thread language is updated to L2 for subsequent replies And a language-switch audit event is recorded with L1, L2, confidence, timestamp, and source message ID And no duplicate reply in L1 is sent for that inbound message
Confirmation on Switch (Medium Confidence)
Given account setting confirm_on_switch = true And the renter sends a message in language L2 != current thread language L1 with detection confidence between 0.70 and 0.84 inclusive When the system generates the next reply Then the reply begins with a single confirmation line presented bilingually (L1 and L2), maximum 140 characters, indicating the switch to L2 And the remainder of the reply is written entirely in L2 And if the renter declines the switch in their next message (negative intent detected in L1 or L2), the thread language reverts to L1 and a revert event is recorded
Hysteresis to Prevent Oscillation
Given the thread language is L1 And the renter alternates between L1 and L2 across successive messages When deciding whether to switch languages Then the system switches to L2 only after two consecutive renter messages in L2 with confidence >= 0.85 or an explicit switch intent is detected And once switched, the system will not switch again for at least 5 minutes unless two consecutive messages in a different language meet the confidence threshold or explicit switch intent is detected
Per-Contact and Per-Thread Preference Updates for Bilingual Households
Given a thread with multiple participants using different languages When a switch to L2 occurs Then the thread-level language is updated immediately to L2 And the initiating participant’s contact-level preferred language is updated to L2 only after they send two consecutive messages in L2 or explicitly confirm the switch And other participants’ contact-level preferred languages remain unchanged And the switch event is stored with the participant ID that triggered it
Cross-Channel Synchronization and Template Regeneration
Given a switch from L1 to L2 is applied on channel A When the next outbound message is sent on any channel (SMS, email, WhatsApp, in-app) Then the message content, prompts, templates, quick replies, and UI elements are regenerated in L2, including date/time and number formatting And the per-thread language preference in the CRM is updated within 2 seconds and used across all channels And no mixed-language UI elements appear in the outbound message
Context and Terminology Preservation
Given there is an active work order with identifiers (work_order_id, property address, scheduled time) referenced in the conversation When the reply language switches from L1 to L2 Then all previously captured entities and placeholders retain their values without re-asking the renter And domain glossary terms are rendered using their configured L2 translations for 100% of glossary keys present in the message And references (IDs, dates, phone numbers, links) remain unchanged and correctly formatted for L2 locale
Single Outbound Policy and Latency
Given one inbound renter message triggers a language switch When the system sends its response Then exactly one outbound reply is sent for that inbound event, in the selected language only And the additional processing latency introduced by the switch is <= 300 ms at p95, measured end-to-end from inbound receipt to outbound enqueue And the total response time SLA remains <= 2 seconds at p95
Bilingual Household Preferences
"As a property manager, I want each contact in a household to receive messages in their own preferred language so that everyone understands instructions and schedules."
Description

Stores and manages language preferences at both contact and household/unit levels, allowing different occupants of the same property to receive communications in their preferred language. Supports default and per-topic overrides (e.g., urgent alerts vs. scheduling), consent-based changes, and manager restrictions. Integrates with the tenant directory, applies preferences to outbound notifications, and resolves conflicts with deterministic rules. Improves comprehension across households while minimizing coordinator rework.

Acceptance Criteria
Contact-Level Preference Creation and Directory Sync
Given a tenant contact exists in the directory with contact_id=X and unit_id=U When a manager sets the contact’s preferred language to "es-MX" Then the system stores a record {contact_id:X, unit_id:U, scope:"contact", topic:"default", language:"es-MX", updated_at:timestamp} And an audit log entry is saved with actor_id, action:"set_language", target:"contact", previous_value, new_value, reason, timestamp And a GET to the preferences API for contact_id=X returns "es-MX" as the contact default And the tenant directory profile for contact_id=X reflects the language value within 1 second
Household Default and Per-Contact Overrides Applied to Outbound Notifications
Given a household (unit_id=U) has a household default language "en-US" and two contacts A and B And contact A has contact default "en-US" and contact B has contact default "es-ES" When the system sends a "maintenance_scheduling" notification to both contacts Then contact A receives the en-US template (template_id ends with _en-US) with locale-formatted tokens (dates, currency) for en-US And contact B receives the es-ES template (template_id ends with _es-ES) with locale-formatted tokens for es-ES And delivery logs contain one entry per recipient with fields {contact_id, channel, language_used, template_id} And no recipient receives a mixed-language message body
Topic-Based Overrides (Urgent Alerts vs Scheduling)
Given contact C has a contact default language "en-US" And contact C has a topic override {topic:"urgent_alerts", language:"es-MX"} And the household default is "en-US" When the system sends an "urgent_alerts" notification to contact C Then the message to contact C is in es-MX When the system sends a "scheduling" notification to contact C Then the message to contact C is in en-US And the delivery log for each message records the topic, effective_language, and the precedence rule applied
Deterministic Conflict Resolution Rules
Given the following precedence order exists for effective language resolution: (1) per-contact topic override > (2) per-contact default > (3) household topic override > (4) household default > (5) manager portfolio default (if set) > (6) system default "en-US" And a household has household default "en-US" and household topic override for "urgent_alerts" set to "en-US" And contact D has contact default "es-ES" and no topic override When an "urgent_alerts" notification is sent to contact D Then the effective language is "es-ES" because per-contact default (2) outranks household topic override (3) And the resolution path [2] is stored in the delivery log When two settings exist at the same precedence level with different timestamps Then the most recent updated_at value deterministically wins and is recorded in the audit trail
Consent-Based Changes and Manager Restrictions
Given contact E attempts to change contact F’s language preference When contact E does not have recorded consent from contact F and no manager override is in place Then the change is rejected with error_code="CONSENT_REQUIRED" and no preference is updated When contact F provides explicit consent via in-thread confirmation or verified link within 24 hours Then the system updates contact F’s preference and records consent metadata {consenter_id, method, timestamp} And when a manager sets a restriction "lock topic=urgent_alerts language to household default" Then any attempt by a contact to set a topic override for urgent_alerts is rejected with error_code="RESTRICTED_BY_MANAGER" and is logged
Mid-Thread Language Switch With Preference Update Prompt
Given contact G has stored contact default "en-US" And during an ongoing thread AutoLocale detects incoming text in "fr-CA" When the system replies in fr-CA within the same thread Then the reply is localized to fr-CA without changing stored preferences And the system prompts: "Use French for future messages?" with Yes/No options When contact G selects Yes Then the stored contact default updates to "fr-CA" and an audit entry is created When contact G selects No Then stored preferences remain "en-US" and subsequent thread replies continue in fr-CA for the current thread only
Move-In/Move-Out, Archival, and Fallbacks
Given contact H is marked "moved_out" from unit_id=U When the status changes to moved_out Then contact H’s preferences are archived (status:"inactive") and are not applied to new occupants of unit U When a new contact I is added to unit U with no preferences set Then their effective language for notifications is the household default if present, otherwise the system default "en-US" When an attempt is made to set an unsupported language code Then the request is rejected with error_code="INVALID_LANGUAGE" and a list of supported codes is returned
Housing Glossary & Translation Memory
"As a maintenance coordinator, I want consistent housing-specific terminology across languages so that renters get clear instructions and technicians receive accurate details."
Description

Provides a domain-specific glossary and translation memory optimized for housing and maintenance (e.g., GFCI, HVAC, shutoff valve), including do-not-translate lists, locale-specific phrasing, and safety terminology. Centralizes a style guide to enforce clear, non-technical, fair-housing-compliant language across all locales. Supports versioning, curator workflows, and A/B testing of phrasing. Exposes APIs and an admin UI to add terms, approve suggestions, and automatically reuse validated translations to improve consistency and reduce costs.

Acceptance Criteria
Runtime glossary enforcement during tenant chat translation
Given a tenant message contains terms present in the glossary or do-not-translate list for the target locale When AutoLocale translates the message Then glossary terms are output exactly as specified for that locale and do-not-translate entries remain unchanged Given a message includes terms tagged as safety-critical in the glossary When the translation is generated Then the output uses the locale-approved safety phrasing and does not alter mandated warnings Given locale-specific variants exist for a term (e.g., en-US vs en-GB) When translating into that locale Then the selected variant matches the locale’s style guide rules Given an automated glossary validator runs on a 500-message test corpus per locale When translation completes Then the validator reports 0 glossary-rule violations and 0 do-not-translate violations
Translation memory reuse and leverage
Given a source segment has an exact match in the validated translation memory for the same source and target locales and context When translating the segment Then the exact match is reused verbatim with matchScore = 1.0 and provenance = "TM" Given fuzzy matches ≥ 0.85 exist in TM When translating a segment with no exact match Then the highest-scoring suggestion is applied if confidence ≥ 0.95; otherwise it is flagged for post-edit and logged Given no matches exist in TM When a curator approves the human-verified translation Then the segment pair is stored in TM with context keys, locale, version, and approval metadata Given a standard regression corpus of repeated segments When processed end-to-end Then ≥ 60% of segments are served from TM and p50 translation latency improves by ≥ 30% versus a no-TM baseline
Admin UI term curation and approval workflow
Given a user with Curator role When they add a new term with locale variants, do-not-translate flag, tags, and usage examples Then the term is saved as Draft and appears in the approval queue with required-field validation enforced Given a user with Approver role reviews a Draft term When they approve, request changes, or reject with a reason Then the action is recorded in an immutable audit log with actor, timestamp, and diff Given an approval occurs on any term or translation When it is published Then a new glossary semantic version is created and clients can pin or upgrade to that version Given an edit to a published term is submitted When it is awaiting approval Then live services continue to use the prior version and a one-click rollback to any prior version is possible
Glossary and translation memory APIs availability and correctness
Given a request with a valid API key scoped to glossary:read When calling GET /glossary?locale=es-MX&version=1.3.0 Then the API responds 200 within 300 ms p95 and returns entries for the requested locale and version Given a request with glossary:write scope When POSTing a valid term payload Then the API responds 201 with the new Draft resource location; without scope it responds 403 Given a TM query POST /tm/matches with source text, sourceLocale, targetLocale When an exact match exists Then the response contains matchScore = 1.0 and the validated translation; otherwise it returns the top 5 fuzzy matches sorted by score Given clients omit a required version parameter When calling any glossary or TM endpoint Then the API responds 400 with a machine-readable error code and message Given sustained traffic within limits When requests exceed 1000 req/min per API key Then the API responds 429 with a Retry-After header and resumes service when rate falls below the threshold
Style guide compliance in generated text
Given a locale style guide with reading level ≤ B1, average sentence length ≤ 20 words, and inclusive language enabled When AutoLocale renders any outbound text Then automated checks report 0 critical violations of these rules Given fair-housing compliance rules and a prohibited-phrases list When generating explanations, instructions, or notifications Then violations count = 0 and any flagged borderline cases are routed to human review Given locale preferences for measurement units, date/time, and numeric formatting When text includes measurements or dates Then units and formats are localized per the style guide (e.g., psi vs bar, mm/dd vs dd/mm)
A/B testing of phrasing variants for key prompts
Given two approved variants exist for key "water_shutoff_instruction" When an experiment is started with a 50/50 split for en-US Then traffic allocation per variant is within ±2% of target and per-variant metrics (response rate, confusion rate) are logged Given guardrail thresholds are configured (confusion rate ≤ 5%, safety-acknowledge CTR ≥ 90% of control) When any guardrail is breached for 5 consecutive minutes Then the experiment auto-pauses and reverts to control within 5 minutes and alerts are sent to curators Given an experiment reaches its end date with a statistically significant winner When an Approver confirms promotion Then the winning variant becomes default, TM entries are updated with provenance = "experiment", and the experiment is archived
Mid-thread locale switch with glossary continuity
Given a conversation switches from Spanish to English mid-thread When AutoLocale continues in English Then glossary terms are enforced for en-US, prior context is respected, and validated TM segments are reused where applicable Given a household profile lists preferred locales [es-US, en-US] for two recipients When each recipient receives system messages Then each message is localized to the recipient’s preferred locale using the correct glossary variants Given a performance SLO for locale switching When a switch occurs during active messaging Then glossary violation count remains 0 and additional p95 latency is < 50 ms versus single-locale baseline
Tone-Aware Messaging
"As a renter, I want messages that match my urgency and remain respectful and safe so that I feel understood and can act quickly."
Description

Detects renter urgency and formality and adapts replies to culturally appropriate, respectful, housing-safe tone without making promises or providing risky instructions. Normalizes slang, clarifies ambiguous statements, and inserts safety disclaimers when hazards are suspected. Routes sensitive or escalated cases to a coordinator and aligns with style rules per locale. Enhances trust, reduces misunderstandings, and accelerates issue triage.

Acceptance Criteria
Hazard Urgency Detected with Safety Disclaimer and Escalation
Given a renter message indicates potential hazard (e.g., mentions gas smell, smoke, carbon monoxide alarm, electrical sparking) or the urgency classifier score is >= 0.85 When the system composes the reply Then it inserts a locale-specific safety disclaimer that advises evacuation and contacting emergency services where applicable, avoids repair instructions, avoids time-bound promises, sets priority to P0, and routes the thread to a coordinator within 1 minute, with the action logged (classification label, confidence, disclaimer ID)
Formality and Politeness Alignment per Locale
Given the renter’s initial message yields a formality level (e.g., formal, neutral, informal) and locale style rules are loaded When the system generates the reply Then the reply matches the detected formality level, applies locale-appropriate honorifics/pronouns (e.g., usted/tu, Sie/du), avoids slang in formal mode, uses plain, respectful phrasing in informal mode, and passes the style-rule linter with zero errors and zero warnings for formality compliance across the thread
Slang Normalization and Ambiguity Clarification
Given a renter message contains slang, idioms, or ambiguous terms affecting triage (e.g., “the thing is busted,” “it’s leaking bad somewhere”) When the system crafts the next message Then it paraphrases the renter’s issue in normalized, housing-safe language to confirm understanding and asks 1–3 concise, multiple-choice clarifying questions (and a photo request if none on file), avoids judgmental language, and logs the extracted entities (issue type, location, severity) with confidence scores
Mid-Thread Language Change with Tone Continuity
Given the renter switches languages mid-thread or a second household member replies in a different language When the system sends the next message Then it continues in the renter’s latest language, preserves prior tone/formality level, complies with locale style rules, optionally acknowledges the language change if required by rules, and does not request the renter to restate prior information; all within the same response time SLA as monolingual threads
Sensitive Topic Routing and De‑escalating Tone
Given a message contains sensitive or escalatory content (e.g., harassment, injury, threats of legal action, discrimination claims) When the system detects this content Then the auto-reply uses a de‑escalating, empathetic, housing-safe tone without legal advice or commitments, provides a concise acknowledgement and next step, routes to a coordinator within 2 minutes, suppresses further automated troubleshooting until a human responds, and records the escalation reason and timestamp
Locale Style Compliance and Prohibited Phrase Enforcement
Given an outbound message is ready to send When evaluated against locale-specific style rules and prohibited-phrase lists Then it contains no hard promises (e.g., “will be fixed today,” “guarantee”), no risky instructions (e.g., “open the breaker panel,” “climb onto the roof”), adheres to fair housing safe-language guidelines, passes the linter with zero blocking issues, and auto-corrects violations prior to sending (or blocks sending and flags for review if auto-correction fails)
Multi-channel Localization & RTL Support
"As a renter, I want localized messages to display correctly across SMS, email, and the portal so that I don’t miss critical details."
Description

Generates localized content that renders correctly across SMS character limits, email, and the FixFlow portal, including Unicode normalization, right-to-left layout, emojis, and rich text fallbacks. Preserves links, appointment details, and attachments with localized date/time, units, and address formats. Provides graceful degradation to plain text when needed and prevents message splitting or truncation. Ensures consistent conversation threads across channels and devices.

Acceptance Criteria
Single-Part SMS Localization With Unicode
Given the renter’s locale is detected and the outbound channel is SMS When the system composes a message containing appointment details, a link, and emojis Then the encoding is selected to fit content (GSM-7 if possible, otherwise UCS-2) And the final SMS length is within a single-part limit for the chosen encoding (<=160 GSM-7 or <=70 UCS-2) And no message splitting or truncation occurs And the link is preserved and shortened to fit within the limit and remains clickable And date/time and units are formatted per the renter’s locale and timezone And the message text is Unicode-normalized (NFC) without altering meaning And the conversation/thread identifier remains consistent for replies
RTL Email Rendering With Mixed Content
Given the renter’s language is Arabic and the outbound channel is email with mixed LTR/RTL content (numbers, English brand name, link, address) When the email is generated and sent Then the message direction is RTL with correct isolation for LTR spans (bidi controls or CSS) to preserve reading order And numerals, addresses, and mixed-language segments render in the intended order across major email clients And localized link anchor text displays correctly and the URL is preserved and clickable And emojis render without tofu; if unsupported, a safe glyph fallback is used And if an email client strips HTML, a readable plain-text alternative is provided with all required details intact
Portal Thread Consistency for Mixed LTR/RTL Messages
Given a conversation contains messages in English and Hebrew When viewed in the FixFlow web portal and mobile web Then each message bubble applies per-message direction (LTR or RTL) based on detected language And the chronological order of the thread is consistent across devices and browsers And copying any message to clipboard preserves logical character order And appointment cards, links, and attachments render correctly and are actionable regardless of direction
Rich Text Fallback for SMS and Limited Email Clients
Given a composed message includes bullets, emphasis, inline links, and an attachment When the target channel is SMS or an email client that does not support HTML Then the content is transformed to plain text while preserving structure (e.g., dashes for bullets, quoted emphasis) And a single short link is provided for the attachment download with a localized filename shown on the landing page And appointment date/time, location, and preparation notes are preserved and readable And the final message fits channel constraints without splitting or truncation
Locale-Aware Date/Time, Units, and Address Formatting
Given a renter locale of fr-FR and timezone Europe/Paris with metric preferences When generating an appointment confirmation across SMS, email, and portal Then date/time are formatted as dd/MM/yyyy HH:mm in the renter’s timezone And measurements (e.g., temperatures, distances) use metric units with correct localized symbols and spacing And the address is formatted per the locale’s postal conventions and language And any calendar invite (ICS) uses the renter’s timezone and localized summary/description
Mid-Thread Language Switch and Bilingual Household Delivery
Given a household has two recipients (en-US and es-MX) and the renter replies "Podemos en español?" When the system detects a language switch in the thread Then subsequent messages to that renter are generated in Spanish while other recipients continue receiving English And the thread/conversation ID remains the same across SMS, email, and portal And historical messages are not altered And no duplicate notifications are sent to any recipient
Link and Attachment Integrity With Unicode Filenames
Given a message includes a deep link with non-Latin characters and an attachment named "válvula_fregadero.jpg" When sending via SMS, email, and the portal Then URLs are normalized and encoded so they remain clickable across iOS, Android, and major email clients And SMS uses a short link that resolves to a localized landing page showing the correct Unicode filename And email and portal display and download the attachment with the original Unicode filename intact And link previews (where applicable) use localized text and do not exceed SMS constraints
Coordinator Override & Feedback Loop
"As a coordinator, I want to override language choices and submit corrections so that conversations stay accurate and future messages improve."
Description

Offers coordinators a simple UI to override detected language, lock a thread’s language, and submit corrections on translations or tone. Captures side-by-side originals and revisions, reasons for change, and outcome tags; feeds this data to improve models, glossary, and templates. Includes audit logs, role-based permissions, and dashboards for detection accuracy, switch events, and response outcomes. Shortens resolution time and creates a continuous improvement cycle.

Acceptance Criteria
Manual Language Override in Active Thread
Given an active tenant thread with auto-detected language and a coordinator with "Override Language" permission, When the coordinator selects a new language from the override control and clicks "Apply", Then all subsequent system and bot messages in that thread are generated in the selected language within 2 seconds of send, And a visible chip "Overridden to <LANG_CODE>" with timestamp and actor appears in the thread header, And auto-detect is paused until reverted. Given a successful override, When the tenant sends messages in a different language, Then the system does not auto-switch languages while override is active, And a suppressed-switch event is recorded in audit. Given an override is active, When the coordinator clicks "Revert to Auto", Then auto-detect resumes, the override chip is removed, and a "Reverted to Auto" audit event is recorded. Given an override attempt fails due to network or permission error, Then the coordinator sees a non-blocking error toast with actionable retry and the thread language remains unchanged.
Lock Thread Language to Prevent Auto Switch
Given a thread with any current language state and a coordinator with "Lock Language" permission, When the coordinator toggles "Lock language" and selects a language, Then the selected language is locked and cannot be changed by auto-detect or by users lacking "Unlock Language" permission, And a lock icon with tooltip "Language locked" appears in the header. Given a locked thread, When the tenant uses a different language for 3 consecutive messages, Then the system replies in the locked language and sends a one-time bilingual note explaining the lock, And a suppressed-switch event is logged for each message. Given a locked thread, When a coordinator with "Unlock Language" permission clicks "Unlock", Then the lock is removed, auto-detect resumes, and an audit event is recorded. Rule: If an override is active, attempting to lock prompts the user to replace the override with a lock or cancel; only one of override or lock may be active at a time.
Submit Translation/Tone Correction with Side-by-Side Capture
Given a previously generated system/bot message exists in a thread, When a coordinator opens "Submit correction", Then the UI displays side-by-side: source tenant text, original system output (with language and tone labels), and an editable "Corrected output" field, plus controls for "Reason for change" and "Outcome tags". Rule: Save is disabled until "Corrected output" has 1–2000 characters and at least one "Reason for change" is selected. Rule: On Save, the system stores the triplet (source, original, corrected), language codes (ISO 639-1), tone tags, actor, timestamp, thread ID, and message ID, and returns success within 2 seconds (p95). Given the original message has already been sent to the tenant and "Send corrected follow-up" is checked, Then the tenant receives the corrected message within 5 seconds (p95) with a clarifying preface, And the thread displays a "Corrected" badge on the original message. Given "Send corrected follow-up" is not selected, Then the correction is stored for improvement pipelines and template updates only, and no tenant-facing message is sent.
Capture Reason for Change and Outcome Tags
Rule: "Reason for change" is a required multi-select with options including "Wrong language", "Mistranslation", "Inappropriate tone", "Ambiguous phrasing", "Terminology/glossary issue", "Formatting", and "Other"; selecting "Other" requires a free-text note (min 10, max 500 chars). Rule: "Outcome tags" supports multi-select including "Improved tenant response rate", "Reduced back-and-forth", "Escalation avoided", "Vendor clarity improved", "Complaint resolved", and "No impact"; at least one tag must be selected before save. Given a correction is saved, Then the system emits an event to the improvement pipeline queue with payload including thread ID, message ID, reasons, outcome tags, corrected text, and actor, And the event delivery status is "Queued" within 3 seconds (p95). Given event emission fails, Then the system retries with exponential backoff up to 3 attempts and displays a non-blocking banner if final delivery fails; the correction remains persisted locally with status "Queue Failed".
Role-Based Permissions for Override and Corrections
Rule: Users with role "Coordinator" and permission "LANG_OVERRIDE" can view and use override/lock controls; without this permission, the controls are not rendered. Rule: Users with permission "CORRECTION_SUBMIT" can submit corrections; users with "VIEW_ONLY" can view side-by-side but cannot edit or save (Save disabled and API returns 403). Rule: Only roles "Admin" or "Owner" can export audit logs and view organization-wide dashboards; Coordinators can view dashboards scoped to portfolios they have access to. Given a user attempts a restricted action, Then the system returns HTTP 403 at the API layer and shows a toast "You don't have permission to do this"; no changes are persisted and no audit record is created for the blocked action (only for the access denial).
Audit Logging of Overrides and Corrections
Rule: Every override, lock, unlock, correction save, and revert generates an immutable audit record with fields: event_id, event_type, actor_id, actor_role, thread_id, message_id (nullable for thread-level actions), previous_state, new_state, timestamp (UTC ISO 8601), reasons (if any), outcome_tags (if any), and client IP. Rule: Audit records are write-once and cannot be edited or deleted by any role; Admins may append comments which are stored as separate audit entries linked by parent event_id. Given a user searches the audit log by thread ID, actor, date range, or event_type, Then results return within 2 seconds (p95) for up to 10,000 records and are exportable to CSV with column headers matching the fields. Rule: System clocks are synchronized via NTP; audit timestamps across services differ by no more than 100 ms in 99% of events. Rule: All suppressed auto-switch attempts are recorded with reason "Locked/Overridden" and include the candidate language code and confidence.
Dashboard: Detection Accuracy, Switch Events, and Response Outcomes
Given an authorized user opens the AutoLocale Quality dashboard, Then it displays for the selected date range: detection accuracy (%) = correct initial language detections / total initial detections; count of auto switch events; count of coordinator overrides; count of suppressed switches; average first-response time; tenant response rate; and escalation rate. Rule: Dashboard supports filters by portfolio, property, coordinator, language, and date range (up to last 180 days) and updates visualizations within 1 second (p95) after a filter change for datasets ≤100k rows. Rule: Data freshness SLA is ≤15 minutes lag for streaming conversation events and ≤24 hours for model/glossary retraining metrics; freshness timestamp is displayed. Rule: Metrics in UI must match backend API values within 0.5% for the same filters; discrepancies trigger a warning banner. Given no data matches the filters, Then the dashboard shows an empty state explaining "No data for selected filters" with a CTA to reset filters, and no errors are thrown.

SnapLink

Sends a secure, one‑tap camera link that opens in the phone’s browser to capture photos or video right from the chat. Provides lightweight guidance for diagnostic shots and automatically anchors media to the correct ticket. Works in low bandwidth, falls back to MMS when needed, and avoids any app download friction—speeding triage and decisions.

Requirements

One-Tap Secure SnapLink Generation
"As a property manager, I want to send tenants a secure one-tap camera link from the ticket chat so that they can quickly share visual evidence without installing an app."
Description

Generate a unique, signed, short-lived SnapLink per ticket and recipient that opens directly to a capture flow in the mobile browser from within FixFlow chat. Support delivery via SMS, email, and in-app chat with automatic threading to the originating conversation. Include per-link scoping to a single ticket, configurable expiration (e.g., 24–72 hours), one-time or limited-use tokens, and the ability to revoke and reissue links. Track delivery, open, and completion events to power downstream notifications and analytics. Ensure links use a trusted domain, short URL format for SMS, and preserve tenant locale and time zone. Provide admin controls for default expiry, allowed delivery channels, and message templates.

Acceptance Criteria
Per-Recipient Unique, Signed, Expiring SnapLink
Given a ticket T and recipient R with tenant context set, When an agent generates a SnapLink, Then a unique URL is produced whose token is cryptographically signed and verifiable Given the generated SnapLink, When the token is inspected server-side, Then it is scoped to ticket T and recipient R only and cannot access any other ticket Given admin default expiry is 48 hours and per-link expiry override is not set, When the link is created, Then the token TTL is 48 hours from issuance Given a per-link expiry override of 24 or 72 hours, When the link is created, Then the token TTL equals the override value within the allowed 24–72 hour range Given a one-time token (uses_limit=1), When the link is opened once and the capture flow is reached, Then any subsequent access attempts return 410 Gone and are logged Given a limited-use token (uses_limit=N), When the link is opened N times successfully, Then the (N+1)th attempt returns 410 Gone and is logged Given the link is accessed before expiry with a valid signature, When the user taps it, Then the capture flow loads with HTTP 200 over HTTPS Given the link is expired or the signature is invalid, When it is accessed, Then the response is 401/410 with a branded expiry/invalid page and no ticket data leakage
Multi-Channel Delivery and Automatic Threading with Trusted, Short Links
Given channels SMS, email, and in-app chat are enabled, When an agent sends a SnapLink, Then a message is dispatched via the selected channel and recorded on the originating ticket’s conversation thread Given SMS delivery, When the message is sent, Then the URL uses a trusted, allowlisted HTTPS domain and a short path such that the full SMS body is ≤160 GSM-7 chars with the default template Given email delivery, When the message is sent, Then the email subject and body are rendered from the configured template, include the SnapLink, and are threaded to the ticket’s email conversation Given in-app chat delivery, When the message is sent, Then the SnapLink appears as a chat message within the ticket’s chat transcript and is visible to the tenant Given any channel send attempt, When the provider returns a failure, Then the message status is marked Failed with provider code and a retry action is available Given successful dispatch, When delivery receipts arrive (where supported), Then the message status updates to Sent/Delivered and is visible in the ticket activity log
One-Tap Browser Capture Launch with Locale and Time Zone Preservation
Given a tenant with stored locale L and time zone TZ, When the tenant opens the SnapLink on iOS or Android, Then the capture flow opens directly in the default mobile browser without requiring app install or login Given the capture flow loads, When UI text is rendered, Then it appears in locale L and all time displays are converted to TZ Given the SnapLink is opened from within FixFlow chat, When tapped, Then it deep-links to the same mobile browser capture flow without intermediate pages Given network is available, When the capture flow loads, Then the page is served over HTTPS with HSTS enabled and TTFB ≤ 1.5s for p50 in target regions
Event Tracking for Delivery, Open, and Completion
Given a SnapLink is generated, When it is dispatched, Then a Delivery event (Queued/Sent/Delivered/Failed with channel and provider metadata) is stored and associated to ticket T and recipient R Given the recipient opens the link, When the capture flow loads, Then a unique Open event is recorded once per device within a 30-minute window and stamped with tenant TZ-adjusted timestamp Given the recipient completes at least one media upload, When the upload succeeds, Then a Completion event is recorded with counts of photos/videos and total bytes, and the ticket is updated accordingly Given events are recorded, When analytics are queried, Then Delivery, Open, and Completion metrics are available and filterable by time range, channel, property, and technician Given notifications are configured, When a Completion event occurs, Then an internal notification is triggered to the assigned manager within 60 seconds
Revocation and Reissue of SnapLinks
Given an active SnapLink, When an agent or admin clicks Revoke, Then the token is immediately invalidated globally within 60 seconds and further access returns 410 Gone with a friendly message Given a revoked link, When the user attempts access, Then no ticket data or capture UI is exposed, and an audit entry is written with IP, time, and actor Given Reissue is selected, When a new SnapLink is generated for the same ticket and recipient, Then a new token and URL are created and the prior token remains invalid Given reissue occurs, When sending via the prior channel, Then the new message threads to the same conversation and the activity log links the reissue chain
Admin Configuration for Expiry, Channels, and Templates
Given an admin with proper permissions, When default expiry is set between 24 and 72 hours, Then newly created SnapLinks inherit that TTL unless a per-link override is provided Given channel configuration, When SMS or email or in-app chat is disabled, Then those channels are not selectable at send time and APIs return 403 for attempts Given message templates per channel include placeholders {ticket_id}, {property_name}, {tenant_first_name}, {link}, When a preview is requested, Then placeholders resolve using sample data and validation blocks saving if required placeholders are missing Given configuration changes, When saved, Then changes are audit-logged with actor, time, and diffs and take effect for subsequent links without altering existing active links
In-Browser Photo/Video Capture
"As a tenant, I want to capture and submit clear photos or short videos in my phone’s browser so that I can show the issue without downloading an app."
Description

Provide a mobile web capture experience that uses the device camera via getUserMedia and falls back to file picker if permissions are denied. Support photo bursts and configurable video duration (e.g., up to 60 seconds), with client-side compression, HEIC-to-JPEG conversion, orientation correction, and thumbnail generation. Allow users to review, retake, annotate with simple notes, and submit multiple media items in a single session. Show clear progress indicators and gracefully handle interruptions (e.g., call or app switch). Ensure compatibility with major iOS/Android browsers and provide an accessible UI (WCAG AA) with localized copy.

Acceptance Criteria
Browser Camera Capture with Permission Fallback
- Given a supported mobile browser (iOS Safari 15+, Android Chrome 100+, Samsung Internet 19+), When the user opens the capture link, Then the app requests camera permission via getUserMedia and shows a live preview within 1 second of grant. - Given camera permission is denied or getUserMedia is unavailable, When the user attempts to capture, Then the app opens the native file picker restricted to photo/video files and displays guidance text. - Given the preview is active, When the user rotates the device, Then the preview maintains correct orientation and the shutter remains responsive (<200 ms tap-to-capture).
Photo Burst Capture
- Given live preview is active, When the user presses and holds the shutter, Then up to 10 photos are captured at ≥3 fps and added to the tray in capture order. - Given burst photos are captured, When the user opens the tray, Then each item displays a timestamped thumbnail with orientation corrected. - Given the user selects any burst photo, When they tap Retake, Then only that photo is replaced without affecting others.
Configurable Video Capture with Duration Limit
- Given a server-supplied max duration (e.g., 60s), When the user starts recording, Then a countdown/progress shows remaining time and recording auto-stops at the limit ±0.5s. - Given a recorded video, When upload begins, Then client-side compression reduces filesize by ≥30% versus original while keeping video playable (H.264/AAC MP4) and max resolution ≤1080p. - Given recording completes, When the user reviews, Then they can play, delete, or re-record before submission.
Media Processing: HEIC→JPEG, Orientation, Thumbnails
- Given a HEIC/HEIF photo is selected via file picker, When processing starts, Then it is converted client-side to JPEG with color preserved and no visible rotation issues. - Given any photo or the first frame of a video, When thumbnails are generated, Then a 320x320 (max) JPEG thumbnail ≤200 KB is produced for display. - Given a portrait photo with EXIF orientation, When uploaded, Then the server receives a correctly oriented JPEG, and the preview matches the device’s native gallery orientation.
Multi-Item Review, Annotation, and Submission
- Given 1–20 media items are in the tray, When the user taps Submit, Then all items and their per-item notes (up to 140 characters) are uploaded in the displayed order in a single session. - Given network connectivity is available, When upload completes, Then the UI shows a confirmation with the count of items uploaded and clears the local tray. - Given the user edits a note on any item, When they save, Then the change persists with that item and appears in the review screen prior to submit.
Progress Indicators and Interruption Recovery
- Given uploads are in progress, When the app is backgrounded (app switch or call) and reopened within 10 minutes, Then the session and queue resume automatically from the last completed chunk and progress is restored. - Given the network drops mid-upload, When connectivity returns within 10 minutes, Then the upload resumes without duplicating items; otherwise a retry action appears with a clear error. - Given multiple items are uploading, When one item fails permanently (HTTP 4xx), Then it is marked failed with an actionable message while other items continue and the overall session can complete.
Accessibility and Localization Compliance
- Given the capture UI is rendered, When tested with a screen reader (VoiceOver/TalkBack), Then all actionable elements have accessible names, focus order is logical, and contrast ratios meet WCAG 2.1 AA. - Given the device locale is supported, When the capture page loads, Then all user-facing copy appears in that locale; when unsupported, it falls back to English without placeholders. - Given an RTL locale, When the page loads, Then layout mirrors correctly and all controls remain usable via touch and keyboard.
Guided Diagnostic Capture Prompts
"As a technician, I want tenants to receive simple capture prompts tailored to the issue so that the media I receive is actionable for remote diagnosis."
Description

Offer lightweight, context-aware guidance during capture to improve diagnostic value. Automatically select a prompt set based on the ticket category (e.g., leak, appliance, HVAC) and property type. Present step-by-step microcopy and example frames (e.g., "stand back full view," "close-up of model/serial," "show shutoff valve") with optional checklist completion before submit. Allow managers to override or customize prompt sets per portfolio and save presets. Validate minimum media count and diversity (e.g., one wide, one close-up) and warn on blurry/low-light images when detectable without heavy processing.

Acceptance Criteria
Auto-Selection of Prompt Set by Category and Property Type
Given a ticket with category "Leak" and property type "Apartment" When the tenant opens the SnapLink capture page Then the "Leak | Apartment" prompt set is loaded within 500 ms of page render And the first step instruction is visible above the capture control And the selected prompt set ID is associated with the ticket record Given a ticket with an unmapped category or property type When the tenant opens the capture page Then the default "General | All" prompt set is loaded And an unmapped-prompt-selection event is logged with ticket ID and attributes
In-Capture Step-by-Step Guidance with Example Frames
Given a loaded prompt set with 3 steps and example thumbnails When the user advances from step 1 to step 2 Then a progress indicator displays "Step 2 of 3" And the current step microcopy is readable on-screen without scrolling on a 360x640 viewport And tapping "View example" opens a thumbnail overlay within 300 ms And closing the overlay keeps the camera preview active and focused And the current instruction is exposed to screen readers via aria-live when the step changes
Checklist Gating Before Submit (Configurable)
Given checklist gating is enabled for the prompt set with required items [Full view, Close-up] When the user attempts to submit with only the Full view item completed Then submission is blocked and an inline message lists the missing item "Close-up" And a "Go to missing step" control navigates to the first unmet requirement Given checklist gating is disabled for the same prompt set When the user submits with zero required items completed Then submission proceeds without blocking and the attempt is logged with counts of completed items
Manager Overrides and Preset Management
Given a portfolio manager with customization permissions When the manager clones the system "Appliance | All" prompt set, edits steps, and saves as "Appliance | Preset A" Then the preset is versioned and saved within 2 seconds and becomes selectable as default for that portfolio And new tickets created in the portfolio after save auto-load "Appliance | Preset A" for category "Appliance" And existing in-progress captures retain their original prompt set And an audit log entry records user, timestamp, and diff of changes And the manager can revert the portfolio to the system default in one action
Media Count and Diversity Validation
Given the prompt set requires minimum 2 media with diversity: at least 1 Full view and 1 Close-up When the user has captured only one Full view photo Then the Submit button is disabled and a hint lists missing "Close-up" And the missing type is highlighted in the step list Given the user has captured 1 Full view and 1 Close-up When the user taps Submit Then submission succeeds without validation errors Given automatic type detection is unavailable When the user reviews a photo Then the user can manually tag it as "Full view" or "Close-up" to satisfy diversity validation
Blurry/Low-Light Detection and Warning
Given a captured image with blur or low-light metrics exceeding thresholds When the image is added to the review tray Then a non-blocking warning banner appears within 200 ms offering "Retake" and "Use anyway" And selecting "Retake" returns to the same step with the camera active And selecting "Use anyway" allows submission without additional prompts And per-image detection processing time is ≤100 ms on a mid-tier device and occurs client-side without uploading full-resolution for analysis
Low-Bandwidth Degraded Guidance Delivery
Given measured network downlink <300 kbps or example assets fail to load within 2 seconds When the capture page renders Then text-only step prompts are displayed and example frames are replaced by a "Text-only mode" placeholder And the page remains interactive within 1 second of first paint And total guidance asset payload (excluding captured media) is ≤150 KB And the user can complete capture and submit without loading example frames And a telemetry event records degraded-mode activation with network metrics
Automatic Ticket Anchoring & Metadata Tagging
"As a property manager, I want uploaded media to auto-attach to the correct ticket with useful metadata so that I can review and act without manual sorting."
Description

Upon successful upload, automatically attach media to the originating ticket and chat thread with correct ordering and attribution to the sender. Persist metadata including timestamp, uploader identity, device type, network type (if available), and optional geolocation (with explicit consent). Generate different renditions (thumbnail, web, original) and perform deduplication and safe filename normalization. Emit events/webhooks for downstream systems (e.g., vendor portals) and update ticket state (e.g., move to "Awaiting Review"). Enable quick actions such as "promote to cover image" and "copy link for vendor" while maintaining access controls.

Acceptance Criteria
Auto-Anchor Media to Originating Ticket and Chat with Correct Ordering and Attribution
Given a SnapLink is generated from ticket T and sent to participant P And P uploads N media items successfully When the upload completes Then each item is attached to ticket T and appears in the ticket media gallery and corresponding chat thread And each item is attributed to participant P And gallery ordering uses capture timestamp; if unavailable, upload receive time; ties resolved by server receive order And the chat thread message for each upload includes thumbnails in chronological order
Metadata Persistence with Explicit Geolocation Consent
Given a user uploads media via SnapLink session S for ticket T When the system processes the media Then it persists metadata: capture_timestamp, uploader_id, device_type, network_type (if detectable), geolocation (only if S recorded explicit allow) And if geolocation consent is not granted, no location is stored and the value is null And metadata is retrievable via API and visible in the ticket UI to authorized users And all timestamps are stored in UTC with millisecond precision And missing fields are recorded as null; no default or fabricated values are used
Rendition Generation and Availability
Given an image or video is uploaded When processing completes Then the system generates renditions: thumbnail (max 320 px on longest edge), web (max 1600 px), and original (unaltered) And for video, a poster-frame thumbnail is generated And each rendition has a stable URL plus content-type and dimensions/size metadata And image renditions for files ≤20 MB are available within 10 seconds 95% of the time And requesting any rendition respects access controls
Deduplication and Safe Filename Normalization
Given a media file F is uploaded to ticket T When an identical content hash already exists on ticket T within the last 30 days Then the system prevents duplicate storage, reuses the existing asset, and creates a new reference in the chat/thread as needed And the UI indicates the existing media was referenced rather than duplicated And F’s saved filename is normalized: path stripped, control characters removed, lowercased, spaces to hyphens, max length 128, original extension preserved, uniqueness ensured with a short hash suffix And filenames are safe for all supported filesystems and URLs
Event/Webhook Emission and Ticket State Update
Given ticket T is in New or In Progress When the first media in a new batch is successfully attached Then ticket T transitions to Awaiting Review And a MediaUploaded event is emitted with payload including ticket_id, media_id(s), uploader_id, timestamps, renditions, metadata, and state_before/state_after And the event is delivered to all configured webhooks within 15 seconds with retries using exponential backoff for up to 24 hours and idempotency keys And no duplicate events are delivered to the same endpoint when retried And if T is already Awaiting Review or a later state, the state does not regress
Quick Actions: Promote to Cover Image and Copy Vendor Link
Given an authorized staff user views media M on ticket T When they select Promote to cover image Then M becomes the cover image for T everywhere the cover is displayed And the action is audited with actor, timestamp, and prior cover recorded When the user selects Copy link for vendor Then a signed, read-only URL is generated scoped to the selected media, expiring after 72 hours by default and revocable And the link reveals only the intended media and minimal metadata, with no ticket PII And access via the link is logged and respects expiration and revocation
Low-Bandwidth and MMS Fallback Uploads Anchor and Tag Correctly
Given a tenant completes media submission via low-bandwidth mode or MMS fallback for ticket T When the media is received by the system Then the media is auto-anchored to ticket T and attributed to the tenant And metadata is populated with available fields; network_type reflects MMS or low-bandwidth; missing fields are null And deduplication, filename normalization, rendition generation, event emission, and state update rules apply identically
Low-Bandwidth Upload with MMS Fallback
"As a tenant on a slow connection, I want my photos or video to still reach the manager—automatically using MMS if needed—so that my issue isn’t delayed."
Description

Optimize media capture for constrained networks using adaptive compression, chunked/resumable upload, and background retry with exponential backoff. Provide an offline queue when connectivity drops and resume seamlessly. Detect sustained poor throughput and offer a one-tap MMS fallback that packages the ticket token so inbound carrier messages can be automatically associated with the ticket. Support major US carriers and gracefully handle MMS size limits by splitting or adjusting quality. Display clear user messaging for each path and ensure identical post-processing regardless of transport.

Acceptance Criteria
Adaptive Compression Under Poor Connectivity
Given SnapLink is opened in a mobile browser and measured median upload throughput is ≤ 250 kbps over the last 10 seconds When the user captures a 12 MP photo Then the client compresses the image so the long edge is between 1280 px and 1920 px and the file size is ≤ 400 KB, completing client-side processing within 1.5 seconds Given the same network condition When the user captures a 10-second 1080p video Then the client downscales to 720p and adjusts bitrate to target an estimated upload completion ≤ 45 seconds at the measured throughput, producing a file ≤ 2.5 MB
Chunked Upload with Resumable Background Retry
Given a file upload using chunked transfer with server-side chunk acknowledgements When connectivity drops after at least one chunk is acknowledged Then the client resumes from the last acknowledged chunk within 5 seconds of reconnection without re-sending confirmed chunks And per-chunk and whole-file checksums validate integrity prior to finalize And retries follow exponential backoff starting at 1s, doubling to a max interval of 32s with ±20% jitter, capping at 6 attempts per chunk before marking the item as Failed (retry available) And uploading continues up to 5 minutes after screen lock/app backgrounding; if OS suspends, it auto-resumes on foreground
Offline Queue and Seamless Resume
Given the device is offline or loses connectivity during capture When the user captures up to 20 media items Then each item is added to an offline queue, visibly labeled "Queued (Offline)", and persists across browser refresh or device reboot And upon connectivity restoration, uploads auto-start within 3 seconds, process FIFO with up to 2 concurrent uploads, and show per-item progress And the user can pause, resume, or cancel individual queued items; cancelled items are removed from storage And after 3 consecutive upload retries fail for an item, the system surfaces a one-tap MMS fallback action for that item
Automatic MMS Fallback with Ticket Token
Given median throughput < 150 kbps for 20 seconds or 3 consecutive chunk failures on the current item When the system offers MMS fallback and the user taps "Send via MMS" Then the native SMS/MMS composer opens with the recipient number prefilled and the message body containing the ticket token and brief instructions And inbound MMS messages that contain the token are auto-associated to the ticket within 10 seconds of carrier receipt, including any attached media And media received via MMS is labeled "via MMS" in the ticket timeline And the MMS option is hidden on devices without telephony/SMS capability
MMS Size Handling Across Major US Carriers
Given the carrier is detected as Verizon, AT&T, T-Mobile, or UScellular When preparing media for MMS Then the client applies the configured per-carrier payload cap and ensures each MMS payload does not exceed that cap And if a single asset exceeds the cap, the client re-encodes to fit; if still too large, the client splits videos into sequential segments such that each segment payload ≤ the cap and composes multiple drafts labeled 1/N, 2/N, etc., each including the ticket token; if the OS disallows multi-draft composition, the UI provides step-by-step instructions to send the prepared drafts sequentially And for unknown carriers, a conservative default cap of ≤ 500 KB per image and ≤ 1.5 MB per video segment is enforced And if the user declines quality reduction for an oversize asset, the MMS send is blocked with an explanatory message and an option to record a shorter clip
Consistent Post-Processing and Ticket Association
Given media arrives via HTTP upload or via MMS When server-side post-processing executes Then the same pipeline runs for both transports: malware scan, orientation normalization, EXIF stripping, generation of 320 px and 1024 px thumbnails, perceptual hash, and metadata extraction And the final media object schema is identical and attached to the originating ticket; only the transport field differs And duplicates detected by perceptual hash are not duplicated in the timeline; the event notes "duplicate suppressed" And processing time from receipt to availability is ≤ 15 seconds for images and ≤ 60 seconds for videos under nominal load
User Messaging and Status Indicators
Given any transport path (HTTP upload, offline queue, MMS fallback) When the user interacts with SnapLink during capture and upload Then the UI surfaces clear, accessible status messages and actions including: "Uploading X%", "Network lost—queued", "Resuming…", "Poor network detected—try MMS", "Sent via MMS—awaiting receipt", "Received and attached", and "Retry in Ns" And all status messages meet WCAG 2.1 AA for contrast and are announced by screen readers And error states include recovery actions without blocking further captures And all status transitions are logged with timestamp, device identifier, transport type, and error codes
Access Control, Expiration & Audit Logging
"As an account owner, I want strict controls and auditability around SnapLink access so that our team stays compliant and protects tenant privacy."
Description

Enforce strict access controls on SnapLink sessions and resulting media. Scope tokens to the ticket and recipient, with configurable TTL, IP rate limiting, and device/browser fingerprinting for anomaly detection. Encrypt uploads in transit and at rest, run antivirus/malware scans, and optionally watermark media with ticket ID and timestamp. Require explicit consent for location or microphone use and display a privacy notice. Maintain immutable audit logs for link creation, delivery, opens, uploads, views, downloads, and revocations, exportable for compliance. Support RBAC for who can generate links, view originals, or forward media to vendors.

Acceptance Criteria
Scoped, Expiring SnapLink Token
Given a ticket T and recipient R, When a SnapLink is generated, Then the token authorizes only media actions for ticket T by recipient R. Given a valid token before TTL expiry, When the recipient opens the link, Then access is granted and a 'LinkOpened' audit event is recorded. Given the TTL has elapsed, When the link is opened, Then HTTP 410 Gone is returned and a 'TokenExpired' audit event is recorded. Given a token is manually revoked by an authorized user, When the link is opened within 60 seconds after revocation, Then HTTP 403 Forbidden is returned and a 'TokenRevoked' event is recorded. Given an attempt to use the token to access another ticket or by a different recipient, When the link is opened, Then HTTP 403 Forbidden is returned and a 'ScopeMismatch' event is recorded.
Abuse Detection and Rate Limiting
Given a SnapLink token, When more than 10 requests are made from the same IP within 60 seconds for that token, Then subsequent requests within that window return HTTP 429 Too Many Requests and a 'RateLimited' audit event is recorded. Given a SnapLink token, When opens are attempted from more than 5 distinct IPs within 10 minutes, Then the token is locked for 5 minutes, returns HTTP 423 Locked, and a 'BruteForceSuspected' event is recorded. Given the first successful open establishes a device/browser fingerprint (UA family + OS family + pixel ratio), When a subsequent open differs in 2 or more of these attributes, Then an 'AnomalyDetected' event is recorded; access proceeds unless tenant setting 'block_on_anomaly' is true, in which case HTTP 403 Forbidden is returned.
Encrypted Uploads and Malware Scanning
Given any media upload via SnapLink, When data is transmitted, Then TLS 1.2+ with HSTS is enforced. Given media is stored, Then it is encrypted at rest with AES‑256 (or provider‑managed equivalent) and keys are rotated per policy. Given an upload completes, When antivirus scanning runs, Then clean results attach media to the ticket; If malware is detected, Then the file is quarantined, the uploader is shown a blocking message, no preview is generated, a manager notification is sent, and a 'MalwareDetected' audit event is recorded; scanning completes within 60 seconds for files ≤ 500 MB.
Optional Media Watermarking
Given tenant setting 'watermark_media' is ON, When images or videos are processed for preview or external sharing, Then a visible watermark containing ticketId and UTC timestamp is added at bottom‑right with 60% opacity; originals remain unmodified. Given 'watermark_media' is ON and media is shared to vendors via link, When a vendor accesses the media, Then only watermarked versions are accessible unless the vendor link includes permission 'ViewOriginalMedia'.
Explicit Consent for Location/Microphone with Privacy Notice
Given the capture flow requests location or microphone, When the recipient opens the SnapLink, Then a privacy notice states purpose, retention period, and contact details, and an explicit 'Allow' action is required to proceed. Given the recipient taps 'Deny' or closes the consent modal, When capture would otherwise start, Then capture is aborted, no sensors are accessed, and the chat displays an alternative-instructions message. Given consent is granted, When sensors are accessed, Then a consent record (data type, timestamp UTC, token, IP, user agent) is written to the audit log and is valid only for that session; new links require new consent.
Immutable Audit Logging and Export
Given any SnapLink lifecycle event (create, deliver, open, upload, view, download, revoke, rate-limit, anomaly, consent), When it occurs, Then an append-only audit record is written including: eventType, ticketId, actorId or recipient, IP, fingerprint hash, timestamp UTC, HTTP status, outcome. Given audit log storage, When a modification or deletion is attempted, Then the operation is rejected and an 'AuditTamperAttempt' event is recorded; hash-chain integrity is verifiable via periodic digest. Given an authorized user requests export with filters (date range and ticketId), When the export is generated, Then logs are delivered as CSV or NDJSON within 60 seconds for up to 100k records, with a SHA‑256 checksum and an access-controlled download URL expiring in 24 hours.
RBAC Enforcement for Link and Media Access
Given RBAC is configured, When a user attempts to generate a SnapLink, Then only users with permission 'GenerateSnapLink' can do so; others receive HTTP 403 Forbidden and an 'AuthorizationFailed' audit event is recorded. Given RBAC is configured, When a user attempts to view original, unwatermarked media, Then only users with permission 'ViewOriginalMedia' can access; others see watermarked previews only. Given a user attempts to forward media to a vendor, When the action is executed, Then only users with permission 'ForwardMediaToVendor' can create vendor-facing links, and the action (actor, target, timestamp) is recorded in the audit log.
Delivery, Capture Metrics & Alerts
"As a property manager, I want visibility and alerts on link delivery and capture success so that I can intervene quickly and keep repairs moving."
Description

Provide observability for SnapLink performance and outcomes: delivery status, open rate, time-to-first-media, completion rate, average upload time by network type, MMS fallback rate, and failure reasons. Surface dashboards and per-ticket activity timelines, with CSV export and API access. Trigger configurable alerts when a tenant hasn’t opened a link within a set window, uploads repeatedly fail, or low-quality media is submitted. Support A/B testing of prompt sets and message templates to improve completion and diagnostic quality over time.

Acceptance Criteria
Delivery and Open Tracking
Given a SnapLink is sent from FixFlow chat to a tenant When the message is queued, sent, delivered, opened, expires, or fails Then the system records an event with event_type in [queued, sent, delivered, opened, expired, failed] and an ISO-8601 UTC timestamp within 5 seconds of occurrence And per-ticket timeline displays the events in chronological order with tenant local time and UTC And dashboard aggregates show counts and rates (sent, delivered, open rate) aligned with the raw events with ±0.5% tolerance for the selected date range And open rate equals unique links opened / links delivered for the filter set
Time-to-First-Media and Completion Rate
Given a tenant opens a SnapLink When the first photo or video successfully uploads Then time_to_first_media_seconds is recorded as the difference between first_open_at and first_media_uploaded_at And if no media is uploaded before link expiry (24h) or ticket close, time_to_first_media_seconds is null And completion is recorded true when all required media in the assigned prompt set are uploaded; otherwise false And dashboards display median and p90 time_to_first_media and completion rate per template and property with data latency < 60 seconds And per-ticket timeline shows first_media_uploaded and completion events
Upload Performance, Network Type, Fallback, and Failure Reasons
Given a media upload attempt via SnapLink or MMS When the upload starts and ends Then upload_duration_seconds and network_type in [wifi, 5g, 4g, 3g, 2g, unknown] are recorded per asset And dashboard shows average upload_duration_seconds by network_type and file_size_bucket And if three consecutive browser upload attempts fail within 10 minutes Then the system triggers MMS fallback and records fallback_used=true with fallback_reason And failure_reason_code in [CAMERA_PERMISSION_DENIED, NETWORK_TIMEOUT, FILE_TOO_LARGE, LINK_EXPIRED, CARRIER_BOUNCE, UNSUPPORTED_FORMAT, STORAGE_ERROR, UNKNOWN] and a human_readable_message are recorded for each failed attempt And top failure reasons are visible in the dashboard and included in exports and API
Dashboards and Per-Ticket Timeline
Given a user with Manager or Owner role opens the SnapLink Performance dashboard When they filter by date range (up to 90 days), property, unit, template, channel, and network type Then widgets render within 2 seconds for up to 30 days of data and within 6 seconds for up to 90 days And the dashboard shows sent, delivered, open rate, time-to-first-media (median, p90), completion rate, average upload time by network type, MMS fallback rate, and top failure reasons And clicking a metric drills down to the underlying tickets within the same filters And each ticket’s activity timeline shows all events with timestamps, actor, channel (web/MMS), and links to the stored media And data in dashboard and timeline are consistent with CSV export and API for the same filters and period
CSV Export and Metrics API
Given the user requests a CSV export for a selected filter and date range up to 50,000 rows When the export is generated Then a UTF-8 CSV with ISO-8601 UTC timestamps and the following columns is produced: ticket_id, property_id, unit_id, tenant_id, snaplink_id, sent_at, delivered_at, first_open_at, time_to_first_media_seconds, completed, media_count, avg_upload_seconds, network_type_at_first_upload, mms_fallback_used, last_failure_reason_code, template_id, prompt_variant_id And the file is available for download within 60 seconds and retained for 24 hours And the Metrics API endpoint /v1/snaplink/metrics supports the same filters, pagination (cursor), and rate limiting (min 60 requests/min), and returns JSON with the same fields And API requests require a valid API key with scope metrics:read and are logged with request_id for audit
Configurable Alerts for Inactivity, Failures, and Low Quality
Given an admin defines alert rules for SnapLink inactivity, upload failures, and low-quality media When a tenant has not opened a link within X minutes after delivery Then an alert is generated once per link per rule with delivery via in-app and optional email/SMS and includes ticket_id, tenant, link, and elapsed_minutes And when Y consecutive upload failures occur within Z minutes Then an alert is generated with last_failure_reason_code and a troubleshooting guidance link And when media quality_score < threshold on any uploaded asset Then an alert is generated tagging the asset and prompting a re-request flow And alerts are deduplicated within 30 minutes per link per rule, support acknowledge/snooze, and appear in the ticket timeline and alerts dashboard And all alert events are available via API and CSV export
A/B Testing of Prompts and Message Templates
Given two or more active variants of prompt sets and/or message templates exist When a new SnapLink is created for an eligible ticket Then the tenant is randomly assigned to a variant using the configured allocation ratio (default 50/50) with per-property stratification and variant_id persisted on the link and ticket And dashboards and exports segment all key metrics (open rate, time-to-first-media, completion rate, upload time, fallback rate, quality_score) by variant And the system computes lift and two-tailed significance at alpha=0.05 with sample size and power displayed; variants not meeting minimum sample size are marked "insufficient data" And an admin can schedule a test end date and optionally auto-select the winning variant based on a chosen primary metric And variant assignment and changes are auditable with timestamps and actor

NumberMatch

Matches incoming phone numbers to tenants, leases, and units instantly, with quick verification for guests or shared phones. Handles nicknames and secondary contacts, then auto‑attaches the conversation to the right property record. Prevents misrouted tickets and ensures every message lands in the correct place—no spreadsheet lookup required.

Requirements

Instant Number-to-Record Matching
"As a property manager, I want incoming calls and texts to automatically match to the right tenant and unit so that tickets are created and routed without manual lookup."
Description

Normalize inbound phone numbers to E.164, detect channel (voice/SMS), and perform low-latency matching to tenants, leases, and units within the portfolio. Implement deterministic and probabilistic matching with confidence scoring and precedence rules (active lease > current tenant > secondary contact > historical). Index phone fields and cache hot records to meet sub-300ms end-to-end lookup. Support locale-aware formatting, country codes, and duplicate number detection. Expose a matching result object (entity type, entity ID, confidence, rationale) for downstream consumers. Emit events for matched/unmatched outcomes and store match metadata for analytics and auditing.

Acceptance Criteria
E.164 Normalization Across Locales
Given inbound phone numbers in varied human-readable formats and portfolio default country is set And tenant/lease locale is known when available When the numbers are parsed for matching Then each valid number is normalized to E.164 with the correct country code And invalid or incomplete numbers are rejected and flagged as invalid_number And normalization outputs for sample cases match expected E.164 values (+14155550123 for US 415 555 0123, +442079460958 for GB 020 7946 0958, +61293744000 for AU 02 9374 4000) And normalization success rate for a test set of valid inputs is >= 99.9%
Channel Detection and Attribution
Given an inbound message arrives via SMS When the message is ingested Then the detected channel is sms and is included in the matching context and result And for an inbound voice call the detected channel is voice and is included in the matching context and result
Sub-300ms End-to-End Lookup
Given phone fields are indexed and hot records are cacheable And the cache is warmed with prior lookups When executing 1,000 consecutive lookups on hot numbers Then the p95 end-to-end latency from ingest to result emission is <= 300 ms And the p99 latency is <= 400 ms And the cache hit rate for repeat lookups of the same 100 numbers is >= 80% And no single lookup exceeds 1,000 ms
Precedence-Driven Matching with Confidence Scoring
Given a normalized number associated with multiple record types across the portfolio When matching is performed Then the selected match follows precedence: Active Lease > Current Tenant > Secondary Contact > Historical And a deterministic exact association returns confidence >= 0.90 And a probabilistic association returns confidence between 0.50 and 0.89 and includes a rationale describing contributing signals And if the highest confidence is < 0.50, the outcome is unmatched with rationale low_confidence
Duplicate Number Detection and Ambiguity Handling
Given a normalized number is linked to two or more active records with equal precedence When matching is performed Then the system identifies ambiguity and does not auto-attach to any single record And the outcome is unmatched with rationale duplicate_number And an event is emitted indicating an unmatched result with reason duplicate_number
Matching Result Object Contract
Given any lookup result is produced When the result object is serialized Then it contains entityType in [tenant, lease, unit] or null when unmatched And entityId is a valid UUID or null when unmatched And confidence is a float in the inclusive range [0.0, 1.0] And rationale is a non-empty string explaining precedence and match type And the object validates against the published JSON schema
Event Emission and Metadata Storage for Auditing
Given a lookup attempt completes (matched or unmatched) When the outcome is finalized Then an event is emitted with outcome matched or unmatched, entityType/entityId (or null), confidence, rationale, channel, and normalized number And match metadata is persisted with the same fields plus a timestamp And stored records are retrievable by normalized number and time range filters And the write success rate for events and metadata during tests is >= 99.9%
Ambiguity Resolution & Quick Verification Prompts
"As a tenant using a shared phone, I want a quick verification prompt to confirm who I am and my unit so that my issue is filed to the correct property."
Description

When multiple records share a number or confidence is below threshold, initiate a brief verification flow (SMS reply or IVR keypad) to confirm identity and unit (e.g., "Reply 1 for Unit 3B - Alex, 2 for Unit 3B - Sam"). Provide configurable prompts, timeouts, retries, and fail-safe escalation to manual triage. Support multilingual prompts (e.g., EN/ES) and accessibility for voice. Cache successful selections for a session to streamline subsequent messages. Persist verification outcome to finalize attachment and improve future matching models.

Acceptance Criteria
Multi-Match SMS Verification (EN)
Given an inbound SMS from a phone linked to 2–5 active candidates and the match confidence is below the configured threshold When the system sends a numbered English prompt listing candidates with unit and name (e.g., "1: Unit 3B — Alex; 2: Unit 3B — Sam") Then a reply "1" within 15 minutes selects the first candidate and attaches the current message and thread to the selected tenant and unit And a confirmation SMS ("Confirmed: Alex — Unit 3B") is sent within 5 seconds of selection And the selection is cached for the session for 30 minutes (configurable) so subsequent inbound messages in the session are auto-attached without re-prompting And an audit log entry is recorded with candidate_ids, selected_id, phone, channel=sms, prompt_version, and timestamp
IVR Keypad Verification and Voice Accessibility
Given an inbound voice call from a phone with multiple candidate matches or low-confidence match When the IVR plays TTS options enumerated with unit and name and accepts DTMF inputs 1–9 Then a keypad input "2" selects the second candidate and attaches the call record/voicemail transcript to the selected tenant and unit And callers can press * to repeat options or 0 to escalate per configuration And after 3 failed or no-input attempts, the flow escalates to manual triage and stops automated prompts for this call
Multilingual Prompts (EN/ES)
Given the tenant/candidate language preference is set to 'es' or the inbound message indicates Spanish (e.g., "ES") When the verification prompt is sent Then the SMS/IVR content is delivered in Spanish with the same options and numbering as English And the confirmation message is also sent in Spanish upon selection And if no 'es' preference or indicator exists, the system defaults to English And the language used is captured in the audit log for the interaction
Configurable Timeouts, Retries, and Escalation
Given SMS verification is configured with timeout_seconds=120, max_retries=2, and escalation_queue="Manual Triage" When no valid numeric reply is received within 120 seconds for an attempt Then the system re-prompts up to 2 times and consumes one retry per invalid/no-response And invalid replies (non-numeric or out-of-range) do not match any candidate and trigger a clarifying prompt including the valid range And after retries are exhausted, the conversation is routed to Manual Triage with reason=no_response and the requester is notified of escalation And once escalated, no further automated verification prompts are sent in this thread until an agent intervenes
Guest or Shared Phone Quick Verification
Given guest handling is enabled and the prompt includes an option "0: Not listed / Guest" When the user replies "0" Then the system requests name and unit via SMS and validates the unit against active properties And on a valid unit (e.g., "3B"), the conversation attaches to that unit with a temporary Guest contact labeled with the provided name and phone And if the unit is invalid after 1 retry, the flow escalates to Manual Triage and preserves the transcript And the guest selection is cached for the current session but is not persisted as a permanent contact without agent approval
Persist Verification Outcome and Improve Matching
Given a selection is made via SMS or IVR When the selection is confirmed Then the system persists selected_contact_id, unit_id, verification_method, language, selected_at, and session_id and marks the thread as attached And a training event "number_match_outcome" is emitted with candidate_count, top_confidence, selected_id, and was_prompted=true And subsequent inbound messages from the same phone within 30 days auto-match to the persisted mapping unless the lease/contact status changed or a conflict is detected, in which case verification re-triggers And the verification outcome is visible in the admin audit with filters for date range, method, and language
Multi-Contact & Shared Line Linking
"As a property manager, I want shared numbers to be linked to multiple contacts and leases so that roommates and family members can communicate without confusion."
Description

Allow a single phone number to be linked to multiple contacts and leases (roommates, spouses, guardians, property staff) with relationship tags and effective dates. Define default routing per number with per-message identity confirmation when ambiguity exists. Support temporary guest numbers that auto-expire after a configurable period. Maintain historical linkages for audit while preventing misrouting to inactive leases. Provide APIs and UI to view, add, or remove number-to-contact associations safely.

Acceptance Criteria
Create Multiple Associations with Relationship Tags and Effective Dates
Given a normalized E.164 phone number And the user has permission to manage associations When the user links the number to two or more contacts and leases, providing a relationship tag and an effective start (and optional end) date for each link Then the system saves all links, requiring relationship tag and start date for each, and returns success with unique association IDs And overlapping effective date ranges are allowed across different contacts/leases And an audit record is stored for each created link including actor, timestamp, and before/after values And each contact and lease profile displays the number with the correct relationship and effective dates
Default Routing for Numbers with Multiple Active Associations
Given a phone number has multiple active associations And a default route is configured to a specific contact/lease When an inbound SMS or call arrives from that number Then the conversation auto-attaches to the configured default contact/lease only And no other association receives the message And if no default is set, the inbound is placed into an Ambiguous queue for resolution And the system enforces at most one default route per phone number at any time
Per-Message Identity Confirmation on Ambiguous Inbound Messages
Given a phone number has two or more active associations And the "Require identity confirmation on ambiguity" setting is enabled for that number When an inbound message arrives Then the sender receives an automated prompt listing active contacts (by display name and relationship) to choose from, plus a Guest option if configured And until a valid selection is received within the configured timeout, the message remains Pending and is not attached to any record And upon valid selection, the message is attached to the chosen contact/lease and the action is logged And invalid inputs trigger a re-prompt up to the configured retry limit before routing to the Ambiguous queue
Temporary Guest Number Auto-Expiration and Renewal
Given a guest association is created for a phone number with a configurable expiration period When the expiration timestamp is reached Then the association status becomes inactive and the number no longer routes to that guest And future inbound messages route per default/identity confirmation rules ignoring the expired association And the system retains the association historically for audit and reporting And authorized users can extend or renew the guest association by setting a new effective period, with all changes audited
Prevent Routing to Inactive Leases while Preserving History
Given an association links a number to a lease whose effective end date has passed or status is inactive When a new inbound message arrives from that number Then the system must not attach the message to the inactive lease And if another active association exists, route per default/identity confirmation rules; otherwise, place in the Ambiguous queue And historical queries "as of" a past date return the association that was active at that time And all changes in lease status affecting routing are captured in the audit log
Secure REST APIs for Viewing and Managing Associations
Given the public API is available When a client calls GET /phone-numbers/{e164}/associations Then the API returns current and historical associations with relationship tags, effective dates, status, and default/confirmation flags, paginated and filterable by status and date range When a client calls POST to create or PATCH to update an association with required fields (relationship tag, effective dates) Then the API validates input, enforces permissions (401/403), supports idempotency via Idempotency-Key, and optimistic concurrency via ETag/If-Match, returning 201/200 with resource representations When a client calls DELETE on an association Then the API end-dates (soft-deletes) the link and retains history; hard delete is not permitted And all API mutations emit audit events with actor, timestamp, and change set
UI Management of Number-to-Contact Associations with Safe Operations
Given a user with manage permissions views a contact, lease, or phone number profile Then the UI lists all linked associations with relationship tags, effective dates, active/inactive status, and default routing indicator And the user can add a link (with relationship and dates), set/unset default route, toggle identity confirmation requirement, and end-date (remove) a link And the UI prevents setting more than one default route per number and warns when removing the last active association would leave the number unrouted And destructive actions require explicit confirmation and reflect immediately in routing behavior And the screen meets accessibility basics (keyboard navigation, labeled controls) and surfaces success/failure toasts for all operations
Auto-Attach Conversations to Correct Records
"As a property manager, I want conversations to automatically attach to the correct lease, unit, and maintenance ticket so that history stays accurate and work can proceed immediately."
Description

Automatically attach inbound/outbound messages and call logs to the resolved property entities (tenant, lease, unit) and any relevant open maintenance ticket, or create a new triage ticket when none exists. Ensure idempotent linking to avoid duplicates, and support re-link with full cascade updates if an attachment is corrected later. Update conversation timelines and notify downstream workflows (triage, approvals, scheduling) via internal events. Provide deep links for agents to the attached record and maintain a clear audit trail of attachment decisions.

Acceptance Criteria
Inbound message from recognized tenant auto-attaches to tenant, lease, unit, and open maintenance ticket
Given a normalized inbound SMS or voicemail from a phone number matched by NumberMatch to a single active tenant, their current lease, and unit with match confidence >= 0.9 and an open maintenance ticket exists for that tenant/unit When the message is ingested by FixFlow Then the conversation, message, and call log (if applicable) are attached to the matched tenant, lease, unit, and to the most recently updated open maintenance ticket for that tenant/unit within 5 seconds And exactly one attachment record is created per entity (no duplicates) And the conversation timeline for each attached entity reflects the new entry with the correct timestamp and source channel
Outbound agent reply and call log auto-attach to the same resolved records
Given an agent sends an outbound SMS or places/receives a call from within FixFlow to/from a number that resolves via NumberMatch to tenant T, lease L, unit U (or is already associated to an existing conversation attached to T, L, U within the last 30 days) When the outbound activity is recorded Then the message/call log is attached to T, L, U and the most recently updated open maintenance ticket for that tenant/unit (if any) within 5 seconds And if no open maintenance ticket exists, the activity is attached to T, L, U only (no new ticket is created by outbound activity) And exactly one attachment per entity is stored
Unknown or guest number verification and triage ticket creation
Given an inbound SMS/call from a number not uniquely matched to a tenant/lease/unit When the sender completes quick verification (e.g., receives and returns a one-time verification code) or an agent manually confirms the correct tenant/lease/unit Then the conversation is attached to the verified tenant, lease, unit within 5 seconds And if no relevant open maintenance ticket exists for that tenant/unit, a new triage ticket is created and linked to the conversation within 5 seconds And the triage ticket title auto-includes the channel and phone number, and the first message body is captured as the ticket description
Idempotent linking eliminates duplicates across retries and replays
Given the ingestion endpoint receives the same message/call event (same provider messageId/callId) up to 3 times due to provider retries When processing these repeated events Then exactly one conversation is created (or updated) and exactly one set of attachments to tenant, lease, unit, and ticket is persisted And no duplicate timeline entries are created on any attached entity And only one set of internal events is emitted for the unique messageId/callId (subsequent duplicates are ignored)
Re-linking a conversation cascades updates to all attachments and timelines
Given a conversation currently attached to tenant T1, lease L1, unit U1, and ticket K1 is corrected by an agent to tenant T2, lease L2, unit U2, and optionally ticket K2 When the re-link action is confirmed Then all existing attachments to T1, L1, U1, K1 are removed and new attachments to T2, L2, U2, K2 are created within 10 seconds And all conversation timeline entries are moved from the old entities to the new entities preserving original timestamps and authors And internal events conversation.relinked and ticket.linked (if applicable) are emitted with from/to references and correlationId
Internal events emitted and deep links available for downstream workflows and agent navigation
Given a conversation is attached (initially or after re-link) When the attachment is committed Then internal events conversation.attached and, if a triage ticket is created or linked, ticket.created or ticket.linked are emitted within 2 seconds with payload including conversationId, tenantId, leaseId, unitId, ticketId (if any), channel, sourceNumber, confidence, and correlationId And the conversation view displays deep links to the attached tenant, lease, unit, and ticket that open the target record in a new tab within 1 click And each target record’s activity timeline displays a deep link back to the conversation
Audit trail of attachment decisions and re-links is complete and immutable
Given any attachment or re-link action (automatic or manual) When the action completes Then an audit log entry is written containing timestamp, actor (system or user id), input phone number (E.164), match method (exact, nickname, secondary contact, manual), confidence score, selected tenant/lease/unit/ticket IDs, rules applied, and reason (if manual) And prior audit entries remain immutable and are marked superseded when re-linked, with a chain of custody visible in the UI And audit entries are exportable via API and retrievable by conversationId within 200 ms for the most recent 100 entries
Telephony Webhooks & Real-time Events
"As a system administrator, I want reliable telephony webhooks and real-time events so that NumberMatch reacts instantly to inbound messages and calls."
Description

Integrate with telephony providers (e.g., Twilio/Plivo) via secure webhooks to receive inbound SMS and call events, validate signatures, and handle retries safely. Process delivery receipts and call state changes in real time to trigger matching and attachment workflows. Implement resilient queuing, backoff, and dead-letter handling to guarantee processing. Provide per-account configuration for provider credentials and numbers. Expose health metrics and alerts for webhook latency, error rates, and throughput.

Acceptance Criteria
Validate and Authenticate Provider Webhooks
Given a webhook from Twilio or Plivo with a valid signature and a timestamp within a 5-minute tolerance When it is received over HTTPS at the webhook endpoint Then the signature is verified against the account’s stored secret and a 2xx is returned within 1 second Given a webhook with an invalid signature or timestamp outside the tolerance window When it is received Then respond with 401 within 1 second, persist no side effects, and increment the signature_failure metric Given a webhook using an unsupported HTTP method or non-HTTPS When it is received Then respond with 405/400, do not process the payload, and log minimal, non-sensitive request metadata only
Idempotent Processing and Duplicate Suppression
Given multiple deliveries of the same provider event ID within 24 hours When they are received Then only the first delivery advances processing and subsequent deliveries return 2xx without producing duplicate side effects Given a re-delivery triggered by provider retry after a transient failure When processed Then the event is handled exactly once and the duplicate_suppressed metric is incremented for subsequent deliveries Given an event already transitioned to a terminal state When a duplicate arrives Then the state remains unchanged and the attempt is audit logged as a duplicate
Inbound SMS Handling & NumberMatch Attachment
Given a valid inbound SMS event with from, to, body, and provider event ID When it is received Then the payload is normalized and enqueued within 200 ms and processing completes within 3 seconds p95 Given the SMS is processed When NumberMatch runs Then the sender number is matched to the correct tenant/lease/unit (including nicknames and secondary contacts) within the same account and the message is attached to the correct property record/conversation Given the number is unknown or ambiguous across contacts When processed Then the message is placed in a verification queue with an actionable task and is not attached until resolved Given a successful attach When completed Then an audit record stores provider event ID, matched entity IDs, account_id, and timestamps
Delivery Receipts and Call State Processing
Given a delivery receipt for an outbound SMS When it is received Then the message status is updated to delivered/failed within 2 seconds p95; temporary failures schedule exponential backoff retries up to 3 attempts over 10 minutes; permanent failures are marked final and optional notifications are issued Given call state events (initiated, ringing, answered, completed, failed) When they are received Then the call record is updated idempotently and attached to the matched tenant/lease/unit within 3 seconds p95; missed or failed calls create a “missed call” task Given provider-specific payload differences When normalized Then a uniform schema is stored including provider, event_id, from, to, status/state, timestamp, direction, and account_id
Resilient Queueing, Backoff, and Dead-Letter Handling
Given a transient processing error occurs When retrying Then exponential backoff with jitter is applied starting at 10 seconds, doubling up to a maximum delay of 5 minutes, with a maximum of 5 attempts within 30 minutes Given a non-retryable error or retries are exhausted When handling the event Then the event is moved to a dead-letter queue with error class, message, stack (if available), and payload snapshot; a DLQ_created metric is emitted Given the DLQ rate exceeds 1% of total events over a rolling 5-minute window When monitored Then an alert is sent to the on-call channel with links to relevant dashboards and sample logs Given an authorized operator selects a DLQ item to replay When reprocessing is triggered Then the item is re-enqueued once with a new correlation ID and the action is audit logged
Per-Account Provider Configuration & Isolation
Given an account admin adds or updates Twilio/Plivo credentials and phone numbers When saving the configuration Then credentials are validated via a provider test request and stored encrypted; invalid credentials are rejected with actionable error messages Given events are received for numbers owned by multiple accounts When routing Then the event is deterministically associated to the correct account by destination number and isolated from other accounts’ data Given provider credentials are rotated When updated Then the system accepts both old and new signatures for a 1-hour overlap window without downtime, after which old credentials are rejected and audit logged Given sandbox mode is enabled for an account When events are received Then they are flagged sandbox, processed in isolation, excluded from production alerts, and included in sandbox-only metrics
Metrics, Monitoring, and Alerting
Given normal operation When exporting telemetry Then per-account and global metrics are published at 1-minute resolution for webhook latency (p50/p95/p99), ingestion throughput (events/sec), processing time, error rates, queue depth, DLQ count, and signature failures Given p95 webhook latency exceeds 2 seconds for 5 consecutive minutes When evaluated by alerting rules Then a warning alert is generated; if p95 exceeds 5 seconds, a critical alert is generated Given error rate exceeds 1% over 5 minutes or signature failures spike 3x the 7-day baseline When detected Then an alert is sent with links to dashboards and recent relevant logs Given monthly reporting When SLOs are evaluated Then webhook ingestion availability meets or exceeds 99.9% and SLO burn alerts are configured to page on-call before breach
Admin Rules, Overrides & Merge Duplicates
"As a property manager, I want controls to adjust matching rules and override incorrect matches so that I can maintain data accuracy without engineering help."
Description

Provide an admin console to configure matching thresholds, precedence rules, language options, and verification prompts. Enable authorized users to manually override incorrect matches, search and reassign attachments, and merge duplicate contacts/numbers with conflict resolution and rollback. Include a nickname/alias dictionary editor to improve match accuracy. Log all changes with user, timestamp, and reason for full traceability.

Acceptance Criteria
Configure Matching Thresholds and Precedence Rules
Given I am an authorized admin, When I open NumberMatch settings, Then I can set numeric thresholds for strong/medium/weak match (0–100) with inline validation and help text. Given thresholds and precedence order are saved, When a new inbound number is processed, Then the engine uses the saved values within 60 seconds and routes accordingly. Given I change rules, When I click Save, Then the system version-stamps the rules, logs user, timestamp, and reason, and confirms success within 2 seconds. Given a version history exists, When I select a prior version and click Restore, Then the previous rules are reinstated and logged without data loss. Given invalid input (e.g., threshold outside 0–100 or duplicate precedence), When I attempt to save, Then the save is blocked with specific error messages.
Configure Language Options and Verification Prompts
Given I am an authorized admin, When I select default tenant language and per-portfolio overrides, Then verification prompts are delivered in the configured language for new verifications. Given templates include variables, When I preview a prompt, Then variables render with sample data and formatting matches the target locale. Given a tenant’s preferred language is unknown, When a verification is sent, Then the system falls back to portfolio default; if unavailable, to English. Given a right-to-left language is selected, When I preview/send prompts, Then layout and text direction render correctly. Given changes are saved, When the next verification is triggered, Then the new copy is used and the change is logged with user/timestamp/reason.
Manual Override of Incorrect Match with Audit Log
Given a conversation auto-attached to Record A, When an authorized user selects Override and chooses Record B, Then the conversation and future messages from that number attach to Record B immediately. Given an override occurs, When I view the audit log, Then it shows user, timestamp, old record, new record, required override reason, and a link to the source conversation. Given an override within 24 hours, When I click Undo, Then attachments revert to the prior record and the log records the rollback. Given a user lacks override permission, When they attempt override, Then the action is blocked and the attempt is logged as denied.
Search and Reassign Attachments to Correct Property Record
Given I am in Admin Console, When I search by phone number, tenant name, unit, or lease ID, Then results return within 3 seconds with filter and sort options. Given I select 1–500 messages/voicemails/photos, When I click Reassign and choose a target record, Then all selected items move to the target while preserving timestamps and sender metadata. Given reassignment completes, When I open both source and target records, Then counts and timelines reflect the move and no orphaned links exist. Given a reassignment job runs, When any item fails, Then the report lists failed items with reasons and no partial move occurs without explicit per-batch confirmation and retry. Given any reassignment, When I view the audit log, Then it records user, timestamp, item IDs, source, destination, and reason.
Merge Duplicate Contacts/Numbers with Conflict Resolution and Rollback
Given two or more contacts are selected, When I initiate Merge, Then I can choose a primary record and resolve field conflicts (name, email, phone labels) before confirming. Given contacts share phone numbers, When merging, Then phone numbers are de-duplicated, retaining labels and keeping the most recent verification status. Given merge confirmation, When the process completes, Then all related entities (tickets, messages, leases) point to the primary record and no dangling references remain. Given a merge completes, When I review the audit log, Then it lists user, timestamp, merged IDs, primary ID, field-level resolutions, and a rollback token valid for 7 days. Given rollback is invoked within 7 days, When executed, Then the system restores pre-merge records and relationships or produces a diff report for any irreversible items.
Nickname/Alias Dictionary Editor to Improve Match Accuracy
Given I am an admin, When I add an alias "Liz" -> "Elizabeth" scoped to Portfolio P, Then matches for "Liz" in P treat it as "Elizabeth" within 60 seconds. Given an alias conflicts with an existing canonical name, When I save, Then I must confirm precedence and the system warns with the count of impacted records. Given I bulk import 100 alias pairs via CSV, When the import validates, Then all valid rows are added and invalid rows are reported with line numbers and reasons. Given I delete or edit an alias entry, When saved, Then future matches reflect the change and the audit log captures user, timestamp, old value, new value, and reason. Given locale-specific entries exist, When a tenant’s locale differs, Then the system applies the most specific rule (unit -> portfolio -> global) using the documented fallback order.
Privacy, Consent & Audit Compliance
"As a landlord, I want consent tracking and audit logs for number matching and messaging so that we stay compliant and can resolve disputes."
Description

Track and enforce consent for messaging and calls at the contact level (opt-in/opt-out), blocking outbound messages to opted-out numbers and recording consent changes. Encrypt phone numbers at rest and in transit, restrict access via role-based permissions, and redact sensitive data in logs. Maintain an immutable audit trail of matches, verifications, overrides, and attachments with timestamps and acting user/service. Apply retention policies for guest linkages and historical records to meet regulatory and customer requirements.

Acceptance Criteria
Block Outbound to Opted-Out Contacts (SMS and Voice)
Given a contact is marked opted-out for SMS and/or voice at the contact level When a user or automation attempts to send an SMS or initiate a call to that contact Then the system blocks the outbound action for the opted-out channel(s) And the UI displays a clear, non-dismissible warning indicating the opt-out block And API responses return HTTP 403 with code CONSENT_REQUIRED and the blocked channel(s) And no message/call is queued or sent to any provider And an audit entry is created capturing contact, channel, actor (user/service), timestamp, and reason "blocked—opt-out"
Record and Audit Consent Changes at Contact Level
Given a user with the appropriate permission edits a contact’s consent status (SMS and/or voice) When the consent change is saved via UI or API Then an immutable audit record is appended capturing previous value, new value, channel(s), actor (user/service), method (UI/API), timestamp (UTC), and reason/source And the audit record cannot be edited or deleted by any role And outbound suppression/allow rules for the contact reflect the change within 5 seconds And an export of the contact’s consent history returns a complete, chronological log of all changes
Role-Based Access Masks and Restricts Phone Numbers
Given a user without the "View PII" permission accesses records containing phone numbers When viewing UI screens, notifications, exports, and API responses Then phone numbers are masked to last 4 digits (e.g., ******1234) And copy/download/export controls for masked numbers are disabled And direct attempts to access unmasked numbers return HTTP 403 and are logged with actor, endpoint, and timestamp And users with the "View PII" permission can view unmasked numbers, and access events are captured in audit logs
Redact Sensitive Data in Application and System Logs
Given application, request, and infrastructure logs are generated during NumberMatch operations When logs from a representative test run are scanned Then no full phone numbers or other sensitive identifiers appear; only masked or hashed tokens are present And redaction rules apply uniformly to app logs, request traces, error reports, and third-party logging sinks And an automated CI job that scans logs for PII passes with zero findings before release And any log write that would include unredacted PII is rejected and emits a sanitized fallback entry
Immutable Audit Trail of Matches, Verifications, Overrides, and Attachments
Given NumberMatch performs a match, quick verification, manual override, or conversation-to-record attachment When any such event occurs Then an append-only audit record is created with event type, masked input number, matched entity (contact/lease/unit), actor (user/service), timestamp (UTC), and a correlation ID And audit entries cannot be edited or deleted by any role; only new entries can be appended And the audit trail is filterable by contact, unit, lease, actor, event type, and time range, and is exportable as CSV And all records from the same interaction share a correlation ID that allows end-to-end reconstruction of the processing path
Retention Policies for Guest Linkages and Historical Records
Given a guest or secondary contact number is linked to a unit/lease When the configured retention period for guest linkages elapses Then the system automatically detaches the guest linkage and anonymizes associated historical records per policy And an audit entry is recorded for the retention action with policy identifier, actor "system", and timestamp And administrators can configure retention periods per customer, and a default policy is applied when none is configured And a scheduled job runs at least daily to enforce retention and produces a downloadable report of actions taken
Encryption of Phone Numbers at Rest and In Transit
Given phone numbers are stored and transmitted by NumberMatch When data is at rest in databases, backups, and search indexes Then phone numbers are encrypted at rest using industry-standard algorithms with keys managed by a centralized KMS and rotated per policy And all service-to-service and client connections that carry phone numbers enforce TLS 1.2+ with strong ciphers and HSTS enabled And automated security tests verify encryption-in-transit and confirm no plaintext phone numbers exist in storage snapshots or exports And access to encryption keys is limited to designated services and is fully audited with timestamp, principal, and purpose

Duplicate Shield

Identifies duplicate reports across SMS, WhatsApp, portal, or email and smart‑merges them into a single ticket with a unified message history. De‑dupes repeated photos, preserves context, and prevents double dispatch or conflicting updates. Keeps the board clean, reduces noise, and protects vendor time.

Requirements

Unified Duplicate Detection Engine
"As a property manager, I want duplicate reports automatically detected across all channels so that my ticket board stays clean and I don’t waste time juggling the same issue multiple times."
Description

Build a cross-channel detection service that identifies duplicate maintenance reports across SMS, WhatsApp, tenant portal, and email in near real time and proposes a single canonical ticket. The engine normalizes inbound messages (tokenization, stop-word removal, language detection), enriches them with metadata (unit, property, tenant/contact identity, timestamps, channel, geo or building context), and scores similarity using a weighted blend of text similarity (fuzzy match + embeddings), structured fields (unit, asset, category), and media fingerprints. The service supports configurable time windows and thresholds per portfolio/property, handles multi-tenant units (roommates) by mapping contact identities to the same unit, and provides low-latency matching (<2s P95) under concurrent submissions. It exposes an API and event hooks to the intake pipeline so candidates can be flagged and routed before ticket creation, reducing board clutter and downstream double-work.

Acceptance Criteria
Cross-Channel Duplicate Proposal Before Ticket Creation
Given two or more inbound reports for the same property/unit arrive via different channels within the configured time window When the engine’s weighted similarity score is >= the configured threshold Then the API returns decision="duplicate", includes the canonical ticket reference (if exists) and a pre-creation block flag And the response includes the score, contributing feature scores (text/structured/media), applied thresholds, and time window identifier And no new ticket identifier is suggested for creation And when the score is < threshold, the API returns decision="create" with no block flag
Low-Latency Matching Under Concurrent Submissions
Given a steady load of 100 concurrent submissions per portfolio (1,000 total concurrent system-wide) for 15 minutes When all are processed by the detection engine Then P95 end-to-end decision latency is < 2s and P99 < 3.5s And request timeout rate is <= 0.5% and error rate (5xx) <= 0.1% And no backlog exceeds the configured queue depth threshold
Per-Property Configurable Windows and Thresholds
Given Property A is configured with time_window=6h and threshold=0.78, and Property B with time_window=2h and threshold=0.88 When identical test submissions are sent to both properties Then Property A decisions use 6h/0.78 and Property B decisions use 2h/0.88 as evidenced in the decision payload And a configuration change is applied Then new decisions reflect the change within 5 minutes without service restart And where no property-level config exists, portfolio defaults apply; where none exist, system defaults apply
Roommate Identity Resolution to Shared Unit
Given two verified contacts mapped to the same unit but using different channels and phone/email identifiers When they submit reports describing the same asset/category within the configured time window Then the engine evaluates them as a duplication candidate regardless of differing contact IDs and returns decision="duplicate" when score >= threshold And when they submit clearly different categories/assets or semantically different descriptions and score < threshold Then the engine returns decision="create" for distinct tickets
Media Fingerprint-Based Similarity
Given two submissions contain visually identical or near-identical photos of the same issue with differing resolution/compression When perceptual media fingerprints are computed Then images with Hamming distance <= 8 are treated as a media match and contribute to the similarity score per configured weight And identical media are retained once on the canonical ticket with references from all merged submissions And images with Hamming distance > 8 do not contribute as duplicates
Normalization and Language-Aware Similarity
Given messages describing the same issue are submitted in different languages (e.g., English and Spanish) When the engine normalizes text (tokenization, lowercasing, stop-word removal) and detects language Then it selects language-appropriate embeddings and computes semantic similarity And combines text, structured fields (unit/asset/category), and media features using default weights (e.g., text=0.5, structured=0.3, media=0.2) And after updating weights to text=0.3, structured=0.5, media=0.2 in configuration, a controlled test case flips from create->duplicate as expected
API and Event Hooks for Pre-Creation Routing
Given the intake pipeline calls the Duplicate Detection API with a unique request_id When a duplicate candidate is detected Then the API responds 200 with decision payload within 2s including request_id, canonical_ticket_id (or null), score, and feature contributions And emits a "duplicate.candidate" event with the same correlation identifiers before any ticket is created And repeated calls with the same request_id return the same decision and do not emit duplicate events And when no duplicate is detected, the API returns decision="create" and no duplicate.candidate event is emitted
Smart Merge & Unified Timeline
"As a dispatcher, I want duplicate tickets to merge into one canonical ticket with a unified timeline so that I can triage and schedule from a single source of truth."
Description

Implement a merge workflow that consolidates detected duplicates into a single canonical ticket while preserving full context. The system unifies message histories into a chronological timeline with source attribution (channel, sender), keeps original ticket IDs as linked references, and records an immutable merge audit (who/what/when/why). Business rules define canonical field selection (e.g., earliest created, most complete data) and conflict resolution (category, priority, SLA). The merge is reversible (unmerge) with full rollback. Reopened or subsequent duplicate reports auto-attach to the canonical ticket without losing provenance. The outcome is a single, comprehensive thread for triage, approvals, and scheduling that reduces cognitive load and prevents information loss.

Acceptance Criteria
Canonical Merge with Unified Timeline and Source Attribution
- Given multiple tickets are flagged as duplicates across SMS, WhatsApp, portal, or email - When the user confirms Merge on the selected set - Then exactly one canonical ticket is created and the remaining tickets are marked as merged into the canonical - And the canonical timeline lists all messages, notes, photos, status changes, and assignments from the merged tickets in strict chronological order by event timestamp; for identical timestamps, order by channel priority [SMS, WhatsApp, Portal, Email] then by event ID - And each timeline entry shows channel, sender (name or address), original ticket ID, and ISO 8601 timestamp with timezone - And no events are omitted or duplicated in the canonical timeline
Preserve and Surface Linked Original Ticket References
- Given a successful merge has completed - Then the canonical ticket displays a linked list of all original ticket IDs and their source channels - And each original ticket is set to state "Merged-Into {canonical_id}", becomes read-only, and redirects to the canonical when opened - And searching or API lookup by any original ticket ID returns the canonical ticket reference and merged status - And reporting and dashboards count the merged set as one active ticket
Immutable Merge Audit Trail
- Given a merge action occurs (manual or automated) - When the merge is committed - Then an audit record is written including: initiator (user or system), action (merge), canonical ticket ID, list of merged ticket IDs, timestamp, detection reason/rule, and diff of canonical field changes (before → after) - And the audit record is immutable (non-editable) and append-only - And the audit record is visible in the canonical ticket’s Audit tab and available via export/API - And any subsequent unmerge appends a new audit record that references the original merge via a correlation ID
Canonical Field Selection and Conflict Resolution Rules
- Given conflicting values exist across tickets to be merged - When selecting canonical values - Then Priority is chosen as the highest severity (Emergency > High > Normal > Low) - And SLA target is chosen as the shortest remaining time-to-breach - And Category/Subcategory is chosen as the earliest non-null value; if multiple distinct values exist, choose the plurality; if tie, choose the earliest non-null - And Contact/Unit details are selected from the record with the greatest number of required fields populated; if tie, choose the earliest-created ticket - And each resolved field displays the source ticket ID that supplied the canonical value - And automated tests validate at least 10 conflict permutations per field with expected outcomes
Media Deduplication with Provenance Preservation
- Given photos/videos exist across the tickets being merged - When the merge executes - Then media assets are deduplicated using content/perceptual hashing (exact hash match treated as identical; perceptual hash Hamming distance threshold ≤ 5) - And identical media appear once in the canonical gallery/timeline, annotated with all source ticket IDs and channels that supplied them - And all non-identical media are preserved without loss - And measured false-positive dedupe rate is ≤ 1% on a 1,000-item test set
Reversible Unmerge with Full Rollback
- Given a merged canonical ticket exists - When a user with Merge Admin permission initiates Unmerge - Then each original ticket is restored with its pre-merge state (status, assignments, SLA timers, attachments, comments) using the merge snapshot - And original ticket IDs regain active/searchable status and are removed from the canonical’s merged list - And no data loss occurs; post-merge-only events remain accessible on a retained archived record linked from all originals - And an immutable unmerge audit record is appended with timestamp, initiator, and restored ticket IDs
Auto-Attach of Subsequent Duplicates and Reopened Reports
- Given a canonical ticket exists for an issue - When a new report matches duplicate fingerprint rules (e.g., same unit/asset, category similarity ≥ 0.8, within 14 days, or same conversation thread) - Then the new report auto-attaches to the canonical within 10 seconds of ingestion - And the canonical timeline shows the new entry with channel, sender, and an "auto-attached duplicate" tag - And no new vendor dispatch or approval workflow is created; existing canonical workflows are reused - And if an original merged ticket is reopened by the tenant, the message routes to the canonical timeline while the original remains in merged state
Media De-duplication & Similarity
"As a maintenance coordinator, I want repeated photos and videos automatically identified and grouped so that I can quickly assess an issue without sifting through duplicate media."
Description

Add image and video near-duplicate detection to prevent redundant media storage and clutter within tickets. Use perceptual hashing (pHash/aHash) and frame sampling for videos to detect exact and near-duplicates across channels and time windows, tagging duplicates and retaining a single reference asset while preserving links back to the original submissions. Maintain EXIF-derived context (timestamps, orientation) and surface the highest-resolution, clearest media as primary. Provide visual grouping of similar media within the ticket and optional suppression of duplicate thumbnails in tenant-facing views. This reduces storage costs, accelerates review, and keeps the ticket media section concise and informative.

Acceptance Criteria
Exact Duplicate Images Across Channels Are De-duplicated and Tagged
Given a ticket with stored images and computed perceptual hashes (pHash, aHash) And Duplicate Shield is enabled When a new image arrives via SMS, WhatsApp, portal, or email And the image is normalized for orientation before hashing And the new image’s pHash Hamming distance to an existing image is <= 5 OR the byte content matches exactly Then the system marks the submission as "Duplicate Image" And no new binary is stored; the submission references the existing asset ID And the media group’s duplicate count increases by 1 And the submission’s source, channel, submitter, and timestamp are linked to the primary asset
Near-Duplicate Images Use Highest-Quality Primary and Single Storage
Given at least two similar images exist or arrive for the same ticket And perceptual hashes are computed on orientation-normalized images When a new image’s pHash Hamming distance to an existing image is between 6 and 12 inclusive Then the images are grouped under one media group and marked "Similar" And only one binary asset is retained as the primary And the primary is selected by: highest resolution (width x height), then lowest compression (largest file size for the same format), then highest clarity score (variance of Laplacian) And all non-primary submissions reference the primary asset and are tagged "Duplicate of [asset_id]" And the group label displays "Similar (n)" and shows the distance value used in details
Video Near-Duplicate Detection via Frame Sampling and Primary Selection
Given video analysis is enabled for the ticket When a new video is ingested And frames are sampled at 1 FPS for up to the first 30 seconds (or full duration if shorter) and normalized for rotation And pHash is computed per sampled frame And at least 80% of aligned sampled frames have Hamming distance <= 12 to a stored video in the ticket Then the new video is marked "Duplicate Video" and references the existing primary video; no new binary is stored And the primary video is selected by highest resolution, then highest average bitrate And the duplicate submission’s source and timestamp are linked to the primary video asset
EXIF Context Preservation and Orientation Normalization for Hashing
Given an incoming image contains EXIF timestamp and orientation When the image is ingested Then EXIF timestamp and original orientation are stored on the submission record And the image is normalized to the correct orientation before hashing and display And a rotated version (90/180/270 degrees) of the same photo is detected as duplicate within the configured thresholds And each duplicate submission retains its own EXIF metadata accessible via links from the primary asset
Visual Grouping in Ticket UI and Tenant-Facing Duplicate Suppression
Given a ticket contains a media group with one primary and one or more duplicates When viewed in the manager UI Then the group appears as a single card showing the primary thumbnail, duplicate count, and source badges And expanding the card reveals all submissions with source and timestamp When viewed in the tenant UI with Suppress duplicates = ON (default) Then only the primary thumbnail is shown And toggling suppression OFF displays all submissions without page reload
Storage Optimization and Audit Trail for Media De-duplication
Given a ticket has de-duplicated media When duplicates are linked to a primary asset Then only one binary file is stored for the group And no additional binary storage is consumed per duplicate; per-duplicate metadata overhead is <= 4 KB And an audit event is recorded with action="media_dedup", asset_id, duplicate_submission_id, channel, pHash_distance, thresholds_used, timestamp, actor="system" And the audit event appears in the ticket activity log within 2 seconds of the de-duplication
Dispatch Guardrails & Conflict Prevention
"As a vendor manager, I want the system to prevent double dispatch for the same issue so that vendors aren’t sent twice and we avoid unnecessary costs."
Description

Introduce safeguards that block double dispatch and conflicting updates once duplicates are merged. When a vendor is scheduled or a purchase order is created on the canonical ticket, the system locks dispatch-related actions on linked duplicates, shows a clear banner indicating the active schedule, and prevents duplicate work orders. Concurrency controls ensure atomic state transitions under race conditions (e.g., two agents act simultaneously). The guardrails also harmonize status updates so tenants and vendors receive a single, consistent set of communications tied to the canonical ticket, eliminating confusion and protecting vendor time.

Acceptance Criteria
Lock dispatch actions on duplicates when vendor scheduled on canonical
Given a set of merged duplicate tickets with a designated canonical ticket And the canonical ticket has a confirmed vendor assignment and scheduled time window When an agent attempts to schedule a vendor from any linked duplicate via UI or API Then the action is blocked and returns HTTP 409 Conflict with message "Dispatch is managed on canonical ticket [ID]" And the duplicate ticket shows disabled dispatch controls And an informational banner displays the vendor name and scheduled window from the canonical ticket And an audit log entry is recorded on both tickets including actor, timestamp, and blocked action
Prevent duplicate purchase order creation from linked duplicates
Given the canonical ticket has an active purchase order (PO) associated When a user attempts to create a PO from any duplicate ticket Then the system prevents creation of a new PO and surfaces the existing PO reference linked to the canonical ticket And returns HTTP 409 Conflict with error code DUPLICATE_PO_BLOCKED And no vendor notifications are sent as part of the blocked attempt And an audit log entry is created noting the prevention and referencing the existing PO ID
Display canonical schedule banner on duplicates
Given a canonical ticket with an active or pending vendor schedule When an agent or tenant views any duplicate ticket conversation/thread Then a non-dismissible banner is shown on the duplicate containing canonical ticket ID, vendor name, scheduled date/time with timezone, and a "View canonical ticket" link And the banner appears in Agent Console (web), Tenant Portal, and Mobile views with consistent copy And the banner updates within 5 seconds of any schedule change on the canonical ticket And the banner is removed within 5 seconds if the canonical dispatch is canceled
Concurrency control for simultaneous dispatch attempts
Given two agents initiate vendor scheduling on two different tickets merged into the same canonical ticket within a 1-second window When the system processes these requests Then only one request succeeds in creating the dispatch on the canonical ticket And the other request is atomically rejected with HTTP 409 Conflict and error code DISPATCH_ALREADY_EXISTS And both outcomes are captured in audit logs with correlation IDs And no duplicate work orders, calendar events, or notifications are created
Harmonized outbound communications to tenants and vendors
Given duplicates are linked to a canonical ticket And the canonical ticket status changes (e.g., Scheduled, En Route, Completed, Rescheduled, Canceled) When notifications are triggered Then only one set of outbound communications is sent, using the canonical ticket’s thread and identifiers And duplicate tickets do not emit any outbound notifications And replies from tenants or vendors to any channel/thread on a duplicate are ingested and appended to the canonical ticket timeline within 5 seconds And notification templates reference only the canonical ticket ID
Re-enabling dispatch actions after canonical cancellation
Given duplicates are locked because the canonical ticket has an active dispatch When the dispatch on the canonical ticket is canceled Then dispatch-related controls on duplicates are re-enabled within 5 seconds And the banner indicating active schedule is removed from duplicates And subsequent scheduling from any duplicate creates or updates the canonical ticket’s dispatch rather than creating a separate dispatch record
Vendor integration idempotency prevents duplicate work orders
Given vendor work orders are sent via integration (e.g., email, API) from the canonical ticket When a dispatch attempt is initiated from any duplicate ticket Then an idempotency key derived from the canonical ticket prevents creation of a second vendor work order And the system returns the existing vendor work order reference to the user And the vendor receives at most one work order per incident And no additional outbound request is sent to the vendor integration for the same incident
Admin Controls, Threshold Tuning & Overrides
"As an operations lead, I want to adjust de-duplication rules and override merges when needed so that the system fits our workflow and risk tolerance."
Description

Provide a configuration console where admins can tune duplicate detection thresholds, weighting of signals (text vs. media vs. unit match), and time windows per portfolio or property. Support rule-based exceptions (e.g., never auto-merge gas leak reports), ignore lists (test numbers/emails), and channel-specific behaviors. Offer manual actions for force-merge and unmerge with justification capture to improve future model decisions. Include preview/simulation mode to show what would merge under current settings. This ensures the feature adapts to different operational styles while keeping control in the hands of managers.

Acceptance Criteria
Per-Portfolio and Per-Property Threshold & Weight Tuning
Given I am an admin with edit permissions When I open Duplicate Shield settings for a selected portfolio and property Then I can set text similarity threshold between 0 and 100 in 1-point increments (default 80) And I can set media match weight between 0.00 and 1.00 in 0.05 increments (default 0.50) And I can set unit match weight between 0.00 and 1.00 in 0.05 increments (default 0.30) And saving persists values and reload shows the same values And a success confirmation appears within 2 seconds of save And each save creates a versioned audit entry with timestamp, admin ID, scope, and diffs And I can rollback to any prior version and see the values applied immediately
Time Window Configuration for De-duplication
Given a property inherits portfolio defaults and allows override When I set the deduplication time window to 30 minutes at the property level Then two reports 10 minutes apart with matching signals are eligible to auto-merge And two similar reports 45 minutes apart are not auto-merged And setting the window to Off disables time-based restriction (other signals still apply) And reverting to "Inherit" restores portfolio defaults
Rule-Based Exceptions and Channel-Specific Overrides
Given a rule "Never auto-merge category: Gas Leak" exists at the portfolio scope When two gas leak reports meet all merge thresholds Then the system does not auto-merge and marks them "Requires review" with an exception badge And exceptions take precedence over channel and threshold settings And channel-specific thresholds (SMS, WhatsApp, Portal, Email) are applied when evaluating non-exempt reports And the active rule is recorded in the decision log for each evaluated pair
Ignore List Management
Given I add a phone number and email to the ignore list with reason and expiration date When new reports arrive from those identifiers Then they are excluded from duplicate detection and merge suggestions And an audit entry records who added, reason, and expiry And after expiry, the identifiers are automatically removed and normal detection resumes And I can bulk import and export ignore entries via CSV with validation and error reporting
Manual Force-Merge and Unmerge with Justification
Given I am viewing two tickets and have merge permissions When I select Force Merge Then I am required to enter a justification between 10 and 500 characters And the resulting ticket shows a unified message history ordered by timestamp with duplicate photos de-duplicated by hash And vendor dispatches are consolidated without creating duplicate work orders or notifications And an audit record stores user, time, ticket IDs, and justification and is available in exports When I Unmerge the ticket Then the original tickets are restored with their full histories and a system note links both tickets with the unmerge justification
Preview/Simulation Mode for Current Settings
Given I open Preview Mode for Property A When I simulate on the last 14 days of data Then no production tickets are changed and only a preview list is shown And the results include candidate pairs, scores by signal (text/media/unit), channel, and predicted action And I can filter by channel and minimum score and export the list to CSV And simulations up to 1000 tickets complete within 60 seconds or display a timeout message with partial results
Permissions and Access Control
Given a non-admin user opens Duplicate Shield settings Then they see a read-only view with controls disabled Given a property admin opens settings Then they can edit only properties they are assigned to and not portfolio-level defaults Given a portfolio admin opens settings Then they can edit portfolio defaults and any property overrides And all permission denials are logged with user ID and reason
Audit Trail, Review Queue & Impact Analytics
"As a product owner, I want visibility into what was merged and why, plus metrics on outcomes, so that I can trust the system and iterate on its effectiveness."
Description

Create comprehensive observability around de-duplication. Every detection and merge generates an auditable record (signals, scores, rule hits). Provide a review queue for low-confidence candidates requiring human approval, with one-click accept/reject feeding back as labeled data. Deliver analytics on impact: number of duplicates prevented, time-to-merge, false positives/negatives, vendor dispatches avoided, storage saved, and response-time reduction. Expose exports and dashboards by property and time period to demonstrate ROI and support continuous tuning.

Acceptance Criteria
Audit Log for Detection and Merge Events
Given a duplicate candidate is detected or a merge is executed across SMS, WhatsApp, portal, or email When the event is processed Then the system writes an immutable audit record within 500ms p95 containing: event_type, event_id, timestamp (UTC ISO‑8601), property_id, source_channel(s), candidate_ticket_ids, merged_ticket_id (if applicable), detection_signals[], model_version, model_score (0–1), rules_triggered[], photo_hashes_deduped[], actor (system|user_id), request_id, reason, review_required (true|false) And the record is retrievable via UI and Admin API within 2s p95 And audit records are retained for at least 24 months and are tamper‑evident (checksum present and verified) And reading an audit record never exposes redacted PII fields (phone, email) beyond last 4 characters
Low-Confidence Review Queue Workflow
Given a duplicate candidate is scored When model_score < configurable_threshold (default 0.60) and >= floor_threshold (default 0.30) or conflicting rules are triggered Then the candidate appears in the Review Queue within 2s p95 with: side‑by‑side summaries, message excerpts, photo thumbnails (dedup hints), signals, score, rules hit, and suggested action And one‑click Accept merges immediately; one‑click Reject dismisses and suppresses re‑surfacing for 7 days And queue supports sort by recency and score, search by ticket id/address, and pagination And role‑based access restricts visibility to property‑scoped users
One‑Click Human Decisions Feed Labeled Data
Given a reviewer clicks Accept on a review item When the action is confirmed Then the tickets are merged within 1s p95; an audit record records decision_source=human_accept And a positive training label is appended to the labeling store within 5s p95 with feature snapshot and outcome=duplicate And the UI displays a success toast and removes the item from the queue Given a reviewer clicks Reject When the action is confirmed Then a negative training label is appended with outcome=not_duplicate; an audit record records decision_source=human_reject And an Undo option is available for 10 minutes; undo reverses merge/dismissal and audit writes a compensating event
Impact Analytics Dashboard by Property and Period
Given a user selects a property set and date range When the dashboard loads Then the following KPIs are displayed with definitions tooltips and trendlines: duplicates_prevented, time_to_merge (median, p90), false_positive_rate, false_negative_rate, vendor_dispatches_avoided, storage_saved_mb, response_time_reduction And filters for channel, property, and severity apply within 2s p95 and persist via sharable URL And all KPI counts reconcile to underlying audit records within ±0.5% And time_to_merge is measured from first duplicate detection to final merge event
Exportable Analytics with Stable Schemas
Given a user with Export permission selects a date range and properties When they request an export Then CSV and JSON exports are generated with schema version, column dictionary, and timezone UTC within 2 minutes p90 for up to 1M rows And exports include audit fields, decision provenance, and KPI rollups per day and per property And completed exports are available via secure download and pre‑signed URL that expires in 24 hours And all export accesses are logged with user_id, timestamp, and IP
False Positive/Negative Measurement and Definitions
Given analytics compute quality metrics When classifying outcomes Then false_positive is any auto‑merge later undone by a human within 14 days or any review Accept later reverted; false_negative is any duplicate pair not merged within 24h but later merged And rates are computed as FP/(FP+TP) and FN/(FN+TP) per period and per property And quality metrics exclude test/sandbox properties And definitions are surfaced via tooltip and export metadata
Vendor Dispatches Avoided and Response‑Time Reduction Calculations
Given merged duplicates remove extra work orders When calculating impact Then vendor_dispatches_avoided equals count of candidate tickets that would have triggered dispatch based on rule dispatch_eligibility minus those merged before dispatch time And response_time_reduction equals baseline_response_time minus observed_response_time for affected tickets, with baselines derived from pre‑feature 30‑day medians per property And storage_saved_mb sums de‑duped media bytes not stored due to merge within period And each metric exposes a View Details drilldown to the contributing tickets
Cross-Channel Tenant Communication Unification
"As a tenant, I want to receive clear, non-duplicative updates about my maintenance request regardless of how I reported it so that I stay informed without being overwhelmed."
Description

Unify outbound and inbound communications for merged tickets so tenants receive consistent updates without spam across SMS, WhatsApp, portal, and email. Route all replies back to the canonical ticket while preserving per-tenant privacy (no reply-all exposure) and honoring channel preferences. De-duplicate notifications when multiple roommates report the same issue, send a single confirmation referencing the canonical ticket, and provide opt-in status pings. Include language-aware templates and fallbacks per channel to maintain clarity and reduce noise.

Acceptance Criteria
Unified Confirmation on Canonical Ticket Creation
- Given multiple duplicate reports about the same issue are matched into a canonical ticket, When the canonical ticket is created, Then each reporting tenant receives exactly one confirmation on their preferred channel within 30 seconds referencing the canonical ticket ID. - Given a tenant has multiple reachable channels, When confirmation is sent, Then duplicate confirmations across other channels are suppressed for that tenant for 2 hours. - Given a channel does not support deep links, When confirmation is sent, Then include the canonical ticket ID and short instructions instead of a link. - Given confirmations are sent, Then all confirmation events are logged on the canonical ticket timeline with channel, timestamp, and delivery status.
Reply Routing to Canonical Ticket Across Channels
- Given a tenant replies via SMS, WhatsApp, portal, or email to any outbound message for the issue, When the message is received, Then it is appended to the canonical ticket’s message history within 10 seconds and no new ticket is created. - Given a tenant replies from a different channel than the original confirmation, When the message is received, Then the same tenant identity is associated and the correct ticket remains unchanged. - Given the canonical ticket is closed, When a tenant replies, Then the message is appended and an auto-response is sent informing of closed status with a link/ID to reopen or create a new ticket. - Given a message cannot be delivered to the canonical ticket due to system error, When the error occurs, Then the message is queued and retried up to 3 times and surfaced as a warning on the ticket.
Per-Tenant Privacy and No Reply-All Exposure
- Given multiple tenants are on the same lease, When FixFlow sends an update, Then each tenant receives a separate message thread and cannot see other tenants’ contact information. - Given a tenant replies to an update, When the reply is processed, Then only managers/vendors see the reply; other tenants do not receive it unless the manager broadcasts a new update. - Given an outbound email or WhatsApp message is sent, When recipients are addressed, Then no group thread is created and no “reply all” capability is presented to tenants.
Channel Preference Honor and Fallback
- Given a tenant has a preferred channel configured, When an outbound message is sent, Then the system attempts delivery on the preferred channel first and records delivery status. - Given delivery on the preferred channel fails (e.g., hard bounce, WhatsApp unreachable) within 2 minutes, When fallback is enabled, Then the system auto-falls back to the next available channel (SMS, email, portal) and sends exactly one message. - Given no channels are available, When an outbound is attempted, Then the system records a failure state and alerts the manager with suggested actions. - Given multiple attempts occur due to transient errors, When delivery eventually succeeds, Then previous pending attempts are canceled to prevent duplicate notifications.
De-duplicated Notifications for Roommates
- Given two or more tenants report the same issue and tickets are merged, When sending initial confirmations, Then each reporting tenant receives exactly one confirmation on their preferred channel and non-reporting tenants receive none unless opted-in by the manager. - Given a status change event (e.g., “Scheduled”, “Technician en route”), When notifications are dispatched, Then each tenant receives at most one notification per event within a 15-minute window regardless of how many channels they are reachable on. - Given duplicate inbound reports continue to arrive after merge, When the system detects a match, Then suppress additional confirmations and instead send a short “merged into ticket {ID}” notice to the reporter’s preferred channel only.
Opt-in Status Pings with Frequency Controls
- Given tenants can opt in to status pings, When a tenant sends an explicit opt-in command (e.g., “STATUS ON”, portal toggle), Then pings are enabled for that tenant and recorded on the canonical ticket. - Given pings are enabled, When no status change occurs, Then the system sends at most one ping per 24 hours per tenant summarizing current status, with channel-specific STOP/HELP text. - Given a tenant opts out (e.g., “STOP”), When the command is received, Then pings cease within 60 seconds and the opt-out is recorded; transactional updates (e.g., appointment booked) continue. - Given rate limits are exceeded due to rapid status changes, When multiple changes occur within an hour, Then the system batches updates into a single digest per tenant.
Language-Aware Templates and Fallbacks
- Given a tenant has a language preference, When an outbound message is sent, Then the template in that language is used; if unavailable, the English template is used and the fallback is logged. - Given channel-specific constraints (e.g., SMS length), When a template is rendered, Then the message fits the channel limit without truncating critical fields (ticket ID, time, action link); overflow is split into threaded parts with sequence markers. - Given dynamic placeholders are rendered, When a message is sent, Then all placeholders resolve with non-empty values; otherwise the message is blocked and a default fallback is inserted with an alert to the manager. - Given multi-language conversations, When tenants on the same lease have different language preferences, Then each tenant receives the update in their own language without cross-language mixing in any thread.

Text Commands

Enables fast, no‑login actions via SMS/WhatsApp keywords: tenants can reply RESCHEDULE, PHOTO, CANCEL; vendors can CONFIRM or RUNNING LATE; approvers can APPROVE 150 or REQUEST ESTIMATE. Enforces permissions, logs every action, and triggers instant confirmations—shrinking days of coordination into minutes.

Requirements

Keyword Command Parser & Router
"As a tenant, I want to text RESCHEDULE to change my appointment so that I don’t have to log into a portal or wait on hold."
Description

Implement a robust SMS/WhatsApp command interpreter that recognizes and normalizes supported keywords (e.g., RESCHEDULE, PHOTO, CANCEL for tenants; CONFIRM, RUNNING LATE for vendors; APPROVE <amount>, REQUEST ESTIMATE for approvers). The parser must be case- and whitespace-insensitive, tolerate minor typos via fuzzy matching for known commands, and support parameter extraction (amounts, dates, work order references). Route recognized commands to the appropriate domain services (scheduling, approvals, media intake) based on the sender’s role and active context. Maintain conversation state to associate incoming texts with the correct tenant, vendor, or approver and their most recent or specified work order. Provide deterministic fallback for ambiguous inputs (e.g., prompt to select a work order) and a HELP response describing available commands. Integrate with messaging providers (e.g., Twilio SMS, WhatsApp Business API) using webhooks and respect provider-specific payloads and rate limits.

Acceptance Criteria
Tenant reschedules via SMS with natural-language date
Given a verified tenant with an active work order conversation And the tenant texts " reshedule tomorrow 3pm " to the existing thread When the parser processes the message Then it normalizes the command to RESCHEDULE in a case/whitespace-insensitive manner And tolerates the single-character typo (edit distance ≤ 1) And resolves "tomorrow 3pm" to a concrete timestamp in the property’s timezone And associates the message to the most recent active work order in the conversation state And routes the request to the scheduling service And replies with a confirmation SMS including the normalized command and the resolved date/time within 5 seconds And creates an audit log entry capturing timestamp, senderId, workOrderId, normalizedCommand=RESCHEDULE, parameters, and outcome=Accepted
Vendor reports running late with ETA minutes
Given a verified vendor assigned to a specific work order And the vendor texts "RUNNING LATE 20" via WhatsApp When the parser processes the message Then it recognizes RUNNING LATE and extracts ETA=20 minutes And updates the scheduling domain with the new ETA for the correct work order And notifies the tenant and property manager via their preferred channels And responds to the vendor with confirmation including workOrderRef and ETA within 5 seconds And writes an idempotent audit log entry with providerMessageId to prevent duplicate processing on retries
Approver approves with currency amount via SMS
Given a verified approver with permission on a work order awaiting approval And the approver texts "approve $150.00" to the thread When the parser processes the message Then it normalizes the command to APPROVE and extracts amount=150.00 USD from "$150.00" And validates the approver’s permission and approval limit And routes the approval to the approvals service which transitions the work order status accordingly And sends immediate confirmations to the approver and vendor including the approved amount within 5 seconds And records an audit log with normalizedCommand=APPROVE, amount=150.00, currency=USD, outcome=Accepted And If the sender lacks permission or the amount exceeds limit, Then the system replies with an error explaining the reason and provides HELP without changing work order state and logs outcome=Rejected
Deterministic fallback when multiple active work orders
Given a verified contact (tenant or vendor) with 3 active work orders And the contact texts "CANCEL" When the parser detects ambiguity in work order context Then it replies with a numbered selection list of up to 5 candidates ordered by most recently updated And persists a selection window for 10 minutes When the contact replies with a valid number (e.g., "2") within the window Then the command is applied to the selected work order and confirmation is sent within 5 seconds And the selection is logged with workOrderId mapping When the contact replies with an invalid number or times out Then the system re-prompts once and on second failure sends HELP without performing the action
Role-based HELP command
Given any sender texts "help" or an unrecognized keyword When the parser processes the message Then it normalizes HELP and determines the sender’s role (tenant, vendor, approver) and active context And replies with a single SMS/WhatsApp message listing only the allowed commands for that role with 1–2 examples each And includes a note on how to specify a work order reference if multiple exist And the response is delivered within 5 seconds and does not alter any work order state And the HELP invocation is logged with outcome=Info
Fuzzy matching tolerance and safety
Given supported commands are {RESCHEDULE, PHOTO, CANCEL, CONFIRM, RUNNING LATE, APPROVE, REQUEST ESTIMATE, HELP} When a sender submits a keyword with edit distance ≤ 1 to a supported command (e.g., "reshedule", "confrm") Then the parser maps it to the intended command and proceeds normally When a sender submits a keyword with edit distance ≥ 2 or ambiguous between two commands at equal distance Then the parser does not auto-select and instead replies with a clarification prompt or HELP And in all cases, the matched command and distance are recorded in the audit log
Provider integration, idempotency, and rate limiting
Given inbound webhooks from Twilio SMS and WhatsApp Business API with provider-specific payloads When messages are received Then the system extracts sender address, message body, providerMessageId, and channel consistently across providers And acknowledges the webhook with HTTP 2xx within 2 seconds And ensures idempotent handling by ignoring duplicate providerMessageId within a 24-hour window And outbound replies are throttled to the configured provider rate limits (e.g., not exceeding the configured msgs/sec per sender-recipient pair) with queued retries and exponential backoff on 429s And no test run exceeds the configured rate; all throttled sends are eventually delivered or marked failed with reason logged
Identity & Permission Enforcement via Phone Mapping
"As a property manager, I want text commands to execute only for verified, authorized senders so that sensitive actions aren’t performed by the wrong person."
Description

Map inbound phone numbers to verified FixFlow user identities (tenant, vendor, approver) and their associated properties, units, and work orders. Enforce role-based access control so only authorized users can execute specific commands on relevant work orders (e.g., tenants cannot APPROVE, vendors cannot CANCEL tenant appointments). Support multi-tenant scenarios where a number may relate to multiple units or roles; challenge for disambiguation when necessary. Include opt-in/consent tracking and unsubscribe (STOP) handling to meet messaging compliance requirements. Provide secure one-time verification flows when a number is unknown or unverified. All permission checks must execute before command actions and produce clear denial responses when access is insufficient.

Acceptance Criteria
Phone Number Maps to Verified Identity and Role
Given an inbound SMS/WhatsApp message from a normalized E.164 phone number When the system receives a supported command keyword Then it must resolve the number to exactly one verified FixFlow user identity within 300ms p50 and 800ms p95 And attach all active roles (tenant, vendor, approver) and their related properties, units, and open work orders to the request context And if multiple identities share the same number, mark the request as ambiguous and proceed to disambiguation without executing any command And if the number is mapped to an inactive/suspended user, return a denial message with reason code user_inactive and do not execute the command
Role-Based Command Permission Enforcement Precedes Actions
Given a resolved user identity and a parsed command When the user lacks the required role permission for the command Then the system must deny the command before any state change with an explanatory SMS and reason code (e.g., tenant_not_authorized_for_approve, vendor_not_authorized_for_cancel) And no work order, appointment, or approval state shall be modified And the denial decision is logged with correlation ID Given a tenant sends APPROVE <amount> When permission is evaluated Then the command is denied with instructions on allowed tenant commands and state remains unchanged Given a vendor sends CANCEL When permission is evaluated Then the command is denied with instructions on vendor-allowed commands and state remains unchanged Given an approver sends APPROVE 150 within their approval limit and scoped property When permission is evaluated Then the command is allowed and proceeds to update the approval state
Disambiguation for Multi-Role or Multiple Contexts
Given a phone number maps to more than one active role, property, unit, or relevant work order When a command is received without sufficient context Then the system must respond with a numbered list (max 5 most recent relevant items) including minimal identifiers (e.g., WO# and unit) and a prompt to reply with the choice number or WO# And no command action is executed until a valid selection is received And invalid replies trigger up to 2 re-prompts with examples And if no valid selection is received within 10 minutes, the flow times out with a clear message and no changes made And upon valid selection, the chosen context is bound for 15 minutes of inactivity or until command completes, whichever comes first
One-Time Verification for Unknown or Unverified Numbers
Given an inbound message from a phone number not linked to a verified FixFlow identity for the channel When a command is received Then the system must halt command execution and initiate a secure OTP verification flow (6-digit code, expires in 10 minutes) And limit OTP sends to 3 per hour and verify attempts to 5 per 24 hours; exceeding limits returns a rate_limited denial And upon correct OTP entry and minimal identity confirmation (e.g., last name + unit or vendor company), the number is linked to the user and marked verified for the channel And upon verification success, the original command may be re-attempted by the user; no automatic execution occurs without explicit resend And failed verification or timeout returns a clear denial and does not change any work order state
Consent Tracking and STOP Handling
Given a phone number has not opted in for messaging on a channel When the system would send a proactive message Then it must not send and log a consent_required event Given the user replies START When processed Then the system records opt-in timestamp, channel, and source, confirms via SMS, and allows future messages Given the user sends STOP/END/CANCEL/UNSUBSCRIBE When processed Then the system immediately records opt-out with timestamp, sends a single confirmation SMS, blocks further outbound and command processing except HELP/START, and returns unsubscribed status on subsequent commands Given the user sends HELP When processed Then the system responds with channel-specific help, supported keywords, and a link to terms/privacy
Work Order Scope Enforcement Across Entities
Given a tenant identity sends RESCHEDULE or CANCEL When evaluating scope Then only work orders for that tenant’s unit with eligible statuses (Scheduled, Pending) are actionable; attempts on other units or ineligible statuses are denied with reason code scope_violation Given a vendor identity sends CONFIRM or RUNNING LATE When evaluating scope Then only work orders currently assigned to that vendor and in an active dispatch state are actionable; others are denied with reason code assignment_required Given an approver sends APPROVE <amount> or REQUEST ESTIMATE When evaluating scope Then only work orders for properties the approver manages are actionable; APPROVE requires a parseable positive amount within the approver’s limit; missing/invalid amount or over-limit is denied with reason code invalid_or_over_limit And work order IDs outside the user’s scope cannot be actioned even if explicitly provided
Audit Logging of Identity and Permission Decisions
Given any inbound message is processed When identity resolution, consent check, disambiguation, verification, and permission evaluation occur Then the system must write an immutable audit record containing correlation ID, timestamp, channel, normalized phone, resolved user ID (or null), roles, selected context IDs (property/unit/WO), command, decision (allow/deny), reason code, and message template used And sensitive fields are masked per policy and logs are retained for at least 13 months And an internal API allows authorized admins to query logs by correlation ID, date range, user ID, or work order ID; access is controlled via RBAC And audit writes must complete successfully before executing any state-changing command; failures cause a safe denial with reason code audit_unavailable
Context-Aware Scheduling via Text
"As a vendor, I want to text CONFIRM to accept an assigned job so that scheduling updates instantly without logging into another system."
Description

Enable scheduling operations through text commands with real-time availability. For RESCHEDULE, fetch open technician/vendor slots for the relevant work order and reply with selectable options; upon selection, update the calendar, notify all parties, and write back to the work order. Support vendor CONFIRM and RUNNING LATE updates that adjust ETA, update the schedule, and trigger tenant notifications. Handle time zones per property/unit, business hour constraints, and blackout periods. Ensure idempotency so repeated commands don’t create duplicate bookings. Persist all changes and notifications in the work order timeline.

Acceptance Criteria
Tenant RESCHEDULE Returns Valid Slots and Updates Appointment
Given a tenant has an existing scheduled appointment for a work order at a specific property And the property and assigned vendor have configured business hours and blackout periods When the tenant texts "RESCHEDULE" in the work order thread Then the system replies with 3–7 next available time slots within overlapping business hours, excluding blackout periods, in the property's local time And each option is selectable via numeric reply (e.g., "1", "2", "3") When the tenant replies with a valid option number Then the appointment is moved to the selected slot without double-booking the vendor And the tenant, assigned vendor, and manager receive confirmation messages of the new appointment And the work order timeline records the reschedule with before/after times, selected option, and message IDs
Vendor CONFIRM Updates Schedule and Notifies Stakeholders
Given a vendor is assigned to a scheduled appointment for a work order When the vendor texts "CONFIRM" in the work order thread Then the appointment status updates to Confirmed for that vendor and appointment And the tenant and manager receive confirmation notifications And the work order timeline records the vendor confirmation with sender identity, channel, and timestamp
Vendor RUNNING LATE Adjusts ETA and Triggers Notifications
Given a confirmed appointment has an expected arrival window or ETA When the vendor texts "RUNNING LATE <minutes>" with a numeric delay Then the system adjusts the ETA by the specified minutes and updates the schedule And if the new ETA violates business hours or hits a blackout period, the system proposes the next valid slot to the tenant via text with selectable options And the tenant and manager receive an updated ETA or rebooking prompt And the work order timeline records the previous ETA, new ETA, and delay reason
Time Zone Correctness Across Participants
Given a work order is tied to a property with a defined time zone and participants may be in other time zones When the system sends available slots, confirmations, or ETA updates Then all times presented to recipients are shown in the property's local time with a clear time zone abbreviation And any time values parsed from user replies are interpreted in the property's time zone And all timeline entries persist timestamps in UTC with the property's time zone offset stored as metadata
Business Hours and Blackout Enforcement for Scheduling
Given the property and vendor have business hours and blackout periods configured When generating options for RESCHEDULE or adjusting times due to RUNNING LATE Then only times within overlapping business hours and outside blackout periods are offered or set And if no valid slot exists in the next 7 calendar days, the system replies that no slots are available and prompts for broader date ranges or manager assistance And if a user selects an invalid, expired, or unavailable option, the system responds with an error and re-lists current valid options
Idempotent Handling of Duplicate Commands
Given the system computes an idempotency key from work order ID, sender ID, command type, parameters, and current appointment state When duplicate commands with the same key are received (e.g., repeated option selection, repeated "CONFIRM", or the same "RUNNING LATE" delay) before the appointment state changes Then only the first valid command mutates scheduling state And subsequent duplicates return a non-mutating acknowledgment indicating the action is already applied And no duplicate bookings, overlapping appointments, or duplicate timeline entries are created
Permissions Enforcement and Audit Logging
Given the sender phone is mapped to a tenant, vendor, or manager and the message is within a work order thread When the sender issues RESCHEDULE, CONFIRM, or RUNNING LATE Then the action executes only if the sender has permission for that work order and action (e.g., tenant for their unit, assigned vendor for their job) And unauthorized attempts return an authorization error without exposing work order details And all actions and notifications are persisted to the work order timeline with actor, action, parameters, pre/post state, channel (SMS/WhatsApp), and correlation ID for traceability
Amount-Aware Approvals by SMS
"As an approver, I want to approve a repair with a spend limit by replying APPROVE <amount> so that work can begin without waiting for desktop approval."
Description

Allow approvers to authorize spend limits directly via text by parsing commands like APPROVE 150 and REQUEST ESTIMATE. Extract currency and amount, validate against policy thresholds, and update the work order’s approval state with caps where applicable. Send confirmation with the approved ceiling, remaining budget (if partial), and next steps to the vendor and tenant. For REQUEST ESTIMATE, notify the vendor to submit an estimate and link the response back to the thread. Provide guardrails (e.g., require clarification if multiple work orders are open, reject malformed amounts) and maintain a complete audit record of the decision and actor.

Acceptance Criteria
Approve amount via SMS updates work order, notifies parties, and audits
Given an approver with approval permission and a single active work order thread When they reply via SMS/WhatsApp with "APPROVE 150" (any case) Then the system parses amount=150.00 and currency to the property default And sets workOrder.approval.state = "approved_with_cap" and workOrder.approval.cap = 150.00 with currency And within 30 seconds sends confirmations to approver, vendor, and tenant containing: work order ID, "Approved up to {currency}{amount}", and "Vendor may proceed up to this cap" And persists an audit record with fields: actorUserId (or unknown), actorPhone, command="APPROVE", rawMessage, parsedAmount, currency, workOrderId, timestamp, and outcome="approved" And acknowledges the inbound webhook/SMS gateway with a 2xx response code
Currency parsing and malformed amount handling
Given an approver replies with any of the formats "APPROVE 150", "APPROVE $150", "APPROVE 150.00", "APPROVE USD 150", or "APPROVE £200.50" When the message is processed Then the system extracts ISO currency and numeric amount accurately and normalizes to 2 decimal places And for locales using comma decimals (e.g., property locale fr-FR), input "APPROVE 150,00" is parsed as 150.00 And inputs with non-numeric or invalid formats (e.g., "APPROVE one fifty", "APPROVE $", "APPROVE 150..00", negative amounts) are rejected And within 30 seconds the approver receives a clarification SMS containing one valid example format matching the property locale And no approval state is changed and an audit record logs outcome="rejected_malformed_amount" with validation errors
Policy threshold validation and over-limit handling
Given the property policy grants the actor an approval limit of $200.00 USD When the approver sends "APPROVE 150" Then the approval is applied with cap=$150.00 and confirmation includes "Remaining policy budget: $50.00" And audit outcome="approved_within_threshold" Given the same policy When the approver sends "APPROVE 250" Then the command is not applied and the approver is notified within 30 seconds: "Amount exceeds your $200.00 limit; reply APPROVE 200 or escalate" And the work order approval state remains unchanged and audit outcome="rejected_over_limit" with attemptedAmount=$250.00 And if escalation is enabled, a notification is sent to the designated higher-level approver within 60 seconds containing work order ID and attempted amount
Disambiguation when multiple open work orders exist
Given an approver has two or more open work orders and sends "APPROVE 150" from a channel not tied to a single work order thread When the system cannot uniquely resolve a work order Then no approval is applied and within 30 seconds the approver receives a prompt listing candidate work orders with inline options: e.g., "Reply APPROVE 150 WO1234 or APPROVE 150 WO5678" And an audit record logs outcome="clarification_requested" with the candidate IDs Given the approver replies "APPROVE 150 WO1234" When processed Then the approval is applied to WO1234, confirmations are sent to relevant parties, and the clarification request is closed
REQUEST ESTIMATE triggers vendor action and threads the estimate
Given an approver replies "REQUEST ESTIMATE" in a work order thread without a current vendor estimate When processed Then workOrder.estimate.status is set to "requested" and an audit record with command="REQUEST ESTIMATE" is stored And within 30 seconds the vendor is notified with instructions to submit an estimate via SMS (e.g., "ESTIMATE 275") or via a link, and the approver and tenant receive acknowledgments Given the vendor responds "ESTIMATE 275" When received Then the estimate is linked to the same work order thread, workOrder.estimate.amount = $275.00 with source="vendor_sms" And the approver is notified with "Estimate received: $275.00; reply APPROVE 275 or REQUEST ESTIMATE for revision" And an audit record logs both messages and linkage
Permission and actor verification for approvals
Given a message "APPROVE 150" is received from a phone number not mapped to an authorized approver for the work order/property When processed Then the system rejects the command, sends "You do not have approval permissions for this work order" within 30 seconds, and logs audit outcome="rejected_unauthorized" Given a mapped approver sends "APPROVE 150" but policy requires a higher role (e.g., owner-only) When processed Then the system rejects the command, includes the required role in the response, optionally notifies the required approver, and leaves the work order unchanged
Instant Confirmations & Template Messaging
"As a tenant, I want immediate confirmation texts after I take an action so that I’m confident my request was processed."
Description

Send immediate, role-specific confirmation and follow-up messages after each command, using reusable templates that include action summaries, reference numbers, dates/times, and next steps. Support tenant-friendly prompts for PHOTO requests and provide secure upload links when needed. Localize templates for supported languages and ensure readability on common devices. Use consistent tone and branding, include opt-out instructions, and throttle non-critical messages to avoid spam. Log all outbound messages with delivery status for traceability and retries.

Acceptance Criteria
Tenant RESCHEDULE Instant Confirmation
Given a tenant with an existing scheduled visit sends "RESCHEDULE" from their verified number for Work Order {WO_ID} When the system records a new appointment date/time Then the tenant receives a confirmation via the same channel within 10 seconds that includes: action summary ("Rescheduled"), reference {WO_ID}, old date/time and new date/time in the tenant’s time zone, and clear next steps And if the reschedule attempt fails, the tenant receives an error message within 10 seconds with a retry or contact option
Approver APPROVE Command Confirmation
Given an approver replies "APPROVE {amount}" for Work Order {WO_ID} from a verified approver number When the approval is applied to the work order Then the approver receives a confirmation within 10 seconds that includes: action summary ("Approved"), reference {WO_ID}, approved amount with currency per property locale, date/time of approval, and next steps And if the amount is invalid or exceeds policy, a rejection message is sent within 10 seconds with guidance to use "REQUEST ESTIMATE"
Vendor CONFIRM and RUNNING LATE Notifications
Given a vendor texts "CONFIRM" for Work Order {WO_ID} When the confirmation is recorded Then the vendor receives an acknowledgment within 10 seconds including reference {WO_ID} and appointment date/time And the tenant receives a follow-up within 15 seconds summarizing the confirmed appointment and next steps Given a vendor texts "RUNNING LATE {minutes}" for Work Order {WO_ID} When the delay is recorded Then the vendor receives an acknowledgment within 10 seconds And the tenant receives an update within 15 seconds with the new ETA and next steps
Tenant PHOTO Prompt and Secure Upload
Given a tenant texts "PHOTO" for Work Order {WO_ID} or the system requests photos When the request is processed Then the tenant receives within 10 seconds a prompt with clear instructions and a secure upload link scoped to {WO_ID} And the link uses HTTPS, is single-use, and expires in 24 hours; attempts after expiry return instructions to request a new link And the upload endpoint accepts JPEG/PNG/HEIC images and MP4 videos up to 25 MB each, rejecting others with a clear error message via the same channel And upon successful upload, the tenant receives a confirmation within 10 seconds listing the number of files received and next steps
Template Quality, Localization, Readability, Branding, and Opt-out Compliance
Rule: For every confirmation and follow-up, the selected template matches the recipient’s preferred language when available; otherwise English is used Rule: All dynamic placeholders are resolved; no "{{...}}" tokens appear in delivered messages Rule: Dates/times and currencies are formatted to the recipient’s locale and time zone Rule: SMS confirmations are <= 320 characters; WhatsApp confirmations are <= 1000 characters Rule: Messages start with the "FixFlow" brand name and use a consistent, friendly tone (sentence case; no excessive capitalization) Rule: Every message ends with "Reply STOP to opt out" Rule: All links are HTTPS and <= 40 characters
Non-Critical Message Throttling and De-duplication
Rule: Non-critical follow-ups (reminders, summaries, FYIs) to the same recipient for the same Work Order are limited to 1 per 15 minutes and no more than 3 per 24 hours Rule: Duplicate confirmations caused by repeated identical commands within 2 minutes are de-duplicated; only one confirmation is sent, referencing the prior action Rule: Critical confirmations (initial command acknowledgments and required security prompts) are never throttled Rule: Throttled or de-duplicated events are recorded with a reason code for auditability
Outbound Message Logging, Delivery Status, and Retries
Given any confirmation or follow-up is sent for Work Order {WO_ID} Then a log entry is created containing: message_id, template_id, work_order_id, recipient_role, channel (SMS/WhatsApp), recipient_number, timestamps (queued/sent), and a payload checksum And provider delivery status events (queued/sent/delivered/failed) with provider codes are captured and linked to the message_id And if delivery fails or no delivery receipt is received within 5 minutes, the system retries up to 3 times with exponential backoff (1 min, 3 min, 9 min) And final status (delivered/failed) is persisted; all attempts are associated to the original message_id And numbers opted out via STOP or marked undeliverable are not retried
End-to-End Audit Logging & Traceability
"As a property manager, I want a complete audit trail of text-based actions so that I can resolve disputes and meet compliance requirements."
Description

Record every inbound and outbound text interaction with normalized fields: actor identity, role, channel (SMS/WhatsApp), timestamp, originating number, detected command, parameters, targeted work order, previous and new states, and outcome (success/failure). Provide immutable, filterable logs accessible from the work order timeline and exportable for compliance. Include correlation IDs to tie multi-message flows together and ensure clocks are synchronized for accurate ordering. Support retention policies and redaction of sensitive data while preserving operational context.

Acceptance Criteria
Inbound Tenant Command Logged with Normalized Fields
Given a verified tenant linked to work order WO-123 and a scheduled appointment When the tenant sends "RESCHEDULE 2025-09-22 14:00" via SMS from an E.164 number Then one inbound log entry is created and retrievable by workOrderId=WO-123 And the entry includes actorId (tenant UUID), role=tenant, channel=SMS, originatingNumber (E.164), timestamp (UTC ISO 8601 with milliseconds), detectedCommand=RESCHEDULE, parameters.dateTime normalized to ISO 8601 with timezone, targetedWorkOrderId=WO-123, previousState=Scheduled, newState=RescheduleRequested, outcome=Success And the entry is queryable by filters (role=tenant, channel=SMS, command=RESCHEDULE, outcome=Success, date range) and returns exactly 1 result
Inbound Vendor Command with Parameters Logged
Given an assigned vendor for work order WO-456 with appointment start at 09:00 When the vendor replies "RUNNING LATE 30" via WhatsApp Then one inbound log entry is created with actorId (vendor UUID), role=vendor, channel=WhatsApp, originatingNumber (E.164), timestamp (UTC ISO 8601 with milliseconds), detectedCommand=RUNNING LATE, parameters.delayMinutes=30 (integer), targetedWorkOrderId=WO-456, previousState=Scheduled, newState=VendorDelayed, outcome=Success And the log entry is retrievable by API and UI using workOrderId or correlationId and the parameters are stored as typed values (integer)
Inbound Approver Amount Command Logged
Given an approver with permission to approve spend for work order WO-789 and a pending approval request of $200 max When the approver texts "APPROVE 150" via SMS Then one inbound log entry is created with actorId (approver UUID), role=approver, channel=SMS, originatingNumber (E.164), timestamp (UTC ISO 8601 with milliseconds), detectedCommand=APPROVE, parameters.amount=150.00 (decimal), targetedWorkOrderId=WO-789, previousState=ApprovalPending, newState=Approved, outcome=Success And if the approver exceeds limits (e.g., "APPROVE 500"), a separate inbound log entry records outcome=Failure with reason=LimitExceeded and no state change
Outbound Confirmation Logged and Entries Are Immutable
Given a state change to Approved on work order WO-789 When the system sends a confirmation message to the tenant via WhatsApp Then one outbound log entry is created with actorId=system, role=automation, channel=WhatsApp, destinationNumber (E.164), timestamp (UTC ISO 8601 with milliseconds), messageTemplateId (if applicable), targetedWorkOrderId=WO-789, previousState=ApprovalPending, newState=Approved, outcome=Success And any attempt to update or delete this log entry via API or UI is rejected (HTTP 405/409) and instead an additional append-only correction event is recorded with a reference to the original entry And the original entry’s content-hash remains unchanged between reads
Correlation ID Ties Multi-Message Flow
Given a photo collection flow initiated by tenant texting "PHOTO" for work order WO-222 When the system requests more detail, the tenant replies, and then sends an image, and the vendor is notified Then every inbound and outbound message in this flow shares the same correlationId and lists targetedWorkOrderId=WO-222 And querying by correlationId returns the complete ordered set of events for the flow with no extraneous entries And if a parallel flow is started (e.g., "CANCEL"), it uses a distinct correlationId with parentCorrelationId linking to the originating event
Timeline Visibility, Filtering, and Chronological Ordering
Given work order WO-333 with at least 50 log entries across SMS and WhatsApp for tenants, vendors, approvers, and system When a user opens the Work Order Timeline Then the Logs section displays entries for WO-333 only, sorted by timestamp descending by default and switchable to ascending And applying filters (role=vendor, channel=SMS, command=CONFIRM, outcome in [Success, Failure], date range) returns results within 2 seconds and the count matches the server API response And entries with identical timestamps are consistently ordered using a monotonic sequence/ingest index so conversational order is preserved across channels
Compliance Export, Retention, and Redaction Preserve Context
Given retention policy: redact PII after 90 days, purge after 365 days, and a set of logs on work order WO-444 older than these thresholds When an authorized user exports logs for WO-444 as CSV and JSONL with an applied date filter Then the export is generated within 30 seconds and includes header/schemaVersion, correlationId, actorId, role, channel, timestamp (UTC), originating/destination numbers (masked except last 4), command, parameters, workOrderId, previousState, newState, outcome, and redaction/purge markers And sensitive fields (message bodies, images, full phone numbers) are redacted per policy while operational context fields remain intact And a SHA-256 checksum is provided and matches the downloaded file And the redaction and purge actions themselves are logged as system events and appear in the export And attempting to retrieve purged content returns a structured NotFound with a tombstone reference
Error Recovery, HELP, and Safeguards
"As a tenant, I want helpful guidance when I send a wrong or incomplete text so that I can still complete my task quickly by SMS."
Description

Deliver clear corrective guidance for unknown or invalid commands, including a HELP menu listing available options per role. When ambiguity exists (e.g., multiple open work orders), prompt the user to select the correct one. Apply rate limiting and basic abuse detection to prevent spam or accidental loops. Provide graceful degradation when provider APIs fail: queue retries, surface partial failures, and notify internal ops if SLAs are at risk. Ensure all error paths still generate audit entries and do not perform partial writes without compensation.

Acceptance Criteria
Invalid Command Triggers Role-Based HELP Menu
- Given a recognized user (tenant, vendor, or approver) sends an unsupported keyword or a supported keyword with invalid syntax - When the message is received and parsed - Then the system responds within 5 seconds with a HELP message tailored to the user's role and current context, listing only permitted commands and one example per command - And no state-changing action is performed - And an audit entry is recorded containing userId (or phone if unknown), role, inbound text, detected error code, helpVariantId, timestamp, and correlationId
Ambiguous Command Prompts Work Order Disambiguation (Tenant)
- Given a tenant has 2 or more open/scheduled work orders - When they send RESCHEDULE, CANCEL, or PHOTO without a specific identifier - Then the system replies within 5 seconds with a numbered list (max 5 items) of matching work orders showing short title and scheduled date/time, and asks the user to reply with a number - And upon receiving a valid selection within 10 minutes, the original command is executed against the selected work order and a confirmation is sent - And an invalid selection (non-numeric or out of range) results in one re-prompt and HELP; a second invalid attempt ends the flow with HELP - And all messages and outcomes are audited; no changes occur until a valid selection is received; selection window expiry is logged
Rate Limiting and Loop Protection on Inbound Messages
- Given any phone sends messages to the FixFlow endpoint - When more than 5 commands are received within 60 seconds or an identical message is received 3 or more times within 2 minutes - Then the system rate-limits further processing for 10 minutes for that phone, responds once with a throttling notice and HELP, and suppresses additional responses during the window - And if an auto-reply loop is detected (alternating messages repeating for 3 cycles), the conversation is paused for 30 minutes and internal ops are notified - And no state-changing actions are executed while throttled/paused; all attempted messages are logged with reason=rate_limited or loop_detected
Provider Failure Triggers Retry and User-Facing Degradation
- Given an outbound confirmation or prompt must be sent via SMS/WhatsApp - When the messaging provider returns HTTP 5xx, network timeout, or rate-limit errors - Then the message is queued with exponential backoff retries at 1m, 5m, and 15m (max 3 attempts) and marked pending in the audit log - And the inbound command is processed server-side and persisted atomically regardless of outbound failures - And if delivery remains undelivered after 15 minutes, a partial failure is recorded and surfaced to internal ops with correlationId, workOrderId (if any), error code, and next retry time; internal ops alert is sent - And if the user sends another inbound message before delivery succeeds, they receive a merged confirmation summary of prior successful actions - And if the provider recovers, only the latest valid confirmation is sent using idempotency to avoid duplicates
Error Paths Create Complete Audit and Perform Compensation
- Given any error occurs during command parsing, validation, or action execution - When an exception or validation failure is raised - Then an audit entry is written with correlationId, messageId, userId/phone, role, commandIntent, status=error, errorCode, and rollbackStatus - And no partial writes persist; any created records in the failed transaction are compensated within 30 seconds via a queued compensation job - And retries of the same inbound message are idempotent using a deterministic idempotency key derived from messageId and normalized command
HELP Menu Lists Only Permitted Commands Per Role and Context
- Given a recognized user sends HELP - When their permissions and current context (e.g., pending approvals, assigned jobs, open work orders) are evaluated - Then the HELP response includes only commands the user is allowed to run at that moment, each with a brief description and one example - And commands requiring selection are indicated (e.g., "If multiple jobs, you'll be asked to choose") - And the HELP response is sent within 5 seconds and audited with helpVariantId; it does not expose any data or actions outside the user's permissions
Parameter Validation and Correction for APPROVE and Similar Commands
- Given an approver sends APPROVE without an amount or with a non-numeric/negative amount, or a vendor sends RUNNING LATE without a duration - When the command parameters are missing or invalid - Then the system replies within 5 seconds with a concise correction prompt showing the exact required syntax and one valid example (e.g., "Reply APPROVE 150 to approve $150") - And if multiple relevant items exist (e.g., multiple pending approvals), the system first disambiguates, then validates parameters - And after a valid correction is received within 10 minutes, the action executes exactly once with idempotency; otherwise the session expires and the audit shows reason=expired

Product Ideas

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

Photo Gatekeeper AI

Detects leaks, gas, mold, and urgency from tenant photos, auto-prioritizes, and sets SLAs. Flags 'shutoff now' hazards with step-by-step prompts.

Idea

Access Window Wizard

Auto-negotiates tenant access windows by scanning vendor and tenant calendars. Sends magic links and smart nudges to lock confirmed slots in minutes.

Idea

Emergency Cost Shield

Applies after-hours rules, pre-negotiated rates, and remote diagnostics to slash emergency dispatches. Requires pre-approval thresholds and auto-reroutes to virtual fix first.

Idea

Vendor Scoreboard

Ranks vendors by first-time-fix rate, cost per job, and on-time arrival. Auto-assigns top performer per issue type.

Idea

Move-Out Blitz Kit

One-click turnover templates batch-create tickets, parts lists, and schedules. Side-by-side photo checklists speed make-ready and catch chargebacks.

Idea

Compliance Trail Vault

Creates tamper-evident logs with hashed photos, time-stamped actions, and e-signed approvals. Generates audit-ready PDFs per unit or funding source.

Idea

Text-to-Ticket Bridge

Converts SMS/WhatsApp messages into structured tickets with photo links and guided questions. Keeps old habits while standardizing intake instantly.

Idea

Press Coverage

Imagined press coverage for this groundbreaking product concept.

P

FixFlow Unveils After‑Hours Cost Control Suite to Cut Emergency Spend Without Sacrificing Safety

Imagined Press Article

Austin, TX — FixFlow today announced the After‑Hours Cost Control Suite, a coordinated set of capabilities that helps independent landlords and small property managers make safer, smarter decisions when issues pop up at night, on weekends, or during holidays. The suite reduces unnecessary emergency dispatches, standardizes approvals, and provides guided remote mitigation so teams can keep tenants safe while protecting operating margins. Built for owners and managers overseeing 1–150 units, the After‑Hours Cost Control Suite combines policy controls, remote diagnostics, intelligent approvals, and scheduling automation into one clear workflow. Early customers report 25% lower emergency repair costs, 40% fewer after‑hours truck rolls, and significantly faster decision cycles—without adding headcount or asking tenants to download an app. Why this matters: after‑hours calls drive the highest stress and the highest bills. Vendor rates surge, decisions must be made quickly, and misdiagnoses are common when details are scarce. FixFlow’s photo‑first intake and guided video, paired with approval policies and smart scheduling, give teams the information and discipline they need to act decisively and consistently. Key capabilities included in the suite: - After‑Hours Guardrails: Enforces clear, configurable rules before any dispatch. The system checks severity, building impact, shutoff status, and required remote steps, routing non‑critical issues to virtual fix first while escalating hazardous situations immediately. - LiveFix Triage: Launches a guided remote session with annotated video, checklists, and step‑by‑step prompts. Captures before/after media, logs actions, and hands off cleanly if a visit is needed. - Dynamic Thresholds: Applies time‑aware, issue‑specific pre‑approval limits that adapt to risk and SLAs, automatically approving when within policy and alerting the right approver when needed. - Next‑Day Saver: Compares emergency versus next‑day options with a simple risk‑and‑cost view, then books the earliest daylight slot when it’s safe to wait—explaining the plan to tenants in plain language. - Rate Lock Enforcer: Applies pre‑negotiated rate cards in real time, flags out‑of‑policy surcharges, and requests vendor acceptance via magic link before wheels roll. - Batch Rescue: When an after‑hours visit is unavoidable, the system offers compatible nearby jobs to piggyback within the same run—turning one emergency fee into multiple completed tickets. - Repair Predictor and Confidence Meter: Forecasts likely fix path, parts, and time range and displays human‑readable reasons for triage confidence, enabling clear, defensible decisions. “Emergency maintenance shouldn’t require a coin flip at midnight,” said Maya Lopez, CEO of FixFlow. “Our After‑Hours Cost Control Suite gives small operators the same disciplined playbook that large portfolios rely on—photo‑first evidence, guided remote steps, locked rates, and dynamic approvals—so safety and cost control finally move in the same direction.” Beta customers across student housing, small multifamily, and single‑family portfolios piloted the suite this summer. “We cut weekend dispatches nearly in half just by enforcing LiveFix steps and using Next‑Day Saver,” said Riley Chen, owner‑operator at RC Homes. “Tenants felt informed and safe, and we avoided paying premium rates when it wasn’t truly urgent. The confidence readouts and rate locks let me approve or defer in minutes, not hours.” How it works in practice: - A tenant submits photos or video via FixFlow’s chat link. Guided Retake prompts ensure diagnostic‑quality media in one pass. - Valve Finder overlays likely shutoff valves or breakers to help the tenant mitigate damage safely while a coordinator reviews. - The Confidence Meter summarizes what the AI sees and recommends LiveFix Triage if a remote walkthrough will stabilize the situation. - Dynamic Thresholds evaluate policy, risk, and SLAs. If the job is within safe parameters, Next‑Day Saver automatically books the earliest daylight slot and texts the tenant clear mitigation steps and status updates. - If a dispatch is needed, Rate Lock Enforcer secures the price, and Batch Rescue suggests compatible tickets nearby to improve utilization and dilute the emergency fee. Results you can measure: In a six‑week beta across 1,120 units, participants reported a 25% reduction in emergency repair costs, 18% faster time‑to‑decision on after‑hours tickets, and a 40% drop in unnecessary truck rolls. Tenant CSAT for after‑hours incidents improved as well, with comments citing clear instructions and proactive updates. Availability and pricing: The After‑Hours Cost Control Suite is available today for all FixFlow customers. Features are included in FixFlow Pro and Business plans, with LiveFix Triage session volume and Auditor exports available as add‑ons. Existing customers can enable the suite in Settings with prebuilt policy templates for common risk scenarios. Who it’s for: Independent landlords and small property managers who need to act decisively after hours without blowing the budget—especially portfolios where one on‑call person has historically juggled every midnight decision by text. About FixFlow: FixFlow automates maintenance intake, photo‑first tenant triage, approvals, technician scheduling, and tenant communication for independent landlords and small property managers overseeing 1–150 units. Customers report cutting response times by up to 60% and lowering emergency repair costs by roughly 25% while saving managers three to five hours weekly per property. Call to action: To see the After‑Hours Cost Control Suite in action, request a demo at fixflow.app/demo. Media contact: FixFlow Press Office press@fixflow.app +1 (512) 555‑0198 fixflow.app/press

P

FixFlow Launches Chain‑of‑Custody Compliance Stack for Audit‑Ready Maintenance in Minutes

Imagined Press Article

Austin, TX — FixFlow today introduced the Chain‑of‑Custody Compliance Stack, a set of evidence, approvals, and records tools that makes every work order audit‑ready by default. Designed for affordable housing programs, grant‑funded repairs, and portfolios with strict documentation requirements, the stack eliminates missing‑document surprises and compresses audit prep from days to minutes. At the heart of the announcement are ChainSeal Ledger and MetaProof Capture, which cryptographically chain every photo, note, approval, and status change to create a tamper‑evident record. The system locks trustworthy metadata—server timestamps, user IDs, device fingerprints, and optional geofence matches—to each upload, flags stripped or stale media, and requests a live retake when verification is needed. “Maintenance records should be defensible without a scavenger hunt,” said Maya Lopez, CEO of FixFlow. “With our Compliance Stack, proof is captured in the flow of work—hashes, timestamps, approvals, COIs, and audit trails—then compiled into a signed packet in one click. Teams stay focused on residents instead of digging through inboxes and spreadsheets.” The Chain‑of‑Custody Compliance Stack includes: - ChainSeal Ledger: Cryptographically chains every update and provides one‑click integrity checks that highlight exactly where any change occurred. - MetaProof Capture: Auto‑locks trusted metadata to each upload and flags altered or stale images, prompting a secure retake via magic link. - RoleRoute Approvals: Configurable approval matrices by cost, trade, funding source, and program rules with e‑sign and required attestations. - Audit Pack Builder: Generates an indexed, digitally signed PDF/ZIP containing the full timeline, hashed media manifest, work orders, COIs, invoices, and approvals. Templates support HUD/HQS, LIHTC, and insurance requirements. - Privacy Redactor: Automatically obscures faces, license plates, mail labels, and other PII before sharing, while preserving an access‑restricted original and logging all views or exports. - Retention Guard: Applies jurisdiction‑specific retention schedules with legal holds and deletion certificates to control storage and meet records policies. - Auditor Portal: Creates a time‑boxed, read‑only portal scoped to selected units, dates, or programs so auditors can self‑serve without altering records. For compliance‑heavy operators, the difference is immediate. “Our last HQS audit was the first I’ve ever closed without a single missing‑document finding,” said Cam Nguyen, compliance manager at Prairie Housing. “We exported the Audit Pack in under five minutes, and the auditor used the portal to spot‑check metadata and approvals without emailing us for screenshots. It saved our team dozens of hours.” How it works during a typical ticket: - Tenants submit photo‑first requests through FixFlow’s guided intake. MetaProof Capture verifies the media and anchors trusted metadata at upload. - Coordinators triage with visible confidence reasons. Required approvals are routed via RoleRoute Approvals, enforcing policy‑specific attestations before work proceeds. - Vendors are vetted automatically against Compliance Gate for licensing, insurance, and W‑9 status. Assignments reflect real‑time compliance, and expiring documents are penalized or blocked. - Field technicians log parts and labor, upload completion proof, and capture before/after photos. ChainSeal Ledger hashes each event into the audit trail. - At closeout, Audit Pack Builder compiles all artifacts and exports a digitally signed packet suitable for program rules, claims, or inspections. The stack integrates with the rest of FixFlow’s maintenance automation platform, including Skill Router, Rate Lock Enforcer, Quality Pulse, and SLA Forecaster, so operators get both operational speed and compliance integrity. When auditors or insurers have questions, the combination of ChainSeal proofs and the Auditor Portal reduce back‑and‑forth and shorten review cycles. Benefits reported by early users include an 80% reduction in audit preparation time, fewer missing‑document findings, and faster reimbursements for qualifying work. Portfolio owners also cite stronger cost control due to enforced approvals and increased trust in vendor assignments driven by up‑to‑date COIs and certifications. Availability and pricing: The Chain‑of‑Custody Compliance Stack is available today for FixFlow Pro and Business customers. Audit Pack Builder templates for HUD/HQS and LIHTC are included; additional templates and retention policies are available via an add‑on library. The Auditor Portal is included with volume‑based usage limits. Who it’s for: Affordable housing and grant‑funded programs, owner‑operators who must pass frequent inspections, and any team that needs dispute‑ready documentation without slowing down day‑to‑day operations. About FixFlow: FixFlow automates maintenance intake, photo‑first triage, approvals, technician scheduling, and tenant communications for independent landlords and small property managers overseeing 1–150 units. Customers report cutting response times by up to 60% and lowering emergency repair costs by roughly 25% while saving three to five hours per property each week. Call to action: Book a live walkthrough of the Compliance Stack at fixflow.app/compliance. Media contact: FixFlow Press Office press@fixflow.app +1 (512) 555‑0198 fixflow.app/press

P

FixFlow Introduces Smart Scheduling and Instant Confirmations to Shrink Tenant Downtime and Vendor No‑Shows

Imagined Press Article

Austin, TX — FixFlow today launched a new Smart Scheduling and Instant Confirmations bundle that compresses days of phone tag into minutes, cutting tenant downtime and dramatically reducing vendor no‑shows. The release unifies availability discovery, intelligent slot recommendations, one‑tap confirmations, and automated nudges—no logins or app downloads required for tenants or vendors. Built for independent landlords and small property managers overseeing 1–150 units, the bundle pairs FixFlow’s photo‑first intake with real‑time calendar matching and communication automation. Early users report a 60% reduction in time‑to‑first‑response, a 38% drop in no‑shows, and faster SLA compliance without adding headcount. “Scheduling is the quiet killer of maintenance timelines,” said Maya Lopez, CEO of FixFlow. “We’ve taken the back‑and‑forth out of it. Tenants get the right windows up front, vendors get routes that make sense, and coordinators get their evenings back. The result is faster fixes and fewer headaches for everyone involved.” The Smart Scheduling and Instant Confirmations bundle includes: - Smart Slot Match: Cross‑checks tenant, vendor, and building calendars against SLA deadlines and travel buffers to propose the top three conflict‑free windows. Options are ranked for fastest resolution and route efficiency. - One‑Tap Confirm: Sends secure magic links so tenants and vendors can confirm, reschedule, or propose alternatives without logging in. Selected slots are held temporarily and pushed to calendars instantly on confirmation. - Nudge Sequencer: Orchestrates reminders across SMS, email, and WhatsApp using response‑time patterns and preferred languages. Escalation paths move from gentle nudges to urgent prompts and optional voice IVR. - Waitlist Backfill: Detects cancellations and ghosted appointments, instantly backfilling openings with best‑fit jobs from a smart waitlist or nearby units to preserve utilization and SLAs. - Preference Memory: Learns each tenant’s preferred days, hours, access method, and building rules, pre‑filtering suggested windows to maximize acceptance and reduce reschedules. - Keyless Pass: Generates time‑bound, single‑use access codes for supported smart locks or lockboxes, verifying vendor identity before release and logging entry/exit. - Capacity Guard and SLA Forecaster: Protect against over‑assignment and predict each vendor’s likelihood to meet SLA using historical performance, travel time, access risk, and seasonality. For operators, the experience is straightforward. A tenant reports an issue via FixFlow’s SnapLink from a text thread; Guided Retake captures diagnostic photos the first time. ThreadSense turns the conversation into a structured ticket automatically, and AutoLocale continues the chat in the tenant’s preferred language. Smart Slot Match then proposes three windows already aligned to vendor routes and building constraints. One‑Tap Confirm secures the window in seconds, Nudge Sequencer follows up at the right moments, and Keyless Pass handles secure access if the tenant can’t be onsite. “Before FixFlow, it took three or four calls to land a time that stuck,” said Gabe Ortiz, principal at Lighthouse Stays, which manages 34 short‑term units. “Now tenants confirm from a link, we avoid first‑available delays, and Nudge Sequencer keeps everyone honest. No‑shows are down almost 40% and our cleaning and maintenance routes finally run on time.” Coordinators benefit from a board that stays clean and current. Duplicate Shield merges repeat reports automatically so teams never double‑dispatch. NumberMatch ties every message to the right unit without spreadsheet lookups. When a slot opens up, Waitlist Backfill refills it instantly from compatible tickets—protecting vendor utilization and preventing SLA slippage. The bundle is designed to play well with FixFlow’s broader automation. Skill Router assigns the right trade with the right tools based on detected issue patterns and first‑time‑fix history. Rate Lock Enforcer keeps costs compliant with negotiated cards. Quality Pulse captures tenant feedback after the visit and feeds it into Score Tuner, which adjusts vendor rankings by trade, building, or issue type to align with each operator’s goals. Benefits reported by early adopters include: - Faster first contact and shorter time‑to‑first‑visit thanks to ranked slots and instant confirmations. - Fewer reschedules due to Preference Memory and language‑aware outreach. - Reduced no‑shows and tighter routes from Capacity Guard and Waitlist Backfill. - Higher tenant satisfaction as communication shifts from uncertain voicemail loops to clear, time‑boxed updates via the channels people actually use. “Scheduling friction used to undo the gains from photo‑first triage,” said Devon Patel, owner‑operator at DVP Properties. “FixFlow’s new workflow keeps the momentum going: the right tech, the right window, locked in with a single tap. My coordinators went from chasing calendars to managing exceptions.” Availability and pricing: Smart Scheduling and Instant Confirmations are available today for FixFlow customers on Pro and Business plans. One‑Tap Confirm and Keyless Pass integrations are included; certain smart lock models and voice IVR escalation may require add‑ons. Preference Memory and Nudge Sequencer are on by default with controls in Settings for language, escalation policies, and blackout rules. Who it’s for: Solo landlords and small portfolio managers who need to resolve issues quickly without hiring a full‑time dispatcher, and field teams that want routes that run on time with fewer surprises. About FixFlow: FixFlow automates maintenance intake, photo‑first tenant triage, approvals, technician scheduling, and tenant communication for independent landlords and small property managers overseeing 1–150 units. Customers report cutting response times by up to 60% and lowering emergency repair costs by roughly 25% while saving three to five hours per property each week. Call to action: Start a free trial or schedule a live demo at fixflow.app/scheduling. Media contact: FixFlow Press Office press@fixflow.app +1 (512) 555‑0198 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.