Rental maintenance software

FixFlow

Instant Repairs, Restored Trust

FixFlow centralizes rental maintenance into a mobile web portal, using photo-first intake, automated smart triage, and one-click approvals that route repairs to preferred vendors. Independent landlords and small property managers (25–200 units) cut response time 40%, slash administrative work 60%, and reduce repeat vendor visits while restoring tenant trust.

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 small landlords and tenants to resolve maintenance instantly and transparently, transforming repairs into frictionless, trust-building experiences.
Long Term Goal
Within 3 years, power maintenance for 50,000 rental units, reducing average repair response time by 40%, cutting manager administrative work by 60%, and restoring tenant trust.
Impact
FixFlow reduces average repair response time by 40% for independent landlords and small property managers (25–200 units), cuts administrative work by 60% per manager, and lowers repeat vendor visits 25%, improving tenant satisfaction within three months.

Problem & Solution

Problem Statement
Independent landlords and small property managers (25–200 units) face scattered tenant repair reports, slow vendor response, and time-consuming manual approvals, while existing platforms are costly, bloated, and lack automated photo-first triage and one-click vendor routing.
Solution Overview
Centralize maintenance with a mobile-first, photo-first intake that auto-triages and prioritizes repairs, then routes one-click approvals and scheduling to preferred vendors—replacing scattered texts, spreadsheets, and missed bookings to cut response time and administrative overhead.

Details & Audience

Description
FixFlow centralizes rental maintenance requests, scheduling, and documentation into a lightweight web and mobile portal. Independent landlords and small property managers (25–200 units) use it. It cuts repair response time and administrative work by streamlining tenant photo-first reports, vendor coordination, and one-click approvals. Its automated smart triage categorizes, prioritizes, and routes requests to preferred vendors for instant approvals and scheduling.
Target Audience
Independent landlords and small property managers ages 30–60, residential, frustrated by slow repairs, prefer mobile-first.
Inspiration
At midnight a panicked tenant sent blurry photos of a burst pipe; I watched the landlord frantically toggle between text threads, voicemail, and a spreadsheet trying to book a plumber. The vendor missed the window, tenant escalated, trust evaporated. That chaotic hour made clear that a photo-first intake, automated triage, and one-click vendor routing could stop delays and restore dignity to repairs.

User Personas

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

A

After-Hours Fixer Felix

- Role: Regional property manager overseeing 120 units - Location: Secondary-city suburbs across three zip codes - Experience: 8 years maintenance coordination; on-call rotation lead - Tools: AppFolio, Google Voice, shared vendor spreadsheet

Background

Promoted after repeatedly stabilizing weekend emergencies without over-dispatching vendors. Burned by false alarms, he craves photo-backed triage and clear on-call coverage.

Needs & Pain Points

Needs

1. Instant severity triage from tenant photos 2. One-tap dispatch to on-call vendor 3. Automated updates to calm anxious tenants

Pain Points

1. False alarms trigger unnecessary midnight truck rolls 2. Vendor availability unclear after business hours 3. Scattered notes delay approvals and billing

Psychographics

- Thrives on swift, decisive crisis resolution - Despises busywork and duplicate after-hours calls - Trusts tools that prove problem severity - Balances tenant care with cost discipline

Channels

1. Phone urgent SMS 2. Email mobile summaries 3. Slack vendor DMs 4. AppFolio tasks 5. YouTube how-tos

T

Turnover Taskmaster Tasha

- Role: Make-ready coordinator for 180 mixed units - Location: Urban core with seasonal student demand - Experience: 6 years; former leasing agent turned ops - Tools: Buildium, Airtable checklists, shared calendars

Background

Cut her teeth flipping 40 units in a 10-day window each August. Repeated vendor bottlenecks forced her to systematize scopes and punch lists.

Needs & Pain Points

Needs

1. Calendarized turn schedules with dependency alerts 2. Bulk approve scoped work orders 3. Before/after photo checklists by trade

Pain Points

1. Vendors miss handoffs; painters block cleaners 2. Incomplete scope causes repeat visits 3. Lost keys and unclear access windows

Psychographics

- Obsessed with on-time, rent-ready confirmations - Loves batching work and clear dependencies - Demands photo-proofed, standardized completion standards - Measures success in zero-day vacancy loss

Channels

1. Google Calendar timelines 2. Email batch approvals 3. SMS vendor updates 4. LinkedIn vendor groups 5. YouTube make-ready tips

I

Integration-First Ingrid

- Role: Operations director, 190 units across two LLCs - Location: Multi-neighborhood metro; remote bookkeeper - Experience: 10 years; process automation champion - Tools: AppFolio, QuickBooks Online, Zapier

Background

Previously burned by mismatched ledgers and orphaned work orders after software switches. Led a migration project that cemented her passion for clean integrations.

Needs & Pain Points

Needs

1. Two-way sync for vendors, invoices, statuses 2. Exportable audit trails with timestamps 3. SSO and role-based permissions

Pain Points

1. Double entry creates month-end reconciliation hell 2. Users bypass process via emails 3. Unclear data ownership during migrations

Psychographics

- Wants truth-in-one-system, no exceptions - Allergic to manual re-entry and exports - Values APIs, webhooks, clear data schemas - Trusts vendors with published roadmaps

Channels

1. AppFolio marketplace 2. QuickBooks Apps 3. LinkedIn ops communities 4. Email release notes 5. Zapier community

V

Vendor Network Builder Victor

- Role: Owner-operator, 70 scattered single-family homes - Location: Two adjacent counties; 60-mile radius - Experience: 5 years; ex-contractor turned landlord - Tools: Google Sheets vendor list, Thumbtack, Yelp

Background

Started with his own tools, then outgrew personal bandwidth. Burned by no-shows and shoddy work, he systematized vendor trials and performance tracking.

Needs & Pain Points

Needs

1. Scorecarding vendor performance with photo proof 2. Geo-routing by trade and availability 3. Easy onboarding for new vendors

Pain Points

1. No-shows derail tenant trust and timelines 2. Coverage gaps in certain zip codes 3. Paperwork delays W-9s and insurance

Psychographics

- Prioritizes reliability over rock-bottom bids - Believes in steady work for great crews - Skeptical until vendors prove consistency - Loyal once trust is earned

Channels

1. Facebook trade groups 2. Yelp contractor reviews 3. Thumbtack job posts 4. Phone vendor referrals 5. Email onboarding

D

Documentation Defender Dana

- Role: Compliance and risk manager, 150 units mixed-income - Location: Older housing stock; frequent code inspections - Experience: 12 years; insurance claims background - Tools: SharePoint folders, email approvals, city portal

Background

Handled a costly mold claim lacking documentation. Since then, she built strict SOPs around maintenance evidence and retention.

Needs & Pain Points

Needs

1. Mandatory photo and estimate checkpoints 2. Auto-stamped approvals and status logs 3. Retention policies and export packages

Pain Points

1. Missing photos sink claims and abatements 2. Approvals scattered across inboxes 3. Inspectors demand timelines she can’t prove

Psychographics

- If it’s not documented, it didn’t happen - Prefers clear standards over ad-hoc judgment - Calm, meticulous, deadline-driven - Values defensible, audit-ready records

Channels

1. Email approvals 2. City portal 3. LinkedIn compliance 4. YouTube inspection 5. Microsoft Teams SOPs

T

Tech-Light Transitioner Terry

- Role: Independent landlord, 35 units, duplex-heavy - Location: Midwestern small city; older tenants - Experience: 20 years; minimal software history - Tools: Paper files, voicemail, basic SMS

Background

Built the portfolio slowly while working a day job. Past attempts at software failed due to complexity and tenant resistance.

Needs & Pain Points

Needs

1. Tenant-friendly photo intake via link 2. Simple, guided setup with templates 3. Readable mobile summaries, not dashboards

Pain Points

1. Constant phone calls for minor issues 2. Confusion about who to call when 3. Forgotten follow-ups without reminders

Psychographics

- Prefers simple steps over feature overload - Trusts tools he can explain in minutes - Patient but wary of disruption - Values human support and onboarding

Channels

1. SMS links 2. Email summaries 3. YouTube walkthroughs 4. Facebook landlord 5. Phone support

Product Features

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

Night Triage

Instantly classifies after-hours requests by severity using photo-first intake, keywords, and property context. True emergencies auto-queue for dispatch while non-urgent issues are scheduled for the morning, cutting costly overnight calls and accelerating critical responses.

Requirements

After-Hours Window Detection & Configuration
"As a property manager, I want after-hours periods to be automatically and correctly recognized per property and timezone so that Night Triage only runs when appropriate and respects holidays and seasonal hours."
Description

Implements property-level definitions for after-hours periods, including timezone auto-detection, daylight saving adjustments, and regional holiday calendars to determine when Night Triage should activate. Provides an admin UI to configure quiet hours per property, seasonal schedules, and blackout dates, with overrides for emergencies and special events. Integrates with FixFlow’s property context to ensure triage rules are only applied outside business hours, minimizing false triggers. Emits standardized signals to the triage pipeline to gate classification and dispatch. Ensures resiliency with failover defaults, validation on misconfigurations, and audit logs whenever schedules are modified. Expected outcome: accurate activation of Night Triage that respects local time and business policies, reducing unnecessary overnight engagements while ensuring emergencies are not missed.

Acceptance Criteria
Property Timezone Auto‑Detection and Override
Given a property has a valid street address and no manual timezone override When the property is created or its address is updated Then the system auto-detects and assigns the correct IANA timezone for that location And the assigned timezone is stored in the property context and displayed in the Admin UI And all after-hours calculations use the assigned timezone Given an admin sets a manual timezone override When the override is saved Then the override persists and is used for all schedule calculations until removed And the schedule preview reflects the override immediately
Daylight Saving Time (DST) Transition Handling
Given a property in a timezone entering DST (spring forward) When the after-hours window spans the missing hour Then Night Triage activates/deactivates at the correct local wall-clock times And no duplicate or skipped activations occur beyond DST semantics And audit logs and signals include the correct timezone offset Given a property in a timezone leaving DST (fall back) When the after-hours window spans the repeated hour Then only one activation window is applied during the repeated hour And no duplicate dispatch or gating signals are emitted And timestamps in logs/signals include the correct timezone offset
Regional Holiday Calendar Application
Given a property is configured to a regional holiday calendar with policy "Treat holidays as after-hours" When an intake event occurs on a configured holiday during normal business hours Then Night Triage treats the event as after-hours for that day And the gating signal includes reason=holiday and the holiday identifier Given a property opts out of holiday treatment When a configured holiday occurs Then standard business hours are applied and after_hours=false is emitted for business-hours events Given an admin adds an ad hoc holiday for a property with start and end times When the date/time falls within that window Then Night Triage follows the holiday policy for the property for that window
Admin UI Quiet Hours Configuration and Validation
Given an admin defines quiet hours (start/end) for a property When the input is invalid (e.g., end equals start, malformed time, or overlaps an existing segment) Then the form blocks save and displays specific inline validation errors Given valid quiet hours are saved When the configuration is persisted Then an audit log records actor, timestamp, before/after values, and reason (if provided) And the UI shows a preview of the next 7 days of Night Triage activation windows consistent with the saved schedule
Seasonal Schedules and Blackout/Special Event Overrides
Given seasonal schedules with effective date ranges exist for a property When the current date is within a season Then the season’s quiet hours supersede the base schedule for that period Given blackout dates are configured to suspend Night Triage When a blackout date/time window is active Then Night Triage remains off regardless of normal quiet hours, except for emergency overrides Given a special event override is configured to force Night Triage on When the override window is active Then Night Triage activates for that window regardless of base or seasonal schedules
Failover Defaults and Misconfiguration Resiliency
Given the schedule cannot be computed due to missing timezone, invalid configuration, or calendar service outage When an intake event occurs Then the system applies the configured environment-level failover after-hours window And emits a warning event and audit log detailing the failover reason and property And requests flagged as emergencies still dispatch immediately Given configuration is corrected When the next evaluation cycle runs Then normal schedule evaluation resumes without service restart and failover warnings cease
Standardized Signal Emission and Business-Hours Gate
Given an intake event occurs outside configured business hours for a property When Night Triage evaluates the event Then a standardized gating signal is emitted with fields: property_id, after_hours=true, timezone, effective_schedule_id, reason, and validity window And non-urgent classifications are queued for morning processing and emergencies are auto-queued for dispatch Given an intake event occurs during configured business hours When Night Triage evaluates the event Then after_hours=false is emitted and Night Triage is bypassed And no overnight dispatch alerts are sent
Photo-First Intake & Quality Validation
"As a tenant, I want clear guidance and validation when uploading photos or video so that my issue can be accurately triaged at night without repeated back-and-forth."
Description

Enforces photo-first intake with optional short video to improve classification accuracy, guiding tenants with live tips (lighting, framing, focus) and validating uploads for blur, low light, and obstruction before submission. Supports multi-image capture, attachment ordering, and annotation to highlight the issue area. Provides assisted fallback (MMS/email) if web upload fails and compresses media client-side while preserving critical metadata. Integrates with FixFlow ticket creation and storage, linking media to the request and redacting sensitive content where possible. Expected outcome: higher-quality visual evidence that improves severity classification and reduces unnecessary overnight dispatch.

Acceptance Criteria
Enforce Photo-First Submission
Given a tenant begins an after-hours request in the FixFlow portal When they attempt to submit without at least 1 photo or 1 short video Then the Submit action is disabled and an inline prompt instructs to add media Given a tenant adds at least 1 photo or 1 short video When all required form fields are complete Then the Submit action becomes enabled and submission proceeds Given camera access is denied by the device When the tenant continues the flow Then the app offers gallery upload as an alternative and still enforces the media requirement Given an unsupported media format is selected (not JPG/PNG/HEIC/MP4/MOV) When the tenant attempts to add it Then the app blocks the file and shows a supported formats message
Live Capture Tips and Pre-Submission Quality Checks
Given the live camera preview detects low light (mean luma < 60 on 0–255 scale) When the tenant is framing a shot Then a tip displays: "Increase lighting or enable flash" and the shutter remains available Given a captured photo has excessive blur (variance of Laplacian < 100) or obstruction coverage > 30% When the tenant proceeds to submit Then submission is blocked with a specific reason (e.g., "Image is blurry") and actionable tips are shown with Retake and Use Fallback options Given a captured photo passes all quality checks (luma ≥ 60, blur ≥ 100, obstruction ≤ 30%) When the tenant proceeds to submit Then a "Looks good" indicator is shown and submission is allowed Given a short video is captured When validating quality Then at least one frame every 2 seconds is checked for low light and severe blur; if more than 50% of sampled frames fail, submission is blocked with tips
Multi-Image Capture, Ordering, and Annotation
Given the tenant is adding evidence When they capture or upload photos Then they can add up to 10 photos and see thumbnails Given multiple photos are attached When the tenant reorders via drag-and-drop Then the order is persisted and sent to the backend as an explicit index for each attachment Given the annotation tool is opened on a photo When the tenant adds arrows, circles, or text Then an annotation overlay is saved non-destructively and the original image is preserved Given the tenant submits the request When media uploads complete Then originals and annotation overlays are stored and visible in triage in the same order as arranged by the tenant Given an upload for any photo fails When retry is tapped Then only the failed upload is retried without impacting successfully uploaded media
Optional Short Video with Client-Side Compression
Given the tenant chooses to add a video When they record or upload Then the app limits duration to 30 seconds and max resolution to 1080p Given a video is selected When client-side compression runs Then the output is H.264/AAC with target size ≤ 12 MB while preserving capture timestamp and orientation metadata and stripping GPS/location data Given network interruption occurs during video upload When connectivity returns within 2 minutes Then the upload resumes from the last completed chunk without restarting Given both photos and a video are attached When the tenant reorders attachments Then the final order (including the video) is persisted and reflected in storage and triage
Upload Failure Assisted Fallback (MMS/Email) Linkage
Given the tenant attempts web upload When 2 consecutive upload attempts fail or progress stalls for > 30 seconds Then the app presents assisted fallback options with a unique MMS number and email address prefilled with a tokenized subject/code Given the tenant sends media via the provided MMS/email When FixFlow ingests the message Then the media is linked to the in-progress request using the token and a confirmation is shown to the tenant within 5 minutes Given fallback media is received When quality validation runs server-side Then any failures are flagged as "Needs review" for triage without blocking the request from proceeding
Media Linking, Metadata Preservation, and Redaction
Given the tenant submits a request with media When the ticket is created in FixFlow Then all media are linked to the ticket with preserved order indices and annotation overlays Given images contain sensitive content When automatic redaction runs Then faces and license plates detected with confidence ≥ 0.7 are blurred, OCR-detected IDs (e.g., credit card numbers) are masked, and EXIF GPS/device model are stripped from the redacted derivative while capture timestamp and orientation are preserved Given redaction is in progress When processing completes Then a redaction audit log (what was redacted and confidence) is attached to the ticket and redacted versions are what vendors/tenants can view by default Given an image is submitted When redaction runs Then processing completes within 10 seconds per image for 95% of cases
Real-Time Delivery to Night Triage Classifier
Given an after-hours request is submitted with at least one media item that passed validation When the submission completes Then the media (and annotations) are available to the Night Triage classifier within 5 seconds Given Night Triage has received validated media When classification runs Then 95% of emergency determinations are produced within 30 seconds of submission and the dispatch queue is auto-populated with the linked redacted media Given media validation fails and the tenant does not use fallback within 10 minutes When the Night Triage window remains active Then the request is queued with a low-confidence flag and a prompt is scheduled to re-request media in the morning
Severity Classification Engine (Multimodal)
"As an on-call property manager, I want requests automatically classified by severity with clear reasoning so that true emergencies are escalated immediately and routine issues are deferred to morning."
Description

Builds a rules-augmented, multimodal engine that combines image analysis, NLP keyword detection, and property context (e.g., building type, HVAC configuration, occupancy) to classify after-hours requests into Emergency, Urgent, or Routine with confidence scoring. Applies hard safety rules (e.g., gas smell, active fire, major leak) to override low-confidence outcomes and force emergency escalation. Provides an admin-tunable threshold and rule editor, plus A/B evaluation against historical tickets. Outputs structured labels and reasons to downstream workflows and logs all inputs/decisions for auditability. Expected outcome: consistent, explainable severity decisions that accelerate critical responses and defer non-urgent issues.

Acceptance Criteria
Hard Safety Rule Override for Emergencies
Given current time is outside business hours And the request matches any hard safety rule via keywords, image cues, or context (e.g., gas smell/CO alarm, active fire/smoke, electrical burning, major leak/burst pipe) When the engine processes the request Then the final label is "Emergency" And override_applied is true And reason_codes include the fired rule IDs And the request is placed on the "Night Dispatch" queue within 1 second of classification And the decision payload includes base_label and base_confidence And P95 decision latency for such overridden requests is <= 2 seconds over a sample of 1,000 requests
Multimodal Fusion Classification and Explanation
Given an after-hours request with image(s), free-text description, and property context When the engine processes the request Then it returns label in ["Emergency","Urgent","Routine"] And confidence is a numeric value between 0 and 1 rounded to two decimals And top_features includes at least 3 entries with names and weights that together account for >= 80% of attribution And reasons reference contributing image cues, detected keywords, and context fields by name And no modality-specific error prevents classification
Admin-Tunable Thresholds and Rule Editor
Given an admin with the "Severity Config" role updates the Emergency confidence threshold to 0.85 and disables the "electrical burning smell" rule When the configuration is saved Then the configuration version increments with editor, timestamp, and change summary recorded And subsequent classifications use the new thresholds and rule set within 2 minutes And audit logs for new decisions show the applied config_version And non-privileged users attempting edits receive HTTP 403
A/B Evaluation Against Historical Tickets
Given an evaluation run is started against at least 5,000 labeled after-hours historical tickets When the run completes Then the report includes per-class precision, recall, F1, confusion matrix, override rate, and average decision latency And Emergency recall is >= 0.98 And Emergency false negatives are 0 for cases where any hard safety rule is present And weighted F1 is >= baseline F1 + 0.05 or within 0.01 while the Emergency recall target is met And the full report is exportable as CSV and JSON
Structured Output and Routing
Given any after-hours classification decision is produced When the payload is emitted to downstream workflows Then it conforms to schema {request_id(UUIDv4), label(enum), confidence(0..1), override_applied(boolean), reason_codes(array<string>), top_features(array<object{name,weight}>), model_version(string), rule_set_version(string), config_version(string), timestamp(ISO8601)} And emergencies route to "Night Dispatch" and non-emergencies route to "Morning Schedule" within 5 seconds And if schema validation fails, the message is retried with exponential backoff up to 5 times and logged with error code "SEVERITY_SCHEMA_INVALID"
End-to-End Audit Trail and Reproducibility
Given a request_id is provided When an auditor retrieves the decision record Then the record contains: hashed references to input images, original text, parsed keywords, property context snapshot, per-modality scores, rules fired, model_version, rule_set_version, config_version, label, confidence, decision_latency, and routing actions And the record is immutable and tamper-evident via a content hash And the record can be exported in JSON and CSV And PII fields are redacted while preserving explanation utility
Graceful Degradation and Low-Confidence Handling
Given an after-hours request is missing either image or text, or the model returns final confidence < 0.50 without any hard safety rule firing When the engine processes the request Then it returns a label in ["Urgent","Routine"] with review_required = true And the payload lists missing_modalities by name And such requests route to "Morning Review" (not "Night Dispatch") And this behavior is controlled by the admin setting low_confidence_review and is recorded in the audit log
Auto-Dispatch to On-Call Vendors
"As a landlord, I want true emergencies auto-routed to the correct on-call vendor with acknowledgments and escalation so that critical issues are handled immediately without me coordinating overnight."
Description

Automatically creates emergency work orders and routes them to on-call preferred vendors based on skill, service area, and on-call rotation calendars. Delivers media, location, and safety notes; requests acknowledgment within a configurable SLA; and escalates to backups if unresponsive. Supports two-way messaging, ETA capture, and live status updates for tenants and managers. Integrates with FixFlow’s vendor directory, one-click approvals, and cost controls while respecting per-property dispatch rules. Expected outcome: faster resolution of critical incidents without manual coordination, reducing damage and tenant risk.

Acceptance Criteria
Emergency Auto-Dispatch After-Hours
Given a maintenance request is submitted after-hours and classified as "Emergency" by Night Triage And the property has at least one on-call preferred vendor for the required skill When the request is saved Then the system creates an Emergency work order with a unique ID within 10 seconds And sets the work order status to "Dispatched" And sends the dispatch to the primary on-call vendor via push/SMS/email with a secure link And includes property address and unit, tenant contact mask, all media attachments, and safety notes
On-Call Vendor Selection by Skill, Area, and Rotation
Given vendor directory entries include skills, service areas, and on-call rotation calendars And the property has dispatch rules with preferred and excluded vendors When the system selects a vendor for an emergency work order Then the selected vendor has the required skill tag(s) And the property address falls within the vendor's service area And the vendor is marked "On Call" for the incident date/time on their rotation calendar And the vendor is in the property's preferred list and not in the excluded list And if multiple vendors qualify, the one at the top of the on-call rotation for that skill is chosen And the selection rationale (skill, area, rotation, property rule) is logged on the work order
Acknowledgment SLA and Escalation to Backups
Given the acknowledgment SLA is configured to 5 minutes and a backup sequence of up to 3 vendors When the initial dispatch is sent Then if no acknowledgment is received within 5 minutes, the system automatically escalates to the next vendor And repeats escalation every 5 minutes until a vendor acknowledges or 3 vendors have been tried And upon each escalation, the property manager is notified And if no vendor acknowledges within 15 minutes total, the incident is escalated to the on-call manager via SMS and phone call and marked "Unassigned Emergency"
Delivery of Media, Location, and Safety Notes
Given a vendor receives a dispatch link When they open the link Then they can view property address with map link, unit number, access instructions, safety notes, and tenant contact mask And they can view and download all attached photos and videos with load time under 3 seconds per media item on a typical 4G connection And any failed media load shows a retriable error with a "Retry" control and is logged
Two-Way Messaging and ETA Capture
Given a vendor has acknowledged the dispatch When the vendor submits an ETA timestamp Then the ETA is stored on the work order and displayed to tenant and manager portals within 10 seconds And both tenant and manager can exchange messages with the vendor in the work order thread And all messages are timestamped, attributed, and delivered as push/SMS/email according to user preferences And updates are persisted in the audit trail
Live Status Updates to Tenants and Managers
Given a vendor changes status to "En Route", "Arrived", "Work Started", or "Completed" When the status is updated via the vendor link or app Then the tenant and manager see the new status in their portals within 10 seconds And a notification is sent according to their channel preferences And the work order timeline records the status change with timestamp and actor
Cost Controls and One-Click Approvals for Emergencies
Given the property's emergency auto-approval limit is set to $500 and one-click approvals are enabled When an emergency work order is created Then the work order is auto-approved up to $500 And if the vendor submits an estimate exceeding $500, a one-click approval request is sent to the manager immediately And the vendor is shown the approved cap and cannot mark the job "Completed" with costs exceeding the cap until approval is granted And upon approval, the vendor is notified in real time; upon rejection, the manager selects an action (cancel or revise scope) and the vendor is notified
Morning Queue & Smart Scheduling
"As a property manager, I want non-urgent overnight requests batched and pre-scheduled for the morning so that I can approve efficient routes and avoid costly after-hours visits."
Description

Queues non-urgent after-hours requests for next business day and generates optimized scheduling suggestions based on vendor capacity, proximity, and job type, with batching for the same property or building. Presents managers with a morning review board to approve, reprioritize, or merge tickets, then sends confirmations to tenants with proposed time windows and ICS invites. Integrates with FixFlow’s scheduling and calendar systems, respecting tenant preferences and do-not-disturb windows. Expected outcome: efficient daytime scheduling that avoids unnecessary overnight callouts and reduces administrative overhead.

Acceptance Criteria
Auto-Queue Non-Urgent After-Hours Requests
Given a maintenance request is submitted after-hours per the property’s configured schedule and triage classifies it as Non-Urgent, When nightly processing runs, Then the ticket is added to the Morning Queue within 5 minutes and excluded from overnight dispatch. Given a maintenance request is classified as Emergency during after-hours, When triage completes, Then it is not placed in the Morning Queue and is routed for immediate dispatch. Given a Non-Urgent ticket is in the Morning Queue, When the business day start time configured for the property is reached, Then the ticket status updates to Ready for Scheduling and appears on the Morning Review Board.
Optimized Vendor Suggestions by Capacity, Proximity, and Job Type
Given a Non-Urgent ticket with property location and job type, When generating schedule suggestions for the next business day, Then the system returns at least three vendor options (or all available if fewer) that match job type skills, sorted by estimated travel time ascending, and not exceeding vendor capacity. Given a preferred vendor list is configured, When generating suggestions, Then preferred vendors with capacity and matching skills are ranked above non-preferred vendors. Given no vendors have capacity on the next business day, When generating suggestions, Then the earliest subsequent date with capacity is proposed with ranked vendor options.
Batching Jobs for Same Property or Building
Given multiple Morning Queue tickets for the same property or building with compatible job types, When generating schedule suggestions, Then the system proposes batched appointments assigned to a single vendor where capacity allows, with contiguous time windows and consolidated trip count. Given tickets are batched, When estimating durations and buffers, Then intra-building travel buffers are capped at 5 minutes and duplicate trip charges are not suggested. Given a manager un-batches or re-batches tickets, When saving changes, Then schedules and associated cost/time estimates recalculate within 30 seconds.
Morning Review Board Actions
Given the Morning Review Board is opened at the property’s business day start, When tickets load, Then each ticket displays severity, triage notes, suggested vendors with ETAs, batching recommendations, and tenant preference/DND indicators. Given a manager approves a suggested slot, When Approve is clicked, Then the vendor slot is reserved, the ticket status updates to Scheduled, the approver and timestamp are logged, and the change is visible on calendars within 60 seconds. Given a manager reprioritizes tickets, When Save is applied, Then the system recalculates suggestions within 30 seconds to reflect the new priority order. Given two tickets for the same property are merged from the board, When Merge is confirmed, Then a combined work order is created preserving original ticket references and history.
Tenant Confirmation with Time Windows and ICS Invites
Given a ticket is approved and scheduled, When confirmations are sent, Then the tenant receives a message via their preferred channel containing appointment date, a 2-hour arrival window, vendor name, vendor contact, property address, and an ICS invite in the correct timezone. Given the tenant’s notification preference is SMS-only, When sending confirmation, Then no email is sent and an ICS-compatible link is provided via SMS. Given a scheduled time is changed, When the schedule is updated, Then an updated confirmation and ICS update are sent within 2 minutes and the prior invite is superseded or canceled.
Respect Tenant Preferences and Do-Not-Disturb Windows
Given a tenant profile includes preferred times and do-not-disturb windows, When generating schedule suggestions, Then all proposed time windows avoid DND periods and prioritize preferred times when vendor capacity allows. Given DND and preference constraints leave no available window on the next business day, When scheduling, Then the earliest non-conflicting window on a subsequent day is proposed and flagged for manager review. Given a tenant allows supervised entry, When the tenant is unavailable during the earliest window, Then the system may propose a slot within building access hours and clearly notes supervised entry in the tenant confirmation.
Calendar Integration and Conflict Management
Given FixFlow internal calendars and vendor calendars are integrated, When a schedule is approved, Then the appointment is created on both calendars without conflicts; if a conflict is detected, Then the system automatically proposes the next best available slot and surfaces it to the manager. Given time zone differences or daylight saving transitions, When generating the ICS file, Then the event time reflects the correct local property time. Given a vendor cancels an appointment, When a cancellation event is received, Then the ticket returns to the Morning Queue with original priority preserved and the tenant is notified of rescheduling within 5 minutes.
Multi-Channel Notifications & Safety Guidance
"As a tenant, I want immediate, clear updates with safety instructions during an after-hours issue so that I know what to do and when help will arrive."
Description

Delivers real-time, localized notifications to tenants, managers, and vendors via SMS, email, and in-app push with rate limiting and quiet-hours sensitivity. For emergencies, includes concise safety checklists (e.g., shut off water, trip breaker, evacuate if gas odor) and expected response timelines. Provides message templates by severity and supports language localization and accessibility standards. Integrates with classification outputs and dispatch events to keep all parties informed. Expected outcome: clear, timely communication that guides safe tenant actions and reduces confusion during after-hours events.

Acceptance Criteria
After-Hours Emergency Multi-Channel Delivery With Safety Checklist
Given an after-hours incident classified as Emergency, When the classification is saved, Then send SMS, email, and in-app push to the reporting tenant within 15 seconds. And Then include the top 3 safety actions matched to the incident type (e.g., water leak, gas odor, electrical hazard) and property context where available. And Then include the expected response timeline sourced from the active dispatch or on-call SLA (e.g., "On-call will contact within 15 minutes" or "Vendor en route ETA 30–60 minutes"). And Then include a one-tap emergency contact (call/SOS) and track click events. And Then bypass quiet-hours and rate limits for safety-critical emergency messages. And Then record delivery receipt per channel; on failure trigger fallback per policy.
Quiet Hours Suppression and Rate Limiting for Non-Urgent Notifications
Given an after-hours incident classified as Routine or Low priority, When submitted between 22:00–07:00 local property time, Then send a single confirmation receipt within 60 seconds and suppress additional notifications until 07:00. And Then apply per-incident rate limiting to a maximum of 1 confirmation and 1 follow-up per 2 hours during quiet hours. And Then deduplicate notifications so identical status updates within 10 minutes are not re-sent. And Then at 07:00 send a morning summary containing a scheduling link and do not exceed 2 messages in 10 minutes per recipient per incident.
Language Localization and Accessibility Compliance
Given a recipient's language preference is set, When a notification is generated, Then render content in that language; else fallback to English and include a link to change language. And Then format dates, times, and phone numbers per locale and property timezone. And Then in-app notifications meet WCAG 2.1 AA (contrast >= 4.5:1, focus order, ARIA labels), and email HTML uses semantic headings and alt text; SMS uses plain-text with clear separators. And Then safety instructions are written at or below an 8th-grade reading level and avoid idioms.
Event-Driven Updates from Classification and Dispatch
Given an incident's severity changes (e.g., Routine -> Emergency), When the change is saved, Then notify tenant, manager, and on-call vendor within 30 seconds with the new severity and updated guidance. And Given a dispatch is created or assigned, When the vendor acceptance is recorded, Then notify tenant with assigned vendor name (or masked), ETA window, and next steps. And Then notify managers with a link to the work order and audit trail; only send updates on material state changes to avoid noise.
Delivery Failure Handling, Retries, and Channel Fallback
Given a notification send failure on SMS (carrier error or no receipt in 60 seconds), When detected, Then retry up to 3 times with exponential backoff (30s, 2m, 5m). And Then attempt fallback to alternate channels (push, email) preserving deduplication and message threading. And Then for Emergency notifications with all channels failed, escalate to automated voice call to the tenant and alert the manager. And Then log delivery status, failure reason, and fallback actions in the incident audit log.
Severity-Based Message Templates and Content Controls
Given severity classification (Emergency, Urgent, Routine), When generating a message, Then use the corresponding approved template with placeholders for incident ID, property name, timeline, and links. And Then enforce SMS content length <= 320 characters by truncation rules and link shortening; email supports full content; push <= 180 characters with deep link. And Then templates are versioned and editable by admins; changes are audited with author, timestamp, and diff. And Then each message includes opt-out instructions where legally required and excludes opt-out on safety-critical emergency alerts.
Morning Scheduling Workflow and Consent Management
Given a non-urgent incident created during quiet hours, When the morning summary is sent, Then provide the tenant a one-tap scheduling link offering slots within the next 48 hours. And Then collect tenant preferred contact channel and quiet-hours window; store preferences for future use. And Then honor unsubscribe/opt-out per channel for non-critical updates and confirm the opt-out via an immediate acknowledgment; retain consent records with timestamp and source. And Then managers receive a consolidated digest at 07:15 local for all non-urgent after-hours incidents with scheduling statuses.
End-to-End Audit Trail & SLA Reporting
"As an operations lead, I want a complete audit trail and SLA reporting for Night Triage so that I can prove compliance, identify bottlenecks, and improve performance over time."
Description

Captures immutable logs of all triage events, configuration changes, classification inputs/outputs, dispatch actions, acknowledgments, and communications with timestamps and actors. Provides dashboards and exports for KPIs such as after-hours volume, emergency rate, time-to-ack, time-to-dispatch, and avoidance of after-hours calls, segmented by property and vendor. Supports configurable data retention and privacy controls, enabling compliance audits and model improvement. Expected outcome: transparency and measurable performance gains that build trust with stakeholders and inform continuous optimization.

Acceptance Criteria
Immutable Triage Event Logging
Given an after-hours maintenance request is submitted When the system processes photo intake, extracts keywords, classifies severity, assigns a queue, and updates the case state Then an append-only audit log entry is created for each step with fields: event_type, request_id, property_id, vendor_id (if applicable), actor_id, actor_role, timestamp (ISO 8601 with timezone), source_ip, inputs_summary_hash, outputs_summary, previous_event_hash, event_hash And attempts to modify or delete an existing event are blocked; any correction creates a new event referencing prior_event_id And the hash chain validates end-to-end so any tampering of stored events is detectable And 99.9% of audit events persist within 2 seconds of occurrence and are retrievable within 200 ms p95 for the last 7 days
Configuration Change Audit Trail
Given a permitted user creates, updates, or deletes a triage rule, severity threshold, vendor routing preference, or SLA setting When the change is submitted Then an audit event records: change_id, scope (global/property), entity_type, entity_id, action (create/update/delete), before_values, after_values, actor_id, approver_id (if required), change_reason, timestamp And the configuration change is not applied unless the audit event is successfully persisted And unauthorized or failed attempts are rejected and logged with reason and actor_id And exporting configuration changes for a date range returns exactly the recorded changes with matching fields and counts
Dispatch and Acknowledgment SLA Metrics
Given a case is classified as an emergency during after-hours When the system dispatches to a preferred vendor and the vendor acknowledges receipt Then the following per-case metrics are recorded: time_to_dispatch = dispatch_ts − emergency_classified_ts, time_to_ack = vendor_ack_ts − dispatch_ts And metrics are aggregated hourly and daily by property and vendor and stored immutably And an alert is generated and logged when time_to_ack exceeds the configured threshold for the property And dashboard p50/p90/p95 values for time_to_ack and time_to_dispatch match raw event-derived calculations within ±0.1 minute
Communications Logging and Privacy Controls
Given any case-related communication (SMS, email, push, voice) is sent or received When the message is processed Then an audit event stores: channel, direction, template_id (if applicable), to_masked, from_masked, delivery_status, provider_message_id, error_code (if any), retry_count, timestamp, content_redacted according to policy And redaction removes configured PII fields while retaining non-PII tokens sufficient for traceability And delivery failures trigger retries with exponential backoff; each retry is separately logged And a retention job purges message content after N days while retaining metadata; each purge action is logged and a sample verification report confirms purge success
KPI Dashboards with Property/Vendor Segmentation
Given a user selects a date range, after-hours filter, properties/vendors, and timezone When the KPI dashboard loads Then it displays: after_hours_volume, emergency_rate, median and p90 time_to_ack, median and p90 time_to_dispatch, and avoided_after_hours_calls And definitions are applied as: emergency_rate = emergencies ÷ after_hours_volume; avoided_after_hours_calls = count of non-urgent after-hours requests scheduled for morning without dispatch And segmentation toggles provide per-property and per-vendor breakdowns, and drill-down links reveal the underlying cases and audit events And all displayed values reconcile with the corresponding CSV export within ±1 count or ±0.1 minute for the same filters
Secure Data Export and Integrity Verification
Given a user with export permission requests audit and KPI data for a specified date range and filters When the export is generated Then the system produces CSV and JSON (gzipped) files with schema_version, data dictionary URL, and includes a SHA-256 checksum and signed manifest listing all parts And the export is delivered via pre-signed URL valid for 24 hours; the request and download are logged as audit events And large exports are chunked with no chunk exceeding 100 MB; completeness is verifiable by manifest part counts and checksums And access control is enforced such that unauthorized export or URL access attempts are denied and logged
Model Feedback Dataset for Continuous Improvement
Given a reviewer validates a triage classification as correct or incorrect When feedback is submitted Then a feedback record captures: request_id, property_id, model_version, predicted_label, reviewer_label, reviewer_id, timestamp, inputs_summary_hash, consent_state And only properties with model_improvement_opt_in = true are included in feedback exports; toggling opt-out retroactively excludes prior records from future exports while retaining internal audit linkage And the model quality report computes precision and recall for after-hours emergencies from feedback and matches raw feedback aggregates within ±0.5% And feedback records comply with retention and redaction policies, with PII removed according to configuration

Fallback Cascade

If the on-call vendor doesn’t accept within a set window, FixFlow auto-escalates to skill-matched backups by proximity and past performance. This eliminates dead-ends, shrinks time-to-dispatch, and stops midnight back-and-forth.

Requirements

Auto-Acceptance Timer
"As a property manager, I want the system to auto-escalate when an on-call vendor doesn’t respond in time so that urgent repairs are dispatched quickly without me monitoring the clock."
Description

Starts a countdown when a work order is offered to the on-call vendor and automatically triggers the fallback cascade if no response is received within a configurable window. The window can vary by issue priority, property, and quiet hours, ensuring emergencies escalate faster while routine jobs respect vendor schedules. Integrates with FixFlow’s dispatch service to set, pause, and cancel timers based on accept/decline events and supports dynamic extensions if a vendor is actively viewing details. The outcome is a reliable, dead-end-free initiation of the cascade that shortens time-to-dispatch and removes after-hours back-and-forth.

Acceptance Criteria
Emergency no-response triggers fallback at expiry
Given a P1 Emergency work order is offered to the on-call vendor and a 10-minute auto-acceptance window is configured for P1 When the offer is sent and no accept, decline, or message is received before the window expires Then the system initiates the fallback cascade immediately at expiry And the original offer is marked "Expired" And an audit log entry records offer time, expiry time, and cascade start time And the dispatcher receives a notification that the cascade has started
Priority, property, and quiet-hours window selection
Given base auto-acceptance windows are configured as P1=10 minutes, P2=30 minutes, P3=60 minutes And a property-level override sets P1=5 minutes for the selected property And quiet hours are configured from 22:00–06:00 local time to add 60 minutes to P2/P3 windows When a P1 work order at the overridden property is offered at 14:00 Then the timer is set to 5 minutes When a P3 work order is offered at 23:30 local time Then the timer is set to 120 minutes (60 base + 60 quiet-hours) And the fallback cascade does not start before the computed timer elapses And the computed timer duration is displayed in the dispatch UI
Accept/decline cancels timer and controls cascade
Given an on-call vendor has an active auto-acceptance timer for a work order When the vendor accepts before expiry Then the timer is cancelled and removed And the fallback cascade is not triggered And the audit log records the acceptance time and timer cancellation When the vendor declines before expiry Then the timer is cancelled And the fallback cascade is triggered immediately upon decline And the audit log records the decline and cascade start
Active viewing grants one-time extension
Given a configured active-viewing extension of 5 minutes that may be applied once per offer And the on-call vendor is actively viewing the work order details within the last 2 minutes before timer expiry When the timer enters the final 2 minutes Then the system extends the timer by 5 minutes once And the UI displays "Extended 1/1" with the new expiry time And if no response is received by the extended expiry, the fallback cascade starts immediately When the vendor is not actively viewing in the last 2 minutes Then no extension is applied
Reassignment resets and withdrawal cancels timer
Given a work order offer with an active auto-acceptance timer When the dispatcher reassigns the offer to a different vendor before expiry Then the original timer is cancelled And a new timer is started for the new vendor using the currently applicable window rules And only one active timer exists across all vendors for the offer When the offer is withdrawn or the work order is cancelled Then any active timer is cancelled And no fallback cascade is triggered
Reliability: no Offered state without an active timer
Given a work order transitions to Offered state for any vendor When transient errors occur during timer creation Then the system retries timer creation until success or within a maximum of 60 seconds And a watchdog detects any Offered work order without an active timer within 2 minutes And if the computed window would have expired by the time the watchdog runs, the fallback cascade is initiated immediately And all retries and watchdog actions are recorded in the audit log
Idempotent timers prevent duplicate cascades
Given the system receives duplicate offer events or retry messages for the same vendor/work order When timers are created Then at most one active timer exists per vendor/work order And at timer expiry, at most one fallback cascade is triggered And duplicate expiry signals do not produce multiple cascades or duplicate notifications And the audit log shows a single cascade start entry for the offer
Skill and Proximity Matching Engine
"As a dispatcher, I want backups chosen by skills, distance, and past performance so that the best-qualified nearby vendor gets the job first."
Description

Ranks and selects backup vendors using skill tags, certifications, service categories, geographic coverage, travel time estimates, and historical performance metrics such as acceptance rate, completion time, first-time fix rate, and tenant ratings. Leverages FixFlow’s vendor profiles, property geodata, and past work order outcomes to generate a deterministic, auditable candidate list with tie-breakers and exclusion logic. Ensures the cascade targets the most capable, nearby vendors first to minimize travel, improve outcomes, and reduce repeat visits.

Acceptance Criteria
Eligibility Filtering by Skills, Certifications, Category, and Coverage
Given a work order with a specified service category, required skill tags, and required certifications And vendor profiles contain skill tags, certifications with expiration dates, offered service categories, geographic coverage, and active status When the matching engine evaluates eligibility Then include a vendor only if all conditions are true: - All required skill tags are present - The required service category is offered - Required certifications exist and are not expired at evaluation time - The property location lies within the vendor’s geographic coverage - Vendor status is Active And exclude any vendor failing any condition with a machine-readable exclusion reason code per vendor
Proximity Ranking by Estimated Travel Time
Given three eligible vendors with identical skills and performance metrics And their base locations and the work order property location are provided When the engine computes proximity Then it calculates an estimated travel time (ETA) for each vendor using road-network distance And orders vendors by ascending ETA when all other scores are equal And exposes the ETA used in the audit output to the nearest whole minute
Performance-Based Scoring and Weighting
Given eligible vendors with differing acceptance rate, median completion time, first-time fix rate, tenant rating, and estimated travel time When the engine computes the total score Then it normalizes each metric to a 0–1 scale (with lower completion time and travel time mapped to higher scores) And applies the default weights {proximity/travel time: 0.30, first-time fix rate: 0.25, acceptance rate: 0.20, median completion time: 0.15, tenant rating: 0.10} And produces a total score = sum(weight_i × metric_i) And includes the per-metric contributions and final score in the audit output for each vendor
Deterministic Tie-Breakers for Equal Scores
Given two or more eligible vendors have equal total scores rounded to three decimal places When the engine determines final rank order Then apply tie-breakers in this exact sequence until resolved: 1) Higher first-time fix rate 2) Shorter estimated travel time 3) Higher tenant rating 4) Higher acceptance rate 5) Shorter median completion time 6) Lower vendor UUID (lexicographical) And record the applied tie-breaker at each comparison in the audit output
Determinism and Auditability of Candidate List
Given a fixed set of inputs (work order data, vendor profiles, configuration) and a fixed algorithm version When the engine runs multiple times on the same inputs Then it produces the identical eligible vendor set, per-metric scores, total scores, and final ranking order And it persists an audit record containing: input identifiers and configuration version, excluded vendors with reason codes, eligible vendors with per-metric scores and total score, tie-breaker decisions, and the final ranked list with timestamps and algorithm version
No Qualified Vendors Handling
Given no vendors satisfy eligibility conditions When the engine evaluates candidates Then it returns an empty candidate list without error And provides an aggregated summary of exclusion reasons with counts And outputs a machine-readable status code "NO_QUALIFIED_VENDORS" in the audit record
Escalation Rule Builder
"As an operations admin, I want to configure how and when the system escalates offers so that our policies match urgency, vendor relationships, and budget constraints."
Description

Provides a configurable policy engine and admin UI to define cascade behavior, including number of waves, vendors per wave, wait times between waves, channel preferences, preferred vendor biasing, exclusion lists, and cool-off periods. Supports portfolio-level defaults with property and category overrides, plus export and import for standardization across accounts. Integrates with FixFlow permissions and audit to track who created or changed rules and when. This enables tailored escalation strategies that adapt to business goals without engineering changes.

Acceptance Criteria
Create and activate portfolio default escalation rule
Given I am an Admin with Manage Rules permission And there is no existing Portfolio Default escalation rule When I configure a new rule with 3 waves, 2 vendors per wave, 10-minute wait between waves, channel preferences [SMS, Phone], preferred vendor bias 30%, an exclusion list containing at least one vendor, and a 24-hour cool-off period And I save the rule as the Portfolio Default Then the rule saves without validation errors and status is Active And the generated cascade preview shows 3 waves with 2 unique, eligible vendors per wave honoring exclusions and cool-off And properties without overrides reference this Portfolio Default rule for new work orders
Override precedence for portfolio, property, and category
Given a Portfolio Default escalation rule exists And a Property Override rule exists for Property P And a Category Override rule exists for Property P and Category Plumbing When a Plumbing work order is created for Property P Then the Category Override rule is applied When an Electrical work order is created for Property P Then the Property Override rule is applied When a Plumbing work order is created for Property Q (no overrides) Then the Portfolio Default rule is applied And when the Category Override is removed, the next matching work order for Property P falls back to the Property Override
Enforce preferred bias, exclusions, and cool-off
Given a rule has preferred vendor biasing set to 40% And Vendor V1 is marked Preferred, Vendor V2 is on the exclusion list, and Vendor V3 completed a job 6 hours ago And the cool-off period is 12 hours When a cascade is generated for a matching work order Then V1 appears in an earlier wave or position relative to non-preferred vendors per biasing rules And V2 does not appear in any wave And V3 does not appear in any wave due to the active cool-off
Per-wave channel preferences and response window
Given a rule defines per-wave channel preferences [App Push, SMS, Phone] and a 5-minute response window per wave When a wave starts for eligible vendors Then notifications are sent in the specified channel order until acceptance is received And acceptance via any channel within the response window marks the vendor as accepted and stops further notifications for that vendor And if no acceptance occurs within 5 minutes, the wave closes and the next wave starts after the configured inter-wave wait time
Export and import rules across accounts
Given I have Manage Rules permission in both Account A and Account B And Account A contains a Portfolio Default rule and related overrides When I export rules from Account A Then a downloadable file is produced in the documented format containing all referenced settings When I import the file into Account B Then the import validates schema and references, reports any unmapped vendors or properties, and prevents partial saves on fatal errors And successfully validated rules are created in Account B without altering existing rules unless explicitly chosen to overwrite And an import summary lists created, updated, skipped items, and warnings
Permissions and audit logging for rule lifecycle
Given I am an Admin with Manage Rules permission When I create, edit, or delete an escalation rule Then an audit entry is recorded with actor, timestamp, action type, and changed fields And users without Manage Rules permission cannot create, edit, delete, import, or export rules and receive a permission error And audit records are immutable and viewable by Admins in the Audit UI
Multi-Channel Vendor Notifications
"As a vendor, I want clear, actionable notifications across my preferred channels so that I can accept or decline jobs instantly from anywhere."
Description

Sends cascade offers via SMS, push, email, and optional voice calls with deep links for one-click accept or decline. Includes essential job details, photos, location, and spend guardrails while redacting sensitive tenant data until acceptance. Tracks delivery, open, and click events with automatic fallback to alternate channels if undelivered. Localizes content and respects vendor quiet hours and notification preferences. Ensures vendors actually receive and can act on offers immediately, reducing time-to-accept.

Acceptance Criteria
Auto Fallback Across Channels on Undelivered or Unavailable
Given a vendor has an ordered channel cascade [SMS, Push, Email] and channel preferences configured And an offer is initiated to the vendor When the first channel attempt returns "undelivered" or "invalid destination" from the provider Or when no device/token exists for that channel Then the system sends the offer via the next available channel within 30 seconds And records attempt status, provider response code, and timestamp for each channel And stops further attempts once any channel reports delivered and the vendor accepts or declines And skips channels disabled by the vendor or restricted by quiet hours And prevents duplicate sends on the same channel for the same offer instance
Secure One-Click Deep Links for Accept/Decline
Given each offer includes unique, signed deep links for Accept and Decline per vendor and channel When the vendor clicks a deep link Then the action (Accept or Decline) is executed with a single click and a confirmation state is shown And the job is marked accepted within 2 seconds on Accept and competing offers are closed And tokens are single-use and idempotent; repeated use shows the final state without changing it And tokens expire after 24 hours or immediately once any vendor accepts the job And all deep links require HTTPS and are logged with channel, IP, and timestamp
Essential Details Included; Sensitive Data Redacted Until Acceptance
Given an offer notification is sent via any channel Then the content includes job category, priority, earliest service window, city-level location, street address without unit, spend cap, and up to 3 photos And photos are stripped of EXIF/metadata and face/license plates are blurred when detected And tenant name, phone, email, exact unit number, and entry instructions are omitted until acceptance And upon acceptance, the full details are revealed in the vendor portal and sent in a confirmation message
Localized Notification Content by Vendor Locale
Given a vendor has a locale set (e.g., en-US, es-MX, fr-CA) and a timezone When an offer is sent Then subject/body/push copy/IVR are rendered in the vendor's language And dates, times, and currency are formatted per locale and vendor timezone And if a translation key is missing, English is used and the missing key is logged And deep links route to the localized portal experience
Quiet Hours and Channel Preferences Enforcement
Given a vendor defines quiet hours 22:00–07:00 in their local timezone and per-channel allowances (Email=Allowed, SMS=Blocked, Push=Allowed, Voice=Blocked during quiet hours) When an offer is created at 23:15 local time Then only allowed channels (Email, Push) are used And blocked channels are not attempted and are scheduled for 07:00 if the offer is still active And all decisions are logged with suppression reason and evaluated in the vendor's timezone, respecting DST And no more than one notification per allowed channel is sent for the same offer instance during quiet hours
Delivery, Open, Click Event Tracking and Correlation
Given notifications are sent across multiple channels for the same offer When delivery/open/click webhooks are received Then events are persisted within 5 seconds with offer ID, vendor ID, channel, event type, and timestamp And events are correlated into a single timeline per offer-vendor and deduplicated across channels And the first Accept or Decline is authoritative; subsequent actions are acknowledged and ignored And a dashboard exposes per-channel delivery rate, open rate, and click-to-accept rate for the offer
Optional Voice Call with DTMF Accept/Decline and Voicemail
Given a vendor enabled Voice notifications and has a verified phone number When an offer is initiated Then a call is placed within 60 seconds using TTS to summarize job type, city, and spend cap And pressing 1 accepts; pressing 2 declines; invalid input reprompts once And if no answer is detected, a voicemail of ≤45 seconds is left with a callback link or code And the captured response updates the offer status within 3 seconds and cancels further channel attempts
Real-Time Availability and Calendar Sync
"As a property manager, I want the system to avoid notifying unavailable vendors so that escalation reaches someone who can take the job now."
Description

Synchronizes vendor availability from connected calendars and on-call rotations to automatically skip unavailable vendors and schedule offers during working windows. Supports temporary pauses, blackout dates, and maximum concurrent offers per vendor to prevent overload. Integrates with FixFlow scheduling, ensuring that acceptance prompts immediate booking options when availability is known. This reduces failed offers and speeds dispatch to vendors who can actually respond.

Acceptance Criteria
Calendar-Busy Vendors Are Skipped in Offer Generation
Given a vendor has a connected calendar with a busy event overlapping the offer window When FixFlow generates offer candidates for a new work order Then that vendor is excluded from the offer batch Given a vendor has an all-day OOO or event marked as busy on the target date When offers are generated for that date Then the vendor is excluded from eligibility Given a previously blocking calendar event is removed When the availability index refreshes (<=60 seconds) Then the vendor becomes eligible for the next offer evaluation
On-Call Rotation and Working Windows Govern Offer Timing
Given a vendor is not on-call during the current rotation window When FixFlow evaluates vendors for an offer Then the vendor is excluded Given a vendor is on-call and has working hours configured in their local time zone When FixFlow schedules an offer send time Then the offer is sent only within the vendor's working window Given the on-call rota changes mid-window When the rota update is received Then vendor eligibility updates within 120 seconds
Enforce Maximum Concurrent Offers Per Vendor
Given vendor.maxConcurrentOffers = N and the vendor currently has N outstanding, unexpired offers from FixFlow When the system prepares additional offers Then no new offers are sent to that vendor Given one of the vendor's outstanding offers is accepted, declined, or expires When the event is processed Then the next queued offer to that vendor may be sent within 60 seconds, never exceeding N concurrent offers Given monitoring is enabled When observing offer counts per vendor Then counts never exceed N at any time
Honor Temporary Pauses and Blackout Dates
Given a vendor has enabled a temporary pause from Start to End When FixFlow evaluates offer eligibility within that window Then the vendor is excluded from all offer sends during the pause Given a vendor has configured blackout dates When offers are generated for those dates Then the vendor is excluded from eligibility for those dates Given the pause ends or a blackout date passes When the availability index refreshes (<=120 seconds) Then the vendor is re-included without manual intervention
Immediate Booking Prompt on Acceptance When Slot Known
Given FixFlow has synchronized at least one free slot of duration >= required job time on the vendor's calendar When the vendor taps Accept on an offer Then FixFlow immediately presents available booking slots for confirmation Given the vendor selects a slot When the booking is confirmed Then FixFlow writes a reservation to the vendor's calendar and FixFlow schedule within 5 seconds and prevents double-booking Given no free slots are known at acceptance time When the vendor taps Accept Then FixFlow prompts the vendor to propose times or select "schedule later" without placing any calendar hold
Real-Time Availability Refresh on External Calendar Changes
Given a connected calendar event is added, updated, or deleted When the change webhook/poll is received Then FixFlow updates the vendor's availability and re-evaluates offer eligibility within 120 seconds Given a slot reserved by FixFlow is removed externally When the removal is detected Then FixFlow either re-creates the reservation if still valid or alerts the dispatcher within 60 seconds Given availability changes occur When updates are processed Then an audit record is stored with timestamp, source, vendor ID, and action
Time Zone-Accurate Working Windows and DST Handling
Given the vendor's profile time zone is TZ When FixFlow interprets working windows and calendar events Then all time calculations use TZ Given a daylight saving time transition occurs in TZ When offers and bookings overlap the transition Then their scheduled times remain correct with no unintended gaps or overlaps Given the vendor updates their profile time zone When the change is saved Then future offer timing and availability calculations switch to the new TZ within 120 seconds
Cascade Audit Trail and Stakeholder Alerts
"As a portfolio owner, I want visibility into how a job escalated and who responded so that I can validate SLAs and maintain tenant trust."
Description

Records a complete timeline of cascade actions including timers, offer sends, deliveries, vendor views, accepts/declines, and rule evaluations. Displays an in-app timeline for staff, with optional alerts to owners and tenants when escalation occurs or a backup accepts, maintaining transparency and trust. Exposes exportable logs and metrics APIs for compliance and performance reviews. This provides accountability, reduces confusion, and helps optimize policies over time.

Acceptance Criteria
Complete Cascade Event Audit Logging
Given a cascade is active, When any event occurs (timer start/expire, offer sent/delivered/viewed, vendor accept/decline, rule evaluated, escalation triggered), Then the system appends an immutable log entry within 2 seconds including: event_type, timestamp (ISO 8601 UTC with ms), actor (system/user/vendor_id), request_id, incident_id, vendor_id (if applicable), rule_id/version (if applicable), channel (if applicable), outcome, and payload checksum (SHA-256). Given sequential events, When logs are queried by incident_id, Then events are returned in strict chronological order with stable sort on timestamp then sequence_id, with no duplicates or gaps. Given write attempts, When a user without Audit:Write permission tries to modify or delete a log entry, Then the action is blocked and a security event is logged; only super-admins may set retention policies and no role may edit entries. Given retention is 24 months, When the cutoff is reached, Then logs are archived to WORM storage with index preserved and exportable upon request within 24 hours. Given a vendor reply via any channel, When the system ingests it, Then the correlated log entry includes correlation_id matching the outbound offer's message_id and delivery provider ids.
Staff In-App Timeline Visibility and Performance
Given a staff user with access to a property, When they open the incident timeline, Then it renders the latest 50 events within 1.5 seconds on 4G and achieves a Lighthouse performance score ≥ 80 for that view. Given many events (>5,000), When the user scrolls or paginates, Then next pages load within 1 second and maintain order; filters by event_type, vendor, date range, and text search return results within 2 seconds. Given real-time updates, When a new event is logged, Then the timeline reflects it within 3 seconds via WebSocket/SSE with fallback polling every 10 seconds. Given time zone preferences, When displaying timestamps, Then show in user’s local time with a tooltip for UTC and handle DST transitions correctly. Given accessibility needs, When navigating the timeline, Then keyboard navigation, focus states, ARIA roles, and screen reader labels meet WCAG 2.1 AA, including color contrast.
Owner and Tenant Alerting on Escalations and Accepts
Given property-level alert settings, When an escalation occurs or a backup vendor accepts, Then owners and opted-in tenants receive a notification via their preferred channels (email/SMS/push) within 60 seconds. Given quiet hours (e.g., 22:00–07:00 local), When an alert would be sent, Then SMS/push are suppressed and an email is queued; a morning summary is sent at 07:00 unless marked urgent. Given retries, When delivery fails (5xx or no receipt), Then retry up to 3 times with exponential backoff and log provider response codes in the audit trail. Given idempotency, When duplicate triggers occur, Then recipients receive no duplicate alerts within a 15-minute window per incident and event_type. Given privacy, When composing messages, Then vendor personal phone/email are excluded; include incident ID, property address, issue summary, and accepted vendor company name only; include unsubscribe/opt-out instructions.
Exportable Logs and Compliance Exports
Given staff with Export permission, When requesting an incident or date-range export, Then a CSV is generated with schema [incident_id, event_seq, event_type, utc_ts_ms, local_tz, actor_type, actor_id, vendor_id, rule_id, rule_version, channel, outcome, correlation_id, payload_checksum] and is available within 90 seconds for up to 100k events; larger exports stream to a pre-signed URL within 15 minutes. Given security, When an export is generated, Then the file is encrypted at rest, the link expires in 24 hours, and access is scoped to the requester’s org; all export requests are logged. Given integrity, When the export completes, Then a SHA-256 digest is provided and per-row checksums match a manifest. Given filters, When exporting, Then filters for property, vendor, event_type, and time range apply accurately and timezone handling matches the UI. Given redaction, When payloads contain PII (phone, email), Then those fields are masked according to policy in exported payload snippets.
Metrics API for Cascade Performance and Transparency
Given an authenticated client with Metrics:Read, When querying /metrics/cascade with filters (date_range, property_id, vendor_id, tier, radius), Then the API responds within 800 ms p95 and returns metrics: time_to_first_accept, time_to_dispatch, escalations_per_incident, acceptance_rate_by_tier, vendor_response_distribution, SLA_breaches. Given definitions, When comparing metrics with the in-app dashboard using identical filters, Then values differ by ≤1% due to aggregation windows. Given pagination, When requesting time-series buckets (hour/day/week), Then bucket boundaries are UTC-consistent and cursor or limit/offset pagination is supported. Given rate limits, When a client exceeds 60 requests per minute, Then the API returns 429 with Retry-After and logs the event. Given versioning, When the metrics schema changes, Then a new versioned endpoint is provided and the previous version remains available for ≥90 days.
Timer Accuracy and Rule Evaluation Traceability
Given a cascade step with a 10-minute accept window, When the timer starts, Then it expires at 10 minutes ±5 seconds even across server restarts and DST changes. Given rule evaluation, When selecting next vendor(s), Then the audit log records rule_id, version, input attributes (redacted where sensitive), decision output, and evaluation latency, within 2 seconds of decision. Given job retries, When a timer or rule job is retried, Then duplicate actions are prevented via idempotency keys and only one audit entry is marked final; retries are logged with attempt numbers. Given clock drift, When system time deviates >1 second from NTP, Then health alerts are raised and new timer scheduling pauses until corrected. Given manual override, When staff override the cascade path, Then the action and reason are logged with user_id and timestamps, and standard alerts follow the same rules.
Permissions and Data Integrity Controls
Given RBAC, When a user lacks Audit:View, Then the timeline and exports are inaccessible; users with OwnerView see redacted vendor details; super-admins can view full data. Given tamper detection, When any audit store mutation is detected, Then a hash-chain break alert is raised within 60 seconds and a copy is written to offsite immutable storage. Given backups, When daily backups run, Then audit logs are backed up with point-in-time recovery and restore is tested monthly; recovery of a single incident’s log completes within 30 minutes. Given data residency, When an org is marked EU, Then logs and exports are stored and served from EU-only regions and cross-region transfers are blocked.
Acceptance Race Handling and Idempotency
"As a dispatcher, I want the system to confirm only one vendor and auto-cancel the rest so that we avoid double dispatch and billing errors."
Description

Implements atomic locking to ensure only the first vendor acceptance is confirmed, immediately canceling outstanding offers and notifying other candidates. Prevents duplicate dispatches, double-bookings, and payment conflicts. Includes time-bounded holds requiring schedule confirmation within a configurable window, after which the cascade resumes automatically. Integrates with FixFlow’s work order state machine, payments, and notifications to keep all parties aligned.

Acceptance Criteria
First Acceptance Wins Under Concurrency
Given a work order in AwaitingVendor with offers sent to multiple vendors And acceptance locking is enabled When two or more vendors submit acceptance within 200 ms of each other Then the system acquires an atomic lock for the work order and confirms exactly one vendor And the winner is selected deterministically by earliest receivedAt timestamp then lowest vendorId as tie-breaker And the winner receives 200 Confirmed within 500 ms And all other acceptances receive 409 OfferUnavailable with no state change And exactly one dispatch record exists for the work order And an audit event records lock holder, contenders, decision time, and correlationId
Auto-Cancel And Notify Non-Selected Vendors
Given a vendor has been confirmed for a work order When the confirmation is persisted Then all other outstanding offers for that work order are cancelled within 10 seconds And non-selected vendors receive a cancellation notification with reason=OfferUnavailable and the confirmed vendorId redacted And their portals show OfferUnavailable within 10 seconds And the cascade queue removes their pending items for this work order And tenant and landlord receive a Vendor Accepted notification within 30 seconds And the work order timeline shows cancellation entries for each non-selected vendor
Idempotent Acceptance And Retries
Given a vendor submits POST /vendor/accept for a work order with idempotency-key K When the same request is retried up to 10 times within 5 minutes Then only one acceptance is recorded and all retries return 200 with the same body and header Idempotency-Replay=true When a request without the original key or with a different key is submitted after the work order is already confirmed Then the API returns 409 OfferUnavailable with no new side effects And external webhook deliveries related to acceptance are deduplicated by eventId for 24 hours And duplicate webhook deliveries do not create additional dispatches, state changes, notifications, or payment events
Time-Bounded Hold With Auto-Release
Given schedule_confirmation_required=true and hold_ttl_minutes=30 When a vendor is confirmed at time T0 Then the work order state is VendorAccepted_PendingSchedule with hold_expiration=T0+30m And if schedule is not confirmed before hold_expiration, the system auto-releases at T0+30m±1m And upon auto-release the cascade resumes to next eligible vendors within 60 seconds And the previously confirmed vendor is notified of release with reason=HoldExpired And tenant and landlord are notified that dispatch is continuing And if schedule is confirmed before expiration, the state becomes Scheduled and the cascade is halted
State Machine And Atomic Transitions
Given allowed transitions: AwaitingVendor -> VendorAccepted_PendingSchedule -> Scheduled and AwaitingVendor -> VendorAccepted_PendingSchedule -> AwaitingVendor (expiry/cancel) When a transition occurs due to acceptance, schedule confirmation, expiry, or cancel Then the state transition and all side effects (dispatch record, notifications, payment authorization/reversal) commit atomically in a single transaction or are fully rolled back And duplicate transition requests within 5 minutes are idempotently acknowledged with no additional side effects And invalid transitions are rejected with 422 and no state change And an immutable audit log captures prior_state, new_state, actor, reason, timestamp, and correlationId for each transition
Single Payment Authorization And Safe Reversal
Given payments are enabled When vendor acceptance is confirmed Then exactly one payment authorization is created for the work order and linked by correlationId And concurrent or duplicate acceptance attempts do not create additional authorizations (authorization count remains 1) And on auto-release or cancel before schedule confirmation, the authorization is voided within 5 minutes and is never captured And on re-dispatch to a new vendor, a new authorization is created and the prior one remains voided And all payment events are idempotent and visible in the audit log with provider reference and correlationId

One-Time PINs

Generates time-boxed, job-scoped lockbox codes on dispatch and revokes them on completion or timeout. Vendors get secure entry without phone calls; managers get a clean audit trail and reduced key risk.

Requirements

Lockbox Provider Integrations
"As a property manager, I want FixFlow to automatically generate and revoke lockbox codes with my preferred hardware so that vendors can access units securely without me coordinating keys."
Description

Implement an abstraction layer to integrate with major smart lock/lockbox providers (e.g., Master Lock, Igloohome, Yale, Schlage) to programmatically create, update, and revoke time-boxed, job-scoped PINs. Map devices to FixFlow properties/units, handle OAuth/token refresh, retries, rate limits, and idempotency, and normalize provider responses. Support per-property provider configuration, sandbox/production environments, and health checks with automatic failover where possible. This enables seamless code generation on dispatch and revocation on completion, reducing manual coordination and key risk while fitting into FixFlow’s dispatch workflow.

Acceptance Criteria
Token Management and OAuth Refresh per Provider
Given a provider integration is configured with valid client credentials and a refresh token When an access token expires or a call returns 401/403 due to token expiration Then the system refreshes the token before retrying, replays the original request once, and records a correlation ID for both attempts Given multiple concurrent requests require a token refresh When the first request initiates a refresh Then only one refresh is performed and subsequent requests reuse the new token within 100 ms Given a refresh attempt returns invalid_grant or equivalent permanent error When detected Then the integration is marked Unhealthy for the affected property within 30 seconds, further calls are blocked, and an alert is emitted to administrators Given sandbox and production credentials are stored When a property is configured to use production Then refresh and API calls are only made against production endpoints and keys
Device-to-Unit Mapping and Validation
Given a manager maps a provider device to a FixFlow unit When the mapping is saved Then the system validates the device exists via provider API, is reachable, and is not already mapped to a different active unit; otherwise the save is rejected with a clear error Given a dispatch is initiated for a work order on a unit When generating a PIN Then the device used is the one mapped to that unit; if no mapping exists or the device is offline, the operation fails with an actionable error within 2 seconds Given a mapped device is deleted or becomes unavailable at the provider When background validation runs Then the mapping is flagged Broken within 5 minutes and surfaced in the property’s integration health dashboard
Time-Boxed Job-Scoped PIN Creation on Dispatch
Given a work order is dispatched with a scheduled service window (start_at, end_at) When a PIN is created Then the PIN start time is max(now, start_at - 15 minutes) and the expiry is end_at + 60 minutes (configurable per property), and both timestamps are stored and returned Given repeated create requests for the same job and device within 24 hours When an idempotency key (job_id+device_id+action) matches Then no duplicate provider PINs are created; the previously created PIN details are returned with 200 Given provider constraints on code format (length, numeric-only) exist When generating a code Then the system conforms to provider constraints and normalizes the response to {provider, device_id, provider_code_id, starts_at, ends_at, last4, status} Given security and auditing requirements When storing the PIN Then the full PIN is encrypted at rest, masked in logs (showing only last 2 digits), and an audit record is written with actor, timestamps, request/response, and idempotency key
PIN Revocation on Completion or Timeout
Given a work order is marked Completed in FixFlow When the completion event is processed Then the associated PIN is revoked at the provider within 60 seconds and the local status transitions to revoked with a timestamp Given a PIN reaches its expiry window without completion When the timeout job runs Then the PIN is revoked automatically; if the provider is unavailable, the system retries with exponential backoff for up to 6 hours and marks the code pending-revoke until success Given a revoke request is repeated for the same PIN When an idempotency key matches Then the operation is idempotent and no duplicate revocations are created; the audit trail shows a single revoke outcome Given a revoke occurs When notifications are enabled for the property Then the assigned vendor and manager receive a confirmation that access has been disabled
Rate Limits, Retries, and Idempotency Controls
Given a provider returns HTTP 429 with or without a Retry-After header When calling create/update/revoke endpoints Then the client honors Retry-After (if present) or applies exponential backoff starting at 1s doubling to a max of 60s, up to 5 attempts before deferring to a queue for later processing Given transient network errors (5xx, timeouts) When performing an operation Then the system retries with jittered exponential backoff and preserves idempotency via keys scoped to job_id+device_id+action Given persistent errors exceed thresholds (e.g., >50% failures over 5 minutes) When circuit-breaker evaluation runs Then the provider is marked Degraded for the property, and non-critical operations are paused while critical revocations continue via queued retries
Per-Property Provider Configuration and Environment Isolation
Given a property selects a provider, credentials, and environment (sandbox or production) When saving configuration Then secrets are validated via a live verify call, stored encrypted, and an audit entry is recorded indicating who changed what and when Given a property is configured for sandbox When creating or revoking PINs Then all calls are routed exclusively to sandbox endpoints and no production devices are affected Given a user presses the Test Integration button When executed Then the system performs a dry-run create-and-revoke on a designated test device, returns the normalized response, and reports end-to-end latency under 5 seconds or a clear failure reason
Provider Health Checks and Automatic Failover
Given a property has both a primary and fallback provider configured with device mappings When health checks run every 30 seconds and 3 consecutive failures occur for the primary Then the primary is marked Unhealthy and new PIN creations automatically use the fallback mapping; the user is notified of failover Given the primary provider recovers for 5 consecutive health checks When new jobs are dispatched Then the system resumes using the primary provider without altering active fallback PINs; subsequent revocations respect the originating provider Given a failover action occurs When auditing Then a record captures the trigger, timestamps, affected jobs, from_provider, to_provider, and operator visibility in the health dashboard
PIN Lifecycle & Revocation Engine
"As an operations lead, I want PINs to automatically expire or be revoked when a job ends or times out so that access is limited to the service window and risk is minimized."
Description

Create a lifecycle service that ties PIN creation, activation, expiration, and revocation to job states (dispatched, on-site, completed, canceled) and schedules. Enforce unique, per-job/per-vendor codes, configurable access windows, and optional single-use constraints. Automatically revoke on completion, cancel on timeout, and prevent overlapping active codes per device. Provide safe concurrency controls, retries, and rollback on provider errors. Store PINs/token references securely (hashed PINs, encrypted secrets), and expose lifecycle state to the job timeline and API. This reduces access risk and ensures codes reflect real-time job status within FixFlow.

Acceptance Criteria
Dispatch Generates Time-Boxed Job/Vendor PIN
Given a work order is moved to Dispatched with vendor V assigned, device D selected, and access window [S,E] in the job/property time zone When the dispatch action is confirmed Then the system creates a unique PIN for (job, V, D) that does not match any other active or scheduled PIN on D within [S,E] And the PIN is inactive before S, active from S through E, and invalid after E And only vendor V can retrieve the plaintext PIN once via vendor portal/API; managers see a masked value after initial reveal And the API response includes a token/reference ID but not the plaintext PIN after initial reveal And an audit event "PIN Created" with device, window, actor, and vendor is appended to the job timeline
Automatic Revocation on Completion/Cancellation/Timeout
Given an active or scheduled PIN exists for job J When J is moved to Completed or Canceled Then the system revokes the PIN immediately with status Revoked(reason) And subsequent validation attempts with that PIN are denied And a timeline event and webhook "PIN Revoked" with reason and timestamp are emitted And when current time > E and J is not Completed, the system auto-revokes with reason Timeout and notifies vendor and manager
Optional Single-Use PIN Constraint
Given job J has Single-Use PIN enabled and a PIN is active When the first successful unlock/validation event for that PIN is received from the provider Then the system revokes the PIN within 5 seconds with reason SingleUseConsumed And any further attempts with that PIN are denied And the job timeline records "PIN Consumed (single-use)" with timestamp and device
Prevent Overlapping Active Codes Per Device
Given device D has any PIN active or scheduled during window W When a new PIN is requested for device D with window W' Then if W' overlaps W, the request is rejected with 409 Conflict and no PIN is created And if W' is non-overlapping, the new PIN is created per rules And concurrent create requests for D are serialized so that only one PIN is created and persisted
Concurrency Controls, Idempotency, Retries, and Rollback
Given a PIN create or revoke call is issued to the provider with idempotency key K tied to (job, vendor, device, action) When transient errors (HTTP 5xx, timeouts) occur Then the system retries with exponential backoff up to N=3 attempts And duplicate requests with key K do not create duplicate provider PINs nor duplicate records And if all attempts fail, FixFlow rolls back local state to the prior consistent state, emits an Error event, and schedules a reconciliation task
Secure Storage and Lifecycle Exposure
Given a PIN is created, updated, or revoked When data is persisted or retrieved Then the PIN value is stored only as a salted hash; provider secrets are encrypted at rest And plaintext PINs are never written to logs or analytics; access is audited by user and timestamp And authorized users can view the plaintext PIN only once; subsequent views show a masked value (e.g., last 2 digits) And API endpoints expose lifecycle states (Created, Active, Revoked, Expired, Consumed) with timestamps on job and PIN resources
Secure PIN Delivery & Verification
"As a vendor technician, I want to receive a secure PIN with clear instructions and timing on my phone after I’m dispatched so that I can access the unit without calling the manager."
Description

Deliver PINs to authorized vendor contacts via secure channels (SMS, email, in-app vendor portal) with clear instructions, access window, and property details. Use expiring, single-use reveal links and optional identity verification (login or OTP challenge) before showing the full code; mask codes in notifications. Track delivery status, opens, and acknowledgments; support localization and resend with audit. Limit forwarding by binding links to device or session where feasible. Integrate with FixFlow’s vendor directory and dispatch records to ensure only assigned vendors receive access details.

Acceptance Criteria
Masked PIN Notification with Expiring Reveal Link
Given a dispatch is created and a one-time PIN is generated for the assigned vendor contact in the FixFlow vendor directory When notifications are sent via configured channels (SMS, email, in-app) Then only the assigned contact(s) receive the notification And the notification contains property name, address, unit, access window start/end, and concise entry instructions And the PIN is masked in all notifications (e.g., ****1234) and never fully displayed And the notification includes a unique, single-use reveal link per recipient and per channel And the reveal link expiration is set to the access window end time (or the configured maximum, whichever is earlier) And each send attempt is logged with timestamp, channel, recipient ID, and dispatch ID
Configurable Identity Verification Modes
Given the organization or dispatch has a verification mode of None, OTP, or Login When the recipient opens the reveal link Then if mode=None, the signed link alone permits reveal on the first device used And if mode=OTP, the recipient must pass a one-time code challenge sent to the delivery phone/email before reveal And if mode=Login, the recipient must authenticate to the vendor portal as the assigned contact before reveal And only contacts attached to the dispatch can pass verification; other users receive an access denied message without any PIN digits And after 5 failed verification attempts within 15 minutes, the link is temporarily locked for 15 minutes and the manager is notified And all verification attempts and outcomes are written to the audit trail with timestamp and actor context
Single-Use and Time-Boxed PIN Enforcement
Given a reveal link exists for a dispatch PIN When the recipient successfully reveals the PIN Then that reveal link becomes invalid immediately for subsequent visits And the PIN is valid only within the configured access window When the job is marked completed or cancelled, or the access window elapses (whichever occurs first) Then the PIN is revoked and cannot be revealed or used thereafter And further reveal attempts display an "expired or revoked" message without exposing any PIN digits And revocation is recorded in the audit log with timestamp, actor, and reason
Resend with Audit and Rate Limiting
Given a manager with appropriate permissions opens the dispatch When they initiate a resend, select recipient(s), channel(s), and specify a reason Then new reveal link(s) are generated and prior unrevealed links for the same recipient are invalidated And per-recipient resend limits are enforced (e.g., maximum 3 resends within 60 minutes); excess attempts are blocked with an explanatory message And only vendor contacts assigned to the dispatch are selectable as recipients And the audit trail records the actor, timestamp, recipients, channels, reason, and related prior link IDs
Delivery, Open, and Acknowledgment Tracking
Given notifications have been sent Then delivery status (delivered, failed, bounced) is captured when supported by the channel provider And email opens and link-click events are recorded with timestamp and device metadata when available And the reveal page provides an "Acknowledge receipt" action that records an acknowledgment event with timestamp and recipient ID And if no open is recorded within N hours after dispatch and fewer than M hours remain before the access window starts, the system alerts the manager via in-app and email And the dispatch timeline displays delivery, open, reveal, acknowledgment, resend, and failure events in chronological order
Device/Session Binding and Forwarding Controls
Given a recipient opens a reveal link Then the link is bound to that device/session fingerprint upon first open And subsequent opens from a different device are blocked unless the recipient completes re-verification via OTP And a maximum of 1 device re-bind per link is allowed within a 24-hour period; further attempts are denied and logged And link URLs are signed, unguessable tokens; any tampering results in access denied without exposing PIN information And all blocked/denied attempts are captured in the audit trail with device metadata
Localization and Time Zone Correctness
Given a vendor contact has language/locale preferences and the property has a defined time zone When notifications are sent and the reveal page is viewed Then all static text renders in the vendor's preferred language with fallback to English if a translation key is missing And dates/times display in the property's local time zone with explicit abbreviation, and include the vendor's local time in parentheses when different And addresses and phone numbers are formatted per locale conventions And missing translation keys generate a logged warning and fallback content without breaking the flow
Access Window Controls & Extensions
"As a property manager, I want to adjust or extend an access window from the job screen so that I can accommodate schedule slips without creating security gaps."
Description

Provide manager-facing controls to configure default and per-job access windows (start/end, grace periods, time-of-day limits), plus one-click extension/renewal actions from the job screen. Enforce policy constraints (max duration, max extensions) and require reason codes for exceptions. Automatically notify vendors of window changes and update provider codes accordingly with full audit. Handle time zones, daylight saving, and overlapping schedule conflicts. Expose access window settings via API and templates to standardize across properties.

Acceptance Criteria
Configure Default Access Window at Property Level
Given a manager sets property default access window start=08:00, end=18:00, graceStart=15m, graceEnd=15m, allowedHours=08:00–20:00 in the property’s timezone When they save Then the settings persist and a success message is shown Given property defaults are configured When a new job is created without overrides Then the job inherits start/end, grace periods, and allowedHours from the property defaults Given property defaults are configured When retrieving GET /api/properties/{id}/access-window Then the response returns the saved values with timezone metadata in ISO 8601 format Given invalid values (e.g., end before start, negative grace, non-overlapping allowedHours) When saving Then validation errors are shown and no changes persist Given property defaults exist When a manager views a job’s access window Then the UI indicates “From Property Defaults” unless overridden
Per-Job Access Window Override with Grace and Time-of-Day Limits
Given a job with inherited property defaults When a manager edits the job’s access window on the job screen Then they can set start/end, graceStart/graceEnd, and time-of-day limits and Save Given a per-job override is saved within policy limits When viewing the job Then the window displays as “Overridden” and shows the effective local times Given a per-job override is saved When retrieving GET /api/jobs/{id}/access-window Then the response reflects the override values and source="job" Given a per-job override exists When a manager clicks “Revert to Defaults” Then the job window reverts to property defaults and source="property" Given proposed per-job values violate allowed time-of-day limits When saving Then validation blocks save and presents specific errors for the offending fields
One-Click Extension Within Policy Limits
Given a job with an active access window and remaining extensions>0 When a manager clicks “Extend +30m” Then the job’s window end time increases by 30 minutes and the remaining extensions decrement by 1 Given an extension is applied When retrieving the job’s access window via API Then end time reflects the new value and includes lastExtendedBy and lastExtendedAt in audit metadata Given an extension would exceed MaxDuration or MaxExtensions When a manager attempts to extend Then the system blocks the action with a clear error and no changes are made Given an extension succeeds When viewing the job Then the UI displays the updated end time, remaining extensions count, and “Extended” badge
Enforcement of Max Duration and Max Extensions with Exception Reason Codes
Given policy MaxDuration=8h and MaxExtensions=2 When creating or editing a job window exceeding 8h or extensions>2 Then the system blocks the change and shows a specific error Given a manager has “Can Override Policy” permission and attempts to exceed policy When prompted for a reason code Then they must select from a configured list or enter free text (min 5 characters) to proceed Given a valid reason code is provided by an authorized user When saving an over-limit window Then the save succeeds, the window is marked as Exception=true, and the reason code is stored Given an unauthorized user attempts a policy exception When saving Then the action is denied and no changes persist Given an exception save occurs When viewing audit logs Then the entry includes policy name, violated limit, exception flag, reason code, user, timestamp, and before/after values
Automatic Vendor Notification, Code Update, and Audit on Window Changes
Given a job access window is created, updated, extended, or canceled When the change is saved Then the assigned vendor receives a notification (email/SMS/in-app per preferences) within 60 seconds including job ID, address, start/end (with timezone), and updated entry code if changed Given a window change that affects entry timing When saving Then the lock/entry provider is updated so the code validity aligns with the new window times; on success, the provider confirmation ID is stored Given the provider update fails transiently When saving Then the system retries up to 3 times with exponential backoff and alerts the manager if all retries fail, leaving the previous code validity intact Given a vendor notification is sent When viewing the job timeline Then delivery status (queued, sent, failed) and message IDs are visible Given any window change occurs When viewing audit logs Then the log contains actor, timestamp (UTC), property timezone, change set, reason code (if any), notification IDs, and provider response
Scheduling Accuracy: Time Zones, Daylight Saving, and Overlap Prevention
Given a property timezone is set When displaying or enforcing access windows Then all times are shown and validated in the property’s local timezone and stored as ISO 8601 with offset Given an access window spans a DST change (spring forward/fall back) When enforcing code validity Then the absolute UTC interval reflects the correct local start/end and no unintended one-hour gaps or overruns occur Given a proposed job window for a unit/lock device overlaps an existing active window for the same unit/device When saving Then the system blocks the save and surfaces the conflicting job(s) with links and time ranges Given grace periods are configured When computing effective window Then early/late grace is applied without causing overlaps beyond policy; overlapping results still trigger conflict validation Given API clients send/receive windows When processing Then the service accepts and returns timezone-aware timestamps and rejects naive timestamps with a 400 error
Access Window Settings Exposed via API and Property Templates
Given a manager creates an Access Window Template with start/end, grace, allowedHours, and policy caps When assigning the template to multiple properties Then those properties adopt the template as their defaults within 2 minutes Given a template is updated When propagation completes Then linked properties reflect the new defaults; jobs with per-job overrides remain unchanged Given API endpoints exist When calling GET/PUT /api/properties/{id}/access-window and GET/PUT /api/jobs/{id}/access-window Then requests/responses validate fields, enforce the same rules as UI, and return 200 on success with the updated resource Given an API update changes a job window When successful Then vendor notifications, provider code updates, conflict checks, and audit entries execute identically to UI-driven changes Given a property is removed from a template When saved Then it retains the last applied defaults but no longer receives future template updates, and the change is audited
Access Audit Trail & Alerts
"As a portfolio owner, I want a complete audit of who accessed which unit and when so that I can prove compliance and investigate incidents."
Description

Capture a tamper-evident audit trail of all PIN-related events: creation, delivery, view/reveal, activation, usage, failed attempts, revocation, and extensions. Ingest supported lock telemetry (door open/close, invalid tries) via webhooks or polling. Present a consolidated timeline per job/unit and enable export and API access. Configure real-time alerts for anomalies (after-hours use, repeated failures, early access) to managers and owners. Provide dashboards and filters to support compliance, incident investigation, and vendor performance analysis within FixFlow.

Acceptance Criteria
Consolidated PIN Event Timeline per Job/Unit
Given a job linked to a unit with one or more dispatched PINs And PIN and lock telemetry events occur across multiple sources When a manager opens the job’s Audit Timeline Then a single chronological timeline displays all events merged across sources And each event shows event type, UTC timestamp, local timestamp (unit time zone), actor (vendor/manager/system), source (webhook/poll/API/manual), related PIN, vendor, outcome, and correlation ID And duplicate events with the same lock event ID or idempotency key are de-duplicated And events can be filtered by event type, actor, source, and date range And the first 100 events render within 2 seconds on median workstation hardware And newly arriving events appear on the timeline within 5 seconds of ingestion
PIN Event Coverage and Normalization
Given a dispatched job with an active PIN lifecycle When the system processes events for creation, delivery, view/reveal, activation, usage (successful unlock), failed attempt, revocation, extension, door_open, door_close, and invalid_try Then a normalized audit record is created for each with event_type ∈ {creation, delivery, view, activation, usage, failed_attempt, revocation, extension, door_open, door_close, invalid_try} And each record includes required fields: event_id, job_id, unit_id, pin_id (if applicable), vendor_id (if applicable), event_type, occurred_at (UTC), occurred_at_local (unit TZ), source, outcome, and raw_payload_ref And unknown or unsupported vendor payloads are stored as event_type = unknown with raw_payload_ref preserved and flagged for mapping And revocation and extension records include previous_expiry and new_expiry And usage and failed_attempt records include lock_device_id and attempt_count (if provided) And all records are searchable and filterable within 60 seconds of occurrence
Tamper-Evident Audit Log Integrity
Given audit logging is enabled for the portfolio When audit records are written Then records are append-only and cannot be updated or deleted via UI or API And any attempt to modify or delete returns 405 and creates an integrity_alert audit record And each record contains a digest and previous_digest forming a verifiable hash chain per job timeline And a public verification endpoint returns status = valid for an unaltered timeline and invalid with the first failing record index if tampered And exported timelines include the chain root and per-record digests to enable offline verification
Telemetry Ingestion via Webhooks and Polling
Given a supported smart lock vendor is configured with webhook credentials When the vendor posts a signed webhook for door events Then the system validates signature, ingests the event, responds 2xx within 2 seconds, and records idempotency key to avoid duplicates And events are ordered by device timestamp with detection and correction of clock skew up to ±120 seconds And if webhook delivery fails or is silent for more than 90 seconds, polling activates every 60 seconds until webhooks resume And webhook and polling events for the same device are de-duplicated and merged into a single timeline without gaps
Anomaly Alerts Configuration and Delivery
Given alert rules are configured for after-hours access, early access, and repeated failed attempts per portfolio or job And recipients include managers and owners with selected channels (email, SMS, push) When an access occurs outside the allowed schedule window Then an alert is delivered to all configured recipients within 15 seconds with job, unit, vendor, event time (local and UTC), and deep link to timeline When an access occurs before the scheduled access window start Then an alert is delivered within 15 seconds and the event is labeled early_access on the timeline When failed_attempt events reach or exceed the configured threshold (e.g., 3 in 5 minutes) Then a repeated_failures alert is delivered within 15 seconds and the counter resets after the cooldown window And alerts are de-duplicated for identical conditions within a 5-minute window and support acknowledgement that is recorded on the timeline
Export and API Access to Audit Trail
Given a manager selects a date range, units/jobs, vendors, and event types When exporting the audit timeline Then a downloadable CSV and JSON are generated with schema: event_id, job_id, unit_id, pin_id, vendor_id, event_type, occurred_at_utc, occurred_at_local, actor, source, outcome, device_id, metadata, digest, previous_digest And exports up to 100,000 events complete within 30 seconds and larger exports stream results in chunks And export actions are themselves recorded as audit events with requester, filters, and file checksum And the REST API provides equivalent filtered access with cursor-based pagination, rate limiting (at least 60 req/min), and RBAC ensuring users see only authorized portfolios/units
Dashboards and Filters for Compliance and Analysis
Given a portfolio with active jobs and audit data When a user opens the Access Compliance dashboard Then KPIs display for the selected period: access_success_rate, failed_attempt_rate, avg_time_from_dispatch_to_first_entry, after_hours_access_count, avg_door_open_duration And filters include portfolio, property, unit, vendor, job type, date range, event type, and alert type And applying filters updates widgets and tables within 2 seconds for a 30-day range and within 5 seconds for a 90-day range And clicking any KPI or table row drills down to the pre-filtered job/unit timeline And saved views persist user-defined filters and can be shared with role-based access
Offline & Fallback Access
"As a dispatcher, I want a safe fallback to grant access when a lock is offline so that the job can proceed without compromising security."
Description

Offer secure fallback options when provider APIs or hardware are unavailable: pre-authorized backup static codes with tight scope and auto-rotation, manager-approved reveal with reason capture, and post-incident reconciliation to revoke and rotate. Support tenant-assisted unlock workflows or QR-based on-site verification where applicable. Cache pending actions and sync once connectivity is restored. Clearly label fallback usage in the audit trail and restrict via policy and RBAC to minimize exposure while keeping jobs unblocked.

Acceptance Criteria
Activate Fallback on Provider Outage Under Policy and RBAC
Given an active work order with an associated lockbox And the primary provider API or hardware heartbeat has failed for at least 60 seconds And organization policy permits fallback for this property And the actor has the Fallback Access permission When the actor initiates fallback Then the system displays only the fallback options allowed by policy And blocks continuation if policy forbids fallback And logs the outage detection signal and policy check result And labels the session as Fallback in the UI and audit
Manager-Approved Code Reveal with Reason Capture
Given fallback requires manager approval per policy And the requester is a vendor assigned to the dispatched job When the requester taps Request Code Reveal Then the system prompts for a reason (minimum 10 characters) and incident type selection And sends an approval request to the on-call manager When the manager approves with MFA within 10 minutes Then the code is revealed to the vendor once and auto-masks after 60 seconds And the audit records the requester, approver, reason, timestamps, and a redacted code (last 2 digits only) When the manager rejects or the 10-minute window elapses Then no code is revealed and the audit records the denial or timeout
Pre-Authorized Static Backup Code: Scope, Timebox, Auto-Rotation
Given the job has a pre-authorized static backup code generated at dispatch And the code is scoped to a single property and lockbox And the validity window is from 30 minutes before scheduled start to 60 minutes after scheduled end When the vendor enters the code at the correct lockbox within the window Then entry is granted and the attempt count increments And the code becomes unusable after 3 successful uses or when the job is marked complete, whichever comes first When the job is marked complete or the validity window elapses Then the system auto-rotates the lockbox code within 5 minutes and invalidates the backup And no backup code is reused within 90 days
Offline Caching of Actions and Deferred Sync
Given the mobile device is offline When the vendor retrieves a fallback code or performs an entry Then the app caches the action with immutable timestamp, user, job ID, geolocation if enabled, and a redacted token And prevents editing or deletion of cached records When connectivity is restored Then the app syncs cached records within 2 minutes with de-duplication by client event ID And the audit trail reflects original offline timestamps and displays an Offline indicator
QR-Based On-Site Verification for Entry without Connectivity
Given a property has a FixFlow verification QR posted on-site And the vendor device has no internet connectivity When the vendor scans the QR using the FixFlow app Then the app validates the embedded signed token locally and confirms the vendor is assigned to the active job And verifies a geofence within 50 meters of the property And, if valid and within the job window, reveals a single-use 6-digit local fallback token And masks the token after 60 seconds and stores a signed proof for later sync When the token is used or 10 minutes elapse Then the token expires and is invalidated during reconciliation
Tenant-Assisted Unlock Workflow as Fallback
Given the tenant is opted in for assist and policy allows tenant-assisted unlocks When the vendor requests Tenant Assist Then the tenant receives a one-time approval link by SMS/email valid for 10 minutes When the tenant approves and passes 2FA Then the system triggers a temporary unlock or reveals a temporary code to the tenant to relay And the audit logs tenant identity (hashed), approval method, and timestamps with a Fallback label When the tenant denies or the link expires Then access is not granted and the request is closed with reason recorded
Post-Incident Reconciliation: Revoke and Rotate with Audit Labeling
Given fallback access occurred while offline or during provider outage When connectivity to the provider and hardware is restored Then the system revokes any active fallback tokens and rotates impacted lockbox codes within 5 minutes And sends notifications to the manager and property stakeholders summarizing actions taken And generates an incident reconciliation report listing all fallback events, actors, reasons, timestamps, and outcomes And each audit entry is labeled Fallback with a root-cause tag and is exportable

Guided Mitigation

Provides tenants with step-by-step, property-specific instructions (e.g., shut off water, trip breaker, contain leaks) while help is en route. Reduces damage, calms tenants, and can prevent unnecessary emergency dispatches.

Requirements

Dynamic Mitigation Playbooks
"As a tenant experiencing an urgent issue, I want tailored steps for my specific incident type so that I can stabilize the situation until help arrives."
Description

Configurable library of incident-type playbooks (e.g., active leak, power outage, gas smell) that deliver step-by-step, conditional instructions with media (images, short clips), timers, and safety warnings. Playbooks support branching logic based on tenant responses (e.g., "was the main valve found?") and automatically adapt by property and unit context. Includes an authoring interface, versioning, and multi-language content support. Provides a runtime engine to render steps in the tenant portal and capture completion telemetry for later analysis.

Acceptance Criteria
Active Leak Branching & Safety Acknowledgment
Given an authenticated tenant selects incident type "Active Leak" for a property with a published playbook version When the tenant starts the playbook Then step 1 and a safety warning banner render within 2 seconds on a 3G/4G connection, and the "Next" button remains disabled until the safety acknowledgement checkbox is checked When the tenant answers "Main water shutoff found" on the decision prompt Then the system routes to branch A steps and hides branch B steps When the tenant answers "Not found" Then the system routes to branch B steps including "Locate Shutoff" guidance Then the chosen branch ID and answer are persisted and included in the session telemetry
Step Timers & Auto-Escalation Rules
Given a step configured with a 120-second timer and an escalation rule "Notify Manager: Urgent" if not completed When the step starts Then a visible mm:ss countdown displays and begins immediately When the timer expires and the step is not marked complete Then the system posts an escalation event to the management console within 5 seconds, sends a notification to the on-call contact, tags the session as "Escalated", and displays a next-action message to the tenant When the tenant completes the step before expiry Then no escalation event is sent and the next step unlocks immediately Then telemetry records the timer start/stop times and whether escalation fired
Property & Unit Context Adaptation
Given property-level context includes a main shutoff location image and unit-level context includes a breaker map for unit 5B When the step "Shut off main water" renders for unit 5B Then the instruction text, the property image, and the unit breaker map are displayed, replacing generic content When required context attributes are missing Then the system falls back to generic content and displays a non-blocking "Generic guidance shown" indicator Then the selected context source (unit > property > generic) is recorded in telemetry for each step
Inline Media Rendering & Accessibility
Given a step contains one JPG image (<=1 MB, alt text provided) and one MP4 clip (<=10 MB, 10–20s, H.264, captions provided) When rendered on a mobile device Then the image displays with alt text, the video loads first frame within 2 seconds, plays muted with captions available, and provides Play/Pause and Seek controls When bandwidth is detected below 1 Mbps or autoplay is blocked Then the system defers video autoplay, shows a tap-to-play control, and ensures the static first frame is visible Then media URLs are signed and expire after 15 minutes; access after expiry is denied with a recoverable refresh Then telemetry captures media load time, play duration, and errors per asset
Authoring, Preview, Publish, and Versioning
Given a user with Author role When creating a new playbook "Active Leak" Then the author can add/edit steps with branching conditions, safety warnings, timers, and attach media, and all changes auto-save as a draft version When the author clicks Preview and selects a property and unit Then the preview renders with that context and matches runtime output When the author publishes Then a new immutable version number is assigned, an audit log entry with diff and publisher identity is created, and tenants receive only the latest published version within 60 seconds When a published playbook is superseded Then previous versions remain accessible to admins for 1 year and are not served to tenants
Multi-language Delivery & Fallback
Given a tenant profile has preferred language "Spanish (es)" When the tenant starts a playbook with Spanish content available Then localized step titles, bodies, safety warnings, and media captions display in Spanish When a step lacks Spanish content Then the system falls back to English for that step, shows a "Translated content not available" icon, and allows language switching without losing progress Then localization coverage percentage and per-step fallback events are recorded in telemetry, and tenant-entered inputs are stored with language codes
Telemetry Capture & Analytics Availability
Given a tenant completes a playbook session When each step is completed, skipped, or branched Then the system records events including session ID, playbook version, step ID, branch decision, timestamps, duration, device type, and network quality When the session ends Then 99% of event batches are durable and queryable via the Analytics API within 60 seconds using OAuth 2.0 When an analytics export job is requested for a date range Then a CSV or JSON file is delivered to secure storage with documented schema, excluding or pseudonymizing PII Then telemetry events conform to a versioned schema, and schema validation errors are logged and remain below 0.1% of events per day
Property-Specific Instructions & Asset Mapping
"As a property manager, I want to attach unit-specific shutoff locations and instructions so that tenants can act quickly without guesswork."
Description

Per-property and per-unit configuration of critical assets and instructions (e.g., main water shutoff location, breaker panel map, valve photos, door codes), with fallbacks to building-level and portfolio defaults. Supports image annotations, floorplan pins, and step-localized asset references to reduce confusion under stress. Includes role-based permissions for owners/managers to maintain data, audit logging, and validation to prevent stale or conflicting guidance.

Acceptance Criteria
Unit-Level Overrides With Building/Portfolio Fallbacks
Given a unit has a unit-level instruction for 'main water shutoff', When a tenant starts Guided Mitigation for an Active Leak, Then the unit-level instruction, images, and pins are displayed and building/portfolio defaults are not shown. Given a unit lacks a unit-level instruction but the building has one, When a tenant starts Guided Mitigation for an Active Leak, Then the building-level instruction, images, and pins are displayed. Given both unit and building lack the instruction but a portfolio default exists, When a tenant starts Guided Mitigation for an Active Leak, Then the portfolio default is displayed. Given fallbacks are applied, When instructions are rendered, Then step-localized asset references resolve to the correct scope (unit > building > portfolio) without broken links.
Role-Based Permissions for Instruction Maintenance
Given a user with role Owner or Property Manager and scope including Property X, When they create or update an instruction or asset mapping for Property X, Then the change is saved successfully. Given a Tenant or Vendor user attempts to create, update, or delete any instruction or asset mapping, When the action is submitted, Then the system denies the action with HTTP 403 and no data is changed. Given a Manager whose scope excludes Property Y, When they attempt to edit instructions for Property Y, Then the system denies the action with HTTP 403. Given any successful create, update, or delete by an authorized user, When the operation completes, Then the system records the actor, role, and scope in the audit log.
Validation to Prevent Stale or Conflicting Guidance
Given an instruction tagged as critical (e.g., water shutoff, breaker panel), When its last_verified_at date is older than 12 months, Then the system blocks publishing it as Active and displays a verification required error. Given a unit already has an Active 'main water shutoff' asset mapping, When a second Active mapping for the same unit and asset type is submitted, Then the system rejects the save with a single active mapping per unit error. Given a building-level instruction exists for 'main water shutoff', When a unit-level override is created, Then the system requires an override_reason and marks the building-level instruction as superseded for that unit. Given duplicate or contradictory steps are detected within the same scope (identical step titles with conflicting content), When saving, Then the system flags the conflict and requires resolution before publish.
Image Annotations, Floorplan Pins, and Step-Localized References
Given a manager uploads an asset photo, When they add at least two hotspot annotations with labels, Then tenants see tappable hotspots that reveal the labels on tap. Given a floorplan is available for a unit, When the manager drops a pin and links it to an asset record, Then tenants see the pin on the floorplan and tapping it opens the linked asset photo and details. Given an instruction step contains a reference token to an asset (e.g., [asset:water_shutoff]), When the step is rendered for a tenant, Then the token resolves to the unit-specific asset (or fallback) and displays the associated photo and/or floorplan pin inline. Given an asset reference cannot be resolved at any scope, When the step is rendered, Then the system hides the unresolved token and shows a generic safe action note without broken placeholders.
Immutable Audit Trail for Instruction and Asset Changes
Given any create, update, or delete to instructions or asset mappings, When the change is committed, Then an audit entry is written with user_id, role, action, timestamp (UTC), scope (unit/building/portfolio), before_state, after_state, and reason (if provided). Given an audit entry exists, When any user attempts to modify or delete it, Then the system prevents the change and returns an immutable log error. Given a property admin views the audit trail for Unit U, When they filter by date range and action type, Then only matching entries are returned in descending timestamp order within 2 seconds for up to 5,000 entries. Given the audit service is unavailable, When a write is attempted, Then the system rejects the originating change with a 503 error and no data mutation occurs, ensuring no changes occur without an audit record.
Mitigation Readiness Checklist at Unit Onboarding
Given a manager attempts to mark a unit as Mitigation Ready, When required critical assets (main water shutoff location + photo/annotation, breaker panel location + labeled photo, primary entry method/door code if applicable) are missing, Then the system blocks readiness with a checklist of missing items. Given all required assets exist with at least one image annotation or floorplan pin each, When the manager marks the unit as Mitigation Ready, Then the status updates successfully and the timestamp and actor are recorded. Given a unit was previously marked Mitigation Ready, When any linked critical asset becomes stale (last_verified_at > 12 months), Then the system automatically flags the unit as Needs Review and notifies the assigned manager. Given portfolio-level defaults were used to satisfy readiness, When a unit-level asset is later added, Then the system switches references to the unit-level asset and clears the default usage indicator for that unit.
Tenant Guided Step-by-Step Flow
"As a tenant on my phone during an emergency, I want a simple, clear checklist to follow so that I can safely perform mitigation without feeling overwhelmed."
Description

Mobile-friendly, accessible (WCAG 2.2 AA) guided checklist that progressively reveals steps, confirms completion/skip, and surfaces contextual warnings. Supports rich media, large tap targets, readable typography, multilingual text, and haptic/visual cues for urgency. Provides quick-access safety actions (call 911, call manager), and captures confirmations, notes, and media from tenants. Deep-linkable from alerts and the FixFlow tenant portal with automatic authentication tokens.

Acceptance Criteria
Progressive Step Reveal, Completion, and Skip
Given a tenant opens a guided flow with multiple steps When the flow loads Then only the first step is expanded and subsequent steps are collapsed Given a step is marked Complete When the completion is confirmed Then the next step auto-expands and the progress indicator updates to reflect completed/total counts Given a step is marked Skip When the tenant provides an optional reason and confirms skip Then the step is recorded as Skipped with a timestamp and the next step auto-expands Given network interruption occurs after a completion or skip action When the app reconnects Then the local state is synced and previously completed/skipped steps persist without duplication Given the tenant navigates back to a prior step When they reopen it Then previous inputs (confirmations, notes, media) are pre-populated and editable Given the tenant attempts to mark a future step complete When preceding steps are incomplete Then the action is blocked and a message explains prerequisite steps Given the tenant reloads the page or returns via the same link When the checklist is reopened Then the flow resumes at the last active step
Contextual Warnings and Urgency Cues
Given the incident type is an active water leak When the tenant opens the checklist Then a high-severity warning banner with icon and red accent is displayed at the top with an ARIA live announcement and device haptic feedback where supported Given a step has a hazard tag (e.g., electrical) When the step is expanded Then a contextual warning appears before actionable controls and requires explicit acknowledgment (checkbox) before proceeding Given the tenant acknowledges a warning When they proceed Then the acknowledgment is logged with timestamp and user agent Given the tenant device does not support haptics When a high-severity warning is shown Then a visual pulse animation is used instead without blocking interaction Given a warning condition ceases (e.g., leak contained step is completed) When the next step opens Then the high-severity banner reduces to an info state
Quick-Access Safety Actions (911 and Manager)
Given the checklist is loaded When the tenant taps Call 911 Then the app triggers a tel:911 intent with a confirmation dialog and logs the attempt without storing PII beyond a timestamp Given the checklist is loaded and the property manager phone is configured When the tenant taps Call Manager Then the app triggers a tel:<manager_number> intent with a confirmation dialog and logs the attempt Given no telephony capability is detected (e.g., tablet without SIM) When the tenant taps a call action Then the app displays the number with a copy-to-clipboard option instead of starting a call Given the screen is scrolled When the tenant needs safety actions Then a persistent, accessible safety bar remains visible or reachable within one action (e.g., FAB) with a minimum 44x44 CSS pixel target size Given a tenant opens the checklist via deep link without prior sign-in When the page loads Then safety actions are available before any sign-in prompts
Rich Media and Notes Capture per Step
Given a step requests media When the tenant taps Add Photo/Video Then they can capture from camera or upload existing files (jpg, png, heic, mp4) up to 50 MB total per step and see upload progress Given media uploads complete When thumbnails are shown Then the tenant can remove or annotate each media item before submitting the step Given a slow or intermittent network When an upload fails Then the app retries up to 3 times with exponential backoff and provides a manual retry option Given the tenant adds a text note When they save the step Then the note (up to at least 1,000 characters) is stored and displayed with the step Given the step is submitted When the backend acknowledges Then the UI shows a success toast and the media and notes are attached to the incident timeline
WCAG 2.2 AA Accessibility and Mobile Usability
Given the checklist is viewed on a 320px wide device When the tenant scrolls and interacts Then no horizontal scrolling is required and all interactive targets are at least 44x44 CSS pixels Given the tenant uses a screen reader When navigating the checklist Then step titles, statuses, buttons, warnings, and progress are announced with correct roles, names, and states; non-text content has alt text; dynamic changes use appropriate aria-live regions Given the tenant zooms text to 200% When interacting with steps Then content remains readable and functional without overlap or truncation; line-height is at least 1.5; contrast is at least 4.5:1 for text and 3:1 for large text/icons Given keyboard-only navigation When tabbing through controls Then focus order is logical and visible, with no keyboard traps and all actions reachable Given motion/haptic sensitivity is enabled in OS settings When urgent cues would play Then reduced-motion is honored and haptics are suppressed while providing equivalent visual cues Given session timeouts may occur When the tenant is inactive Then no automatic logout happens within at least 20 minutes while completing a step; if a timeout occurs, entered data is preserved
Multilingual Rendering and Locale Switching
Given the tenant’s locale is included in the deep-link token (e.g., es-ES) When the checklist opens Then all UI strings and step content render in that locale (e.g., Spanish) with the same layout and no truncation Given the tenant changes language via a selector When a different language is chosen Then the UI switches immediately without full reload and persists the choice for subsequent visits Given a translation is missing When rendering a string Then the app falls back to English and logs the missing key for telemetry Given a right-to-left language is selected (e.g., Arabic) When the checklist renders Then the layout mirrors correctly and numerals/dates are localized appropriately; icons/punctuation align for RTL Given step content includes dynamic values (dates, numbers) When rendered Then values are formatted for the selected locale
Deep-Link Authentication to Tenant Checklist
Given a tenant receives an alert with a signed magic link token When they tap the link on a mobile device Then they are automatically authenticated and taken directly to the corresponding incident’s active checklist without additional login Given the token is expired (older than 30 minutes) or already used When the link is opened Then access is denied with a clear message and a button to request a new link Given the token is for a different unit or user When validation runs Then access is denied and the event is logged; no incident details are disclosed Given the link is opened in a different browser or device When the token is valid Then authentication succeeds and the checklist opens as expected Given authentication completes When the checklist loads Then the token is removed from the URL via history.replaceState and is not persisted in storage
Safety Rules & Emergency Escalation
"As a property manager, I want the system to detect hazardous conditions and escalate appropriately so that tenant safety and property risk are managed in real time."
Description

Real-time rules engine that evaluates tenant inputs (e.g., smell of gas, visible electrical sparks, water above threshold) to trigger safety-first instructions (evacuate, avoid electronics), override non-critical steps, and escalate to emergency services or on-call protocols. Generates immediate alerts to managers and vendors with severity classification and provides a defensible audit trail of decisions and timestamps.

Acceptance Criteria
Gas Leak Emergency Escalation
Given a tenant selects "Gas leak / smell" during intake and provides property, unit, and contact details When the report is submitted Then the system classifies severity as Critical within 3 seconds And displays instructions to evacuate immediately and avoid using electronics or switches And suppresses all non-critical guided steps And initiates the configured emergency services escalation workflow And sends alerts via push, SMS, and email to the property manager and on-call team with severity, property, unit, and incident ID within 10 seconds And records an audit log entry capturing input values, matched rule IDs, decisions taken, alert recipients, and timestamps for each action
Electrical Sparks Safety Override
Given a tenant selects "Electrical - visible sparks/overheating" and does not indicate active fire When the report is submitted Then the system classifies severity as Critical within 3 seconds And displays instructions to move away from the source and shut off the main breaker if safe And suppresses non-critical guided steps And auto-dispatches the preferred electrician with high priority And sends alerts via push, SMS, and email to the property manager and dispatched vendor within 10 seconds And records an audit log entry capturing input values, matched rule IDs, decisions taken, dispatch details, and timestamps
Severe Water Leak Mitigation and On-Call Dispatch
Given a tenant reports an active water leak and indicates pooling water at or above the configured threshold When the report is submitted Then the system classifies severity as High within 3 seconds And displays property-specific instructions to shut off the main water valve and contain the leak And suppresses non-critical guided steps And auto-dispatches the preferred plumber with high priority And sends alerts via push, SMS, and email to the property manager and dispatched vendor with severity, property, unit, and incident ID within 10 seconds And records an audit log entry capturing input values, matched rule IDs, decisions taken, dispatch details, and timestamps
Non-Critical Issue No Escalation
Given a tenant selects a non-critical issue (e.g., slow drain, dripping faucet) with no safety indicators When the report is submitted Then the system classifies severity as Low within 3 seconds And displays standard guided mitigation steps without suppression And does not trigger emergency services or on-call dispatch And creates a work order scheduled for the next business window And notifies the property manager via in-app inbox and email within 10 seconds And records an audit log entry capturing input values, matched rule IDs, decisions taken, and timestamps
Decision Audit Trail Completeness and Accessibility
Given any incident triggers the rules engine When classification, instruction display, alerting, or dispatch actions occur Then the system records for each action the actor or system ID, input values, matched rule IDs, decision outcome, recipients, and precise timestamps And prevents edits to historical audit entries while allowing new appended entries And allows authorized managers to view the complete audit trail from the incident detail within one click And allows export of the audit trail to PDF and CSV including timestamps and decision rationale
Alert Deduplication and Update Policy
Given multiple submissions for the same unit and incident indicators occur within a 5-minute window When the rules engine evaluates the subsequent submissions Then the system does not send duplicate initial alerts or create duplicate dispatches And instead appends an incident update with the new details to existing stakeholders And preserves a single incident ID across updates And records audit log entries for deduplication decisions and update notifications with timestamps
Triage and Vendor Handoff Integration
"As a dispatcher or vendor, I want mitigation outcomes and media attached to the work order so that I can arrive prepared with the right parts and avoid repeat visits."
Description

Seamless linkage between Guided Mitigation and FixFlow’s smart triage and dispatch: attach mitigation status, step outcomes, tenant media, and timestamps to the work order; update priority/SLA; and notify preferred vendors with context to reduce repeat visits. Supports one-click manager approvals when mitigation meets criteria and auto-inserts recommended line items based on completed steps.

Acceptance Criteria
Mitigation Data Attached to Work Order on Triage Completion
Given a tenant completes Guided Mitigation steps and the triage session is closed for a maintenance issue linked to a work order When the triage engine finalizes the outcome Then the linked work order is updated with mitigation_status in {started, in_progress, completed, aborted} And the work order includes step_outcomes[] with step_id, status in {success, failed, skipped}, notes, media_ids[], started_at, completed_at And tenant_media[] is attached with media_id, type in {photo, video, audio}, uploaded_at And mitigation_started_at, mitigation_completed_at, and triage_completed_at are stored in ISO 8601 UTC And the work order activity feed shows a "Guided Mitigation" entry summarizing status and number of steps completed And GET /work-orders/{id} returns these fields and all media URLs are accessible to manager and assigned vendor roles only
Auto-Update Priority and SLA From Mitigation Outcomes
Given property-specific triage rules are configured and the work order belongs to that property When mitigation outcomes are posted to the work order Then the work order priority is recalculated per rules and updated within 5 seconds (p95) And the SLA target datetime is recalculated and saved to work_order.sla_target_at And a history entry records previous_priority, new_priority, previous_sla_target_at, new_sla_target_at, rule_id, and timestamp And if no change results from recalculation, no new history entry is created
Contextual Vendor Notification With Media and Timeline
Given a preferred vendor is selected or auto-assigned by triage When the handoff occurs or a manager approves dispatch Then the vendor receives a notification via email and in-portal containing property address, contact policy, mitigation summary, steps completed/pending, SLA target, and secure media links And the vendor portal displays the mitigation timeline with step statuses and media thumbnails And media links are accessible to the vendor without tenant login and expire after 7 days And notification delivery status is tracked (queued, sent, delivered, failed) with up to 3 automatic retries on failure
One-Click Manager Approval When Mitigation Gates Satisfied
Given mitigation gates for the issue type are satisfied per configuration When a manager clicks "Approve Dispatch" on the work order Then the work order status transitions to Approved and the preferred vendor is dispatched without additional forms And the approval event is logged with approver_id, timestamp (ISO 8601 UTC), and pre-approval summary snapshot And the Approve button is disabled with a tooltip explaining unmet gates when criteria are not satisfied And the approval action is idempotent across duplicate clicks and completes within 1 second (p95)
Auto-Insert Recommended Line Items Based on Completed Steps
Given mapping rules exist from mitigation step_ids to billable line items When mitigation step outcomes are received Then recommended line items are added to the work order draft estimate with code, description, quantity, and unit cost populated And no duplicate line items are created for the same step_id And managers can edit or remove recommended items prior to dispatch And each inserted item records rule_id and source_step_id in metadata for auditability
Idempotent Handoff and Complete Audit Trail
Given network retries or duplicate payloads from triage may occur When identical mitigation payloads with the same triage_session_id and version are received Then the system updates the work order exactly once and does not create duplicate activity entries, notifications, or line items And all handoff events, rule evaluations, notifications, approvals, and field changes are recorded with ISO 8601 UTC timestamps, actor, and correlation_id And the audit log can be filtered by work_order_id and triage_session_id to reconstruct the sequence of actions And all timestamps pass integrity checks (no future-dated entries, monotonic order for same correlation_id)
Offline & Low-Bandwidth Operation
"As a tenant with weak or no internet, I want the instructions to still work so that I’m not blocked from taking urgent actions during an emergency."
Description

Resilient delivery of guidance under poor connectivity via prefetching lightweight text-first steps, local caching, graceful image/video degradation, and queued telemetry uploads with retries. Includes SMS fallback for critical instructions and links, with automatic resume when connectivity returns. Ensures essential safety steps are always accessible during time-sensitive events.

Acceptance Criteria
Offline Access to Critical Steps During Water Leak
Given the tenant has an active "Water Leak" mitigation flow And the device has no internet connectivity And property-specific steps were cached within the last 30 days When the tenant opens the mitigation flow Then the app renders the first 5 critical steps as text within 1 second from local cache And images and videos are replaced by alt-text captions and file-size placeholders And no blocking error dialog is shown And an "Offline Mode" banner is visible And the displayed content version matches the cached version timestamp and is no older than 30 days
Background Prefetch of Property-Specific Mitigation Steps
Given the tenant has authenticated and opened the home screen And measured bandwidth >= 1 Mbps or RTT <= 200 ms When the app is idle for 1 second Then it prefetches text-first steps for the top 10 incident types for the tenant’s property And the total prefetch payload is <= 500 KB And prefetch completes within 3 seconds on a 1 Mbps network And items are stored in encrypted local storage with version and expiry (30 days) And if bandwidth is below threshold, only critical incident types (Gas, Electrical, Active Leak) are prefetched with total payload <= 150 KB
Adaptive Media Degradation on Constrained Network
Given the device network is 2G-equivalent (throughput < 100 kbps or RTT > 1000 ms) When a mitigation step containing media is displayed Then images are deferred or delivered as low-res progressive JPEGs <= 30 KB each, max-width 480 px And videos are replaced by a “Load video (est. size)” control and are not auto-loaded And the step remains interactive within 500 ms of navigation And total auto-loaded payload per step does not exceed 100 KB
Automatic SMS Fallback for Critical Instructions
Given the tenant has a verified mobile number And the app cannot reach the API for 5 consecutive requests over 5 seconds And the tenant selects a critical incident type (Gas Leak, Electrical Hazard, Active Water Leak) When offline is confirmed Then the system sends an SMS containing the top 3 safety steps and a short link to the full flow within 5 seconds And the SMS is deduplicated via an idempotency key for 10 minutes And retries are attempted up to 3 times with exponential backoff (2s, 4s, 8s) And STOP and HELP keywords are honored automatically And the app records the send attempt in the local queue for later reconciliation
Store-and-Forward Telemetry Queue
Given the device is offline or experiencing intermittent connectivity When the tenant completes, skips, or views a step Then an event is written to a durable local queue with timestamp, step id, action, and hash signature And the queue persists across app restarts and OS process kills And the queue caps at 1 MB; when full, non-critical view events are dropped before action events, oldest first And upon reconnection, events are uploaded in order within 10 seconds, with exponential backoff up to 5 attempts And successful uploads are removed from the queue; failures surface a non-blocking toast only after all retries fail
Seamless Session Resume After Reconnect
Given the tenant progressed through steps while offline And network connectivity returns When the tenant reopens the mitigation flow or returns focus to it Then the app syncs progress within 3 seconds And previously completed steps are marked as synced without duplicating server records And the tenant is returned to the last incomplete step And conflicts between local and server progress are resolved by last-write-wins using timestamps with 1-second precision, logged in audit trail
Secure Cache and Eviction Policy
Given mitigation content is cached locally When the tenant logs out, changes properties, or 30 days elapse (whichever comes first) Then cached content and telemetry are purged within 2 seconds of the trigger And cached content is encrypted at rest using OS keystore-bound keys (AES-256-GCM) And cache reads fail closed if decryption fails or integrity check does not match And PII is not cached; only step content, identifiers, and minimal session metadata And a manual “Clear offline data” control is available in Settings and completes within 2 seconds
Mitigation Analytics & Feedback Loop
"As an operations lead, I want visibility into which steps are effective and where tenants get stuck so that we can improve playbooks and reduce damage and unnecessary dispatches."
Description

End-to-end analytics capturing step completion rates, time-to-first-action, escalation frequency, and damage-prevented proxies (e.g., water runtime reduced). Provides dashboards for managers, cohort comparisons by property/incident type, and feedback collection from tenants and vendors to improve playbooks. Supports A/B testing of step wording and ordering, with privacy safeguards and data retention controls.

Acceptance Criteria
Core Metrics Capture & Latency for Guided Mitigation
Given a tenant starts a Guided Mitigation flow for any incident type When the tenant views a step Then the system emits a step_viewed event with incident_id, step_id, variant_id (if applicable), user_role=tenant, timestamp_ms, and device_type And when the tenant completes a step Then the system emits a step_completed event with the above fields plus duration_ms and success=true; if skipped or failed, success=false with reason_code And when the tenant triggers an escalation action Then the system emits an escalation_initiated event with incident_id, escalation_type, channel, and timestamp_ms Then all events are persisted to the analytics store within 5 seconds at p95 And duplicate events are deduplicated via an idempotency_key within a 24-hour window And offline clients buffer events and sync successfully within 1 hour of reconnection with original timestamps preserved
Damage-Prevented Proxy: Water Runtime Reduction
Given an incident is labeled as Water Leak and includes a 'Shut off water' mitigation step When the tenant completes the shutoff step or an IoT meter reports valve_closed Then water_off_ts is recorded for the incident And when created_ts and water_off_ts are present Then runtime_minutes = max(0, (water_off_ts - created_ts) / 60,000) is computed per incident Then the dashboard displays median, p75, and p95 runtime_minutes by property and incident type for a selected date range And if water_off_ts is missing Then the incident is excluded from aggregates and flagged in a data quality report And when meter data is used Then the metric is labeled 'metered'; otherwise labeled 'self-reported'
Manager Dashboard: KPIs & Filters
Given a manager with portfolio access opens the Analytics Dashboard When they select a date range (up to 180 days), property set, and incident types Then the dashboard renders within 3 seconds at p90 the KPIs: step completion rate, time-to-first-action, escalation rate, and water runtime proxy And each KPI supports drill-down to an incident list with columns: incident_id, property, incident_type, created_ts, first_action_ts, escalated (Y/N), variant_id And the dashboard supports timezone selection applied consistently to all timestamps And data for the current view can be exported as CSV within 10 seconds for up to 10,000 incidents
Cohort Comparison by Property and Incident Type
Given a manager defines two or more cohorts by property groups and/or incident types When the comparison is applied Then side-by-side charts and statistics for step completion rate, time-to-first-action, escalation rate, and water runtime proxy are rendered And the system enforces a minimum cohort size of 50 incidents per selected period; smaller cohorts are marked 'insufficient data' and excluded from comparisons And cohort definitions can be saved, named, and reloaded with identical filters and segments
Feedback Loop from Tenants and Vendors
Given an incident with Guided Mitigation is closed When 0–2 hours elapse post-closure Then the tenant is sent a survey via their last-used channel with: usefulness (1–5), step clarity (1–5), and optional free-text And the assigned vendor is sent a survey with: steps followed (Yes/No), time saved estimate (minutes), and optional free-text Then responses are stored with incident_id, property_id, step_ids referenced (if any), respondent_role, and timestamp And the dashboard aggregates tenant usefulness and vendor compliance by property and incident type with filterable distributions And if no response is received within 48 hours Then exactly one reminder is sent; no further reminders are sent
A/B Testing Framework for Step Wording and Ordering
Given an experiment is created with experiment_id, variants (A, B, ...), target incident types, and allocation ratios When an eligible tenant starts the mitigation flow Then they are randomly assigned to a variant per allocation and the assignment persists for that incident across sessions And variant_id is recorded on all emitted events for that incident Then the experiment results view reports per-variant metrics: step completion rate, time-to-first-action, escalation rate, and water runtime proxy with date filters And experiments can be paused or ended; upon pause/end, new tenants receive control and results are archived and immutable for the configured period
Privacy Safeguards & Data Retention Controls
Given analytics events are collected Then PII is excluded or pseudonymized: incident_id and user identifiers are hashed; no raw emails or phone numbers are stored in analytics And analytics data are encrypted in transit (TLS 1.2+) and at rest (AES-256 or cloud-managed equivalent) And each organization can configure a retention period between 90 and 730 days; upon expiry, events are hard-deleted within 7 days and the deletion is audit-logged And tenants can opt out at incident start; when opted out, only a single opt_out event is recorded and no further analytics are collected while functionality remains unaffected And admins can request deletion for a specific incident; associated analytics are deleted within 24 hours and the action appears in an immutable audit log

Morning Digest

A ready-to-act summary of overnight incidents with photos, timelines, vendor notes, costs, and exceptions. One-click approvals and follow-ups let teams review, codify learnings, and hand off seamlessly to daytime operations.

Requirements

Overnight Incident Aggregation
"As a property manager, I want all overnight incidents consolidated with photos and timelines so that I can review a single accurate thread per issue in the morning."
Description

Aggregate and normalize all overnight incidents reported between configurable cutoff times (e.g., 7pm–7am) across FixFlow intake channels (photo-first portal, SMS, email, IVR). Deduplicate related reports, associate tenant, unit, property, and existing work orders, and attach all submitted media and timestamps to produce a single coherent incident thread. Enforce data freshness SLAs and resilience with retries. Expose a consolidated data model for the Morning Digest generator and downstream analytics.

Acceptance Criteria
Aggregate Incidents Between Configurable Cutoff Times
Given a property timezone of America/New_York and a cutoff window of 19:00–07:00 When incidents arrive between 19:00:00 and 06:59:59 local time Then they are included in the overnight aggregation for that property Given incidents arrive at or after 07:00:00 local time Then they are excluded from the prior night's aggregation and queued for the next window Given a Daylight Saving Time transition occurs during the window When the window crosses the DST change Then inclusion is based on local wall time with no incident lost or double-counted Given a per-property cutoff override is updated before the window opens When the next window starts Then the new cutoff applies without requiring a deploy and is recorded in the run config Then the aggregation run is labeled with window_start, window_end, timezone, and run_id
Ingest and Normalize Across All Intake Channels
Given an incident is submitted via portal, SMS, email, or IVR When it is received by the system Then a normalized incident record is created with fields: source_channel, tenant_id, unit_id, property_id, contact, message, media_refs[], timestamps (captured_at, received_at) Given only a phone number or email is present When a matching tenant exists Then tenant, unit, and property are auto-resolved; otherwise unresolved_contact=true is set Given an email parse error or audio transcription failure occurs When parsing fails Then parse_status=failed, error_code is set, and the record is eligible for retry Then all timestamps are stored in UTC and local_time with timezone for auditability
Deduplicate Related Reports Into Single Incident Thread
Given multiple reports for the same unit within a 2-hour sliding window with similarity score >= 0.8 or identical media hash When aggregation runs Then they are merged into a single incident thread with a canonical incident_id Then the thread retains ordered child_event_ids referencing the original source messages and channels Given two reports from different units or similarity < 0.5 Then they are not merged Given the same payload is re-delivered or aggregation is re-run Then the merge is idempotent and the incident_id remains unchanged Then a dedup_decision with rationale, similarity metrics, and matched features is stored for each merge
Associate Tenant, Unit, Property, and Existing Work Orders
Given an aggregated incident Then tenant_id, unit_id, and property_id are populated and validated against master data; violations set validation_status=failed with details Given an open work order exists for the same unit and category within the last 7 days When aggregation runs Then work_order_id is linked with link_reason=match_rule Given multiple candidate work orders are found Then the highest-confidence match is linked and others listed as secondary_candidates[] with scores Given no suitable work order exists Then work_order_id remains null and link_reason=no_match Then all associations record matched_at (UTC), matched_by (system/rule), and are auditable
Attach Media and Preserve Timestamps
Given media (photos, audio, video) are submitted across channels When aggregation completes Then all media are attached to the incident thread and de-duplicated by content hash Then each media item stores captured_at (if available), received_at, source_channel, mime_type, size, and checksum Then originals are retained and web-ready derivatives are generated (e.g., resized image, transcript) with processing_status per item Given any media processing fails Then the incident still aggregates, the item is marked processing_status=failed with error_code, and a retry is scheduled
Data Freshness SLA and Resilient Retries
Given the overnight window closes Then 95% of incidents are aggregated and available to Morning Digest within 2 minutes and 100% within 5 minutes, measured per property per day Given transient ingestion or dependency errors occur When retries are attempted Then exponential backoff with jitter is applied for at least 3 attempts within 10 minutes before dead-lettering Then dead-lettered items are visible in an operations queue with reason, next_action, and reprocess capability Then SLA compliance metrics are emitted as structured logs and time-series metrics tagged with window_id and property_id
Expose Consolidated Data Model to Morning Digest and Analytics
Given a request by the Morning Digest generator at or after 07:05 local time Then a queryable API or data store view returns incident threads for the window with fields: incident_id, thread_events[], tenant/unit/property refs, work_order_id, media, exceptions, costs (if any), and timestamps Then the schema is documented with versioning and changes are backward-compatible (additive fields) Then each incident thread includes lineage: source_message_ids[], dedup_decisions[], processing_status, and retry history Given an analytics export job runs daily Then it can extract the same data in columnar format with PII flags and data retention tags
One-Click Approvals & Follow-ups
"As a property manager, I want one-click approvals and follow-ups in the digest so that I can take immediate action without opening multiple screens."
Description

Embed actionable controls in each digest item to approve vendor dispatch, pre-approve spend up to policy thresholds, request missing information from tenants/vendors, escalate to on-call, assign to a teammate, or snooze for day review. Enforce role-based permissions, budget policies, and idempotency. Surface contextual confirmations and instantly persist actions to FixFlow workflows (work orders, tasks, notifications) and vendor integrations, updating timelines in real time.

Acceptance Criteria
Approve Vendor Dispatch With Role & Policy Checks
Given a digest item with a triaged category and a preferred vendor available And the user has role Property Manager with permission APPROVE_DISPATCH When the user clicks Approve Dispatch Then a confirmation modal displays vendor, ETA window, estimated cost, and applicable policy And the Approve action is enabled only if budget policy and role allow dispatch And on confirm, a vendor dispatch request is created on the related work order And a task "Vendor En Route" is added and notifications are sent to vendor and tenant And the digest item shows status Dispatched within 2 seconds And the work order timeline records the approval event with user, timestamp, and policy reference
Pre-Approve Spend Up To Policy Threshold
Given a property budget policy with spend_threshold and role-based limits configured And the digest item has an estimated cost When the user clicks Pre-Approve Spend and enters an amount Y Then validation enforces Y <= property spend_threshold and within the user’s role limit And if validation fails, the action is blocked with an inline error explaining the violated rule And on confirm, a budget hold of Y is created on the work order with expiry per policy And the vendor integration receives the pre-approval limit Y via API within 5 seconds And the timeline logs Pre-Approval with amount, policy, user, and expiry
Request Missing Information From Tenant Or Vendor
Given a digest item missing required fields per category (e.g., access window, shutoff photo) When the user clicks Request Info and selects Tenant or Vendor with required prompts Then the system sends a request via SMS/email containing a secure link to submit text/photos And submissions are attached to the work order conversation thread and media gallery And the digest item shows a Waiting for Info badge with due-by time per policy And when all required fields are received before due-by, the badge clears and Approve actions are re-enabled And the timeline records the request, reminders, and each submission with actor and timestamp
Escalate To On-Call With Audit Trail
Given an on-call schedule is configured for the portfolio When the user clicks Escalate to On-Call and enters a note Then the current on-call user is paged via configured channels (push/SMS/voice) within 60 seconds And a high-priority task is created and assigned to the on-call user And delivery receipts and acknowledgement are captured and displayed And the timeline logs Escalation with note, delivery status, and acknowledgement timestamp And if no on-call is configured, the action is blocked with a guided error to configure coverage
Assign To Teammate With Permissions
Given the user has permission ASSIGN and the teammate T is within portfolio scope When the user assigns the digest item to T with a due time Then the item appears in T’s My Queue within 5 seconds with the selected due time And T receives a notification via the team’s default channel And if T lacks required permissions for the action category, assignment is blocked with an explanatory error And the timeline records Assignment with assigner, assignee, due time, and reason (optional)
Snooze For Day Review With Auto-Resurface
Given a digest item is visible in Morning Digest When the user clicks Snooze and selects a snooze-until time Then the item is hidden from default digest views until snooze-until And a reminder notification is sent at snooze-until And if an SLA breach would occur during snooze, the system alerts the user and requires confirm to proceed And the item auto-unsnoozes on first event update or at snooze-until, whichever comes first And the timeline records Snoozed and Unsnoozed events with user and timestamps
Idempotency And Real-Time Timeline Updates
Given any action (approve dispatch, pre-approve spend, request info, escalate, assign, snooze) is triggered When duplicate clicks occur within 60 seconds or the client retries due to network errors Then the backend deduplicates using an idempotency key per item-action and returns the original result And no duplicate tasks, dispatches, notifications, or budget holds are created And all clients reflect the final action state within 2 seconds via real-time sync And vendor integrations receive at most one API call per idempotency key And the timeline shows a single canonical event with a retry_count metric
Smart Triage Summaries
"As an operations lead, I want smart triage summaries with severity and risks so that I can prioritize the most critical incidents first."
Description

Auto-generate concise triage cards per incident with severity score, SLA risk, recurrence detection, impacted unit/property, current status, photos, vendor notes, costs-to-date, and recommended next actions. Combine rules (after-hours categories, water/electrical priority) with machine-learned scoring, and surface exception flags for missing photos, policy violations, or cost overages. Provide deep links to the full work order and prior history.

Acceptance Criteria
Auto-Generated Triage Card Completeness
Given 1–50 incidents created within the digest window, When the Morning Digest loads, Then a triage card is auto-generated for 100% of those incidents And each card displays: severity_score (integer 0–100), sla_risk (On Track | At Risk | Breached), recurrence (Yes | No), impacted unit and property names, current_status (New | Dispatched | Vendor En Route | In Progress | On Hold | Completed), 1–3 photo thumbnails if photos exist, the most recent vendor note with timestamp, costs_to_date in local currency with 2 decimals, and 1–3 recommended_next_actions And the first card renders within 2 seconds of page load and all cards render within 5 seconds for up to 50 incidents
Severity Score and SLA Risk Calculation
Given an incident with a computed severity_score, Then the score is an integer between 0 and 100 and a severity_label is shown using thresholds: 0–24 Low, 25–49 Medium, 50–74 High, 75–100 Critical Given an incident with an SLA due_at timestamp, When comparing now() to due_at, Then sla_risk is On Track if remaining_time >= 120 minutes, At Risk if remaining_time between 1 and 119 minutes, and Breached if remaining_time <= 0 And both severity_label and sla_risk badges are visible on the triage card and match the above rules for at least one example of each state in test data
Rule-Enhanced Priority for After-Hours Water/Electrical Incidents
Given an incident categorized as one of {Water Leak, Flooding, No Water, Electrical Hazard, Power Outage} and created between 18:00 and 08:00 local time, When computing final severity, Then apply a +25 rule boost capped at 100 using severity_final = min(100, severity_ml + 25) And for incidents not in those categories or created between 08:01 and 17:59, Then no rule boost is applied (severity_final = severity_ml) And the triage card displays severity_final and a tooltip or note indicating that a rules boost was applied when applicable
Recurrence Detection and History Linking
Given an incident for unit U and category C, When there are N >= 1 prior incidents for (U, C) in the last 30 days, Then the triage card shows a recurrence flag "Repeat (N in 30d)" and a deep link to the filtered history list for (U, C) And when there are no prior incidents in the last 30 days, Then recurrence is shown as "No" and no history count badge is displayed And clicking the recurrence history link navigates to a list pre-filtered to unit U, category C, and the last 30 days
Exception Flagging for Missing Photos, Policy Violations, and Cost Overages
Given an incident with zero photos at intake, Then the triage card displays an exception badge "Missing Photos" Given an after-hours dispatch occurred and no approval record exists for the incident, Then the triage card displays an exception badge "Policy Violation: Unapproved After-Hours Dispatch" Given costs_to_date exceed approved_amount by >= 10% and >= $50 equivalent, Then the triage card displays an exception badge "Cost Overage" with the overage amount And multiple exceptions display as separate badges And when the underlying condition is resolved (e.g., a photo is uploaded, approval recorded, or costs corrected), Then the corresponding badge is removed within 60 seconds of data refresh
Recommended Next Actions and One-Click Controls with Status Update
Given a triage card with severity_label Critical or sla_risk At Risk/Breached, Then recommended_next_actions include at least one of {Approve Vendor, Escalate} Given a triage card with a "Missing Photos" exception, Then recommended_next_actions include "Request Photos" When a one-click action is invoked, Then the system sends a request to the backend, shows a success confirmation on 2xx within 2 seconds, updates the card status/fields accordingly, and writes an audit log entry with actor, timestamp, and action And on non-2xx, Then an error is displayed and no state changes are persisted in the UI
Deep Links to Work Orders and Prior History Integrity
Given a triage card, Then it contains a "View Work Order" deep link that navigates to the corresponding work order route with the correct work_order_id and receives HTTP 200 And it contains a "View History" deep link that navigates to a history view pre-filtered to the impacted unit/property and category And both links preserve the user session (no unexpected 401/403) and resolve within 2 seconds on a typical network And link targets open with the correct context (work order header shows the same incident ID as the triage card)
Timezone-Aware Digest Delivery
"As a team member across time zones, I want the digest delivered at a local-friendly time so that I reliably start my day with the latest incidents."
Description

Generate and deliver the Morning Digest on a configurable schedule per portfolio/team with timezone awareness and daylight saving adjustments. Support delivery via email, in-app mobile web notifications, and a secure shareable link (SSO/expiring token). Provide delivery retries, failure alerts, and user-level preferences for delivery time, channels, and quiet hours.

Acceptance Criteria
Portfolio Schedule Respects Timezone and DST
Given a portfolio "Westside Homes" set to timezone America/Los_Angeles and a Morning Digest schedule of 07:30 local time every day And a second portfolio "Eastside PM" set to America/New_York with 07:30 local time When the scheduler runs Then "Westside Homes" receives exactly one digest between 07:30:00 and 07:31:00 local time each day, adjusted for DST transitions And on DST start, delivery occurs at 07:30 local time after the shift; on DST end, delivery occurs once at 07:30 local time (no double send) And "Eastside PM" receives its digest at 07:30 local time independent of the west coast schedule And the incident aggregation window is computed in each portfolio's timezone from the last successful digest cutoff to the current send time And the next planned run time is recorded and visible in logs/metrics in both UTC and local time
User Preferences Override Team Defaults
Given a team default Morning Digest schedule of 08:00 local and channels [email, in-app] And user Alice sets preferences: delivery time 09:15 local, channels [email], quiet hours 21:00–07:59 local When digests are generated Then Alice's digest is delivered at 09:15 local via email only And no in-app notification is sent to Alice And for users without overrides, delivery uses the team default at 08:00 via [email, in-app] And if a user chooses "Do not send," no channels are attempted for that user and this is logged And invalid preference combinations are rejected with a validation error at save time
Quiet Hours Deferral and Skipping
Given user Bob has quiet hours set to 22:00–08:30 local and a personal delivery time of 07:45 local When the scheduled time falls within Bob's quiet hours Then the digest is deferred and delivered between 08:30:00 and 08:31:00 local And the deferral is recorded in delivery logs with reason "quiet_hours" And if quiet hours span 24 hours for a given day, the digest is skipped for that day and marked "suppressed_quiet_hours" without retries
Multi-Channel Delivery and Retry Policy
Given delivery channels are configured for a user as [email, in-app, secure link] And the system has a retry policy of up to 3 attempts per failed channel with exponential backoff (5m, 15m, 30m) When an email delivery fails due to a transient error Then the system retries the email up to 3 times as per policy And all attempts share the same idempotency key so the user sees at most one email if multiple attempts succeed And if a channel ultimately fails, the next channel is still attempted And successful deliveries are not retried And per-user per-schedule duplicate digests are prevented across retries and channels
Delivery Failure Alerts and Monitoring
Given organization admins are configured as alert recipients And a digest delivery run experiences either (a) >5% channel failures for the run, or (b) any individual user with all channels failed after retries When the run completes Then admins receive an alert within 5 minutes via email with a summary (run ID, portfolio/team, counts by channel, error codes, affected users) And an alert event is emitted to the monitoring webhook if configured And a "retry run" action can be triggered by an admin and uses the same digest content window (idempotent)
Secure Shareable Link with SSO and Expiring Token
Given secure links are enabled with a configurable token TTL of 1–168 hours (default 72) And the org requires SSO for web sessions When a user opens a digest via the shareable link within the TTL Then access is granted if either (a) the user has an active SSO session that matches the org, or (b) the expiring token is valid and unrevoked And after the TTL or upon revocation, the link returns 401/403 and no content is shown And the link contains no PII or sensitive identifiers in the URL path or query string And all link accesses are logged with user/agent/IP and can be revoked immediately by an admin
Idempotent Digest Generation Window
Given the system maintains a last_successful_cutoff timestamp per portfolio/team When generating a Morning Digest for a scheduled run Then the content includes incidents created/updated between last_successful_cutoff (exclusive) and generation_time (inclusive) in the portfolio's timezone And if generation is retried, the same cutoff and generation_time are reused to avoid drift or duplication And if no prior cutoff exists, a default window of the previous 12 hours in the portfolio's timezone is used And upon marking the run successful, last_successful_cutoff for the portfolio/team is updated atomically to the generation_time
Role-Based Views & Portfolio Filters
"As a portfolio owner, I want role-based, filtered views so that I see only relevant properties and data appropriate to my permissions."
Description

Tailor digest content and visibility to user roles (owner, property manager, maintenance lead) and selected portfolios/properties. Control exposure of costs, vendor details, and tenant PII via permission sets. Enable saved filters, default views, and fast switching between portfolios to support 25–200 unit operations without performance degradation.

Acceptance Criteria
Owner View: Full Costs & Vendor Details; Tenant PII Masked
Given an authenticated user with role Owner and access to Portfolio A When the user opens Morning Digest filtered to Portfolio A Then each incident displays total estimated and incurred cost, vendor name and primary contact info, and any vendor notes/attachments And tenant names are shown as initials only and phone/email are masked to last 4 characters And incidents from portfolios outside Portfolio A are not visible
Maintenance Lead View: Costs Hidden, Vendor Identity Obscured
Given a user with role Maintenance Lead and access to Portfolio B When the user opens Morning Digest for Portfolio B Then all cost fields (estimates, incurred, line-item rates) and invoices are not rendered And vendor name is replaced with "Assigned Vendor" and contact details are hidden And photos, timelines, failure reasons, and task status remain visible And the one-click Approve action is not displayed
Permission Toggle: Tenant PII Exposure
Given a user with access to Property 101 and the permission "View Tenant PII" disabled When the user opens the Morning Digest for Property 101 Then tenant name, phone, and email are masked per policy (initials-only; last 4 characters visible) When the same user's permission "View Tenant PII" is enabled Then unmasked tenant name, phone, and email are displayed on new page loads within 60 seconds
Saved Filter: Create, Set Default, Auto-Apply
Given a property manager with access to Portfolios A and B When the user creates a digest filter selecting Portfolio A, Status = Open, Exceptions = On, and saves it as "AM Exceptions" Then the filter "AM Exceptions" is saved and available in the filter selector When the user sets "AM Exceptions" as the default Then upon next login the Morning Digest auto-loads with "AM Exceptions" applied within 2 seconds
Performance: Fast Portfolio Switching at 25–200 Units
Given an account with 200 units across up to 5 portfolios and the user has access to all When the user switches the portfolio filter between any two portfolios Then the digest refresh completes and above-the-fold content renders within 2 seconds on a typical 4G connection (95th percentile under 3 seconds) And no client-side errors or request timeouts occur during switching
Multi-Portfolio Selection: Accurate Aggregation and Scoping
Given the user selects multiple portfolios (A and C) in the filter When the Morning Digest loads Then the incident list includes only properties from portfolios A and C And incident count, total estimated cost, and exceptions count reflect only incidents from A and C And totals equal the sum of the underlying incidents displayed
Permission Toggle: Vendor Detail Exposure
Given a user with permission "View Vendor Details" enabled When the user opens the Morning Digest Then vendor company name and primary contact are displayed on each incident When the permission "View Vendor Details" is disabled Then vendor details are replaced with "Assigned Vendor" and a masked contact label on new page loads within 60 seconds
Audit Trail & Day Ops Handoff
"As a day operations coordinator, I want all digest actions logged and handed off as tasks so that nothing falls through the cracks during shift change."
Description

Record an immutable audit trail for all digest-driven actions with actor, timestamp, and before/after state, and automatically create follow-up tasks or updates for daytime operations. Provide exportable summaries, Slack/email notifications for handoffs, and tag-based learnings to codify recurring issues and reduce repeat vendor visits.

Acceptance Criteria
Immutable Audit Log on One-Click Approval
- Given an authenticated user (Owner/PM/Staff) is viewing Morning Digest, When they click "Approve" on an incident item, Then an audit event is persisted with fields: event_id (UUIDv4), action="approve", actor_user_id, actor_role, incident_id, entity_type, entity_id, occurred_at (UTC ISO 8601, ms precision), request_id, ip_address, and digest_id. - Then the audit event includes a SHA-256 hash of its payload and the hash of the immediately preceding event for the same entity to form an immutable chain. - Then the event is write-once; any correction creates a new audit event linked via previous_event_id; no update-in-place is permitted. - Then the approval does not complete unless the audit write succeeds; on failure, the user receives an error, and the item remains unapproved. - Then the event is queryable in the Audit Trail UI and API within 5 seconds of approval.
Before/After State Capture for Digest Actions
- Given a digest-driven action mutates an entity (status, priority, cost, vendor, ETA, notes, photos), When the action commits, Then the audit event includes before_state and after_state JSON containing only changed fields and their previous/current values. - Then currency values include currency_code and minor_units; timestamps are UTC ISO 8601; free-text notes are stored with phone/email redacted by pattern. - Then photo changes include media_id and checksum for each added/removed photo. - Then the after_state equals the persisted entity state at commit time; discrepancies fail the commit and prompt the user to refresh. - Then concurrent edit conflicts are recorded as a separate conflict audit event with both request_ids.
Automatic Day Ops Task Creation on Handoff
- Given a digest action is marked as requiring follow-up (e.g., needs scheduling, awaiting parts, re-visit required), When the action is saved, Then a Day Ops task is created with task_id, incident_id, action_type, summary, due_date per SLA, assigned_team per routing rules, and audit_event_id. - Then the task appears in the assigned team’s Day Ops queue within 10 seconds of creation. - Then idempotency is enforced: duplicate request_id replays do not create duplicate tasks. - Then if routing rules are missing, the task is placed in the Unassigned queue and a configuration alert is generated. - Then closing the task from Day Ops writes a completion audit event and updates the incident’s digest status accordingly.
Slack and Email Notifications for Handoff
- Given notification channels are configured for the property/portfolio, When a Day Ops task is created from a digest action, Then a Slack message is posted to the configured channel and an email is sent to the configured list including incident_id, address/unit, action summary, assignee, due date, and a deep link to the task with photos link. - Then notifications are delivered within 60 seconds of task creation. - Then delivery failures are retried with exponential backoff up to 3 attempts and logged; after final failure, an admin alert is created. - Then opt-out preferences are honored for email recipients; Slack posting respects channel permissions and surfaces authorization errors in admin alerts. - Then a notification audit event is recorded per channel with delivery status (success/failure and message_id).
Exportable Audit Summary (CSV/PDF)
- Given a user with reporting permission selects a date range (≤31 days) and one or more properties, When they request Export Audit Summary, Then CSV and PDF files are generated including: incident_id, property, unit, action_type, actor, occurred_at, before/after key fields, vendor, cost, notes, tags, handoff_task_id, and event hash. - Then exports are available within 60 seconds for up to 10,000 audit events; larger requests run asynchronously and notify on completion. - Then CSV is UTF-8 with headers, comma-delimited; PDF is paginated for A4/Letter and includes a summary page. - Then a SHA-256 checksum is provided for each file; downloaded content matches the audit records as of request time. - Then PII (tenant phone/email) is redacted unless the requester has the Include PII permission enabled for the export.
Tag-Based Learnings to Reduce Repeat Visits
- Given a user is reviewing a digest item, When they add standardized tags from the controlled vocabulary (e.g., recurring-clog, callback-required), Then the tags are applied to the incident and an audit event is recorded with actor, occurred_at, and tags list. - Then tags are visible on the Day Ops task, included in notifications, and present in exports. - Then users with permission can propose new tags; proposals require admin approval before they become selectable; all tag changes are audited. - Then the Learnings view aggregates tag counts by property and vendor for a selected period and highlights the top 5 recurring issues with drill-down to incident lists. - Then adding or removing a tag from the digest completes within 2 seconds and requires no more than 3 clicks.

On-Call Roster

Centralizes rotating on-call schedules by trade and region. Automatically routes emergencies to the correct contact, supports quick mobile swaps for coverage gaps, and prevents misroutes during holidays or vacations.

Requirements

Roster Builder by Trade & Region
"As an operations manager, I want to create and publish rotating on-call schedules grouped by trade and region so that emergencies are routed to the right technician without manual intervention."
Description

Provide an admin and manager-facing interface to define trades (e.g., plumbing, HVAC, electrical) and regions, create recurring on-call rotations (daily/weekly), assign internal staff or preferred vendors as contacts, and publish effective schedules with timezone support. Enforce validation to prevent gaps/overlaps, allow start/end dates, and support draft vs. published states with role-based permissions. Integrates with FixFlow’s vendor directory and property-region mapping to serve as the single source of truth for routing decisions, reducing manual coordination and errors.

Acceptance Criteria
Create Trade and Region Definitions (Admin)
Given I am an Admin user, When I create a new Trade named "Plumbing" and a Region named "North Bay", Then both records are saved and appear in their respective lists with created-by and created-at metadata. Given a Trade or Region exists, When I update its name or description, Then changes persist and an audit entry records who changed what and when. Given a Trade or Region has one or more associated schedules, When I attempt to delete it, Then the system prevents deletion and displays a dependency error identifying the schedules. Given I attempt to create a duplicate Trade or Region name (case-insensitive) within the organization, Then the system blocks the action and prompts me to use the existing record.
Set Up Weekly Rotation with Timezone
Given I select Trade "Plumbing" and Region "North Bay", When I create a weekly rotation starting 2025-10-01 08:00 in timezone America/Los_Angeles, Then the system generates contiguous 7-day slots forward for the configured planning horizon without gaps. Given the rotation spans a daylight saving time shift in the selected timezone, When slot boundaries occur, Then the system adjusts start/end timestamps to preserve intended local times and prevent overlaps. Given I preview the schedule, When I view it from a different timezone, Then slot times display in my local time with the source timezone clearly labeled.
Validate No Gaps or Overlaps per Trade-Region
Given a draft or published schedule exists for Trade "Plumbing" in Region "North Bay", When I add or edit slots within the effective date range, Then saving is blocked if any time gap exists between consecutive slots for that trade-region. Given two slots overlap in time for the same trade-region, When I attempt to save, Then the system rejects the change and highlights the conflicting slots and timestamps. Given overlapping times occur across different trades or different regions, Then the system allows the save and does not flag a conflict.
Assign Internal Staff and Preferred Vendors
Given an internal user or preferred vendor exists in the directory, When I assign them as the on-call contact for a slot and provide at least one valid contact method, Then the slot validates successfully. Given I attempt to assign a contact not present in the directory, Then the system prevents assignment and prompts me to add the contact via the directory. Given the assigned contact is marked inactive in the directory, When I attempt to publish the schedule, Then publishing is blocked and the affected slots are listed with an actionable error. Given I configure a backup contact and escalation order for a slot, Then the system saves the order and displays it in the slot details.
Draft vs Published Schedules with Role-Based Permissions
Given my role is Manager or Admin, When I save a schedule as Draft, Then it is visible to roles with edit rights and is not used for routing. Given my role is Manager or Admin, When I publish a draft schedule, Then its status changes to Published and routing uses it from the specified effective start timestamp. Given my role is Viewer, When I access schedules, Then I can view Published schedules only and Draft schedules are hidden. Given a Published schedule is edited, When I save changes as a new Draft, Then the existing Published schedule remains active until the new Draft is published.
Effective Date Ranges and Holiday Overrides
Given a schedule has an effective start and end date, When the current time is outside this range, Then routing does not use this schedule and falls back to the most recent valid Published schedule or the org default. Given I create a holiday override for Trade "Plumbing" in Region "North Bay" for 2025-12-25 00:00–23:59 local time, Then during that window the override contact supersedes the base rotation. Given multiple overrides affect the same window, Then the system applies the most specific and most recently updated override and records the selection in the audit log.
Routing Lookup Uses Published Roster
Given a maintenance ticket for Property X mapped to Region "North Bay" and tagged Trade "Plumbing", When routing is requested at 2025-10-05 01:30 local time, Then the API returns the current on-call contact for that trade-region from the Published roster with contact method and number. Given Property X has no region mapping, When routing is requested, Then the API responds with a 409 error describing the missing mapping and logs an incident for follow-up. Given the on-call contact lacks a required contact method for the configured routing channel, When routing is requested, Then the API returns a 422 validation error identifying the missing data and the schedule slot id.
Automated Emergency Routing Engine
"As a property manager, I want emergencies automatically assigned to the correct on-call contact so that tenants receive fast responses without me coordinating by phone."
Description

Automatically routes emergency tickets to the correct on-call contact based on triage-derived trade, property region, and the active roster. Supports multi-channel notifications (SMS, voice call, email, push), acknowledgment workflows, and ticket assignment write-back. Handles duplicate suppression, load balancing within defined pools, and routing to preferred vendors first. Respects SLAs with timers and retries, and logs all decisions for auditability. Integrates tightly with FixFlow’s photo-first intake and smart triage to ensure speed and accuracy.

Acceptance Criteria
Route to Correct On-Call by Trade and Region
Given an emergency ticket with triage-derived trade and property region And an active on-call roster exists for that trade and region And roster holiday/vacation exceptions and mobile swap overrides are in effect When the routing engine processes the ticket Then it selects the on-duty contact matching the trade and region who is not on holiday/vacation and respects any active swap override And it completes selection within the configured routing latency threshold And it does not select any contact outside the specified trade or region And it records the selected contact identifier on the ticket as pending assignment
Multi-Channel Notification with Fallback
Given a selected on-call contact with enabled notification channels and configured notification strategy When the routing engine initiates notifications Then it sends notifications via all enabled channels according to the configured strategy (parallel or sequential) And each channel attempt includes ticket ID, property, trade, severity, and an acknowledgment action/link And failed channel attempts are retried per configured retry count and interval And delivery outcomes for each channel (success, failure, timeout) are recorded to the audit log And if all channels fail, the engine flags a notification failure and proceeds to escalation per policy
Acknowledgment and Escalation within SLA
Given an emergency ticket with a configured acknowledgment SLA window and escalation path When the on-call contact does not acknowledge within the SLA window Then the engine escalates to the next contact/vendor per the active roster or escalation policy And it continues escalation until an acknowledgment is received or the path is exhausted And when any contact/vendor acknowledges, the engine stops further notifications and escalations And the acknowledgment actor and timestamp are recorded And the system marks the SLA as met or breached based on the acknowledgment time
Ticket Assignment Write-Back on Acknowledgment
Given an acknowledgment received from a contact or vendor When the engine processes the acknowledgment Then it updates the ticket assignment to the acknowledging contact/vendor And sets the ticket status to Assigned (or equivalent) with the acknowledgment timestamp And records the communication channel used for acknowledgment And ensures idempotency so repeated acknowledgments do not create duplicate assignments And posts an activity entry visible to managers and tenants indicating the assignment
Duplicate Emergency Suppression
Given an incoming emergency ticket for a property unit with trade and symptom matching an existing open emergency ticket within the configured suppression window When the engine evaluates duplicates using the configured deduplication key and window Then it links the new ticket to the open master ticket as a duplicate And suppresses new notifications to contacts/vendors for the duplicate And attaches the new intake data (photos, notes, timestamps) to the master per configuration And records the suppression decision and deduplication key in the audit log
Load Balancing and Preferred Vendor Priority
Given an eligible pool of contacts/vendors for the ticket’s trade and region with capacity limits and a configured load-balancing algorithm And preferred vendors are designated for the property or portfolio When the engine selects whom to notify Then it attempts preferred vendors first that meet eligibility, coverage, and capacity constraints And if no preferred vendor is eligible, it selects from the remaining pool according to the configured algorithm (e.g., round-robin, least-load) And it does not assign beyond the configured maximum active tickets per contact/vendor And the selection rationale and current load metrics are recorded in the audit log
Decision Audit Log Completeness
Given an emergency ticket processed by the routing engine When an auditor retrieves the routing decision log for that ticket Then the log includes inputs (triage trade, severity, property region), roster state (on-call list, swaps, holidays), deduplication evaluation, selected contact/vendor, notification attempts per channel with outcomes, acknowledgment events, escalation steps, SLA timers and results, and ticket write-back results And each log entry is timestamped, immutable, and correlated by ticket ID And the log is exportable in JSON format via an authorized endpoint or UI
Mobile Shift Swap & Coverage Requests
"As an on-call technician, I want to swap a shift from my phone and have the roster update instantly so that coverage gaps don’t occur."
Description

Enable on-call users to propose and accept swaps or request coverage from a mobile web interface with one-tap actions. Enforce conflict checks, roster validation, and optional manager approval rules. Apply real-time updates to the published roster and notify impacted parties. Maintain a full audit trail of requests, approvals, and changes to ensure accountability and prevent coverage gaps.

Acceptance Criteria
One-Tap Swap Proposal (Same Trade & Region)
Given the requester is currently assigned on-call for Trade X in Region Y during window T on mobile web When they tap "Propose Swap", select a teammate, and submit Then the system validates both users belong to the Trade X/Region Y roster and are eligible for window T And rejects if conflicts exist (overlap, PTO, vacation, holiday blackout) with a clear error And applies approval policy and sets state to Pending Target Acceptance or Pending Manager Approval as configured And creates a swap request with unique ID and timestamps And makes no changes to the published roster And sends notifications to the selected teammate and approver(s) And records an audit entry with requester, target, trade, region, time window, policy evaluation, and notification results.
Swap Acceptance & Real-Time Roster Update
Given an open swap request where all required approvals are satisfied When the selected teammate taps "Accept" on mobile Then the published roster updates within 30 seconds for window T And emergency routing switches immediately for all times >= now within T And no coverage gaps or double-assignments are introduced And notifications are sent to requester, acceptor, and approver(s) And an audit entry records the before/after assignment and acceptance details And the request state becomes Completed.
Coverage Request Broadcast & Fill
Given a user cannot cover an assigned on-call window T for Trade X/Region Y When they tap "Request Coverage", confirm window T, and submit Then notifications go to all eligible roster members per policy And the first eligible "Take Shift" response is accepted; others are informed the shift is filled And manager approval is requested if required; otherwise the roster updates within 30 seconds And if unfilled after 10 minutes, escalation notifications are sent per policy And ineligible responders are blocked with reason (conflict, not on roster, blackout) And all offers, responses, approvals, and updates are recorded in the audit trail.
Conflict & Policy Enforcement
Given a swap or coverage create/accept action When validation runs Then the system blocks actions that would cause overlaps, gaps, out-of-trade/region assignments, holiday blackout violations, approved PTO conflicts, or exceeding consecutive shift limits And presents a single consolidated error listing all blocking reasons And allows manager override only for soft constraints as configured; hard constraints (gap/overlap) cannot be overridden And all checks and overrides are captured in the audit trail.
Notifications to Impacted Parties
Given any request state change (created, accepted, approved, declined, canceled, expired) When the state changes Then impacted parties (requester, target(s), approver(s)) receive in-app and email notifications within 30 seconds And delivery is retried up to 3 times on transient failures And notification delivery status is visible in the audit trail And users can tap the notification to deep-link to the request on mobile web.
Audit Trail Completeness & Export
Given any swap or coverage lifecycle event When the event is processed Then an immutable audit entry is stored with event type, timestamp (UTC), actor, previous assignment, new assignment, trade, region, time window, policy decisions, approvals, and notifications sent And entries are queryable by date range, trade, region, user, and request ID And a CSV export of these entries is available to authorized users And entries become visible in the audit view within 10 seconds.
Concurrency & Idempotency Protection
Given multiple users act on the same request concurrently or a user retries due to network issues When two accept/take actions arrive within 2 seconds or a duplicate submission occurs Then exactly one action is committed and others receive an 'Already Filled' or 'Duplicate Request' message And no double-assignment or coverage gap is created And idempotency keys prevent duplicate processing within a 60-second window And all conflicting attempts are logged in the audit trail.
Holiday & Vacation Overrides
"As a scheduler, I want holidays and approved vacations to automatically override rotations so that emergencies are not misrouted when primary contacts are unavailable."
Description

Support regional holiday calendars and individual time-off entries that automatically override standard rotations. Provide pre-conflict alerts during schedule creation, and auto-reroute coverage to backups or pools during holiday periods and approved vacations. Include reminder notifications to managers when gaps are detected and tools to reassign coverage in a few taps. Ensures no misroutes during holidays or staff leave.

Acceptance Criteria
Regional Holiday Overrides Standard Rotations
Given a region has an active holiday defined from <start> to <end> in the region’s local timezone And a standard on-call rotation exists for that region and trade When the schedule covers any portion of that holiday window Then the scheduled primary for each affected slot is automatically overridden by the region’s designated holiday backup or backup pool And tasks created during the holiday window are not routed to the overridden primary And the calendar UI marks affected slots as "Holiday Override" and shows the backup assignee And the scheduling API returns override_source = "holiday" for affected intervals
Individual PTO Overrides On-Call Assignment
Given a user has an approved vacation/PTO entry spanning <start> to <end> And the user is a primary in the rotation for that trade and region When the schedule is generated or an incident arrives during that PTO window Then the user is not assigned or notified And routing uses the first eligible backup per roster rules And the schedule view flags those slots as "PTO Override" with the replacement assignee And the audit log records the override with user_id, time window, and replacement_id
Pre-Conflict Alerts During Schedule Creation
Given a manager is creating or editing an on-call schedule When a slot conflicts with a regional holiday or an approved PTO entry Then the system displays a pre-conflict alert before save that identifies the conflict type, assignee, date/time, and region And the alert offers one-click "Auto-assign backup" and "Open candidate list" actions And selecting "Auto-assign backup" replaces the assignee with the top eligible backup and clears the conflict And conflicts are highlighted inline until resolved or explicitly acknowledged with a reason
Auto-Reroute to Backups During Holiday/PTO
Given an emergency ticket is created for trade X in region Y during a holiday or an assignee’s PTO When the on-call router selects a contact Then it skips all unavailable primaries in the conflict window And attempts the first eligible backup; if no backup, attempts the regional backup pool; if none, escalates to the manager on duty And the first notification is sent within 30 seconds of ticket creation And no notification is sent to any user marked unavailable due to holiday/PTO And the routing decision is logged with reason = "holiday" or "PTO"
Manager Reminders for Coverage Gaps
Given a gap will exist in on-call coverage due to a holiday or PTO in the next 7 days When the gap is detected (at detection time and again 24 hours before the gap) Then the manager(s) for the region receive reminder notifications via email and mobile push with link to resolve And the reminder includes affected dates, trade, region, and suggested candidates And reminders stop automatically once coverage is assigned
Mobile Reassignment in <= 3 Taps
Given a manager opens a gap or conflict notification on a mobile device When they tap "Reassign" Then they can select a suggested candidate and confirm in 3 taps or fewer And the reassignment is saved and reflected in the schedule within 10 seconds And the new assignee and impacted stakeholders receive notifications within 10 seconds And the change is traceable in the audit log with actor, timestamp, and reason
Escalation & Failover Ladder
"As a dispatcher, I want unacknowledged alerts to escalate to backups automatically so that no emergency is missed."
Description

Configure stepwise escalation rules if a primary on-call contact does not acknowledge within defined thresholds (e.g., escalate to backup, then manager, then vendor pool). Support simultaneous ring, timed retries, and acceptance confirmation. Automatically reassign the ticket upon acceptance and notify tenants with status updates where applicable. Capture acknowledgment timestamps and outcomes for SLA reporting and continuous improvement.

Acceptance Criteria
Escalate on Unacknowledged Primary Within Threshold
Given a critical maintenance ticket triggers the escalation ladder with Step 1 targeting the primary on-call contact and configured for 2 retries at 60-second intervals with a 3-minute escalation threshold When notifications are sent to the primary at t0, t0+60s, and t0+120s And no acknowledgment is received by t0+180s Then the system escalates to Step 2 (backup contact) within 5 seconds of the 3-minute threshold And all remaining retries for Step 1 are cancelled And the audit log records each attempt with timestamp, channel, delivery status, and the escalation reason "timeout"
Simultaneous Ring With First-Accept Wins
Given an escalation step is configured to simultaneous ring two or more eligible contacts When the ladder reaches this step Then the system initiates calls/messages to all step targets within 2 seconds of each other When any one target sends an acceptance confirmation within 90 seconds Then the ticket is assigned to that accepting contact within 5 seconds And all other targets receive a cancellation notice and the system stops further escalation
Acceptance Confirmation Stops Escalation and Reassigns Ticket
Given an on-call contact receives an escalation notification for a ticket When the contact sends an explicit acceptance confirmation via any supported channel Then the ticket is reassigned to the accepting contact within 5 seconds And the escalation ladder terminates immediately And the system records the acceptance timestamp, channel used, and accepting user ID in the audit log
Tenant Status Updates on Escalation and Acceptance
Given tenant notifications are enabled for the ticket When the ladder escalates to the next step Then the tenant receives a status update within 60 seconds indicating the escalation and current handling role/group When a contact accepts the ticket Then the tenant receives a confirmation within 60 seconds including the accepting party's role and any provided ETA window And no tenant messages are sent for tickets flagged as internal-only or where tenant notifications are disabled
SLA Audit Trail Captured Per Escalation Event
Given a ticket runs through any part of the escalation ladder When a notification is sent, delivered, undeliverable, acknowledged, accepted, timed out, or skipped Then the system records per-event fields: step index, targets, channel(s), send time, delivery status, acknowledgment time (if any), acceptance time (if any), outcome, and time-to-acknowledgment in seconds And an SLA report for the ticket exposes these fields for review And a portfolio export for the last 30 days completes within 10 seconds and returns a CSV including these fields
Respect Unavailability, Holidays, and Coverage Swaps
Given one or more contacts in a step are marked unavailable due to vacation, holiday, or are not currently on-call due to a coverage swap When the step is executed Then the system skips all unavailable contacts without sending notifications and proceeds to the next eligible target within 2 seconds And the audit log records each skipped contact with reason (vacation, holiday, not-on-call)
Vendor Pool Failover After Manager Timeout
Given the escalation ladder includes a vendor pool failover step filtered by trade, region, and vendor availability And the manager step has timed out without acceptance When the ladder reaches the vendor pool step Then the system simultaneously rings all eligible preferred vendors within 5 seconds And the first vendor to accept is assigned the ticket and receives the work order details immediately And if no vendor accepts within 10 minutes, the ticket status updates to "escalation_failed" and an alert is sent to the duty manager
Calendar Sync & Availability Integration
"As an on-call team member, I want my on-call shifts to appear in my calendar and respect my availability so that I can plan ahead and avoid conflicts."
Description

Provide one-way iCal feeds for published rosters and optional two-way integrations with Google/Microsoft calendars to surface on-call shifts and read busy times to avoid conflicts. Offer a lightweight availability API/webhook for preferred vendors to push temporary unavailability. Handle timezones, daylight saving changes, and permissioned access controls. Reduces scheduling friction and prevents hidden conflicts.

Acceptance Criteria
Published Roster iCal Feed Access & Freshness
Given a published on-call roster with shifts across multiple time zones When a user with view permission requests the ICS URL with a valid token Then the response is an RFC 5545–compliant calendar containing one VEVENT per shift with a stable UID, correct DTSTART/DTEND in the roster’s time zone, and SUMMARY including trade and region And When a roster shift is created, updated, or deleted Then the ICS reflects the change within 5 minutes And When an ICS token is rotated by an admin Then a new unique URL is issued immediately and the previous URL returns 401 within 5 minutes And When the ICS URL is requested without a valid token Then the response is 401 with no calendar data
Google Calendar: Push On-Call Shifts
Given a user has linked their Google account with the required OAuth scopes When a future on-call shift is published or updated on the roster Then a corresponding event is created or updated on the user’s primary Google Calendar within 5 minutes with accurate title (trade/region), timezone/DST handling, and no duplicates And When a roster shift is cancelled Then the corresponding Google event is deleted within 5 minutes And When the user unlinks Google Then no further events are created, updated, or deleted by FixFlow
Read Free/Busy to Prevent Conflicts
Given a user has granted read free/busy access for Google or Microsoft calendars When a scheduler assigns or modifies a shift time for that user Then the system checks free/busy for the shift window and warns if overlap is 15 minutes or more And When a conflict is detected Then the scheduler is shown the next three available alternatives within the same trade and region and may override with a required justification note And No external event titles or descriptions are stored; only time ranges and availability status are cached for up to 24 hours
Microsoft 365 Calendar: Two-Way Sync
Given a user has linked their Microsoft 365 calendar with the required OAuth scopes When a future on-call shift is published or updated on the roster Then a corresponding event is created or updated on the user’s default Microsoft calendar within 5 minutes with accurate title (trade/region), timezone/DST handling, and no duplicates And When a roster shift is cancelled Then the corresponding Microsoft event is deleted within 5 minutes And When the user unlinks Microsoft 365 Then no further events are created, updated, or deleted by FixFlow
Vendor Availability Webhook/API
Given a preferred vendor holds a valid API key When the vendor POSTs a temporary unavailability window (start/end in UTC, reason) with an Idempotency-Key header to the availability endpoint Then the vendor is marked unavailable for routing within that window within 1 minute And Overlapping or duplicate submissions are treated idempotently and merged And The API responds 202 with a correlation ID and a confirmation callback/webhook is delivered within 1 minute And All availability changes are audit-logged with actor, timestamps, and source and are queryable for 90 days
Timezone & Daylight Saving Integrity
Given shifts that span DST changes or are assigned across multiple time zones When iCal feeds and two-way sync events are generated Then each event’s start and end reflect the correct local time and absolute UTC instants with no unintended 1-hour gaps or overlaps And When a shift crosses midnight or a DST boundary Then the event duration equals the scheduled hours And Internal storage uses UTC with canonical time zone identifiers for conversion
Permissions & Access Controls for Sync
Given role-based permissions are configured When a user without Roster View or Integration Manage permissions attempts to access an ICS URL or enable calendar sync Then access is denied with 403 and the attempt is audit-logged And Only org admins can enable org-wide sync policies while individual users must consent to link their calendars And Tokenized ICS URLs are at least 32 random bytes, non-indexable, rotatable, and rate-limited to 60 requests per minute returning 429 on exceed
Audit Log & SLA Reporting
"As an owner-operator, I want reports on on-call coverage and response times so that I can optimize staffing and demonstrate SLA compliance to stakeholders."
Description

Record every routing decision, acknowledgment, escalation step, swap, and override with timestamps and actors. Provide dashboards and exportable reports (CSV) for coverage gaps, time-to-acknowledge, time-to-dispatch, and escalation rates by trade and region. Integrate with FixFlow’s existing reporting to attribute outcomes (e.g., reduced repeat visits) to improved on-call performance, enabling staffing optimization and owner-facing SLA proof.

Acceptance Criteria
Routing decisions, swaps, and overrides are logged with timestamps and actors
Given an incident is auto-routed to the on-call contact by trade and region When the routing occurs Then an audit log entry is created with event_type=route_assigned, incident_id, trade, region, priority, to_contact_id, rule_id, actor=system, and timestamp in ISO 8601 UTC with millisecond precision Given a user performs a manual override to assign a different contact When the override is saved Then an audit log entry is created with event_type=override_applied, from_contact_id, to_contact_id, actor=user:{user_id}, reason, and timestamp Given a coverage swap is created or canceled on mobile When the swap is saved or canceled Then an audit log entry is created with event_type in {swap_created, swap_cancelled}, roster_period, trade, region, from_contact_id, to_contact_id, actor=user:{user_id}, and timestamp Given a routing attempt is blocked due to a holiday or vacation rule When the block occurs Then an audit log entry is created with event_type=route_prevented, rule_id, reason, actor=system, and timestamp Given the audit log is queried by incident_id When results are returned Then entries are ordered by timestamp ascending and include all events for that incident with no missing steps
End-to-end acknowledgment and escalation steps are traceable
Given per-trade SLA config defines time_to_ack and escalation steps When an incident is created Then the SLA deadline for acknowledgment is computed and stored on the incident and in the audit log as event_type=sla_started Given an on-call contact acknowledges an incident When the acknowledgment is recorded via mobile or API Then an audit log entry event_type=ack_received is created with contact_id, channel, and timestamp, and time_to_ack_ms is computed and stored Given an incident is not acknowledged by the SLA deadline When the deadline passes Then an audit log entry event_type=escalation_triggered is created with step_number, from_contact_id, to_contact_id, and timestamp, and the next contact is notified Given multiple escalation steps are configured When each step triggers Then each step is logged with unique event_id and step_number, and the chain can be reconstructed end-to-end for the incident Given dispatch to a vendor is recorded When dispatch occurs Then an audit log entry event_type=dispatch_recorded is created and time_to_dispatch_ms is computed from incident creation to dispatch
Coverage gap detection and dashboard by trade and region
Given rosters are defined by trade, region, and time windows When there exists any continuous window of at least 5 minutes with no on-call contact assigned Then a coverage_gap record is created with trade, region, start_timestamp_utc, end_timestamp_utc, and gap_minutes Given a user opens the coverage dashboard with filters for date range, trade(s), and region(s) When the filters are applied Then the dashboard displays total gap_minutes, number_of_gaps, and top affected trades/regions, and values match the underlying coverage_gap records Given the roster is updated (new assignment, swap, or cancellation) When the change is saved Then the dashboard and coverage_gap records update within 60 seconds to reflect the new coverage state Given a user drills into a trade/region When they view hourly breakdown Then gaps are visualized per hour block and the sum of block gaps equals the total gap_minutes for the selected period
Exportable CSV for SLA metrics with filters
Given a user selects a date range, trade(s), region(s), and aggregation level (daily/weekly/monthly) When they click Export CSV Then a UTF-8 CSV is generated with a header row and columns: period_start_utc, period_end_utc, trade, region, incidents, ack_sla_target_minutes, ack_sla_met_count, ack_sla_met_pct, median_time_to_ack_ms, p90_time_to_ack_ms, dispatch_sla_met_count, median_time_to_dispatch_ms, escalation_rate_pct, gap_minutes Given the export is generated When the file is downloaded Then numeric values match the on-screen dashboard within rounding rules (0.1% for percentages, nearest millisecond for durations), and totals reconcile with the sum of rows Given the export would exceed 50,000 rows When the user requests it Then an asynchronous export job is created, the user is notified upon completion, and the file is available for download for at least 7 days Given the CSV is opened in a spreadsheet application When the user inspects timestamps Then timestamps are in ISO 8601 UTC, and the file includes metadata rows at the top prefixed with # for report_generated_at_utc and filter_summary
Outcome attribution links on-call performance to FixFlow results
Given FixFlow reporting includes work_orders with outcome metrics (repeat_visits_30d, tenant_satisfaction_score) and incidents have incident_id and work_order_id When On-Call Performance is joined into existing reports Then metrics are available segmented by ack_sla_met (true/false) and by escalation_count (0,1,2+), and repeat_visits_30d is computable per segment Given a property manager views the Owner SLA report When the "Include On-Call Performance" toggle is enabled Then the report displays, per trade and region, the changes in repeat_visits_30d and median_time_to_ack_ms compared to the previous period, and filter settings are shown in the report header Given the report time range is changed When a new range is applied Then joins are recomputed using work_order_id and incident_id, and totals reconcile with underlying FixFlow reporting within 0.5% Given an export is requested from the attribution view When the CSV is generated Then it includes columns: trade, region, period_start_utc, incidents, ack_sla_met_pct, median_time_to_ack_ms, escalation_rate_pct, repeat_visits_30d_pct, reduced_repeat_visits_delta_pct
Audit log immutability, role-based access, and time zone consistency
Given an audit log entry is created When any user attempts to update or delete it via UI or API Then the operation is rejected with HTTP 403 and no changes are made; corrections must be recorded as a new event_type=correction referencing original_event_id Given role-based access control is configured When a user with role in {OwnerAdmin, PortfolioManager} accesses the audit log and SLA reports Then full data for their permitted properties is visible; users with role in {Staff, Vendor} see only incidents assigned to them; unauthorized users see no records Given a user exports a CSV When the user's role lacks export permission Then the Export action is disabled in the UI and API returns HTTP 403 Given timestamps are stored in UTC When a user in any region views the log or dashboard Then display times reflect the user's locale time zone with daylight saving applied, while raw exports remain UTC Given two events occur across different regions near a daylight-saving transition When events are sorted by timestamp Then ordering is correct chronologically and no duplicate or missing hours are displayed

Critical Path Guard

Automatically calculates and visualizes the critical path for each unit turn. Flags tasks that jeopardize the move‑in date, simulates the impact of delays or duration changes, and recommends the smallest resequencing to recover time. Sends proactive nudges to assignees and offers one‑click fixes so managers keep deadlines without constant firefighting.

Requirements

Critical Path Computation & Visualization
"As a turn manager, I want to see the critical path for each unit turn so that I can focus teams on tasks that determine the move-in date."
Description

Constructs a task dependency graph for each unit turn using FixFlow’s turn templates, historical duration averages, and per-task constraints. Computes the critical path (CPM) with total/ free float and recalculates in real time as tasks complete, dates shift, or durations update. Visualizes the plan in a responsive Gantt and network view with clear highlighting of critical tasks, slack indicators, and projected move-in date. Supports portfolio filtering (by property, building, unit) and mobile-friendly interaction for field teams. Data originates from existing work orders and vendor assignments; outputs feed alerts, simulations, and recommendations.

Acceptance Criteria
CPM Calculation from Turn Template
Given a unit turn generated from a standard FixFlow turn template with defined task dependencies, historical average durations, and per-task duration overrides And some tasks include constraints such as Start No Earlier Than (SNET), Finish No Later Than (FNLT), and minimum lag times between predecessors and successors When the system constructs the dependency graph and runs the Critical Path Method (CPM) Then the system computes ES/EF/LS/LF for every task and total and free float values And all critical path tasks have total float = 0 days And tasks respect SNET/FNLT/lag constraints in the computed schedule And the computed critical path and floats match an independently calculated baseline within 0.1 day tolerance for a 50-task sample And if a cyclical dependency exists, the system halts computation, flags the cycle with the offending tasks, and records an error for remediation without crashing
Real-time Recalculation on Task Updates
Given an in-progress unit turn schedule has been computed When a task is marked complete, an estimated duration is edited, or a dependency is added/removed/changed Then the schedule, floats, and critical path are recalculated within 2 seconds for plans up to 150 tasks And downstream ES/EF/LS/LF dates reflect the change And the projected move-in date updates accordingly And a change log entry is created capturing the trigger, old/new values, recalculation time, and affected tasks And if recalculation causes the move-in date to exceed the target date, the schedule is flagged as jeopardized
Gantt and Network Visualization with Critical Highlighting
Given a computed schedule for a unit turn When the user opens the Gantt view Then the initial render completes within 1.5 seconds for up to 150 tasks And critical tasks are visually highlighted (e.g., red bars/edges) with a contrast ratio >= 4.5:1 against the background And non-critical tasks display slack indicators (e.g., thin trailing bars or shaded regions) proportional to total float And hovering or tapping a task reveals a tooltip with ES, EF, LS, LF, total float, free float, and assignee When the user switches to the network (PERT) view Then nodes and edges render with the same critical/non-critical highlighting And layout prevents edge overlaps for >= 90% of edges on the 150-task test set via automatic routing And view toggling preserves the selected task and zoom level
Projected Move-in Date Accuracy and Slack Indicators
Given a unit turn with a configured target move-in date When the schedule is computed Then the projected move-in date equals the maximum EF among terminal tasks on the critical path to within 0.1 day rounding And the UI displays the projected move-in date, delta vs target (in days), and a risk badge when delta > 0 And each task shows total float and free float values consistent with CPM definitions (free float never negative and never greater than total float) And milestone tasks (e.g., Final Clean, Inspection) display zero-duration correctly while still participating in path and float calculations
Portfolio Filtering and Scope Control
Given multiple turns across properties, buildings, and units exist When the user applies a portfolio filter (property, building, unit) Then only turns matching the filter are loaded into the Gantt and network views And counts (turns, tasks) update to reflect the filtered scope And the filter operation completes within 800 ms for up to 500 active turns and 20,000 tasks And clearing the filter restores the previous unfiltered state without a full page reload
Mobile-Friendly Interaction for Field Teams
Given a user opens the schedule on a mobile device with viewport width 375–414 px When the Gantt or network view loads over a simulated 4G connection Then the Largest Contentful Paint occurs within 2.5 seconds and total page weight for the visualization layer is <= 500 KB And horizontal scroll, pinch-to-zoom, and drag-to-pan are supported without jitter (> 45 FPS in interaction) And tap targets for tasks and controls are >= 44x44 px and tooltips are reachable via long-press And labels truncate with ellipses and reveal full text on tap without overlapping neighboring elements
Data Integration and Output Publishing
Given existing work orders and vendor assignments for a unit turn When the system builds the task graph Then tasks are created or linked idempotently using stable identifiers (no duplicate tasks on repeated imports) And missing duration data falls back to historical averages by task type and property with a clearly marked default And upon each successful recomputation, a schedule-update event is emitted containing turn ID, projected move-in date, critical path task IDs, and per-task float values for downstream alerts and simulations And the event is delivered at least once and is versioned with a schema identifier
Real-time Jeopardy Detection & Thresholds
"As a property manager, I want tasks that threaten the move-in date to be automatically flagged so that I can intervene before delays cascade."
Description

Continuously monitors schedule variance, float consumption, and forecasted end dates to flag tasks that jeopardize the move-in date. Applies configurable thresholds (e.g., float < 0.5 day, start slip > 1 day) and severity levels with color-coded badges. Triggers on task updates, vendor confirmations, and checklist completions. Integrates with FixFlow notifications and the activity feed to surface risks where users work. Exposes APIs/webhooks to inform downstream systems (e.g., tenant onboarding).

Acceptance Criteria
Real-time Flag on Task Update
Given a unit turn with a defined move-in date and baseline schedule And configurable thresholds exist (e.g., float_threshold = 0.5 day, start_slip_threshold = 1 day) When a task’s actual start, remaining duration, or dependency changes are saved Then the system recalculates schedule variance, total/remaining float, and forecasted move-in date within 10 seconds And if float < float_threshold OR start slip > start_slip_threshold OR forecasted move-in date > move-in date Then a jeopardy flag is created/updated on the task and unit turn with computed severity And if no thresholds are breached, no new flag is created and any existing flag remains unchanged
Jeopardy Detection on Vendor Confirmation
Given a task pending vendor confirmation with a planned start and duration When a vendor confirmation is received that shifts the task start later by more than the configured start_slip_threshold Then the task is flagged within 10 seconds with severity per configured mapping and shows a color-coded badge And the forecasted move-in date is recalculated and the delta vs baseline is displayed on the unit turn timeline And an audit entry records vendor_id, previous/planned dates, confirmed date, and computed slip When a vendor confirmation advances the start or is within threshold Then no jeopardy flag is created and any existing flag caused solely by unconfirmed start is cleared
Checklist Completion Trigger Recalculation
Given a task with downstream dependents and an existing jeopardy flag due to float consumption When a prerequisite checklist item is completed and the task actual finish is recorded Then the system recalculates float and forecasted end dates within 10 seconds And if thresholds are no longer breached, the jeopardy flag is cleared and a risk_cleared event is recorded And if thresholds are further breached, the flag severity is escalated accordingly
Configurable Thresholds and Severity Mapping
Given organization-level default thresholds and optional portfolio/property overrides When an admin sets float_threshold to 0.5 day and start_slip_threshold to 1.0 day with severities {Warning, Critical} Then new calculations use the updated values immediately and all open risks are re-evaluated within 60 seconds And thresholds accept decimal values in hours or days with validation and unit normalization And only users with the appropriate role can create/update thresholds; others receive a permission error
Color-Coded Severity Badges in UI
Given severity levels mapped to colors (e.g., Warning = amber, Critical = red) When a jeopardy flag is present on a task Then the badge appears consistently on the task card, critical path view, and unit turn timeline with the mapped color And a tooltip shows the breached metric(s), current value(s), threshold(s), and timestamp And when the risk clears, the badge is removed or downgraded within 10 seconds across all views
Notifications and Activity Feed Surfacing
Given FixFlow notifications and an activity feed are enabled When a jeopardy flag is created or severity changes Then an in-app notification is sent to the task assignee and the unit turn owner with a deep link to the task And an activity feed entry is added to the unit turn containing severity, metric(s), values, thresholds, and computed move-in delta And duplicate notifications for the same task/metric/severity are throttled to at most one per 10 minutes
Risk Events API and Webhooks
Given a registered webhook with secret and a consumer with API access When a risk is created, updated, or cleared Then a POST is sent within 15 seconds to the webhook URL with event_type ∈ {risk_created, risk_updated, risk_cleared}, task_id, unit_id, metric(s), current_value(s), threshold(s), severity, jeopardizes_move_in, forecasted_move_in_date, occurred_at, and idempotency_key And deliveries are signed with HMAC-SHA256 using the shared secret and retried up to 3 times with exponential backoff on 5xx or timeout And a REST endpoint GET /v1/risks?unit_id=…&status=open returns current risks with pagination and filter by severity; responses conform to the published schema
Delay Impact Simulator (What-If)
"As a turn coordinator, I want to simulate delays or duration changes so that I can understand schedule impacts before committing changes."
Description

Provides a sandbox to adjust task durations, start dates, dependencies, and resource assignments, instantly recomputing the critical path and showing deltas to turn completion and move-in. Supports scenario naming, saving, and sharing with teammates; includes guardrails to prevent publishing conflicting changes. Highlights which tasks change criticality and where new bottlenecks may form. Allows one-click promotion of a scenario to the live schedule with audit logging.

Acceptance Criteria
Duration/Start/Dependency/Resource Edits Recompute Critical Path and Deltas
Given a live schedule snapshot is loaded in the simulator When the user changes any of: task duration, task start date, dependency type/lag (FS/SS/FF/SF), or resource assignment Then the engine recalculates the schedule and updates the critical path within 1 second for schedules with ≤200 tasks And the UI shows delta to turn completion and move-in date (± days/hours) with red for delays and green for gains And all recalculated dates honor dependency constraints and resource calendars/availability
Scenario Naming, Saving, Versioning, and Sharing
Given the user has unsaved edits in the simulator When the user saves the scenario Then a required name (3–60 chars) unique within the turn is enforced and a new immutable version is created And the scenario is shareable only with teammates who have project access; viewers can view/duplicate, editors can edit And a success toast confirms save within 500 ms and the activity log records user, timestamp, scenarioId, and version
Criticality Change and Bottleneck Highlighting
Given a recalculation occurs in the simulator When task floats and paths are updated Then tasks that enter or exit the critical path are tagged (Critical/Non‑critical) and visually highlighted And tasks with total float change ≥ 1 hour display the float delta And a Bottlenecks panel lists time windows where any resource utilization exceeds 100%, sortable by severity
Guardrails Prevent Publishing Conflicting Changes
Given a scenario was created from live schedule version V And the live schedule has advanced to version V+1 or the scenario introduces invalid constraints When the user clicks Promote to Live Then the system blocks promotion and shows a Conflict dialog listing impacted tasks (before/after) and validation failures (e.g., circular dependencies, negative lags violating calendars, resource overallocation >100%) And the user must refresh/rebase or resolve issues before promotion is enabled
One‑Click Promotion with Diff Preview and Audit Logging
Given a valid scenario passes all validations When the user clicks Promote to Live and confirms the diff preview Then changes are applied to the live schedule within 3 seconds and a mandatory comment is captured And an audit log entry records user, timestamp, scenarioId, source version, target version, and affected task IDs/fields And assignees on updated tasks receive notifications within 10 seconds
Delta Visualization for Turn Completion and Move‑In Dates
Given a recalculation after any what‑if change When the simulator computes new dates Then the header displays both baseline and simulated completion/move‑in dates side‑by‑side with the numeric delta (± days/hours) And hovering the delta reveals a breakdown of top 3 contributing tasks by path impact And timezone is consistent with the property’s configured timezone
Minimal Resequencing Recommendations
"As a manager, I want the system to suggest the least disruptive resequencing to get back on track so that we can meet deadlines without re-planning the entire turn."
Description

Generates data-driven recommendations to recover time with the least disruption, proposing the smallest set of schedule edits to hit the target move-in date. Considers constraints such as vendor availability, skill requirements, access windows, quiet hours, material lead times, and compliance tasks. Uses heuristic optimization to suggest parallelization, sequence swaps, or micro-scope adjustments while quantifying time saved and risk. Presents alternatives with rationale and confidence, ready for one-click application.

Acceptance Criteria
Minimal Edit Set to Meet Target Move‑In Date
Given a unit turn project with a defined target move‑in date, task graph, and constraints (vendor availability, skills, access windows, quiet hours, material lead times, compliance tasks) When the system generates resequencing recommendations Then at least one recommendation achieves the target move‑in date without violating any constraints And each recommendation displays editedTasksCount, totalShiftHours, predictedTimeSaved, riskScore, confidence, and a rationale And the recommendations are sorted by ascending editedTasksCount, then ascending totalShiftHours, then descending confidence
Constraint‑Respecting Recommendations
Given a project with defined access windows, quiet hours, vendor calendars and skills, material lead times, and compliance task ordering When the system proposes resequencing or parallelization Then zero tasks in any recommendation are scheduled outside access windows or quiet hours And all vendor assignments in recommendations match availability and required skills/licenses And all material‑dependent tasks start no earlier than material receipt plus lead time And all compliance tasks appear in the required order prior to dependent inspections or move‑in And each recommendation reports 0 constraint violations
Parallelization and Sequence Swap Suggestions
Given tasks with no hard dependency between them and sufficient resource availability When the system searches for time recovery options Then recommendations may include parallelizing eligible tasks and swapping the order of soft‑dependent tasks And no recommendation introduces dependency cycles or resource conflicts And each such proposal identifies affected task IDs and quantifies predictedTimeSaved in hours or days And any micro‑scope adjustment in a proposal is ≤ 15% of the original task effort and includes a rationale note
Real‑Time Recalculation After Delay or Duration Change
Given an in‑progress turn where a task start is delayed or a task duration is changed When the update is saved Then the critical path is recomputed and updated minimal resequencing recommendations are generated within 5 seconds for projects with ≤ 50 tasks And the top recommendation either meets the original target move‑in date or, if impossible, presents the nearest achievable move‑in date with the minimal edit set And affected assignees receive a proactive nudge with a one‑click Apply Fix within 30 seconds
One‑Click Apply and Undo of Recommendation
Given a selected recommendation visible to the manager When the manager clicks Apply Then the schedule updates atomically to match the recommendation and the critical path and move‑in date refresh in the UI within 2 seconds And all affected assignees and vendors are notified of changes within 1 minute And an audit log entry records the actor, timestamp, recommendation ID, and before/after diffs And the manager can Undo within 10 minutes to restore the prior schedule with a corresponding audit log
Alternatives with Rationale, Confidence, and Sorting
Given multiple feasible ways to recover time When recommendations are generated Then the system presents 2–5 alternatives when available, otherwise at least 1 And each alternative displays predictedTimeSaved, editedTasksCount, totalShiftHours, riskScore (0–100), confidence (0–100%), and a rationale referencing specific constraints and trade‑offs And alternatives are sorted by minimal resequencing (editedTasksCount first, then totalShiftHours), with ties broken by higher confidence And the user can select any alternative for one‑click Apply
One-Click Fixes & Auto-Reschedule
"As an operator, I want to apply schedule fixes with one click so that the plan, vendors, and tenants are instantly aligned."
Description

Applies selected recommendations or manual adjustments in a single action: updates task dates, dependencies, and assignments; checks conflicts; and recalculates the schedule. Automatically notifies affected assignees and vendors via preferred channels, updates calendars, and posts changes to the work order timeline. Includes rollback within a configurable window and full audit trail for compliance. Ensures changes respect vendor preferences and SLAs configured in FixFlow.

Acceptance Criteria
Apply Selected Recommendation in One Click
Given a unit turn project with selected recommendations and/or queued manual adjustments And the user has schedule edit permission and confirms Apply Fix When the user triggers the one-click apply action Then all affected task dates, dependencies, and assignments are updated in a single atomic transaction And the system validates for circular dependencies, date inversions, and orphaned tasks before committing And, on success, the schedule and critical path are recalculated for the project And the operation completes within 3 seconds for projects up to 200 tasks And a success summary of changes is returned to the user
Enforce Vendor Preferences and SLAs
Given vendors have preferences (working hours, blackout days, capacity) and SLAs configured in FixFlow When a proposed change would violate a vendor preference or SLA Then the system blocks the change and displays the specific violation (rule name, threshold, impacted task) And proposes the smallest resequencing that satisfies all constraints while preserving the move-in date when possible And allows the user to preview the impact before applying And no updates are persisted until a compliant plan is selected and applied
Notify Assignees and Sync Calendars
Given one-click changes are successfully applied And affected assignees and vendors have preferred notification channels configured When the apply action completes Then notifications are sent to each recipient via their preferred channel(s) within 2 minutes And each message includes task identifier, new date/time window, location, work order link, and acknowledgment action And connected calendars (e.g., Google/Outlook) are updated to reflect the new schedule within 2 minutes And per-recipient delivery status (sent, delivered, failed) is recorded for audit
Post Changes to Work Order Timeline and Audit Trail
Given one-click changes are successfully applied When the apply action completes Then a consolidated entry is posted to the work order timeline summarizing the change set with before/after values per field And the audit trail records actor, timestamp, source (recommendation vs manual), fields changed with prior/new values, dependency updates, schedule recalculation result, notifications sent, and calendar sync outcomes And audit entries are immutable and filterable by work order, task, actor, and date range
Rollback Changes Within Configured Window
Given a change set created by One-Click Fix exists within the configured rollback window When a user with rollback permission initiates rollback on that change set Then all affected tasks revert atomically to their prior dates, dependencies, and assignments And the schedule and critical path are recalculated to match the pre-change state And notifications and calendars are updated to reflect the rollback And a new audit entry logs the rollback with a link to the original change set And rollback is disabled after the window expires or when intervening dependent changes prevent a clean rollback, with an explanation provided
Permissions and Concurrent Change Handling
Given role-based permissions are configured and optimistic concurrency is enforced When a user without schedule edit permission attempts a one-click apply Then the action is blocked with a permissions error and no changes are persisted And when a permitted user attempts to apply but underlying tasks have changed since the recommendation/adjustment was prepared Then the system detects version drift and requires refresh or re-simulation before applying And no partial updates occur if any validation or concurrency check fails
Proactive Nudges & Escalations to Assignees
"As a vendor or technician, I want timely reminders with quick actions so that I can stay on schedule without constant calls from managers."
Description

Delivers proactive reminders for upcoming or at-risk critical tasks through in-app, email, and SMS with deep links to accept, confirm arrival, or request reschedule. Supports configurable cadences, quiet hours, and rate limiting. Tracks engagement to inform escalation logic to managers when assignees do not respond. Personalizes content with unit, access, and materials context to increase follow-through, and updates task status upon quick actions.

Acceptance Criteria
Nudge Delivery for At‑Risk Critical Task
Given a critical task on the unit turn is on the critical path and forecasted to jeopardize the move‑in date within the next 48 hours And the assignee has at least one contact channel enabled (in‑app, email, SMS) When the risk threshold is crossed Then the system schedules and sends a proactive nudge across the assignee's enabled channels per preference order within 5 minutes And the message body includes unit identifier, property address, access instructions, materials needed, due date/time, and move‑in date And the message contains three deep links: Accept Task, Confirm Arrival, Request Reschedule pointing to the task context And delivery outcomes per channel are captured (queued, sent, delivered, failed) with timestamps
Cadence, Quiet Hours, and Rate Limits
Given the organization has configured nudge cadences (e.g., T‑48h, T‑12h, T‑2h), quiet hours (e.g., 8pm–7am), and a daily per‑assignee rate limit (e.g., 5) When a task enters an at‑risk state Then the system schedules nudges at the configured cadence relative to the task due time And if a scheduled send falls within quiet hours in the assignee's time zone, it is deferred to the next permitted window and labeled "deferred" And the system enforces the per‑assignee rate limit across channels, skipping excess nudges and logging "rate‑limited" And duplicate nudges for the same task/risk window are de‑duplicated within a 2‑hour window
Engagement Tracking and Manager Escalation
Given a nudge was sent for a critical task When the assignee opens or clicks any channel message Then the engagement is recorded with channel, action type, and timestamp And the escalation countdown is reset When no meaningful engagement (open, click, or quick action) is recorded within the configured threshold (e.g., 2 hours) and the task remains at‑risk Then an escalation is sent to the manager via in‑app and email including task, assignee, aging, impact on move‑in, and recommended one‑click fixes And if the assignee subsequently takes a quick action or updates the task, further escalations for this task episode are suppressed
Personalized Content Completeness
Given a nudge is generated for a critical task When composing the message for any channel Then the content includes: unit number, property name/address, access instructions (lockbox code/entry notes), materials/tools required, tenant access constraints, current task status, due date/time, and target move‑in date And all placeholders are fully resolved; if any field is missing, a defined fallback string is inserted and the event is flagged in logs And links resolve directly to the task view pre‑filtered to the unit with the assignee's context
Quick Actions Update Task State
Given the assignee taps Accept Task from any channel When the deep link is opened and authenticated via single‑use token valid for 24 hours Then the task status is updated to Accepted, the accept timestamp is stored, and watchers are notified Given the assignee taps Confirm Arrival When the deep link is opened and authenticated Then the task status is updated to In Progress, the arrival timestamp is stored, and estimated completion prompts appear Given the assignee taps Request Reschedule When the reschedule flow is completed with a new slot Then the task's scheduled start/end are updated, the critical path is recalculated, downstream tasks are resequenced with minimal changes, and confirmations are sent And all quick actions are idempotent; repeated clicks do not create duplicate updates and expired tokens return a safe error message
Nudge Suppression on Task Progress
Given a nudge is scheduled for an at‑risk critical task When the task transitions to Accepted, In Progress, or Completed before the send time Then the pending nudge is canceled and a "suppressed due to progress" audit entry is recorded And if multiple risk triggers occur within 60 minutes for the same task, the system consolidates into a single consolidated nudge

Auto‑Leveler

Balances work across units, trades, and crews by auto‑resolving resource conflicts and smoothing start times. Suggests least‑disruptive shifts, preserves dependencies, and updates downstream tasks in one click. Keeps crews utilized, eliminates idle gaps, and shortens overall turn duration.

Requirements

Real-Time Conflict Detection
"As a property manager, I want the system to automatically detect and flag scheduling conflicts across crews and trades so that I can prevent double-booking and avoid avoidable delays."
Description

Continuously monitors scheduled tasks across units, trades, and crews to identify overlapping bookings, overtime violations, travel buffer breaches, and SLA risks. Ingests work orders and turn schedules from FixFlow, normalizes time windows, and flags conflicts with severity, root cause, and affected stakeholders. Runs as an event-driven service reacting to task create/update/cancel events and exposes an API for UI badges and downstream suggestion engines. Reduces rework and delays by preventing double-booking before it reaches vendors or tenants.

Acceptance Criteria
Overlapping Booking Detection for Shared Resource
Given an existing task T1 assigned to the same resource (crew/tech) with window [S1,E1] and a new or updated task T2 with window [S2,E2] And all time windows are normalized to UTC (accounting for time zone and DST) When the create/update event for T2 is processed Then a conflict is created within 5 seconds if S2 < E1 and E2 > S1 And the conflict type is OVERLAP with severity computed per org policy thresholds (High/Medium/Low by overlapMinutes) And the conflict includes rootCause "Shared resource double-booked", affectedStakeholders (resourceId, managerId, tenantIds), and conflictingTaskIds [T1,T2] And GET /conflicts?taskId=T2 returns the conflict And no conflict is created if resources differ or if T2.status ∈ {Cancelled, Draft}
Overtime Violation Detection per Crew Policy
Given a resource profile with maxDailyHours and current assigned tasks on date D When a new/updated task assignment causes total scheduled minutes on D to exceed maxDailyHours Then a conflict of type OVERTIME is created within 5 seconds And severity is determined by breachMinutes against policy thresholds And the conflict payload includes breachMinutes and the contributing taskIds And no conflict is created if overtimeOverride=true for the resource on D
Travel Buffer Breach Between Sequential Jobs
Given two sequential tasks Tprev and Tnext for the same resource on the same day with locations Lprev and Lnext And policy.travelBufferMinutes is configured And routing ETA minutes between Lprev→Lnext is available (or falls back to policy.defaultTravelMinutes if routing is unavailable) When an update creates gapMinutes - ETA < policy.travelBufferMinutes Then a conflict of type TRAVEL_BUFFER is created within 5 seconds And the conflict payload includes estimatedTravelMinutes, requiredBufferMinutes, gapMinutes, and severity based on shortfallMinutes And no conflict is created when Lprev and Lnext share the same propertyId
SLA Risk Detection from Schedule and Dependencies
Given a task T with slaDueAt, estimatedDuration, and dependency tasks that must finish before T can start When the current schedule and dependency finish times make the earliest feasible completion of T later than slaDueAt Then a conflict of type SLA_RISK is created within 5 seconds And severity is High/Medium/Low per predictedMissMinutes versus policy thresholds And the conflict payload includes predictedMissMinutes and blockingDependencyIds And no conflict is created if T.sla.optOut=true or T.sla.paused=true
Conflict Lifecycle: Resolution, Idempotency, and Audit Trail
Given an active conflict exists for specific task(s) and type When subsequent events remove the violating condition (e.g., task time shifted, reassigned, or cancelled) Then the conflict is marked resolved with resolvedAt within 5 seconds and no active conflict remains for the same type and tasks And reprocessing the same eventId is idempotent and does not create duplicate active conflicts And all create/resolve actions are recorded in an audit log with timestamps, type, taskIds, and actor=system
Conflict API Contract, Security, and Performance
Given an authorized caller scoped to an org When calling GET /conflicts with filters (taskId, crewId, type, dateRange) and pagination Then the response is 200 with items containing id, type, severity, rootCause, taskIds, affectedStakeholders, createdAt, resolvedAt And p95 latency is ≤ 300 ms for result sets up to 100 items; unauthorized requests return 401; cross-org access returns 403; invalid params return 400 with error codes And GET /conflicts/summary?taskId returns counts by severity for that task
Time Window Normalization Across Time Zones and DST
Given tasks are created/updated across multiple time zones and around DST transitions When events are processed Then all comparisons use normalized UTC windows, preserving originalLocalTime for display And no false overlaps occur across DST fall back/spring forward boundaries And unit tests cover at least two DST boundary cases per supported locale
Least-Disruptive Shift Suggestions
"As a scheduling coordinator, I want suggested schedule changes that minimally impact overall turn timelines so that I can resolve conflicts quickly without creating new problems."
Description

Generates ranked resolution options for detected conflicts that minimize impact on overall turn timelines and tenant disruption. Considers task dependencies, tenant availability windows, vendor SLAs, travel time, crew skill coverage, and overtime costs to propose actions such as micro-shifts in start times, crew swaps, task splits, or resequencing. Surfaces impact metrics (turn duration delta, utilization change, idle gap reduction) for each option and integrates with the conflict detector and dependency engine for feasibility checks.

Acceptance Criteria
Ranked Suggestion List With Impact Metrics
Given an active scheduling conflict detected by Auto‑Leveler for a unit turn And the system has computed candidate resolution options When the user opens the conflict details panel Then at least 3 resolution options are displayed And options are ranked ascending by composite disruption score per current configuration And each option displays turnDurationDelta (hours), utilizationChange (percentage points), idleGapReduction (hours), travelTimeDelta (minutes), overtimeCostDelta (currency) And the displayed metrics match backend calculations within ±1% (or ±1 minute for time-based metrics)
Feasibility Validation Against Dependencies, SLAs, and Tenant Windows
Given tasks with defined predecessor/successor dependencies, vendor SLAs, and tenant availability windows When the engine generates resolution suggestions Then no option violates hard dependencies or required lag times And no option schedules a vendor outside their SLA service window And no option schedules work outside tenant availability windows unless explicitly marked "Requires Tenant Exception" And infeasible options are excluded or labeled "Infeasible" with reason codes: Dependency, SLA, Availability And all shown options carry Feasible=true
Micro‑Shift Start Time Suggestions Within Availability
Given tenant availability windows and a configured maxMicroShiftHours And a conflict that can be resolved by shifting start times When the engine proposes micro‑shift options Then no suggested shift exceeds maxMicroShiftHours And shifted start and end times fall entirely within tenant availability windows And no micro‑shift option introduces new conflicts per the conflict detector And the top-ranked micro‑shift option reduces idle gaps or turnDurationDelta relative to baseline
Crew Swap Suggestions Preserving Skill Coverage
Given crew skill matrices, certifications, and task skill requirements When the engine proposes crew swap suggestions Then the replacement crew satisfies all required skills and active certifications for the task And utilization for affected crews remains within configured maxUtilization% for the planning horizon And travelTimeDelta for swapped crews does not exceed configured maxTravelDelta unless the option is flagged "High Disruption" And no swap assigns a crew with an overlapping assignment or marked unavailable
Task Split and Resequencing to Reduce Idle Gaps
Given a task flagged as splittable with minSplitDuration and allowedSplitCount When the engine suggests task split and resequencing options Then the task is split into segments that each meet or exceed minSplitDuration and do not exceed allowedSplitCount And successor tasks start as early or earlier than baseline without violating dependencies And aggregate idleGap across related tasks is reduced versus baseline And turnDurationDelta is less than or equal to configured maxTurnIncreaseForSplit
One‑Click Apply Updates Downstream Tasks and Resolves Conflicts
Given the user selects a ranked suggestion and clicks Apply When the system commits the change Then the schedule is updated atomically across affected tasks and units And all downstream dependent tasks are recalculated and updated accordingly And the conflict detector reruns and shows zero remaining conflicts for the resolved item And an audit log entry is created with optionId, metrics snapshot, userId, and timestamp
Cost‑Aware Ranking Minimizes Overtime
Given overtime rules and rates are configured When the engine generates and ranks suggestions Then options that introduce overtime are penalized in the disruption score and visually flagged "Overtime" And if at least one feasible non‑overtime option exists, a non‑overtime option appears above any overtime options And if no non‑overtime option exists, the option with minimal overtimeCostDelta ranks highest among overtime options And overtimeCostDelta is computed as sum(overtimeHours × overtimeRate) for all affected resources
Dependency Preservation Engine
"As a project manager, I want schedule adjustments to respect task dependencies and constraints so that critical sequencing is never broken."
Description

Models and enforces task constraints (finish-to-start, start-to-start, lags/leads, must-start-on, cannot-start-before) so that any proposed or applied changes maintain valid sequencing. Pulls dependency rules from scope templates and work order data, validates user selections, and recalculates downstream start/finish times on acceptance. Blocks invalid moves and highlights the specific dependency that would be violated, ensuring inspections, curing/drying times, and permit holds are preserved.

Acceptance Criteria
Blocks Invalid Finish-to-Start Move
Given Task B has a finish-to-start (FS, 0d) dependency on Task A And Task A has a defined finish date/time When a user attempts to set Task B start earlier than Task A finish Then the system blocks the change And displays an error that names the violated dependency rule and the tasks (e.g., "B cannot start before A finishes — FS 0d") And highlights the specific dependency link between A and B And no task dates are persisted
Recalculate Downstream on Accepted Shift
Given a dependency chain exists among tasks with FS/SS relationships and lags/leads And the scheduler proposes shifting an upstream task by a specific offset When the user clicks Accept to apply the shift Then the engine recalculates start/finish for all downstream tasks respecting all dependencies and lags/leads And task durations remain unchanged unless explicitly edited And a list of affected tasks with before/after dates is displayed And the recalculation completes within 1 second for schedules up to 200 tasks And the new dates are persisted to the schedule
Merge Template and Work Order Constraints
Given a scope template defines task dependencies and constraints And a work order provides additional constraints and/or overrides When a new schedule is initialized for a job using the template and work order Then all template dependencies are loaded into the schedule And work order constraints are applied And where conflicts exist, work order constraints override the template And the resulting dependency set is visible in each task’s details And an import summary lists the total dependencies loaded and overrides applied
Enforce Start-to-Start with Lag
Given Task B has a start-to-start (SS) dependency on Task A with a +1 day lag When Task A’s start time is changed Then Task B’s start auto-adjusts to be no earlier than A.start + 1 day And attempts to set Task B start earlier than A.start + 1 day are blocked And the violation message specifies the SS +1d rule and the tasks involved
Respect Must-Start-On and Cannot-Start-Before
Given Task C has a Must-Start-On (MSO) date D and a Cannot-Start-Before (CSB) date E When auto-leveling or a manual move proposes C.start earlier than E or not equal to D when MSO applies Then the engine adjusts C.start to the nearest valid time that satisfies MSO and CSB And if satisfying MSO/CSB would violate a predecessor dependency, the move is blocked And the blocking message identifies the exact conflicting rule(s) And the UI suggests the next valid start that satisfies all constraints
Preserve Permit Holds and Curing Buffers
Given a permit hold prevents start before an approval timestamp And a curing/drying buffer of 48 hours exists as a lag between Task X and Task Y When the schedule is adjusted manually or by Auto‑Leveler Then Task Y never starts before the permit is released nor within the curing buffer window And any attempt to violate these is blocked with a message naming the specific hold or buffer And the hold and buffer appear in dependency details for the affected tasks
One-Click Apply & Notifications
"As a landlord, I want to apply a proposed schedule fix in one step and notify everyone automatically so that changes are implemented consistently and communicated clearly."
Description

Enables applying a selected suggestion in a single action that atomically updates all affected tasks, reservations, and vendor assignments. Triggers automated notifications to tenants, vendors, and crews via email/SMS/push, updates external calendars, and writes a complete audit trail with before/after timestamps and user attribution. Includes pre-commit validation, transactional updates with rollback on failure, and permission checks aligned to FixFlow roles.

Acceptance Criteria
Apply Suggestion Atomically Across Schedule
Given a valid Auto‑Leveler suggestion that affects tasks, reservations, and vendor assignments And a user with permission to apply suggestions is on the review screen When the user clicks "Apply" Then the system validates pre-conditions and shows zero blocking errors And updates all affected tasks, reservations, and vendor assignments in a single atomic transaction And preserves task dependencies and recalculates downstream dates accordingly And returns a success confirmation with counts of entities updated And the end-to-end operation completes within 5 seconds for up to 200 affected tasks And if any step fails, no partial changes persist and the user sees a rollback error with a correlation ID
Pre-Commit Validation Blocks Conflicting Changes
Given a staged suggestion is selected for application When validation runs Then the system checks: role permissions, overlapping reservations, vendor/crew capacity, tenant quiet hours, calendar integration connectivity, and dependency integrity And blocking issues are displayed inline with specific items and reasons And the Apply action is disabled until all blocking issues are resolved And non-blocking warnings are listed and the user can proceed after acknowledgement And validation results are returned within 2 seconds for up to 200 affected tasks
Role-Based Permission Enforcement on Apply
Given FixFlow roles and permissions are configured When a user without "Schedule.ApplySuggestion" attempts to apply a suggestion Then the system denies the action with a 403 and explains required role(s) without exposing sensitive data And logs the attempt in the audit trail When a user with "Schedule.ApplySuggestion" and scope to the impacted properties applies Then the action proceeds And if the suggestion affects restricted vendors or units outside the user's scope, the system blocks apply and identifies the out-of-scope items
Notification Fanout After Successful Apply
Given an apply transaction commits successfully When notifications are generated Then tenants, vendors, and crews receive email/SMS/push according to their preferences and channel availability And each message includes job identifier, property/unit, old and new time window, and contact instructions And messages are localized to recipient locale and time zone And notifications are sent within 60 seconds of commit with delivery status captured And failures to deliver are retried up to 3 times with exponential backoff; persistent failures surface as alerts in activity log
External Calendar Updates with Transactional Guarantees
Given recipients with connected Google/Microsoft calendars When the apply is executed Then existing calendar events are updated or canceled and new events are created to reflect the new schedule And updates are idempotent using provider event IDs to avoid duplicates And if any calendar update fails after configured retries, the overall apply is rolled back and no notifications are sent And on success, calendars reflect the new times within 60 seconds and contain links back to FixFlow
Comprehensive Audit Trail for One-Click Apply
Given an apply operation is initiated When the operation completes (success or rollback) Then an immutable audit record is created with user ID, role, suggestion ID, timestamp (UTC), correlation ID, and client IP And per-entity before/after snapshots are stored for all changed tasks, reservations, and vendor assignments And notification and calendar update outcomes are logged with provider message IDs And audit entries are queryable by date range, property, user, and suggestion ID, and exportable as CSV
Idempotency and Concurrency Control on Apply
Given a user double-clicks "Apply" or a network retry occurs When duplicate apply requests with the same idempotency key are received within 60 seconds Then only one transaction is executed and subsequent requests return a 200 with the original result And if the underlying data changed since suggestion generation, the system detects a version conflict and blocks the apply with a refresh prompt And if another user applies a conflicting change concurrently, at most one transaction commits and the other rolls back with a conflict error
Two-Way Vendor Calendar Sync
"As a vendor crew lead, I want my crew’s calendar to stay in sync with FixFlow so that our availability is accurate and schedule updates appear instantly."
Description

Integrates with vendor calendars (Google, Microsoft 365/Outlook, iCal) to read availability and blackout periods and to push confirmed schedule updates. Supports OAuth, per-crew permissions, time zone normalization, recurring events, and webhook-based change detection with retries for reliability. Prevents desynchronization by reconciling external changes against FixFlow tasks and surfacing conflicts for resolution via Auto-Leveler.

Acceptance Criteria
OAuth Connection and Token Lifecycle
Given a vendor initiates OAuth with Google, Microsoft 365/Outlook, or iCal/CalDAV When authorization completes Then FixFlow stores access/refresh tokens encrypted, scoped to minimum calendar permissions, and logs provider, crew, and timestamp Given an access token is expired or within 5 minutes of expiry When FixFlow attempts a provider call Then FixFlow refreshes the token using the refresh token without user action and proceeds Given the provider revokes or invalidates tokens When FixFlow receives invalid_grant/401 Then FixFlow marks the connection Disconnected, halts writes, and notifies the vendor and property manager Given crew-level calendar scoping is configured When OAuth is completed Then FixFlow can only read/write the calendars assigned to that crew
Calendar Availability and Blackout Import
Given a vendor crew is connected to a calendar When FixFlow syncs the next 60 days Then it imports all events (including expanded recurrences) from assigned calendars and computes busy/free windows Given all-day events or events tagged as blackout When imported Then FixFlow marks those periods as unavailable for scheduling Given unauthorized calendars exist for the vendor When fetching availability Then FixFlow ignores events from calendars not explicitly assigned to the crew Given sync completes When the scheduling grid is displayed Then busy windows match the provider calendar within ±1 minute tolerance
Time Zone and DST Normalization
Given a vendor calendar is in time zone A and the property is in time zone B When viewing or scheduling a task Then FixFlow displays times correctly in both contexts with correct offsets Given a DST transition occurs within an event window When a task spans the transition Then start/end times remain correct and the intended duration is preserved Given an imported event lacks explicit time zone When FixFlow processes it Then FixFlow applies the calendar's default time zone for normalization
Push Confirmed Schedule Updates to External Calendars
Given a FixFlow task is confirmed for a connected crew When push sync runs Then FixFlow creates/updates an external calendar event with title "[FixFlow] <TaskID>: <Unit/Trade>", location, start/end, and description with job notes Given a confirmed task is edited or canceled in FixFlow When changes are saved Then the external event is updated or deleted within 30 seconds and preserves the provider event ID Given the provider connection is read-only (ICS feed) When push is requested Then FixFlow updates a per-crew ICS feed instead of direct write and marks the connection as Read-Only Given a provider/API/network error occurs during push When retries are triggered Then FixFlow retries up to 5 times with exponential backoff and surfaces failure after exhaustion
Webhook Change Detection and Processing
Given provider webhooks are configured for the connected calendar When an external event is created, updated, or deleted Then FixFlow receives the webhook and processes the change within 60 seconds Given duplicate webhook deliveries occur When processing the change Then FixFlow deduplicates using provider change IDs and processes idempotently Given a 5xx processing error or temporary provider failure When handling a webhook Then FixFlow retries with exponential backoff; after max retries, it writes to a dead-letter queue and alerts the owning manager
Conflict Detection and Auto‑Leveler Surfacing
Given an external change overlaps a confirmed FixFlow task or violates a dependency When reconciliation runs Then the task is flagged In Conflict and appears in Auto‑Leveler with at least two least‑disruptive shift suggestions that preserve dependencies Given a manager selects a suggested shift in Auto‑Leveler When the change is confirmed Then FixFlow updates the internal schedule and pushes updates to the external calendar in one click Given a manager dismisses a suggestion When dismissal is saved Then the conflict remains visible until resolved or overridden, and an audit entry is recorded
Periodic Reconciliation and Drift Repair
Given two‑way sync is enabled When the nightly reconciliation job runs Then FixFlow compares external events to internal mappings for the next 30 days, repairs missing/extra events, and surfaces unresolved discrepancies in a report Given an external event linked to a FixFlow task is deleted outside FixFlow When reconciliation detects the deletion Then the FixFlow task is flagged for action and the manager is prompted to reschedule or mark canceled Given mapping identifiers (provider event ID and etag) exist When an external event changes Then FixFlow updates mappings atomically to prevent duplication or orphan records
Utilization Balancer & Smoothing
"As an operations manager, I want workload balanced across crews and times so that utilization is high and idle gaps are minimized."
Description

Calculates target utilization per crew/trade and smooths start times to eliminate idle gaps while honoring SLAs and tenant availability. Uses rolling-horizon optimization to distribute workload evenly across days and hours, recommending micro-adjustments that increase throughput without violating constraints. Provides visual utilization charts and actionable recommendations embedded in the schedule view.

Acceptance Criteria
Target Utilization Calculation per Crew/Trade
Given crew/trade calendars with working hours, PTO/holidays, and an SLA reserve buffer configured at the org or trade level When the Utilization Balancer runs for a selected date range Then target utilization is computed per crew and per trade using available working hours minus SLA reserve and blocked time, expressed as a percentage rounded to 1 decimal And the targets display next to each crew/trade in the schedule header within 2 seconds of run completion And the API endpoint for utilization returns the same values with a generated_at timestamp And when any input (capacity, PTO, SLA buffer) changes, targets are recalculated and the UI refreshes within 5 seconds
Smoothing Start Times Without Violating Constraints
Given a schedule containing idle gaps greater than 30 minutes within crew shifts and constraints for tenant availability, SLAs, travel buffers, and task dependencies When smoothing is executed for the selected horizon Then total idle time per crew shift is reduced by at least 40% compared to pre-smoothing, unless a constraint prevents further reduction And no task is moved outside of its tenant availability window And no task violates its SLA due time And task precedence and trade dependencies are preserved And no individual task start time shifts by more than ±45 minutes unless marked as flexible And any remaining gap >30 minutes is flagged with a constraint reason
Rolling-Horizon Workload Balancing
Given a rolling horizon H of 7 days and a baseline utilization profile When the optimizer runs Then only tasks with start times within the next H days are candidates for movement; tasks outside H are unchanged And the standard deviation of daily utilization across crews within H decreases by at least 25% from baseline And the difference between max and min daily utilization across crews within H is ≤10 percentage points after optimization And the optimization completes within 30 seconds at P95 for up to 500 active tasks and 50 crews
Actionable Micro-Adjustment Recommendations & One-Click Apply
Given the schedule view is open with utilization overlays enabled When the balancer identifies over- or under-utilization Then a recommendations panel lists at least 3 ranked micro-adjustments (if available), each showing tasks affected, time deltas, predicted utilization delta %, and SLA risk level And selecting Apply on a recommendation updates the schedule, resolves conflicts, and recomputes utilization within 3 seconds And the user can Undo the applied recommendation in one click to fully restore prior assignments and times And the system logs an audit entry with user, recommendation ID, before/after timestamps, and outcomes
Embedded Visual Utilization Charts in Schedule
Given a selected date range and crews/trades in scope When the user toggles Utilization in the schedule view Then the UI displays per-crew and per-trade utilization against target with color bands: under <70%, optimal 70–90%, over >90% And hovering shows hour/day, % utilized, capacity hours, and tasks contributing And charts update within 1 second after smoothing or applying a recommendation And charts meet WCAG AA for color contrast and provide screen-reader labels And the user can export utilization as PNG and CSV And an empty state appears with guidance when no data is available
Conflict Auto-Resolution with Dependency Preservation
Given overlapping assignments or double-booked resources exist When smoothing or a recommendation is applied Then double-bookings for crews, equipment, and trades are reduced to zero unless blocked by an explicit constraint And task dependencies (finish-to-start, tenant sequencing) remain satisfied And any unresolvable conflict is flagged with a reason and next-best suggestion And affected vendors and tenants receive notifications or queued updates within 60 seconds when times change And an audit trail records each change with before/after times, constraint reasons, and notifier status
What-If Simulation Preview
"As a small property manager, I want to simulate schedule adjustments before applying them so that I can choose the option with the best overall impact."
Description

Offers a sandbox to preview the impact of candidate schedule changes before committing. Simulates effects on turn duration, crew utilization, travel time, tenant disruption score, and cost deltas, allowing side-by-side comparison of multiple scenarios. Supports exporting the chosen plan and capturing rationale for auditability.

Acceptance Criteria
Create and Compare Multiple Simulations
Given a baseline schedule with at least 20 tasks across 3 trades and 2 crews And the user has Planner or Owner role When the user creates up to 5 simulation scenarios by adjusting task start dates or crew assignments Then the system calculates for each scenario: turn duration (days), average crew utilization (%), total travel time (minutes), tenant disruption score (0–100), and cost delta (currency) relative to baseline And displays the scenarios side-by-side in a comparison table And allows sorting the table by any metric And highlights the best and worst scenario per metric And persists scenario names and notes for later review
Accurate Impact Metrics with Constraint Preservation
Given a simulation proposes changes to tasks with precedence dependencies and crew capacity limits When the proposed changes would break a dependency or overbook a crew Then the preview auto-resolves using Auto‑Leveler rules to preserve dependencies and respect crew capacity And flags each resolved conflict with a visible badge and a resolution summary And produces no preview schedule containing broken dependencies or overallocated resources And recomputes metrics within 3 seconds for schedules up to 200 units and 500 tasks
Export Chosen Plan with Rationale
Given at least one simulation exists and the user selects one as the chosen plan When the user clicks Export Then the system requires a rationale of at least 15 characters before proceeding And generates JSON, CSV, and PDF exports containing: baseline vs chosen metrics, list of changed tasks with old/new start/end, assigned crew/vendor, travel time deltas, and cost deltas And names files as {portfolio}-{project}-{scenarioId}-{yyyyMMdd-HHmmss} And writes an audit record with user ID, role, timestamp, scenarioId, rationale, metrics before/after, and export URIs And shows a success confirmation with links to the files
Apply Selected Simulation to Live Schedule
Given a chosen simulation is less than 30 minutes old relative to its baseline snapshot When the user clicks Apply to Live Schedule Then the system validates baseline drift; if drift is detected, it prompts the user to rebase or cancel And applies all changes atomically; if any update fails, no changes are committed And updates downstream tasks to preserve dependencies And completes within 10 seconds for up to 500 task updates And provides an Undo option available for 10 minutes that fully reverts the apply and records an inverse audit entry
Responsive Sandbox Performance and Limits
Given a portfolio of 25–200 units and up to 500 tasks When the user runs a simulation or edits up to 20 tasks in an existing scenario Then initial computation completes within 3 seconds at the 95th percentile And recalculation after small edits completes within 1 second at the 95th percentile And a progress indicator is shown during computation And no more than 10 open scenarios per project are allowed; attempts beyond the limit show a clear limit message And scenario edits auto-save within 2 seconds of change
Metric Assumptions and Inputs Visibility
Given the user is viewing the simulation comparison When the user opens Metrics Details Then the system displays input versions and timestamps for travel-time matrix, crew calendars, vendor SLAs, and tenant quiet hours used And shows a staleness warning if any input is older than 24 hours with an option to refresh And upon refresh, recomputes metrics, updates the comparison, and logs the input versions in the audit
Role-Based Access to Simulation
Given a user is authenticated When the user attempts to create, export, or apply simulations Then only Planner or Owner roles can create and apply simulations And Viewer role can open and compare scenarios but cannot export or apply And unauthorized attempts return HTTP 403 and a UI message explaining required permissions And all actions are logged with user ID and role

Vendor Slotting

Requests real‑time time slots from preferred vendors right inside the timeline. Proposes windows based on dependency completion and travel time, supports one‑tap accept or alternate offers, and auto‑escalates to backups if unconfirmed. Cuts coordination calls and locks schedules into Turnboard with reliable ETAs.

Requirements

Vendor Availability Integration
"As a property manager, I want to pull real-time availability from preferred vendors so that I can propose accurate appointment windows without back-and-forth calls."
Description

Integrate FixFlow with preferred vendors’ calendars to request and retrieve real-time availability windows via API (OAuth2), iCal feeds, or a lightweight vendor portal. Support vendor business hours, blackout dates, technician-level capacity, service areas, and time zones. Cache and refresh availability on a configurable interval with graceful degradation to manual slot entry when APIs are unavailable. Provide validation for overlapping bookings and rate limiting, and surface integration health status. Ensures reliable, up-to-date slots without phone/email coordination and lays the foundation for automated scheduling actions.

Acceptance Criteria
OAuth2 Calendar API Availability Retrieval
Given a preferred vendor is connected via OAuth2 with valid tokens and required scopes When FixFlow requests availability for the next 14 days within the vendor’s business hours Then the vendor API responds with HTTP 200 within the configured timeout and rate limits And returned windows exclude blackout dates and closed days And each window includes start and end in ISO 8601 with timezone offset and technician identifiers And each window includes remaining capacity per technician or per window And all windows are normalized to the property’s timezone for display and stored with source metadata
iCal Feed Parsing with Time Zones and Recurrence
Given a valid iCal feed URL is configured for a vendor or technician When the feed is fetched and parsed Then all events in the lookahead horizon are mapped to unavailable blocks And RRULE recurrences and EXDATE exceptions are correctly expanded And VTIMEZONE data is applied so DST transitions are accurate And malformed events are skipped with warnings without failing the whole import And the next refresh is scheduled per the configured interval
Technician-Level Capacity and Service Area Filtering
Given a vendor defines technicians with capacities and service areas When availability is requested for a property address and geo coordinates Then only windows with at least one eligible technician whose service area contains the property are returned And window capacity reflects the sum of eligible technicians’ remaining capacity And windows outside vendor business hours are excluded And blackout dates override all availability
Vendor Portal Availability Entry with Business Hours and Blackouts
Given a vendor without API or iCal access signs into the lightweight portal When the vendor creates or edits availability windows per technician Then the UI enforces vendor business hour boundaries and technician time zones And prevents overlaps with existing unavailable events and existing bookings And supports setting blackout dates that remove availability And saves entries in ISO 8601 with source="portal" And new availability is immediately queryable in FixFlow
Configurable Caching and Refresh with Graceful Degradation to Manual Entry
Given a cache TTL is configured per vendor source When availability is requested and cached data is fresh Then cached data is served with a lastUpdated timestamp When cached data is stale and upstream returns 200 or 304 Then the cache is refreshed and the latest data is served When upstream calls time out or error for N consecutive attempts Then the integration is marked Degraded and users are prompted to add manual slots via the portal And last-known availability remains visible with a freshness disclaimer until expiry
Overlapping Booking Detection and Prevention
Given existing bookings and held slots per technician are stored When a new hold or booking is attempted for a technician/time window Then the system blocks overlaps that would exceed technician or window capacity And returns a validation error identifying the conflicting booking or hold And suggests the next three non-overlapping windows from current availability And records the conflict in the audit log
Rate Limiting Backoff and Integration Health Status
Given a vendor API returns 429s or rate-limit headers When limits are approached or exceeded Then requests are throttled using exponential backoff and Retry-After guidance And the client enforces a maximum concurrent request count per vendor And integration health reflects request success/error rates as Healthy, Degraded, or Disconnected And health status and last sync time are visible in Integration Settings and the Vendor timeline
Dependency- and Travel-Aware Slot Proposals
"As a dispatcher at a vendor, I want the system to propose slots that factor in readiness and travel time so that my schedule remains efficient and realistic."
Description

Generate proposed visit windows that account for job readiness (e.g., prerequisite tasks complete, parts delivery ETA, tenant access constraints), vendor travel time between jobs, expected job duration, and building restrictions (quiet hours, elevator bookings). Use a mapping service to estimate drive times with buffers for traffic variability and add configurable prep/wrap times. Offer 2–3 best-fit windows inside the timeline, respecting SLAs and vendor business hours. Continuously re-score proposals if dependencies change to keep options realistic and efficient.

Acceptance Criteria
Feasible Windows Respecting Dependencies and Restrictions
Given a work order with defined prerequisites (required tasks, parts delivery ETA), tenant access windows, building restrictions (quiet hours, elevator bookings), and vendor business hours When the system generates visit proposals for that work order Then it presents between 2 and 3 distinct feasible visit windows And each window starts no earlier than the latest dependency completion ETA And each window is fully within at least one tenant access window And each window does not overlap building quiet hours or elevator blackout times And each window falls within the vendor’s business hours And each window duration equals expected job duration plus configured prep and wrap times And no window overlaps the vendor’s already scheduled commitments
Travel-Time and Buffer-Accurate Window Calculation
Given the vendor’s prior and next scheduled jobs (if any), their locations and planned times, the candidate job location, and configured drive-time buffer and minimum buffer values When the system computes feasible windows Then it queries the mapping service for time-of-day travel estimates between relevant legs And it applies the configured buffer to each travel estimate to produce conservative travel times And it includes configured prep and wrap times in the total time needed And it rejects any window where prior job end + buffered travel + prep would be later than the proposed start And it rejects any window where proposed end + buffered travel + wrap would be later than the next job start
SLA and Vendor Business Hours Compliance
Given a work order with an SLA deadline and a vendor’s business hours calendar When proposals are generated Then at least one proposed window begins before the SLA deadline if a feasible option exists And if no feasible window can meet the SLA, the system still returns the earliest feasible window and flags it with “SLA Risk” including the blocking constraints And no proposed window starts outside vendor business hours unless a vendor override flag is set on the work order
Continuous Re-scoring on Dependency Changes
Given proposals already exist for a work order And a dependency input changes (e.g., prerequisite task status, parts ETA, tenant access window, building restriction) When the change is saved Then the system re-scores and regenerates proposals within 15 seconds And previously invalid windows are withdrawn from the timeline and replaced with updated options And the timeline displays an “Updated” indicator with a timestamp and reason summary And an audit log entry is recorded capturing old and new proposals and the triggering change
Route-Aware Sequencing with Neighboring Jobs
Given a vendor has a scheduled job immediately before and/or after the candidate job on the same day When generating proposal windows for that day Then each proposed window start time is greater than or equal to (prior job planned end + buffered travel + prep) And each proposed window end time is less than or equal to (next job planned start − buffered travel − wrap) And proposals that satisfy both constraints are preferred and ranked higher than those that satisfy only one And no returned proposal causes an overlap or negative slack with neighboring jobs
Tenant Access and Building Blackout Enforcement
Given tenant-provided access constraints (e.g., Tu/Th 14:00–18:00) and building blackout periods (quiet hours, elevator reservations) When the system proposes windows Then all proposed windows are fully contained within permitted access times and do not overlap any blackout period And if fewer than 2 feasible windows exist, the system returns the maximum feasible set and flags the availability as “Limited” with the blocking constraints listed
One-Tap Accept & Alternate Offer Workflow
"As a vendor scheduler, I want to accept a proposed slot or quickly suggest an alternate so that we can lock in a workable time without delays."
Description

Provide an in-timeline action for vendors to accept a proposed slot with one tap or submit a structured alternate offer (new window, earliest arrival, reason code). Include role-based access for managers/techs, mobile-first UI, and instant notifications to stakeholders. Validate conflicts in real time and prevent double-booking. On acceptance or alternate, persist an audit trail and update the request status. Reduce friction by enabling quick confirmations that sync downstream systems automatically.

Acceptance Criteria
Mobile One‑Tap Accept on Proposed Slot
Given an authenticated vendor user viewing a work order timeline on a mobile device, when a proposed slot is displayed, then an “Accept” action is visible above the fold and enabled. Given the vendor taps “Accept” once, when the action is processed, then the booking is confirmed, the request status updates to “Vendor Scheduled,” and the confirmed window is shown on the timeline within 2 seconds. Given network latency or double‑tap, when the vendor attempts to accept the same slot again, then the UI is debounced and no duplicate bookings are created. Given a successful accept, when downstream systems are synced, then Turnboard reflects the scheduled slot and an ETA is computed from travel time rules within 5 seconds.
Structured Alternate Offer Submission
Given an authenticated vendor user, when they choose “Propose Alternate,” then the form requires either a new time window (start/end) or an earliest arrival time and a mandatory reason code from a configured list. Given the vendor enters values, when the proposal is submitted, then inputs are validated (start < end, within vendor working hours, time zone normalized) and conflicts are checked before acceptance of the proposal. Given validation passes, when the proposal is saved, then the request status updates to “Vendor Proposed Alternate,” the proposed window/earliest arrival and reason code persist to the timeline, and the accept action is disabled until manager decision. Given validation fails, when the vendor submits, then inline error messages identify the exact invalid fields and the proposal is not saved.
Role‑Based Access and Permissions Enforcement
Given role assignments exist, when a user without Vendor, Manager, or Tech roles views the timeline, then Accept and Propose Alternate actions are not visible. Given a Manager or Tech with Schedule:Manage permission, when they view the timeline, then they can perform Accept or Propose Alternate on behalf of the vendor and their role is recorded. Given a vendor not assigned to the work order, when they attempt to act via deep link, then the system denies with a 403 and no state change occurs. Given any action (accept or alternate), when it is saved, then the audit record captures actor ID, role, org, timestamp (UTC), and channel (mobile web).
Real‑Time Conflict Validation and Double‑Booking Prevention
Given a vendor attempts to accept a slot, when the system detects an overlap with an existing commitment including travel buffer, then the accept is blocked and the vendor is prompted with the next three available windows. Given two users attempt to accept the same slot concurrently, when the first write succeeds, then the second receives a conflict message and no duplicate booking is created (atomicity guaranteed). Given a vendor proposes an alternate, when the proposed time conflicts with another job or violates minimum travel time rules, then submission is rejected with specific conflict details. Given a confirmed booking exists, when the vendor tries to accept a different slot for the same work order, then the system requires explicit reschedule confirmation and preserves the previous slot in audit history.
Instant Stakeholder Notifications on Accept or Alternate
Given a successful accept, when the booking is confirmed, then notifications are sent to tenant, property manager, and vendor coordinator via configured channels within 30 seconds, including job ID, date, arrival window/ETA, and contact info. Given an alternate is proposed, when the proposal is saved, then the manager receives an approval task and notification with the proposed window and reason code; the tenant is not notified until approval. Given any stakeholder has opted out of a channel, when notifications are sent, then that channel is skipped and others proceed without error. Given a notification is undeliverable, when a bounce or failure is detected, then it is retried up to 3 times over 10 minutes and surfaced in the activity log.
Audit Trail Creation and Downstream Sync
Given an accept or alternate action occurs, when the record is saved, then an immutable audit entry stores previous and new schedule, actor, role, timestamp (UTC), reason (if alternate), and source, retrievable via API and timeline. Given a booking is confirmed or updated, when integrations are enabled, then a webhook is fired with the schedule payload and acknowledged or retried with exponential backoff (3 attempts) on failure without losing state. Given a booking is confirmed, when Turnboard sync runs, then the board reflects the slot within 5 seconds and the vendor iCal feed updates within 60 seconds. Given any sync fails after all retries, when the system flags the event, then a sync_needed flag is set and an alert is visible to managers without rolling back the user‑visible schedule.
Auto-Escalation to Backup Vendors
"As a property manager, I want unconfirmed requests to auto-escalate to backup vendors after a set timeout so that work orders keep moving and SLAs are met."
Description

If a preferred vendor does not confirm within a configurable timeout or declines, automatically escalate the request to the next vendor in a ranked backup list that matches trade, geography, and capacity. Support sequential or parallel pings (configurable), cooldowns to avoid spam, and stop conditions when a confirmation is received. Maintain a complete audit trail of outreach attempts, timestamps, and responses. Notify the property manager and tenant on escalations to maintain transparency and SLA adherence.

Acceptance Criteria
Sequential Escalation After Timeout
Given a work order with a preferred vendor and a ranked backup list, and sequential mode is enabled with a configured confirmation timeout When the preferred vendor does not confirm or declines before the timeout elapses Then the system escalates to the next matching vendor in rank within 60 seconds of the timeout And marks the previous attempt as Timed Out or Declined with a timestamp and reason And repeats escalation in rank order until a vendor confirms or the list is exhausted And stops escalation immediately upon the first confirmation and prevents further pings
Parallel Pings with Stop-on-First-Confirm
Given a work order with parallel escalation enabled and a configured max concurrent pings (N) When escalation begins Then the system sends requests concurrently to the top N matching vendors And upon the first vendor confirmation, the system immediately cancels all outstanding requests and records the cancellations with timestamps And ensures only one assignment is created and visible in the timeline And no additional vendors are contacted after the first confirmation
Cooldown Enforcement to Prevent Vendor Spam
Given a configured cooldown period for re-contacting the same vendor on the same work order When a vendor has been contacted and either declined or timed out Then the system must not re-contact that vendor again until the cooldown period has fully elapsed And any suppressed attempt due to cooldown is logged with a reason of Cooldown Active And re-contact after cooldown is permitted only if no confirmation has been received and the vendor still matches criteria
Matching by Trade, Geography, and Capacity
Given the work order has a required trade, property location, and requested time window When selecting vendors for escalation Then the system includes only vendors whose trade matches the required trade And whose service area/geography covers the property location And who have available capacity for the requested time window And vendors failing any criterion are skipped and logged with the unmet criteria And if no vendors match, the system halts escalation and notifies the property manager within 60 seconds
Audit Trail of Outreach Attempts
Given any outreach (send, cancel, response) occurs during escalation When the event is processed Then the system records an immutable audit entry with: vendor ID, action (send/cancel/response), outcome (sent/declined/timed out/confirmed), timestamp, channel(s) used, correlation/group ID for parallel batches, actor (system or user), and related request payload version And the audit trail is presented in chronological order in the work order timeline and is exportable And every escalation decision (e.g., skip due to mismatch or cooldown) has a corresponding audit entry with reason
Transparent Notifications to PM and Tenant
Given an escalation occurs due to timeout or decline When the system escalates to the next vendor(s) Then the property manager and tenant are notified within 60 seconds with the reason for escalation, vendor(s) contacted, and the updated expected response/ETA window And when a vendor confirms, both stakeholders receive a confirmation notification and the final ETA And no duplicate notifications are sent for the same state change
Turnboard Sync & ETA Lock-In
"As a property manager, I want confirmed appointments to be locked into Turnboard with reliable ETAs so that everyone references the same schedule."
Description

Upon confirmation, create a locked appointment on Turnboard with vendor, unit, trade, and ETA, preventing accidental edits that cause conflicts. Sync ETA updates, travel start, on-site, and completion timestamps from vendor actions or telematics (if available). Detect conflicts and propose reschedule options when delays occur. Provide .ics export and two-way updates with external calendars where configured. Establish a single source of truth for schedules and ETAs across teams and tenants.

Acceptance Criteria
Confirmed Booking Creates Locked Turnboard Appointment
- Given a vendor accepts a proposed time slot, when the acceptance is recorded, then a Turnboard appointment is created within 2 seconds. - The appointment includes vendor ID and name, unit ID and address, trade, date, start time, end time (or ETA window), request ID, and appointment ID. - The appointment status is set to Locked at creation. - The appointment is visible on Turnboard calendar and the request timeline within 2 seconds of creation across web and mobile. - An audit log entry is created with actor, timestamp, and source "Vendor Slotting".
Edit Protection and Conflict Prevention
- Locked fields (vendor, unit, trade, date, start time, end time/ETA window) are non-editable for users without the Schedule Override permission. - Unauthorized edit attempts are blocked with a message and no changes are persisted; the attempt is logged with user ID and timestamp. - Users with Schedule Override can modify locked fields only after entering a reason; changes are versioned and notifications are sent to tenant, vendor, and PM within 60 seconds. - Edits that create double-bookings for the vendor or overlapping unit access are blocked unless an alternative time is selected. - Unlocking requires explicit confirmation and re-locks automatically after the update is applied.
Real-Time ETA and Milestone Sync from Vendor App
- When the vendor marks "Start travel," Turnboard sets the travel_start timestamp within 10 seconds. - When the vendor marks "On site," Turnboard sets the on_site timestamp within 10 seconds. - When the vendor marks "Complete," Turnboard sets the completion timestamp and closes the appointment within 10 seconds. - Vendor-entered ETA updates adjust the appointment ETA window on Turnboard within 10 seconds and notify tenant and PM. - Duplicate vendor events within a 60-second window are deduplicated; all applied updates are recorded with source "Vendor App."
Telematics-Based Auto-Updates
- If telematics is connected and consented, exiting the vendor home geofence triggers travel_start and recalculates ETA when signal is newer than 2 minutes and accuracy ≤ 100 m. - Entering a 100 m geofence around the unit address sets on_site; leaving the geofence sets left_site. - Telematics cannot auto-complete jobs; completion requires an explicit vendor action. - Manual milestone entries from the vendor never move timestamps backward relative to telematics-derived times; conflicts are flagged for review. - Telematics updates can be disabled per appointment; when disabled, no telematics events alter milestones or ETA.
Conflict Detection and Assisted Rescheduling
- When ETA drift exceeds the locked window by >5 minutes, the appointment is marked At Risk and affected stakeholders are notified. - The system evaluates vendor schedule, travel time, and dependencies, and proposes at least 3 alternative slots within the next 72 hours ordered by minimal total delay. - Selecting a proposed slot updates the Turnboard appointment, preserves the appointment ID, versions the schedule, and notifies tenant and vendor within 60 seconds. - If no confirmation occurs within 15 minutes, escalation rules trigger outreach to backup vendors and update the timeline. - External calendar events linked to the appointment are updated to reflect the new time; conflicts created by the new time are flagged before confirmation.
Calendar .ics Export and Two-Way Sync
- Users can export an .ics file that includes title, location, start/end times, request ID, vendor, and trade; importing the file reproduces the correct time zone and reminders. - With Google/Microsoft calendar integration enabled, creating the appointment also creates a single external event with a stable externalEventId. - Updates to start/end times and notes sync bidirectionally within 60 seconds; locked fields (vendor, unit, trade) are not editable from external calendars. - External edits to locked fields are reverted on the next sync and the editor receives a notification of the rejected change. - Canceling in Turnboard cancels the external event; canceling externally requires Turnboard confirmation before the appointment is canceled.
Single Source of Truth Consistency Across Surfaces
- The ETA window and milestone timestamps displayed on PM dashboard, vendor portal, tenant portal, and notifications match exactly within 5 seconds of any update. - Each surface shows a Last Updated timestamp and source for ETA/milestone changes. - Clients with data older than 30 seconds show a stale-state indicator and auto-refresh on reconnect. - Reporting exports and APIs return the same appointment data for the given appointment ID as shown on Turnboard at export time. - Hourly consistency checks detect and log mismatches across data stores; zero mismatches over 24 hours in staging is required before release.
Tenant Notifications & Self-Service Reschedule
"As a tenant, I want to receive and confirm proposed visit windows or request a different time so that the vendor arrives when I can provide access."
Description

Notify tenants via SMS/email/push with proposed windows, confirmation status, and preparation instructions. Allow tenants to confirm, choose an alternate window within guardrails, or provide access notes (pets, lockbox codes, interpreter needed). Respect quiet hours and language preferences, and write all actions back to the timeline. If a tenant declines or cannot accommodate, trigger a reschedule flow and re-run slot proposals to find the next best window automatically.

Acceptance Criteria
Proposed Window Notification Delivery and Content
Given a new appointment window is posted to the work order timeline by Vendor Slotting and the tenant has at least one notification channel enabled When the window is created or updated to Awaiting Tenant Then the tenant is notified via all enabled channels (SMS, email, push) within 60 seconds And the notification includes property address, appointment date, window start and end time, current confirmation status, preparation instructions, and a deep link to confirm or choose another time And all times in the message are displayed in the tenant’s local time zone And per-channel delivery success or failure with timestamps is recorded on the work order timeline
Tenant Confirms Appointment From Any Channel
Given the tenant receives a notification with a confirm action via SMS, email, or push When the tenant confirms the proposed window via link or supported quick-reply Then the appointment status updates to Tenant Confirmed And the confirmation is locked into Turnboard with the latest ETA if available And vendor and property manager are notified on their preferred channels And the action is written to the timeline including actor=Tenant, channel, timestamp, and the confirmed window details
Tenant Selects Alternate Window Within Guardrails
Given the tenant opens the reschedule flow from the notification link And alternate slots exist that satisfy vendor availability, property guardrails, dependencies, and travel-time constraints When the tenant selects an alternate slot Then the system validates the selection is within guardrails and not already reserved And the appointment updates to Tenant Proposed and is sent to the vendor for one-tap accept or alternate offer And if the vendor accepts within the configured SLA, the status updates to Scheduled and the tenant receives confirmation And if the vendor does not accept within the configured SLA, the request auto-escalates per backup vendor rules And all actions and status changes are recorded on the timeline
Tenant Provides Access Notes and Special Requirements
Given the tenant opens the access and preparation notes section from the notification or portal When the tenant submits notes including pets on premises, access instructions (e.g., lockbox code), preferred contact method, and interpreter needed with language Then the notes are saved to the work order and associated with the appointment And the notes are visible to the assigned vendor and property manager And a timeline entry records that access notes were added or updated with timestamp and actor=Tenant
Quiet Hours and Language Preferences Enforcement
Given the tenant has quiet hours and a language preference configured When a notification would be sent during the tenant’s quiet hours window Then the system queues the notification and sends it at the end of quiet hours And the notification content uses the tenant’s preferred language template And the timeline records both the scheduling and actual send time with channel and language used
Auto-Reschedule on Tenant Decline or Inability to Accommodate
Given the tenant declines the proposed window or indicates none of the displayed options are workable When the decline is submitted Then the system initiates the reschedule flow and re-runs slot proposals using current constraints and travel-time data And the tenant is presented with the next-best set of available windows (minimum of 3 where possible) And if no vendor confirms an option within the configured SLA, the request auto-escalates to backup vendors And all proposals, selections, notifications, and status changes are written to the timeline

Lead‑Time Radar

Attaches materials and appliance SKUs to tasks, tracks POs, and compares supplier ETAs to needed‑by dates. Warns early on risk, suggests alternates or rental stock, and auto‑creates pre‑order tasks to protect the timeline. Prevents stalls caused by parts and long‑lead items.

Requirements

SKU-to-Task Linkage & Bill of Materials
"As a property manager, I want to attach standardized materials SKUs and quantities to each maintenance task so that procurement and scheduling align on exact parts and timelines."
Description

Enable associating standardized materials and appliance SKUs (with quantity, variants, and required attributes) to each maintenance task. Provide fast search and selection against preferred vendor catalogs and custom items, with per-portfolio approved alternates. Derive needed-by dates from work order SLAs or scheduled visits and store them at the line-item level. Persist a normalized bill of materials that downstream procurement, scheduling, and vendors can reference. Support vendor preference rules, unit compatibility notes, and cost tracking to ensure sourcing decisions remain consistent and auditable across properties.

Acceptance Criteria
Link Preferred Vendor SKU to Maintenance Task
Given an open maintenance task and a user with Edit Task permission When the user selects a SKU from search results Then the SKU is attached to the task as a new BOM line with fields: taskId, skuId, vendorId, uom, quantity (default 1), unitPrice, currency, requiredAttributes (empty if none), compatibilityNotes (if any), createdBy, createdAt And the BOM line persists to storage and is returned by GET /tasks/{taskId}/bom within 2 seconds of save And the UI displays the new line item immediately after save with exact price and description from the vendor catalog
Search and Select SKUs Across Preferred and Custom Catalogs
Given a portfolio with preferred vendors and custom items enabled When a user searches by keyword, SKU, or UPC/EAN Then results include matching items from preferred vendor catalogs, other connected catalogs, and portfolio custom items, ranked with preferred first And the first page (top 20) returns within p95 <= 800 ms And filters for vendor, category, availability, and variant attributes are available and, when applied, update results within p95 <= 600 ms And selecting a result opens a detail view with price, attributes, and Add to Task action
Specify Quantity, Variants, and Required Attributes per Line Item
Given a selected SKU with defined variants and required attributes When the user adds it to a task Then the user can set quantity respecting UOM rules (integers for 'each'; up to 2 decimals for linear/weight measures) And the system enforces completion of all required attributes before save and disables incompatible attribute combinations And unit-level compatibility notes (e.g., voltage, gas/electric, size constraints) are displayed using the unit profile And saving with missing required attributes or incompatible choices is blocked with specific validation messages
Approved Alternates per Portfolio
Given a portfolio-level list of approved alternates for a SKU When a user adds that SKU to a task Then the UI surfaces approved alternates with pricing and availability And selecting an approved alternate replaces the original SKU on the BOM line and logs originalSkuId -> alternateSkuId And attempting to select a non-approved alternate is blocked unless the user has Approve Alternates permission, in which case approval is recorded with user, timestamp, and justification
Derive Needed-By Dates from SLA or Scheduled Visit
Given a work order with either an SLA due date and/or a scheduled technician visit When a SKU line is added to the BOM Then the neededBy date for the line is set to the earliest of (scheduled visit date, SLA due date), and stored at the line level And if neither exists, neededBy is null and the line is flagged "Needed-by missing" And when the visit date or SLA changes, neededBy recomputes within 5 seconds and changes are logged And manual edits cannot set neededBy later than the SLA due date without an override reason and Manager role; overrides are audited
Persist Normalized Bill of Materials
Given a task with one or more BOM lines When the task is saved Then the BOM is stored in a normalized schema where each line references skuId and vendorId rather than duplicating catalog data And GET /tasks/{taskId}/bom returns a stable schema including: lineId, skuId, vendorId, description, uom, quantity, unitPrice, currency, neededBy, requiredAttributes, alternates, compatibilityNotes, lineTotal, version And each edit creates a new BOM version (version increments by 1) and preserves prior versions retrievable via GET /tasks/{taskId}/bom?version={n} And vendors and procurement users can view the BOM in their portals with read-only fields appropriate to role
Vendor Preference Rules and Cost Tracking Auditability
Given a SKU available from multiple vendors and portfolio vendor preference rules When a user adds the SKU to a task Then the preferred vendor is auto-selected and priced with contract terms And choosing a non-preferred vendor requires an override reason and Sourcing Override permission; the override is logged And the system computes lineTotal = quantity x unitPrice and BOM total, including taxes/shipping if provided, within rounding rules And all sourcing decisions and cost changes create immutable audit entries with oldValue, newValue, userId, timestamp, and reason, retrievable via GET /tasks/{taskId}/bom/audit
PO Tracking & Supplier ETA Sync
"As an operations coordinator, I want POs and supplier ETAs to sync into each task so that I always see accurate arrival dates without manual updates."
Description

Track purchase orders at the line-item level and sync supplier ETAs into tasks. Ingest PO updates via API integrations (e.g., supplier portals), EDI, or intelligent email parsing of order confirmations and shipment notices. Handle partial shipments, backorders, substitutions, and cancellations with clear status per line. Maintain a history of promised vs actual dates to inform supplier performance. Automatically update task timelines and notify stakeholders when ETAs change, ensuring a single source of truth without manual data entry.

Acceptance Criteria
Line‑Item PO Tracking & Status Mapping
Given a new PO with multiple line items (SKU, qty, cost, needed‑by), When the PO is created or imported, Then each line item is stored with fields: lineId, SKU, description, orderedQty, confirmedQty, shippedQty, receivedQty, backorderedQty, canceledQty, supplierETA, status, supplierRefs. Given supplier updates with events (confirmation, shipment, backorder, substitution, cancellation) for a line, When processed, Then the line status is set to one of: Ordered, Confirmed, Partially Shipped, Shipped, Backordered, Substituted, Canceled, Delivered, matching the event and quantities. Given a substitution event provides a replacement SKU, When applied, Then the original line links to the replacement SKU, the substitution reason is recorded, and the task reflects the effective SKU without losing original traceability.
Multi‑Channel ETA Ingestion (API, EDI, Email)
Given a supplier API webhook payload for PO/line updates, When received, Then ETAs, quantities, and statuses are updated within 5 minutes and the source is recorded as API. Given EDI 855 (PO Acknowledgment) and 856 (ASN) files containing matching PO and line numbers, When imported, Then the system updates confirmed dates and shipment ETAs per line and records the file metadata with source=EDI. Given order confirmation and shipment emails from supported suppliers in the test suite (≥20 templates), When processed, Then ≥95% of line items have correct SKU, quantities, and ETA extracted and mapped to the correct PO lines. Given the same message is received more than once, When processed, Then updates are idempotent and no duplicate history entries are created.
Partial Shipments, Backorders, and Cancellations
Given a line with orderedQty=10 and a shipment notice for qty=6, When processed, Then shippedQty=6, backorderedQty=4, status=Partially Shipped, and supplierETA for the remaining qty is retained or updated. Given a backorder notice with a new ETA, When processed, Then status=Backordered and supplierETA is set to the new date. Given a cancellation for qty=4 on the remaining backorder, When processed, Then canceledQty increases accordingly, remaining backorderedQty decreases, and status updates to reflect remaining fulfillment state. Given all remaining qty is delivered, When processed, Then status=Delivered and supplierETA is cleared.
Promised vs Actual Date History & Audit Log
Given any change to a line's supplierETA or status, When saved, Then a history record is appended with previous value, new value, timestamp, user/system actor, and data source (API/EDI/Email/Manual). Given a line with at least 3 ETA changes and a delivery date, When viewed in history, Then the variance per change (in days) and total promised‑vs‑actual variance are displayed and exportable (CSV) at the PO and supplier levels. Given a supplier performance report request for a date range, When generated, Then it aggregates average promised‑vs‑actual variance and on‑time rate per supplier using the stored history.
Automatic Task Timeline Updates & Notifications
Given a task with a needed‑by date and linked PO line(s), When a linked line's supplierETA changes, Then the task timeline is recalculated within 5 minutes and any slippage vs baseline is recorded. Given the recalculation results in the task end date exceeding the needed‑by date, When this occurs, Then the task is marked At Risk and an in‑app and email notification is sent to the task owner, assigned vendor, and watchers, including old ETA, new ETA, and impact delta. Given multiple ETA changes occur within a 15‑minute window, When notifications are sent, Then they are deduplicated into a single summary notification.
Single Source of Truth UI & Overrides
Given a user views a task or PO, When supplier ETAs and statuses exist, Then the UI displays the latest values, source, and last‑sync timestamp in read‑only fields by default. Given a user without override permission attempts to edit supplier ETA or status, When they try, Then the system prevents edits and explains that data is synced from suppliers. Given a user with override permission performs a manual override, When saved, Then the change is tagged as Manual Override, the original supplier value is retained in history, and the next inbound sync does not overwrite unless explicitly reconciled.
Reconciliation, Matching, and Error Handling
Given an inbound update cannot be matched to an existing PO/line with high confidence, When processed, Then it is placed in a Reconciliation Queue within 5 minutes with suggested matches and confidence scores. Given a user resolves a reconciliation item by selecting the correct PO/line, When applied, Then the update is committed and the matching rule learns from the decision for that supplier. Given a payload fails validation (e.g., unknown SKU, missing quantities) or processing errors occur, When logged, Then the system records an error with context, surfaces an alert to admins, and retries up to 3 times with exponential backoff. Given duplicate updates are received (same supplier message ID or hash), When processed, Then the system suppresses duplicates and records a single history entry.
Needed-by vs ETA Risk Scoring & Alerts
"As a maintenance scheduler, I want early warnings when ETA exceeds the needed-by date so that I can adjust plans before a job stalls."
Description

Continuously compare each item’s supplier ETA to its needed-by date to calculate slack and risk level (green/amber/red). Recompute risk on relevant events such as schedule changes, PO updates, or vendor delays. Surface warnings prominently on the task timeline and portfolio dashboard, and route alerts through email, SMS, or in‑app notifications based on user preferences. Provide clear next steps and affected dependencies so teams can act before a job stalls.

Acceptance Criteria
Slack Computation & Risk Color Thresholds
Given an item with needed-by datetime and supplier ETA datetime in the portfolio timezone When slackHours = floor((neededBy - eta) / 1h) is computed Then riskColor = Green if slackHours >= 72; Amber if 0 <= slackHours < 72; Red if slackHours < 0 Given riskColor is computed When the item detail and its parent task are rendered Then both display the same riskColor and slackHours Given either needed-by or ETA is missing When risk is evaluated Then show "Set dates to assess risk" and do not display a riskColor
Real-time Recalculation on Key Events
Given a schedule change to a task's needed-by datetime, a PO/ETA update, or a vendor delay event is received When the event is saved Then slackHours and riskColor are recomputed within 60 seconds and lastComputedAt is updated Given multiple events for the same item occur within 2 minutes When recomputation runs Then only one recomputation is performed and alerts reflect the latest values Given the riskColor transitions (Green->Amber, Amber->Red, Red->Amber/Green) When the transition occurs Then a transition log entry is recorded with prior/new slackHours, prior/new riskColor, timestamp, and actor/source
Alert Routing & Content Based on Preferences
Given a riskColor transition to Amber or Red or a slackHours decrease of 24 or more When user alert preferences enable a channel Then an alert is sent via each selected channel (Email, SMS, In‑App) Given an alert is sent When the recipient opens it Then the alert content includes: item name/SKU, task link, ETA, needed-by, slackHours, riskColor, affected dependencies count, and next-step options Given alerts are generated for the same item When severity does not escalate and 4 hours have not elapsed since the last alert per channel Then no duplicate alert is sent on that channel Given an Amber alert When queued Then it is delivered within 10 minutes; Red alerts within 2 minutes Given a user has disabled a channel or is within quiet hours When an alert would be sent Then that channel is suppressed and only In‑App is shown immediately
Prominent Warnings on Task Timeline
Given a task has one or more items at Amber or Red When the task timeline is viewed Then a warning banner appears at the top showing highest riskColor and earliest slackHours among items Given the warning banner is displayed When the user expands details Then the list of at‑risk items is shown with per‑item slackHours and quick actions for next steps Given all items are Green or dates are missing preventing risk assessment When the task timeline is viewed Then no warning banner is shown
Portfolio Dashboard Risk Roll-up & Filters
Given the portfolio dashboard is open When any item's riskColor changes Then the dashboard risk tiles update counts of Red and Amber items and affected tasks within 2 minutes Given the "At Risk" filter is applied When the task list loads Then tasks are sorted by highest severity first (Red, Amber), then by ascending slackHours Given the user exports the "At Risk" list When the export completes Then the CSV includes task ID, item SKU, riskColor, slackHours, needed-by, ETA, and lastComputedAt
Multi-Item Aggregation & Dependency Propagation
Given a task has multiple items with varying riskColors and slackHours When overall task risk is calculated Then the task riskColor equals the highest item riskColor and displayed slackHours equals the smallest slackHours among items Given a task is blocked by upstream tasks or milestones that depend on an at‑risk item When an item turns Red or Amber Then a "Blocked by" indicator is shown on the dependent tasks with a link to the blocking item
Dependencies & Next-Step Actions
Given an item is Amber or Red When the user opens the dependencies panel Then the system shows the count and list of directly affected tasks/milestones Given the dependencies panel is open When the user selects "Create pre‑order task" Then a pre‑order task is created, linked to the original task, assigned to the procurement role, with due date set to ETA minus 3 days Given next-step suggestions are available (alternates, rental stock) When a suggestion is selected Then the selected action opens the relevant flow prefilled with the item SKU and context
Alternate Sourcing & Rental Stock Suggestions
"As a property manager, I want the system to suggest in-stock equivalents or rental units when lead times are long so that work can proceed without delay."
Description

When risk is detected or lead times are long, recommend equivalent SKUs from preferred vendors, nearby suppliers, or property-owned inventory. Validate compatibility rules (model, dimensions, voltage, finish) and display trade-offs across price, lead time, and warranty. Include rental appliance options and short-term stock as bridges. Enable one-click creation of replacement POs or inventory reservations with required approvals captured in the audit trail.

Acceptance Criteria
ETA Risk Triggers Alternate SKU Suggestions
Given a maintenance task has an attached SKU with supplier ETA later than the needed-by date OR a risk flag is raised by Lead-Time Radar When the user opens Alternate Sourcing in the task Then the system returns suggestions within 2 seconds And at least one option from preferred vendors, nearby suppliers, or property inventory is displayed if available And each option shows source type, price, ETA/date, warranty term, compatibility status, and deltas vs original And if no compatible options exist, a "No compatible alternates" message with reasons is shown
Compatibility Validation Enforcement
Given the original SKU has compatibility attributes (model, dimensions, voltage, finish) When generating suggestions Then items failing any hard compatibility rule are excluded And items passing all rules are labeled "Compatible" And the user cannot create a PO/reservation for items marked "Incompatible" And the reason for exclusion is available on hover or tap
Rental Stock Bridge Option
Given the lead time gap (original ETA minus needed-by date) exceeds the configured rental threshold And no compatible in-stock alternate can meet the needed-by date When viewing suggestions Then rental appliance and short-term stock options are presented with start and end dates that cover the gap And estimated total rental cost is displayed And the user can create a rental order in one click And the order requires approval per rules and logs to the audit trail
One-Click Replacement PO with Approval Audit
Given a compatible alternate SKU is selected When the user clicks "Create Replacement PO" Then a draft PO is created with vendor, item, price, ETA, shipping details, and a link to the task (and original PO if present) And approval routing is triggered per approval rules And upon approval the PO is issued to the vendor and the task status updates accordingly And the audit trail records requester, approver(s), timestamps, decision, and rationale
One-Click Inventory Reservation & Deduction
Given a compatible item exists in property-owned inventory within the configured service radius When the user clicks "Reserve Inventory" Then the item is reserved and on-hand quantity reduced accordingly And a pick/transfer/installation subtask is created with due dates aligned to the needed-by date And required approvals are captured and logged in the audit trail And releasing or timeout of the reservation restores quantity and logs the event
Trade-Off Comparison Across Price, Lead Time, Warranty
Given two or more suggestions are available When the user opens the comparison view Then options are shown side-by-side with price delta, ETA/date and days saved, warranty term, and vendor rating And the default sort is "Earliest Arrival" And the user can sort by "Lowest Cost" or "Longest Warranty" and see the list re-sorted And the user's selected option persists when closing the comparison
Preferred Vendor and Nearby Supplier Prioritization
Given suggestions include preferred vendors, non-preferred vendors, and nearby suppliers When displaying the suggestions list Then compatible options from preferred vendors are ranked above non-preferred And nearby suppliers within the configured radius are highlighted with in-stock status And vendors on the blocked list are not shown
Auto Pre-Order Tasks & Timeline Protection
"As a coordinator, I want pre-order tasks to be auto-created when risk is detected so that purchasing actions happen early and consistently."
Description

Automatically create pre-order tasks when risk thresholds are met, assigning owners, due dates, and checklists tailored to the item type and vendor. Link dependencies so the main work order cannot advance to on-site steps until materials are secured or an approved alternate is chosen. Apply budget guardrails for one-click approvals within limits and escalate when exceeded. Auto-close or cancel pre-order tasks when risk clears, and log all actions for compliance and reporting.

Acceptance Criteria
Risk Threshold Breach Triggers Auto Pre-Order Task
- Given risk rules are configured (buffer_days, evaluation_interval_seconds, no_po_within_hours), When an item’s supplier ETA exceeds its needed-by date minus buffer or a PO has not been placed within the configured window, Then the system creates exactly one pre-order task for that item within 60 seconds of detection. - Then the task is linked to the originating work order and line item, includes the risk reason code, and is set to Open. - And if an Open or In Progress pre-order task already exists for that line item, no additional task is created (idempotent behavior). - And retries caused by transient errors do not create duplicates; task count for the line item remains 1.
Role-Based Assignment, Due Date, and Checklist Templating
- Given a new pre-order task is created, When assignment rules run, Then the task owner is set to the designated Purchasing role for the property; if none, the task is assigned to the team queue "Purchasing". - Then the task due date is calculated as needed-by date minus (standard lead time + buffer), never earlier than now + minimum_lead_time_hours; if calculation yields a past time, set to now + minimum_lead_time_hours. - Then the checklist template matching item_type and vendor is applied, with all required steps present; 100% of required steps must be present and ordered per template. - And any manual changes to owner, due date, or checklist are captured in the audit log with before/after values and actor.
Dependency Gate Blocks On-Site Steps Until Materials Secured or Alternate Approved
- Given a work order has at least one Open or In Progress pre-order task, When a user attempts to transition the work order to any on-site status (Scheduled, Dispatched, On-Site), Then the transition is blocked with error code PREORDER_BLOCK and message referencing the blocking line items. - Then allowed transitions remain to planning statuses (e.g., Awaiting Parts) so coordination can continue. - When materials are secured (PO status Received or Inventory Reserved with quantity >= required) or an alternate item with Approved status is selected for each blocking line item, Then the gate lifts automatically within 60 seconds, and the on-site transitions become available.
Budget Guardrails with One-Click Approval and Escalation
- Given a pre-order task with a cost estimate (item + tax + shipping - discounts) in the property currency, When the total is within both the remaining budget and the current user’s approval limit, Then the One-Click Approve action is enabled and sets the PO to Approved, recording the approval with timestamp and approver. - When the total exceeds the user’s limit or the remaining budget, Then One-Click Approve is disabled and the Escalate action sends an approval request to the configured approver group, setting status to Pending Approval and notifying recipients. - When an escalated request is approved, Then the PO moves to Approved and the action is logged; when rejected, Then the request records a required reason and the pre-order remains Open.
Automatic Close/Cancel of Pre-Order Tasks When Risk Clears
- Given a pre-order task exists, When the risk condition no longer applies because the item is secured (PO Received or Inventory Reserved) or the ETA now meets the needed-by date with buffer, Then the task auto-transitions within 60 seconds: to Done if secured, or to Cancelled - Risk Cleared if no longer needed. - Then linked dependencies on the work order update accordingly; any cleared block is removed immediately after the auto-transition. - And the closure/cancellation record includes the reason code, timestamps, and actor = System.
Complete Audit Trail for Compliance and Reporting
- For every create, assignment, due date change, checklist apply/complete, approval, escalation, dependency block/unblock, and close/cancel event, Then an immutable audit log entry is recorded capturing: timestamp (UTC), actor (user id or System), action type, entity ids (work order, line item, task, PO), reason code (where applicable), and before/after values. - Then audit entries are queryable by work order id and date range, and exportable to CSV; results include all fields listed above. - Then missing or failed logging attempts are surfaced as system alerts and retried; no user-facing action completes without a corresponding audit entry.

Turn Playbooks

Reusable templates for light, standard, and full turns by building and unit type. Preloads dependencies, typical durations, checklists, and photo QA steps, then adapts based on historical performance. Enables one‑click setup, consistent execution, and faster training for new coordinators.

Requirements

Template Library by Turn Type
"As an operations manager, I want standardized turn playbook templates by turn level and unit profile so that coordinators can launch consistent scopes quickly and reduce variability."
Description

Provide reusable, property-scoped templates for Light, Standard, and Full turns, with variants by building and unit profile (beds/baths, finishes, age). Each template includes default scopes of work, task lists, typical durations, dependencies, required materials, preferred vendor mappings, budget ranges, SLAs, and safety/compliance notes. Support metadata tagging, import/export, cloning, and permissioned editing so operations can standardize at scale while enabling local customization.

Acceptance Criteria
Property-Scoped Templates by Turn Type
Given a user assigned to property P with role in {Property Admin, Coordinator} When the user creates templates for turn types Light, Standard, and Full Then each template is saved with fields turnType ∈ {Light, Standard, Full} and propertyId = P And the templates are visible/searchable only to users assigned to property P (and Org Admins) And users assigned only to property Q cannot view or apply these templates And the library groups templates by turnType and returns a count per group via UI/API
Variant Selection by Building and Unit Profile
Given a building with metadata {buildingType, yearBuilt, finishes} and a unit with metadata {beds, baths, finishes} And a library containing template variants with metadata constraints When a user selects turnType = Standard for that unit Then the system returns variants whose constraints match the unit/building metadata And variants are ranked by specificity (more matching attributes ranks higher) then by updatedAt (newest first) And if no variant matches, the default Standard template for the property is suggested And the user can override and pick any available variant before proceeding
Template Content Completeness and Validation
Given a template draft When the user attempts to save it Then the following sections must exist: scopesOfWork, taskList, typicalDurations, dependencies, requiredMaterials, preferredVendorMappings, budgetRange, SLAs, safetyComplianceNotes And taskList contains at least 1 task with unique taskIds And each task has estimatedDuration > 0 with unit in {minutes, hours, days} And dependencies form an acyclic graph (no circular dependencies) And budgetRange includes currency and min >= 0 and min <= max And SLAs define target durations with unit in {hours, days} And preferredVendorMappings reference existing vendorIds in property P or are explicitly set to "Unassigned" And safetyComplianceNotes are required for any task marked riskLevel in {Medium, High} And save is rejected with field-level errors if any validation fails
Metadata Tagging and Search/Filter
Given templates with tags and metadata {turnType, buildingType, beds, baths, finishes} When a user filters by any combination of these fields and/or tags Then only templates in property P matching all specified filters are returned And tag filtering uses AND semantics across multiple tags and is case-insensitive And free-text search matches template name or description substring, case-insensitive And clearing all filters returns the full property-scoped library
Import and Export of Templates
Given a selection of templates in property P When the user exports them Then a single .json bundle is produced containing all template definitions with schemaVersion and sourcePropertyId = P And when a user imports that bundle into property Q Then each template is validated against the schema; invalid templates are listed with error reasons and are not imported And for duplicates (same templateKey in Q), the user can choose Overwrite, Create Copy, or Skip And preferredVendorMappings are re-mapped to vendorIds in property Q where exact name matches; unmatched entries are set to "Needs Remap" And an audit record is created for import/export actions with user, timestamp, counts, and outcome
Clone and Local Customization
Given an existing template T in property P When a user clones T Then a new template T' is created with a new templateId, propertyId = P, sourceTemplateId = T.templateId, and identical content And the user can modify any fields in T' without altering T And differences between T and T' are tracked and viewable as a change summary And T' remains fully editable subject to permissions
Permissioned Editing and Audit Trail
Given role-based permissions configured as: Property Admin {create, edit, delete, import, export}, Coordinator {create, edit, clone}, Viewer {read-only} When a user attempts an action on a template Then actions not permitted by role return HTTP 403 (API) or disabled UI controls (UI) And delete operations require confirmation and are soft-deletes with restore available within 30 days And all create/edit/delete/import/export actions write to an immutable audit log with userId, timestamp, action, templateId, and field-level diff And audit entries are retrievable by Property Admins via UI/API
One-Click Turn Setup
"As a turn coordinator, I want to launch a full set of turn tasks with one click so that I save setup time and eliminate scope and assignment errors."
Description

Enable coordinators to select a template and with one action generate a sequenced set of work orders, pre-filled checklists, photo QA steps, timelines, vendor assignments (based on preferred vendor rules), and notifications. Auto-attach budget estimates and approval routing, reserve materials if tracked, and link all tasks to the unit’s turn record for consolidated tracking.

Acceptance Criteria
One-Click Generation Creates Full Turn Plan
Given a coordinator selects a Turn Playbook template matching the unit and building type And the unit has an active turn record When the coordinator clicks "Generate Turn" Then the system creates all work orders with dependencies and target start/end dates per template durations And pre-fills task checklists and photo QA steps per template And links every generated task and work order to the unit’s turn record And displays a success summary with counts of work orders, checklists, and photo steps created
Preferred Vendor Auto-Assignment and Notifications
Given preferred vendor rules exist for the property and categories in the template When generation runs Then each work order is auto-assigned to the highest-priority eligible preferred vendor per rules and service area And if no preferred vendor is eligible, the work order is assigned to the default vendor pool with status "Unassigned" And assigned vendors, the coordinator, and stakeholders receive notifications per channel settings within 60 seconds containing work order details and SLA dates
Budget Estimates and Approval Routing
Given the template contains budget line estimates per task and approval thresholds are configured When generation completes Then a consolidated turn budget is attached to the turn record with task-level estimates and total And work orders exceeding threshold are set to "Awaiting Approval" and are not dispatched And approval requests are routed to the correct approver group with one-click approve/deny options And upon approval, the work order status updates to "Scheduled" and vendor is notified; upon denial, it is set to "On Hold" with reason required
Materials Reservation from Inventory
Given inventory tracking is enabled and the template lists required materials with quantities When generation runs Then the system reserves available quantities against the property warehouse or assigned store And if stock is insufficient, the shortfall is flagged and a purchase request draft is created And reserved quantities are visible on each work order and the inventory ledger is updated
Performance, UX Feedback, and Idempotency
Given a template with up to 50 tasks When "Generate Turn" is clicked Then the operation completes in under 3 seconds at the 95th percentile and under 6 seconds worst case And a progress indicator is shown during processing and a success toast appears on completion And repeated clicks within 60 seconds do not create duplicates; a second request returns the existing generation summary
Error Handling, Rollback, and Audit Trail
Given a transient system failure occurs during generation When the failure is detected Then no partial turn remains; the system rolls back created items and surfaces an error message with a retry option And an audit log entry records actor, timestamp, template ID/version, unit, and outcome And all generated items record created-by and template version metadata on success
Dependency Graph & Auto-Scheduling
"As a turn coordinator, I want the system to automatically schedule dependent tasks and recalculate when things change so that turns finish on time with minimal manual coordination."
Description

Model task dependencies (finish-to-start, start-to-start, buffers) and typical durations to automatically produce a schedule and critical path for each turn. Integrate vendor calendars, working hours, and holidays; detect conflicts; propose resequencing when delays occur; and recalculate ETAs in real time. Provide timeline/Gantt visualization and enforce constraints such as access readiness and cure times.

Acceptance Criteria
Auto-Schedule Generation with Critical Path
Given a Turn Playbook with tasks containing finish-to-start and start-to-start dependencies, typical durations, and default property working hours M–F 08:00–17:00 in the property's timezone When the coordinator clicks Auto-schedule on a new unit turn Then the system generates start and finish times for all tasks within working hours and honoring all FS and SS dependencies And computes and stores the critical path, total float per task, and overall project ETA And no task is scheduled outside working hours or across holidays without splitting into valid segments And the generated schedule is saved to the turn within 3 seconds
Vendor Calendar Integration and Working Hours Compliance
Given tasks are assigned to vendors with calendars including working hours, time-off, and holidays, potentially in different timezones When Auto-schedule runs Then each task is placed only in slots where the assigned vendor is available And task times display in the property's timezone while respecting vendor availability in their own timezone And the system prevents double-booking a vendor across overlapping tasks and surfaces any unassignable task with the earliest feasible start suggestion
Buffers and Material Cure-Time Enforcement
Given a dependency with a 12-hour buffer between Patch & Prime and Paint Walls, and a Reseal Tub task requiring a 24-hour cure lockout on the bathroom area When Auto-schedule runs Then Paint Walls starts no earlier than 12 hours after Patch & Prime finishes, adjusted to the next valid working slot And no tasks tagged as disallowed for the bathroom area are scheduled during the 24-hour cure window And buffer and cure constraints are visibly indicated on the Gantt for the affected tasks
Access Readiness Gate Constraint
Given a gate constraint Access Ready must be met before any in-unit tasks can start and Access Ready is currently Not Met When Auto-schedule runs Then in-unit tasks are not scheduled and are marked Blocked by Access with no start time And tasks not requiring in-unit access are scheduled normally And when Access Ready is marked Met, the system schedules previously blocked tasks within 5 seconds and updates task and project ETAs
Delay Handling and Auto-Resequencing with ETA Recalculation
Given a generated schedule where a task is delayed by 1 day When the delay is saved Then the system recalculates downstream tasks, the critical path, and the project ETA within 5 seconds And proposes at least one resequencing option that maintains constraints while minimizing total delay by advancing non-critical work where possible And the coordinator can accept a proposal with one click to apply the new sequence And an audit entry records prior and new dates, affected tasks, and the reason Delayed
Conflict Detection and Resolution Suggestions
Given a schedule where two tasks assigned to the same vendor or an area-exclusive tag overlap in time When the schedule is validated or a manual edit creates an overlap Then the system flags a conflict for each resource with a message naming the tasks, the resource, and the overlap window And offers at least one conflict-free adjustment that preserves all dependencies And blocks publishing changes if Enforce Constraints is enabled, or allows save with warnings if disabled
Timeline/Gantt Visualization and Constraint-Aware Editing
Given an existing schedule When the coordinator opens the Timeline view Then the Gantt renders tasks with accurate start, finish, duration, and dependency links, and highlights critical path tasks distinctly And dragging a task to a new start time either snaps to the next valid slot if constraints allow or reverts with a visible reason if constraints would be violated And any valid drag edit triggers recalculation of impacted tasks and ETA within 2 seconds And zoom controls switch between day and week scales while maintaining correct task positions
Photo-First QA Checklists
"As a maintenance lead, I want photo-verified QA steps tied to each task so that I can ensure workmanship quality without visiting each unit."
Description

Allow each task to define required photos (before/after, angles), annotated checklists, and pass/fail criteria that must be satisfied before completion. Enforce capture at the right step, compress and store images securely, support offline capture with later sync, and persist QA artifacts in the unit’s history for audit and vendor performance evaluation.

Acceptance Criteria
Enforce Required Photos Before Task Completion
Given a task defines required photo types with counts (e.g., Before-Wide:1, Before-Fixture:1, After-Wide:1) And the assignee is on the task's QA step When the assignee attempts to mark the task Complete Then the Complete action is disabled until all required photo types meet the required counts and are assigned to their labels And missing photo types are listed inline with capture shortcuts And queued (unsynced) captured photos count toward the requirement while offline
Angle and Label Guidance During Capture
Given a required photo type specifies angle guidance and a label When the assignee opens the camera from the QA step Then the app displays an on-screen overlay and label that match the required angle And the captured photo is auto-tagged to the matching required label and step And an After-labeled photo cannot be applied to a Before requirement (and vice versa) And the same photo cannot fulfill multiple required labels
Annotated Checklist With Pass/Fail Gate
Given a task defines a QA checklist with items marked Required or Optional and per-item pass/fail When the assignee completes the checklist Then all Required items must be marked Pass and any required fields/photos linked before the task can be marked Complete And any item marked Fail blocks completion and sets task status to Needs Fix with a mandatory note And photo annotations (arrows/boxes/text) are saved as non-destructive overlays and are viewable in task and unit history
Offline Photo Capture and Deferred Sync
Given the device is offline on a task's QA step When the assignee captures required photos and fills the checklist Then all artifacts are saved locally with checksums and queued for upload And the task can be marked Complete if local requirements are satisfied And upon reconnect, artifacts auto-sync within 60 seconds, retry up to 5 times with exponential backoff, and surface per-artifact errors without data loss across app restarts
Image Compression and Secure Transport/Storage
Given a photo is captured or selected for a QA step When the system prepares the artifact for upload Then the image is compressed client-side to longest edge ≤ 2048px and size ≤ 1.5 MB while preserving orientation And EXIF GPS data is stripped; orientation is normalized And the artifact is transmitted over TLS 1.2+ and stored encrypted at rest (AES-256 or equivalent) And access is enforced via role-based permissions and expiring signed URLs (≤ 15 minutes)
Persist QA Artifacts to Unit History for Audit
Given a task is completed with QA artifacts When viewing the associated unit's history Then photos, annotations, checklist results, pass/fail decision, user, vendor, timestamps, and task IDs are visible and immutable And artifacts remain retrievable for at least 36 months per retention policy And an audit trail records creation, edits to notes/annotations, and any re-open events with actor and timestamp
Dependency Gate on QA Pass
Given Task B depends on Task A's QA pass in the playbook When Task A has any missing required photos or any Fail status Then Task B cannot be started and shows a dependency warning referencing Task A's unmet QA And when Task A passes all required items and artifacts exist, Task B becomes startable And for other users, Task B remains blocked until Task A's pass is synced to the server
Adaptive Duration & Scope Optimization
"As an operations analyst, I want playbooks to adapt based on historical performance so that schedules become more accurate and costs decrease over time."
Description

Analyze historical executions by building, unit profile, vendor, and seasonality to recommend duration adjustments, buffer changes, and scope tweaks to templates. Surface explainable recommendations with confidence scoring, allow sandbox testing/A-B comparison, and let admins apply changes selectively. Flag outliers and recurring rework to drive continuous improvement.

Acceptance Criteria
Generate Explainable Duration & Scope Recommendations by Segment
Given historical turn executions exist with attributes for building, unit profile, vendor, and season And at least 20 qualifying executions exist for a segment or its configured parent segment When the optimization job runs for a selected template version Then the system produces recommended adjustments for each task duration and dependency buffer as +/- hours/days, rounded to the nearest hour/day And the system includes an explanation listing the top 3 drivers with their effect sizes (e.g., +0.8 days for winter, -0.5 days for Vendor A) And the system assigns a confidence score between 0 and 100 for each recommendation And recommendations that would violate task dependencies, minimum durations, or create negative buffers are suppressed with a rationale And all recommendations are persisted with timestamp, segment key, template version, and author=system
Confidence Scoring and Data Sufficiency Guardrails
Given a candidate segment with fewer than 20 executions in the past 18 months When generating recommendations Then the system attempts roll-up to the configured parent segment hierarchy (e.g., building → region → portfolio) And if still fewer than 20 executions, the recommendation is marked Not Actionable with reason=Insufficient Data and confidence < 60 And if the final dataset contains >= 20 executions, the confidence score is computed and displayed, and the top/bottom 2.5% duration outliers are excluded by default (toggle to include) And confidence, sample size, and inclusion/exclusion settings are visible in the UI and exportable
Sandbox A/B Comparison of Template Changes
Given a baseline template version and a set of recommended changes (candidate) And a user selects a date range, segment filters, and evaluation KPIs (e.g., average turn duration, on-time rate, rework rate) When the user runs Sandbox A/B Then the system backtests candidate vs. baseline on matched historical cohorts and displays KPI deltas with 95% confidence intervals And the system labels the candidate as Recommended if average turn duration decreases by ≥ 8% with no increase in rework rate > 2 percentage points And results (inputs, cohort definition, KPI table, deltas, CI) can be exported to CSV and PDF And sandbox runs are saved with a unique ID, timestamp, and user
Selective Apply with Versioning and Rollback
Given a list of task and buffer recommendations with confidence and explanations When an admin selects a subset of recommendations and clicks Apply Then a new template version is created containing only the selected changes, with unchanged items carried forward And the change log records who applied changes, when, selected items, prior and new values, and linked sandbox run (if any) And changes apply only to new turns scheduled after the effective timestamp; in-progress turns are not altered And the admin can rollback to the previous version in one action, restoring all prior values and logging the rollback
Outlier and Recurring Rework Detection & Flagging
Given completed turn executions for a segment When a task duration exceeds the segment mean by > 2.5 standard deviations or the task triggers rework tickets within 30 days in ≥ 10% of executions over the last 6 months Then the system flags the execution as an Outlier and/or Recurring Rework case And the template dashboard surfaces counts and rates by task, vendor, and building, with links to example executions and photos And flagged items can be snoozed or acknowledged with a required reason, which is stored for audit And flagged patterns feed into recommendation explanations as drivers (e.g., +1.2 days due to recurring paint rework)
Seasonality-Aware Buffer Adjustment
Given historical seasonality effects are detected for weather-sensitive tasks (e.g., exterior work) with confidence ≥ 70 When generating buffer recommendations for the upcoming season Then the system proposes buffer modifications using season multipliers bounded by configured min/max limits (e.g., ±30%) And if seasonality confidence < 70 or data is insufficient, buffers default to baseline values with a Not Actionable note And proposed seasonal buffers do not break critical path dependencies or reduce any buffer below zero And all seasonal assumptions are listed in the explanation with the supporting sample size
Playbook Versioning & Approvals
"As a regional manager, I want controlled versioning and approvals for playbooks so that we maintain consistency and traceability across properties."
Description

Provide draft/publish versioning for templates with change logs, diffs, and rollback. Support approval workflows before templates go live, effective-date scheduling, version pinning per property, and an audit trail of who changed what and when to ensure governance and compliance.

Acceptance Criteria
Draft to Publish Versioning Lifecycle
Given I create a new Turn Playbook template When I save it Then it is created as a Draft with a unique version ID and is editable Given a version is Published When I attempt to edit it Then the system requires creating a new Draft from that version; the Published version remains immutable When I create a Draft from a Published version Then the Draft inherits all fields of the source version and records the parent version in metadata
Approval Workflow Gatekeeping
Given a Draft is ready When I submit it for approval Then the Draft locks for editing and appears in the Approvals queue for users with the Approver role When an Approver approves the Draft Then the system marks it Approved and enables Publish Now or Schedule options When an Approver rejects the Draft with a required comment Then the Draft unlocks for editing and the rejection comment is stored in the audit trail Then a version cannot be Published unless it has an Approved state
Change Log and Diffs Visibility
Given multiple edits occur across versions When I open the Change Log for a version Then I see a chronological list of events showing actor, timestamp (ISO 8601), action type, and optional comments When I compare Version A to Version B Then the diff shows added, removed, and modified items for dependencies, typical durations, checklists, and photo QA steps with field-level granularity Then unchanged fields are omitted from the diff Then I can export the change log and diff to CSV and PDF
Rollback to Prior Version
Given there exists a previously Published version When I initiate Rollback from the current Published version to the prior one Then the system creates a new Draft cloned from the target prior version and records "Rollback" as the reason Then the rollback Draft requires approval before it can be published When the rollback Draft is approved and published Then the current Published version remains in history and the new version becomes default for unpinned properties at its effective time
Effective-Date Scheduling
Given a version is Approved When I schedule a future effective date and time Then the system validates the timestamp is in the future and displays the account timezone Then I can cancel or reschedule any time before the effective time When the effective time is reached Then the version becomes the default for unpinned properties and is used for new playbooks created after that time; in-progress playbooks are unaffected Then the audit trail records schedule, reschedule, cancel, and go-live events
Version Pinning Per Property
Given Property A is pinned to Version X When a newer version is Published or becomes effective Then new playbooks created for Property A use Version X When Property A is unpinned Then the next new playbook for Property A uses the current default version Then the UI displays a list of properties pinned to each version and an audit entry is recorded for pin and unpin actions
Audit Trail Completeness and Integrity
Given users perform create, edit, submit, approve, reject, publish, schedule, rollback, pin, and unpin actions When I filter the audit trail by date range, actor, property, template, version, or event type Then only matching entries are returned within 2 seconds for up to 10,000 results Then each entry is immutable and includes actor ID, role, timestamp (ISO 8601), event type, target template/version ID, and a link to the relevant change log or diff if applicable Then I can export the filtered audit trail to CSV
Coordinator Training Mode
"As a new coordinator, I want guided steps and examples so that I can execute turns correctly without extensive training."
Description

Offer an optional guided mode that overlays tooltips, step-by-step instructions, and examples for each phase of a turn, including sample templates and checklists. Provide inline definitions, keyboard shortcuts, and an opt-in mentor review for the first N turns to accelerate onboarding and reduce errors.

Acceptance Criteria
Training Mode Toggle and Onboarding Prompt
Given a coordinator opens Turn Playbooks for the first time, when the view loads, then a modal explains Training Mode with Enable and Skip options and a “Don’t show again” checkbox. Given the user enables Training Mode, when they return in a new session, then Training Mode remains enabled for that user across devices. Given Training Mode is enabled, when any Turn page renders, then a visible “Training Mode” badge appears and guided overlays are active. Given the user disables Training Mode, when they refresh or start a new session, then the overlays and badge are not shown. Given the user toggles Training Mode, when the toggle state changes, then an analytics event “training_mode_toggled” is recorded with user_id and new_state.
Phase-by-Phase Guided Tooltips
Given a turn is in any phase (Scope, Schedule, Assign Vendors, QA, Closeout), when Training Mode is enabled, then a step-by-step tooltip appears anchored to the primary action for that phase. Given the viewport width is under 768px, when a tooltip opens, then it repositions to avoid covering the target and provides an on-screen Continue button within the viewport. Given the coordinator clicks Next or invokes the Next shortcut, when a step completes, then focus moves to the next step and progress (e.g., 2/6) updates. Given the user clicks “Show example,” when available, then an example image or text displays within the tooltip without navigating away. Given the user closes or skips a step, when they confirm, then the system logs the skip and does not block core actions.
Inline Definitions and Glossary Access
Given a term with an info icon is present, when the user hovers, focuses, or taps it, then a definition appears within 300 ms and is dismissible via Esc or click-away. Given a screen reader is used, when the definition is focused, then it is announced with role=tooltip and an accessible name referencing the term. Given the user selects “View all definitions,” when in Training Mode, then a side panel opens with a searchable glossary filtered to terms on the current screen.
Keyboard Shortcuts Help and Execution
Given Training Mode is enabled, when the user presses Shift+/, then a non-modal cheat sheet appears listing available shortcuts and remains until dismissed. Given focus is inside a text input or textarea, when the user presses N or P, then the shortcuts do not trigger and the characters are entered into the field. Given the user presses N while a guided tooltip is open, then the next step opens; given the user presses P, the previous step opens. Given the user presses S within a turn, then a save-draft executes and a confirmation appears within 2 seconds. Given the user presses G then D within 1 second (not typing), then the glossary panel opens.
Sample Templates and Checklists Sandbox
Given Training Mode is enabled, when the user selects “Open sample turn,” then a sandbox turn opens with a prominent “Sample Data” banner and no real notifications are sent to vendors or tenants. Given the user attempts to publish, schedule, or assign vendors in a sample turn, when they confirm, then the system blocks the action and displays a non-destructive notice explaining sample mode. Given the user clicks “Reset sample,” when confirmed, then all sample progress resets to the initial state within 3 seconds. Given sample QA steps require photos, when the user opens a sample task, then preloaded example photos are displayed for review.
Mentor Review Opt-in for First N Turns
Given Training Mode is enabled, when the coordinator creates a real turn and opts into Mentor Review, then on submit the turn is marked Pending Mentor Review and cannot be closed until approved. Given a mentor is selected, when the turn is submitted, then the mentor receives an in-app and email notification within 1 minute. Given the mentor requests changes, when the coordinator resubmits, then the review status updates and prior mentor comments are preserved with timestamps. Given the coordinator has completed N mentor-reviewed turns, when they create the (N+1)th turn, then the opt-in prompt defaults to off with a message indicating the mentor period is complete. Given N is configured between 1 and 10 in Training Settings, when saved, then subsequent turns use that value.
Progress Tracking and Dismiss/Snooze Controls
Given training tooltips appear, when the user selects “Snooze for this session,” then all training overlays hide until the user re-enables or a new session begins. Given the user completes all guided steps for a turn, when they reach closeout, then a completion toast appears and a Training Progress meter shows 100% for that turn. Given the user selects “Reset training,” when confirmed, then progress, snooze state, and dismissed tips are cleared and Training Mode returns to initial state. Given analytics is enabled, when a guided step starts, completes, or is skipped, then an event is recorded including step_id, result (completed/skipped), and duration_ms.

Bulk Shift

Portfolio‑wide rescheduling when events hit (storms, utility shutdowns, elevator outages). Shifts affected tasks while preserving dependencies, recalculates ETAs, and notifies vendors and tenants with reason codes. Auto‑requests refreshed quotes if shifts exceed thresholds, saving hours of manual cleanup.

Requirements

Bulk Event Trigger & Scoped Impact Selection
"As a property manager, I want to define the scope and rules for a bulk schedule shift when an event occurs so that all affected tasks are rescheduled consistently without manual edits."
Description

Provide a unified interface to initiate a portfolio-wide bulk shift from predefined or custom event types (e.g., storms, utility shutdowns, elevator outages). Allow precise scoping via filters such as portfolio, property, building, unit, asset, task type, vendor, priority, and date range. Support shift policies (absolute reschedule date/time, relative offsets, postpone-until-after window) and event windows. Compute and display a real-time preview of impacted tasks, expected conflicts, and summary counts before execution. Respect blackout dates, tenant availability windows, and building access rules while operating within FixFlow’s mobile web portal.

Acceptance Criteria
Initiate Bulk Shift with Predefined Event Type
Given I am an authenticated user on FixFlow’s mobile web portal with access to Bulk Shift When I start a new bulk shift and select a predefined event type And I set an event window with start and end date/time Then the system validates required inputs and displays available shift policies (Absolute date/time, Relative offset, Postpone-until-after) And the Preview action is enabled only when all required fields are valid
Create and Use Custom Event Type
Given I am creating a bulk shift When I define a custom event type and provide a name (minimum 3 characters) and optional description Then the custom event type is accepted and used as the event label for this bulk shift run And the label appears consistently in the preview header and the confirmation dialog
Apply Scoped Impact Filters Across Portfolio
Given I have opened the scoping step of a bulk shift When I set any combination of filters (portfolio, property, building, unit, asset, task type, vendor, priority, date range) Then only tasks matching all selected filters and scheduled within the date range are included in the impacted set And the preview displays the total impacted task count and the count of tasks excluded by the filters
Configure and Apply Shift Policies and Event Window
Given I have a set of impacted tasks in the preview When I select the Absolute policy and a target date/time Then proposed schedules for impacted tasks are set to the exact target date/time When I select the Relative Offset policy with a positive or negative offset (hours/days) Then proposed schedules equal original schedules plus the offset, preserving minutes if minutes are unspecified When I select the Postpone-Until-After policy with an event window Then tasks with schedules inside the window are moved to the earliest available time after the window while preserving their relative order
Real-time Preview with Impacted Tasks and Conflict Reasons
Given I have configured filters, event window, and a shift policy When I change any filter or policy value Then the preview recalculates and refreshes within 3 seconds And it shows for each impacted task the proposed new schedule and a status of OK or Conflict And conflicts are annotated with the reason: Blackout date, Tenant availability window, or Building access rule And the preview header displays counts for Total, OK, and Conflict
Execute Bulk Shift and Post-Execution Summary
Given the preview shows at least one impacted task When I confirm and execute the bulk shift Then all tasks marked OK are rescheduled to their proposed schedules And tasks marked Conflict remain unchanged And a completion summary displays counts for Updated, Skipped (Conflicts), and Unchanged that match the preview counts
Dependency-Aware Rescheduling Engine
"As an operations coordinator, I want bulk shifts to maintain task dependencies and constraints so that rescheduling does not break workflows or cause resource conflicts."
Description

Implement a scheduling engine that preserves task dependencies (predecessor/successor, must-start-on, must-finish-by) and constraints (tenant time windows, vendor hours/capacity, building access, holidays, time zones). Apply shift rules to compute new start/end times while maintaining dependency logic and preventing conflicts. Automatically find next-best slots when requested times are unavailable, mark exceptions needing manual review, and write back changes to work orders and calendars.

Acceptance Criteria
Preserve Predecessor–Successor Links in Bulk Reschedule
Given Work Order WO-123 has Task A scheduled 2025-09-08 09:00–11:00 and Task B with a finish-to-start dependency on A with 2h lag And a portfolio-wide +1 day shift is applied When the rescheduling engine runs Then Task A is rescheduled to 2025-09-09 09:00–11:00 And Task B is scheduled to start no earlier than 2025-09-09 13:00 And no dependency constraint is violated And no overlap is created for the same vendor, crew, or location resources And the schedule preserves original task ordering within each dependency chain
Respect Must-Start-On and Must-Finish-By Constraints
Given Task C has a Must-Start-On of 2025-09-10 13:00 local and Task D has a Must-Finish-By of 2025-09-12 17:00 local And a -1 day shift is requested across the portfolio When the engine computes new times Then Task C remains scheduled to start at exactly 2025-09-10 13:00 local And Task D is scheduled to finish no later than 2025-09-12 17:00 local And if no feasible slot exists that satisfies these constraints and all other rules, the task is not moved and an exception is created with code "ConstraintViolation" and reason details And no writeback occurs for tasks marked with exceptions
Enforce Tenant/Vendor/Building Windows Across Time Zones and Holidays
Given a task at a building in America/New_York with tenant availability 08:00–12:00 local, vendor hours 07:00–15:00 America/Chicago, and building access 09:00–17:00 local And the building has a holiday closure on 2025-11-27 local When a +2 day shift pushes the task onto 2025-11-27 Then the engine selects the next feasible slot that intersects all three windows after translating times to the building’s local time zone And no part of the scheduled time occurs on the holiday And the scheduled start and end fall entirely within all applicable windows And time zone conversions are logged with source and target TZ identifiers
Honor Vendor Daily Capacity and Prevent Overbooking
Given Vendor V-45 has capacity 3 concurrent tasks per 15-minute interval And five tasks assigned to V-45 are shifted into the same day When the engine reschedules Then at no minute does V-45 have more than 3 active tasks And lowest-priority tasks (priority: High > Normal > Low; tie-breaker: earliest original start) are deferred to the next feasible interval And capacity utilization per 15-minute interval is recorded for audit
Auto-Select Next-Best Slot When Requested Time Unavailable
Given a task requests 2025-09-15 10:00–12:00 but this slot violates tenant availability When the engine searches for alternatives Then it selects the earliest feasible slot that minimizes (in order): 1) start time, 2) added slack to successors, 3) travel overlap for the same vendor And tie-breakers are resolved by lowest Work Order ID, then lowest Task ID And the selected slot is returned with reason code "RequestedSlotUnavailable" and includes delta in minutes from requested start And search completes within 2 seconds for portfolios ≤ 500 tasks in scope
Write Back New Times to Work Orders and Calendars
Given a set of tasks is successfully rescheduled without exceptions When the engine commits changes Then Work Order records are updated with new start/end, dependency deltas, and version increment within 60 seconds And an audit trail entry is created per task with action "BulkShift", initiator, timestamp, and rule summary And vendor and tenant calendar feeds (ICS) are updated or regenerated within 2 minutes with no duplicate events (idempotent by event UID) And ETAs for in-progress and upcoming tasks are recalculated and stored for notifications
Flag and Route Unresolvable Conflicts to Manual Review
Given the engine cannot find a feasible slot within a 14-day search window without violating hard constraints When rescheduling completes Then the task status is set to "NeedsReview" and an exception record is created with code, offending constraints, attempted windows, and last evaluated slot And no calendar entries are created or modified for the task And an alert is sent to the assigned coordinator queue within 30 seconds And the task is excluded from further automated shifts until resolved
ETA Recalculation & SLA Impact Forecasting
"As a portfolio manager, I want updated ETAs and SLA risk visibility after a bulk shift so that I can manage exceptions and communicate realistic timelines."
Description

After executing a bulk shift, recalculate ETAs and arrival windows at task, unit, and property levels. Evaluate results against SLA and policy thresholds, highlighting projected breaches and risk scores. Update dashboards, work orders, and tenant/vendor timelines with new ETAs and status badges, and produce a summary of SLA impacts for stakeholder review.

Acceptance Criteria
Portfolio-wide ETA Recalculation After Bulk Shift Execution
Given a Bulk Shift with a defined offset and reason code is executed successfully When recalculation runs Then 100% of impacted tasks receive a new ETA and arrival window based on routing rules and vendor calendars And unaffected tasks retain their original ETA and arrival window with no changes And recalculation completes within 10 minutes for up to 2,000 impacted tasks And all timestamps respect each property's local time zone And the recalculation process is idempotent for the same Bulk Shift ID (no duplicate changes)
Arrival Window Recalculation and Rollups at Task/Unit/Property
Given tasks have service windows, dependencies, and travel buffers configured When ETAs are recalculated Then each task's arrival window is recomputed to the nearest 15-minute granularity within vendor working hours And successor tasks' ETAs are not earlier than predecessor completion plus configured lag And unit-level rollups show the earliest start and latest end across active tasks in the unit And property-level rollups show the earliest start and latest end across active tasks in the property And any window outside quiet hours is shifted to the next permissible window
SLA Threshold Evaluation, Risk Scoring, and Breach Highlighting
Given SLA targets and a warning threshold of 80% of the SLA duration are configured When new ETAs are produced Then tasks projected to exceed SLA are flagged "Projected Breach" with delta time displayed (e.g., +3h 15m) And tasks at or above 80% but below 100% of SLA are flagged "At Risk" And each task receives a numeric risk score 0–100 matching the reference algorithm within ±1 point And portfolio and property summaries display counts by status (On Track, At Risk, Projected Breach) And policy-exempt tasks are excluded from breach counts and labeled "Policy Exempt"
Cross-Surface Updates: Dashboards, Work Orders, Tenant/Vendor Timelines
Given recalculation completes When users open the Operations Dashboard, Work Orders, Tenant Portal timelines, or Vendor Portal schedules Then each surface displays the updated ETA and arrival window, the shift reason code, and a status badge (On Track, At Risk, Projected Breach) And all surfaces show a "Last updated" timestamp within 1 minute of recalculation completion And stale caches are invalidated so no view shows pre-shift ETAs after a hard refresh And tenant-facing times respect locale format and property time zone; vendor-facing times display in 24-hour format
SLA Impact Summary Report for Stakeholder Review
Given a Bulk Shift completes When the SLA Impact Summary is generated Then it includes: total tasks shifted, average ETA delta, counts by SLA status and severity, properties affected, top impacted vendors, and risk score distribution And the report links to affected work orders and can be exported as CSV and PDF And generation completes within 2 minutes for up to 2,000 tasks And the report is accessible from the Bulk Shift record and via a shareable URL with role-based access control
Audit Trail and Change History for ETA Recalculation
Given ETAs and windows existed prior to recalculation When recalculation applies new values Then the system records before/after values, actor, Bulk Shift ID, timestamp, and reason code in an immutable audit log And each affected work order shows a change history entry with the ETA/window diff And audit entries are queryable, exportable, and retained for at least 24 months And multiple recalculations on the same work order are versioned in chronological order
Multi-Party Reasoned Notifications
"As a property manager, I want automated, reasoned notifications to vendors and tenants after a schedule shift so that everyone knows the new plan and can confirm availability."
Description

Dispatch templated, localized notifications to vendors and tenants via email, SMS, and in-app channels, including reason codes, new schedule details, and confirm/decline links. Support batching, rate limiting, quiet hours, retries, and delivery/read receipts. Respect contact preferences and opt-outs. Log all communications to the work order and audit trail.

Acceptance Criteria
Vendor reschedule notification with reason and quote‑refresh
Given a portfolio-wide shift moves a scheduled vendor appointment by >= 1 hour And the vendor locale is es-MX And the delta exceeds the quote-refresh threshold of 2 business days When the shift is approved for dispatch Then the system generates a localized (es-MX) templated message including reason code, previous date/time window, new date/time window, and updated ETA And includes secure one-click Confirm and Decline links And includes a request to submit a refreshed quote with a link And sends via the vendor’s preferred primary channel with automatic fallback to the secondary channel on failure And creates an in-app notification for the vendor portal And logs message ID, channel, locale, reason code, and template version to the work order and audit trail
Tenant notifications honoring quiet hours, batching, and rate limiting
Given tenants affected by a bulk shift have quiet hours set to 9:00 PM–8:00 AM local time And multiple work orders for the same tenant are shifted within a 10-minute window And the reason code is not an emergency/override type When notifications are ready to send during quiet hours Then messages are queued and sent at 8:00 AM local time And messages to the same tenant are batched into a single notification summarizing affected work orders (up to 5 per message) And SMS sending is rate-limited to 25 messages/second per provider limits And the system records a suppression event when quiet hours defer the send And all sends and suppressions are logged to the work order and audit trail
Respect contact preferences and opt-outs with channel fallback
Given a recipient has opted out of SMS and has email enabled And in-app notifications are enabled for the recipient When a bulk shift notification is dispatched Then SMS is suppressed with suppression reason "Opted Out" And email is sent as the primary channel And an in-app notification is created And if all channels are opted out, no message is sent and an internal note is added to the work order And the audit trail records the suppression and chosen channels with timestamps
Reliable delivery with retries, fallback, and idempotency
Given an email send fails with a transient 4xx provider code When the system retries Then it retries up to 3 times with exponential backoff (1m, 5m, 15m) with jitter ±20% And retries respect quiet hours windows And on final failure, delivery status is set to Failed with provider code and reason And a fallback channel (SMS) is attempted once if permitted by preferences And all send attempts share a unique Notification-ID to ensure idempotency and prevent duplicates And all attempts and outcomes are logged to the work order and audit trail
Delivery and read receipts captured and surfaced
Given providers return delivery and read webhooks When a message status changes to Delivered or Read Then the system updates the communication record with status, timestamp, provider message ID, and channel And surfaces the latest status in the work order timeline and recipient profile And stores raw webhook payloads for 90 days for audit And if read receipts are not supported by a channel, Read remains Unknown without retries
Secure confirm/decline flows update schedule and trigger follow-ups
Given a vendor clicks Confirm from an email notification When the signed token is validated and not expired (valid for 14 days, single-use) Then the work order appointment status updates to Confirmed and recalculated ETA is shown And a confirmation message is sent to tenant and property manager per their preferences And the event is logged to the work order and audit trail And if Decline is clicked, the vendor can supply an optional reason And the system triggers re-triage to preferred alternates and notifies the property manager
Auto Quote Refresh on Threshold Breach
"As a property manager, I want quotes to refresh automatically when schedule changes exceed thresholds so that budgets and approvals remain accurate without manual follow-up."
Description

When a shift exceeds configured thresholds (e.g., move >48 hours, beyond quote validity, outside vendor rate windows), automatically request refreshed quotes from preferred vendors with reason codes attached. Transition affected work orders to Awaiting Quote, track responses, update budget estimates, and route approvals to owners/managers. Escalate overdue quote requests per SLA.

Acceptance Criteria
Auto-Request on 48h Time Shift Threshold
Given a work order with a trade and preferred vendors configured and an existing quote on file And the portfolio threshold time_shift_hours = 48 And a bulk shift moves the scheduled date by 72 hours When the shift is applied Then the system sends a quote refresh request to all preferred vendors for the work order’s trade within 5 minutes And the request includes reason_code = "TIME_SHIFT_THRESHOLD" and delta_hours = 72 And the request includes updated target date/time and scope reference And the system records an audit entry with timestamp, requester = "system", and recipients list
Auto-Request on Quote Validity Expired After Shift
Given an accepted quote with valid_until = 2025-09-10 stored on the work order And a bulk shift moves the scheduled date to 2025-09-12 When the shift is applied Then the system sends a quote refresh request to the quoting vendor within 5 minutes And the request includes reason_code = "QUOTE_VALIDITY_EXPIRED" And duplicate refresh requests are suppressed if one was sent for the same work order within the last 12 hours
Auto-Request on Vendor Rate Window Change
Given vendor rate windows are configured (e.g., standard, after-hours, weekend) And a work order scheduled at 10:00 Friday is shifted to 20:00 Friday When the new time crosses into a different rate window Then the system sends a quote refresh request to all impacted preferred vendors within 5 minutes And the request includes reason_code = "RATE_WINDOW_CHANGE" with from_window and to_window values And the request payload includes updated service time window and any access constraints
Transition to Awaiting Quote on Threshold Breach
Given any threshold breach is detected (time shift > threshold, quote validity expired, or rate window change) When the quote refresh request is dispatched Then the work order status changes to "Awaiting Quote" immediately And dependent tasks retain their relative offsets and remain blocked until a quote is approved And the previous status and timestamp are stored in the audit log
Budget Estimate Updates on Quote Responses
Given a work order in "Awaiting Quote" with multiple refresh requests outstanding When one or more refreshed quotes are received Then the system updates budget_estimate to the lowest compliant quote within 2 minutes And records per-vendor quote amounts, validity dates, and attachments And logs variance_from_previous_estimate as absolute and percentage values And recalculates affected ETAs based on the selected quote’s earliest available date
SLA-Based Escalation for Overdue Quotes
Given SLA thresholds configured as reminder_1 = 24h, reminder_2 = 48h, final_escalation = 72h And a work order in "Awaiting Quote" has no vendor response When each threshold is reached Then at 24h the system sends a reminder to vendors and CCs the property manager with reason_code = "QUOTE_OVERDUE_24H" And at 48h it reassigns to the next eligible preferred vendor (if available) and notifies the original vendor of reassignment And at 72h it escalates to the owner with a summary of attempts and marks the escalation in the audit log
Route Approvals on Refreshed Quote Receipt
Given approval rules are configured for the portfolio (e.g., manager <= $500, owner > $500) And a refreshed quote is received and passes validation When routing is evaluated Then the system creates an approval request to the correct approver within 2 minutes And sets the work order status to "Pending Approval" And includes reason_code = "QUOTE_REFRESHED" and variance details in the approval payload And upon approval, updates status to "Scheduled" and sends confirmation to the selected vendor
Bulk Change Audit, Rollback, and Idempotency
"As a compliance-focused admin, I want full auditability and safe rollback for bulk shifts so that I can correct mistakes and satisfy audit requirements."
Description

Capture an immutable audit log for each bulk shift, including initiator, timestamp, scope, reason codes, and before/after schedule values. Provide one-click rollback to prior state and partial undo for failed subsets. Ensure idempotent operations to safely re-run the same bulk shift without duplicating changes, with transactional application, retry logic, and failure isolation per task.

Acceptance Criteria
Immutable Audit Log for Bulk Shift
Given a user initiates a bulk shift with defined scope and reason codes When the bulk shift is submitted and processed Then the system creates a single immutable audit record with fields: bulkShiftId (UUIDv4), initiatorUserId, initiatorRole, submittedAt (UTC ISO-8601), scopeSummary (filters and count of affected tasks), reasonCodes[], per-task before/after schedule values (taskId, oldStart, oldEnd, newStart, newEnd), notificationSummary, vendorQuoteActions, per-task result status And the audit record cannot be modified via UI or API (write attempts return 403 and are logged) And the audit record is queryable by bulkShiftId within 2 seconds via API and visible in the UI audit list filtered by date/id
One-Click Full Rollback Restores Prior Schedules
Given a completed bulk shift with a stored audit snapshot When an authorized user triggers Rollback for that bulkShiftId Then all affected tasks are restored to their pre-change schedule values and dependencies as captured in the snapshot in a single operation And a new audit entry of type "rollback" is created linking to the original bulkShiftId with per-task results And duplicate or repeated rollback attempts perform no changes once state matches the snapshot (idempotent) And notifications to vendors/tenants are sent to supersede prior shift notifications, including a rollback reason code
Partial Undo for Failed Subset
Given a bulk shift where some tasks failed to update When the user selects "Undo failed subset" Then only the failed tasks are reverted to their pre-change state; successful tasks remain unchanged And tasks that cannot be reverted are flagged with error codes and reasons in the audit log And a partial-undo audit entry is created referencing the original bulkShiftId with the list of affected taskIds
Idempotent Re-run of the Same Bulk Shift
Given the same bulk shift payload (scope, shift parameters, reasonCodes) and clientRequestId is submitted more than once When the system processes the subsequent submissions within 24 hours Then the system applies schedule changes only once per task and returns the original bulkShiftId on subsequent runs And no duplicate notifications or quote requests are sent on subsequent runs And if task state has drifted since the original run, the re-run reports conflicts without overriding unless a force option is explicitly provided
Transactional Per-Task Application with Retries and Isolation
Given a bulk shift affecting N tasks When applying changes to tasks Then each task update is executed atomically and independently so one task’s failure does not rollback other successful updates And transient failures are retried up to 3 times with exponential backoff, with each attempt logged in the audit record And final per-task statuses are recorded as Success, Failed-Permanent, Failed-RetryExhausted, or Skipped-Conflict, and totals are summarized in the audit entry
Audit Log Retrieval, Filtering, and Export with Access Control
Given users with appropriate roles request audit history When filtering by date range, initiator, bulkShiftId, reason code, or result status Then matching audit entries are returned within 2 seconds for up to 10k records with pagination And detailed per-task entries can be exported to CSV and JSON with the same filters applied And users without permission cannot access or export audit records (403 response), and access attempts are logged
Correlation Across Notifications, Webhooks, and APIs
Given a bulk shift is executed When notifications, webhooks, and API responses are generated Then each artifact includes the same bulkShiftId and X-Idempotency-Key for traceability And consumers can retrieve the corresponding audit record by bulkShiftId via API And missing or mismatched correlation identifiers in system-generated artifacts are treated as defects and surfaced in monitoring
Role-Based Access & Approval Gates
"As an account owner, I want permission controls and approvals for bulk shifts so that only authorized changes are made and large-scale impacts are reviewed."
Description

Enforce role-based permissions for initiating and approving bulk shifts. Configure approval gates based on scope size, number of affected tasks, projected cost impact, or SLA risk. Require selection of reason codes and a change summary prior to execution. Store approvals and provide read-only visibility to vendors and tenants where appropriate.

Acceptance Criteria
Initiation Permissions for Bulk Shift
Given a signed-in user with a role in the configured initiator roles When they open Bulk Shift and select Initiate Then a draft shift is created and no permission error is shown Given a signed-in user with a role not in the configured initiator roles (including Vendor or Tenant) When they attempt to initiate a Bulk Shift Then the action is blocked with a permissions error and no draft is created Given an unauthenticated user When they navigate to any Bulk Shift URL Then they are redirected to the sign-in flow and no data is exposed
Approval Gates by Scope Size (# Affected Tasks)
Given a draft bulk shift affecting N tasks and a configured task_threshold When N > task_threshold Then a scope-size approval gate is required and Execute is disabled until approved Given N ≤ task_threshold Then no scope-size approval is required and this gate is marked Not Required Given the scope-size gate is required and an authorized approver approves Then the gate status updates to Approved and contributes to enabling Execute
Approval Gates by Projected Cost Impact
Given a calculated projected cost delta amount and percent for the draft bulk shift and configured thresholds amount_threshold and percent_threshold When amount ≥ amount_threshold OR percent ≥ percent_threshold Then a cost-impact approval gate is required and Execute remains disabled until approved by a configured finance approver Given both amount < amount_threshold AND percent < percent_threshold Then the cost-impact gate is Not Required Given the cost-impact gate is required and is rejected by a finance approver Then Execute remains disabled and the draft status shows Rejected with the rejection reason
Approval Gates by SLA Risk
Given the system computes SLA risk for affected tasks and a configured sla_risk_threshold When any task’s risk ≥ sla_risk_threshold after the proposed shift Then an SLA-risk approval gate is required and Execute is disabled until approved by a configured operations approver Given no task’s risk meets or exceeds sla_risk_threshold Then the SLA-risk gate is Not Required Given the SLA-risk gate is required and is approved Then the gate status updates to Approved and contributes to enabling Execute
Reason Code and Change Summary Required Before Execution
Given a user attempts to execute a bulk shift When no reason code is selected Then Execute is disabled and an inline validation message states Reason code is required Given a reason code is selected When the change summary is below the configured minimum length Then Execute is disabled and an inline validation message states Minimum summary length not met Given a reason code is selected and the change summary meets the configured minimum length and all required approval gates are Approved or Not Required Then Execute is enabled
Audit Trail and Read-Only Visibility for Vendors and Tenants
Given a bulk shift reaches execution Then the audit log stores initiator identity, timestamps, reason code, change summary, required gates and their approvers, decisions, and before/after ETA deltas, and is immutable Given a vendor or tenant views the executed bulk shift in the portal When they open the details Then they can see read-only schedule changes and the reason code but cannot edit, approve, or view internal cost impact or internal-only notes Given an internal user with Viewer role opens the executed bulk shift Then they can view the full audit trail read-only and cannot approve, reject, or modify the record

Smart Sections

Automatically organizes each Proof Pack into clear, professional sections—Incident Summary, SLA Timeline, Approvals, Vendor Actions, Cost Breakdown, and Before/After photos. Dynamically adapts to the issue type and property, so reviewers find what they need fast without hunting through threads. Cuts prep time, improves readability, and boosts first‑pass approval rates with insurers and owners.

Requirements

Adaptive Section Templates
"As a property manager, I want proof packs to auto-organize into context-appropriate sections so that reviewers can locate key information quickly and approve on first pass."
Description

Generates standardized sections—Incident Summary, SLA Timeline, Approvals, Vendor Actions, Cost Breakdown, and Before/After Photos—using a rules-driven template engine that adapts to issue type, property profile, and recipient (owner/insurer). Applies conditional inclusion, dynamic ordering, and fallback placeholders when data is incomplete. Integrates with Proof Pack data models and workflows, normalizes terminology, and ensures consistent headings and formatting across devices. Supports locale-aware labels and time zones, and provides telemetry for section usage and completion rates.

Acceptance Criteria
Conditional Section Inclusion by Issue Type and Recipient
Given a Proof Pack with issueType "Plumbing Leak", recipient "Insurer", and complete data When the adaptive template engine generates sections Then the rendered sections are exactly: Incident Summary, SLA Timeline, Vendor Actions, Cost Breakdown, Approvals, Before/After Photos And no additional sections are rendered Given a Proof Pack with issueType "Lockout", recipient "Owner", and no approval required per property profile When the adaptive template engine generates sections Then the rendered sections are exactly: Incident Summary, SLA Timeline, Vendor Actions, Cost Breakdown, Before/After Photos And the Approvals section is omitted
Recipient-Specific Ordering of Sections
Given recipient "Insurer" for any issue type When the adaptive template engine orders sections Then the order is exactly: Incident Summary, SLA Timeline, Vendor Actions, Before/After Photos, Cost Breakdown, Approvals Given recipient "Owner" for any issue type When the adaptive template engine orders sections Then the order is exactly: Incident Summary, Approvals, Cost Breakdown, Vendor Actions, Before/After Photos, SLA Timeline
Fallback Placeholders for Incomplete Data
Given the Before/After Photos section has no "After" photos available When the adaptive template engine renders the section Then the section is included with a standardized placeholder block labeled "After photos pending" and the current timestamp And no null or empty values are displayed Given the Cost Breakdown is missing a vendor invoice total When the adaptive template engine renders the section Then the section displays a placeholder line item "Pending vendor invoice" and subtotal "TBD" Given approvals are required by workflow but no decision is recorded When the adaptive template engine renders the Approvals section Then the section shows a placeholder "Awaiting approval from {role}" populated from workflow context
Locale- and Timezone-Aware SLA Timeline
Given a property with timezone America/Chicago and a viewer in Europe/Paris When the SLA Timeline is rendered Then all timestamps display in America/Chicago with timezone abbreviation And relative durations are computed from UTC and reflect the property timezone Given an SLA event from 2025-03-09 01:30 CST to 2025-03-09 03:30 CDT When the SLA duration is calculated Then the elapsed time displays as 2h 00m Given locale fr-FR on the recipient When sections are rendered Then section headings and labels use the fr-FR locale pack And dates use the fr-FR long format and numbers/currency in Cost Breakdown use fr-FR conventions with the property currency
Consistent Headings and Formatting Across Devices
Given mobile (375x667), tablet (768x1024), and desktop (1440x900) viewports When the Proof Pack sections are rendered Then the section count and order are identical across devices And section headings match the standard labels exactly: Incident Summary, SLA Timeline, Approvals, Vendor Actions, Cost Breakdown, Before/After Photos And no horizontal scrolling occurs at 320px width for any section content And line breaks and spacing do not truncate headings or labels
Terminology Normalization Across Templates
Given source data contains synonyms such as "WO", "Work Order", or "Job" When sections are rendered Then the normalized term "Work Order" is used consistently in headings and body text per the FixFlow glossary Given property-specific custom labels exist in source data When sections are rendered Then standard section headings are preserved and custom terms only appear within section body text where mapped by the glossary
Section Telemetry: Emission and Aggregation
Given a Proof Pack is rendered When the adaptive template engine completes rendering Then it emits a template_rendered event with packId, recipientRole, issueType, propertyId, locale, timezone, renderDurationMs And it emits one section_rendered event per section with sectionKey, included (true/false), hadPlaceholders (true/false) Given telemetry is ingested When daily metrics are computed Then a per-section inclusion rate and completion rate (percent with hadPlaceholders = false among included) are available in the analytics dashboard filtered by recipientRole, issueType, and propertyId
SLA Timeline Generator
"As an operations lead, I want an accurate SLA timeline with milestones and breaches so that I can demonstrate compliance and prioritize exceptions."
Description

Computes and renders a chronological SLA timeline from intake to resolution using workflow events, vendor updates, and approval timestamps. Highlights milestones (acknowledged, dispatched, on-site, resolved), calculates durations, flags breaches and pauses (e.g., tenant unavailable), and annotates responsible parties. Normalizes time zones, supports configurable SLA policies per property/issue type, and exposes a compact and detailed view. Provides API hooks and cached calculations for performance, with graceful degradation when events are missing.

Acceptance Criteria
Compact vs Detailed Timeline View Toggle
- Given a Proof Pack with intake, acknowledged, dispatched, on-site, and resolved events When the user selects Compact view Then the timeline shows only milestones with timestamps and total cycle time - Given the same Proof Pack When the user selects Detailed view Then the timeline shows all events in chronological order with per-segment durations and responsible party annotations - Given either view When the user toggles between views Then rendering completes without error and data remains consistent across views
Time Zone Normalization to Property Locale
- Given events recorded in mixed time zones When the timeline is generated Then all timestamps render in the property's local time zone with UTC offset displayed - Given events spanning a DST change When durations are calculated Then durations are correct to the minute with no discontinuity - Given a property time zone configuration change When the timeline is regenerated Then all timestamps and durations reflect the new time zone
SLA Policy Selection by Property and Issue Type
- Given a property and issue type with a specific SLA policy When the timeline is generated Then thresholds for acknowledge, dispatch, on-site, and resolve are applied from that policy - Given no matching policy When the timeline is generated Then the global default policy is applied - Given the applied policy When the timeline is generated Then the policy identifier and version are included in the output metadata
Milestone Detection and Duration Calculations
- Given workflow events from intake to resolution When the timeline is generated Then milestones acknowledged, dispatched, on-site, and resolved are detected and labeled - Given the detected milestones When durations are calculated Then acknowledge time, dispatch time, travel time, fix time, and total cycle time are displayed rounded to the nearest minute - Given events arrive out of order When generating the timeline Then events are sorted by normalized timestamp before calculation
Breach Flagging and Pause Handling
- Given applied SLA thresholds and registered pause windows When durations are calculated Then pause intervals are excluded from SLA computations - Given any milestone duration exceeds its SLA threshold after exclusions When the timeline is generated Then that segment is flagged as Breach with a badge and reason - Given a pause is added, edited, or removed When the timeline is regenerated Then breach status and durations are recalculated accordingly
Graceful Degradation with Missing Events
- Given one or more expected milestone events are missing When the timeline is generated Then missing milestones are marked as Pending or Unknown and the timeline renders without error - Given a duration depends on a missing event When generating the timeline Then that duration displays as N/A and is excluded from totals - Given missing data is detected When the timeline is displayed Then a non-blocking notice lists which events are missing
API Hooks and Caching Performance
- Given the API endpoint /proofpacks/{id}/slaTimeline When called with view=compact or view=detailed Then the response is 200 with JSON matching the published schema - Given a cached timeline exists When the endpoint is called Then p95 response time ≤ 500 ms and median ≤ 200 ms - Given underlying events, pause windows, or SLA policies change When changes are saved Then the cached timeline for affected Proof Packs is invalidated and recomputed on next access - Given a cold request with no cache When the endpoint is called Then initial computation completes within 1.5 s and returns an ETag header
Approvals & Audit Trail
"As an owner or insurer reviewer, I want a clear approvals history with decisions and evidence so that I can trust the provenance and reduce back-and-forth."
Description

Aggregates all approval events (one-click approvals, insurer decisions, owner sign-offs) with timestamps, actor identity, outcome, and comments into a tamper-evident section. Displays linked artifacts (quotes, invoices, photos), enforces role-based visibility, and supports redaction of PII where required. Captures version history and reason codes for reversals, and records metadata necessary for insurer audits. Integrates with notification system for traceability and provides export-safe formatting.

Acceptance Criteria
Consolidated Approval Events Timeline
Given a maintenance request with recorded approvals and decisions When the Proof Pack is rendered by Smart Sections Then the Approvals & Audit Trail lists 100% of approval events in chronological order (ascending by ISO 8601 timestamp with timezone) And each event displays: timestamp, actor name, actor role, actor organization, outcome (Approved|Rejected|Requested_Changes|Auto_Approved), comment text (if any), and decision channel (Web|Email_Link|API) And events without comments display an explicit “No comment” indicator And the section renders within 500 ms for up to 200 events on a standard tenant case And if no events exist, the section displays “No approvals recorded”
Linked Artifacts in Approval Events
Given approval events that reference artifacts (quotes, invoices, photos) When the Approvals & Audit Trail is displayed Then each event shows a Linked Artifacts list with artifact label, type (Quote|Invoice|Photo|Other), file size, and content hash And clicking a link opens a preview for images/PDFs and initiates download for other types And broken or missing artifact references display a “Missing artifact” badge and are logged And artifact links respect the viewer’s permissions (unauthorized users see a redacted stub) And all linked artifacts are included in exports as references with stable IDs
Role-Based Visibility and PII Redaction
Given a signed-in user with a defined role (Tenant|Vendor|Owner|Property_Manager|Admin|Insurer_Reviewer) When the user opens the Approvals & Audit Trail Then field- and event-level visibility is enforced per role policy (e.g., tenants cannot view insurer comments; vendors cannot view owner PII) And PII-marked fields (email, phone, bank details, policy numbers) are redacted as “[REDACTED]” with a visible reason tag and policy ID for non-privileged roles And authorized roles can view unredacted values via explicit reveal action that is audited with actor, timestamp, and purpose And exports apply identical redaction rules to prevent leakage And all access denials and reveals are captured as audit events
Tamper-Evident Audit Trail Hash Chain
Given approval events are persisted When events are written to the audit store Then each event is assigned a SHA-256 content hash and a previousEventHash to form a verifiable chain And any mutation post-write is blocked; if detected (via recomputation) the chain status is set to FAILED and the UI displays an integrity warning And on render and export, a verification step recomputes hashes and records verificationResult (PASS|FAIL) with timestamp And the export includes a chain summary (firstHash, lastHash, eventCount, verificationResult)
Version History and Reversal Reason Codes
Given an approval decision is edited or reversed When the change is submitted by an authorized role Then a new immutable versioned event is appended capturing priorValue, newValue, actor, timestamp, and required reasonCode from a controlled list (Error_Correction|Policy_Update|Owner_Request|Insurer_Request|Other) And the original event remains immutable and viewable And the UI exposes “View history” showing a diff of outcome/comments per version And exports include all versions in order with the latest clearly marked as current
Notification Traceability and Correlation
Given notifications are configured for approval events When an approval event occurs Then the audit event stores notificationIds for all dispatched messages And each notification record includes channel (Email|SMS|Push|Webhook), recipient, status (Queued|Sent|Delivered|Failed), and timestamps And the Approvals & Audit Trail displays delivery status badges and surfaces failures with retry metadata And exports include the notification correlation for each approval event
Export-Safe Audit-Ready Reports
Given a completed Proof Pack When the Approvals & Audit Trail is exported Then PDF and JSON exports are produced with identical content and schema versioning (schemaVersion present and valid) And JSON validates against the published schema; PDF includes section bookmarks, page numbers, event timestamps with timezone, redaction indicators, and the hash chain verification block And redactions are applied consistently across formats; no PII appears for non-privileged exports And export completes within 5 seconds for <= 200 events and files are named with a stable convention: {caseId}_approvals_{yyyy-MM-dd}_v{n}.{ext}
Vendor Actions & Cost Breakdown
"As a property manager, I want vendor actions and costs summarized by line item so that I can justify expenses and spot anomalies before submission."
Description

Consolidates vendor activity logs (on-site notes, actions taken, parts used) and financials into a readable section with line items for labor, materials, fees, taxes, and discounts. Auto-summarizes total cost, variance vs. estimate, and warranty coverage. Supports multi-currency display based on property locale, standardized units, and links to source invoices. Validates data completeness, flags anomalies (e.g., duplicate parts), and provides roll-up for multi-visit jobs. Exposes export-ready data for accounting sync.

Acceptance Criteria
Consolidated Vendor Activity Log Rendering
Given a job with one or more vendor visits containing on-site notes, actions taken, and parts used When a Proof Pack is generated with Smart Sections Then a single section titled "Vendor Actions & Cost Breakdown" is present And entries are grouped by visit in chronological order (ascending by visit start time) And on-site notes display timestamp, technician, and note text And actions display description, technician, start/end time or duration, and outcome And parts display part name and SKU, quantity, standardized unit, and per-unit cost (if provided) And the section renders within 2 seconds for up to 200 log entries
Line-Itemized Financials with Totals, Variance, Warranty, and Invoice Links
Given labor, materials, fees, taxes, and discounts captured for a job and an estimate amount exists When the section renders Then financials appear as line items grouped by category (Labor, Materials, Fees, Taxes, Discounts) And each line item shows description, quantity (if applicable), unit, unit price, and line total And each line item references at least one source invoice ID with a clickable link And total cost equals Labor + Materials + Fees + Taxes − Discounts within ±0.01 of currency unit And variance vs estimate is computed and displayed as amount and percentage with sign And warranty coverage displays covered amount and remaining charge; if none, shows "No warranty coverage"
Multi-Currency Display by Property Locale
Given a property locale with a defined currency code When the section renders any monetary value Then all amounts display in the property currency with correct symbol/code and locale-specific formatting And the currency is consistent across all subtotals and totals And amounts round to the currency's minor unit And original invoice currency code is shown alongside each invoice link when it differs from the property currency
Data Completeness Validation & Error Flags
Given required fields for vendor logs, financial line items, and invoice links When generating the section Then any entry missing required fields is flagged inline with a validation message naming the missing fields And the section shows an "Incomplete" status badge if any required fields are missing And the "Export" action is disabled until all validation errors are resolved And when all required fields are present, the section shows a "Ready for export" status badge
Duplicate Part/Action Anomaly Detection
Given vendor log entries for a visit When two or more part entries share the same SKU, unit cost, visit ID, and technician within a 10-minute window Then the entries are flagged with "Potential duplicate" and a count of suspected duplicates is displayed And duplicate action entries with identical description, duration, visit ID, and technician within a 10-minute window are similarly flagged And an "Anomalies" callout summarizes counts of duplicates by type And flagged items retain links to their source entries for audit
Multi-Visit Job Roll-Up and Per-Visit Breakdown
Given a job with multiple visits and associated line items When the section renders Then vendor actions and parts are grouped under each visit with timestamps And per-visit subtotals for Labor, Materials, Fees, Taxes, Discounts are displayed And a roll-up total across all visits is displayed and equals the sum of per-visit subtotals within ±0.01 And variance vs estimate uses the roll-up total
Accounting Export (CSV/JSON) with Traceable Line Items
Given the section is marked "Ready for export" When the user triggers "Export" Then CSV and JSON files are generated within 5 seconds And exported records include: job ID, property ID, visit ID, vendor ID, line item category, description, quantity, unit, unit cost, line total, tax amount, discount amount, currency code, invoice ID/link, warranty covered amount, total cost, variance And monetary values match the on-screen values within ±0.01 And invoice links in the export resolve (HTTP 200) or include a stored object ID
Before/After Photo Curator
"As a landlord, I want the best before/after photos highlighted and labeled so that improvements are obvious without digging through galleries."
Description

Automatically selects representative before/after images using quality heuristics (sharpness, exposure, uniqueness) and metadata timestamps. De-duplicates similar frames, applies optional privacy blurs (faces, documents), generates concise captions, and supports manual overrides. Produces a clean side-by-side layout with alt text for accessibility, responsive sizing, and fast-loading thumbnails. Maintains links to full-resolution originals and ensures images are optimized without losing evidentiary value.

Acceptance Criteria
Auto-Select Representative Before/After Images
- Given at least one pre-repair and one post-repair image with timestamps, when the curator runs, then it selects exactly one Before and one After image by default. - Given multiple candidate images, when selecting, then chosen images are the top composite-quality scores within their respective pre/post windows and are not near-identical to each other (SSIM <= 0.97). - Given only pre- or only post-repair images, when selection completes, then the available side is populated and the missing side shows a labeled placeholder (e.g., “No After Photo Provided”). - Given no images, when the curator runs, then it returns an empty state with guidance copy and no errors. - Given the same input set, when run repeatedly, then the selection is deterministic.
Quality Heuristics Scoring and Thresholding
- Given an image set, when scored, then each image receives a composite quality score computed from sharpness, exposure, and uniqueness, normalized to 0–1 with documented weights. - Given images with extreme blur or exposure outliers, when scored, then they are excluded from default selection unless no higher-quality alternative exists (configurable thresholds). - Given two images with equal composite scores, when selecting, then the tie breaks by proximity to the target event window (earliest pre, latest post), then by timestamp, then by file size. - Given a debug flag, when enabled, then per-image component scores and final ranking are visible to QA/admin roles.
Duplicate Frame De-Duplication
- Given visually similar images in the same time window, when evaluated, then images with similarity ≥ 95% SSIM or ≤ 6-bit Hamming distance on 64-bit perceptual hash are treated as duplicates. - Given duplicates, when curating, then only the highest composite-score image remains visible; duplicates are collapsed behind a “View Similar (n)” control. - Given duplicate collapsing, when expanded, then all duplicates are listed with thumbnails and timestamps. - Given de-duplication, when re-run on the same data, then the result set is consistent and idempotent.
Optional Privacy Blurs for Faces and Documents
- Given face or document text regions detected, when privacy blurs are enabled, then those regions are blurred in the curated renditions while originals remain unaltered and linked. - Given blurring, when applied, then the blur does not cover the primary damage area bounding box by more than 5% of its area. - Given a user toggles privacy off for a Proof Pack, when saved, then curated images render without blurs while links to full-resolution originals remain intact. - Given privacy is enabled, when downloading the original, then the file is the unblurred original (with metadata preserved).
Caption and Alt Text Generation
- Given selected Before/After images, when curated, then each image receives a concise caption including status (Before/After), issue type, and capture timestamp (local to property timezone). - Given curated images, when rendered, then each has alt text that describes the scene and status in ≤ 160 characters and passes automated accessibility checks (non-empty alt, contrast unaffected). - Given missing metadata (e.g., timestamp), when generating captions, then placeholders are avoided and human-readable fallbacks are used (e.g., “timestamp unavailable”). - Given manual caption edits, when saved, then edited captions persist across reloads and exports.
Manual Overrides and Locking
- Given curator defaults, when a reviewer selects different Before/After images, then the selection is locked and marked as Overridden. - Given an override lock, when the curator re-runs (e.g., on new uploads), then it does not replace the locked selections unless the reviewer explicitly reverts to Auto. - Given an override, when viewed in audit, then the log shows user, timestamp, original selection, new selection, and reason (optional). - Given revert to Auto, when applied, then the system re-selects using the current rules and data and clears the Overridden indicator.
Responsive Layout, Thumbnails, and Original Links
- Given curated Before/After, when viewed on mobile (≥ 320px), tablet (≥ 768px), and desktop (≥ 1024px), then the layout displays side-by-side where space allows and stacks with clear labels on narrow screens without overflow or cropping. - Given thumbnails, when loaded on a typical 4G connection, then each thumbnail is ≤ 150 KB, lazy-loaded, and the pair renders with LCP ≤ 2.5s on a mid-tier device. - Given optimized renditions, when compared to originals, then visual fidelity is maintained with SSIM ≥ 0.98 and no perceptible artifacts in the damage area. - Given user clicks a curated image, when opened, then it links to the full-resolution original in a new tab, preserving original EXIF (including capture time) and displaying file size.
Section Customization & Export Profiles
"As a portfolio admin, I want customizable section orders and export profiles per recipient so that each stakeholder receives the format they expect."
Description

Enables admins to configure default section order, visibility, and labeling, and to create recipient-specific profiles (e.g., insurer X vs. owner Y) with required fields and formats. Supports theme elements (logo, colors), legal disclaimers, and localized terminology. Provides one-click export to web share and PDF with consistent pagination, table of contents, anchors, and accessible headings. Includes validation rules per profile and previews to ensure compliance before sending.

Acceptance Criteria
Admin Configures Default Section Order & Visibility
Given I am an admin with access to Smart Sections settings And I select property type "Single-Family" and issue type "Plumbing" And I rearrange sections to "Incident Summary, SLA Timeline, Vendor Actions, Cost Breakdown, Approvals, Before/After Photos" And I hide "SLA Timeline" When I click Save Defaults Then the configuration persists and is versioned with a timestamp and admin ID And newly created Proof Packs for "Single-Family"/"Plumbing" default to the saved order And "SLA Timeline" is hidden from editor and exports by default And previously published Proof Packs remain unchanged And the UI displays a confirmation message within 2 seconds of save And attempting to hide a mandatory section is prevented with an inline error identifying the section
Profile-Specific Labels and Required Fields
Given an admin creates a recipient profile "Insurer X" And renames "Incident Summary" to "Loss Summary" And marks "Cost Breakdown" and "Approvals" as required When a user selects profile "Insurer X" for export Then section labels in preview and export use the profile overrides And export is blocked until required sections contain non-empty content And inline validation lists each missing requirement with a section anchor And resolving missing items updates the validation state in real time And the profile name and version are recorded in export metadata
Theme Elements Applied per Profile
Given an admin uploads a 600x200 logo and sets brand colors (primary #0055AA, secondary #FFC107) for profile "Owner Y" And selects a header/footer layout with page numbers and contact info When exporting a Proof Pack with profile "Owner Y" Then the logo renders on the cover and page headers without clipping at 1x and 2x pixel density And primary/secondary colors apply to headings and tables And color contrast meets WCAG 2.1 AA (>= 4.5:1 for text) And header/footer display "Page X of Y" and contact info on all pages except the cover
Legal Disclaimers and Localization
Given profile "Insurer X" defines a legal disclaimer with placement "end" for PDF and "footer" for web share And localized variants are provided for en-US and es-ES When exporting with locale es-ES Then the Spanish disclaimer appears at the specified locations in PDF and web share And the disclaimer block is read-only in both outputs And a timestamp and profile version are appended to the disclaimer block And if a locale variant is missing, the system falls back to the default language and logs a warning
Localized Terminology Mapping
Given profile "Owner Y" maps terminology ("Vendor" -> "Contractor", "Approvals" -> "Authorizations") for locale en-GB When previewing and exporting with profile "Owner Y" and locale en-GB Then all section headings, table headers, and TOC entries reflect the mapped terms And underlying data values remain unchanged And unmapped terms use the system default wording
One-Click Export to Web Share and PDF with Structure
Given a Proof Pack passes all profile validations and profile "Insurer X" is selected When the user clicks Export Then a web share URL is generated and a PDF is produced within 15 seconds at the 95th percentile And the PDF contains a table of contents with correct page numbers matching section start pages And each TOC entry links to the corresponding section anchor in the PDF And the web share page includes anchors for each section for deep-linking And section order, labels, theme, disclaimers, and terminology are consistent across web and PDF And re-exporting with unchanged content produces identical page numbers And the PDF is tagged with a semantic heading structure (H1 title, H2 sections, H3 subsections)
Preflight Validation and Compliance Preview
Given a profile defines rules for required sections, alt text on images, max image size of 10 MB, and presence of cost line items When the user opens Preview for that profile Then the system runs validations and lists blocking errors and non-blocking warnings with counts And the Send/Export action is disabled until all blocking errors are resolved And clicking a validation item scrolls to the offending section or asset And resolving issues updates the validation list in real time And a final preflight summary displays Pass with profile name and version before enabling Export

Audit Seal

Applies a tamper‑evident digital signature and chain‑of‑custody log to every export. Embeds signer history, timestamps, and a verification QR so auditors and insurers can validate authenticity instantly. Reduces disputes, protects against document manipulation, and strengthens compliance posture.

Requirements

Tamper‑Evident Signing Engine
"As a property manager, I want every exported document sealed with a tamper‑evident signature so that I can prove it hasn’t been altered when sharing with insurers or courts."
Description

Implement asymmetric digital signatures applied at the moment of export for all supported document types (e.g., work orders, invoices, maintenance logs, photo bundles). Hash the full byte content plus key metadata (export ID, version, tenant, timestamp) and sign using KMS/HSM‑backed keys. Embed the signature in-file where supported (PDF signature field; JSON detached signature for CSV/JSON) and persist a server-side signature record for cross-check. Enforce fail‑closed behavior (no unsigned exports), emit structured error logs/metrics, and provide idempotent re-signing for regenerated exports. Ensure low-latency operation (<200 ms overhead per export) and compatibility across mobile web and backend export services.

Acceptance Criteria
KMS/HSM-Backed Asymmetric Signature Applied at Export
Given an export is initiated for a supported document type (work order, invoice, maintenance log, photo bundle) When the signing step runs Then the system computes a hash over the exact byte content of the export plus metadata fields: export_id, version, tenant_id, export_timestamp And the hash is signed using a configured KMS/HSM-backed asymmetric private key (key_id present) And the returned artifacts include signature value, algorithm, and key_id And verification with the corresponding public key succeeds
Format-Specific Signature Embedding and Detachment
Given a PDF export When the export completes Then a PDF digital signature field named "Audit Seal" is embedded and reports "signature valid" in standard PDF validators Given a CSV export or a JSON export When the export completes Then a detached signature file named {basename}.sig.json is produced alongside the file containing: algorithm, key_id, hash, signature, export_id, version, tenant_id, export_timestamp And the hash matches the file bytes and verification succeeds Given a ZIP photo bundle export When the export completes Then a detached signature file named {basename}.zip.sig.json is emitted alongside the .zip (not inside it) signing the exact ZIP bytes And verification of the detached signature against the ZIP bytes succeeds
Server-Side Signature Record and Cross-Check
Given a signed export completes Then a server-side signature record is persisted containing: export_id, version, tenant_id, export_timestamp, key_id, algorithm, hash, signature, created_at When the cross-check API is called with export_id and version by an authorized subject Then the record is returned and the recomputed hash of the stored bytes plus metadata equals the stored hash, and the signature verifies against key_id When any byte of the export or any included metadata field changes Then cross-check returns a verification status of "mismatch" and indicates the failing component (bytes vs metadata)
Fail-Closed Behavior with Structured Errors and Metrics
Given KMS/HSM is unreachable, misconfigured, or the signing operation fails for any reason When an export is attempted Then the export is blocked (no unsigned artifact is returned) and the API responds with HTTP 5xx and error_code="SIGNING_FAILED" including export_id and doc_type And a structured error log entry is written with fields: export_id, version, tenant_id, doc_type, key_id (if available), error_code, latency_ms, correlation_id And metrics increment signature_failure_total and observe signing_latency_ms And no export files are persisted or delivered without a valid signature
Idempotent Re-Signing for Regenerated Exports
Given an export with export_id=X and version=V has been successfully signed When the same export is regenerated with identical bytes and the same export_id and version (including the original export_timestamp) Then the system returns the existing signature and signature record without creating duplicates And concurrent retry requests (up to 5 simultaneous) return 200 with the same signature And if bytes differ or the version changes, a new signature is generated and a new record is created
Low-Latency Signing Overhead
Given representative workloads across all supported document types When measuring signing overhead under 50 concurrent exports in the staging environment Then P95 signing overhead per export is <= 200 ms and P99 <= 300 ms, measured from hash start to signature availability And a signing_latency_ms histogram is emitted per document type and overall And an alert triggers if P95 > 200 ms for 5 consecutive minutes
Cross-Platform Export Compatibility (Mobile Web and Backend Services)
Given a user initiates an export from mobile web (latest iOS Safari, Android Chrome) and from backend export services When each export completes Then all resulting artifacts are signed following the same rules regardless of initiation source And no client-side verification or signing capability is required for success And signature verification results are identical across initiation sources for the same bytes and metadata
Chain‑of‑Custody Ledger
"As an auditor, I want a complete chain‑of‑custody log for each export so that I can trace who did what and when without gaps."
Description

Create an append‑only, immutable event ledger that records every custody‑relevant action for an export: creation, edits, approvals, vendor assignment, status changes, export generation, downloads, and external shares. Link events via cryptographic digests and monotonic timestamps to detect gaps or tampering. Store write‑once snapshots of critical states and expose a query API and UI panel that renders a time‑ordered, human‑readable trail per export. Integrate with existing workflow services and ensure high availability, retention policies, and redaction safeguards for sensitive fields.

Acceptance Criteria
Ledger Is Append-Only With Cryptographic Linking
Given an export with existing ledger events When a new custody-relevant action is recorded Then the system appends a new immutable event (no updates/deletes permitted) and never alters prior events And the event contains: event_id (UUIDv4), export_id, actor_id, actor_type, action_type, canonical_event_json, action_payload_hash (SHA-256), prev_event_digest (hex), event_digest (hex), monotonic_timestamp (int64), recorded_at_utc (ISO-8601), signature And event_digest = SHA-256(prev_event_digest || canonical_event_json) And prev_event_digest references the immediate predecessor or the literal GENESIS for the first event And attempts to update or delete any event via API/UI are rejected with 405 Method Not Allowed and are themselves logged as security events And attempts to write an event with monotonic_timestamp less than the previous event’s monotonic_timestamp are rejected with 422 MonotonicityError And writes are idempotent by event_id; replaying the same event_id returns 200 with no duplicate row and an identical event_digest
Complete Event Coverage for Custody-Relevant Actions
Given custody-relevant actions occur on an export When the following actions happen: creation, field edit, approval/denial, vendor assignment/change, job status change, export generation, export download, external share/create/expire/revoke, access via share, and API retrieval of the export Then an event of the correct action_type is appended within 2 seconds of the action’s commit And each event includes actor identity (system/user/service), source (UI/API/webhook), request_id/correlation_id, and action_payload_hash derived from a canonicalized payload And downloads and external shares capture requester principal, IP (truncated per policy), user-agent, and success/failure code And missing any of the above events in a sampled audit of 1,000 actions yields a defect; target capture rate ≥ 99.9% over a rolling 30-day window
Write-Once Snapshots of Critical States
Given a critical state transition occurs (approval decision, vendor assignment set, export file generation) When the transition commits Then a content-addressed snapshot (SHA-256 of canonical bytes) is stored in WORM storage with immutability lock for the tenant’s retention period And the snapshot_hash and storage_uri are recorded in the corresponding ledger event And retrieving the snapshot via API requires scope ledger.read and returns bytes whose SHA-256 equals snapshot_hash And attempts to overwrite, delete, or shorten immutability of a locked snapshot are rejected by storage and surfaced as 403 ImmutableObject with reference to the storage request_id And a dedicated redaction pathway can cryptographically erase encrypted fields inside the snapshot without changing the original event_digest; a new redaction_performed event is appended with details of fields affected
Tamper Detection and Chain Verification
Given an auditor requests verification for an export’s ledger When the API GET /exports/{id}/ledger/verify is invoked Then the response includes: chain_status (PASS|FAIL), first_failure_index (nullable), failure_reason (nullable), head_event_digest, total_events, verified_range (start_index,end_index) And verification fails if: any prev_event_digest mismatch, missing index in sequence, non-monotonic timestamp, or digest recomputation mismatch And the endpoint returns P95 latency ≤ 500 ms for ledgers ≤ 2,000 events and supports pagination-based streaming verification for larger chains And the UI panel displays a visible badge: Verified (green) when chain_status=PASS, Warning (amber) when partial data, Failed (red) when chain_status=FAIL And the system emits a tamper_alert security event within 60 seconds on first FAIL per export
Query API for Time-Ordered Ledger Access
Given an authenticated client with scope ledger.read When the client calls GET /exports/{id}/ledger with optional filters (start_time, end_time, event_types[], page_size≤200, cursor, sort=asc|desc) Then the API returns a time-ordered list of events with stable pagination cursors, including fields necessary to recompute digests And default sort is ascending by monotonic_timestamp, ties broken by event_id And P95 latency ≤ 300 ms for page_size=100; P99 ≤ 800 ms; availability ≥ 99.95% monthly And rate limit is enforced at 600 requests/minute per tenant with 429 responses including Retry-After And responses exclude or redact sensitive payload fields per tenant policy while preserving action_payload_hash and verification capability
UI Panel Renders Human-Readable Chain of Custody
Given a user views an export in FixFlow When the user opens the Chain of Custody panel Then the panel lists events in chronological order with columns: Local Time (tenant TZ), UTC Time, Actor, Action, Summary, Verification And selecting an event reveals a details drawer showing canonical_event_json (redacted as per policy), event_id, prev_event_digest, event_digest, snapshot links (if any), and copy-to-clipboard controls And the panel shows verification status for the current view (Verified/Warning/Failed) consistent with the verify API And accessibility meets WCAG 2.1 AA: keyboard navigation, focus states, and screen-reader labels for all interactive elements And loading 200 events renders in ≤ 1.0 s on a standard device (Lighthouse ≥ 85 for this view)
Retention, High Availability, and Redaction Safeguards
Given tenant-level policy defines ledger_retention_years and pii_redaction_policy When events exceed the active retention period Then they are transitioned to archival storage while preserving head_event_digest and inclusion proofs; verification continues to PASS for retained ranges And the service meets RPO ≤ 5 minutes and RTO ≤ 30 minutes with data stored across ≥ 3 availability zones; monthly write availability ≥ 99.95% And PII is stored only in encrypted form; redaction is performed via cryptographic erasure of encryption keys, leaving header/digest intact and appending a redaction_performed event with reason, requester, and fields affected And all access to ledger and snapshots is audited with who/what/when and retained for the policy period And configuration changes to retention or redaction policies require dual authorization and are logged as policy_change events
Embedded Signer History & Timestamps
"As an insurance adjuster, I want signer history and timestamps visible in the document so that I can validate actions without logging into the platform."
Description

Embed visible and machine‑readable signer history into each export, including user identity, role (landlord, PM, approver), action (approved, edited, dispatched), and ISO‑8601 timestamps with synchronized time source. Present a concise signer panel within the document (e.g., PDF overlay/metadata) and include the same data in structured metadata for automated validation. Respect privacy by limiting to minimum required identifiers, while preserving strong linkage to platform accounts and the ledger entry IDs.

Acceptance Criteria
PDF Signer Panel Overlay
Given a maintenance request export is generated as a PDF When the export is produced Then page 1 contains a "Signer Panel" overlay positioned in the top-right margin area without obscuring more than 5% of underlying content And each signer action is rendered as a separate row with fields: action, role, accountId(short), ledgerEntryId(short), timestamp And the timestamp is formatted as ISO-8601 UTC with millisecond precision (e.g., 2025-09-06T14:03:22.123Z) And text is embedded as selectable vector text (not raster), font size >= 8 pt, contrast ratio >= 4.5:1 And rows are sorted chronologically ascending by timestamp, with ties broken by ledgerEntryId lexicographic order And accountId(short) is the first 8 characters of the platform account UUID; ledgerEntryId(short) is the first 8 characters of the ledger ID And the overlay is present on all exported pages that contain signature-relevant content, or a summary page is appended if the document exceeds 5 pages
Structured Metadata Mirrors Panel
Given a maintenance request export is generated When inspecting the PDF's XMP metadata under the namespace fixflow:signers or the bundled JSON manifest signers[] Then the number of signer entries equals the number of rows in the visible Signer Panel And for each entry, fields action, role, accountId (full UUID v4), ledgerEntryId, timestamp, sequence match the visible panel values (accountId/ledgerEntryId may be truncated in the panel but must match by prefix) And the JSON/XMP validates against schema version v1.0 with required fields [action, role, accountId, ledgerEntryId, timestamp, sequence] And the timestamp values are identical across metadata and panel And an automated validator can parse and extract all fields within 100 ms on a mid-tier device
Synchronized ISO-8601 Timestamps
Given the exporting service has network access to the time source When an export is generated Then all signer timestamps are recorded in UTC ISO-8601 with millisecond precision and a trailing Z And the time delta between recorded timestamp and an authoritative NTP source is <= 5 seconds at time of export And metadata includes timeSource details: {provider: "NTP", server: "<fqdn>", lastSyncAt: ISO-8601 UTC} And if time delta > 5 seconds or time source unavailable, the export is blocked and the user is shown error "Time source not synchronized" with retry option
Privacy-Minimized Identifiers
Given signers have platform accounts When signer identity is embedded Then the visible panel includes only role, action, accountId(short), ledgerEntryId(short), and initials (two-letter initials derived from display name) And no email address, phone number, physical address, or government identifiers appear in the visible panel or metadata And the structured metadata includes full accountId (UUID v4) and ledgerEntryId to preserve linkage And an automated PII scan on the rendered PDF and metadata finds zero instances of patterns matching emails or E.164 phone numbers
Action and Role Coverage
Given actions may include approved, edited, dispatched and roles include landlord, property_manager, approver When an export is generated for a request with these events Then each event appears exactly once with its correct role and action And any event referencing an unknown role or missing accountId is rejected and the export fails with error "Invalid signer data" And events are ordered strictly by timestamp; if two events share the same timestamp, the sequence field provides a deterministic order And the metadata includes a complete list of actions present in the export under signers[].action without omissions
Verification Endpoint Consistency
Given the export contains a verification QR code linking to the verification endpoint When the QR is scanned and the endpoint returns signer history JSON Then the endpoint's signer entries count and field values match the embedded metadata exactly for action, role, accountId, ledgerEntryId, timestamp, sequence And any mismatch causes the verification status to be "Invalid" with a reason code "SIGNER_MISMATCH"
Verification QR Encoding
"As a field inspector, I want to scan a QR on the document to verify authenticity instantly so that I can make decisions on‑site."
Description

Generate and place a scannable QR code on each export that encodes a signed verification URL and short alphanumeric code. On scan, route to the public verification endpoint; on manual entry, allow code lookup. Include checksum and nonce to prevent URL tampering and resistant error correction level for reliable scanning from printed copies. Ensure consistent placement on PDFs and a coversheet for multi‑file bundles; provide dark‑mode and printer‑friendly rendering.

Acceptance Criteria
QR Payload Structure and Tamper Resistance
Given an export is generated When the QR payload is decoded Then it contains: verification_url (HTTPS), short_code (8–12 uppercase alphanumeric), nonce (unique per export), checksum, and a cryptographic signature Given the QR payload is validated When the signature is checked against the system public key Then the signature verification succeeds for an unmodified payload Given any single-character change to the verification_url, short_code, nonce, or checksum When the verification endpoint processes the modified payload Then the request is rejected with 4xx and no document data is returned Given a batch of 10,000 consecutively generated exports When short_codes and nonces are inspected Then zero collisions are found within the batch
Scan Routing to Public Verification Endpoint
Given a QR is scanned using default iOS/Android camera apps When the device opens the encoded link Then it routes to the public verification endpoint over TLS 1.2+ and returns HTTP 200 for valid tokens Given a valid, untampered QR When the verification page loads Then it displays status "Valid", export identifier, timestamp, and the short_code within 2 seconds (p95) on a 3G network profile Given a tampered or expired token When the link is opened Then the endpoint returns a human-readable 4xx error and does not expose any document content
Manual Short-Code Lookup
Given a user cannot scan the QR When they navigate to the public verification page and enter the short_code manually Then the system performs a case-insensitive lookup and displays the same verification result as the QR scan flow Given an invalid or unknown short_code When it is submitted Then the system responds with "Code not found" and HTTP 404 without revealing any metadata Given a valid short_code with leading/trailing whitespace When it is submitted Then the input is normalized and the lookup succeeds
Print Reliability and Error Correction
Given a PDF export is printed on A4/Letter at 300 DPI (grayscale) or 600 DPI (B/W) When the printed QR is scanned under indoor lighting (≥200 lux) Then 10/10 scans succeed using default iOS/Android camera apps Given the QR is partially obstructed or degraded When up to 20% of the code area is damaged Then the QR remains scannable due to an error correction level of at least Q Given the QR is generated When measured on the page Then its physical size is ≥25 mm with a quiet zone of ≥4 modules and a contrast ratio ≥12:1 (dark modules to background)
Consistent Placement on PDFs and Bundle Coversheet
Given a single-file PDF export When the QR is placed Then it appears at bottom-right within a 10–15 mm margin, sized 30–35 mm, above page content, and does not overlap existing text or images Given a multi-file bundle export When the bundle is created Then a coversheet is generated as page 1 with the QR placed in the same position and size as single-file exports Given varying page sizes or orientations (A4/Letter, portrait/landscape) When the PDF is generated Then the QR position remains bottom-right relative to page orientation with consistent margins and size
Dark-Mode and Printer-Friendly Rendering
Given the verification QR is rendered on-screen in dark mode When the page is viewed Then the QR is drawn as dark modules on a white bounding box (no inversion) and remains fully scannable Given the export is printed using a "printer-friendly" option When the document is produced Then the QR is rendered as pure black on white without gradients, shadows, or color fills Given the QR and label are rendered When checked in the PDF Then the short_code is printed in a legible monospace font ≥10 pt beneath the QR and the QR is embedded as vector or ≥300 DPI bitmap
Public Verification Portal
"As a third‑party auditor, I want a public verification portal so that I can validate documents without requesting system access."
Description

Deliver a no‑login, rate‑limited web portal where third parties can upload a file, paste a verification code, or arrive via QR to validate authenticity. Perform signature verification against FixFlow’s key registry and cross‑check with the chain‑of‑custody ledger. Display a simple result (Valid, Invalid, Unknown), show signer history, timestamps, export metadata, and the relevant ledger hash. Provide accessibility compliance, localization readiness, and an API endpoint for partners to automate bulk checks.

Acceptance Criteria
Code-Based Verification via Entry or QR
Given an unauthenticated user arrives via a QR deep link with an embedded verification code or pastes a code into the verification field When the user submits the verification request Then the system validates the input is non-empty, <= 64 characters, and contains only visible characters; invalid input shows an inline error without calling the backend And no login or account creation is required at any step And the system verifies the associated digital signature against the FixFlow key registry and cross-checks the chain-of-custody ledger And an outcome is computed and returned within 2.5 seconds at p95 And the user is routed to the result view
File Upload Verification
Given an unauthenticated user selects a file to verify When the user uploads a file up to 25 MB that is a supported FixFlow export Then disallowed types or oversized files are blocked client-side with an accessible error and no network request is made And for allowed files, the server extracts Audit Seal metadata, verifies the digital signature against the FixFlow key registry, and cross-checks the chain-of-custody ledger And an outcome is returned within 4 seconds at p95 for 25 MB files And if Audit Seal metadata is missing or unreadable, the outcome is Unknown with an explanatory message
Result Rendering for All Outcome States
Given a verification completes for any input method When the system produces an outcome Then the page prominently displays one of: Valid, Invalid, or Unknown with distinct color, icon, and text label And for Valid: signer history (ordered), timestamps, export metadata, and the relevant ledger hash are displayed and copyable And for Invalid: a clear reason (e.g., signature mismatch, revoked key, altered content) is shown; unverified metadata is not presented as trusted And for Unknown: an explanation is shown (no match, incomplete data, or unsupported format) with next-step guidance And timestamps are formatted in ISO 8601 with timezone; the ledger hash is truncated for display with a control to copy the full value And no PII beyond intended export metadata is displayed
Rate Limiting and Abuse Protection
Given the public portal is accessed by any client When more than 60 verification attempts from the same IP occur within 60 seconds Then subsequent requests receive HTTP 429 with a Retry-After header and an on-page cooldown message And rate limits are enforced per IP and per user agent to mitigate scripted abuse And legitimate users under thresholds experience no challenges or delays And rate-limit events are logged with timestamp and hashed IP for monitoring
Accessibility Compliance (WCAG 2.1 AA)
Given users relying on keyboard and screen readers access the portal When navigating all views and completing a verification Then all interactive elements are keyboard operable with visible focus; no keyboard traps exist And form fields have programmatically associated labels; errors are announced via aria-live and include programmatic focus And text and UI components meet contrast ratios of ≥ 4.5:1 (text) and ≥ 3:1 (components) And semantic landmarks and heading hierarchy are present; QR instructions have equivalent text And automated audit (axe-core) reports zero critical violations; manual smoke tests pass on NVDA (Windows) and VoiceOver (macOS/iOS) for core flows
Localization Readiness
Given the portal is switched to a non-default locale When rendering all user-facing text and formatted values Then all strings are sourced from localization files with 100% coverage for keys in the verification flow And dates/times, numbers, and pluralization are locale-aware via the i18n framework And switching between en-US and one additional locale (e.g., es-ES) does not cause layout overflow or truncation in core views And RTL rendering is visually correct for critical views; no concatenated strings block translation
Partner Bulk Verification API
Given a partner with a valid API key calls POST /api/v1/verification/bulk with up to 100 items per request When each item provides either verificationCode or fileHash Then the API returns HTTP 200 with per-item results including status (Valid, Invalid, Unknown), reason, signer history, timestamps, export metadata, and the relevant ledger hash where applicable And requests exceeding 100 items or 5 MB payload return 413 with an error payload; malformed requests return 400; missing/invalid API key returns 401 And p95 latency is ≤ 3 seconds for 100 items; rate limit is 600 items/minute per API key with HTTP 429 and Retry-After on exceed And OpenAPI documentation is published and matches actual request/response schemas
Key Management & Rotation
"As a security officer, I want managed keys with rotation and revocation so that document signatures remain trustworthy over time."
Description

Use cloud KMS/HSM‑backed asymmetric keys with strict access controls, audit logging, and automated rotation policy. Maintain key versioning and certificate chains to allow long‑term validation of older documents. Implement instant key revocation with backfilled status markers on affected exports, health checks for signing endpoints, and alerting for anomalous signing activity. Document disaster recovery procedures and provide a read‑only key registry to the verification portal.

Acceptance Criteria
KMS/HSM-Backed Asymmetric Signing Keys with Least-Privilege Access
Given the signing service is deployed, When it requests to sign, Then the private key remains non-exportable in cloud KMS/HSM and signing occurs within KMS. Given IAM policies are configured, When an unauthorized principal attempts kms.sign or getPublicKey, Then the request is denied (403/PermissionDenied) and the attempt is recorded in audit logs. Given the signer service account, When its permissions are inspected, Then it has only kms.sign and getPublicKey on the specific key resource and no admin privileges (no create, destroy, import, setIamPolicy). Given key properties are queried via KMS API, When checked, Then key purpose is AsymmetricSign and protection level is HSM (or provider’s managed HSM class) with key export disabled. Given audit logging is enabled, When a signing operation completes, Then KMS audit logs include principal, keyId/version, timestamp, and outcome for each operation.
Automated Key Rotation with Backward Validation via Key Versioning
Given a rotation interval is configured, When the interval elapses, Then a new key version is created automatically and set as primary for new signatures without downtime. Given documents signed before rotation, When they are verified after rotation, Then verification succeeds using the original key version and its certificate chain. Given a signature is generated, When its metadata is inspected, Then the Audit Seal embeds keyVersionId, key fingerprint, and certificate chain reference. Given a rotation attempt fails, When promotion of the new version errors, Then alerts fire and the previous primary remains enabled until successful promotion. Given observability is enabled, When rotation completes, Then rotation_success metrics with keyId, newVersion, and duration are emitted and a dashboard shows the last rotation timestamp.
Instant Key Revocation and Backfilled Export Status
Given a key version is marked compromised, When an authorized operator revokes it, Then revocation propagates within 5 minutes and the key can no longer be used for signing. Given revocation occurs, When the system scans existing exports signed by that key version, Then all affected exports are backfilled within 15 minutes with a 'Signer Key Revoked' marker including revokedAt, keyVersionId, and reason. Given a revoked key version, When a document signed by it is verified in the portal, Then the portal shows 'Signature Revoked' with timestamp and returns a machine-readable status code REVOKED. Given revocation is in effect, When any signing request targets the revoked key, Then the service returns 403 and emits a high-severity alert.
Comprehensive Audit Logging and Anomalous Signing Alerts
Given any signing action occurs, When audit logs are inspected, Then each entry includes tenantId, principal, keyId/version, document fingerprint, UTC ISO-8601 timestamp, source IP, outcome, and latency. Given audit logs storage, When nightly integrity checks run, Then log batches validate via hash chain or WORM checksum without gaps or tampering. Given alert policies are configured, When signing volume exceeds baseline by 3x in 5 minutes, occurs outside allowed hours, or originates from a new IP range, Then a P1 alert is sent to on-call within 2 minutes and an incident is created. Given log retention policy, When querying logs within the configured window (>= 365 days), Then logs are accessible read-only via the analytics interface and API.
Read-Only Key Registry Exposed to Verification Portal
Given the verification portal requests the key registry API, When the response is returned, Then it lists all key versions with status (Active, Disabled, Revoked), createdAt, rotatedAt, revokedAt, key fingerprint, and certificate chain links. Given a client attempts POST/PUT/PATCH/DELETE on the registry, When the request is made, Then it fails with 405/403 and the attempt is logged. Given a key state changes (create, rotate, revoke), When the change occurs, Then the registry reflects the new state within 60 seconds. Given a QR-based verification, When the portal resolves signer data, Then it uses the registry to validate the signer chain and displays the signer history without requiring write access.
Signing Endpoint Health Checks and Safe Degradation
Given the /healthz endpoint is called, When KMS connectivity and key readiness are OK, Then it returns 200 with kms_connectivity=true, active_key_version present, and sign_latency_p95<200ms over the last 5 minutes. Given KMS is unreachable or no active key is available, When /healthz is called, Then it returns 503 with failing checks and the service fails closed (blocks signing). Given a degradation is detected, When SLOs breach, Then an alert is triggered and the dashboard reflects RED status for signing availability. Given a canary sign test runs every minute, When two consecutive failures occur, Then traffic is drained and on-call is paged.
Disaster Recovery for Keys and Signing Services
Given a simulated region failure, When the DR runbook is executed, Then the signing service is restored in a secondary region using multi-region KMS keys within 60 minutes RTO and audit logs/key registry have RPO<=5 minutes. Given historical documents pre-incident, When verified post-failover, Then signatures remain verifiable via preserved certificate chains and key versions. Given quarterly DR tests, When completed, Then evidence (test results, timestamps, approver) is stored immutably and is retrievable by auditors. Given a primary key destruction simulation, When DR is executed, Then no private key material is exported, a new key version is promoted as primary, and historical verification continues to use prior key versions unaffected.
Export Format Coverage & One‑Click Enablement
"As a landlord admin, I want a single setting to apply Audit Seal to all exported documents so that my team doesn’t need to manage it per file."
Description

Support Audit Seal across all export channels used in FixFlow (PDF work orders/invoices, CSV reports, JSON API payloads, ZIP photo archives). Provide an account‑level toggle to enable sealing by default, with per‑template overrides. Ensure backward compatibility for existing templates, background jobs to bulk‑seal historical exports, clear UI indicators of sealed status, and robust error handling/retry logic in export pipelines.

Acceptance Criteria
Account-Level Default Sealing Toggle
Given an account has the sealing default set to Enabled When a user exports a PDF work order, CSV report, JSON API payload, or ZIP photo archive Then each export is sealed and the export record stores a seal_id and verification_url Given an account has the sealing default set to Disabled When a user exports the same formats Then no seal is applied and no QR or seal metadata is embedded or attached When the toggle is changed Then subsequent exports honor the new default without requiring template edits and previous exports remain unchanged
Per-Template Override Behavior
Given a template override is set to Off while the account default is Enabled When exporting using that template Then the export is not sealed and the export record shows override=Off as the effective source Given a template override is set to On while the account default is Disabled When exporting using that template Then the export is sealed and the export record shows override=On as the effective source Given a template has no override When exporting using that template Then sealing behavior follows the current account default
Export Format Coverage and Verification Artifacts
Given a sealed PDF export is generated Then the PDF contains a visible verification QR on page 1, an embedded digital signature bound to the PDF bytes, signer history, and an issuance timestamp Given a sealed CSV report is generated Then the export record and download page expose a verification_url and seal metadata and the CSV content hash matches the hash recorded in the seal Given a sealed JSON API export is requested Then the response includes a seal object with signature, signer history, issuance timestamp, and verification_url, and the signature validates against the serialized payload hash Given a sealed ZIP photo archive is generated Then the archive includes a manifest of files and hashes and the seal validates the manifest and contained files When the verification_url is requested or the QR is scanned Then the verification service returns Authentic with matching hash for untampered exports within 2 seconds at p95
Backward Compatibility and Non-Breaking Outputs
Given sealing is Disabled When exporting using existing templates Then the exported file bytes, file name, and MIME type match pre-feature baselines for each format Given sealing is Enabled for a non-PDF format When exporting Then the primary file's content and MIME type remain unchanged and seal artifacts are accessible via metadata or sidecar without altering the primary payload Given API clients pinned to previous versions When calling existing endpoints without requesting seal data Then responses remain schema-compatible and do not include seal fields
Historical Bulk-Sealing Job
Given Audit Seal is enabled and there are 10,000 historical exports without seals When the bulk-seal background job runs Then it processes at least 200 exports per minute, skips already sealed items, and is idempotent When transient failures occur during sealing Then the job retries up to 5 times with exponential backoff and moves irrecoverable items to a Needs Attention queue with error details When the job is paused or crashes Then it resumes from the last successfully sealed export without duplicating seals Then a progress dashboard shows total candidates, processed, succeeded, failed, and ETA
UI Indicators of Sealed Status
Given a user views the Export History page Then each row displays a Sealed or Unsealed badge, timestamp, and a Verify action when sealed Given a user opens a sealed PDF Then a Sealed by FixFlow ribbon and QR appear on page 1 without overlapping template content Given a user downloads a sealed non-PDF export Then the confirmation or details view displays sealed status and a link to verify authenticity Then all indicators meet WCAG AA contrast and include accessible labels and tooltips
Export Pipeline Error Handling and Deferred Sealing
Given the signing service is unavailable during export When a user triggers an export Then the primary export completes, is marked Unsealed with pending_seal status, and a non-blocking notification is shown Then the system automatically retries sealing up to 3 times over 30 minutes with exponential backoff When a later retry succeeds Then the export status updates to Sealed, verification artifacts are attached, and a single success notification is sent if a prior failure alert was issued When retries are exhausted Then the export remains Unsealed, an alert is logged to monitoring, a banner appears with remediation options, and support receives an event

PII Redactor

Auto‑detects and masks sensitive data—tenant names, phone numbers, access codes, and payment details—across photos, messages, and attachments before sharing. Includes role‑based presets (Insurer, Buyer, HOA) and keeps a secure unredacted original for internal records. Minimizes privacy risk while keeping evidence usable.

Requirements

Multi-Modal PII Detection Engine
"As a property manager, I want FixFlow to automatically detect sensitive information in photos, messages, and attachments so that I don’t accidentally share private data with external parties."
Description

Implement a service that automatically detects sensitive data in text, images, and common document formats across FixFlow (messages, comments, photos, PDFs, and office docs). Coverage includes tenant names, phone numbers, emails, property addresses, access/lockbox/door codes, and payment details (card numbers, bank routing/account). Use a hybrid approach: OCR for images/screenshots, pattern/rule-based extraction for structured numbers, and NLP models for names and addresses. Provide per-entity confidence scores, configurable thresholds, and a pluggable taxonomy so new PII types can be added without code changes. Return structured detections with offsets/bounding boxes for downstream redaction, support English and Spanish initially, and handle HEIC/large image downscaling while preserving coordinates. Expose an idempotent internal API and emit detection metrics for monitoring and tuning.

Acceptance Criteria
Detect PII in text messages/comments (EN/ES) with offsets and thresholds
Given a text input in English or Spanish containing tenant names, emails, US phone numbers, street addresses, access codes, and payment details When the detection engine processes the text with a default threshold of 0.70 Then it returns a JSON array of detections where each item includes entity_type ∈ {person_name, email, phone, address, access_code, card_number, bank_routing, bank_account}, start_offset, end_offset, confidence, and normalized_value when applicable And all offsets are byte-based UTF-8 positions relative to the original text And no detection with confidence < 0.70 is included And updating the per-entity threshold via configuration or request parameter applies within the request and is reflected in the returned set And overlapping duplicate hits of the same entity_type and span are deduplicated
OCR-based PII detection in images with HEIC and coordinate preservation
Given input images in HEIC, JPEG, or PNG up to 50 megapixels with EXIF orientation metadata When the engine downscales for OCR and detects PII Then each detection includes bounding_box coordinates relative to the original image resolution (x, y, width, height in pixels) And coordinates mapped back from the processing scale differ by ≤2 pixels from ground-truth overlays And per-detection confidence is present; boxes with confidence below the configured threshold are excluded And EXIF orientation is respected such that bounding boxes align to the visually upright image And HEIC images are decoded without error and produce detections equivalent to the same content in JPEG
PII detection in PDFs and Office documents with page-level locations
Given a multi-page PDF and DOCX/XLSX/PPTX containing digital text and embedded scanned pages with PII When the engine processes the documents Then detections from digital text include page_index and character offsets (start_offset, end_offset) within that page's text stream And detections from scanned pages include page_index and bounding_box coordinates in page pixel space And card_number detections pass Luhn validation; bank_routing passes ABA checksum; bank_account appears within three tokens of 'account' or 'acct' And access_code is a 4–10 digit sequence within three tokens of 'code', 'PIN', or 'lockbox' (case-insensitive) And emails and phones conform to RFC5322-compatible email and E.164/US phone patterns respectively
Pluggable taxonomy and per-entity configuration without redeploy
Given a new PII type 'passport_number' added via the taxonomy configuration store with its pattern, context rules, and a threshold of 0.80 When the configuration is published Then subsequent detection requests reflect the updated taxonomy and include 'passport_number' detections when present And disabling an existing type via configuration results in zero detections of that type in subsequent requests And per-entity thresholds in configuration override request defaults for that entity And no service restart or code deployment is required for the change to take effect
Idempotent internal API returns deterministic structured detections
Given the same request payload (content bytes, content_type, language_hint, thresholds, taxonomy_version) is submitted multiple times with the same idempotency_key within a 24-hour window When the engine processes the requests Then the HTTP status and response body, including detection_ids, ordering, and values, are identical across attempts And the response conforms to the schema including request_id, resolved_language, and entities[] with entity_type, confidence, location (offsets or bounding_box), normalized_value, and redaction_hint And if a different payload is sent with the same idempotency_key within the window, the API returns 409 Conflict without processing the new payload
Detection metrics published for monitoring and tuning
Given a detection request completes When metrics are emitted Then a metrics event is sent to the monitoring sink containing request_id, content_modality ∈ {text, image, pdf, office}, resolved_language, total_detections, counts_by_entity_type, average_confidence, processing_latency_ms, and outcome ∈ {success, error} And for error cases, an event is sent with outcome='error' and error_code without including any PII values And a trace_id/correlation_id from the request propagates into metrics labels to enable request correlation And metrics emission is asynchronous and does not affect detection results if the sink is unavailable
Bilingual detection with auto-language handling (EN/ES)
Given texts in English and in Spanish containing person names and street addresses When the engine processes them without a language_hint Then it auto-identifies the language as 'en' or 'es' and applies the corresponding models And names and addresses are detected in both languages using the default threshold behavior And the response includes the resolved language for each processed item
Role-Based Redaction Presets
"As an account admin, I want role-based redaction presets that auto-apply when I share, so the right details are masked by default for each recipient type."
Description

Provide predefined, editable redaction policies for recipient roles (Insurer, Buyer, HOA) that specify which PII fields to mask versus retain. Allow account admins to clone and customize presets, define field-level rules (e.g., mask phone numbers but keep unit number), and choose masking style (black box, blur, token masking). Map presets to share channels so the correct policy is auto-selected when sending to a given counterparty type. Version and audit all policy changes, and support policy simulation/preview to verify what a recipient will see before sending.

Acceptance Criteria
Default Role Presets Available and Editable
Given a new account with the PII Redactor feature enabled When an Account Admin opens Settings > Redaction Policies Then three default role presets (Insurer, Buyer, HOA) are listed And each preset displays its PII field rules and masking style And the admin can modify field-level mask/retain toggles and masking style And saving persists changes and they appear on re-open of the policy editor
Admin Clones Insurer Preset and Sets Field-Level Rules
Given an existing Insurer preset at version 1 When the admin clicks Clone, names it "Insurer - West", sets Phone Numbers=Mask, Unit Number=Retain, selects Masking Style=Black Box, and saves Then a new preset named "Insurer - West" is created at version 1 with the specified rules and style And simulation on sample text "Call me at (415) 555-1212, Unit 4B" shows the phone number masked and "Unit 4B" visible And the original Insurer preset remains unchanged
Masking Style Selection Applies Across Media
Given a preset with Masking Style=Blur and rules to mask Phone Numbers and Access Codes And PII detection tags are available for a chat transcript, a JPEG with overlaid text, and a PDF attachment When a redacted package is generated using this preset Then all detected phone numbers and access codes are blurred consistently across all assets And OCR or text extraction on the redacted assets does not recover the original values And no unredacted copies are included in the outbound payload
Share Channel Auto-Selects Mapped Preset
Given channel mappings exist: Insurance Claim Email → Insurer preset; Buyer Share Link → Buyer preset When a user initiates a share via Insurance Claim Email Then the Insurer preset is auto-selected in the share flow And if no preset is mapped to the selected channel, the user is required to choose a preset before sending And switching the channel to Buyer Share Link auto-switches the selected preset to Buyer
Policy Changes Versioned and Audited
Given the HOA preset is at version 3 When an admin changes field rules and masking style and saves with change note "Per HOA request" Then the preset version increments to 4 And the audit log records actor, timestamp, previous vs new values, change note, and affected share-channel mappings And audit entries are immutable and viewable in a chronological log with filters for preset, actor, and date range
Policy Preview Matches Sent Output
Given a user opens Preview for preset "Buyer" version 2 on selected assets When the preview renders Then the preview displays the active policy name and version and accurately shows all redactions And sending a share using the same assets and preset produces redacted files that byte-match the previewed files And switching the preview to "Insurer" updates the rendered view within 1 second to reflect the alternate policy
Originals Retained Internally; External Sees Only Redacted
Given a share is sent using preset "Insurer" version 1 When the external recipient accesses the share package Then only redacted assets are retrievable and no links or metadata references to unredacted originals exist And internal users with Redactor:ViewOriginals permission can access the unredacted originals from the case record And the share activity log records the preset name and version used for the outbound package
Secure Original Storage & Access Controls
"As a compliance officer, I want unredacted originals securely retained with strict access controls and auditing so that we can meet legal and evidentiary requirements without risking unintended disclosure."
Description

Store an immutable, unredacted original of each asset in a segregated, encrypted repository (KMS-managed keys, key rotation), separate from redacted derivatives. Enforce least-privilege access via RBAC, with optional step-up approval for viewing originals. Watermark internal previews with user/time and disable external download of originals. Maintain a complete audit trail of detections, redaction actions, shares, and any original access. Support retention policies and legal holds aligned with organization settings.

Acceptance Criteria
Immutable Original Stored in Segregated, Encrypted Repository
- Given an asset is ingested, When storage completes, Then an unredacted original is saved to a repository separate from redacted derivatives (distinct bucket/collection and IAM policy). - Given an original is stored, Then it is encrypted at rest with a tenant-scoped KMS-managed key (AES-256) and the object metadata records key alias and version. - Given an original is stored, Then object immutability (WORM) is enabled for the org-configured retention period and overwrite/delete operations are blocked by policy. - Given any component attempts a write/update to the original, Then the operation is denied and logged. - Given a redacted derivative is requested by clients, Then no direct reference or URL to the original is returned in any API response. - Given cross-tenant access is attempted, Then access is denied.
Least-Privilege RBAC for Original Access
- Given a user without the "Original_View" permission, When they attempt to view or download an original, Then the system returns 403 and shows only redacted content. - Given a user with "Original_View" permission, When they access an original, Then access is granted only via a time-bound, asset-scoped token (TTL <= 15 minutes) issued server-side. - Given a public/share link or external user, When requesting an original, Then the system never serves the original and responds 403. - Given a new user is created, Then by default they do not have "Original_View" permission. - Given an API call uses an expired or mismatched token, Then the request is rejected and logged.
Step-Up Approval Workflow for Original Viewing
- Given org policy requires step-up approval, When a user with "Original_View" requests to view an original, Then a request is created capturing asset, justification, and requested duration. - Given approval policy requires N approvers (configurable 1–2), When approvers act, Then approval within SLA grants time-bound access (<= 30 minutes) and denial blocks access. - Given org has MFA enabled, When access is granted, Then the user must complete MFA within the same session before the original is displayed. - Given approval expires or is revoked, Then subsequent requests using the prior token are rejected. - Given an approval request is pending beyond the defined SLA, Then it auto-expires and notifies the requester.
Watermarked Internal Previews and Original Download Restrictions
- Given an internal user views an original in the UI, Then a visible, non-removable watermark overlays the preview with user ID, email, timestamp (UTC), org, and asset ID on all pages/frames. - Given an external share link is used, Then only redacted derivatives are available; originals are never previewable or downloadable. - Given any attempt to export or download an original (UI or API) by internal users, Then file download is blocked unless explicitly allowed by policy; if allowed, the exported file carries an embedded watermark. - Given a direct object URL without a valid signed token is used, Then the request returns 403.
Complete Audit Trail for Sensitive Events
- Given detection, redaction, share, access request, approval/denial, view, download, and deletion events occur, Then each is recorded as an immutable audit log entry with actor, asset ID, action, timestamp (UTC), source IP/agent, outcome, and reason. - Given audit logs are queried by admin via UI or API, Then results can be filtered by asset, user, action, and time range and exported in CSV/JSON. - Given an audit log integrity check runs daily, Then the system verifies hash-chain or tamper-evident signatures and alerts on discrepancies. - Given any access to an original is attempted, Then a real-time notification can be sent per org settings to designated recipients.
Retention Policies and Legal Holds Enforcement
- Given an org retention policy of X days is set, When X days elapse since asset creation, Then originals and derivatives are purged within 24 hours unless on legal hold. - Given a legal hold is applied to an asset or case, Then deletion is suspended regardless of retention until the hold is removed. - Given an asset is purged, Then associated tokens are invalidated, indexes updated, thumbnails removed, and an audit entry is written. - Given an org updates its retention policy, Then new assets use the new period; existing assets follow their original retention unless migrated by an explicit admin job with confirmation. - Given an export request occurs for assets on hold, Then only redacted versions are exportable unless elevated policy permits and is approved.
KMS Key Management and Rotation
- Given tenant CMKs are configured, Then originals are encrypted with the tenant’s active CMK and key policy restricts use to the storage service principal. - Given key rotation occurs (at least annually or per org policy), Then new writes use the new key version and background re-encryption of existing originals completes successfully with no data loss. - Given a key is disabled or scheduled for deletion, Then affected originals become undecryptable for non-admins and alerts are sent; destructive actions follow a grace period as per policy. - Given tenant offboarding is initiated with cryptographic shredding enabled, Then keys are destroyed after retention windows, rendering originals irrecoverable. - Given KMS access logs are reviewed, Then every decrypt/encrypt operation maps to an authenticated service call and correlates with an application audit event.
Irreversible Redaction Rendering & Fidelity
"As a property manager, I want redactions to be irreversible yet readable so that recipients can understand context without exposing private information."
Description

Generate redacted outputs that are visually clear and forensically safe. For images, burn-in pixelation/boxing overlays and strip EXIF/geolocation. For PDFs, remove hidden text layers and apply vector redactions that are flattened to prevent recovery. For text messages, replace sensitive tokens with masked placeholders while preserving readability (e.g., •••• or (XXX) XXX-1234). Keep layout and evidence context intact, include a subtle ‘Redacted’ watermark, and retain accessible text for non-sensitive portions. Provide consistent styling across platforms and guarantee that redactions cannot be programmatically reversed.

Acceptance Criteria
Image Redaction Burn‑In and Metadata Scrub
Given a source image containing detected PII regions When the user exports a redacted image for sharing Then all PII regions are fully covered by opaque black boxes or pixelation at a block size ≥12px And redaction overlays are permanently baked into raster pixels (no separate layers, masks, or alpha channels exist) And all EXIF, IPTC, XMP, and GPS metadata are removed And the file contains no embedded previews/thumbnails that expose unredacted content And a “Redacted” watermark appears bottom‑right at 24–36% opacity, 10–14pt at 96dpi, not obscuring non‑redacted content And non‑sensitive areas outside redaction boxes are unchanged within ±1px alignment and ±2% color tolerance And attempting recovery via common tools (layer toggling, metadata extraction, thumbnail recovery) yields no underlying PII
PDF Redaction Flattening and Hidden Content Removal
Given a PDF with text, images, and vector objects containing PII When a redacted PDF is exported Then redacted text glyphs and content streams within redaction regions are removed (not hidden) And redaction shapes are flattened so selecting, copying, or searching inside redacted regions yields no characters And hidden layers/OCGs, annotations, and form fields overlapping redacted regions are removed And embedded files, alternate image layers, and thumbnails containing PII are removed And non‑redacted text remains selectable and tagged; reading order and bookmarks are preserved And a “Redacted” watermark appears on each page at 20–30% opacity And automated text extraction across the file returns zero occurrences of redacted tokens
Text Message Masking and Readability Preservation
Given a conversation transcript containing PII (names, phone numbers, emails, access codes, payment details) When a redacted transcript is generated Then phone numbers are masked as (XXX) XXX‑1234 with only the last 4 digits retained And access codes and payment numbers are replaced with U+2022 bullets preserving grouping (e.g., •••• •••• •••• 1234) And detected personal names are replaced with [REDACTED NAME] And emails are masked as f••••@d•••.tld retaining the first letter and top‑level domain And punctuation, whitespace, timestamps, and speaker labels are preserved And non‑sensitive text remains machine‑readable and selectable And a “Redacted” indicator is present in the transcript header or footer
Cross‑Platform Redaction Style Consistency
Given the same source content is exported to PNG, JPEG, PDF, and the web viewer across iOS, Android, and desktop When redactions are applied using the default style Then redaction fill color is #000000 at 100% opacity with 0–1px stroke width And pixelation style uses a block size between 12–16px consistently across platforms And the watermark font, text “Redacted,” size, and opacity are consistent within ±2pt and ±5% opacity across platforms And typeface fallback does not shift layout by more than ±2px per line And timestamps, page numbers, and callouts remain aligned within ±1% relative to the original And automated visual diff reports <1% pixel difference outside redaction regions
Irreversibility Verification and Recovery Resistance
Given a test suite of redacted outputs with the original PII tokens recorded When automated recovery attempts are executed (OCR on images, PDF text scraping, metadata and thumbnail extraction, layer/OCG inspection) Then no redacted token appears in any extracted text, metadata, or embedded stream (0 instances) And OCR over redacted image regions returns only null/empty or ≤5% random noise with no contiguous 3+ character match to any redacted token And disabling layers, toggling OCGs, or inspecting content streams does not reveal underlying content And redacted regions contain no transparency that would permit programmatic reconstruction
Layout, Context, and Accessible Text Retention
Given any document or image is redacted for sharing When the redacted output is exported Then page dimensions, DPI, and orientation match the source And captions, labels, callouts, and other evidence context outside redaction regions are preserved And non‑redacted text remains accessible to assistive technologies; redacted regions are tagged as artifacts or marked “redacted” without exposing content And hyperlinks, non‑sensitive alt text, and bookmarks remain intact in PDFs And the output passes an accessibility audit with no critical issues outside redacted regions
Share Flow Integration & Audit Trail
"As a user sending a work order to an external party, I want redaction to apply automatically with full traceability so that I can share quickly and prove what was sent if questions arise."
Description

Integrate redaction into FixFlow’s existing share and one‑click approval paths. On share, auto-run detection, select the appropriate preset by recipient type, and present a preview with the option to adjust. Generate time-limited, role-labeled links to redacted artifacts and attach a tamper-evident checksum. Record which version, preset, and user were used for each share, and expose this in the activity timeline. Support re-sharing with different presets without duplicating storage, and block external access to originals.

Acceptance Criteria
Auto-redaction on Share with Role-Based Preset
Given a work order contains photos, messages, and a PDF attachment with PII (names, phone numbers, access codes, payment details) And the user selects Share and chooses recipient type "Insurer" (or multiple recipient types) When the share dialog opens Then PII detection runs across all selected artifacts within 2 seconds for a total payload <= 50 MB And the preset matching each recipient type is auto-selected without user input And the preview shows detected PII masked per preset rules across images, text, and PDFs And no original artifacts are transmitted before the user confirms share And if multiple recipient types are selected, separate links are prepared per role with their respective presets
Redaction Preview and Manual Adjust Prior to Share
Given auto-applied redactions are displayed in the Preview When the user removes an auto-detected redaction or adds a new redaction (box on images, span on text/PDF) Then the preview updates within 200 ms and an "Adjustments" summary increments accordingly And the user can undo/redo up to the last 10 adjustments And a Reset to Preset action restores only the auto-detected state for the chosen preset And the final shared redaction state equals the previewed state at the moment of confirmation And all adjustments (add/remove, target, timestamp) are captured for the audit record
Generate Time-Limited, Role-Labeled Share Links with Checksum
Given the user confirms Share from the Preview with recipient type "Insurer" And a TTL defaults to 7 days and is configurable between 1 hour and 30 days When the share is created Then a unique, role-labeled URL (label = "Insurer") is generated per recipient role And the URL serves only the redacted artifacts corresponding to that role's preset And a tamper-evident checksum (SHA-256 + HMAC) is attached and verified on every retrieval And any checksum mismatch rejects with HTTP 400 and an audit event is recorded And the link expires at TTL and returns HTTP 410 after expiry And the share confirmation displays the expiry timestamp and role label
Audit Trail Records Redaction Context and Exposure
Given a share is completed Then an activity timeline entry is created within 5 seconds containing: original artifact IDs and content hashes, redaction manifest IDs, preset ID and version, list of manual adjustments, sharing user ID, org/tenant ID, recipient role labels, link TTL, checksum algorithm and digest, timestamp, and link URL suffix And the entry is immutable and append-only And internal users can view the entry in the timeline and copy individual values And attempts to edit or delete the entry are denied with an authorization error and logged
Re-Share Artifacts with Different Presets Without Storage Duplication
Given an original artifact has been shared with preset "Insurer" When the same artifact is re-shared with preset "Buyer" Then the original blob is stored once only (identical content hash) And each redacted view is represented by a lightweight redaction manifest or overlay And additional storage for each new preset's redaction does not exceed 5% of the original artifact size And both shares resolve to their correct redacted outputs And audit entries for both shares reference the same original content hash
Block External Access to Originals via Shared Links
Given an external share link exists for any artifact When an external party attempts to access the unredacted original via direct object URL, link parameter manipulation, or referer-based navigation Then access to the original is denied with HTTP 403 and zero bytes of the original are served And the attempt is logged with IP, user-agent, and timestamp And only authenticated internal users with "View Originals" permission can access originals via the internal app (never via the external link) And no presigned URL to the original is generated as part of external sharing
One-Click Approval Flow Applies Redaction and Shares
Given a work order requires dispatch and the user clicks One-Click Approve to notify a vendor When the approval is sent Then PII detection runs automatically and the recipient role's preset (e.g., "Vendor") is applied without additional user steps And a role-labeled, time-limited link to the redacted artifacts is attached to the approval notification And if "Auto-share on approve" is disabled, a preview is shown for confirmation; if enabled, it proceeds without preview And the approval event is recorded in the audit trail with preset, user, link, and timestamp And median time-to-send increases by no more than 300 ms versus a baseline approval without redaction for payloads <= 20 MB
Redaction Review & Override UI
"As an operations lead, I want an efficient review screen to confirm and adjust redactions so that I stay in control without slowing down my workflow."
Description

Provide a human-in-the-loop interface to review detected PII, approve or dismiss findings, and add manual redaction regions/tokens before sharing. Show bounding boxes and confidence scores, support bulk accept/reject, undo/redo, and keyboard shortcuts. Optimize for mobile web with pinch-zoom and low-latency interactions on large photos. Surface why an item was detected (rule/model) to build user trust and enable quick corrections.

Acceptance Criteria
Review detected PII on high‑resolution photo
Given a 12MP photo with three auto-detections (tenant name, phone number, keypad code) When the reviewer opens the Redaction Review UI on a mid‑tier mobile device Then the image and all detection bounding boxes render within 200 ms after image paint, and each box shows a PII type label and a confidence score to one decimal place When the reviewer approves any detection Then the redaction preview updates within 150 ms to show the mask and the detection state changes to Approved When the reviewer dismisses any detection Then its mask is removed from preview and the detection state changes to Dismissed When the reviewer taps Share Then the exported image contains masks only for Approved detections, excludes Dismissed ones, completes export within 1 s, and the unredacted original remains preserved in internal storage
Bulk accept/reject in message thread
Given a message thread with 25 auto-detected emails and phone numbers When the reviewer selects all and clicks Bulk Accept Then all selected detections change to Approved and the preview updates within 300 ms When the reviewer clicks Bulk Reject Then all selected detections change to Dismissed and the preview updates within 300 ms And an on-screen toast confirms the action with the count of detections affected And an Undo action is available for 10 seconds to revert the bulk operation
Manual redaction on photo and text
Given a photo and a text message are open in the review UI When the reviewer draws a rectangle and taps Apply Then a mask is added at the specified coordinates and can be moved/resized with drag handles When the reviewer selects a text token (e.g., 'Unit 4 gate code 8421') and marks it as Access Code Then the token is masked in preview and listed under Manual Redactions And manual redactions persist in the export and after a page refresh And the UI prevents creation of masks smaller than 8x8 px and snaps mask edges to pixel boundaries
Undo/redo and keyboard shortcuts
Given the review UI is open on desktop When the reviewer uses A to Approve, X to Dismiss, Ctrl+Z to Undo, and Ctrl+Shift+Z to Redo Then the corresponding actions execute and are reflected in the preview within 150 ms And the action history supports at least 50 steps per item and is cleared only upon leaving the screen or completing Share And a keyboard shortcuts help overlay is accessible via ? and is screen‑reader labeled
Mobile pinch‑zoom and pan performance
Given a 12MP photo with multiple detections on a mid‑tier mobile device When the reviewer pinch‑zooms up to 400% and pans continuously Then the image and bounding boxes remain aligned within 1 pixel at all zoom levels And interaction latency stays under 50 ms for 95% of frames with no visible tearing And double‑tap zoom toggles between fit‑to‑screen and 200% within 150 ms
Detection provenance and quick correction
Given any auto-detected item is selected When the reviewer opens the Why panel Then the UI displays Source (Model vX.Y or Rule name), PII category, and confidence score When the reviewer changes the category (e.g., Phone -> Access Code) or marks Not PII Then the preview updates within 150 ms, the new state is saved, and the change is recorded in the audit log with timestamp and user
Role‑based redaction presets before sharing
Given role presets exist for Insurer, Buyer, and HOA When the reviewer selects the Insurer preset Then only PII types configured for Insurer are set to Approved by default and others to Dismissed, without removing manual redactions When the reviewer changes the preset to Buyer Then the selection updates accordingly and a summary chip lists which PII types will be redacted When Share is tapped Then the export applies the active preset plus any manual overrides and displays a confirmation banner with the role name
Performance, Scalability & SLAs
"As a busy property manager on mobile, I want redaction to finish quickly and reliably so that I can share documents without waiting or retrying."
Description

Meet near-real-time processing for typical assets: ≤3s p50 and ≤8s p95 for images/PDFs up to 10MB, with graceful degradation for larger files. Support batch processing (up to 20 attachments per ticket) with progress indicators, background queueing, retries, and idempotency. Scale horizontally to handle peak intake across 200-unit portfolios, and provide health checks, metrics, and alerts for detection accuracy, latency, and error rates.

Acceptance Criteria
SLA Latency for 0MB Attachments
Given a production-like environment and a dataset of at least 1,000 images/PDFs each 0MB When the PII Redactor processes these attachments end-to-end (from enqueue to redacted artifact persisted and available for sharing) Then per-attachment processing latency p50 000 ms and p95 000 ms measured at the service boundary And results are available for sharing immediately upon completion
Graceful Degradation for >10MB Attachments
Given an attachment larger than 10MB When it is submitted for redaction Then the API responds within 000 ms with a job ID and initial state "Queued" And processing occurs asynchronously without blocking the UI And a visible progress indicator updates at least every 5 seconds until completion or failure And no single HTTP request exceeds 30 seconds; longer work continues in background until completion
Batch Processing up to 20 Attachments per Ticket
Given a ticket with up to 20 attachments each 0MB When submitted as a batch Then all attachments are accepted, enqueued, and processed concurrently up to the configured worker limit And per-attachment statuses are exposed (Queued, Processing, Completed, Failed) via API/UI And aggregate batch progress (07 100%) is displayed And per-attachment processing latencies meet p50 000 ms and p95 000 ms
Retries and Idempotency
Given a transient failure (network timeout or 5xx from a dependency) When processing an attachment Then the system retries the job up to 3 times with exponential backoff (min 1s, max 8s) And Given the same attachment is resubmitted with the same idempotency key or identical content hash within 24 hours When processed Then only one redaction job is executed and all callers receive the same result without duplicate processing
Horizontal Scaling Under Peak Intake
Given a simulated peak burst for a 200-unit portfolio: 200 attachments (0MB) arrive within 10 minutes with up to 50 concurrent submissions When autoscaling policies are enabled Then the system maintains per-attachment processing latency p50 000 ms and p95 000 ms during the burst And queue wait time p95 000 ms And error rate 0% during the burst
Health Checks and Metrics Exposure
Given the service is deployed When calling liveness and readiness endpoints Then both return HTTP 200 and reflect dependency status (redaction engine, queue, storage) And a metrics endpoint exports at minimum: processing_latency_ms (with p50 and p95), queue_depth, job_throughput, error_rate, detection_accuracy, retry_count And metrics are scrapeable by the monitoring system at 60s intervals
Alerts for Latency, Error Rate, and Detection Accuracy
Given alerting is configured When processing latency p95 > 8,000 ms for 5 consecutive minutes OR error_rate > 2% for 5 consecutive minutes OR 7-day rolling detection_accuracy drops by 10% from baseline Then an alert is sent to the on-call channel within 60 seconds with service name, environment, and runbook link And when the condition clears for 10 minutes, the alert auto-resolves

Live Pack Link

Shares a secure, expiring link to a live Proof Pack that updates as new photos, estimates, or notes arrive—no more resending giant attachments. Includes version history and a one‑click “Freeze & Sign” to capture the final, signed PDF when the case is ready. Speeds collaboration and keeps everyone aligned on the latest facts.

Requirements

Secure Expiring Link
"As a property manager, I want to share an expiring, secure link to a live Proof Pack so that external stakeholders can access the latest details without creating accounts or risking data leaks."
Description

Generate a unique, secure public URL for a Proof Pack with configurable expiration (e.g., 24 hours to 30 days), optional passcode, and one-click revoke/regenerate. Tokens are signed and scoped to a single pack, preventing access to other cases. Links work without recipient accounts while enforcing rate limiting and basic bot protection. Owners can view link metadata (creator, created/expiry timestamps) and extend or invalidate links at any time. Integrates with FixFlow’s case model and vendor/tenant contact flows for frictionless sharing via email/SMS.

Acceptance Criteria
Generate Unique Signed Pack Link
Given I am an authorized pack owner or collaborator on a Proof Pack When I create a share link without enabling a passcode Then the system returns a unique HTTPS URL with a signed, unguessable token scoped only to this pack And accessing the URL displays the pack without requiring login until the link expires or is revoked And using the token to request any other pack or resource returns HTTP 403 Forbidden And the link creation is recorded with creator and created timestamp
Configurable Expiration (24 Hours–30 Days)
Given I open Create Share Link for a Proof Pack When I set the expiration to a value between 24 hours and 30 days and confirm Then the link becomes inaccessible immediately after the configured timestamp And requests to the expired link return an Expired page and HTTP 410 Gone And attempting to set an expiration outside the allowed range shows a validation error and prevents creation And when I extend the expiration before or after expiry, the same URL becomes valid through the new timestamp and the change is logged
Optional Passcode Gate
Given I enable passcode protection and set a passcode that meets policy (4–12 characters) When a recipient opens the link Then they are prompted for the passcode and gain access only after correct entry And after 5 consecutive incorrect attempts from the same IP, further attempts are throttled and a CAPTCHA is required And the passcode is stored only as a salted hash and is never shown in plaintext in the UI or logs
One-Click Revoke and Regenerate
Given a share link exists for a Proof Pack When I click Revoke link Then the link becomes unusable within 5 seconds and subsequent requests return HTTP 403 with a Revoked message And an audit entry is recorded with actor, timestamp, and optional reason When I click Regenerate link Then a new unique URL is created with the same settings (expiry, passcode) unless I change them And all prior URLs remain invalid
Unauthenticated Access with Rate Limiting & Bot Protection
Given a recipient opens a valid link without a FixFlow account Then the pack content is viewable without authentication subject to protections And unauthenticated requests are rate-limited to 60 requests per 10 minutes per IP with HTTP 429 on excess And after 10 failed passcode attempts from the same IP within 15 minutes, access is blocked for 15 minutes And high-velocity or suspicious access patterns trigger a lightweight bot challenge (e.g., CAPTCHA) before content is served
Owner Views and Manages Link Metadata
Given a share link exists for a Proof Pack When I open the Share panel Then I can see the link creator, created timestamp, expiry timestamp, and status (active, expired, revoked) And I can extend the expiration by selecting a new timestamp within the allowed range and saving And I can invalidate the link immediately And all changes update in the UI within 2 seconds and are recorded in the audit log
Email/SMS Sharing Flow Integration
Given I choose Share via Email/SMS to a tenant or vendor from the pack When I send the message Then the recipient receives a message with the secure link and no PII or internal identifiers appear in the URL path or query string And if a passcode is enabled, the message omits the passcode by default and warns me if I include it And delivery status (sent, failed) is recorded on the pack And opening the link from the message reflects the link’s current state (active, expired, revoked)
Real-time Pack Sync
"As a landlord, I want the shared link to update instantly as new photos and notes arrive so that collaborators always see the latest facts without resending files."
Description

Ensure the shared link reflects updates to photos, estimates, notes, and approvals in real time using SSE/WebSockets with graceful fallback to polling. Highlight new or changed items with change badges and smooth in-place updates, preserving scroll position. Handle concurrent edits and present a consistent, read-optimized view. Support optimistic caching for fast loads and invalidate caches on server events. Scales to spikes in traffic (e.g., insurer reviews) with CDN-backed assets for media-heavy packs.

Acceptance Criteria
Live Pack reflects updates in real time with SSE/WebSocket and polling fallback
Given a viewer opens a Live Pack link on a supported browser When the server creates or updates a photo, estimate, note, or approval in that Pack Then the viewer UI reflects the change within 1 second (P95) while connected via SSE/WebSocket And if SSE/WebSocket handshake fails within 2 seconds, the client falls back to 5-second polling and reflects changes within 6 seconds (P95) And only one instance of each new or updated item is rendered (no duplicates) And the connection upgrade is retried in the background with exponential backoff without interrupting polling
Change badges appear on new/updated items and clear after view without layout shift
Given a viewer has the Pack open When a new item arrives or an existing item changes Then that item displays a "New" or "Updated" badge on its card within 1 second of rendering And the badge clears when the item has been in the viewport for at least 2 seconds or the user clicks/taps the item And the update animates in-place within 300 ms and does not shift the viewport position And no visible layout jump occurs (viewport scroll offset remains within +/- 16 px)
In-place updates preserve scroll position and user focus/context
Given the viewer has scrolled and has an item focused or a note expanded When any number of new items arrive above or below the current viewport Then the scroll position changes by no more than +/- 16 px And the currently focused element retains focus and caret position And assistive technologies receive a polite live region announcement of the number of new/updated items without moving the virtual cursor
Consistent read-optimized view during concurrent edits
Given two or more editors modify the same note or estimate within a short interval When the viewer receives multiple change events for the same item Then changes are applied in server order and each item render is atomic (no partially merged fields) And the viewer displays the latest committed version with a single timestamp, without flicker or intermediate states And no event is rendered out of order or duplicated after reconciling with a subsequent full-state sync
Optimistic cache provides fast initial render and invalidates on server events
Given the viewer has previously opened the same Live Pack within the last 24 hours When the viewer opens the link again Then initial content renders from local cache within 500 ms And upon establishing the server event channel, any stale items are reconciled and updated in-place within 1 second And cached items older than 24 hours are ignored unless validated via ETag/If-None-Match And a manual refresh results in a state identical to the server within 1 second after load
Connection loss and reconnection resume stream and backfill missed updates
Given an active viewer loses network connectivity for up to 10 minutes When connectivity is restored Then the client automatically reconnects within 3 seconds and backfills all missed changes using last-event-id or timestamp delta And the final state matches the server authoritative state within 2 seconds (P95) And while offline, the UI shows a non-blocking "Reconnecting" status and disables actions that require live state And no duplicate badges are shown for backfilled items
Pack sync scales under traffic spikes with CDN-backed media
Given a spike of 10,000 concurrent viewers across 1,000 Live Pack links with media-heavy packs (avg 50 images at 1 MB each) When updates are broadcast at an average rate of 2 events/second per pack Then event delivery latency to connected clients is < 2 seconds at P95 and < 5 seconds at P99 And >= 90% of image/video asset requests are served via CDN (cache hit ratio) And time-to-first-render of a cached Pack is <= 800 ms at median and <= 2 seconds at P95 on 4G And client memory usage remains <= 150 MB after receiving 200 updates in a single session
Role-Based Access Controls
"As a property manager, I want to control what each recipient can see and do so that sensitive information stays protected while enabling collaboration."
Description

Apply role-based permissions to Live Pack Links to restrict what recipients can view or do (e.g., view-only vs. comment-enabled). Redact sensitive fields (tenant PII, vendor rates) based on role and link configuration. Watermark public views with recipient label and timestamp to discourage misuse. Provide per-link toggles (allow comments, show pricing, show tenant info) and default templates by recipient type (tenant, vendor, adjuster). Integrates with FixFlow’s user/organization model and audit trail.

Acceptance Criteria
Enforce View-Only vs Comment-Enabled by Role and Link Toggle
Given a Live Pack Link with allowComments=false shared to a vendor recipient, When the recipient opens the link, Then the comment composer is hidden and any POST /comments returns 403 and is recorded in the audit log. Given the same link updated to allowComments=true, When the recipient posts a comment, Then the comment saves (201), displays the recipient label and timestamp, is visible to internal users, and an audit entry is created with linkId and actor. Given a Live Pack Link with allowComments=true shared to a tenant recipient, When the tenant views the link on mobile web and desktop, Then the comment composer renders and successfully posts comments in both environments.
Redact Sensitive Fields by Role and Per-Link Toggles
Given showPricing=false and recipient type Tenant, When the link is opened, Then vendor rates, line-item prices, and totals are masked in UI, omitted from API responses, and excluded from PDF/CSV exports. Given showTenantInfo=false and recipient type Vendor, When the link is opened, Then tenant name, phone, email, and unit identifiers are replaced with redacted placeholders in UI, omitted from API, and redacted in downloads. Given showPricing=true and recipient type Adjuster, When the link is opened, Then all pricing fields are fully visible. When a recipient attempts to access a redacted field via direct API, Then the server returns 403 or omits the field and logs the attempt with recipient label and IP.
Watermark Recipient Label and Timestamp on Public Views
Given a public Live Pack Link opened by a recipient labeled "Vendor-Quote" at time T (UTC), Then all images, document previews, and generated PDFs display a non-removable diagonal watermark containing the recipient label, timestamp (YYYY-MM-DD HH:mm:ssZ), and linkId. When the recipient prints, downloads, or screenshots the content, Then the watermark remains present and legible across resolutions. Given an authenticated internal FixFlow user viewing the case inside the app, Then watermarks are not applied to internal console views.
Apply and Override Recipient-Type Templates for Per-Link Toggles
Given the creator selects the 'Vendor' template when generating a link, Then defaults are applied: allowComments=true, showPricing=true, showTenantInfo=false, watermark=true. When the creator overrides any toggle before creating the link, Then the saved link uses the overridden values while the template defaults remain unchanged for future links. When an org admin updates the 'Adjuster' template defaults in settings, Then links created afterward use the new defaults and existing links retain their current settings.
RBAC Enforcement and Audit Trail Integration
Given a user with role Property Manager and permission ManageLivePackLinks creates a link, Then the link is associated to the user's organization and is listable/editable only by roles permitted via org RBAC. When any recipient views the link, posts a comment, or the link's toggles are changed, Then an audit entry is recorded capturing actor (user or recipient label), role/template, IP, userAgent, action, linkId, and timestamp. Given a user without ManageLivePackLinks attempts to create, update, or revoke a link, Then the action is blocked with 403 and a denied attempt is recorded in the audit trail.
Freeze & Sign Permissions and Output Consistency with RBAC
Given an internal user with permission FinalizeCase, When they trigger Freeze & Sign on a Live Pack, Then a final, read-only PDF is generated reflecting current redaction settings for the intended recipient, and the Live Pack Link becomes read-only. Given a public recipient, When they access the Live Pack Link, Then Freeze & Sign controls are not rendered and any direct API attempt to freeze returns 403 and is audited. When the final PDF is generated, Then it includes signature metadata (signer identity, timestamp, linkId, version hash, applied redaction policy) and the content matches what the recipient saw pre-freeze.
Version History & Diffs
"As an adjuster, I want to review version history and see what changed between updates so that I can audit edits and understand how the scope evolved."
Description

Maintain an immutable timeline of pack versions with timestamp, author, and change summary each time content is added or edited. Provide a diff viewer to compare two versions, highlighting added/removed photos, changed estimate line items, and updated notes. Allow exporting any historical version as a PDF. Enforce retention policies and storage quotas aligned with FixFlow’s case lifecycle. Present a human-readable changelog within the Live Pack for transparency without exposing redacted content.

Acceptance Criteria
Immutable Version Timeline Capture
Given a Live Pack is open and the user with edit permissions adds a photo, When they save, Then the system creates a new version with a unique incrementing version ID, an ISO-8601 timestamp with timezone, the author user ID, and an auto-generated change summary including the photo count added. Given the user edits any estimate line item fields (description, quantity, unit cost) or updates a note, When they save, Then the system creates a new version capturing the changed fields and counts in the summary. Given any previous version, When any user attempts to modify or delete it via UI or API, Then the action is blocked with HTTP 403 and an audit event "immutable_version_blocked" is recorded. Given two users save changes concurrently, When both saves complete, Then both versions are persisted with server-assigned timestamps in causal order; the latest content reflects the last save, and both version entries remain visible in the timeline. Given any save event, When it completes, Then the new version appears in the timeline within 2 seconds.
Diff Viewer Highlights Content Changes
Given versions A and B are selected, When the diff viewer loads, Then it highlights photos added (tag "Added") and removed (tag "Removed") with thumbnails, shows estimate line item changes at row level with old vs new values and calculated deltas, and renders note text changes with inline additions (green) and deletions (red); unchanged items are not highlighted. Given versions A and B have no differences, When the diff viewer loads, Then it displays a clear "No differences found" state. Given the user toggles "Hide unchanged", When toggled on, Then only changed items are displayed. Given the user swaps base and compare versions, When applied, Then the diff direction reverses and adds/removes update accordingly. Given a Live Pack with up to 200 photos and 200 estimate items, When rendering a diff, Then initial render completes within 2 seconds on a 10 Mbps median connection.
Historical Version Export to PDF
Given a user with export permission selects any historical version, When "Export PDF" is invoked, Then a PDF is generated that matches that version's content exactly (photos, estimates, notes) and includes version ID, timestamp, and author on the cover page. Given redacted content exists in that version and the viewer lacks clearance, When exporting, Then redacted fields are masked in the PDF and not disclosed in metadata. Given the Live Pack size is up to 100 photos and 300 estimate items, When exporting a version, Then the PDF generation completes within 10 seconds and the file size is under 25 MB; if limits would be exceeded, Then the user is prompted to apply compression and export proceeds within the same time bound. Given a retention policy has archived binary assets for older versions, When exporting, Then the system retrieves archived assets transparently; if any asset is unavailable, Then export fails with an explicit error "asset_unavailable_by_policy" and no partial PDF is generated.
Retention Policy and Storage Quota Enforcement
Given organization retention settings define lifecycle stages (Open, In Progress, Closed), When a case enters "Closed + 90 days", Then versions older than 90 days are purged of binary assets and retained as metadata-only, preserving the changelog and diff summaries. Given a case or organization is placed on legal hold, When retention jobs run, Then no purging occurs for affected Live Packs and an audit entry "retention_skipped_legal_hold" is recorded. Given storage usage reaches 100% of quota, When a user attempts an edit that would create a new version, Then the save is blocked with a non-destructive error and options to upgrade plan or purge eligible assets per policy; no existing versions are deleted automatically. Given a final "Freeze & Sign" version exists, When retention jobs run, Then the signed PDF and its version metadata are always retained regardless of general purge rules. Given purge actions execute, When complete, Then a summary report is available listing the number of versions affected and storage reclaimed.
In-Pack Human-Readable Changelog
Given a viewer opens the Live Pack, When they view the Changelog, Then entries list version ID, timestamp, author, and a plain-language change summary; each entry links to "View version" and "Compare to previous". Given entries include redacted changes, When displayed to a user without clearance, Then summaries omit sensitive details and show placeholders such as "[redacted]". Given a viewer uses filters (date range, author, change type) or search, When applied, Then the changelog updates within 300 ms for up to 1,000 entries and filters persist for the session. Given a tenant is viewing via a Live Pack Link, When viewing the changelog, Then only permitted entries and actions (view, diff) are shown based on link scope; admin-only entries are hidden.
Access Controls, Link Expiration, and Freeze & Sign
Given a recipient opens a Live Pack Link before expiry, When they navigate to Version History and Diffs, Then they can view only the versions and diffs allowed by the link's permission scope; after the link expires, subsequent requests return HTTP 410 and no version data is exposed. Given an owner clicks "Freeze & Sign", When the signing completes, Then a final signed PDF is attached as the terminal version with an immutable flag; further edits are blocked unless an admin explicitly unlocks the case with an audit trail. Given a viewer compares any two versions post-freeze, When the diff viewer loads, Then the final signed version is selectable and highlighted as "Final". Given API access with a token scoped to read-only, When attempting to create, modify, or delete versions, Then the request is rejected with HTTP 403 and no data is changed.
Freeze & Sign Workflow
"As a property manager, I want a one-click Freeze & Sign that produces a final, signed PDF so that I can lock scope and approvals and close the case confidently."
Description

Provide a one-click action to freeze the current state of the live pack, validate required elements (approvals present, missing photos flagged), and generate an immutable, paginated PDF with embedded media references and a cryptographic hash. Trigger an e-sign flow to designated signers (e.g., owner, vendor) with role-based signature blocks and email/SMS delivery. After completion, attach the signed artifact to the case, mark the pack as Final, and show a Signed badge within the Live Pack Link. Store an audit trail and verification endpoint for tamper detection.

Acceptance Criteria
One-Click Freeze Validates Required Elements
Given a Live Pack with required approvals configured, When a user with Freeze permission clicks "Freeze & Sign", Then the system evaluates: approvals present, required photos attached to each relevant task, required estimates present where policy dictates, and no items marked Incomplete. Given one or more validation failures are found, When the user attempts to freeze, Then the freeze is prevented and a modal lists each failed check with item ID, label, and deep link to resolve, and an audit event "freeze_blocked_validation" is recorded. Given all validations pass, When the user confirms Freeze, Then the pack state changes to Frozen within 3 seconds and the e-sign workflow is initiated.
Immutable PDF Generation with Hash and Media References
Given a pack is Frozen, When the system generates the Proof Pack PDF, Then the PDF is paginated with a table of contents and stable ordering matching pack order at freeze time. Given the PDF is generated, Then each photo/media item appears as a thumbnail with caption and a secure reference URL to the original asset, and non-image media are represented with filename, size, and secure reference URL. Given the PDF is generated, Then a SHA-256 hash of the final bytes is computed, embedded on the certification page and metadata, and stored with the artifact record. Given the same Frozen snapshot is regenerated, When the PDF is re-built, Then the byte-for-byte content and SHA-256 hash are identical. Given the artifact is stored, When downloaded, Then the computed SHA-256 matches the stored hash.
Role-Based E‑Sign Routing via Email/SMS
Given designated signers are mapped to roles for the case (e.g., Owner, Vendor), When the pack is Frozen, Then signature blocks are placed in the PDF per role configuration and order. Given signers are configured, When e-sign is initiated, Then each signer receives a unique signing link via email and SMS within 60 seconds, containing case ID, document name, and link expiry timestamp. Given a signer opens their link, When they review the document, Then only their assigned signature fields are actionable, and role-mismatched fields are read-only. Given a signing link reaches expiry, When a signer attempts access, Then access is denied with an Expired message and an audit event "sign_link_expired" is recorded. Given reminders are enabled, When a signer has not completed within the reminder interval, Then an automated reminder is sent via the original channels and logged.
Signature Completion Marks Pack Final and Locks Edits
Given all required signatures are captured, When the final signer completes, Then the signed PDF (with signature certificate) is attached to the case and marked as the Final artifact. Given the Final artifact is attached, Then the Live Pack is marked Final and no further edits, uploads, or reordering are permitted on that pack. Given the pack is Final, When an edit is attempted on any prior item, Then the action is blocked with a message "Pack is Final—create a new revision" and an audit event "edit_blocked_final" is recorded. Given the pack is Final, Then a non-destructive version history entry is created referencing the Frozen snapshot and the Signed Final artifact.
Signed Badge and Final Artifact Visibility on Live Pack Link
Given a pack reaches Final, When anyone with access opens the Live Pack Link, Then a "Signed" badge is displayed prominently with the finalization timestamp (UTC) and signer roles. Given a pack is Final, When the Live Pack Link is viewed, Then a "View/Download Signed PDF" action is available and serves the signed artifact. Given a pack is Final, Then the Live Pack no longer shows live updates and displays a "Final—no longer updating" notice.
Comprehensive Audit Trail and Verification Endpoint
Given Freeze & Sign actions occur, When events happen (validation checks, freeze start, PDF generated, e-sign sent, viewed, signed, reminders, expiries, completion), Then each event is recorded with timestamp (UTC ISO 8601), actor, channel, and metadata. Given a signed artifact exists, When a verifier calls the public verification endpoint with the artifact hash, Then the API returns JSON including caseId, packId, createdAt, signer roles and statuses, stored hash, and validity=true. Given a tampered file is uploaded for verification, When its hash does not match the stored hash, Then the API returns validity=false with reason="hash_mismatch" and no signer data beyond minimal identifiers. Given audit data is requested by an authorized user, When viewed in the UI, Then the chronological audit timeline is rendered and exportable as CSV/PDF.
Error Handling and Edge Cases in Freeze & Sign Flow
Given validations fail, When Freeze is attempted, Then no artifact is generated, no e-sign is triggered, and the user receives a failure modal with actionable items. Given a signer declines, When the decline action is taken, Then the flow pauses, the status becomes "Declined", all parties are notified, and an authorized user can reassign or remove the declined signer. Given a signer is reassigned before signing, When reassignment is confirmed, Then the original link is revoked and a new unique link is issued to the new signer with audit entries for both actions. Given the e-sign vendor has a transient outage, When initiation fails, Then the system retries up to 3 times with exponential backoff and surfaces a non-blocking banner; after retries fail, the flow is marked "Pending Initiation" with an incident reference. Given the pack is already Final, When another Freeze & Sign is attempted, Then the action is blocked with guidance to create a new revision and the attempt is logged.
Change Notifications & Audit Log
"As a small property manager, I want notifications and an access log so that I know when stakeholders have viewed or signed and can follow up promptly."
Description

Enable configurable notifications (email/SMS/in-app) for key Live Pack events such as new photo added, estimate updated, comment posted, freeze initiated, and signature completed. Provide per-user frequency controls (instant, hourly digest, daily digest) and per-link subscriptions. Capture detailed access logs (viewed, downloaded, signed) with timestamp, IP/country, and user/link attribution where available. Surface read receipts and link-expiry warnings to owners. Ensure privacy compliance with data minimization and export/delete controls.

Acceptance Criteria
Instant Multi-Channel Notifications for Key Live Pack Events
- Given a user is subscribed to a specific Live Pack link and has enabled one or more channels (email, SMS, in-app) When any of the following events occurs on that link: photo added, estimate updated, comment posted, freeze initiated, signature completed Then the system sends exactly one notification per enabled channel within 60 seconds of the event - And each notification includes: event type, Live Pack title/ID, actor (if available), ISO 8601 UTC timestamp, and a deep link to the Live Pack - And no notification is sent to users who are not subscribed to that link or who have the relevant channel disabled - And delivery outcomes are recorded (delivered, bounced/failed) per channel for audit visibility
Per-User Notification Frequency Controls (Instant, Hourly, Daily)
- Given a user selects Instant for a channel on a Live Pack link When an eligible event occurs Then a notification is dispatched within 60 seconds and is not included in any digest for that channel - Given a user selects Hourly Digest for a channel on a Live Pack link When one or more eligible events occur within an hour window Then exactly one digest is sent within 10 minutes after the hour summarizing those events in chronological order - Given a user selects Daily Digest for a channel on a Live Pack link When eligible events occur during a calendar day in the user’s profile timezone Then exactly one digest is sent the next day between 08:00–09:00 local time summarizing those events - And when no events occur in the period, no digest is sent - And changing frequency takes effect for events that occur after the change timestamp
Per-Link Subscription Management by Users
- Given a user has access to a Live Pack link When the user toggles subscription on/off per channel for that specific link Then the preference is saved immediately and applied to all subsequent events on that link for that user - And unsubscribing from one link does not change the subscription state of any other links or packs - And the user can view current subscription state per channel for the link at any time
Access Logging for Views, Downloads, and Signatures
- Given a Live Pack link is accessed When it is viewed, a file is downloaded, or a signature action is completed Then an immutable audit entry is recorded with: event type (viewed/downloaded/signed), ISO 8601 UTC timestamp, source IP address, resolved country, link ID, and actor attribution (authenticated user ID if available, otherwise link-token reference) - And audit entries become visible to the Live Pack owner in the Audit Log UI within 60 seconds of the event - And owners can filter the log by date range, event type, country, and actor attribution - And exporting the audit log as CSV or JSON includes exactly the recorded fields without additional personal data
Owner Read Receipts for Live Pack Views
- Given a recipient opens a Live Pack via a shared link When the view event is recorded Then the Live Pack owner sees a read receipt showing: first viewed timestamp, last viewed timestamp, and total view count for that link - And if actor attribution is available, the receipt associates the view to that user; otherwise it associates to the specific link token - And read receipt data updates within 60 seconds of subsequent views
Link Expiry Warnings to Owners
- Given a Live Pack link has an expiry date/time set When the current time is 48 hours before expiry Then the owner receives a warning notification via their enabled channels and an in-app banner is displayed on the link details - When the link reaches expiry Then access attempts show an "Expired" page, no content is served, and an audit entry of type "expired_access_attempt" is recorded with timestamp, IP, country, and link ID - When the owner updates the link expiry to a future time Then the banner is cleared and an audit entry of type "expiry_updated" is recorded
Privacy Compliance: Data Minimization, Export, and Delete
- Rule: Audit logs store only the minimum required fields: event type, ISO 8601 UTC timestamp, IP address, resolved country, link ID, and actor attribution; no email addresses, phone numbers, or message contents are stored in audit entries - Given a data subject export request referencing a verified user identity When an admin initiates an export Then a machine-readable export (JSON and CSV) is produced within 7 days including the subject’s notification preferences and any audit entries where actor attribution matches the subject - Given a verified deletion request for a user When the request is processed Then the system deletes or irreversibly anonymizes the subject’s personal data in audit logs (clear actor attribution and IP address) within 30 days while retaining non-personal event type and timestamps for system integrity - And after deletion, the subject no longer receives notifications and cannot be re-identified from retained records

Claim Templates

One‑click export profiles tailored to major insurers and regulators with required fields, cover letters, and naming conventions prefilled. Add your branding, custom sections, and jargon translation to match each recipient. Eliminates rework and accelerates claim acceptance.

Requirements

Recipient Template Profiles & Versioning
"As an operations admin, I want a library of prebuilt insurer/regulator profiles with version control so that I can quickly select the right template and stay compliant with changing requirements."
Description

A managed library of insurer and regulator claim profiles with versioning, jurisdiction tagging, effective dates, and changelogs. Supports cloning, deprecating, and rolling back profiles; sandbox vs production visibility; and default profiles for top carriers. Profiles define required fields, document structure, naming conventions, and delivery preferences, and are selectable at export and attachable to workflow rules in FixFlow.

Acceptance Criteria
Create and Version a Recipient Template Profile
Given I am an admin with Template Library permissions When I create a profile specifying name, recipient type, jurisdiction tags, effective start date, required fields, document structure, naming conventions, and delivery preferences Then the profile is saved as version 1.0 with status Active and is visible in the library list Given a profile version 1.0 exists When I clone it and edit its definitions Then a new version 1.1 is created in Sandbox with a draft status and the original 1.0 remains the Production active version
Jurisdiction Tagging and Effective Date Enforcement
Given a claim with jurisdiction = X and export date = D When I open Export and pick a recipient Then only profiles tagged with jurisdiction X and with effective date ranges covering D are selectable, and the default suggestion is the highest active version Given no profile matches the jurisdiction/effective date When I attempt selection Then the system blocks selection and displays: "No valid profile for X on D" with a link to request admin action
Changelog Capture, Deprecation, and Version Rollback
Given an admin publishes a new version vN+1 from vN When the version is promoted Then a changelog entry records editor, timestamp, summary, and diffs for required fields, document structure, naming conventions, and delivery preferences Given an active version vN exists When I deprecate vN Then it is unavailable for new exports and workflow rule auto-selection, but continues to display for historical exports; deprecation reason is recorded Given vN+1 is active and causes issues When I perform a rollback to vN Then a new version vN+2 is created with the content of vN, promoted to Production, and all selections use vN+2 going forward; a rollback entry is added to the changelog
Sandbox vs Production Visibility and Promotion
Given a profile version is in Sandbox When a non-admin user initiates an export Then the Sandbox version is not visible or choosable Given a profile in Sandbox passes validation When an admin promotes it to Production Then it becomes visible to all permitted users, receives a Production timestamp and promoter identity, and supersedes the prior active version
Default Profiles for Top Carriers
Given a new FixFlow tenant is provisioned When I open the Template Library Then default Production profiles exist for designated top carriers with current effective dates and jurisdiction tags Given a default profile is designated for Carrier C and Jurisdiction J When a user selects Carrier C during export in Jurisdiction J Then that profile is preselected; users with permission can change it Given the platform provider updates a default profile When the update is released Then tenant admins receive a notification, and the changelog indicates "Provider Update" as the source
Profile Selection at Export with Required Field Validation and Delivery Application
Given a profile is selected for an export When I click Generate/Send Then the system validates all profile-defined required fields; if any are missing, the export is blocked and a list of missing fields with labels is displayed Given required fields are satisfied When the export is generated Then documents are assembled according to the profile’s structure and file naming templates, and filenames match the convention exactly Given delivery preferences specify Email, SFTP, or Portal When the export is sent Then the chosen delivery method is executed using profile-configured endpoints, and the export record captures success/failure, timestamp, and the profile version used
Attach Profiles to Workflow Rules and Auto-Selection
Given an admin creates a workflow rule with conditions (e.g., Carrier = C, Jurisdiction = J) and attaches Profile P When a claim matches the rule and reaches export Then Profile P is auto-selected; if multiple rules match, the highest-priority or most-specific rule is applied; if none match, the default profile for the carrier/jurisdiction is selected Given a workflow rule references a deprecated profile When the rule runs or is opened for editing Then the system flags the rule with a warning and prompts the admin to select a replacement profile
Data Field Mapping & Terminology Translation
"As a claims coordinator, I want to map our data to each insurer’s required fields and terminology so that exports are accurate and accepted without manual rework."
Description

A configurable mapper that links FixFlow data (work orders, assets, residents, policy numbers, costs) to recipient-required fields with transformations (formats, enumerations, currency, time zone) and per-recipient terminology dictionaries. Supports custom fields, fallback/derived values, attachment categorization, and validation of mapping completeness. Provides a UI and API to manage mappings safely with change previews.

Acceptance Criteria
Map FixFlow Fields to Insurer Required Schema
- Given an export profile is selected and a field mapping exists, When an export is initiated for a single work order, Then all recipient-required fields are populated from their mapped FixFlow sources and the export completes successfully. - Given an export profile with at least one unmapped required field, When an export is initiated, Then the export is blocked and a validation report lists each missing field, its recipient path, and suggested FixFlow sources. - Given optional recipient fields are unmapped, When an export is initiated, Then the export completes and those fields are omitted or null per profile rules. - Given a profile mapping includes custom FixFlow fields, When an export is initiated, Then the custom fields are correctly resolved and included.
Terminology Dictionary Per Recipient
- Given a recipient dictionary defines term translations, When a preview or export is generated, Then all mapped text values and labels are translated using the recipient’s dictionary. - Given a term is not present in the recipient dictionary, When an export is generated, Then the original term is used and a non-blocking warning is logged and displayed in the preview. - Given multiple recipients with different dictionaries, When switching profiles, Then translations update accordingly in preview and payload without changing underlying FixFlow data.
Transformation Rules for Formats and Enumerations
- Given date/time fields require a specific timezone and format, When exporting, Then values are converted to the profile’s timezone and format with correct offsets and no day-boundary drift. - Given currency fields require a specific currency code and rounding, When exporting, Then amounts are converted using the configured currency and rounding rules and include the correct symbol/code as required by the profile. - Given enumerated fields have mapping tables, When exporting, Then FixFlow values are mapped to recipient enumerations; unknown values map to the configured default or cause a validation error as configured. - Given a transformation expression is malformed, When saving the mapping, Then the UI/API prevents save and displays the parser error with line and column.
Fallback and Derived Values for Missing Data
- Given a mapping specifies a fallback chain [source -> derived expression -> default constant], When the primary source is empty, Then the system evaluates the chain in order and uses the first non-empty value. - Given all sources in the fallback chain are empty, When exporting, Then the behavior follows the field’s requirement level: required fields block export with a specific error; optional fields export as null/omitted. - Given a derived field TotalCost = LaborCost + MaterialCost, When exporting a work order with both components, Then TotalCost equals the sum to two decimal places; if one component is missing, Then the fallback rules apply.
Attachment Categorization and Naming Conventions
- Given attachment mapping rules categorize files (e.g., Damage Photos, Invoices), When exporting, Then each attachment is assigned the correct recipient category and included only if allowed by the profile. - Given a profile requires at least one attachment in a required category, When none exist, Then export is blocked with an error naming the missing category. - Given a naming convention template is configured, When exporting, Then file names follow the template including required tokens (e.g., ClaimId, WorkOrderId, Timestamp) and disallowed characters are sanitized per profile rules. - Given cover letter generation is enabled, When exporting, Then a cover letter is created using the profile template with translated terms and accurate mapped fields.
Mapping Management UI/API with Safe Preview and Audit
- Given a user with Mapping Editor role, When creating or editing a mapping, Then changes are saved as Draft until explicitly Published. - Given a Draft mapping and a sample work order are selected, When Preview is run, Then the system shows side-by-side before/after, translated values, applied transformations, and any validation warnings/errors without modifying production mappings. - Given a mapping is Published, When exports run, Then the published version is applied and the system records version, author, timestamp, and change summary in an audit log. - Given two editors attempt concurrent edits, When the second editor opens the mapping, Then they see a lock or are prompted to create a new draft; simultaneous publish is prevented. - Given API endpoints for mappings are called, When creating, updating, publishing, or rolling back via API with valid auth, Then operations succeed and return version identifiers; invalid payloads return 4xx with detailed errors. - Given a previously published version exists, When Rollback is requested, Then the system restores that version as the new Published and logs the action.
Compliance Validator & Preflight Checks
"As a property manager, I want the system to flag missing or noncompliant items before I send a claim so that I avoid rejections and delays."
Description

A rules engine that validates a template instance against recipient requirements before export, checking required fields, acceptable value ranges, date and currency formats, file type/size, minimum photo evidence, and naming conventions. Displays blockers and warnings with inline fix guidance, calculates a completeness score, and prevents export when critical checks fail. Exposes a programmatic validation endpoint for automation.

Acceptance Criteria
Block Export on Critical Validation Failures
Given a claim template instance and a selected recipient profile And at least one critical rule fails (e.g., missing required field, unacceptable value, disallowed file type, unresolved naming token) When the user clicks Export Then no export files are generated And the Export action becomes disabled within 100ms And a validation summary panel opens listing all critical failures with field paths And each failure includes a unique code and severity=critical And a validation log entry is created with a correlationId and timestamp
Inline Guidance for Blockers and Warnings
Given a validation run produces issues When the user clicks an issue in the summary panel Then the UI scrolls to and focuses the exact field within 500ms And the field is highlighted and shows fix guidance text and an optional guidanceUrl And issues are tagged severity=warning or severity=critical And warnings do not disable Export; critical issues do And clicking Re-run Validation refreshes the issue list and clears resolved highlights
Completeness Score Calculation and Display
Given a template instance and recipient profile When validation runs Then a completenessScore from 0 to 100 is calculated deterministically from current data And required fields contribute at least 70% of the score weighting; optional enrichments contribute the remainder And the score and a category breakdown are displayed in the summary And the score updates within 300ms after each field fix And if completenessScore < profile.minScoreToExport, Export is disabled and a message explains the threshold
Date, Currency, and Numeric Format Validation per Recipient Locale
Given a recipient profile specifies dateFormat=YYYY-MM-DD, currency=USD with 2 decimals, and numeric ranges for estimate.total [0..100000] When validation runs on a template instance Then any date value not matching the format is flagged with code DATE_FORMAT_INVALID And any currency value with more than 2 decimals or non-USD is flagged with code CURRENCY_FORMAT_INVALID And any numeric value outside configured range is flagged with code VALUE_OUT_OF_RANGE And autofix suggestions normalize lossless variants (e.g., 1,234.5 -> 1234.50); otherwise no change is applied
Attachment Type, Size, and Photo Evidence Minimums
Given a recipient profile requires minPhotos=3, allowedTypes=[jpg,png,pdf], maxFileSizeMB=25, and minResolution=1280x720 for photos When validation runs Then attachments with types not in allowedTypes are flagged with code FILE_TYPE_DISALLOWED (critical) And attachments exceeding maxFileSizeMB are flagged with code FILE_TOO_LARGE (critical) And photos below minResolution are flagged with code PHOTO_LOW_RES (warning unless profile marks as critical) And if total attached photos < minPhotos, export is blocked with code PHOTO_MINIMUM_NOT_MET And corrupted or unreadable files are flagged with code FILE_CORRUPTED (critical)
Export Artifact Naming Conventions
Given a recipient profile defines naming template "Claim_{RecipientCode}_{YYYYMMDD}_{Seq}" When the user previews export Then a filename preview is shown using resolved tokens and sanitized to remove illegal characters And unresolved tokens cause validation failure code NAME_TOKEN_UNRESOLVED (critical) And resulting filename length <= 120 characters; longer names are truncated with a hash suffix and flagged as warning NAME_TRUNCATED And all files in a multi-file export use the same naming base with appropriate suffixes
Programmatic Validation Endpoint Contract
Given an authenticated client POSTs to /api/v1/validation with {templateId, recipientProfileId, dataVersion} When the request is valid Then the response is 200 application/json with fields: exportable:boolean, completenessScore:number(0-100), issues:[{code,severity,fieldPath,message,guidanceUrl}] And p95 response time <= 500ms for payloads <= 1MB at 50 RPS And invalid input returns 400 with error details; unauthorized returns 401; unknown profile returns 404; server errors return 5xx with a correlationId And the endpoint is idempotent for the same payload+dataVersion And clients may pass X-Idempotency-Key and receive 409 on replay with mismatched payload
One-Click Export Packaging
"As a claims coordinator, I want to generate the complete claim package with one click so that I can submit faster and reduce manual formatting."
Description

Automated assembly of the claim package with cover letter, forms, and attachments according to the selected profile, applying branding, numbering, bookmarks, and file naming rules. Generates required formats (PDF, DOCX, ZIP) as a background job with progress notifications, produces a deterministic archive for audit, and stores the artifact for download and re-export. Includes a final review screen and supports multi-language output where defined.

Acceptance Criteria
Profile-Driven Package Assembly
Given a claim with populated core fields and attached evidence files, and a selected export profile When the user triggers One-Click Export Then the system assembles a package containing the cover letter, all profile-required forms, and only the attachments permitted by the profile And the order of sections matches the profile sequence definition And all profile-mapped fields are populated into forms and cover letter per mapping rules And any profile-required field without a value is flagged as a blocking validation for the final review
Branding, Numbering, Bookmarks, and Naming Conventions
Given an export profile with organizational branding assets and naming rules When the export is generated Then the cover letter and forms render with the profile’s logo, colors, headers/footers, and typography And the combined PDF has continuous page numbering starting at 1 and visible on every page except the cover per profile rules And top-level PDF bookmarks exist for each section with the exact titles defined by the profile And each output file name follows the profile naming template (e.g., {ClaimNumber}_{RecipientCode}_{YYYYMMDD}_{Section}.pdf) with zero-padded numbers where specified And attachment file names are normalized and deduplicated per naming rules without collisions
Multi-Format Background Export with Progress Notifications
Given the user selects output formats PDF, DOCX, and ZIP for the chosen profile When the export job starts Then the export runs as a background job without blocking the UI And the UI shows a progress indicator that updates at major stages (assemble, render-PDF, render-DOCX, package-ZIP, finalize) with at least 5% granularity And the user receives in-app notifications on start, 50% milestone, and completion, and a failure notification with error details if any stage fails And all requested formats are generated and attached to the same export run record And partial failure of one format does not prevent completion of others, with status per format recorded
Deterministic Archive and Audit Manifest
Given identical inputs (claim data snapshot, attachments byte content and order, profile version, templates, locale, and export timestamp) When two exports are run at different times Then the produced ZIP archives are byte-identical And a SHA-256 hash is computed and stored for each artifact And the ZIP contains a manifest.json including claim ID, profile ID and version, template versions, locale, export timestamp (UTC), input attachment checksums, generator version, and the file list with checksums And document metadata and timestamps inside artifacts are normalized to the export timestamp to ensure determinism And compression parameters and file ordering in the ZIP are fixed and canonical
Artifact Storage, Versioning, Download, and Re-Export
Given an export completes successfully When the user opens the claim’s Exports tab Then the artifact is listed with formats, size, SHA-256 hash, created-by, created-at, profile, and locale And the user can download each format via a time-limited secure link And re-exporting with unchanged inputs and the same profile version returns the existing artifact without regeneration And re-exporting after inputs or profile version change produces a new artifact version while preserving prior versions And access to artifacts is permission-checked to the claim’s tenant context
Final Review and Approval Gate
Given the user opens the Final Review screen for a pending export When the profile-required fields are incomplete or attachments are missing Then the screen lists blocking validations with direct links to fix locations And the Approve and Export action is disabled until all blocking validations are resolved And the review shows a preview of the cover letter and a checklist of included sections with page counts And upon approval, the system snapshots claim data and attachments and records the approver and timestamp before starting the background job
Multi-Language Output Selection and Rendering
Given the selected export profile supports multiple languages When the user selects a language in Final Review and approves export Then all profile-provided static text (cover letter, section titles, labels) renders in the selected language And dates, numbers, and currency values are formatted per the selected locale And fonts required for the chosen language’s glyphs are embedded to avoid tofu/missing characters And any missing translation key falls back to the profile default language and is logged in the manifest warnings
Branding & Custom Sections
"As a landlord, I want our branding and tailored sections included in claims so that communications look professional and reflect our standards."
Description

Capabilities to add organization branding (logos, letterhead, signatures), define reusable content blocks, and configure conditional sections per recipient (e.g., loss summary, remediation steps). Provides a rich text editor with placeholders for dynamic data, asset management with size optimization, and safeguards to ensure branded outputs remain compliant with the recipient’s template structure.

Acceptance Criteria
Upload and Apply Organization Branding Assets
Given I am an Org Admin on the Claim Template settings page When I upload brand assets (logo: PNG/SVG, signature: PNG, letterhead: PDF/PNG) each ≤ 5 MB Then the system stores originals and generates optimized renditions Given assets are uploaded When I preview a claim export Then the logo renders at ≤ 120x120 px on cover, signature at ≤ 300x80 px, letterhead spans page width, and embedded asset sizes are ≤ 300 KB (logo), ≤ 150 KB (signature), ≤ 600 KB (letterhead) Given an uploaded asset cannot be optimized to its target size without reducing width below 800 px When I attempt to export Then export is blocked and a validation message identifies the offending asset and required dimensions Given branding assets are applied When exporting to PDF and web preview Then total embedded branding asset size per document is ≤ 1.2 MB and no missing-asset placeholders appear
Reusable Content Blocks and Dynamic Placeholders
Given I create a content block titled "Loss Summary" in the rich text editor When I save the block Then it appears in the Block Library with version 1 and is selectable for insertion into templates Given a block contains placeholders {{claim.id}}, {{property.address}}, and {{vendor.name}} When I render a claim with corresponding data Then all placeholders are replaced with the correct values in the preview and exported file Given a placeholder has no data When rendering Then the system applies the admin-defined fallback (static text or omission) and no unresolved {{...}} tokens remain in the final export Given unresolved placeholders remain in any section or block When I attempt to publish/export Then the system blocks export and lists each unresolved token with section, block, and line reference Given I update the block and save as version 2 When inserting into a template Then I can select v1 or v2 and the template stores the chosen version; re-exporting older claims continues to use the version recorded at time of export
Conditional Sections by Recipient Profile
Given a recipient profile "AAA Insurance" is selected When claim category = "Water Damage" and severity ≥ 3 Then include sections [Loss Summary, Remediation Steps, Photos] and exclude [Tenant Feedback] Given recipient profile "City Regulator" is selected When claim category = "Mold" (any severity) Then include section "Environmental Compliance" with block "Mold Abatement Plan" and require fields [Abatement Start Date, Contractor License] Given a rule targets a section not permitted by the recipient schema When saving the template Then save is blocked and the error lists the disallowed section and the recipient rule that conflicts Given rules are configured for both recipients When I preview exports for the same claim under each profile Then the included section list differs per rules and each preview passes recipient schema pre-check with 0 errors
Compliance Guardrails with Recipient Template Structure
Given a recipient schema defines required sections and field order When I configure branding and custom sections Then validation ensures all required sections exist, are ordered as specified, and no additional fields are inserted between locked sections Given a required field is missing or renamed When running validation Then a blocking error lists each missing/renamed field with its expected location and data type Given optional sections are rearranged When validating Then export proceeds only if arrangement complies with schema constraints; violations produce blocking errors that cite the specific rule Given the template passes validation When exporting Then the generated file records a validation result of 0 errors and ≤ 3 warnings in the export log and is marked "Compliant"
Branded Cover Letter and File Naming per Profile with Jargon Translation
Given profile "AAA Insurance" has naming rule "{recipient_code}_{claim_id}_{yyyyMMdd}.pdf" and a branded cover letter template When I export claim 12345 on 2025-09-06 Then the file name is "AAA_12345_20250906.pdf" and the cover letter includes the organization's logo and signature in the header Given a jargon translation map for recipient "AAA" defines Work Order→Service Request and Unit→Apartment When exporting Then those terms are translated in rendered blocks and section headings for that profile only Given the naming rule would produce illegal filename characters for the target OS When exporting Then characters are sanitized per profile rules and the final name still matches the pattern semantics Given the profile time zone is America/New_York When computing {yyyyMMdd} Then the date is derived from that time zone, not the client device
Signatory Management and Immutable Historical Exports
Given I define a signatory with name, title, and signature image When I insert a signature block into the cover letter Then the exported document displays the signature image and printed name and title with an ISO 8601 timestamp of generation Given the user role lacks permission to use a specific signatory When exporting Then the system prevents export or substitutes an authorized signatory per policy and records the action in the audit log Given I update a signatory's signature image after exporting a claim When I re-export the prior claim Then the previous export remains unchanged in storage, and the new export uses the updated signature; the export log shows the signatory version used
Delivery & Tracking
"As a property manager, I want the system to deliver the package to the right recipient and track its status so that I can confirm receipt and follow up appropriately."
Description

Configurable delivery channels per profile, including email with secure links, SFTP, and supported portal integrations, with delivery receipts and status tracking. Implements retries with backoff, error handling, and resubmission flows. Logs delivery events to the claim record, exposes webhooks for downstream systems, and provides an export history with timestamps, actor, template version, and checksum.

Acceptance Criteria
Per-Profile Channel Configuration and Validation
Given a Claim Template export profile with channels Email, SFTP, and Portal enabled and all required fields configured When a user initiates an export for a claim using that profile Then the system validates required configuration per channel and blocks export if any required field is missing, listing missing fields per channel And only the enabled channels are executed in the configured order And the export record stores the executed channel set and the profile identifier used
Email Delivery with Secure Link and Receipt Tracking
Given the profile Email channel is configured with recipients, a subject template, and a secure link TTL of 24 hours And a claim package has been generated with a computed checksum When the user sends the export via Email Then the system sends an email to the configured recipients with the templated subject and a single-use tokenized HTTPS link to the package And the link expires at the configured TTL and is invalidated after first successful access; after expiry, attempts return a 410 Gone response And delivery status updates to SENT upon a successful provider submission, to DELIVERED upon provider delivery confirmation, and to FAILED upon a definitive bounce And a delivery receipt including provider message ID and timestamps is stored on the export record
SFTP Delivery with Checksum Verification and Retry Backoff
Given the profile SFTP channel is configured with host, port, username, host key fingerprint, remote path, and filename convention And a retry policy is configured as exponential with maxAttempts 3, initialDelay 1 minute, and maxDelay 15 minutes When the export is sent while the SFTP server is reachable Then the system establishes an SSH connection verifying the host key matches the configured fingerprint And uploads the file atomically using a temporary name then renames to the final filename per convention And verifies the uploaded file by comparing the computed checksum; a mismatch triggers a reupload attempt And the channel status is set to DELIVERED upon successful rename When a transient error occurs such as timeout or throttling Then the system retries per the configured policy with jitter and logs each attempt When a permanent error occurs such as authentication failure or invalid remote path Then the system marks the channel FAILED without further retries and records the error code and message
Portal Integration Submission and Status Synchronization
Given a supported portal integration is configured with valid credentials and a target destination When the user sends an export via the portal channel Then the system authenticates to the portal and creates a submission with payload and naming per the profile And stores the external submission identifier on the export record And sets the channel status to SENT And periodically polls or receives callbacks until a terminal state is reached Then on acceptance, the status is set to DELIVERED and the acceptance receipt is stored And on rejection, the status is set to FAILED with the provided error codes and human-readable reason And all transitions are logged with timestamps
Delivery Event Logging and Export History Metadata
Given any delivery channel execution for a claim export When a delivery lifecycle event occurs (QUEUED, SENT, DELIVERED, FAILED, RETRIED) Then an event entry is appended to the claim record with fields: timestamp in UTC, actor (user ID or system), channel, endpoint, attempt number, status, error code, error message, template version, and checksum And the Export History API and UI list each export with fields: export ID, claim ID, profile ID, channel set, started and completed timestamps, final status, actor, template version, and checksum And history entries are immutable and available for audit retrieval
Webhook Notifications for Downstream Systems
Given a webhook subscription is configured with a shared secret and events [delivery.sent, delivery.delivered, delivery.failed, delivery.retried] When any subscribed event occurs for a claim export Then the system POSTs a JSON payload to the subscriber endpoint including export ID, claim ID, profile ID, channel, status, attempt number, timestamps, template version, checksum, and error details if any And includes an X-FixFlow-Signature header computed as HMAC-SHA256 over the payload using the subscription secret And includes an Idempotency-Key header unique per event And on 2xx response the webhook is marked delivered; on 5xx or timeout the system retries with exponential backoff up to 6 attempts; after exhaustion the event is marked webhook.failed and recorded in a dead-letter log
Resubmission and Recovery Flows with Versioning
Given an export attempt is in FAILED status When a user selects Resubmit and optionally updates channel configuration or selects a newer template version Then the system creates a new export version linked to the prior attempt and executes only the selected channels And the original export record remains immutable and closed And the export history shows the chain of attempts with reasons and outcomes And if the prior attempt partially delivered, only failed channels are selected by default for resubmission, with an option to include previously delivered channels And idempotency is enforced per channel by de-duplicating against external submission or message IDs within a 24-hour window

Case Bundles

Combine related work orders—like leak, mitigation, and drywall repair—into a single, chronologically stitched Proof Pack with cross‑references. Shows cause‑and‑effect and cost allocation in one place, reducing adjuster questions and back‑and‑forth. Ideal for complex incidents spanning multiple trades or units.

Requirements

Bundle Creation & Management
"As a property manager, I want to group related work orders into a single bundle so that I can manage one incident holistically instead of juggling separate tickets."
Description

Enable users to create a Case Bundle from one or more existing or new work orders, link/unlink items, and manage bundle metadata (incident type, incident start/end, impacted units, severity). Provide merge and split actions to consolidate or separate bundles as facts evolve. Support cross-unit and cross-trade inclusion with safeguards to prevent duplicate linkage. Integrate with FixFlow’s photo‑first intake so new submissions can be added to a bundle from the intake flow. Persist a canonical Bundle entity in the data model with IDs exposed via API, enabling downstream features (approvals, vendor routing, exports) to be bundle-aware. The outcome is a single container that organizes related work orders under one incident for faster handling and clearer reporting.

Acceptance Criteria
Create Bundle from Existing and New Work Orders
Given a user with edit permissions is on the Work Orders list or detail page When the user selects zero or more existing work orders and clicks "Create Bundle" And the user enters required metadata (incidentType, incidentStart, impactedUnits, severity) and optional incidentEnd Then the system validates: incidentStart <= incidentEnd (if provided), severity ∈ {Low, Medium, High, Critical}, impactedUnits not empty And the system creates a Bundle with a unique bundleId and persists all metadata And any selected work orders are linked to the new Bundle and cannot be linked twice to the same Bundle And the Bundle detail shows linked count, createdBy, createdAt, and metadata exactly as saved
Link/Unlink Work Orders with Duplicate Safeguards
Given a Bundle exists When the user opens "Link Work Orders" for that Bundle Then the selectable list excludes work orders already linked to any Bundle And if the user attempts to link a work order by ID that is already bundled, the system blocks the action and shows an error naming the existing bundleId When the user unlinks a work order from the Bundle and confirms Then the work order is removed from the Bundle, the Bundle count updates, and an audit entry (who, what, when) is recorded And the same work order cannot appear twice within the same Bundle at any time
Merge Bundles with Deterministic Conflict Resolution
Given two Bundles (source and target) exist When the user initiates "Merge" selecting a source and a target Bundle and confirms Then all work orders from the source are moved to the target without duplicates And metadata resolves deterministically: incidentStart = min(source.start, target.start); incidentEnd = max(non-null ends); impactedUnits = union(source, target); severity = max severity by order Low<Medium<High<Critical; incidentType = target.incidentType if set else source.incidentType And the source Bundle is set to status=Archived and is no longer linkable And an audit entry records the merge (sourceId, targetId, counts) and the target shows updated totals
Split Bundle into a New Bundle
Given a Bundle with multiple linked work orders exists When the user selects a subset of linked work orders and clicks "Split to New Bundle" Then a new Bundle with a new bundleId is created containing the selected work orders And the original Bundle retains the remaining work orders And the new Bundle metadata defaults are derived: incidentStart = min(selected work order timestamps), incidentEnd = max(selected) if applicable, impactedUnits = units from selected, severity = max severity across selected; incidentType copied from original (editable) And audit entries are recorded on both Bundles referencing the split operation
Add to Bundle from Photo-First Intake Flow
Given a new maintenance submission is being triaged in the intake flow When the triager reaches the bundling step and searches by address, unit, keyword, or bundleId Then the system returns matching Bundles and highlights those with overlapping timeframes/units When the triager selects an existing Bundle or opts to create a new one Then the resulting work order is linked to that Bundle upon submission And if the referenced existing work order is already in a Bundle, the intake flow suggests that Bundle and prevents linking to a different Bundle
Bundle Entity Persistence and API Exposure
Given a Bundle is created or updated When a client calls GET /bundles/{bundleId} Then the API returns the canonical Bundle entity with bundleId, metadata (incidentType, incidentStart, incidentEnd, impactedUnits, severity), linked workOrderIds, createdAt, updatedAt, and status (Active|Archived) When a client calls GET /work-orders?bundleId={bundleId} Then only work orders linked to that Bundle are returned And each work order resource includes bundleId when linked And export endpoints include bundleId and basic Bundle metadata for each exported work order
Cross-Unit and Cross-Trade Inclusion Validation
Given work orders span multiple units and trades When the user links them into a single Bundle Then the system allows cross-unit and cross-trade inclusion without restriction And validates that impactedUnits covers all units represented by linked work orders (auto-suggesting missing units) And prevents linking any work order already assigned to a different Bundle
Chronological Stitching & Cross-References
"As an adjuster, I want a stitched timeline that shows how events unfolded across all related tickets so that I can quickly verify cause and sequence without requesting extra documentation."
Description

Aggregate all bundle artifacts—photos, timestamps, notes, approvals, messages, technician check-ins, and invoices—into a unified timeline that automatically orders events across all linked work orders. Surface cross-references between items (e.g., mitigation visit leading to drywall repair) with deep links back to the source ticket and artifact. Provide filters by trade, unit, vendor, and artifact type, plus a zoomable timeline view optimized for mobile web. Ensure every artifact retains its original context while being viewable in the stitched chronology. This timeline becomes the source of truth for incident narrative and supports downstream Proof Pack generation.

Acceptance Criteria
Unified Timeline Ordering Across Bundled Work Orders
Given a case bundle containing 2+ linked work orders with artifacts (photos, timestamps, notes, approvals, messages, technician check-ins, invoices) When the user opens the Case Bundle timeline Then all artifacts from all linked work orders are aggregated into a single view ordered by normalized timestamps (UTC) across all items And if two artifacts share the same timestamp to the second, ordering is stable and deterministic by artifact type priority [check-in, photo, note, message, approval, invoice] then by artifact ID ascending And if an artifact has a missing timestamp, the system uses its creation time and flags it as "Estimated" in the UI And the total event count displayed equals the sum of included artifacts across the linked work orders And updating an artifact's timestamp or adding/removing artifacts reorders/updates the timeline within 2 seconds of save or on refresh
Cross-Reference Deep Links Between Related Items
Given an artifact that references or is caused by another artifact or work order (e.g., mitigation leading to drywall repair) When the timeline renders the artifact Then a cross-reference chip is shown with the referenced item’s label and relation (e.g., "caused by", "follow-up to") And selecting the cross-reference opens a deep link to the exact source ticket and artifact in an in-app view And the deep link URL includes the case bundle ID, source work order ID, and artifact ID so the destination view loads directly And if the user lacks permission, a 403 message is shown; if the artifact was deleted, the link shows "Unavailable" without breaking the timeline And navigating back returns the user to the same scroll position and zoom level on the timeline
Faceted Filters by Trade, Unit, Vendor, Artifact Type
Given the timeline view When the user applies any combination of filters for trade, unit, vendor, and artifact type Then only matching artifacts remain visible and the total count updates accordingly And filters are multi-select, additive by default, and can be cleared individually or all at once And the active filter state persists for the case bundle across sessions for the same user And deep links to the timeline include the current filter query so the same filtered view can be shared and reopened And filter application re-renders results within 300 ms for up to 500 artifacts
Mobile Web Zoomable Timeline Interaction
Given a mobile web device (iOS Safari 16+, Android Chrome 114+) in portrait or landscape When the user pinch-zooms or uses on-screen zoom controls Then the time scale adjusts smoothly between 1 hour and 90 days with no full page reload And horizontal pan scrolls the timeline while preserving the zoom level and current focus time And interactive targets are at least 44x44 px and meet WCAG 2.1 AA touch target guidelines And the frame rate remains >= 30 fps during zoom/pan on mid-range devices; first input delay < 100 ms And double-tap zooms in by 2x centered on the tapped time
Original Context Preservation and Permissions
Given an artifact displayed in the stitched timeline When the user opens its details Then the UI shows its original work order ID, unit, vendor, author, original timestamp, and artifact type exactly as stored And a "View in Source" action deep-links to the artifact’s native view in its source work order And any edits made from the timeline respect source permissions; users without edit rights see read-only details And any permitted edit made from the timeline is reflected immediately in the source view and audit log
Proof Pack Generation From Stitched Chronology
Given a user viewing a curated timeline (with optional filters applied) When they select "Generate Proof Pack" Then the system produces a Proof Pack that preserves chronological order and shows cross-reference notations inline and as footnotes with deep links And it includes all visible artifacts and excludes filtered-out ones if "Export current view" is selected And it embeds originals (photos at uploaded resolution with capture timestamps; messages/notes with author and time; invoices as attachments or image snapshots) And it generates within 10 seconds for up to 500 artifacts And it produces a stable document hash when the source data has not changed And downloading/opening the Proof Pack on mobile succeeds and links open back into the app
Performance, Pagination, and Loading Behavior on Mobile
Given a case bundle with up to 1,000 artifacts When the user opens the timeline on mobile Then initial content renders with first contentful paint <= 2 s on a mid-range device over 4G, and subsequent artifacts lazy-load in batches of 100 with a visible progress indicator And the app memory usage for the timeline remains under 200 MB during interaction And a sticky "Jump to Now/Start" control appears after scrolling more than three screens And on network error during fetch, a retry control is shown and the timeline state is preserved And analytics record time-to-render, zoom/pan, filter interactions, and export events with anonymized IDs
Cause-Effect Mapping & Cost Allocation
"As a landlord, I want to map cause and assign costs within the bundle so that the right parties are charged and I can settle claims without back-and-forth."
Description

Allow users to declare causal links between work orders (e.g., leak caused mitigation, which caused drywall repair) and allocate costs accordingly. Support percentage or fixed-amount allocations per work order and per line item, with labeled payers (owner, HOA, insurance policy, tenant). Provide validation to ensure allocations sum correctly and a history of allocation changes with effective dates. Integrate with vendor invoices and estimates so that adjustments flow into the bundle’s financial summary. Expose a bundle-level cost breakdown by cause, trade, payer, and unit to reduce disputes and repeat questions.

Acceptance Criteria
Create Causal Chain Across Three Work Orders
Given a Case Bundle with three work orders (Leak A, Mitigation B, Drywall C) When the user sets A as the cause of B and B as the cause of C Then the system saves two directed causal links (A→B, B→C) And prevents cycles by disallowing C→A And orders the chain chronologically by work order start date And displays cross-references on each work order card within the bundle And allows removing a causal link with a confirmation prompt And requires at least one root cause per causal chain
Mixed Percent and Fixed Allocations at Line-Item Level
Given a vendor invoice with line items linked to work orders A, B, C When the user allocates 40% of line item 1 from A to B and $300 of line item 2 from A to C Then the system applies allocations at the line-item and work-order level as entered And prevents saving if any allocation would exceed the source line item remaining amount And applies rounding using largest-remainder to ensure the sum of allocated pennies equals the source line total And preserves currency and tax treatment consistent with the source document And flags any unallocated remainder per line item until explicitly assigned or deferred
Allocation Sum Validation and Error Messaging
Given a line item with a total of $1,000 When the user enters payer splits of 60% Owner, 50% Insurance Then the system blocks save, highlights the line item, and shows "Allocation exceeds 100%" with the overage amount And when the user changes to 60% Owner, 40% Insurance Then the system allows save and shows the line as balanced And for fixed-amount splits, the sum must equal $1,000 exactly (within $0.01 rounding) And negative or credit lines must sum to the exact negative total before save is allowed
Payer Labeling and Split-Billing
Given payer types Owner, HOA, Insurance Policy, and Tenant are enabled When the user creates any allocation entry Then a payer label is required and validated against the allowed set And when Insurance Policy is selected, a policy identifier is required Then the Financial Summary aggregates totals by payer and shows counts and amounts per payer And unlabeled or deprecated payer types prevent save with a clear error
Allocation History, Effective Dating, and Reversion
Given existing allocations on a bundle When the user edits allocations and sets an effective date of today Then the system stores a new immutable version with editor, timestamp, and change note And recalculates the Financial Summary using the latest effective version And allows viewing a chronological history with diffs per line item and payer And supports reverting to any prior version, creating a new version entry
Invoice and Estimate Sync to Bundle Financials
Given a vendor updates an invoice amount linked to the bundle When the invoice is approved or re-synced into FixFlow Then the affected allocations are recalculated within 30 seconds And the bundle Financial Summary updates totals, variances, and payer subtotals And any allocations that no longer balance are flagged for review with a task created And links to the source invoice and estimate revisions are accessible from the bundle
Bundle-Level Cost Breakdown and Export
Given a bundle with allocated costs across multiple trades and units When the user opens the Cost Breakdown tab Then the system displays totals by Cause, Trade, Payer, and Unit with drill-down to line items And all subtotals reconcile to the grand total within $0.01 And filters for date range, payer, trade, unit, and cause are available and combine with AND logic And the user can export the current view to CSV with the same groupings and filters applied
Proof Pack Export & Sharing
"As a claims adjuster, I want a single export that tells the complete incident story with evidence so that I can approve or question costs without requesting additional files."
Description

Generate a shareable Proof Pack (PDF and secure web view) from the bundle’s stitched timeline, including cover summary, incident metadata, causal map, cost allocation breakdown, and embedded or linked originals (photos, invoices, approvals). Offer redaction of sensitive fields and configurable sections. Provide expiring, access-controlled links for external stakeholders (e.g., adjusters), with view tracking and tamper‑evident watermarks. Integrate with FixFlow notifications for one-click share and with the API for automated delivery to partner systems. Ensure exports are performant for large bundles and preserve cross-references for auditability.

Acceptance Criteria
Generate Complete Proof Pack Export (PDF & Web)
Given a case bundle with stitched timeline, causal map, cost allocation, and originals (photos, invoices, approvals), When the user exports as PDF, Then the PDF includes a cover summary, incident metadata, causal map, cost allocation breakdown, and embedded or linked originals, And all items are ordered chronologically per the stitched timeline, And cross-references between items (e.g., work order IDs, photo references, invoice lines) are clickable anchors within the PDF. Given the same bundle, When the user opens the secure web view export, Then the content matches the PDF sections and order, And cross-references navigate within the web view, And deep links to originals open the correct artifact in a new tab with proper access control. Given a large bundle (≥10 work orders, ≥600 photos, ≥25 invoices, ≥100 comments), When export is initiated, Then PDF generation completes within 90 seconds p95 (max 180 seconds), And the first byte of the web view loads within 2 seconds p95 (fully interactive within 5 seconds p95), under up to 5 concurrent exports per account.
Apply and Preview Redaction Before Sharing
Given a bundle containing sensitive fields (tenant name, phone, email, unit number, internal notes), When the user selects fields to redact and opens Preview, Then the preview masks selected values consistently across cover, metadata, photos, invoices, approvals, and comments. Given redactions are configured, When the user exports PDF and web view, Then the redacted values are non-recoverable (vector text removed or rasterized; hidden text/OCR not present) in the PDF and masked in the web view, And the same redaction set is applied to all embedded/linked originals in the export. Given a redacted export was created, When an auditor reviews the export metadata, Then the redaction policy (fields and timestamp) is recorded in the bundle audit log without exposing the redacted values.
Configure Sections and Save Templates for Proof Pack
Given section toggles (Cover Summary, Incident Metadata, Causal Map, Cost Allocation, Originals, Approvals, Comments), When the user saves a configuration as a template with a name, Then the template is available for future exports for the same organization. Given a saved template, When the user exports using that template, Then only the selected sections appear, And unselected sections are entirely omitted from the PDF/web view (not just hidden), And the table of contents and page numbering reflect the included sections only. Given the user updates a template, When a subsequent export uses the updated template, Then the latest template definition is applied and versioned in the audit log.
Share via Expiring Access-Controlled Link
Given a completed export, When the user creates a share link with expiry between 24 hours and 30 days and optional password and viewer role (Viewer or Commenter), Then the link enforces the expiry and required password, And enforces the selected role permissions in the web view. Given a share link, When the expiry time passes or the link is revoked by the owner, Then accessing the link returns 410 Gone for expired and 410 Gone (Revoked) for revoked, And the share status displays Expired or Revoked in the UI. Given a share link configured as single-use, When the first access occurs, Then subsequent accesses return 403 Forbidden unless the owner resets the link. Given organization-wide settings, When a user exceeds 10 active links per bundle, Then the system prevents creation and prompts to revoke or extend existing links.
Track External Views and Produce Audit Log
Given an active share link, When an external stakeholder opens the web view or downloads the PDF, Then a view event is recorded with timestamp, share link ID, bundle ID, viewer identity (authenticated email or provided name), IP, and user agent. Given multiple views, When the owner opens View Activity, Then they can see total views, unique viewers, last accessed time, and per-event details, And they can export the activity as CSV covering a selectable date range. Given data retention policy of 12 months, When events exceed 12 months age, Then they are no longer shown in the UI or exports but are reflected as aggregated counts in the bundle audit summary.
Watermark and Integrity Protection for Shared Exports
Given a share is created for recipient R, When the PDF is generated, Then each page displays a tamper-evident watermark containing organization name, bundle ID, share link ID, recipient (R), and timestamp, with 30–50% opacity and non-removable overlay. Given the export is generated, When an integrity check endpoint is called with the export’s embedded checksum/signature, Then it returns Valid for an unmodified file and Invalid for a modified file, And the web view shows an Integrity Check Failed banner for invalid files. Given a web view share, When the page is rendered, Then a faint dynamic watermark with recipient and share ID is overlaid on images and documents, And screenshots display the watermark information visibly.
One-Click Share via Notifications and API Delivery to Partners
Given a completed export, When the user clicks Share and selects a contact, Then FixFlow sends a notification (email and in-app) containing the secure link, and the share status updates to Sent with timestamp. Given a configured partner integration, When the API is called POST /partners/{id}/deliveries with bundle ID and template, Then the partner receives a webhook with the share URL and metadata, And the delivery status is tracked as Succeeded or Failed, with automatic retry up to 3 times on transient errors. Given a failed delivery, When retries are exhausted, Then the system alerts the owner via notification and flags the delivery as Failed with error details in the activity log.
Automated Bundle Suggestions
"As a maintenance coordinator, I want the system to suggest related cases during intake so that I don’t miss connections between tickets for the same incident."
Description

Use smart triage signals—address/unit, time proximity, keywords, photo similarity, sensor alerts, and reporter identity—to suggest attaching a new intake to an existing bundle or creating a new one. Present ranked suggestions with confidence scores and key matching signals for transparency. Support auto-attach for high-confidence matches per customer policy, with undo. Continuously learn from user accept/decline actions. Integrate with the intake flow to minimize clicks and with notifications to alert managers when potential bundle merges are detected post‑submission.

Acceptance Criteria
Ranked Suggestions with Confidence and Signal Transparency
Given a new intake has potential related work orders within the last 30 days for the same property When the system generates bundle suggestions Then it displays up to 5 suggestions sorted by descending confidence (0–100%) And each suggestion shows at least 3 key matching signals with values (e.g., Same Unit, Time proximity: 12h, Photo similarity: 0.82) And suggestions below the minimum confidence threshold (default 20%) are not shown And the list renders within 2 seconds for the 95th percentile of requests And a Create New Bundle option is always available
Policy-Driven Auto-Attach with Undo
Given the customer’s auto-attach policy threshold is configured (e.g., confidence ≥85% and Same Unit signal present) And a suggestion meets the policy at submission time When the intake is submitted Then the intake is auto-attached to the target bundle within 5 seconds And the user sees an in-flow confirmation with an Undo option available for 15 minutes And choosing Undo detaches the intake, restores its pre-attach state, and records the reversal And account- and property-level policy toggles and overrides are respected
In-Flow Attachment During Intake With Minimal Clicks
Given a reporter has completed photo upload and issue details When bundle suggestions are presented in the intake flow Then accepting the top suggestion requires at most 1 click or tap And creating a new bundle or choosing Skip for now requires at most 2 clicks or taps And the suggestions step is fully keyboard-accessible and screen-reader labeled And the module loads within 1.5 seconds on a 3G network for the 95th percentile
Post-Submission Merge Detection Notifications
Given new evidence or signals arrive after intake submission (e.g., vendor note, sensor alert, new work order) When the system detects a potential bundle merge Then a notification is sent to assigned managers within 1 minute including bundle IDs, confidence score, and top 3 signals And notifications are throttled to no more than 1 per bundle per 6 hours And the notification provides direct actions: Attach, Create New Bundle, Dismiss And actions taken from the notification update the bundle within 5 seconds and appear in the app timeline
Learning From Accept/Decline Feedback
Given users accept or decline suggestions When feedback is recorded Then the system stores accept/decline events with context (signals, confidence, actor, timestamp, candidate and chosen bundle IDs) And within 24 hours the model updates ranking weights or thresholds per customer based on accumulated feedback And repeated declines for the same intake-to-bundle pair suppress re-suggestion for 30 days And an admin can view the last model update time and feedback count in settings
Signal Coverage and Matching Rules
Given signals include address/unit, time proximity, keywords, photo similarity, sensor alerts, and reporter identity When generating suggestions Then at least two independent signals above their thresholds are required to propose a match unless confidence ≥95% And suggestions across different units are allowed only when leak/overflow keywords are present and time proximity <48 hours or a sensor link exists And suggestions are never generated when property addresses differ and no cross-property linkage is configured And missing signal sources degrade gracefully with an insufficient signals note without erroring
Audit Trail and Transparency
Given any suggestion list is shown or an auto-attach is executed When the user views the Proof Pack or bundle activity Then the system displays the suggestions considered, selected action, confidence scores, key signals, policy thresholds, actor, timestamps, and model version And the audit log is immutable, exportable (CSV/JSON), and retained for 24 months And access to audit details is permission-gated And all events include correlation IDs for cross-referencing
Phase Approvals & Vendor Routing (Bundle-Aware)
"As a property manager, I want to approve phases once for the whole bundle so that vendors across trades can proceed in the right order without redundant approvals."
Description

Enable approvals at the bundle or phase level (mitigation, investigation, restoration), respecting spend thresholds and insurer preauthorization rules. Allow one-click approvals that trigger routing to preferred vendors across multiple trades, with task dependencies to prevent out-of-order dispatch. Surface blockers (e.g., waiting on dry-out readings) and hold states at the phase level. Synchronize approval status and routing back to each constituent work order while maintaining bundle context for reporting and SLA tracking.

Acceptance Criteria
Approve Entire Bundle Within Spend Thresholds
Given a Case Bundle with all phases (Mitigation, Investigation, Restoration) having estimates within the landlord’s spend authority and no insurer preauthorization required And all phases have required documents and no active blockers When an authorized approver clicks One-Click Approve at the bundle level Then the system records Bundle Approval with approver, timestamp, and approval method And sets each phase status to Approved where dependencies are satisfied And triggers vendor routing jobs for all eligible phases within 5 seconds And updates each constituent work order with bundleId, phaseApprovalStatus=Approved, and approval timestamp within 10 seconds And writes an immutable audit log entry And prevents duplicate approval attempts by disabling the action until state changes
Phase-Level Approval Requires Preauthorization
Given the Mitigation phase estimated cost exceeds the insurer preauthorization threshold And preauthorization status is Not Received When a user attempts to approve the Mitigation phase Then the approval is blocked and the phase enters Hold: Insurer Preauth And a blocker card displays Required: Insurer Authorization with link to upload/enter authorization details And vendor routing is not initiated When a valid insurer authorization is attached and marked Approved by an approver with authority Then the Approve action becomes enabled And upon approval, the phase status updates to Approved and audit log captures preauth reference number
One-Click Approval Triggers Multi-Trade Vendor Routing
Given the Restoration phase requires multiple trades (e.g., drywall, paint, flooring) And preferred vendor lists per trade are configured with service areas and capacity When the approver clicks Approve on the Restoration phase Then the system selects the highest-ranked eligible preferred vendor per trade that covers the property location and has available capacity And dispatches work orders per trade with bundle and cross-reference metadata within 5 seconds And sends notifications to selected vendors and the requester And updates each child work order with vendorId, dispatchStatus=Routed, and ETA if provided And if no eligible vendor is found for a trade, the phase shows blocker No Eligible Vendor and does not dispatch that trade
Task Dependency Enforcement Across Phases
Given the Restoration phase depends on Mitigation completion with two consecutive compliant dry-out readings And the Mitigation phase is not yet marked Complete or readings are non-compliant When a user attempts to route or start Restoration tasks Then the system prevents dispatch and sets phase state to Blocked by Dependency And displays the unmet dependency details with direct links to the Mitigation readings When Mitigation is marked Complete and compliant readings are recorded Then the dependency clears automatically and the Restoration routing action becomes enabled
Sync Approval and Routing Status Back to Work Orders
Given a bundle-level or phase-level approval occurs and routing is triggered When the system processes the approval Then 100% of constituent work orders receive updated fields within 10 seconds: phaseApprovalStatus, approvalTimestamp, vendorId (if routed), dispatchStatus, and blockerState (if any) And reporting views show the bundle context with cross-references between work orders and phases And SLA timers start or resume at the phase level upon approval and at the work order level upon routing And all changes are available to API consumers within 15 seconds
Change Order Increases Spend After Approval
Given a phase was previously Approved And a submitted change order increases the phase estimated cost above the approver’s spend authority or triggers insurer preauthorization When the change order is saved Then the phase status auto-transitions to Needs Reapproval And all unstarted routed tasks move to Hold: Funding Review and vendors are notified of the hold And the approver and stakeholders receive a reapproval required notification When an approver with sufficient authority reapproves (and preauth is attached if required) Then the hold is lifted, pending tasks resume routing, and the audit log records the reapproval with references to the change order
Audit Log and Role-Based Permissions
Given role-based spend authorities and approval permissions are configured When a user without approval rights or insufficient spend authority attempts bundle or phase approval Then the action is blocked with an authorization error and no state change occurs And when an authorized user performs approval or routing Then an immutable audit entry is created capturing userId, role, bundleId, phase, previousState, newState, spend amounts, preauth references, and timestamp And audit entries are retrievable via UI and exportable via API/CSV And any manual overrides require a justification note or are blocked per policy
Bundle Permissions & Audit Trail
"As an operations lead, I want precise access controls and an audit trail for each bundle so that we can share confidently and defend decisions if questioned."
Description

Introduce bundle-level permissions consistent with FixFlow RBAC: property team full control, vendors limited to assigned tasks, tenants restricted to unit-specific, non-sensitive artifacts, and external adjusters read-only via secure links. Record a complete audit log of bundle events, including who linked/unlinked work orders, edited causal relationships, changed cost allocations, generated exports, and shared links. Provide immutable event IDs and time stamps for compliance. Expose audit logs via admin UI and API to support dispute resolution and regulatory requests.

Acceptance Criteria
Property Team Full-Control Bundle Management
Given a user with Property Team role and access to the property's portfolio that owns all work orders in the bundle When they open the bundle Then they can link/unlink work orders, edit causal relationships, edit cost allocations, add/remove bundle notes and attachments, generate bundle exports, and create/revoke secure links for tenants, vendors, and adjusters Given a user without Property Team role When they attempt any of the above operations via UI or API Then the action is blocked (UI controls disabled; API returns 403) and an access_denied audit event is recorded with attempted_action and actor_role
Vendor Access Scoped to Assigned Work Orders
Given a vendor user whose company is assigned to at least one work order in the bundle When they view the bundle Then they can see only their assigned work orders and artifacts explicitly flagged shareable-with-vendors for those work orders, along with task scheduling and status fields And they cannot see other work orders, tenant PII, internal notes, causal relationships, or cost allocations When the vendor attempts a prohibited operation (edit causal, change cost allocation, link/unlink, generate export, create/revoke links) via API Then the system returns 403 and appends an access_denied audit event with actor_role=vendor and attempted_action
Tenant Read-Only Access to Unit-Specific, Non-Sensitive Artifacts
Given a tenant of Unit A has a valid secure bundle link and the bundle contains work orders from Unit A and other units When the tenant opens the link Then they can view only artifacts tagged non-sensitive and scoped to Unit A, and cannot see cost allocations, vendor notes, internal comments, or work orders from other units When the tenant attempts to access artifacts from other units or download a full Proof Pack Then the system returns 404 for out-of-scope resources and 403 for prohibited downloads, and logs an access_denied audit event When the property team revokes the tenant link Then subsequent attempts return 410 (link_revoked) and a link_revoked audit event is recorded
External Adjuster Read-Only via Expiring Secure Link
Given a property team member creates an adjuster link with scope=bundle, permission=read-only, expiry=72h, and export_download=true When the adjuster accesses the link before expiry Then they can view allowed bundle artifacts and download the Proof Pack export, but cannot modify any data or relationships When the link expires Then access returns 401 (expired_token) and a link_expired audit event is recorded When the link is configured as single-use Then the second access attempt returns 410 (consumed) and a link_consumed audit event is recorded
Complete and Immutable Bundle Audit Log
Given any of the following bundle events occurs: work order linked/unlinked; causal relationship created/edited/deleted; cost allocation created/updated/deleted; export generated/downloaded; secure link created/viewed/revoked/expired/consumed; permission check success/failure Then exactly one audit record is appended per event with fields: event_id=UUIDv4, bundle_id, actor_id, actor_role, actor_org_id, event_type, resource_ids, request_id, timestamp=ISO 8601 UTC (ms precision), client_ip, user_agent, before_value, after_value, and signature And audit records are append-only; any attempt to update or delete an audit record is rejected (405) and a tamper_attempt audit event is logged And audit listing is deterministically ordered by timestamp ascending and secondarily by event_id
Admin UI: Filterable, Paginated, Exportable Audit Trail
Given an admin with bundle.audit:read permission opens the bundle Audit tab When they filter by date range, actor, event_type, and resource Then results match the filters, are sorted by timestamp desc, and paginate at 100 events/page with response time <= 2s for up to 10,000 events When they export the visible audit results as CSV or JSON Then the file contains all filtered records, includes a SHA-256 checksum, and metadata fields: generated_at (ISO 8601 UTC), filter_params, total_count When they open an audit record Then the UI shows before/after diffs and a deep link to the related work order or resource
Audit API: Secure, Queryable, Consistent Access
Given an authenticated client with OAuth scope bundle.audit:read and membership in the property's organization When they call GET /api/bundles/{bundleId}/audit with from, to, event_type, actor, page_size, and cursor parameters Then the API returns 200 with items[], next_cursor, and stable ordering by timestamp,event_id; page_size max=500; invalid parameters return 400 When the client supplies If-None-Match with a prior ETag Then unchanged results return 304 When the client supplies since_event_id Then results start strictly after that event When the client lacks required scope or membership Then the API returns 403 and logs an access_denied audit event

Photo Markup

Add arrows, blur sensitive details, circle damage areas, and caption photos directly before export. Auto‑stamps date, time, and unit metadata while optimizing image size for email without losing EXIF. Clarifies evidence, reduces follow‑up questions, and speeds approvals.

Requirements

In-browser Markup Canvas
"As a tenant reporting an issue, I want to add arrows, shapes, and captions to my photos so that I can clearly show the problem without long explanations."
Description

Provide a mobile-first, browser-based image markup workspace that supports arrows, circles/ellipses, rectangles, freehand drawing, text captions, color and stroke controls, undo/redo, zoom/pan, and multi-touch gestures. Allow quick switching between multiple uploaded photos in a pre-export carousel. Ensure low-latency rendering on mid-range mobile devices and offline-capable editing for sessions with poor connectivity. Integrate with FixFlow tickets by attaching the working set to the maintenance request, and expose a simple tool API so additional tools (e.g., blur/redact) can plug into the same canvas. This capability clarifies issues visually, reduces back-and-forth, and sets the foundation for all markup operations.

Acceptance Criteria
Mobile Markup Tools and Controls
Given a user has opened a photo in the in‑browser canvas on a mobile device, When the user selects the Arrow tool and drags from point A to point B, Then an arrow renders with adjustable start/end handles, And the user can change color (min 6 presets) and stroke width (1–20 px), And the change is reflected immediately. Given the Circle/Ellipse tool is selected, When the user drags on the canvas, Then an ellipse is created with resizable bounding handles, And color and stroke width controls apply. Given the Rectangle tool is selected, When the user drags on the canvas, Then a rectangle is created with resizable handles, And color and stroke width controls apply. Given the Freehand tool is selected, When the user draws a continuous stroke, Then the path renders smoothly following the finger/stylus, And color and stroke width controls apply. Given the Text tool is selected, When the user taps the canvas and types a caption up to 140 characters, Then a text label appears, And the user can move, edit, and set color for the text, And the caption persists on export. Given any tool has created objects on the photo, When the user taps Undo repeatedly, Then the last 50 actions are reverted in order, And when Redo is tapped, Then reverted actions are reapplied in order until no more redo steps remain, And starting a new action clears the redo stack.
Low‑Latency Drawing and Gestures on Mid‑Range Mobile
Given a mid‑range mobile device (Android 11+/iOS 14+ with 4–6 GB RAM), When the user draws freehand strokes for 10 seconds, Then average input‑to‑ink latency is ≤ 50 ms and 95th percentile ≤ 80 ms, And frame rate during drawing is ≥ 55 FPS for 95% of frames. Given the user performs pinch‑to‑zoom and two‑finger pan gestures, When zooming and panning continuously for 10 seconds, Then frame rate is ≥ 55 FPS for 95% of frames, And zoom range supports 0.5× to 8×, And one‑finger drawing does not trigger pan. Given multiple shapes are present (≥ 20 annotations), When selecting and moving any single annotation, Then the move operation applies within 80 ms 95th percentile, And no visible tearing or artifacting occurs.
Offline Editing with Seamless Sync
Given the device loses connectivity while editing, When the user continues adding or modifying annotations on one or more photos, Then all changes are stored locally within 1 second of each action, And an offline indicator is shown. Given the browser tab is closed or the app is killed while offline, When the user reopens the same ticket within 24 hours, Then the last editing state for each photo is restored with no data loss. Given connectivity is restored, When the app detects network reachability, Then all locally stored edits are synced to the server within 15 seconds, And the working set is attached to the linked maintenance request, And sync status shows Success. Given the same photo was edited on two clients while offline, When both sets attempt to sync, Then the server applies last‑updated‑wins per photo with versioning, And the losing version is preserved as a prior version accessible from the ticket history.
Quick Switch Carousel Across Uploaded Photos
Given a user has uploaded multiple photos (up to 20) into the working set, When the user swipes left/right or taps a thumbnail in the carousel, Then the active photo switches within 300 ms at the 95th percentile, And the prior photo’s edits are auto‑saved. Given per‑photo zoom and tool selections, When switching between photos, Then each photo restores its last zoom level and active tool, And unsaved annotations on the previous photo are not lost. Given a photo has no annotations, When displayed in the carousel, Then its thumbnail shows a “no markup” state, And once annotated, Then a badge indicates it contains markups.
Export with Auto‑Stamp, EXIF Retention, and Email‑Optimized Size
Given one or more photos contain annotations and captions, When the user exports the set, Then each exported image includes an overlay auto‑stamp of date, time (with timezone), and unit identifier in the lower corner, And all user‑added captions remain visible. Given export settings are default, When images are generated, Then each JPEG is ≤ 1.5 MB and has a max longest edge of 1600 px, And visual quality is ≥ 80 JPEG quality or equivalent SSIM ≥ 0.95 relative to the canvas view. Given the source image contains EXIF metadata, When export completes, Then EXIF orientation, original capture timestamp, GPS (if present), and camera make/model are preserved in the output file, And no EXIF fields are stripped beyond thumbnail regeneration. Given an exported photo is downloaded and re‑opened, When inspecting metadata, Then the EXIF is intact and the visual overlay (stamps and annotations) is present and correctly aligned.
Attach Markup Set to FixFlow Ticket
Given a user selects Save to Ticket from the markup canvas tied to a maintenance request, When the action completes, Then the working set (all edited photos) is attached to the request as new attachments, And each attachment includes a thumbnail, the annotated image, and a link to the original. Given attachments are added, When viewing the ticket timeline, Then an entry shows the attachment batch with count and timestamps, And per‑photo captions are displayed in the attachment details. Given a vendor is notified via portal/email, When the vendor opens the ticket, Then the annotated images are visible with thumbnails, And downloads succeed within 2 seconds for each image under typical broadband (25 Mbps). Given the FixFlow events API is enabled, When attachments are created, Then an attachment_created event is emitted per image with ticketId, photoId, hasMarkup=true, and file sizes.
Pluggable Tool API for Blur/Redact
Given a developer tool implements the Tool API v1 (registerTool, onPointerDown/move/up, renderPreview, commit, serialize/deserialize), When it is registered at app startup, Then the tool appears in the toolbar with its icon and label, And can be toggled like native tools. Given the sample Blur/Redact plugin is enabled, When the user drags to define a region, Then a configurable blur or pixelation preview appears, And applying commit adds a non‑destructive layer that participates in undo/redo and export. Given an annotation created by a plugin, When Undo/Redo is used, Then the plugin annotation is reverted/applied in correct order alongside native annotations, And redo stack behavior matches native tools. Given a plugin is misbehaving (throws error), When used on the canvas, Then the canvas remains stable, the tool is disabled, and an error toast is shown, And no data loss occurs for existing annotations. Given exports include plugin annotations, When exporting, Then plugin-rendered effects are rasterized into the output image within the same performance thresholds (export batch completes ≤ 5 s for 10 images).
Email-safe Export & Image Optimization
"As a property manager, I want exported annotated images optimized for email while preserving EXIF so that vendors receive clear, lightweight files with trustworthy metadata."
Description

Implement an export pipeline that downscales and compresses annotated images to email-safe sizes while preserving EXIF and visual readability. Support export targets: attach to FixFlow ticket, generate shareable link, email attachment, and single PDF bundle with per-image captions. Provide target size presets with automatic selection based on channel, and maintain EXIF blocks and orientation while embedding visible overlays. Use Web Workers for client-side processing with progress feedback and resumable uploads; fall back to server-side processing when device resources are constrained. Ensure exports preserve annotation fidelity and are immediately available to approvals and vendor dispatch.

Acceptance Criteria
Email attachment: optimized annotated photos under provider limits
Given a user selects the "Email Attachment" export channel for N annotated images (N ≥ 1) When export is initiated Then the system applies the Email preset so that total attachment payload ≤ 19 MB by default (configurable 10–35 MB) and per-image ≤ 2 MB (configurable) And EXIF fields DateTimeOriginal, Make, Model, and GPS (if present) are preserved and Orientation is normalized to 1 And visible overlays (arrows, circles, blur, captions) are burned-in with positional error ≤ 1 px and caption text x-height ≥ 10 px at 100% image scale And the SSIM between the annotated pre-compression composite and the exported image is ≥ 0.96 And if the payload would exceed the limit at the smallest preset, the user is prompted to switch to Shareable Link or PDF instead and email export is blocked
Attach to FixFlow ticket: immediate availability to approvals and dispatch
Given a user selects "Attach to FixFlow Ticket" for M annotated images (M ≥ 1) When export and upload complete Then the optimized images appear as attachments on the ticket timeline and are visible in Approvals and Vendor Dispatch views within ≤ 2 seconds of upload completion And thumbnails are generated and displayed And EXIF fields (DateTimeOriginal, Make, Model, GPS if present) are preserved; Orientation is normalized to 1; overlays are burned-in with positional error ≤ 1 px And each attachment shows the user-provided caption in the ticket UI
Generate shareable link to optimized bundle
Given a user selects "Generate Shareable Link" for K annotated images (K ≥ 1) When export completes Then a URL is produced and shown/copied to the user And the link resolves to a gallery that previews each optimized image with overlays burned-in and per-image captions And users can download individual images and a ZIP bundle from the link And exported images use the Link preset (higher resolution than Email) while preserving EXIF and normalized orientation
Single PDF bundle with per-image captions and metadata stamps
Given a user selects "Single PDF Bundle" for P annotated images with optional captions and unit metadata present When export completes Then a single PDF is generated with one page per image using locale-appropriate page size (Letter or A4) and 12.7 mm margins And each page includes the annotated image with overlays burned-in, a visible footer auto-stamped with date, time, and unit metadata, and the user-provided caption And the PDF file size conforms to the selected channel preset (Email vs Link) And embedded images maintain ≥ 150 DPI effective resolution and the PDF opens in standard readers without errors or warnings
Automatic channel-specific size preset selection
Given a user selects an export channel (Ticket, Email, Shareable Link, PDF) When preflight runs Then a channel-specific preset is auto-selected according to default mapping (Ticket=Standard, Email=Email, Link=High, PDF=Email or High based on send method) And the UI displays the selected preset and predicted total payload size before export And users can override the preset within allowed options And the exported assets do not exceed the channel's payload constraints; if predicted to exceed, the system auto-selects the next smaller preset or prompts to switch channel And EXIF blocks and orientation are preserved in all presets
Client-side processing via Web Workers with fallback to server
Given the device supports Web Workers, has ≥ 2 logical cores, and ≥ 256 MB free memory When the user exports annotated images Then composition, downscaling, and compression run in Web Workers without blocking the UI thread (first input delay ≤ 100 ms) And a progress indicator shows per-image and overall progress, updating at least every 250 ms with estimated time remaining And if a worker crashes, memory is insufficient, or processing exceeds 60 seconds per 12 MP image, the system falls back to server-side processing and notifies the user And outputs from client and server paths are visually equivalent (SSIM difference ≤ 0.01) and preserve the same EXIF fields and orientation
Resumable uploads with offline and retry support
Given uploads are in progress When network connectivity drops or the app is backgrounded Then uploads resume from the last completed chunk upon reconnect or foreground without creating duplicate assets And chunk size is adaptive (128 KB–2 MB) with exponential backoff up to 5 retries per chunk And the user can pause/resume manually; progress and remaining time persist across in-app navigation And if the browser tab closes mid-upload, reopening the ticket within 24 hours resumes pending uploads automatically And after finalization, assets are immediately available in Approvals and Vendor Dispatch
Auto Metadata Stamp & EXIF Preservation
"As a property manager, I want date, time, and unit details auto-stamped on images while keeping EXIF intact so that evidence is standardized and auditable."
Description

Automatically overlay date, time, unit identifier, property name, reporter identity, and request ID onto each image at export, with selectable position and contrast-aware watermark styles. Ensure EXIF metadata—including original timestamps, orientation, and GPS when present—remains intact and is not stripped during processing. Validate and normalize timestamps against the property’s timezone, and record both device time and server-received time for auditability. Provide a toggle per image for including the visible stamp, and add the same metadata to the PDF bundle’s caption area. This standardizes evidence, speeds triage, and strengthens trust in the media’s provenance across FixFlow workflows.

Acceptance Criteria
Overlay Stamp Content, Position, and Contrast
Given a user has entered property name, unit identifier, reporter identity, and request ID and selected stamp position (top-left, top-right, bottom-left, bottom-right) and style (auto, light, dark) with stamp toggle ON When the image is exported Then the exported image visibly overlays: normalized date and time in the property’s timezone, property name, unit identifier, reporter identity, and request ID in the selected position And the overlay text is fully within image bounds with at least 8px padding from edges and is not clipped And when style is Auto, text and stroke/background adjust to achieve a contrast ratio of at least 4.5:1 against the underlying pixels And the overlay operation does not modify or remove EXIF metadata
EXIF Preservation Through Processing and Export
Given an input image containing EXIF fields DateTimeOriginal, CreateDate, Orientation, and GPSLatitude/GPSLongitude When the image is processed with any combination of overlay ON/OFF and exported in a format that supports EXIF (e.g., JPEG) Then the exported image retains all original EXIF fields and values, including DateTimeOriginal, CreateDate, Orientation, and GPSLatitude/GPSLongitude And the EXIF Orientation value in the output matches the input, and viewers that honor EXIF orientation render the photo correctly And no EXIF fields are stripped or rewritten by the export pipeline
Timezone Normalization and Dual Timestamps
Given a property configured with timezone America/New_York and a device that captured the photo at 2025-03-10T23:30:00-08:00 and the server received the photo at 2025-03-11T07:31:00Z When the image is exported with stamp ON Then the overlay shows the date and time normalized to the property’s timezone (2025-03-11 02:30 America/New_York) and includes the timezone abbreviation And the system persists both the device-captured timestamp and the server-received timestamp in the media record for auditability And EXIF timestamps (e.g., DateTimeOriginal) are not modified
Per-Image Visible Stamp Toggle
Given a set of images where the user disables the visible stamp for one image and enables it for another When exporting the set Then the image with stamp disabled is exported without any visible overlay while retaining all EXIF data And the image with stamp enabled contains the overlay as configured And the toggle state is applied per image and does not affect other images in the export
PDF Bundle Captions Mirror Metadata
Given multiple images exported to a PDF bundle with a configured property timezone When the PDF is generated Then each image in the PDF has a caption area that includes: normalized date and time in the property’s timezone, property name, unit identifier, reporter identity, and request ID And the caption values match the overlay values used for the corresponding image And captions are embedded as selectable text in the PDF (not rasterized)
Email-Optimized Export Preserves Quality and EXIF
Given a high-resolution JPEG (≥12 MP) with EXIF When exporting with the email-optimized setting Then the exported image file size is <= 1.5 MB while preserving all EXIF metadata And the longer image edge is at least 2000 px And visual similarity to the source is acceptable (SSIM ≥ 0.95 or PSNR ≥ 35 dB)
Graceful Handling of Timezone and Metadata Anomalies
Given a property with an invalid or deprecated timezone identifier or a photo missing EXIF timestamps When the user exports the image Then the system falls back to the server’s timezone for normalization and clearly labels the timezone used in the overlay And the export completes without error while logging a warning event with details of the anomaly And EXIF is not altered and no placeholder GPS or other metadata is introduced
Non-Destructive Editing & Version History
"As a property manager, I want edits to be non-destructive with version history so that I can revert mistakes and retain originals for documentation."
Description

Store originals and edited variants for each photo, maintaining a reversible edit stack (add/remove shapes, text, redactions) and snapshots at key milestones (first save, export, approval). Allow users to revert to any prior state and re-export without quality loss. Persist edit data as vector instructions separate from the bitmap to keep files lightweight until final render. Surface version labels and diffs within the FixFlow ticket media gallery, and restrict destructive operations to explicit “Flatten & Export” actions. This protects evidence integrity, supports compliance needs, and reduces accidental data loss during collaborative review.

Acceptance Criteria
Original Preservation and Reversible Edit Stack
Given a user uploads a photo to a ticket, Then the system stores the original file immutably and retains its checksum and EXIF. When the user adds annotations (arrows, circles, blur/redaction, text), Then each edit is persisted as an ordered vector instruction without modifying the original bitmap. When the user removes any prior edit from the stack, Then subsequent edits remain intact and the rendered result updates accordingly, with the original checksum unchanged. Then undo and redo operate across the full edit history both within the current session and after reload.
Automatic Snapshot Creation at Key Milestones
Given a photo receives its first saved edit, Then a read-only snapshot labeled "First Save" is created with actor, timestamp, and reference to the edit stack state. When a user exports a photo, Then a snapshot labeled "Export v{n}" is auto-created and versioned sequentially for that photo. When the ticket containing the photo is approved, Then a snapshot labeled "Approval" is auto-created for the current state. Then each snapshot is visible in the photo’s version panel and can be restored as the working state.
Revert to Any Prior State and Re-Export Without Quality Loss
Given a user selects any snapshot or specific edit stack index, When they click Revert, Then the working state matches the selected version exactly. When exporting from any reverted state multiple times with identical export settings, Then the produced image files are bit-for-bit identical and retain the original EXIF metadata. Then export renders at the original photo resolution unless the user explicitly selects an email-optimized size, and no cumulative quality loss occurs from repeated edit/revert/export cycles.
Version Labels and Diffs in Media Gallery
Given a photo with multiple versions, Then the media gallery displays a chronological list including label, creator, timestamp, and snapshot type (First Save, Export, Approval, Manual). When a user selects two versions, Then a diff view lists added/removed/modified annotations and text changes, and visual overlays highlight changed areas. Then users can filter versions by snapshot type and search by editor, and selecting a version previews it without committing a revert.
Explicit Flatten & Export Only for Destructive Outputs
Given a user initiates Flatten & Export, Then a confirmation modal explains the destructive nature and requires explicit confirmation to proceed. When Flatten & Export completes, Then the exported file contains rasterized annotations, while the system preserves the original photo and vector edit stack unchanged internally. Then role-based permissions restrict Flatten & Export to authorized roles, and the action is logged with actor, timestamp, affected version ID, and export settings.
Persist Edits as Vector Instructions Separate from Bitmap
Given an annotated photo is saved, Then annotation data is stored as vector instructions separate from the image binary, including precise geometry, style, and z-order. When the photo is opened on another device/session, Then all annotations remain fully editable with identical positions and properties. Then exactly one original bitmap is stored per photo until an export or flattened copy is generated, and no intermediary rasterized copies exist in storage.
Audit Trail and Compliance for Version History
Given any edit, snapshot, revert, export, or flatten action, Then an immutable audit log entry is recorded with actor, timestamp, action type, photo ID, and version reference. When viewing a version’s details, Then users can view the complete audit trail for that version, including who created it and any related exports. Then the audit trail is exportable (JSON/CSV) by authorized roles and cannot be altered or deleted by standard users.
Sensitive Data Blur & Redaction
"As a tenant or manager, I want to blur sensitive information in photos so that privacy is protected before sharing with vendors."
Description

Provide precise privacy tools including rectangle and freehand blur/pixelate brushes with adjustable strength and edge softness. Offer optional on-device suggestions for faces, screen text, and license plates to accelerate redaction, while keeping the user in control of what is obscured. Mark redactions as irreversible on flattened exports and clearly watermark “REDACTED” as needed, while retaining a secure original in version history with restricted access based on user role. Include keyboard shortcuts and touch affordances for fast operation. This protects tenant privacy and reduces risk when sharing images with vendors or across teams.

Acceptance Criteria
Manual Blur/Pixelate Tools With Adjustable Strength
Given a user with edit permissions has opened a photo in the Photo Markup editor When the user selects the rectangle or freehand tool Then the user can switch between Blur and Pixelate modes And the user can adjust brush size between 4–200 px And the user can adjust redaction strength on a 0–100 scale And the user can adjust edge softness on a 0–100 scale And strokes are applied only within the drawn region without affecting pixels outside the selection And the preview updates within 150 ms after each stroke on supported devices And undo/redo reverts/applies the last redaction operation precisely
On-Device Redaction Suggestions for Faces, Plates, and Screen Text
Given a photo that may contain faces, license plates, or screen text When the user taps “Suggestions” Then detection runs entirely on-device with no network requests And suggested regions are overlaid with labels and confidence values And no redactions are applied until the user confirms And the user can accept or reject suggestions individually or “Accept All” / “Reject All” And dismissed suggestions are removed from the overlay without altering the image And accepting a suggestion converts it into an editable redaction layer matching the current tool settings
Flattened Export With Irreversible Redactions and Watermark
Given a photo with one or more redactions has been edited When the user exports to a flattened image (JPEG/PNG/PDF) Then the exported file contains no editable layers that would allow reversing redactions And redacted pixels are permanently altered in the export And if the organization setting “Export Watermark: REDACTED” is enabled, a visible “REDACTED” watermark is added to the image (bottom-right, at least 3% of the shorter side in height, 60–80% opacity) And if the watermark setting is disabled, no watermark appears And the export operation records the export type, timestamp, user, and watermark setting in the audit log
Secure Original Retention and Role-Based Access
Given an original unredacted photo exists for a redacted export When users without the “View Original” permission attempt to access the original Then access is denied and only redacted versions are visible And share links provided to vendors expose only redacted versions And users with the “View Original” permission can view and restore the original from version history And all original-view events (user, timestamp, item) are recorded in the audit log And stored originals are encrypted at rest and retrievable only through authenticated requests
Keyboard Shortcuts and Touch Affordances for Fast Redaction
Given the Photo Markup editor is in focus on desktop When the user presses B, R, or F Then B toggles Blur/Pixelate mode, R selects Rectangle tool, F selects Freehand tool And [ / ] adjust brush size by 2 px (with Shift by 10 px) And - / = adjust strength by 5 (with Shift by 10) And Cmd/Ctrl+Z undoes and Cmd/Ctrl+Shift+Z redoes the last action And holding Space enables pan until released Given a touch device When the user uses two-finger pan and pinch Then the canvas pans and zooms smoothly And a long-press opens the tool radial or toolbar to switch tools and adjust size/strength And an in-app shortcut/help panel lists all shortcuts and touch gestures
Versioning and Auditability of Redaction Edits
Given a user saves changes in the Photo Markup editor When a save occurs Then a new version entry is created with version number, timestamp, user, and a summary of redaction operations And users can revert to any prior editable version (pre-export) without losing the immutable exported artifacts And when an export occurs, an immutable artifact record is created and linked to the source version And all save, revert, and export actions are captured in the audit trail and are filterable by item and user
Approval & Vendor Workflow Integration
"As a maintenance coordinator, I want annotated photos and edit history visible in the approval and dispatch workflow so that approvals are faster and accountability is clear."
Description

Display annotated images inline in the approval timeline and one-click approval modal, showing who made each edit and when. When an approval is issued, attach the latest exported set to the vendor work order and vendor portal, ensuring third parties can view annotations without requiring editor access. Emit events and webhooks indicating media readiness to downstream systems, and include media hashes to avoid duplicate uploads. Provide permission checks so only authorized roles can edit vs. view, and log all media views for audit. This tight integration shortens decision cycles and reduces repeat site visits.

Acceptance Criteria
Inline Annotated Images in Approval Timeline
Given a work order with annotated images and a signed-in approver, When the approval timeline loads, Then each image renders inline with overlays visible and displays the last editor's name and editedAt timestamp for each annotation group Given an image has zero annotations, When rendered in the timeline, Then it shows no overlay and no editor/timestamp badge Given a new annotation is saved on any image, When the timeline is visible, Then the updated image and metadata appear within 5 seconds without a full page reload Given a 150 ms latency and 1 Mbps connection, When loading the timeline, Then a low-res placeholder appears within 500 ms and the annotated image completes within 2 seconds
One-Click Approval Modal Shows Annotation History
Given the one-click approval modal is opened on a work order with annotated images, When images render, Then the modal shows the latest exported preview per image with badges for the last 3 edits including editor name and timestamp Given the user selects "View edit history" on any image, When the history panel opens, Then it lists all edits in reverse chronological order with editorId, displayName, timestamp (UTC), and action type (arrow|blur|circle|caption) Given an image has more than 3 edits, When the modal first opens, Then it shows a "+X more" indicator with an accessible link to the full history
Vendor Work Order Receives Latest Exported Annotated Set
Given an approval is issued on a work order with annotated images, When the approval is confirmed, Then the system attaches the latest completed export set to the vendor work order and records the exportVersionId Given an export is in progress at the time of approval, When export completes, Then the vendor work order is updated within 60 seconds with the new set and a "media_ready" activity is added Given files are attached, Then each file name includes workOrderId, exportVersionId, and sequence number, image size is <= 1.5 MB, annotations are flattened, and original EXIF is preserved
Vendor Portal Displays Annotations Without Editor Access
Given a vendor opens the vendor portal via a valid time-bound token, When viewing the work order media, Then all images display with annotations baked in, no editing controls are present, and no FixFlow account login is required Given the vendor token is expired or invalid, When the link is opened, Then the portal returns HTTP 401 and no media content is served Given a 3G Fast network profile, When loading the media gallery, Then the first annotated image is visible within 3 seconds
Media Readiness Webhook With Content Hashes
Given an export completes or changes for an approved work order, When media becomes ready, Then a webhook event "media.ready" is sent within 10 seconds containing approvalId, workOrderId, exportVersionId, and an array of files with url, mimeType, sizeBytes, sha256, editedAt, editorId Given webhook delivery fails with 5xx or timeout, When retry policy executes, Then deliveries are retried with exponential backoff at least 6 times over 30 minutes and remain idempotent via an "Idempotency-Key" header Given a valid webhook secret is configured, When an event is delivered, Then the request includes an HMAC-SHA256 signature header and a payload timestamp; receivers can verify within a 5-minute skew window
Duplicate Upload Prevention Using Media Hashes
Given a vendor work order already has an attachment with the same sha256, When a new export set is processed, Then the system does not re-upload the duplicate file and records an idempotent skip in the activity log Given two images differ only by file name, When hashes are computed, Then they produce different sha256 only if bitmap content differs; metadata-only changes do not alter the flattened annotated image hash Given duplicate files are skipped, When the vendor portal displays media, Then no duplicate thumbnails appear
Role-Based Edit vs View Permissions and Audit Logging
Given a user with permission "Media.Edit", When viewing images in the timeline or approval modal, Then annotation controls are enabled; Given a user without "Media.Edit", Then controls are hidden and a "View only" label is shown Given any authenticated user or vendor-token viewer opens or downloads a media item, When the media bytes are served, Then an audit record is written with viewerId or tokenId, role, mediaId, context (timeline|modal|vendor_portal|api), timestamp (UTC), IP, and userAgent Given a user lacks "Media.View", When they attempt to access media, Then the response is HTTP 403, zero bytes are returned, and an audit record with outcome=denied is stored

GeoScore

Dynamically weights vendor rankings by real‑time travel time, service‑area familiarity, traffic conditions, and multi‑stop routes. Prioritizes the vendor most likely to arrive fastest and on time, cutting tenant wait, reducing schedule slip, and improving first‑contact resolution.

Requirements

Real-time Traffic Ingestion
"As a dispatcher, I want live, traffic-adjusted travel times so that I can assign the vendor who can arrive fastest right now."
Description

Integrates GeoScore with live traffic and incident data providers to compute up-to-the-minute drive times from a vendor’s current or last-known location to the job site. Normalizes and caches travel-time data, respects provider rate limits, updates ETAs on a configurable cadence, and falls back to historical time-of-day averages during outages. Handles geocoding, time zones, and edge cases like gated communities and high-rises with access delays. Exposes a reliable travel-time signal to FixFlow’s triage and one-click approval flows for immediate, accurate vendor ranking.

Acceptance Criteria
Live ETA From Fresh Vendor Location
- Given a vendor has a current GPS coordinate with a last_seen timestamp ≤ 5 minutes old, When an ETA is requested to a geocoded job destination, Then the system returns travel_time_live_seconds > 0 based on a live traffic provider. - Given a cache miss, When the live provider responds successfully, Then p95 time to first ETA ≤ 2.0 seconds and the response includes provider="primary" and last_updated_utc. - Given a cache hit for the same origin/destination within TTL, When an ETA is requested, Then p95 response time ≤ 300 ms and provider="cache_primary". - Then the response includes arrival_time_local (ISO 8601), computed from UTC using the job site time zone.
Geocoding and Time-Zone Normalization
- Given a valid street address for the job, When geocoding is performed, Then latitude/longitude are resolved and stored with geocode_quality ∈ {rooftop, interpolated}, and failures are flagged with error_code=GEOCODE_FAILED. - Given geocoding succeeds, When vendor origin and job destination are known, Then ETA computation proceeds; otherwise Then ETA response has status="unavailable" and reason ∈ {GEOCODE_FAILED, ORIGIN_UNKNOWN}. - Given any provider timestamp, When data is stored or exposed, Then timestamps are normalized to UTC and include local_tz for the job site. - Given an address with unit/suite, When geocoding returns building centroid, Then the unit is preserved in metadata without blocking ETA.
Normalization and Caching Policy
- Given live travel-time responses from one or more providers, When normalizing, Then travel_time is stored in seconds and distance in meters with provider_source, provider_version, and confidence ∈ [0,1]. - Given a computed ETA, When caching, Then cache TTL = max(min(config.cadence_seconds, 300), 30) and the cache key includes origin_vendor_id, origin_location_hash, destination_job_id, provider_source, and cadence bucket. - Given steady traffic and configured cadence, When the system has warmed up for 5 minutes, Then cache hit ratio ≥ 80% for repeated ETA requests to the same pairs. - Given a cached entry older than 2× cadence, When an ETA is requested, Then the entry is not served as live and a refresh or fallback is triggered.
Configurable Update Cadence and Rate-Limit Compliance
- Given org config cadence_seconds ∈ [30,300], When a job is pending assignment, Then ETAs refresh every cadence_seconds ± 5 seconds while the vendor origin remains unchanged. - Given a vendor origin update (movement > 100 meters or new timestamp), When last_fetch_age ≥ 5 seconds, Then trigger an immediate ETA refresh. - Given provider rate_limit_qps and rate_limit_per_minute, When issuing requests, Then observed request rate ≤ configured limits with ≥ 99.9% compliance over 1-hour windows. - Given the provider returns HTTP 429 or timeouts, When retrying, Then use exponential backoff starting at 5 seconds, factor 2, capped at 10 minutes, and mark ETA status="degraded" during backoff.
Fallback to Historical Averages During Outage
- Given 3 consecutive live-provider failures or provider unreachable ≥ 30 seconds, When an ETA is requested, Then return historical_average_seconds based on weekday and 15-minute time bucket and set source="historical" and confidence ≤ 0.6. - Given fallback mode is active, When the live provider recovers, Then switch back to live ETAs within one cadence interval and clear degraded status. - Given historical averages are used, When later compared to live values for the same window, Then the median absolute error ≤ 20% over a 14-day rolling window. - Then all fallback ETAs include last_fallback_updated_utc and reason ∈ {PROVIDER_OUTAGE, RATE_LIMIT_BACKOFF, STALE_CACHE}.
Access-Delay Adjustments for Gated/High-Rise
- Given a job has access_delay_minutes specified, When computing ETA, Then arrival_time_local = drive_time_seconds + access_delay_seconds and components.access_delay_seconds is populated and non-negative. - Given no explicit access delay, When job metadata indicates gated_community=true or building_type=high_rise, Then apply default_access_delay_minutes from org config (default 5 minutes) and flag access_delay_source ∈ {explicit, default_rule}. - Given access delay rules change, When recomputing ETA, Then the exposed components reflect the new delay within one cadence interval.
Travel-Time Signal Exposed to Triage and Approvals
- Given a vendor and job pair, When triage or one-click approval requests the travel-time signal, Then the API returns {live_seconds|historical_seconds, source, confidence, last_updated_utc, arrival_time_local, components:{drive_time_seconds, access_delay_seconds}} with HTTP 200. - Given the value is served from cache, When requested by UI services, Then p95 API latency ≤ 200 ms and availability ≥ 99.9% over 30 days. - Given multiple candidate vendors, When ranked by travel_time, Then vendors are ordered by ascending ETA; ties are stable and deterministic by vendor_id. - Given invalid inputs (missing geocode or origin), When requesting the signal, Then HTTP 200 with status="unavailable" and reason populated; no partial or null fields are returned.
Service-Area Familiarity Scoring
"As an operations lead, I want GeoScore to account for vendors’ neighborhood familiarity so that assignments favor crews who know the area and building quirks."
Description

Calculates a dynamic familiarity score per vendor across geographies using past work orders, completion outcomes, building access notes, vendor-declared service zones, and tenant satisfaction. Implements geospatial bucketing (e.g., geohash/ZIP/polygon), recency decay, and exclusion zones. Ingests performance signals like first-contact resolution and repeat-visit rate to reward local expertise. Produces a normalized factor that blends into GeoScore to prefer vendors who know the neighborhood and property quirks, reducing on-site delays and increasing first-time fix likelihood.

Acceptance Criteria
Compute Familiarity by Geospatial Bucket
- Given a service address and bucket_strategy="geohash-6", When computing a vendor's familiarity, Then only historical events whose geohash-6 equals the address bucket are included and events outside are excluded. - Given bucket_strategy="ZIP", When computing familiarity, Then only events with a ZIP equal to the service address ZIP are included. - Given bucket_strategy="polygon", When computing familiarity, Then only events whose coordinates fall within the polygon are included; events outside are excluded. - Given identical inputs (vendor_id, address, as_of, bucket_strategy, config_version), When computing twice, Then the familiarity score and breakdown are identical (deterministic). - Given a computed score, When returning results, Then the response includes bucket_type and bucket_id used for the calculation.
Time-Weighted Recency Decay
- Given events with ages a_i days and half_life_days=H, When computing weights, Then each event weight w_i = exp(-ln(2) * a_i / H) within ±1% numerical tolerance. - Given half_life_days is changed from H1 to H2, When recomputing, Then newer events receive relatively higher weights under the smaller half-life (monotonic with respect to H). - Given recency_decay_enabled=false, When computing, Then all included events have equal weight of 1. - Given events older than max_age_days, When computing, Then those events are excluded from the score.
Performance Signals Influence
- Given two vendors identical except first-contact resolution (FCR), When computing in the same bucket, Then the vendor with higher FCR receives a higher familiarity score (monotonic with respect to FCR weight > 0). - Given two vendors identical except repeat-visit rate, When computing, Then the vendor with higher repeat-visit rate receives a lower familiarity score (monotonic with respect to repeat-visit penalty > 0). - Given tenant satisfaction (CSAT) differs while other inputs are equal, When computing, Then higher CSAT increases familiarity and lower CSAT decreases it, proportional to configured csat_weight. - Given validated building access notes exist for the target bucket/building for a vendor, When computing, Then a positive familiarity boost is applied up to a configured cap; absence of notes yields no boost. - Given completion outcomes include failures/call-backs, When computing, Then penalties are applied per configured outcome_penalties.
Service and Exclusion Zones Application
- Given the service address lies within a vendor-declared service zone and the vendor has no local history, When computing, Then familiarity >= service_zone_floor (default 0.30) and the response includes reason_code="service_zone_floor". - Given the service address lies within a vendor exclusion zone, When computing, Then familiarity = 0.0 and the response includes reason_code="exclusion_zone"; vendor is marked ineligible for familiarity contribution. - Given overlapping service and exclusion zones cover the address, When computing, Then the exclusion zone takes precedence and familiarity = 0.0. - Given the service address lies outside all declared service zones and the vendor has no history, When computing, Then familiarity <= out_of_zone_cap (default 0.10) and the response includes reason_code="out_of_zone_cap".
Cold-Start and Sparse Data Fallbacks
- Given fewer than min_events_threshold (default 20) weighted events exist in the target bucket, When computing, Then the system falls back in order: geohash-6 -> geohash-5 -> geohash-4 -> ZIP -> metro -> global until the threshold is met or exhausted. - Given fallbacks are exhausted and the address is inside a vendor service zone, When computing, Then familiarity = max(service_zone_floor, global_baseline) and response includes fallback_level used and reason_code. - Given fallbacks are exhausted and the address is outside vendor service zones, When computing, Then familiarity = min(out_of_zone_cap, global_baseline) and response includes fallback_level used and reason_code. - Given a result was produced via fallback, When returning results, Then the response includes fallback_level and effective_sample_size used for the score.
Normalization and Output Contract
- Given any valid inputs, When computing, Then familiarity is a normalized float in [0.0, 1.0] inclusive. - Given positive-signal increases (e.g., FCR, CSAT) with all else equal, When computing, Then the familiarity score does not decrease (monotonicity holds). - Given identical inputs (including as_of and config_version), When computing repeatedly, Then the score and per-factor contributions are identical (idempotent). - Given a request, When returning results, Then the payload includes: vendor_id, area_id (bucket_id), bucket_type, familiarity_score, per_factor_contributions, effective_sample_size, as_of, model_version, config_version, and reason_code if applicable.
Determinism, Versioning, and Performance
- Given identical inputs including as_of timestamp and configuration, When computing, Then outputs (score and breakdown) are bit-for-bit identical across runs and nodes. - Given a request without an explicit as_of, When computing, Then the system uses the current time and echoes the resolved as_of in the response. - Given model or config changes, When computing, Then model_version and config_version are included and differ from prior versions, enabling auditability. - Given a batch request for 100 vendors for the same address, When computing, Then p95 latency <= 500ms and p99 <= 800ms; for a single vendor/address, p95 <= 75ms and p99 <= 150ms under nominal load.
Multi-Stop Route Optimization
"As a scheduler, I want GeoScore to consider vendors’ existing stops and time windows so that new jobs slot into routes without causing delays."
Description

Evaluates each candidate vendor’s current schedule and planned route, calculating the insertion cost of the new job with respect to service durations, travel times, and tenant time windows. Supports soft and hard constraints, buffers for parking/elevator time, and re-optimization when urgent jobs arrive. Outputs predicted start/arrival times and schedule ripple effects to ensure assignments are feasible and minimize downstream delays. Integrates directly into FixFlow’s smart triage so that approvals trigger practical, on-time routes rather than isolated ETA estimates.

Acceptance Criteria
Insert New Job Into Midday Route
Given a vendor route for today with 6 scheduled stops (A–F), configured service durations, and real-time travel times And a new approved job with a 2-hour soft window and 30-minute duration When the system evaluates all possible insertion positions across that vendor's route Then it selects the insertion index that minimizes total schedule delay while satisfying all hard constraints And recalculates ETAs for all impacted stops in ≤5 seconds for routes up to 20 stops And the predicted arrival for the new job is within ±2 minutes of the route engine's simulation output
Enforce Tenant Hard Time Window
Given a job with a hard arrival window 14:00–16:00 local time When optimization proposes an insertion into any vendor's route Then the predicted arrival for that job lies within 14:00–16:00 inclusive And no existing stop's hard window is violated And any infeasible vendor/position combinations are discarded with a machine-readable infeasibility reason
Soft Constraints With Penalties
Given soft preferences (e.g., preferred before 12:00, avoid 12:00–13:00) with defined penalty weights And at least one feasible placement exists both inside and outside the preferred period When the system optimizes across placements Then it chooses the placement with the lowest total cost (travel + delay + soft penalties) And the selected plan's soft-penalty score is ≤ any alternative feasible plan's score by ≥1 unit And violating only soft constraints never blocks assignment
Apply Location-Specific Buffers
Given a stop marked High-Rise with parking buffer 10 minutes and elevator buffer 5 minutes When the stop is scheduled within a route Then its effective service time includes +15 minutes buffer And all downstream ETAs reflect the buffer And removing the buffers reduces the predicted arrival times by exactly 15 minutes
Urgent Job Triggers Bounded Re-Optimization
Given an active vendor schedule and an incoming job flagged Priority P1 with ASAP window and 45-minute duration When the system receives the job Then it re-optimizes affected vendor routes within ≤7 seconds across ≤50 candidate vendors And it limits reassignment to ≤3 non-urgent stops per affected route unless needed to prevent a hard-window violation And it produces a change set listing every affected stop with old/new ETA and vendor
Ripple Effects Are Computed and Exposed
Given any optimization run that changes at least one ETA When results are returned Then each stop in each affected route includes predicted start time, arrival time, service start slack, and delta from previous plan in minutes And deltas are accurate to ±1 minute And results are ordered in execution sequence with no negative slack values
Triage-to-Dispatch Feasibility Check
Given a triage-approved job awaiting dispatch When Approve and Assign is triggered Then the system uses multi-stop route optimization to select a vendor and insertion that yields a feasible schedule with no hard-window violations And the approval action is blocked with a clear reason if no feasible placement exists And the assigned vendor and ETA are published to notifications within ≤2 seconds of assignment
Configurable Weighting Engine
"As a portfolio admin, I want to tune the weighting of travel time, familiarity, and reliability so that vendor selection aligns with our operational goals and SLAs."
Description

Provides an admin UI and API to adjust how GeoScore blends inputs such as travel time, familiarity, reliability, vendor rating, cost ceilings, and urgency. Supports per-portfolio and per-category profiles with guardrails, defaults, and versioning. Includes a sandbox mode to simulate score changes on historical jobs and an A/B framework to compare weighting strategies before broad rollout. Ensures the final ranking reflects each landlord’s goals while maintaining platform-wide minimum standards and vendor preferences.

Acceptance Criteria
Admin UI: Adjust and Save Weights per Portfolio and Category
Given an admin with Manage Weights permission is editing Portfolio P and Category C When they set weights for travel_time, familiarity, reliability, vendor_rating, cost_ceiling_sensitivity, and urgency within 0.00–1.00 and click Save Then the system validates each value is numeric and within range, normalizes the sum to 1.00 ± 0.01, creates a new profile version V with timestamp and optional comment, sets V Active for P/C, and displays a success confirmation within 1 second Given an Active version V for P/C When a new job for P/C is scored Then the scoring service uses weights from V and persists version_id=V on the job record Given the admin clicks Cancel while editing weights When no Save action occurs Then no new version is created and the Active version remains unchanged Given any invalid input (non-numeric, out-of-range, or sum outside tolerance) When Save is clicked Then saving is blocked and field-level error messages identify the offending fields
API: Manage Weight Profiles with Auth and Validation
Given a valid OAuth token with scope weights:write When POST /weights/profiles is called with portfolio_id, category, weights map, and comment Then the API responds 201 with version_id, normalized weights, active=true/false, and audit metadata Given a token lacking required scope or a user without Manage Weights permission When calling POST/PATCH endpoints Then the API responds 403 Forbidden with an error code and no changes are persisted Given GET /weights/profiles?portfolio_id=P&category=C When the request is authorized Then the API returns the Active version for P/C and the latest 5 prior versions with metadata Given a payload with missing required fields or weights violating guardrails When POST or PATCH is called Then the API responds 422 Unprocessable Entity with a machine-readable list of validation errors
Guardrails: Enforce Platform Minimums and Vendor Preferences
Given platform-configured minimum thresholds for reliability and urgency weights and a max for cost_ceiling_sensitivity When an admin attempts to save weights outside these thresholds Then the save is rejected with explicit messages indicating which thresholds were violated Given vendor preferences include Preferred and Excluded flags When a ranking is computed Then Excluded vendors are omitted and Preferred vendors receive the platform preference adjustment after weight aggregation Given a job has an enforced cost ceiling and an estimate for vendor V exceeds the ceiling When computing final ranking Then V is demoted or excluded per policy and this behavior cannot be overridden by weight configuration Given any weight is negative, NaN, or infinity When saving a profile via UI or API Then the operation fails validation and is not persisted
Defaults and Fallback Resolution
Given a scoring request for Portfolio P and Category C When no profile exists for P/C Then the system resolves to P/* (portfolio default), else */* (global default), else the system baseline profile, and records the resolved profile_id on the job Given profiles exist at multiple specificity levels When profiles are resolved Then the most specific profile (P/C) takes precedence over P/* which takes precedence over */* Given a job used a fallback profile due to missing P/C When an admin later creates a P/C profile Then subsequent jobs for P/C use the new profile while previously scored jobs retain their recorded profile_id
Versioning and Auditability
Given an existing Active version V1 for P/C When an admin saves changes Then a new immutable version V2 is created with diff metadata and actor identity; V2 becomes Active and V1 remains accessible as history Given a job J was scored with V1 before V2 activation When V2 becomes Active Then J retains V1 as its scoring version and is not automatically re-scored Given an admin selects Rollback to a prior version Vk When they confirm Then a new version Vr is created by copying Vk’s weights, Vr becomes Active, and the audit log records actor, reason, and diff Given GET /weights/versions/{version_id}/diff When requested for two version_ids Then the API returns a field-by-field delta of weights and metadata
Sandbox Simulation of Historical Jobs
Given an admin selects a date range, Portfolio(s), Category(ies), sample size N<=500, and a candidate profile Pc When they run Simulation Then the system computes GeoScore and rankings for the sampled historical jobs using Pc and returns per-job before/after top-3 vendors, ETA deltas, and the % of jobs with a different top-1 vendor Given Simulation is running When no errors occur Then it completes within 60 seconds for N<=500, shows progress, and allows CSV export of results Given Simulation is executed When results are produced Then no live dispatch data is modified, no vendor is notified, and no Active profiles are changed Given the selected period yields <50 matching historical jobs When Simulation runs Then the UI warns about low sample size and proceeds with available jobs
A/B Experimentation for Weight Strategies
Given an experiment E with Strategy A (current Active) and Strategy B (candidate), target Portfolio P and Category C, split 80/20, duration 14 days, and success metrics (e.g., on-time-arrival rate) When E is started Then incoming eligible jobs are randomly assigned per split, the assignment is persisted, and GeoScore uses the assigned strategy to rank vendors Given safety constraints are configured (max sample 500 jobs, kill rule if B’s metric underperforms A by >5% after >=50 jobs) When the kill rule is triggered or a manual stop occurs Then new jobs revert to Strategy A and the experiment status becomes Stopped; historical assignments remain for analysis Given jobs are re-dispatched or involve multi-stop routing When assignments exist Then each job retains its original variant (A or B) across re-dispatches and stops Given the experiment is running When viewing the dashboard Then live counts, lift with 95% CI, and a final exportable report are displayed without exposing PII
SLA-aware ETA Prediction
"As a property manager, I want a prediction of whether an appointment will be on time so that I can avoid committing to windows we are unlikely to meet."
Description

Combines live travel times, vendor route feasibility, job prep buffers, and appointment windows to estimate the probability of on-time arrival. Flags high-risk assignments, recommends alternate windows or vendors, and enforces configurable disqualification thresholds for critical SLAs. Surfaces risk scores and expected lateness in FixFlow’s approval UI and tenant notifications to avoid overpromising and to proactively reschedule when necessary.

Acceptance Criteria
On-Time Probability Computation for Scheduled Appointment
Given a job with appointment window [start,end], vendor active route with committed stops (each with serviceDurationMinutes), vendor current/last-stop location, live travel times, and jobPrepBufferMinutes When the ETA engine computes arrival Then it returns onTimeProbability in range 0.00–1.00 (rounded to 2 decimals), estimatedArrival in the tenant’s timezone, and expectedLatenessMinutes = E[max(0, arrival - end)] rounded to whole minutes And multi-stop route feasibility is respected by inserting the job after the last pre-committed stop and summing travel + service durations And in benchmark tests against a reference model, onTimeProbability absolute error ≤ 0.02 and expectedLatenessMinutes error ≤ 3 minutes for ≥ 95% of test cases And when traffic inputs change, recomputation occurs within 5 minutes; cache TTL ≤ 60 seconds
High-Risk Assignment Flagging
Given configured riskThreshold per SLA type When onTimeProbability < riskThreshold for the selected vendor and window Then the assignment is labeled High Risk with at least one reason code from {TravelTime, RouteFeasibility, PrepBuffer, WindowTight} And the Approve action requires explicit confirmation with a risk warning And when onTimeProbability ≥ riskThreshold, no High Risk label is shown
Alternate Window/Vendor Recommendations for At-Risk Jobs
Given an assignment labeled High Risk When generating alternatives Then up to 3 options are returned that each satisfy onTimeProbability ≥ riskThreshold, sorted by onTimeProbability desc, then travelTimeMinutes asc, then vendorRank asc And each option includes vendorId, proposedWindowStart, proposedWindowEnd, onTimeProbability, and travelTimeMinutes And if no option meets the threshold, return the top 3 closest options labeled Below Threshold And recommendation generation completes within 2 seconds for the 95th percentile of cases
Disqualification Threshold Enforcement for Critical SLAs
Given a critical SLA with disqualifyThreshold and maxExpectedLatenessMinutes When onTimeProbability < disqualifyThreshold OR expectedLatenessMinutes > maxExpectedLatenessMinutes Then primary approval is disabled and an Override action is shown And only users with role ∈ {Manager, Admin} may override after entering a justification ≥ 15 characters And the system writes an audit record with userId, timestamp, onTimeProbability, expectedLatenessMinutes, thresholds, and justification And when constraints are satisfied, approval proceeds with no override
Risk Score and Lateness Display in Approval UI
Given an assignment opened in the Approval UI When the data loads Then display onTimeProbability as a percentage (0–100%), expectedLatenessMinutes (or “—” if 0), and a risk badge with colors: Green (≥ riskThreshold), Amber (< riskThreshold and ≥ disqualifyThreshold), Red (< disqualifyThreshold OR expectedLatenessMinutes > maxExpectedLatenessMinutes) And the view updates within 10 seconds of a backend recomputation and shows “Updated at <local timestamp>” And all displayed elements have accessible labels and color contrast meeting WCAG AA
Tenant Notification Content and Timing for ETA Risk
Given an appointment confirmation event When sending tenant notifications Then include appointment window, non-absolute ETA language (e.g., “estimated between 2:00–4:00 PM”), and no phrasing implying a guaranteed exact time And if onTimeProbability drops below riskThreshold by ≥ 0.10 absolute for ≥ 10 minutes before window start, send a proactive notice within 10 minutes with a reschedule link and current expectedLatenessMinutes if > 10 And notifications track delivery status (Sent, Bounced, Read) for auditability
Proactive Reschedule and Vendor Swap Automation
Given a confirmed appointment where onTimeProbability falls below riskThreshold by ≥ 0.15 absolute for ≥ 15 consecutive minutes When at least one alternative vendor/window meets disqualifyThreshold and maxExpectedLatenessMinutes Then create a reschedule recommendation with up to 3 options (vendor/window) and propose a vendor swap that increases onTimeProbability by ≥ 0.10 absolute And if auto-reschedule is enabled for the SLA type, after 30 minutes without landlord response, reassign to the top option and notify tenant and vendor And log all actions with before/after state including vendorId, window, probabilities, and timestamps
Decision Explainability & Audit Log
"As a compliance manager, I want an audit trail that explains why a vendor was chosen so that we can defend decisions and continuously improve the model."
Description

Captures and stores the full scoring artifact for each assignment, including inputs, normalized factors, weights, intermediate calculations, final score, selected vendor, and top alternatives with reason codes. Provides a human-readable explanation pane in the work order timeline and secure export for compliance reviews. Applies retention policies and redacts PII where required. Enables rapid debugging of unexpected outcomes and continuous improvement of GeoScore’s inputs and weights.

Acceptance Criteria
Full Scoring Artifact Captured per Assignment
Given a work order triggers a GeoScore vendor assignment decision When the decision is finalized Then the system persists a scoring artifact linked to the work order ID and a decision correlation ID And the artifact includes: raw inputs, normalized factors, factor weights, intermediate calculations, final vendor scores, selected vendor, and the top 3 alternative vendors with reason codes And the artifact write completes within 2 seconds of the decision timestamp And a success event is recorded in the audit log with a UTC ISO 8601 timestamp And if the initial write fails, the system retries up to 3 times with exponential backoff and enqueues to a durable queue without blocking decision delivery And any write failure emits an operational alert
Timeline Explanation Pane (Human-Readable)
Given a user with WorkOrder.Read permission views the work order timeline When the timeline includes a GeoScore decision Then an explanation pane renders a human-readable summary including: selected vendor, top contributing factors with weights, decision time, and top 3 alternatives with brief reason codes And PII fields (e.g., tenant name, phone, email) are redacted unless the user has PII.View permission And a link to View Raw Scoring Artifact appears only for users with Decision.Debug permission And the pane achieves p95 render time ≤ 400 ms on a 3G Fast network profile And the content meets WCAG 2.1 AA for contrast and screen-reader labels
Secure Compliance Export
Given an organization admin with Export.Compliance permission When they request an export for a specific date range and portfolio Then the system generates an export of scoring artifacts and access audit events in JSONL and CSV using a published schema version And the export applies redaction rules and excludes PII unless PII.View is also granted And the export is encrypted in transit (TLS 1.2+) and at rest; the downloadable file is AES-256 encrypted and accompanied by a SHA-256 checksum And the export scope is restricted to the requester’s tenant; cross-tenant records are excluded And the export request, completion, and downloader identity are audit-logged And for up to 100,000 decisions the export completes within 10 minutes p95
Retention and PII Redaction Policy Enforcement
Given an organization retention policy of 24 months and jurisdictional redaction rules configured When the daily retention job runs Then artifacts older than 24 months without legal hold are irreversibly purged and the purge is audit-logged with counts And artifacts under legal hold are retained until the hold is released And redaction rules are applied at write time and on export so that PII fields are stored and delivered in redacted form unless PII.View is granted And deletions propagate to all replicas and search indexes within 24 hours And any attempt to read a purged artifact returns HTTP 404
Model Versioning and Reproducibility
Given a specific scoring model and weight set are active When a decision is logged Then the artifact records model version, weight set version, route/traffic data snapshot identifiers, and the normalization formula version And invoking the reproduce endpoint with the artifact re-computes vendor scores that match stored final scores within ±0.01 and yields the same selected vendor And any drift beyond tolerance raises an alert and is flagged in the artifact
Access Control and Tenant Isolation
Given a user from Tenant A When they attempt to access scoring artifacts Then only Tenant A’s artifacts are returned; attempts to access other tenants’ artifacts return HTTP 403 and are audit-logged And all artifact reads record user ID, role, tenant ID, and timestamp in the access audit log And sensitive fields are masked unless the user has Decision.Debug or PII.View, as applicable And support users with Support.ReadOnly may access via approved impersonation flow with a required ticket ID recorded
Performance and Reliability Impact
Given production traffic at 200 decisions per minute with logging enabled When GeoScore makes assignment decisions Then decision p95 latency increases by no more than 30 ms compared to logging disabled And logging write p99 success rate is ≥ 99.9% over a rolling 24-hour window And if the primary log store is unavailable, writes are queued and flushed within 2 minutes of recovery with no decision loss And write operations are non-blocking relative to decision delivery

ScopeMatch

Analyzes tenant photos and triage tags to detect required skills, certifications, tools, and parts (e.g., gas appliance, refrigerant handling, ladder height). Elevates vendors verified for the exact scope so the right tech shows up once—fewer callbacks, fewer repeat visits, and lower total cost.

Requirements

Photo & Tag Ingestion Pipeline
"As a dispatcher, I want tenant photos and triage tags to be reliably captured and normalized so that ScopeMatch can infer the required scope with high accuracy."
Description

Implement a secure, mobile-first intake pipeline that normalizes tenant-submitted photos and triage tags for downstream analysis. Capabilities include multi-image upload, auto-rotate and resize, de-duplication, blur/low-light quality checks with user prompts, EXIF/time/location capture (when available), and metadata enrichment (ticket ID, unit, tenant, device). Triage tags are validated against a controlled taxonomy and mapped to canonical problem categories. All artifacts are stored in encrypted object storage with links to the maintenance ticket. This pipeline guarantees consistent, high-quality inputs for ScopeMatch while integrating seamlessly with FixFlow’s existing photo-first intake and ticketing flows.

Acceptance Criteria
Mobile Multi-Image Upload & Normalization
Given an authenticated tenant on mobile web intake with an open maintenance ticket When they upload a batch of 1–10 photos in supported formats (JPEG, HEIC, PNG) Then the pipeline accepts the batch and returns a 201 response with artifact IDs linked to the ticket Given uploaded images with EXIF Orientation data When normalization runs Then images are auto-rotated to upright orientation and aspect ratio is preserved Given images exceeding the configured max dimension When normalization runs Then the long edge is resized to the configured maximum without upscaling smaller images Given successful normalization When outputs are generated Then a thumbnail and a preview rendition are created per configured sizes in addition to the analysis rendition Given server-side processing of images ≤10 MB When normalization runs under normal load Then p95 normalization latency per image is ≤10 seconds Given an unsupported file type or a file exceeding the configured size limit When the upload is attempted Then the user receives a validation error with clear guidance and no artifact is stored
Blur/Low-Light Quality Check with Retake Prompt
Given an uploaded photo with a blur score above the configured threshold When the quality check runs Then the user is prompted to retake or continue and the artifact is flagged quality_blur=true if continued Given an uploaded photo with average luminance below the configured threshold When the quality check runs Then the user is prompted to enable flash or retake and the artifact is flagged quality_low_light=true if continued Given the user has been prompted twice for the same photo due to quality issues When they choose to continue anyway Then the upload proceeds without further blocking and the override is captured in metadata Given a photo passes both blur and low-light thresholds When the quality check runs Then no prompt is shown and the artifact is marked quality_ok=true Given device/browser limitations that prevent pre-upload checks When the server-side quality check flags issues post-upload Then the user is shown a non-blocking message recommending a retake and the artifact is annotated accordingly
EXIF, Time, Location Capture & Permissions
Given an uploaded image containing EXIF metadata When ingestion runs Then capture and store original_exif_timestamp, orientation, camera_make/model, and, if present, geo_lat/geo_lon Given EXIF geolocation is present and the user has opted in to share location When ingestion runs Then geo_lat/geo_lon are stored with precision as configured and a location_source=exif flag is set Given EXIF geolocation is present but the user has not opted in or the org policy disables location storage When ingestion runs Then geolocation is discarded and a location_redacted=true flag is set Given an image without EXIF timestamp When ingestion runs Then server_received_at is stored and used as capture_time Given permissions are denied for location at the client When the user uploads Then the upload is not blocked and no location data is stored
Triage Tag Validation & Canonical Mapping
Given a set of triage tags submitted by the tenant When validation runs Then all tags must exist in the controlled taxonomy version in effect; invalid tags are rejected with top-3 suggestions Given multiple valid tags map to a canonical problem category When mapping runs Then the ticket is assigned the correct canonical category and the mapping is recorded in metadata Given duplicate tags are submitted When validation runs Then duplicates are removed prior to storage and only unique tags persist Given a valid submission passes validation When storage occurs Then taxonomy_version is recorded with the tags for auditability Given an optional free-text tag is enabled by configuration and is submitted When validation runs Then it is stored as other with free_text captured and flagged for review
De-duplication Within Ticket
Given the same image file is uploaded more than once to the same ticket When exact hashing runs Then the duplicate is automatically suppressed and the user is notified that a duplicate was removed Given two images are near-duplicates (perceptual hash distance ≤ configured threshold) When ingestion runs Then the second image is flagged duplicate_candidate=true and the user is prompted to keep or discard Given a duplicate is suppressed When artifacts are linked to the ticket Then only one primary artifact remains linked and suppressed artifact IDs are referenced in metadata Given images are uploaded to different tickets When de-duplication checks run Then no suppression occurs across tickets; a cross_ticket_duplicate flag is recorded for analytics only
Encrypted Storage, Access Control & Ticket Linking
Given normalized artifacts and validated tags When persistence occurs Then photos are stored in encrypted object storage (server-side AES-256) and all transfers use TLS 1.2+ Given artifacts are stored When the ticket is viewed Then artifacts appear in capture-time order and are linked to the ticket_id with stable artifact_ids Given an authorized user (tenant of the ticket, assigned vendor, or property manager) requests an artifact When access is granted Then a pre-signed URL with expiry ≤15 minutes is issued; unauthorized users receive 403 Given metadata enrichment is required for downstream analysis When ingestion completes Then artifact records include ticket_id, unit_id, tenant_id, device_type, device_os_version, upload_source, taxonomy_version, and quality flags Given a data deletion or retention event is triggered per policy When the request is processed Then artifacts and associated metadata are deleted or archived according to the configured retention rules with an audit log entry
Resilience, Idempotency & Observability
Given a transient network error during upload When the client retries with the same idempotency key Then no duplicate artifacts or tags are created and the operation is safely retried Given a 5xx error occurs during normalization When the job is retried with exponential backoff up to the configured limit Then the job eventually succeeds or fails with a terminal state and a correlation_id is returned to the client Given downstream enqueue to ScopeMatch fails When ingestion completes Then artifacts are persisted durably and a retryable enqueue job is scheduled; p95 time-to-enqueue after recovery is ≤60 seconds Given ingestion events occur When logs and metrics are emitted Then structured logs include ticket_id, artifact_ids, and correlation_id; metrics report success/failure counts and latencies with p50/p95 Given an invalid payload is submitted When validation runs Then a 4xx response is returned with machine-readable error codes and human-readable messages
Scope Inference Engine
"As an operations manager, I want the system to infer the exact job scope from tenant inputs so that we send the right technician with the right qualifications the first time."
Description

Build an ML/NLP/CV service that analyzes uploaded photos and triage tags to infer the required skills, certifications, tools, and parts for a job (e.g., EPA 608 Type II, gas appliance work, ladder >10 ft, refrigerant handling, specific fittings). The engine outputs normalized scope attributes with confidence scores and severity/safety flags. Provide versioned models, configurable thresholds, and a rules layer for deterministic augmentations (e.g., building code locales). Expose a synchronous API for real-time triage and an async batch mode for retraining/experiments. Log inputs/outputs for audit and continuous improvement. Integrates with FixFlow triage and approval flows to drive vendor selection and one-click approvals.

Acceptance Criteria
Synchronous inference API returns normalized scope with confidences and flags
Given a triage request with 1–3 photos (<=5MB each, jpg/png) and triage tags When POST /scope-infer is called with a valid payload Then respond 200 within p95 <= 800 ms and p99 <= 1500 ms And return JSON containing normalized_attributes[], each with {id, type, value, confidence (0.0–1.0), sources[]} And include safety_flags[] and severity {level, rationale} when applicable And include correlation_id and model_version in the response And two identical requests within 24h return identical normalized_attributes and severity And invalid inputs return 4xx with machine-readable error codes; user errors never return 5xx
Configurable confidence thresholds gate required vs suggested attributes
Given org-level confidence thresholds are configured per attribute type (skill>=0.75, certification>=0.80, tool>=0.70, part>=0.70) When inference completes Then attributes with confidence >= threshold are marked state="required" And attributes with confidence between (threshold*0.85) and threshold are returned as state="suggested" And attributes with confidence < 0.60 are omitted And threshold changes via admin config take effect within 5 minutes and are audit-logged with actor, old_value, new_value And each attribute in the response includes its gating state
Deterministic rules layer augments outputs with provenance and precedence
Given rules are configured for locale, property_type, and code constraints When inference runs on a matching job Then rules may add attributes not produced by ML and may elevate severity but may not remove required ML attributes And conflicts prefer the higher-confidence ML attribute unless the rule has override=true And each rule-derived attribute includes provenance {rule_id, rule_version, explanation} And rules execution adds <= 50 ms p95 to total latency
Automatic safety and severity flagging from images and tags
Given photos/tags indicative of gas appliance work, exposed wiring, active water leak, or refrigerant handling When inference runs Then hazard_types are drawn from [gas, electrical, water, refrigerant, structural, biohazard] And severity.level is set per policy: confidence >= 0.85 -> high; 0.60–0.84 -> medium; < 0.60 -> none And safety_flags include actionable next_steps when level >= medium And required certifications are emitted when applicable (e.g., EPA_608_Type_II when refrigerant confidence >= 0.80)
Model versioning, pinning, and safe rollout
Given multiple deployed model versions (e.g., v1.3.0, v1.4.0) When a request specifies model_version=v1.4.0 Then the response uses v1.4.0 and includes model_version=v1.4.0 And when no version is specified, alias "current" is used and can be atomically repointed And canary rollout supports routing 5%–20% of traffic to candidate_version with per-org overrides And automatic rollback triggers if error_rate > 1% or p95 latency > SLA for 5 consecutive minutes And all version/alias changes are audit-logged with actor and timestamp
Asynchronous batch inference for experiments and retraining
Given an async job request with manifest_uri, output_uri, and up to 10,000 items When POST /scope-infer/batch is called Then return 202 with job_id and status=queued And job states progress queued -> running -> succeeded/failed with progress metrics And outputs are written as NDJSON to output_uri with one record per input matching the sync API schema And throughput sustains >= 50 inferences/second and completes within 2 hours for 10,000 items And per-item failures are captured without aborting the entire job
Comprehensive audit logging with secure retrieval
Given inference processing occurs When audit logs are fetched via GET /admin/audit using correlation_id or time range Then entries include input metadata (hashes, dimensions, tags), outputs, model_version, thresholds used, rule provenance, latency, and config change refs And sensitive media are stored by reference (URI + hash) only; PII is redacted from logs And retention is 18 months with immutable storage and access restricted to role=ML_Auditor And exporting up to 10,000 records completes within 60 seconds
Vendor Capability Profile & Verification
"As a property manager, I want verified vendor capabilities kept up to date so that ScopeMatch can reliably match jobs to eligible technicians."
Description

Introduce a structured vendor profile schema capturing skills, certifications (with type, number, issuer, expiration), tool inventory (including ladder heights), parts on-hand, service areas, insurance, and availability windows. Provide a verification workflow for uploading credentials, automated expiration reminders, and optional third-party checks. Normalize capabilities to the same ontology the Scope Inference Engine produces, enabling direct eligibility matching. Sync with existing FixFlow vendor records and expose an admin UI for updates and audit trails.

Acceptance Criteria
Admin Creates and Updates Vendor Capability Profile
Given I am an authenticated FixFlow admin in the Vendor Profile UI When I create or edit a vendor profile with skills, certifications (type, number, issuer, expiration), tool inventory (including ladder heights), parts on-hand, service areas, insurance, and availability windows Then the system validates required fields, normalizes entries to the ScopeMatch ontology, and prevents save if validation fails with field-level errors And on successful save, changes are persisted, synced to the existing FixFlow vendor record without duplication, and a versioned audit entry (who, what, when, before/after) is recorded
Certification Upload and Verification Workflow
Given a vendor profile requires a certification When an admin or vendor uploads a certification document and enters type, number, issuer, and expiration date Then the system stores the document, validates metadata formats, and sets the certification status to Pending And if third-party verification is enabled, a verification request is created and tracked; upon Verified response, status updates to Verified; upon Failed/Expired, status updates accordingly And certifications with status other than Verified are excluded from eligibility checks that require that certification
Automated Credential Expiration Reminders
Given a certification or insurance policy has an expiration date When the expiration is 30, 14, 7, or 1 days away Then the system sends reminders to the vendor primary contact and FixFlow admin via email and in-app notifications with the credential details and renewal link And when the credential expires, status switches to Expired, a final notice is sent, and the vendor is excluded from matches requiring that credential until a new Verified credential is uploaded
Tool Inventory with Ladder Height Normalization
Given an admin records tools including ladders with heights entered in feet or meters When the entry is saved Then the system normalizes ladder height to a standard unit and ontology term, validates numeric ranges, and flags unknown/missing heights And vendors lacking a required ladder height for a scope tag (e.g., Ladder>=20ft) are excluded from eligibility; vendors meeting or exceeding the requirement are eligible
Service Areas and Availability Windows Applied to Matching
Given a vendor has service areas (ZIP/radius or polygon) and weekly availability windows stored with a canonical time zone When a work order at a tenant location and requested time window is evaluated Then the vendor is eligible only if the location is within service area and the requested time overlaps the vendor’s availability, with daylight saving time handled correctly And ineligibility reasons are logged and exposed (e.g., Outside service area, No availability)
Ontology-Normalized Eligibility Matching
Given a work order has scope tags emitted by the Scope Inference Engine When matching runs Then only vendors whose normalized skills, verified certifications, tools, parts on-hand, insurance, service area, and availability satisfy all required tags are returned And each match includes a human-readable rationale showing which tags were satisfied and by which profile fields, and which vendors were excluded with explicit reasons
Two-Way Sync with Existing Vendor Records
Given an existing FixFlow vendor record with an external vendor_id When a capability profile is created or updated Then the system upserts capabilities into the vendor record idempotently, resolves conflicts by latest-updated timestamp, and does not create duplicates And sync successes and failures are logged with correlation IDs, and retriable failures are automatically retried with exponential backoff
Match Scoring & Ranking
"As a dispatcher, I want a ranked list of eligible vendors for the job’s exact scope so that I can approve the best option quickly and confidently."
Description

Develop a matching service that computes an eligibility gate (hard constraints) and a relevance score (soft factors) to rank vendors for a given inferred scope. Hard constraints include required certifications, tool availability (e.g., ladder height), service area, and availability. Soft factors include past performance, first-visit resolution rate, distance/travel time, price bands, SLA fit, and relationship preferences. Provide tunable weights, minimum score thresholds, and tie-breakers. Return a ranked shortlist via API to power one-click approvals and vendor suggestions in FixFlow. Include fallback logic when no eligible vendor meets all constraints.

Acceptance Criteria
Eligibility Gate Filters Ineligible Vendors
Given a work request with inferred scope requiring specific certifications, tool requirements (e.g., ladder_height>=20ft), a property service geofence, and a requested time window When the matching service evaluates vendor eligibility Then only vendors that simultaneously have all required active certifications, meet or exceed tool requirements, cover the property within their service area, and have availability overlapping the requested window are marked eligible and advanced to scoring And all ineligible vendors are excluded from the shortlist with per-vendor reason codes enumerating each failed hard constraint
Weighted Relevance Score Computation
Given a configured weight set totaling 100% for factors {past_performance, first_visit_resolution, travel_time, price_band_fit, SLA_fit, relationship_preference} and a pool of eligible vendors When relevance scoring is executed Then each eligible vendor receives normalized 0–1 sub-scores per factor and an aggregate relevance score in the range 0–100 computed as sum(weight_i * sub_score_i) And the scoring output is deterministic for identical inputs and configuration And the response includes a factor_breakdown per vendor showing each sub-score and applied weight And if the supplied weights fail validation (do not sum to 100% within tolerance or include unknown factors), scoring is not performed and a configuration error is returned
Minimum Score Threshold and Tie-Breakers
Given a configured minimum relevance score threshold T in [0,100] When scoring completes Then vendors with aggregate_score < T are excluded from the ranked shortlist And ties on aggregate_score (rounded to 2 decimals) are resolved in this priority order: (1) higher first_visit_resolution, (2) lower travel_time_minutes, (3) lower estimated_total_cost within price band, (4) lexicographically smaller vendor_id for deterministic ordering
Fallback When No Fully Eligible Vendor
Given zero vendors meet all hard constraints When matching executes Then the API returns a fallback payload that lists zero eligible vendors and provides up to 5 near-miss vendors ranked by relevance among those failing exactly one hard constraint And each near-miss includes a failed_constraint code and requires_manual_override=true And safety-critical constraints (required certifications and hazardous tool requirements) are never auto-relaxed And, if configured, availability may be relaxed by extending the requested window by up to 72 hours and service area by up to 10 miles, with any relaxation clearly labeled in the response
API Shortlist Response Contract and Deterministic Ordering
Given a shortlist request for a scope When the API returns the ranked shortlist Then the response includes metadata {request_id, generated_at_utc, config_version, weights, threshold T} and a list of the top N vendors (default N=5, configurable) sorted by aggregate_score after tie-breakers And each vendor entry includes {vendor_id, eligible=true, aggregate_score 0–100 with 2 decimals, factor_breakdown, tie_breaker_fields_used, distance_miles, travel_time_minutes} And repeated calls with identical inputs and configuration return identical scores and ordering
Relationship Preferences Handling
Given relationship preferences are configured for the portfolio/property (favorites and blocked) and a non-zero weight for relationship_preference When eligibility and scoring are executed Then blocked vendors are excluded regardless of other factors And favorite vendors receive a positive sub-score consistent with the configured weight without violating hard constraints And property-level preferences override portfolio-level preferences where conflicts exist And all adjustments are visible in the factor_breakdown
Compliance & Safety Guardrails
"As a risk-conscious owner, I want automatic blocking of non-compliant dispatches so that we avoid safety incidents and regulatory violations."
Description

Enforce safety-critical and regulatory constraints during approval and dispatch. If the inferred scope requires specific certifications (e.g., EPA 608, gas-fitting), prevent one-click approval to any vendor lacking verified credentials. Flag hazardous contexts (gas leaks, electrical panels, roof access) and require additional confirmations or multi-approval where configured. Persist compliance evidence (inferred scope, vendor credential snapshots, timestamps) to the ticket for audits. Provide configurable policies by jurisdiction and portfolio.

Acceptance Criteria
Block approval for uncredentialed vendor on regulated refrigerant work
Given a maintenance ticket where ScopeMatch infers "Refrigerant handling required" mapped to required credential "EPA 608" And portfolio policy for the property jurisdiction requires EPA 608 for refrigerant handling And the selected vendor lacks an active, verified EPA 608 credential (status != active OR expiresAt < now) When an approver attempts one-click approval via UI or API Then the approval is blocked and no dispatch record is created And the UI Approve button is disabled with tooltip "Blocked: Missing EPA 608" And API responses return HTTP 422 with errorCode "COMPLIANCE_CREDENTIAL_MISSING" and details {credential:"EPA 608", vendorId, ticketId} And an audit event "ComplianceBlocked" is appended to the ticket with fields {ticketId, vendorId, requiredCredential:"EPA 608", reason:"missing_or_expired", evaluatedAt, evaluatorVersion, policySetId}
Require multi-approval for suspected gas leak before dispatch
Given ScopeMatch flags hazard "Gas leak suspected" and rule require_multi_approval=2 is active for this portfolio/jurisdiction When Approver A submits approval Then the ticket approval status becomes "Pending Second Approval" and no vendor dispatch is sent And notifications are sent to approver group "Maintenance Leads" When Approver B (userId != Approver A, authorized role) approves within policy.multiApprovalSLA minutes Then the dispatch is created and timestamped, and the approval record stores approverIds [A,B] Else if the window expires, the approval auto-cancels, no dispatch occurs, and alerts are sent to Approver A with reason "multi_approval_expired"
Re-validate vendor credentials at dispatch time and revoke if changed
Given an approval was granted using a vendor credential snapshot And before dispatch execution the system revalidates vendor credentials against current records When any required credential is expired, revoked, or removed since approval Then the dispatch is blocked, the approval state transitions to "Revoked - Credential Change", and the vendor is de-selected And notifications are sent to approvers and the vendor with errorCode "COMPLIANCE_POST_APPROVAL_CHANGE" And an audit event records previous snapshotId, current credential state, and decision "Revoke"
Persist compliance evidence snapshot for auditability on every decision
Given any approval decision (Blocked, Pending, Approved, Revoked) When the decision is saved Then a ComplianceEvidence record is appended to the ticket containing: inferredScope (skills, hazards, tags), policySetId and version, rulesEvaluated (ids, outcomes), vendorCredentialSnapshot (credential ids, issuer, verifiedAt, expiresAt, status), decision (Allow/Block/Pending/Revoked), approverIds, approvalsRequired, approvalsReceived, timestamps (evaluatedAt, decidedAt), and reasonCodes And evidence is immutable (subsequent decisions create new records; previous records remain unmodified) And the evidence is retrievable in the ticket audit view and via API and returns within 300ms for tickets under 1 year old
Apply jurisdictional policy with portfolio override precedence
Given a property resolves to jurisdiction J and portfolio-level overrides exist for rule R When compliance evaluation runs Then the system applies rules in precedence order: Portfolio override > Jurisdiction default > Global default And the policySetId/policySource fields in evidence reflect the actual source applied for each rule And changing the property address to jurisdiction J2 results in a different rule set being applied on next evaluation, visible in the evidence record
Enforce hazard checklist for roof access with ladder height >= 16ft
Given ScopeMatch infers hazard "Roof access" with ladderHeight >= 16ft And policy require_checklist=fall_protection is active When an approver attempts to approve Then a mandatory checklist modal is presented with items: "Vendor has active fall-protection training", "Two-person team confirmed OR approved anchor points", and "Weather conditions acceptable" And the Approve action remains disabled until all items are affirmed and the approver provides typed-name attestation And the attestation is stored in evidence with fields {checklistId, items, attestedBy, attestedAt, ipAddress} And if the approver cancels or fails to complete, no approval or dispatch occurs
Match Explanation & Manual Override
"As a dispatcher, I want to see why a vendor was recommended and override when necessary so that I can make informed decisions and document exceptions."
Description

Present clear, human-readable explanations for each recommendation: detected scope items, required certifications/tools, and why a vendor was elevated or filtered out. Display confidence scores and any safety flags. Allow authorized users to override the recommended match with a required justification, capturing reason codes and maintaining an immutable audit trail. Expose explanations and override events via API and in the FixFlow approval UI to build trust and support continuous model improvement.

Acceptance Criteria
Explain Recommended Vendor in Approval UI
Given a triaged maintenance request with recommended and filtered vendors When an approver opens the Approval UI match panel Then for each recommended and filtered vendor the panel displays: detected scope items, required certifications, required tools/parts, and a rationale explaining elevation or exclusion And then each rationale references at least one matching or missing vendor attribute per scope requirement And then the explanation includes model version, generation timestamp, and the source of each scope item (photo analysis or triage tag)
Display Confidence Scores and Safety Flags
Given a computed match explanation is available When explanations are rendered in UI and returned via API Then each scope item and overall vendor match includes a confidence score as an integer percentage between 0 and 100 And then any safety-related scope item (e.g., gas, high voltage, refrigerant, ladder >10ft) is flagged with a Safety badge and summarized at the top of the explanation And then vendors lacking required safety certifications are labeled "Not eligible — safety requirement unmet" within the explanation
Role-Based Manual Override with Justification
Given a user has role Approver or Owner When the user chooses Override Recommendation and selects a different vendor Then the system requires selection of a reason code from a managed list and a free-text justification between 20 and 500 characters before saving And then users without override permission cannot access the override control and receive an authorization error if they attempt via API And then upon successful override the chosen vendor becomes the approved selection and the UI marks the request with an Overridden badge including who and when
Immutable Audit Trail for Overrides
Given any override is submitted When the system persists the event Then an audit record is created capturing immutable ID, request ID, prior recommendation set, selected vendor, user ID, user roles, reason code, justification text, timestamp (UTC), actor IP, and application version And then audit records are append-only; update and delete operations are disallowed and return 405 via API And then audit records are visible in the Approval UI audit tab and retrievable via API within 5 seconds of override completion
Expose Explanations and Overrides via API
Given an authenticated API client with scope read:recommendations When it calls GET /v1/requests/{id}/match-explanation Then the response includes scope items, required certifications/tools/parts, per-vendor rationale (elevated/filtered), confidence scores, safety flags, model version, and timestamps And then GET /v1/requests/{id}/overrides returns a paginated list of override events with fields matching the audit record And then API responses use JSON with stable IDs and RFC 7807 error payloads; unauthorized requests return 401 and insufficient scopes return 403
Capture Overrides as Model Feedback
Given an override is saved When post-processing runs Then a feedback event is emitted within 30 seconds to the analytics pipeline including request ID, original top recommendation, overridden vendor, reason code, justification text, and a snapshot of the explanation used at decision time And then delivery is at-least-once with retry for up to 24 hours and failures are logged and exposed via metrics

PriceGuard

Normalizes quotes and historical job costs by issue type, building class, and region to flag outliers. Suggests cost‑efficient vendors who still meet quality thresholds and enables auto‑approval within guardrails—protecting budgets without sacrificing results.

Requirements

Cost Normalization Engine
"As a property manager, I want quotes normalized against historical costs by issue type, building class, and region so that I can see a fair market range and avoid overpaying."
Description

Implements a service that ingests quotes and historical invoices, maps each job to standardized issue types, building classes, and regions, and produces normalized cost ranges (e.g., median, p25/p75, p90) adjusted for seasonality and inflation. Supports currency handling and multi-tenant data isolation. Exposes an internal API for real-time normalization during quote intake and for batch backfills. Stores normalization artifacts for reuse and caching to keep the UI responsive. This engine is the backbone of PriceGuard, enabling consistent comparisons and fair-market baselines across diverse properties and locations.

Acceptance Criteria
Ingestion of Quotes and Invoices with Currency and Tenant Metadata
Given a CSV upload containing required columns (job_id, amount, currency, issue_description, building_class, region, tenant_id, invoice_date), When the file is validated and processed, Then the service returns 201 with accepted_count equal to the number of valid rows, error_count = 0, and all accepted records are persisted with original currency and tenant_id. Given a CSV upload missing any required column, When validation runs, Then the service returns 400 with a machine-readable list of missing columns and no records are persisted. Given an API batch POST of up to 2,000 quote/invoice records for a single tenant, When the request is accepted, Then the service returns 202 with a batch_id and the batch reaches status = "completed" within 5 minutes for well-formed data. Given duplicate job_id values within the same tenant in the same batch or across prior ingestions, When ingestion runs, Then duplicates are rejected with 409 Conflict and are not persisted. Given a record with an unsupported or invalid ISO-4217 currency code, When ingestion runs, Then the record is rejected with 422 Unprocessable Entity and the error is reported at the row level. Given each accepted record, When persisted, Then created_at, source, and checksum fields are stored to enable idempotent replays (replays with identical checksum do not create new records).
Job Taxonomy Mapping to Issue Type, Building Class, and Region
Given an ingested job with issue_description, building_class, and address/region metadata, When taxonomy mapping executes, Then standardized fields issue_type, building_class_code, and region_code are assigned with confidence >= 0.90. Given mapping confidence < 0.70, When taxonomy mapping executes, Then the job is flagged status = "unmapped", routed to a review queue, and excluded from normalization cohorts. Given a tenant-defined mapping override exists for a term or vendor code, When mapping executes, Then the override is applied and recorded with override_source = "tenant". Given an invalid or unknown region/building_class, When mapping executes, Then the job is rejected from normalization with a 422 error and an actionable message. Given a set of 10,000 mixed records, When mapping executes, Then average processing time is <= 100 ms per record and end-to-end throughput is >= 50 records/second on the reference environment.
Normalized Cost Range Computation with Seasonality and Inflation Adjustments
Given a cohort defined by (issue_type, building_class_code, region_code) with >= 30 comparable jobs dated within the past 24 months, When normalization runs, Then it returns p25, p50 (median), p75, and p90 using winsorized data at p5/p95, along with sample_size_used and time_window = 24 months. Given a cohort with 10–29 jobs in the past 24 months, When normalization runs, Then the time_window expands to 36 months; if still < 10 jobs, Then hierarchical fallback applies in order: (issue_type + region, any building_class) -> (issue_type, any region within country) -> (issue_type, national). Given normalization runs, When seasonal adjustment is applied, Then a month-of-service seasonal index derived from the past 36 months is used and values are normalized to an annual baseline; the applied seasonal_factor is included in the response artifact. Given normalization runs, When inflation adjustment is applied, Then all amounts are converted to a base month using a region-specific CPI series as of invoice_date; the cpi_source, cpi_region, and base_month are included in the artifact. Given identical inputs and calculation_version, When normalization runs multiple times, Then outputs are deterministic (identical percentile values and sample_size) and rounded to 2 decimal places in the base currency.
Multi-Currency Conversion and FX Rate Handling
Given a job amount and currency different from the tenant_base_currency, When normalization runs, Then the amount is converted using the FX rate effective on invoice_date (T+0 close) and the fx_source and fx_timestamp are recorded. Given a job amount already in tenant_base_currency, When normalization runs, Then no conversion is applied and fx_applied = false is recorded. Given an invoice_date for which an FX rate is missing or the FX feed is older than 3 days, When conversion is attempted, Then the request fails with 424 Failed Dependency and an actionable error indicating the missing currency/date pair; the record is queued for retry once rates are available. Given a batch of 1,000 conversions across USD, CAD, and EUR, When processed, Then all conversions complete with absolute rounding error <= 0.01 in tenant_base_currency and median latency <= 20 ms per conversion using cached FX tables.
Multi-Tenant Data Isolation and Access Controls
Given two tenants (A and B) with separate data, When tenant A requests normalization via the internal API, Then only tenant A data contributes to cohort formation and artifacts; no records from tenant B are read or exposed. Given an API call missing a valid service token or with a token for a different tenant, When authorization runs, Then the request is rejected with 401/403 and no data access occurs. Given caching is enabled, When the same normalization request is issued by different tenants, Then cache keys include tenant_id and artifacts are not shared across tenants. Given storage of normalization artifacts, When artifacts are written, Then each artifact is tagged with tenant_id and all read queries enforce tenant_id filters at the datastore level. Given a synthetic cross-tenant leakage test, When run, Then zero cross-tenant records are returned (0/10,000 sampled queries) and the test logs a Pass result.
Real-Time Normalization API and Caching for UI Responsiveness
Given a well-formed quote payload containing amount, currency, issue_type, building_class_code, region_code, tenant_id, and invoice_date, When the real-time normalization API is called, Then it returns 200 with normalized_range (p25, p50, p75, p90), sample_size_used, calculation_version, and cached indicator. Given an identical request (same cohort, tenant_id, base month/version) within the cache TTL, When the API is called, Then the response is served from cache with cached = true and p95 latency <= 200 ms; uncached p95 latency <= 500 ms at 50 RPS per tenant in the reference environment. Given new qualifying data is ingested that affects a cohort, When cache invalidation runs, Then artifacts and cache entries for that cohort and tenant are invalidated within 60 seconds. Given the API is called without required fields or with invalid enums, When validation runs, Then the API returns 400 with field-level errors and no computation is performed. Given a tenant exceeds 100 RPS sustained for 60 seconds, When rate limiting applies, Then the API returns 429 with retry-after and continues to serve other tenants without degradation.
Batch Backfill Processing with Idempotency and Progress Reporting
Given a request to backfill normalizations for a tenant and date_range, When the backfill job starts, Then it shards work into batches of <= 5,000 jobs, processes batches idempotently by job_id checksum, and records progress (processed_count, success_count, error_count) per batch_id. Given transient failures (e.g., FX lookup timeout), When encountered, Then the job retries each failed item up to 3 times with exponential backoff; after max retries, items are moved to a DLQ for review. Given a backfill job is paused or crashes mid-run, When resumed with the same job_id, Then processing continues from the last successful batch without duplicating artifacts. Given backfill emits metrics, When the job completes, Then emitted metrics include total_artifacts_written, average_batch_latency, retries, and DLQ_count; completion status = "succeeded" only when error_count = 0. Given backfill writes artifacts, When complete, Then each artifact includes calculation_version, cohort_keys, sample_size_used, time_window, seasonal_factor, cpi_source, and fx_source to ensure reproducibility.
Outlier Detection & Alerts
"As an operations lead, I want automatic flags when quotes fall outside expected ranges so that I can review anomalies before approving work."
Description

Evaluates incoming quotes against normalized baselines to flag high/low outliers using configurable thresholds (percentile bands or standard deviations). Provides per-account policies to tune sensitivity by issue type and region. Surfaces inline warnings in the approval workflow, with alert badges and tooltips that summarize deviation amount and likely drivers. Supports notification options (in-app, email) and one-click routing to review. Includes controls to dismiss or suppress known exceptions while retaining an audit note to reduce alert fatigue.

Acceptance Criteria
Inline Outlier Warning in Approval Workflow
Given a quote's normalized cost deviates beyond the configured threshold for its issue type, building class, and region When the approver opens the approval workflow screen for that quote Then an outlier badge is displayed inline next to the total with type "High Outlier" or "Low Outlier" and color coding (red for high, blue for low) And a tooltip is available that shows deviation percent and dollar delta versus baseline, the threshold method and value (e.g., P90 band, 2.0 SD), and the top 1–3 likely drivers And the baseline range and sample size used are visible in the tooltip And if the quote is outside auto-approval guardrails, the "Auto-Approve" control is not available and a "Send to Review" action is presented
Configurable Thresholds by Issue Type and Region
Given an account admin opens PriceGuard settings When configuring outlier thresholds by issue type and region Then the admin can select a method: Percentile Bands or Standard Deviations And can set numeric values within allowed ranges: Percentile 50–99, SD 0.5–5.0 And can save distinct thresholds per issue type and region combination And saved thresholds take effect on subsequent quote evaluations within 5 minutes And invalid values are rejected with validation messages And a default threshold is applied where a combination has no explicit configuration
Per-Account Outlier Policy Controls
Given an account admin configures outlier policy When setting actions for quotes flagged as outliers Then the admin can choose: Warn-Only or Auto-route to Review And the admin can set auto-approval guardrails: Max % above baseline and Max absolute $ above baseline And the policy is stored per account and auditable with timestamp and actor And changes to policy are versioned and applied to new quotes only, not retroactively altering prior decisions
Notifications: In-App and Email for Outlier Alerts
Given a quote is flagged as an outlier When notifications are enabled for the relevant account and role Then an in-app alert is shown to assigned approvers within 5 seconds with badge, deviation summary, and a deep link to review And an email notification is sent within 2 minutes including quote ID, vendor, deviation summary, and a "Review" CTA And users' channel preferences are honored; if email is disabled for the user, no email is sent And duplicate notifications for the same quote and user are suppressed within a 24-hour window unless the quote changes And clicking the in-app alert or email CTA opens the quote with one-click "Send to Review" available (or completes routing if the user is already authenticated)
One-Click Routing to Review
Given an approver views a flagged quote When the approver clicks "Send to Review" Then a review task is created and assigned per account routing rules within 2 seconds And the quote is moved out of the auto-approval queue And the action and reason (if provided) are recorded in the audit log with user, timestamp, and context And the approver returns to the approval list with a confirmation message
Dismiss or Suppress Known Exceptions with Audit Note
Given an approver views a flagged outlier When the approver selects "Dismiss" Then the alert is removed for this quote only and an audit note is required before completion And the quote remains eligible for approval per policy When the approver selects "Suppress" Then the user can define scope (vendor and issue type, optionally building) and duration (7/30/90 days) And during the suppression window, matching outliers do not trigger notifications and display a "Suppressed" badge with a link to the rule And all dismiss and suppress actions are logged with actor, timestamp, scope, and note And admins can view, edit, or revoke suppression rules, with changes taking effect within 5 minutes
Vendor Recommendation Scoring
"As a landlord, I want recommendations for cost‑efficient, high‑quality vendors so that repairs are completed affordably without sacrificing service."
Description

Calculates a composite vendor score that balances cost efficiency with quality signals (first-fix rate, callback/redo rate, tenant satisfaction, SLA adherence, proximity, and license/insurance validity). Presents a ranked short list of recommended vendors for the specific issue type, building class, and region, with concise explanations of why each vendor is suggested. Honors preferred-vendor lists and compliance filters, and de-prioritizes vendors with recent negative outcomes. Provides fallbacks when data is sparse by using portfolio-level or regional priors.

Acceptance Criteria
Ranked Vendor Shortlist for Request Context
Given a maintenance request with issue type, building class, and region and at least three eligible vendors, When vendor scoring is executed, Then a shortlist of the top vendors is returned sorted by composite score descending and contains at least three entries. Given fewer than three eligible vendors, When vendor scoring is executed, Then all eligible eligible vendors are returned sorted by composite score descending. Given the shortlist is generated, When inspecting the order, Then no vendor with a lower composite score appears above a vendor with a higher composite score.
Composite Score Calculation and Normalization
Given configured weights for cost efficiency, first-fix rate, callback/redo rate, tenant satisfaction, SLA adherence, and proximity, When scoring runs, Then each vendor’s composite score equals the weighted aggregate of these normalized signals on a 0–100 scale with two-decimal precision. Given identical inputs and weights, When scoring is repeated, Then composite scores are deterministic and match within ±0.01. Given any vendor is missing a required metric, When computing the score, Then the metric is filled via the defined fallback policy rather than failing the computation.
Compliance Filters and Preferred-Vendor Tie-Breaking
Given a vendor with invalid or expired license/insurance for the request’s region or issue type, When building the candidate set, Then the vendor is excluded from the shortlist. Given two vendors with equal composite scores and both pass compliance, When one is on the account’s preferred-vendor list, Then the preferred vendor ranks higher. Given two vendors within the configurable tie tolerance, When one is preferred and both pass compliance, Then the preferred vendor ranks higher.
Recent Negative Outcomes De-prioritization
Given a vendor with a recent negative outcome within the configured lookback window (e.g., redo/callback or CSAT below threshold), When ranking, Then a penalty is applied such that, holding other inputs constant, the vendor ranks below comparable vendors without such outcomes. Given a vendor exceeds the configured count of recent negative outcomes within the lookback window, When ranking, Then the vendor is suppressed from appearing in the top three recommendations.
Sparse Data Fallbacks Using Portfolio/Regional Priors
Given a vendor lacks sufficient data for a metric at the issue-type/building-class/region scope per the configured sample threshold, When computing the composite score, Then the system substitutes portfolio-level priors; if still insufficient, Then it substitutes regional priors. Given a fallback prior was applied for any vendor, When returning results, Then the applied prior level (portfolio or regional) is recorded alongside that vendor’s score for auditability.
Concise Explanations for Each Recommendation
Given the ranked shortlist, When presenting each vendor, Then an explanation string is displayed that includes the top three contributing factors and any preference/compliance considerations in 160 characters or fewer. Given an auditor recalculates the top contributing factors from the inputs, When comparing to the explanation, Then the explanation’s factors correspond to that vendor’s highest-weighted positive contributors.
Policy Guardrails & Auto-Approval
"As an owner, I want auto‑approval rules within budget guardrails so that routine repairs move fast while staying within policy."
Description

Offers an admin-configurable rules engine that enables auto-approval when a quote meets defined guardrails (e.g., within N% of normalized median, vendor score above threshold, budget cap not exceeded, and required compliance docs on file). Supports rule scoping by property, owner, portfolio, and region, with override hierarchies and effective dates. Includes simulation mode to preview impact, plus hard/soft stop behaviors. All auto-approvals capture reason codes and a full audit record to ensure accountability and compliance.

Acceptance Criteria
Auto-Approval Within Cost and Quality Guardrails
Given a pending quote with issue type, building class, region and an applicable rule, and a normalized median cost is available When the quote total is within the rule’s N% band of the normalized median AND the vendor’s quality score >= the rule threshold AND the scoped budget cap is not exceeded AND all required compliance documents are present and valid Then the system auto-approves the quote, sets approval_status=Approved, records approval_timestamp, and associates the approval with the evaluated rule ID And no manual approver is required for this approval event
Guardrail Failures Block Auto-Approval
Given a pending quote within the cost band but the vendor score is below the configured threshold When the rule is evaluated Then auto-approval does not occur and failure_reasons includes "VendorScoreBelowThreshold" Given a pending quote within the cost band but one or more required compliance documents are missing or expired When the rule is evaluated Then auto-approval does not occur and failure_reasons includes "ComplianceDocsInvalid" Given a pending quote within the cost band but approving it would exceed the scoped budget cap When the rule is evaluated Then auto-approval does not occur and failure_reasons includes "BudgetCapExceeded"
Scoped Rules and Override Hierarchy Resolution
Rule precedence is Property > Owner > Portfolio > Region > Global Default Given multiple active rules are applicable to a quote When evaluating Then the highest-precedence rule in scope is used exclusively for the decision Given no rule exists for a higher-precedence scope When evaluating Then the engine falls back to the next scope in the hierarchy until a rule is found or the Global Default is applied Given conflicting parameters across scopes When evaluating Then only parameters from the selected rule are applied
Rule Effective Dates Enforcement
Given a rule with effective_start and effective_end defined in the property's local time zone When current_time falls within the inclusive [effective_start, effective_end] window Then the rule is considered active; otherwise it is inactive Given an attempt to create or activate a rule whose effective window overlaps an existing active rule at the same scope When saving the rule Then the system blocks the change with error "OverlappingEffectiveDates" and no partial save occurs
Simulation Mode Preview Without Side Effects
Given simulation mode is enabled and a set of target quotes is selected by filters When simulate is executed Then each quote is annotated with would_auto_approve=true/false, matched_rule_id, and failure_reasons if false And no quote states, approvals, budgets, or audit logs are altered And a simulation_report is produced with counts and estimated budget impact aggregated by scope Given the same inputs are simulated again When executed Then results are deterministic and identical to the prior run
Hard and Soft Stop Behaviors Outside Guardrails
Given a rule configured with stop_behavior=HARD and a quote fails any guardrail When evaluated Then approval actions are blocked in the UI and API with error code "HardStopPolicy" and no override is permitted except by users with Role=Admin via explicit PolicyOverride Given a rule configured with stop_behavior=SOFT and a quote fails any guardrail When evaluated Then users may proceed only after entering a mandatory justification_reason and selecting a policy_override_reason from a configured list And the override is logged and routed for post-approval review Given a soft stop override is saved When persisted Then the system records override actor, timestamp, reason code, and links to the failed rule
Auto-Approval Reason Codes and Audit Trail
Given an auto-approval occurs When recorded Then the system stores reason_code="WithinGuardrails", rule_id and version, actor="System", evaluation_inputs (normalized_median, N%, vendor_score, budget_remaining, compliance_checks), and a hash of the rule parameters Given any approval decision (auto, soft override, or manual decline) When saved Then an immutable audit record is created with before_state and after_state, timestamps, actor, and correlation_id for reconciliation and export Given an audit export request for a date range and scope When executed Then the system returns CSV or JSON including the fields above and completes within 60 seconds for up to 10,000 records
Region & Building Class Taxonomy Management
"As an admin, I want to define regions and building classes for my portfolio so that normalization and comparisons reflect my properties’ realities."
Description

Defines and manages the canonical taxonomies for regions (ZIP, city, metro, state) and building classes (e.g., single-family, small multifamily, vintage, luxury). Maps each property to these taxonomies using address and metadata, with an admin UI for overrides. Ensures all normalization and recommendations reference the correct classifications. Integrates geocoding and supports portfolio-specific region groupings to reflect how customers operate.

Acceptance Criteria
Automated Region Classification from Address
Given a property record with a valid US street address When the address is submitted for processing Then the system geocodes the address and maps it to canonical region IDs for ZIP, city, metro, and state And the mapping is persisted with source=automated and confidence >= 0.90 Given geocoding returns multiple candidates with top confidence < 0.90 When mapping is evaluated Then the property is flagged review_required And no region mapping is persisted And an admin notification is created within 1 minute Given a geocoding request fails or times out When retries (max 3 with exponential backoff) are exhausted Then the property is queued for manual review And the failure is logged with a trace ID Given normal operating conditions When geocoding is executed Then P95 time from submission to persisted mapping is <= 3 seconds
Automated Building Class Assignment from Metadata
Given a property with metadata including unit_count, year_built, and amenities When the property is saved or updated Then the system assigns exactly one building_class_id using rules: unit_count=1 => single-family; 2–10 => small multifamily; year_built < 1970 => vintage; luxury if (amenities include doorman OR gym) AND year_built >= 2000; precedence order luxury > vintage > unit_count-based Given metadata is insufficient for confident classification When rules produce confidence < 0.80 Then building_class_id is set to unclassified And the property appears in the classification_gaps report within 5 minutes Given property metadata changes When changes are saved Then reclassification is triggered and published as an event And P95 reclassification latency <= 1 minute
Admin Override of Region and Building Class
Given a user with role=Portfolio Admin viewing a property When they open the Classification tab Then they can set overrides for region grouping(s) and building_class via searchable pickers And a required reason field must be provided to save Given an override is saved When the save completes Then an immutable audit log entry is recorded with user_id, timestamp, before_value, after_value, reason, and optional ticket_ref And the property displays an Overridden badge with who/when And P95 propagation to read models and downstream services <= 5 minutes Given a user without override permissions (Viewer) When they attempt to save an override via UI or API Then the operation is blocked with HTTP 403 and an inline error message Given an existing override When the admin removes the override Then the system reverts to automated classification on next refresh
Portfolio-Specific Region Groupings
Given a portfolio scope When an admin creates a custom region group Then they can select one or more canonical regions (ZIPs, cities, metros, states) And the group is saved with a unique per-portfolio key and display name Given overlapping selections that cause ambiguous membership within the same portfolio When the group is validated Then the UI blocks the save and highlights conflicts with guidance to resolve Given a property with a geocoded canonical region When portfolio group membership is computed Then membership is deterministic and equals the union of constituent canonical regions Given changes to group definitions When a group is saved, updated, or deleted Then cache is invalidated and P95 propagation to dependent queries <= 5 minutes And groups remain invisible to other portfolios
Normalization and Recommendations Use Correct Classifications
Given a property with current canonical region_id(s) and building_class_id When PriceGuard computes cost normalization for a quote Then the query includes the property’s region and building class dimensions And a unit test verifies presence of region_id and building_class_id filters Given an override exists for the property When normalization and vendor recommendations are computed Then overridden values are used instead of automated classifications Given a taxonomy change (e.g., region merge or class deprecation) When the next normalization job runs Then results reflect the new taxonomy And historical jobs remain associated with their original classification snapshot for analytics Given monitoring is enabled When normalization jobs execute Then mismatches between job dimensions and property classification at compute time are reported (target 0 per day)
Geocoding Integration Resilience and Monitoring
Given integration with primary and secondary geocoding providers When requests are made Then API keys are read from secure storage And per-call timeout is set to 2 seconds And up to 3 retries with exponential backoff are applied before failing And a circuit breaker opens after 5 consecutive failures per provider-region bucket Given steady-state traffic When geocoding throughput reaches expected load Then rate limiting is respected at < 10 RPS per key And no HTTP 429 responses occur during load test Given the primary provider is degraded When the circuit breaker is open Then requests fail over to the secondary provider automatically And response differences are logged for review Given observability is configured When geocoding operations run Then metrics (success rate, P50/P95 latency, error codes) are published And an alert triggers if success rate < 98% over a 5-minute window
Transparency & Auditability
"As a compliance auditor, I want a clear explanation of why a quote was flagged or approved so that I can justify decisions and pass audits."
Description

Provides clear explanations for every flag, recommendation, and auto-approval, including the normalization basis (sample size, median, percentile bands), deviation amounts, rule evaluations, and vendor score breakdowns. Generates immutable event logs and downloadable reports for each decision. Displays concise, user-friendly reasoning in the UI while retaining detailed evidence for compliance reviews, creating trust and enabling continuous improvement of policies.

Acceptance Criteria
UI Flag Explanation for Outlier Quotes
Given a maintenance quote is flagged as a cost outlier by PriceGuard When the landlord opens the quote details in FixFlow Then the UI displays the normalization basis: issue type, building class, region, sample size (n), median, and percentile bands (P25, P50, P75, P90) And the UI displays the deviation as both currency delta and percentage relative to the median And the UI lists all evaluated rules with IDs/names and pass/fail results And the UI shows the vendor score breakdown (price, quality, timeliness, repeat visits) with weights and component scores that sum to the total And all displayed values exactly match the backend decision payload for that decision_id And the explanation view loads in 2 seconds or less at the 95th percentile
Auto-Approval Justification Panel
Given an auto-approval has been executed by PriceGuard for a job When a user views the job's approval panel Then the panel shows policy version, guardrail thresholds, and which conditions were satisfied And it shows the selected vendor and evidence that it met minimum quality thresholds And it shows total amount, budget category, and comparison to normalization basis (median and deviation %) And it includes decision_id, actor ("system"), and timestamp in UTC ISO 8601 And it provides a link to the corresponding audit log entry And any manual override is captured as a separate audit entry with user, timestamp, and reason And the justification content is read-only for end users
Immutable Audit Log for Decisions
Given any PriceGuard decision event (flag, recommendation, or auto-approval) When the event is recorded Then an append-only log entry is created with decision_id, event_type, actor, inputs_snapshot_hash, computed_metrics, rule_evaluations, and outcome And the entry includes a SHA-256 content hash and previous_hash to form a verifiable chain And attempts to mutate an entry are blocked and recorded as separate events without altering the original And entries are queryable by decision_id within 3 seconds of creation And entries are retained for at least 24 months And a verification endpoint returns chain_valid = true for the decision_id
Downloadable Decision Report (PDF/CSV)
Given a user with permission to view a decision When they request a download for that decision Then a report is generated within 5 seconds containing normalization basis, deviation amounts, evaluated rules, vendor score breakdown, and audit metadata And both PDF and CSV formats are available for download And numeric values in the report match the UI to two decimal places and the backend payload exactly And the report footer includes decision_id and the audit log content hash And the report generation is recorded as an audit log event with user and timestamp
Normalization Basis Details and Confidence Indicator
Given PriceGuard computes normalized costs for a selected issue type, building class, and region When the sample size n is 30 or greater Then the UI displays percentile bands and median with n and last_refresh timestamp (UTC ISO 8601) And when sample size n is less than 30, a "Low confidence" indicator is displayed adjacent to the normalization basis And the filters used (issue type, building class, region) are shown and any change updates the basis and n within 1 second And no individual vendor's identifiable pricing is exposed; only aggregated statistics are shown
Vendor Recommendation Transparency and Score Breakdown
Given PriceGuard recommends a vendor for a job When the user opens the recommendation details Then the UI displays the top three vendors with total scores and highlights the chosen vendor And the breakdown shows sub-scores (price normalization, quality rating, on-time rate, repeat-visit rate), their weights, and the summed total And any tie is resolved using documented tiebreaker rules, which are shown in the view And a link to the vendor's historical performance summary is provided And all displayed values match the decision payload and can be copied to clipboard

SLA Forecaster

Predicts each vendor’s probability of meeting your SLA for the specific property, time of day, and weather/backlog context. Surfaces risk badges and recommends the lowest‑risk option or alternate time windows, boosting on‑time performance and reducing escalations.

Requirements

Contextual Data Aggregation Pipeline
"As a property manager, I want the system to factor in real‑world context like vendor backlog, traffic, and weather so that SLA predictions reflect actual conditions and help me choose reliable options."
Description

Build a streaming/batch data pipeline that aggregates and normalizes all contextual signals required for SLA prediction per job: vendor historical on‑time performance by job type and property, current vendor backlog/availability, property metadata, tenant availability windows, traffic/travel-time estimates, time‑of‑day/day‑of‑week/holiday flags, and weather forecasts. Define schemas, feature transformations, and quality checks; de‑duplicate and time‑align events; and publish features to a versioned feature store. Integrate with FixFlow’s work order and vendor systems via APIs/webhooks, and with third‑party weather/traffic providers. Ensure PII minimization, access controls, and retention policies compliant with company standards.

Acceptance Criteria
Real-time Vendor Backlog Ingestion via Webhooks
Given a vendor backlog webhook POST that conforms to schema v1.0 with delivery_id, vendor_id, timestamp, capacity, and queue_depth, When received, Then respond 200 within 1s and write a single event to the streaming bus with the same delivery_id. Given a second POST with the same delivery_id, When processed, Then no additional feature updates are written (idempotent) and a duplicate metric increments by 1. Given the vendor endpoint is temporarily unreachable, When pulling backlog via API, Then retry with exponential backoff (6 attempts over 15 minutes), and if still failing, raise a warning alert within 2 minutes and retain last-known backlog with last_updated_at. Given streaming ingestion, When measuring p95 end-to-end latency from webhook receipt to online feature store write, Then p95 <= 60 seconds and p99 <= 120 seconds. Given a payload failing validation, When processed, Then respond 400 with a machine-readable error list and do not write to the bus or store.
Weather and Traffic Context Enrichment
Given a job with property lat/long and target time window [start,end], When enrichment runs, Then fetch travel_time_minutes from TrafficProviderA and weather_forecast from WeatherProviderB for start, store provider attribution, retrieval_time, and ttl in the feature store. Given TrafficProviderA rate limits or errors, When enrichment runs, Then fallback to TrafficProviderA-Secondary within 5s; if all fail, set features to null and feature_status='stale' with error_code and continue pipeline. Given identical enrichment query (same geohash-6 and hour bucket) within 10 minutes, When requested, Then serve from cache and reduce external API calls by >= 80% compared to no-cache baseline. Given enrichment jobs, When measuring completion time, Then 99% complete within 90 seconds and 0 daily provider quota violations are recorded.
Event De-duplication and Time Alignment
Given multiple source events for the same work_order_id, vendor_id, job_type, and property_id within a 5-minute window with equal event_hash, When processed, Then retain the earliest by event_time and drop the rest. Given a late event arrives within 24 hours with a higher sequence_number for the same keys, When processed, Then recompute features using event_time semantics and upsert both online and offline stores consistently. Given aggregate computations (e.g., vendor_on_time_rate_90d by vendor_id+job_type+property_id), When computed, Then use a sliding 90-day window ending at event_time and produce deterministic results across replays (stable checksum). Given an identical replay of raw inputs, When the pipeline is re-run, Then outputs are bit-for-bit identical (idempotent results).
Feature Store Schema, Versioning, and Backfill
Given a change to feature definitions is merged to main, When released, Then a new schema version vN+1 is created with changelog and migration steps; online and offline stores expose vN and vN+1 concurrently for at least 90 days. Given a backfill request for a historical date range, When executed, Then the pipeline reprocesses from raw logs with the exact code tag and writes to the offline store under the requested version without impacting online data. Given a consumer requests features, When specifying version, Then the API returns features only from that version; default selection is the latest stable version. Given feature definitions, When built, Then a data dictionary is published including field name, description, type, nullability, source, owner, and PII flag.
Data Quality Gates and Monitoring
Given streaming and batch inputs, When validation runs, Then required fields (work_order_id, vendor_id, property_id, timestamp) are non-null; enums are valid; geolocation is within bounds; failing records are quarantined with reason and not published. Given day-over-day ingestion volume, When monitored, Then changes > +/-10% trigger a warning alert and > +/-25% trigger a critical alert. Given feature distributions, When drift detection runs daily, Then a KS-test p-value < 0.01 or mean shift > 3 standard deviations triggers an incident. Given operational metrics, When observed, Then streaming freshness lag p95 <= 5 minutes and batch job completes by 06:00 local property timezone; violations page on-call. Given platform uptime, When measured monthly, Then online feature store availability >= 99.5%.
PII Minimization, Access Controls, and Retention
Given feature computation, When handling tenant or property fields, Then exclude names/emails/phones; if identifiers are required, hash with salted SHA-256; property location reduced to geohash-6 or lat/long rounded to 3 decimals. Given access to the feature store, When a non-privileged role queries sensitive columns, Then access is denied and audit logs capture user, role, time, and columns; only roles 'ml-service' and 'data-eng' can read sensitive columns; only 'data-eng' can write. Given retention policy, When daily cleanup runs, Then raw events are retained 30 days and curated features 400 days; GDPR delete requests result in deletion across raw and derived stores within 7 days and produce a deletion receipt.
SLA Prediction Model Service
"As an operations lead, I want an accurate on‑time probability for each vendor and slot so that I can make scheduling decisions that minimize SLA breaches."
Description

Deliver a stateless, horizontally scalable service that predicts the probability a specific vendor meets the SLA for a given job and time window. Expose a JSON API that accepts the contextual feature vector and returns predicted probability, confidence, and top contributing factors, with P95 latency ≤300 ms and uptime ≥99.9%. Implement calibrated classification (e.g., gradient boosted trees with isotonic calibration) with cold‑start fallbacks for new vendors and sparse contexts. Provide model versioning, shadow/canary deploys, monitoring (latency, error rate, calibration drift), and graceful degradation to heuristic scores if the model is unavailable.

Acceptance Criteria
Real-time SLA Probability Prediction API
- Given a valid request with vendor_id, property_id, job_type, time_window_start, time_window_end, and context When POST /v1/sla/predict Then return 200 with JSON fields: probability (0–1), confidence (0–1), top_factors (array length ≥3 with name and contribution), model_version, request_id, timestamp. - Given the same payload and model_version When sent to two different replicas Then responses are identical for probability (within 1e-6) and top_factors ordering, demonstrating statelessness. - Given a payload missing any required field or with invalid types When POST Then return 400 with error.code and error.message; no partial predictions are returned.
Horizontal Scalability and P95 Latency ≤300 ms
- Given sustained load of 100 RPS per replica for 30 minutes When autoscaling target CPU is 60% Then global P95 latency ≤300 ms and error rate ≤0.1%. - Given 1, 2, and 4 replicas under proportional load When measured Then aggregate throughput scales ≥3.5x from 1 to 4 replicas and P95 latency remains ≤300 ms. - Given a rolling restart of any single replica During steady load Then no error spike >0.5% and no request timeouts >0.1%.
Calibrated Probability Outputs
- Given a holdout dataset of ≥10,000 labeled jobs When evaluated Then Expected Calibration Error (10-bin) ≤0.03 and Brier score ≤0.18. - Given predictions bucketed by probability deciles When compared to observed SLA outcomes Then absolute calibration error per bucket ≤5% for buckets with ≥100 samples. - Given the confidence field bucketed into low/mid/high tertiles When evaluated Then mean absolute calibration error decreases monotonically from low to high confidence.
Cold-Start and Sparse Context Fallback
- Given vendor_id with <50 labeled jobs in the past 12 months or missing critical features When predicting Then service uses cold_start fallback, returns fallback_type="cold_start", probability from class/region baseline, confidence ≤0.6, and top_factors sourced from the heuristic. - Given a cold_start prediction When logged Then metric fallback_rate increments and an audit event is emitted with vendor_id and reason. - Given vendor reaches ≥50 labeled jobs with required features present When predicting Then fallback_type is null and model-based prediction is returned.
Graceful Degradation on Model Unavailability
- Given the primary model scorer is unhealthy or times out (>150 ms internal) When predicting Then return HTTP 200 with heuristic probability, degraded=true, degradation_reason populated, model_version=null, within P95 300 ms. - Given both model and heuristic paths are unavailable When predicting Then return HTTP 503 with error.code="SLA_PREDICT_UNAVAILABLE" within 100 ms and include Retry-After ≤60 s. - Given degraded responses exceed 1% over a 5-minute window When monitored Then an on-call alert is triggered.
Model Versioning, Shadow, and Canary Deployments
- Given model version N+1 in shadow mode When serving requests Then both N and N+1 score each request, only N’s output is returned, and N+1 outputs are logged with correlation_id; no user-visible differences. - Given canary at 5% traffic for N+1 When monitored for 24 hours Then auto-rollback triggers within 5 minutes if any breach occurs: error_rate >0.5%, P95 latency >300 ms, or ECE degradation >0.02 absolute vs N; otherwise auto-promote to 100%. - Given any response When returned Then includes model_version and deployment_mode in {"primary","shadow","canary"}.
Monitoring, Alerts, and Uptime SLO ≥99.9%
- Given the service is running When /metrics is scraped Then Prometheus exports request_total, request_duration_ms_bucket, error_total, p95_latency_ms, ece_live, calibration_drift, fallback_rate, degraded_rate, uptime_slo_burn with bounded label cardinality (e.g., vendor class/region). - Given synthetic probes from 3 regions at 1-minute intervals When measured over trailing 30 days Then uptime ≥99.9% (≤43 min downtime), excluding planned windows tagged maintenance=true. - Given daily label joins ≥5,000 samples When computed Then live ECE is published and an alert fires when ECE ≥0.05 for 2 consecutive days.
Risk Badge UI and Surfacing
"As a landlord on my phone, I want a clear visual risk indicator next to each option so that I can quickly choose the safest path without digging into details."
Description

Render risk badges alongside each vendor and time slot across triage, vendor selection, and one‑click approval screens. Map probabilities to standardized risk tiers (Low/Medium/High) with configurable thresholds and color/accessibility semantics (WCAG AA). Provide tooltips with short explanations and links to details. Ensure responsive performance on mobile web, instrument click/hover analytics, and maintain parity in list and card views without adding extra steps to the existing flow.

Acceptance Criteria
Cross-Screen Risk Badge Rendering
Given the SLA Forecaster returns on-time probabilities for all displayed vendors and time slots When the user opens the Triage screen Then each vendor row and each available time-slot chip shows a risk badge adjacent to the label without overlap or truncation When the user opens the Vendor Selection screen Then the same vendors and time-slot options show badges with the identical tier, color, and percentage as on Triage When the user proceeds to the One-Click Approval screen Then the selected vendor/time-slot shows the same badge and tier, and any alternate suggested windows also display badges When a probability is unavailable for an item Then a neutral "N/A" badge with tooltip "Forecast unavailable" is shown and no blocking error occurs
Tier Mapping and Configurable Thresholds
Given thresholds are configured in Admin as Low >= 80%, Medium 60–79%, High < 60% When an item has SLA probability 82% Then a Low tier badge is shown with label "Low" and percentage "82%" When thresholds are changed in Admin to Low >= 85%, Medium 70–84%, High < 70% Then badges on all screens reflect the new mapping within 60 seconds without redeploy When thresholds configuration is missing or invalid Then system falls back to defaults (Low >= 80, Medium 60–79, High < 60) and logs a non-fatal warning
Color and Accessibility Compliance
Given tier colors are Green (Low), Amber (Medium), Red (High) per design tokens Then each badge meets WCAG AA contrast ratio >= 4.5:1 against page backgrounds in light and dark themes And color is not the sole indicator: badges include a visible text label (Low/Medium/High) and percentage And each badge has aria-label like "SLA risk: Low (82% on-time)" and role="status" And badges, tooltips, and detail links are operable via keyboard (Tab/Shift+Tab; Enter/Space) with a visible focus indicator And screen readers announce tooltip content when opened And no animation or flashing exceeds 3 times per second
Tooltip Explanation and Details Link
Given a user hovers (desktop) or taps (mobile) a risk badge Then a tooltip opens within 150 ms, positioned adjacent to the badge without layout shift And the tooltip shows concise text: "{p}% on-time likelihood for this window" and the tier label And a link or button "View details" is present When the user activates "View details" Then the SLA Forecaster details view opens within the current flow with vendor_id, property_id, and time_slot context When the user taps/clicks outside the tooltip or presses Esc/Back Then the tooltip closes and focus returns to the invoking badge
Responsive Mobile Performance and Flow Unchanged
Given a list of up to 50 vendors each with up to 6 time slots on a mid-tier Android device (e.g., Moto G Power; Chrome latest) When the screen loads Then the 95th percentile additional badge render latency is <= 200 ms after list items appear And the 95th percentile tap-to-response latency on badges is <= 100 ms And scrolling performance remains >= 55 FPS while badges are visible And Lighthouse Performance score degradation attributable to badges is <= 3 points versus baseline And the number of required clicks from Triage to Approval remains unchanged versus the baseline flow (no new mandatory steps or blocking modals)
List and Card View Parity
Given the user toggles between List and Card views on applicable screens Then all vendors and time-slot options display identical badges, tiers, colors, percentages, and tooltips in both views And badge placement matches design within ±4 px and no text truncation occurs at 320–768 px widths And no view exposes additional or fewer badge states than the other
Analytics Instrumentation for Badges
Given a badge first enters the viewport Then an analytics event "risk_badge_impression" is emitted once per item per screen per session with properties: tenant_id, property_id, vendor_id, time_slot_id, tier, probability_bucket, screen_name, view_type, timestamp; excludes PII When a user opens a tooltip Then "risk_badge_tooltip_open" is emitted with the same context When a user clicks "View details" Then "risk_badge_detail_click" is emitted prior to navigation And events are batched, retried offline, and achieve >= 99% delivery to the analytics backend within 5 minutes in QA testing
Lowest‑Risk Vendor and Time Window Recommendations
"As a dispatcher, I want the system to suggest the best vendor and alternate windows so that I can improve on‑time performance with minimal manual analysis."
Description

Compute and display ranked recommendations that minimize SLA risk while honoring business rules (preferred vendors, service areas, rate cards) and constraints (vendor blackout times, tenant availability). For each job, propose the top vendors and alternate time windows that increase on‑time probability, showing expected uplift versus the current choice. Support one‑click apply/override with reason capture and log all decisions for audit. Respect per‑property SLAs and dispatch policies.

Acceptance Criteria
Rank Recommendations by Lowest SLA Risk
Given a maintenance job with property, time-of-day, weather, and vendor backlog context When the SLA Forecaster is invoked for recommendations Then it computes an on-time probability for each eligible vendor-time option using the latest model snapshot and context Given eligible vendor-time options When recommendations are generated Then options are sorted by ascending SLA risk (highest probability first), and the top 3 are returned with probability %, confidence, and risk badge Given fewer than 3 eligible options When recommendations are generated Then only the available options are returned without error Given two options with equal probability (±0.5 pp) When ranking Then ties are broken by preferred vendor rank, then historical on-time rate, then lowest total cost Given the model service is unavailable or times out (>800 ms) When generating recommendations Then a rule-based fallback ranking (preferred vendors within service area, availability, earliest feasible time) is returned and a "Model unavailable" banner is shown Given performance expectations When generating recommendations for ≤100 candidate vendors Then the API responds within 1.5 s P95 and 3.0 s P99
Honor Preferred Vendors, Service Areas, and Rate Cards
Given account-level preferred vendor lists and per-property preferences When generating candidates Then only vendors eligible for the job’s trade and service area are included Given rate card constraints (active contract, price cap) When ranking Then vendors violating rate cards are excluded, and exclusion reasons are logged Given a preferred vendor within 3 percentage points of the lowest-risk non-preferred option When selecting the recommended option Then the preferred vendor is surfaced as the top recommendation with a "Preferred" indicator Given vendor exclusions (suspension, compliance hold) When generating candidates Then excluded vendors are not shown and the reason is attached to the audit log
Respect Blackout Times and Tenant Availability
Given vendor blackout times and tenant-provided availability windows When generating vendor-time options Then any option overlapping a blackout or outside tenant availability is excluded Given at least one feasible slot within the SLA window When proposing alternates Then at least two alternate time windows that meet constraints and increase on-time probability by ≥5 pp vs the baseline are shown Given no feasible slot within the next 72 hours When generating options Then the earliest feasible slot is shown with a "No feasible slot within 72h" indicator and reason tags Given tenant updates availability When recomputing recommendations Then recommendations refresh within 10 seconds and reflect the new windows
Display Expected Uplift vs Current Choice
Given a current vendor and time selection exists When recommendations are displayed Then each option shows the expected uplift in on-time probability in percentage points relative to the current choice, including negative values Given uplift values When displayed Then each value is computed from the same model snapshot as the ranking, rounded to 1 decimal place, and time-stamped Given there is no current selection When recommendations are displayed Then uplift is hidden and a "No baseline" indicator is shown Given the selected recommendation is applied When viewing the job timeline Then the recorded uplift at time of decision is persisted and visible in the audit trail
One-Click Apply and Override with Reason Capture
Given a displayed recommendation When the user clicks Apply Then the system updates the dispatch to the selected vendor and time, sends notifications to vendor and tenant, and records applied_by, timestamp, recommendation_id, and model_version Given the user selects a non-top recommendation or a manual vendor/time When saving Then a reason must be selected from a predefined list with optional free text (min 5 chars), and save is blocked until provided Given an action is recorded When viewing the job Then the decision shows as "Applied" or "Overridden" with the reason, and notifications success/failure are displayed Given a mistake is made When Undo is clicked within 10 minutes Then the previous dispatch is restored, notifications are sent to revert, and the audit log records a reversal entry
Comprehensive Decision Logging and Auditability
Given any recommendation generation or user action When logging Then an immutable append-only record is stored with job_id, property_id, trade, SLA target, tenant availability, vendor calendars snapshot_id, weather context, backlog metrics, candidate set, scores, ranks, filtered-out reasons, thresholds, selected action, actor, and notifications status Given audit retrieval When requesting by job_id or correlation_id Then the record is returned within 3 seconds P95 with PII redacted per policy and retained for 24 months Given a compliance audit When exporting logs Then exports include full decision rationale and model version and pass checksum validation
Per-Property SLA and Dispatch Policy Compliance
Given per-property SLAs and dispatch policies (max travel distance, union rules, after-hours policies) When generating options Then no recommendation violates hard policies; violating options are excluded with reasons logged Given all feasible options miss the SLA due time When displaying recommendations Then an "At Risk" banner is shown, options are sorted by minimal lateness, and the minimum lateness in minutes is displayed Given a policy conflict on a recommended option When the user hovers or taps the option Then constraint reason chips are visible (e.g., Outside service area, After-hours surcharge) Given policy updates are changed in admin When generating recommendations after change Then new policies are enforced within 5 minutes and the policy version is recorded in the audit log
Explainability and Audit Trail
"As a regional manager, I want to see why a recommendation was made and what factors influenced risk so that I can justify decisions and improve vendor relationships."
Description

Provide concise, human‑readable explanations for each prediction (e.g., key drivers like weather severity or vendor backlog) using model‑agnostic feature attributions. Persist prediction inputs, outputs, chosen action, user overrides, and outcomes for at least 18 months in an auditable log. Enable export and admin reporting for compliance and post‑incident reviews, with role‑based access and privacy controls.

Acceptance Criteria
Prediction Explanation with Top Drivers in UI
Given a Dispatcher or Landlord views an SLA Forecaster prediction for a work order When they click the Why? control or expand the explanation Then the UI displays a single sentence (≤ 200 characters) naming the top 3 drivers in plain language with their signed contribution percentages And the driver list shows for each: feature label, actual input value, and whether it increased or decreased on-time risk And the sum of absolute contribution percentages for the shown drivers is ≥ 70% of total attribution And the explanation identifies the attribution_method and model_version used And the explanation renders within 300 ms p95 after the prediction is available
Append-Only Audit Log with 18-Month Retention
Given any SLA prediction is generated or updated When the event is written to the audit log Then the record includes: prediction_id, request_id, tenant_id, work_order_id, timestamp (UTC ISO8601), model_version, attribution_method, input_feature_vector (with schema_version), output (probability, risk_badge), recommended_action, chosen_action, user_id, role, source_ip, hash_prev And records are stored append-only with a SHA-256 hash chain and are immutable to non-admin write operations And any attempt to alter a record is rejected with 403 and an audit event is created And records remain queryable for ≥ 18 months from timestamp and are not deleted or obfuscated before that period And p95 write latency for logging is ≤ 100 ms and does not block the user flow
User Overrides and Outcome Linkage
Given a user overrides the recommended vendor or time window When they submit the override Then a reason field (min 5, max 200 chars) is required and stored with before/after values And the override is linked to the originating prediction_id and work_order_id And when the job outcome (on-time/late, completion_ts) is received, it is linked to the same prediction_id And all linked events are retrievable via API GET /audit/predictions/{prediction_id} and in a timeline view And the override and outcome linkage appears in admin reporting within 15 minutes
Compliance Export with Redaction and Schema
Given a user with Compliance_Export permission selects a date range up to 90 days When they request an export Then a downloadable file is produced within 2 minutes for ≤ 100k records in CSV or JSONL, including a schema header with schema_version and generated_at And PII fields (tenant_name, phone, email, photos) are redacted by default and only included if Include PII is explicitly checked And each file includes a data dictionary and checksum (MD5) and is watermarked with requester user_id And users without Compliance_Export receive 403 and an audit event is recorded
Role-Based Access Controls for Explanations and Logs
Given RBAC roles Viewer, Dispatcher, Vendor, Admin, and Compliance are configured When users access explanations or audit logs Then Viewer and Dispatcher can view explanations for authorized work orders but cannot view raw feature vectors And Vendor cannot view explanations or audit logs beyond their own job outcomes And Admin can view and search audit logs but cannot export PII without Compliance_Export And Compliance can view and export logs, including PII if explicitly enabled And unauthorized access attempts return 403 and create an audit event with user_id, endpoint, and reason
Admin Reporting for Explainability and Audit
Given an Admin opens the Explainability & Audit dashboard When they filter by date range, property, and vendor Then the dashboard shows on-time rate, predicted vs actual gap, override rate, top 5 global drivers, and driver distribution by property And all metrics match the underlying export within ±0.5% for counts and rates And queries over ≤ 13 months of data return within 5 seconds p95 And a Download Report action exports the current view to CSV within 60 seconds
Resilience and Backfill of Audit Trail
Given a temporary outage of the logging service up to 30 minutes When predictions continue to be made Then events are queued and retried with exponential backoff until persisted And ≤ 0.1% of events are dropped; any drops emit an alert to Ops within 5 minutes And a backfill job re-ingests from the retry queue preserving original timestamps and hash chain continuity And prediction flow remains available with ≤ 100 ms p95 additional latency during the outage
Feedback Loop and Continuous Learning
"As a product owner, I want the model to learn from actual outcomes so that predictions stay accurate as conditions change."
Description

Capture ground‑truth outcomes from work orders (arrival/completion timestamps, SLA met/missed) and user feedback to label predictions. Provide dashboards for calibration, lift, and Brier score, and support A/B testing and canary releases. Automate data preparation and periodic retraining with approval gates, model comparison, and rollback procedures to maintain or improve accuracy over time.

Acceptance Criteria
Ground-Truth Outcome Capture
- Given a work order reaches vendor completion state, When vendor logs arrival and completion timestamps or the system ingests telematics, Then the system stores arrival_time and completion_time in UTC with source and timezone metadata and computes SLA_met flag using the configured SLA window. - Given missing timestamps, When users add or edit times within 48h, Then updates are versioned with audit trail and recomputation occurs; lateness reason captured; edits after 48h require admin role. - Given duplicate or out-of-order events, When ingestion occurs, Then the pipeline deduplicates by work_order_id + vendor_id + event_type with 1-minute tolerance and ensures idempotency (repeated ingestion yields identical state). - Given newly completed work orders, When ingestion runs, Then 95th percentile end-to-end label availability latency is <= 15 minutes, success rate >= 99.5% daily. - Given any ingestion error, When it occurs, Then it is logged with correlation_id and retried up to 3 times with exponential backoff; on failure, an alert is sent to on-call within 5 minutes.
User Feedback Labeling
- Given a work order is closed, When tenant and manager feedback is requested, Then at least one survey is sent via preferred channel with a unique link bound to the prediction_id. - Given a feedback is submitted, When received, Then it is linked to the work_order and prediction, stored with rating, free-text, and structured tags; PII is redacted per policy before training datasets are materialized. - Given a 14-day window post-closure, When the window ends, Then label coverage (SLA_met or missed from ground truth) is >= 95% of eligible work orders and feedback response rate is >= 30% for pilot cohorts; metrics appear on the coverage dashboard. - Given opt-out preferences, When they exist, Then the system honors them and excludes from contact lists while still capturing ground-truth outcome labels.
Model Performance & Calibration Dashboard
- Given a selected date range and cohort filters (vendor, property, time-of-day, weather, backlog decile), When the dashboard is loaded, Then it renders Brier score, reliability curve (slope, intercept), decile calibration table, and lift chart comparing model vs baseline within 5 seconds at p95. - Given performance thresholds, When metrics are computed, Then the displayed calibration slope is between 0.9 and 1.1 and intercept between -0.05 and 0.05 for the latest 30-day window or the metric is flagged in red with an explanatory tooltip. - Given metric export is requested, When the user clicks export, Then a CSV with underlying aggregated metrics and filters is downloaded and access is audited; only users with Analyst or Admin role can access the dashboard.
A/B Testing and Canary Releases
- Given a new model candidate, When an experiment is created, Then traffic can be split by property/vendor to variants (A: control, B: candidate) with configurable allocation (1–50%) and sticky assignment; exposures and decisions are logged with experiment_id and variant. - Given guardrails, When monitoring live traffic, Then auto-pause triggers if on-time SLA rate drops by >3% absolute vs control over 200+ assignments or if p-value < 0.05 in a two-sided sequential test; alert sent to Slack/Email within 5 minutes. - Given experiment conclusion, When stopping conditions are met, Then the platform produces a results report including effect size, confidence intervals, power, and practical significance decision; the report is stored and immutable.
Automated Data Preparation & Retraining Pipeline
- Given a weekly schedule or manual trigger, When retraining starts, Then feature pipelines materialize a versioned training dataset with data quality checks (missingness < 2% per feature unless whitelisted; label-leakage tests pass). - Given drift checks, When comparing last 30 days vs prior 90 days, Then population stability index (PSI) per key feature < 0.2 or flagged for review; target rate shift > 5% is highlighted. - Given training completes, When validation runs, Then job artifacts include model card, feature importance, and metrics; total wall-clock <= 2 hours at p95; failures page shows root causes.
Approval Gates, Promotion, and Rollback
- Given a trained candidate model, When entering the approval gate, Then promotion requires meeting or exceeding the champion on a rolling 8-week backtest with Brier score improvement >= 2% and no statistically significant degradation in top-decile lift or calibration slope outside [0.9,1.1]. - Given approval is granted, When deploying, Then deployment is staged as a 10% canary for 24 hours with a kill switch; rollback can be executed within 10 minutes and restores the prior model and feature store snapshot. - Given deployment completes, When promotion finalizes, Then the model registry records version, dataset hash, feature set version, approver, and change log; notifications are sent to stakeholders.
Post-Deployment Monitoring & Label-Drift Alerts
- Given live predictions, When daily monitoring runs, Then dashboards show Brier score, calibration, and on-time SLA rates by vendor/property; alerts fire if any metric deviates > 2 standard deviations from the 30-day baseline for 2 consecutive days. - Given feature drift, When PSI >= 0.3 for any core feature or label delay median > 24h, Then an incident is opened automatically and retraining is queued pending approval. - Given data retention policy, When storing labels and predictions, Then lineage is preserved for 400 days with GDPR-compliant deletion on request within 30 days; training datasets exclude records under deletion hold.
Proactive Escalation and Notification Integrations
"As a coordinator, I want to be alerted when a scheduled job is likely to miss SLA so that I can reschedule or escalate before a breach occurs."
Description

Integrate with FixFlow’s approval/dispatch workflows to trigger proactive alerts for high‑risk jobs and impending SLA breaches. Provide configurable notification channels (email/SMS/Slack) with rate limiting, retries, idempotency keys, and localization. Offer webhooks for vendor systems to acknowledge or reschedule, and update recommendations in real time as context changes.

Acceptance Criteria
High-Risk Job Alert on Approval/Dispatch
Given a work order is approved or dispatched in FixFlow And the SLA Forecaster predicts on-time probability below the configured threshold for the assigned vendor or flags risk = "High" When the approval/dispatch event is recorded Then the system generates a "High Risk" alert within 5 seconds And attaches a risk badge and at least one recommended lower‑risk vendor or alternate time window And records the alert with forecast inputs and threshold used in the audit log And no alert is sent if the predicted risk is below the threshold
Impending SLA Breach Preemptive Notification
Given a job is in progress with a defined SLA due time And the forecasted breach likelihood crosses the preemptive-notification threshold within the next 2 hours When the threshold crossing is detected Then preemptive notifications are sent to the assigned vendor and the property manager on all enabled channels within 60 seconds And the notification includes acknowledgment and reschedule actions And only one notification per recipient per job per channel is sent for this threshold crossing
Multi-Channel Notifications with Rate Limiting
Given organization notification preferences specify enabled channels per role and per event And a rate limit of max 1 alert per job per recipient per 10 minutes per channel is configured When multiple alertable events occur within the rate-limit window Then only the allowed notifications are delivered; excess are queued and summarized in the next permitted send And per-channel opt-out settings are respected And delivery, queued, and suppressed counts are recorded per recipient and channel
Reliable Delivery: Retries and Idempotency
Given a notification dispatch attempt returns a transient failure (e.g., HTTP 5xx, timeout) When dispatch is attempted Then the system retries up to 3 times with exponential backoff starting at 30 seconds with jitter, capped at 10 minutes And each attempt uses the same idempotency key composed of jobId-eventType-recipient-channel And no duplicate messages are created by downstream providers when retries occur And on terminal failure, a delivery_failed event is recorded and a fallback channel is attempted if configured And per-channel success/failure metrics are emitted
Vendor Webhooks: Acknowledge and Reschedule
Given a vendor has registered a webhook URL and shared-secret When FixFlow sends an ack_requested or reschedule_suggested webhook Then the request is signed with HMAC-SHA256, includes a timestamp and idempotency key, and uses HTTPS TLS 1.2+ And non-2xx responses are retried with exponential backoff up to 5 times And a 2xx response that includes an acknowledge or reschedule payload transitions the job state within 5 seconds And duplicate deliveries with the same idempotency key do not create duplicate state transitions And all webhook interactions are auditable with request/response metadata excluding secrets
Real-Time Forecast and Recommendation Updates
Given job context changes (e.g., weather alert, vendor backlog update, time-of-day shift, property access constraints) When any context signal is received Then the SLA forecast is recalculated within 10 seconds And existing alerts and recommendations are updated in place if the risk level or best option changes And scheduled escalations are canceled if risk falls below threshold And the UI risk badge and recommended vendor/time windows are refreshed for affected users within 15 seconds And the audit log captures before/after forecast values and the source of the change
Localization of Notifications and Templates
Given a recipient has a preferred locale and the property has a timezone When a notification is generated Then the content is localized to the recipient’s locale with fallback to the organization default And times/dates are formatted in the property timezone and locale conventions And templates support variable substitution and pluralization rules And administrators can preview localized templates prior to enabling them And all messages are UTF-8 encoded and include a templateVersion in metadata

Compliance Gate

Continuously verifies licenses, insurance, certifications, and expirations per trade and jurisdiction. Automatically demotes or blocks non‑compliant vendors from suggestions and attaches compliance proof to the work order—reducing liability and audit headaches.

Requirements

Credential Ingestion & Verification
"As a vendor manager, I want to capture and verify vendor credentials from multiple sources so that only qualified vendors can be considered for work orders."
Description

Enable intake and verification of vendor licenses, insurance certificates, and trade certifications via three paths: vendor self-upload (PDF/image), admin upload, and third‑party/authority APIs. Use OCR and data extraction to normalize key fields (issuer, policy/license number, coverage limits, effective/expiry dates, trade, jurisdiction). Validate documents against issuing authorities where available, de‑duplicate submissions, and assign a compliance status (Verified, Pending Review, Expired, Rejected) with reason codes. Store artifacts and extracted metadata securely with encryption at rest, access controls, and PII/PHI redaction. Provide a verification workflow with queues, SLAs, and audit logs, plus webhooks for status changes. Expose a read API for the triage and dispatch services to query vendor compliance state in real time.

Acceptance Criteria
Vendor Self-Upload with OCR Normalization and Auto-Validation
Given an authenticated vendor with an active FixFlow vendor profile And a credential file in PDF/JPG/PNG format up to 25 MB When the vendor uploads the file and selects document type and jurisdiction Then the system stores the raw artifact encrypted at rest and scans for malware And performs OCR/data extraction to capture issuer, document_type, policy_or_license_number, coverage_limits, effective_date, expiry_date, trade, jurisdiction And normalizes extracted fields to the credential schema with ISO 8601 dates and standardized jurisdiction codes And validates required fields and formats per document type/jurisdiction rules; if invalid, sets status "Rejected" with reason_code "INVALID_FIELDS" and exposes field-level errors And checks for duplicates by vendor_id + document_type + issuer + policy_or_license_number + effective_date + expiry_date; if found, links to existing and sets status "Rejected" with reason_code "DUPLICATE" And if an issuing-authority API is available, attempts auto-verification; on exact match, sets status "Verified" with reason_code "AUTO_VERIFIED" and records last_verified_at; on mismatch, sets status "Pending Review" with reason_code "REVIEW_NEEDED" And redacts PII/PHI in previews according to redaction rules while preserving the raw artifact securely And emits an audit log entry with actor_id, action "upload", artifact_checksum, and initial status
Admin Upload with Manual Review and Reason Codes
Given a user with role "Compliance Reviewer" or "Admin" And a vendor is selected When the user uploads a credential file and classifies document_type and jurisdiction Then the system associates the artifact to the vendor and stores it encrypted at rest And validates required fields and runs duplicate detection as per matching rules; duplicates are blocked with reason_code "DUPLICATE" And sets initial status to "Pending Review" And allows the reviewer to update status to "Verified" or "Rejected" with a mandatory reason_code from a controlled list and optional free-text notes And captures reviewer_id, timestamp, previous_status, new_status, and reason_code in the audit log And enforces access control such that only authorized roles can view/download artifacts and change statuses
Authority API Verification and Sync
Given a credential with issuer, jurisdiction, and policy_or_license_number And the jurisdiction supports an issuing-authority API When an automated verification job is triggered on create or manual retry Then the system sends a request with required fields and handles authentication for the authority API And matches records by exact policy_or_license_number and vendor legal name (case-insensitive) and issuer And on exact match, updates canonical fields (effective_date, expiry_date, status indicators) from the authority and sets status "Verified" with reason_code "AUTHORITY_VERIFIED", recording last_verified_at and storing the response payload as proof And on no match or conflicting data, sets status "Rejected" with reason_code "AUTHORITY_MISMATCH" and preserves the response payload And on transient errors/timeouts, retries up to 3 times with exponential backoff; upon exhausting retries, sets status "Pending Review" with reason_code "AUTHORITY_UNAVAILABLE" and records next_retry_at And respects authority API rate limits (backs off on HTTP 429) and logs correlation_ids for traceability
Verification Workflow Queues, SLAs, and Audit Logging
Given credentials in status "Pending Review" When they enter the verification queue Then they are visible to reviewers ordered by SLA due time and oldest first within priority tiers And reviewers can claim/release items; a claimed item is locked to a single reviewer until released or resolved And the default SLA for time-to-first-review is 24 hours (configurable per document_type/jurisdiction); breach triggers escalation notifications and marks breach_at And every state change (including claim/release) writes an immutable audit log entry with actor_id, timestamp, from_status, to_status, reason_code, and notes And bulk actions (max 50 items) are supported with per-item validation and audit entries; failures are reported per item
Compliance Status Lifecycle and Expiry Auto-Transitions
Given a credential with an expiry_date When current_time >= expiry_date Then the system auto-transitions status to "Expired" with reason_code "EXPIRY_REACHED" and records transitioned_at And sends 30-day and 7-day pre-expiry notifications to vendor and compliance admins via email and in-app alerts And allows transitions: Pending Review -> Verified/Rejected; Verified -> Expired; Rejected -> Pending Review (only upon new artifact upload); Expired -> Pending Review (upon new artifact upload) And requires evidence attachment or authority verification to set status "Verified" And maintains a complete status history with reason_code and actor for every transition
Security, Access Control, and Redaction
Given any stored credential artifact and metadata Then artifacts are encrypted at rest using AES-256 with keys managed by KMS and rotated per policy And access is controlled via RBAC: Vendor (self-only read/upload), Compliance Reviewer (org-wide read/write), Admin (org-wide read/write), Dispatcher (read-only compliance status), Tenant (no access) And all artifact downloads use time-bound signed URLs valid for 15 minutes; access attempts are logged with actor_id, ip, and outcome And PII/PHI patterns (e.g., SSN, DOB, bank account) are detected and redacted in previews and logs; raw artifacts remain secured and unredacted And all files are scanned for malware before becoming available; infected uploads are rejected with reason_code "MALWARE_DETECTED" And configuration and secrets for third-party integrations are stored securely and never exposed in logs
Webhooks and Read API for Real-Time Compliance State
Given a subscriber has registered a webhook endpoint with an HMAC shared secret When any credential status changes or a credential is within 30 days of expiry Then the system sends events "compliance.status.updated" and "credential.expiring" within 5s average (p95 <= 30s), signed with HMAC-SHA256; non-2xx responses are retried up to 6 times with exponential backoff And the Read API exposes GET /vendors/{vendor_id}/compliance (overall state) and GET /vendors/{vendor_id}/credentials (list) returning: document_type, issuer, policy_or_license_number (masked), coverage_limits, effective_date, expiry_date, status, reason_code, last_verified_at, links to artifacts And endpoints require OAuth2 scopes; unauthorized requests return 401, insufficient scopes return 403, and rate limit is enforced at 600 rpm per client with standard rate-limit headers And responses are updated within 1s of a status change and may be cached for up to 60s; availability SLO for read endpoints is 99.9% monthly
Jurisdiction & Trade Rules Engine
"As a compliance admin, I want to define and maintain rules by trade and jurisdiction so that the system can automatically determine whether a vendor meets local requirements."
Description

Provide a configurable rules engine that defines compliance requirements per jurisdiction (state/county/city) and trade (e.g., electrical, HVAC, plumbing). Rules specify required documents, minimum insurance coverage limits, acceptable license classes, certification bodies, and grace periods. Support inheritance and overrides (e.g., state baseline with city‑specific additions), effective dating and versioning for regulatory changes, and multi‑jurisdiction mapping per property. Evaluate a vendor’s credentials against applicable rules to produce a pass/fail result with detailed deficiencies. Offer an admin UI and API to manage rules, and surface evaluation results to triage, vendor suggestion, and approval flows.

Acceptance Criteria
Evaluate Vendor with State Baseline and City Overrides
Given a state rule for Electrical requires license_class=C-10, min_insurance_general_liability=1000000 USD, certification_body=NFPA, and grace_period_days_for_insurance=14, and a city override raises min_insurance_general_liability to 2000000 USD; When a vendor with license_class=C-10, general_liability=1500000 USD, and valid NFPA 70 certification is evaluated for a property in that city; Then the evaluation result is "Fail" and deficiencies include code="INSURANCE_MIN", required=2000000, actual=1500000, applied_jurisdiction="city". Given the same rules; When the vendor carries 2000000 USD but the policy expired 7 days ago; Then the result is "Pass" with grace_applied=true and deficiencies=[] and applied_rules contains both state and city rule IDs. Given the same rules; When the vendor lacks NFPA certification; Then the result is "Fail" and deficiencies contains code="CERT_MISSING", body="NFPA", doc_type="NFPA 70". Given acceptable_license_classes=["C-10"]; When a vendor has license_class="C-7"; Then the result is "Fail" and deficiencies contains code="LICENSE_CLASS_INVALID", acceptable=["C-10"], actual="C-7".
Aggregate State, County, City Rules for Multi-Jurisdiction Property
Given a property is mapped to state=CA, county=Alameda, city=Oakland and precedence is city>county>state; When evaluating a plumbing vendor; Then the engine merges requirements additively and evaluates against the union set and output.applied_rules includes source_jurisdiction per item. Given county requires pollution_liability_min=500000 USD; When the vendor lacks pollution liability coverage; Then result="Fail" and deficiencies contains code="INSURANCE_TYPE_MISSING", type="Pollution Liability", required_min=500000. Given state baseline min_gl=1000000 and city min_gl=1500000; When evaluating; Then the enforced threshold is 1500000 and deficiency (if any) references applied_jurisdiction="city". Given state requires documents A and B and city adds C; When evaluating; Then output.applied_rules has items for A, B, and C with respective jurisdiction tags.
Activate Versioned Rules by Effective Date
Given HVAC city rule v1 has effective_start=2025-07-01T00:00 local and v2 has effective_start=2025-10-01T00:00 local and system time is 2025-09-06 local; When evaluating; Then version_id=v1 is applied and output.effective_version=v1. Given system time is 2025-10-01T00:00:00 local; When evaluating; Then version_id=v2 is applied automatically without manual migration. Given evaluation at 2025-10-01T00:00Z for a city in UTC-7; When converting to local effective time; Then v1 remains applied until 2025-10-01T00:00 local and output includes effective_timezone and version_id. Given a published rule is edited; When saved; Then a new draft version is created with status="Draft" and does not affect evaluations until published with an effective_start. Given a draft with effective_start in the past is published; When published; Then it becomes active immediately and supersedes prior version and audit_log records publisher_id and timestamp.
Admin UI Rule CRUD, Validation, and Audit
Given an admin creates a new rule set for trade=HVAC and jurisdiction=City A; When required fields (documents, min_insurance, acceptable_license_classes, certification_bodies, grace_periods) are incomplete; Then Save is disabled and inline validation lists missing fields by name. Given all required fields are valid; When the admin clicks Save Draft; Then the draft is persisted and visible in version history with status="Draft". Given a draft exists; When the admin clicks Preview Evaluation and selects a sample vendor and property; Then the UI displays a diff of pass/fail and changed requirements versus the current active version. Given an admin publishes a draft with a future effective date; When saved; Then the UI shows status="Scheduled" with the effective date and audit trail records user, changeset, and prior values. Given the admin toggles inheritance from state baseline; When Off; Then state-required items disappear from the merged preview; When On; Then they reappear.
Rules API CRUD and Evaluation Endpoint
Given POST /rulesets with Idempotency-Key header; When called with a valid payload; Then API returns 201 Created with ruleset_id, version_id, and ETag; When retried with same Idempotency-Key; Then API returns 200 and does not create a duplicate. Given GET /evaluate?vendor_id={id}&property_id={id}; When called with valid IDs; Then API returns 200 with fields: result in {"Pass","Fail"}, deficiencies[], applied_rules[], grace_applied (boolean), version_id, evaluated_at (ISO-8601). Given load of 100 RPS with warmed cache; When measuring latency; Then p95<=300ms and p99<=600ms over a 10-minute window. Given invalid inputs; When vendor_id is unknown; Then API returns 404 with error_code="VENDOR_NOT_FOUND"; When request body fails schema; Then 400 with error_code="VALIDATION_ERROR". Given concurrent updates; When PATCH /rulesets/{id} is called with If-Match ETag that does not match current; Then API returns 412 Precondition Failed and no update is applied.
Evaluation Output Details and Proof Attachment
Given a vendor passes evaluation; When a work order is created or advanced to approval; Then compliance proof files referenced by satisfied requirements are attached to the work order and visible to landlord and approver. Given a vendor fails due to an expired document; When evaluated; Then deficiencies include document_id, expired_on, and required_grace_days and the UI deep-links to the vendor document upload screen. Given a requirement is satisfied by any of [DocA, DocB]; When the vendor supplies DocB; Then evaluation marks the requirement satisfied with doc_reference=DocB and alt_options=[DocA]. Given insurance requirement compares "per occurrence" limit; When the uploaded COI has per_occurrence=900000 and min=1000000; Then the result is a deficiency with code="INSURANCE_MIN", comparator="per_occurrence", required=1000000, actual=900000.
Vendor Suggestion and Approval Gating by Compliance
Given triage requests vendor suggestions for an electrical job; When the vendor pool contains compliant and non-compliant vendors; Then suggestions include only compliant vendors and list non-compliant vendors separately as "Unavailable due to compliance" with reasons. Given a user attempts one-click approval with a non-compliant vendor; When they click Approve; Then the system blocks the action and displays blocking reasons sourced from the latest evaluation deficiencies. Given a vendor is within grace; When suggesting and approving; Then the vendor is allowed with a "Grace Period" badge and grace_expiration date; When grace expires; Then the vendor is excluded from suggestions automatically. Given a rule version change flips a vendor from compliant to non-compliant; When triage is refreshed; Then the vendor is removed from suggestions and the change is logged on the work order timeline with rule_version_before and rule_version_after.
Continuous Expiry Monitoring & Alerts
"As a property manager, I want timely alerts before vendor credentials expire so that I can prevent service disruptions and avoid compliance risk."
Description

Continuously monitor credential effective and expiry dates using scheduled jobs and available issuer webhooks to detect revocations or changes. Generate proactive notifications to vendors and property managers at configurable intervals (e.g., 30/14/7/1 days before expiry), including required actions. Flag at‑risk vendors in the vendor directory and dashboards, and place time‑boxed holds on assignment when expirations are imminent per rule severity. Provide throttling, digesting, and timezone‑aware delivery, with in‑app, email, and SMS channels and an API for external notification providers.

Acceptance Criteria
Scheduled Monitoring Detects Upcoming Expirations
Given a credential with an expiry date, When the scheduled monitoring job runs, Then the system recalculates days-to-expiry and enqueues alerts for the configured intervals (e.g., 30/14/7/1 days) without creating duplicates. Given a credential is already expired, When the job runs, Then an overdue alert is generated once (if enabled) and future interval alerts are suppressed. Given the same credential is processed across multiple job executions, When the job runs, Then alert scheduling is idempotent and produces no duplicate queued alerts for the same interval. Given a credential or vendor is inactive/archived, When the job runs, Then no new alerts are scheduled for that credential. Given a monitoring job task fails, When retry policy is applied, Then the task is retried up to the configured attempts and the failure is logged and surfaced in monitoring/metrics.
Issuer Webhook Revocation Response
Given a signed issuer webhook indicating revocation or effective-date change, When received, Then the payload signature is validated and the event is accepted once using an idempotency key. Given a valid revocation event, When processed, Then the credential status updates to Revoked within the configured SLA and all future alerts for that credential are canceled. Given a revocation is processed, When notifications are generated, Then an immediate high-severity alert is sent to the vendor and property manager including required actions. Given webhook delivery fails upstream, When retries or replays occur, Then duplicate events do not create duplicate notifications or state changes. Given no webhook support exists for an issuer, When monitoring runs, Then polling continues to detect revocations/changes within the configured polling window.
Configurable Multi-Interval Alerts with Required Actions
Given tenant alert intervals are configured (e.g., 30/14/7/1), When a credential approaches expiry, Then alerts are sent at those intervals and suppressed at others. Given the recipient’s timezone is known, When an alert is due, Then it is delivered at the configured local send time using the recipient’s timezone; if unknown, fall back to the property or tenant default timezone. Given an alert is sent, When viewed, Then the message includes credential type, issuer, last-verified date, expiry date, and a primary call-to-action to renew or upload proof. Given channel preferences include in-app, email, and SMS, When an alert is triggered, Then delivery is attempted on all enabled channels and per-channel status is recorded (queued/sent/delivered/failed). Given a rendered template, When variables are populated, Then no placeholder remains unresolved; otherwise the send is blocked and the error is logged.
At-Risk Vendor Flagging in Directory and Dashboards
Given a vendor has any credential with days-to-expiry <= threshold T, When the vendor directory is viewed, Then the vendor is labeled At Risk with days remaining and a tooltip listing affected credentials. Given compliance dashboard widgets, When loaded, Then counts for At Risk, Expiring Soon, and Revoked match the latest monitoring/webhook results. Given filters and exports, When a compliance status filter is applied or CSV is exported, Then risk flags and days-to-expiry are included and accurate. Given a flagged vendor profile is opened, When credentials are displayed, Then affected credentials are highlighted with links to documents and audit trail.
Assignment Holds Based on Rule Severity and Time-Boxing
Given a severity rule that places holds when days-to-expiry <= S, When a work order attempts to assign the vendor, Then assignment is blocked with a clear reason and hold end time. Given a hold duration H is configured, When a hold is placed, Then it auto-expires after H or immediately upon credential renewal/verification, whichever occurs first. Given a user has assignment-override permission, When attempting assignment during a hold, Then an override is permitted only with required justification and is fully audited. Given vendor suggestions are generated, When a vendor is on hold, Then they are demoted or excluded per rule configuration and appear with a Hold tag explaining the condition.
Notification Throttling and Digesting
Given a per-tenant per-channel throttle limit L, When more than L notifications are due within a 60-minute window, Then only L are sent and the remainder are deferred without loss. Given a recipient has multiple alerts due on the same day, When digesting is enabled, Then a single daily digest is sent summarizing all pending alerts instead of individual messages. Given digest content generation, When compiled, Then alerts are grouped by property and vendor and include a single call-to-action per credential. Given items are deferred due to throttling or digesting, When the window elapses or the next digest cycle runs, Then deferred items are sent or included without duplication and with correct sequencing.
External Notification Provider API Integration
Given an external provider is configured, When alerts are ready, Then a secure API endpoint delivers HMAC-signed payloads containing recipient identifiers, channel, content, and idempotency keys. Given the provider acknowledges with 2xx and the same idempotency key, When duplicates are received, Then the provider can ignore them and the system records a single delivery. Given the provider posts delivery receipts to a callback, When receipts are received, Then per-channel status is updated (delivered, bounced, failed) and retries or alternate routing occur per policy. Given the provider is unavailable, When sends fail, Then the system applies exponential backoff and, if configured, falls back to built-in in-app/email/SMS delivery.
Auto Demote/Block Non‑Compliant Vendors
"As a dispatcher, I want non‑compliant vendors automatically excluded or deprioritized in suggestions so that I don’t accidentally assign work to vendors who increase our liability."
Description

Integrate compliance status with the vendor suggestion and dispatch engines to automatically adjust rankings or block selection based on rule outcomes and severity. Non‑compliant vendors are excluded from auto‑assignment and one‑click approvals; partially compliant vendors are demoted with visible reason codes and recommended remediation. Provide safe fallbacks to alternate compliant vendors and automatically re‑enable vendors when they regain compliance. Expose status and rationale in the UI and API, and log all changes for auditability.

Acceptance Criteria
Block Non‑Compliant Vendor From Suggestions and Auto‑Assignment
Given a vendor has compliance.status="non_compliant" and compliance.severity="block" for the work order’s trade and jurisdiction And a new work order requiring that trade and jurisdiction is created When the system generates the vendor suggestion list for that work order Then the blocked vendor is not included in the suggestion list And the vendor is not considered during auto-assignment scoring And the One-Click Approval action cannot select the blocked vendor And the UI renders a blocked badge with a visible reason code on the vendor profile And the API responses for suggestions and auto-assign exclude the vendor and include a compliance_summary.exclusions array with counts and reason_codes
Demote Partially Compliant Vendor With Visible Rationale
Given a vendor has compliance.status="partially_compliant" and compliance.severity="demote" for the work order’s trade and jurisdiction And at least one fully compliant vendor is available When the system ranks vendors for the work order Then all compliant vendors are ranked ahead of the partially compliant vendor And the partially compliant vendor is excluded from auto-assignment and One-Click Approval And the UI shows a warning badge with top reason_codes and a “Recommended remediation” link And the API returns rank_score values where the partially compliant vendor’s rank_score is lower than any compliant vendor’s rank_score and includes compliance.reason_codes
Safe Fallback To Alternate Compliant Vendors
Given the preferred vendor for an account is blocked for the required trade and jurisdiction When auto-assignment executes for a new work order Then the system selects the next-best compliant vendor according to the existing ranking rules And if no compliant vendors are available, the system does not auto-assign And the UI presents fallback options (expand search, invite vendor, schedule later) with a message explaining no compliant vendors are available And the API returns fallback_available=true and a list of compliant alternatives if any
Automatic Re‑Enable On Compliance Restoration
Given a vendor is currently blocked or demoted due to non-compliance And the vendor uploads valid documentation or an external verification webhook marks them compliant When the compliance evaluation job runs or the webhook is received Then the vendor’s compliance.status becomes "compliant" and compliance.severity is cleared within 10 minutes And the vendor is re-included in suggestion lists and eligible for auto-assignment and One-Click Approval And the UI and API reflect the new status with updated last_checked_at and source references And an audit record is created capturing old and new compliance states
UI And API Exposure Of Compliance Status And Rationale
Given a user views the vendor suggestion list for a work order in the UI Then each vendor entry displays a compliance badge showing status (compliant, partially_compliant, non_compliant) and severity (none, demote, block) where applicable And interacting with the badge reveals machine-readable reason_codes and human-readable descriptions And calling the suggestions API returns a compliance object per vendor including status, severity, reason_codes[], last_checked_at, jurisdiction_ids[], and trade_ids[] And blocked vendors are not included in suggestions, but the vendor detail API still returns their compliance object
Comprehensive Audit Logging For Compliance-Driven Changes
Given any change occurs to a vendor’s compliance.status or compliance.severity When the change is applied Then an immutable audit_log record is created with vendor_id, previous_status, previous_severity, new_status, new_severity, rule_ids[], reason_codes[], source, timestamp, and actor ("system" or user id) And audit records are accessible via the Audit UI and Audit API to authorized roles And attempts to delete or alter audit records are rejected and logged
Prevent Manual Selection Of Blocked Vendors
Given a vendor is blocked for the work order’s trade and jurisdiction When a user attempts to manually select that vendor on the work order (UI or API) Then the selection is prevented and a clear error message is shown stating the reason_codes and that the vendor is blocked And the action is disabled in the UI and the API returns 409 Conflict with error_code="VENDOR_BLOCKED" and reason_codes And the attempt is logged in the audit log with actor and reason
Compliance Evidence Attachment to Work Orders
"As a property manager, I want compliance proof automatically attached to each work order so that audits are straightforward and liability is minimized."
Description

At work‑order creation and assignment, attach point‑in‑time compliance evidence (current license, insurance COI, certifications, evaluation result) to the work order record. Generate a shareable audit bundle including document snapshots, verification timestamps, and rule set versions applied. Show appropriate details to landlords and property managers while redacting sensitive data for tenants. Ensure the API and exported PDFs include links or embedded artifacts. Block assignment when required evidence is missing unless an authorized override is applied and logged.

Acceptance Criteria
Attach Evidence at Work-Order Creation
Given a new work order is created with a pre-selected vendor for a specific trade and jurisdiction When the work order is saved Then the system attaches a point-in-time snapshot of the vendor's current license, insurance COI, required trade certifications, and latest compliance evaluation result to the work order And each attachment includes evidenceCapturedAt timestamp, verificationSource, and ruleSetVersionId And attachments are immutable once stored And the work order timeline records "Compliance evidence attached" with actor=system and timestamp
Attach Evidence on Vendor Assignment or Reassignment
Given an existing work order without a vendor or with an assigned vendor When a vendor is assigned or reassigned and the assignment is saved Then the system captures and attaches a new point-in-time compliance evidence snapshot as of the assignment time And prior evidence snapshots remain preserved and viewable in history And the assignment activity references the evidence snapshot ID And the evidenceCapturedAt timestamp is within 60 seconds of the assignment saved time
Generate Shareable Audit Bundle with Snapshots and Rule Set Versions
Given a work order with compliance evidence snapshots When a landlord or property manager requests an audit bundle Then the system generates a bundle containing: document snapshots (embedded or links), evidenceCapturedAt timestamps, verificationSource metadata, ruleSetVersionId, and pass/fail evaluation outcome And the bundle is accessible via a shareable link with configurable expiry and revocation And the bundle is downloadable as a single PDF or ZIP And the bundle includes a checksum to verify integrity And the generation event is logged with requester, timestamp, and link expiry
Role-Based Redaction for Tenant Views
Given a tenant views the work order details When the compliance section is rendered Then the tenant sees a redacted summary with vendor business name, compliance status (Pass/Fail), issue and expiry dates, and trade/jurisdiction And sensitive fields (policy numbers, full certificate addresses, full license numbers, personal identifiers, full document images) are masked or omitted And download links to original artifacts are not displayed to tenants And landlords and property managers viewing the same work order see full unredacted artifacts and metadata
API Response Includes Compliance Artifacts
Given an API client with landlord or property manager scope requests GET /work-orders/{id} When the response is returned Then the payload includes a complianceArtifacts collection with items containing type, filename, size, evidenceCapturedAt, verificationSource, ruleSetVersionId, and either embedded data (base64) or time-bound signed URLs And for tenant-scoped tokens, the complianceArtifacts are redacted to summary fields and exclude embedded data or direct links And the OpenAPI schema documents these fields and example payloads
PDF Export Includes Compliance Evidence
Given a user exports a work order to PDF When the PDF is generated Then the PDF contains a Compliance Evidence section listing each artifact with type, evidenceCapturedAt, ruleSetVersionId, verificationSource, and compliance outcome And for landlord/property manager roles, the PDF embeds the first page of each artifact or includes links to the full versions; for the tenant role, only redacted summaries are shown and links are omitted And all links use secure, expiring URLs with a maximum validity of 24 hours
Block Assignment Without Required Evidence with Authorized Override
Given a user attempts to assign a vendor to a work order and required evidence is missing or invalid per the applicable rule set When the user clicks Assign Then the assignment is blocked and a clear error lists the specific missing or invalid evidence items And the vendor is not set on the work order and no vendor notifications are sent And if a user with ComplianceOverride permission provides an override reason and scope, the assignment succeeds And the override is logged immutably with user, timestamp, reason, scope, and ruleSetVersionId, and is included in the audit bundle
Compliance Audit Trail & Reporting
"As an operations lead, I want detailed compliance audit trails and reports so that I can demonstrate due diligence and pass regulatory or insurance audits."
Description

Maintain an immutable event log of credential submissions, verifications, rule evaluations, alerts sent, overrides, and assignment decisions with actor, timestamp, and before/after states. Provide reporting with filters by date range, trade, jurisdiction, vendor, and property, and export to CSV/PDF for audits. Include KPIs such as compliance rate, time‑to‑verify, and expired‑credential incidents. Support retention policies and role‑based access, and offer a reporting API and webhooks for downstream GRC or BI tools.

Acceptance Criteria
Immutable Audit Event Log for Compliance Actions
- Rule: The system records audit events for types: credential_submitted, credential_verified, rule_evaluated, alert_sent, override_applied, assignment_decided. - Rule: Each event includes fields: event_id (UUIDv4), event_type, actor_id or "system", actor_role, entity_type, entity_id, timestamp_utc (ISO 8601 with ms), before_state (JSON), after_state (JSON), correlation_id, request_id, source_ip, prev_hash, hash. - Rule: Audit events are append-only; update/delete operations are blocked (HTTP 403) and generate a security_audit event. - Rule: Events are retrievable via UI and API within 2 seconds p95 of creation; 99.9% persisted and retrievable within 60 seconds under normal load. - Rule: Event stream hash chain validates end-to-end; tampering detection causes validation to fail and raises a security alert event.
Reporting Filters and CSV/PDF Export for Audits
- Given audit events exist across multiple trades, jurisdictions, vendors, properties, and dates When the user applies filters for date range, trade, jurisdiction, vendor, and property Then the result set matches the intersection of all filters and counts match seeded fixtures; column values match source events. - When the user exports CSV Then the file is UTF-8, comma-delimited with a header row; includes all visible columns plus filter summary; timestamps are UTC ISO 8601; up to 100,000 rows complete within 60 seconds. - When the user exports PDF Then the PDF reproduces the visible table and filter summary with page numbers and totals; up to 20 pages render within 60 seconds. - For result sets exceeding these limits, an async export job is created; users receive a downloadable link when ready; the exported data matches the filtered view exactly.
KPI Calculations: Compliance Rate, Time-to-Verify, Expired Incidents
- Rule: Compliance rate (%) = (number of vendors compliant for the entire selected period) / (number of active vendors during the selected period) * 100, rounded to 1 decimal place. - Rule: Time-to-verify (median and p90, in hours) is computed per credential from credential_submitted to credential_verified, scoped by current filters and date range. - Rule: Expired-credential incidents = count of credential instances that reached status "expired" within the selected date range and filters. - Given seeded datasets with known outcomes When filters are changed (date range, trade, jurisdiction, vendor, property) Then KPI values recompute within 2 seconds p95 and match expected results.
Retention Policy Enforcement for Audit Data
- Rule: An organization-configurable retention period (in days) is enforced for audit events and reports. - Rule: Events older than the retention period are purged by a scheduled job; a retention_audit record (event_id, purge_timestamp, retention_policy_id) is created without storing before/after content. - Rule: Purged events are no longer retrievable via UI/API; exports and KPIs exclude purged data. - Rule: Changes to retention settings apply prospectively; no event is purged before its configured retention period elapses.
Role-Based Access Controls for Audit Trail and Reporting
- Rule: Admins can view and export all audit logs and reports and manage retention settings. - Rule: Managers can view and export audit data limited to properties they are assigned; cannot modify retention settings. - Rule: Auditors have read-only access to assigned properties; can export; cannot modify settings. - Rule: Vendors can view only their own credential-related audit events; cannot access global reports or exports. - Given a user without required permissions attempts access Then the system returns 403 and records a security_audit event with actor, timestamp, and attempted resource.
Reporting API and Webhook Delivery to GRC/BI Tools
- Rule: Reporting API endpoints require OAuth2 bearer tokens; support filters (date_range, trade, jurisdiction, vendor_id, property_id), pagination (cursor), and sorting; responses conform to the published JSON schema. - Rule: p95 API response time is ≤ 2 seconds for queries returning ≤ 5,000 records; pagination is required beyond that threshold. - Rule: Webhooks can be configured for topics: vendor_non_compliant, credential_expiring, credential_expired, audit_export_ready; payloads include event_id, event_type, timestamp_utc, and HMAC signature. - Rule: Webhook deliveries are signed with HMAC-SHA256 (X-FixFlow-Signature) and retried with exponential backoff for up to 24 hours until a 2xx response is received. - Given a webhook endpoint is unreachable Then deliveries are retried and visible in a retry log; upon receiving 2xx, the event is marked delivered exactly once to the subscriber.

FairShare

Balances assignments across high‑performing vendors to prevent overload, churn, and single‑vendor dependency. Honors capacity caps, geographic quotas, and ‘learning slots’ for rising vendors—keeping your bench resilient while maintaining SLA and cost targets.

Requirements

Load-Balanced Auto-Assignment Engine
"As a property manager, I want new work orders automatically distributed across qualified vendors without overloading anyone so that we maintain SLA compliance, control costs, and keep vendors engaged."
Description

A deterministic, explainable assignment engine that balances work across high-performing vendors while honoring constraints such as skills match, SLA urgency, cost bands, capacity caps, geographic quotas, and available learning slots. The engine consumes normalized inputs from FixFlow’s smart triage (job type, priority, location, estimated duration/cost) and vendor profiles (performance tier, travel time, capacity remaining) to compute a multi-objective score and select the optimal vendor. It supports auto-assign per policy or returns a ranked shortlist with rationales, includes fallback/escalation when constraints conflict, and operates within sub-300ms latency for single assignments and efficient batching for bulk loads. Full auditability is provided via decision logs, and the component integrates with one-click approvals to trigger assignment upon approval.

Acceptance Criteria
Deterministic, Explainable Vendor Selection
Given the same normalized job input and identical vendor profiles at the same policy and engine versions When the engine runs an auto-assignment three times within a 1-minute window Then the selectedVendorId, the ordered shortlist (top 5), and all score breakdowns are identical across runs Given two or more vendors have equal composite scores When a tie-break is required Then the engine applies tie-breakers in order: lower travelTimeMinutes, then lower estimatedCost, then lexicographically smaller vendorId, and records the applied tie-breaker in the rationale Given an assignment decision exists When the decision is retrieved via API Then the response contains compositeScore, componentScores (skills, SLA, cost, travel, capacity, geo), eligibility flags, and the final rationale explaining selection
Eligibility Constraints Enforcement
Given a job requiring skills set S When candidates are evaluated Then any vendor missing any skill in S is marked ineligible and excluded from ranking Given a vendor has capacityRemaining = 0 When candidates are evaluated Then that vendor is ineligible and the rationale includes "capacity cap reached" Given a vendor has a geographic quota limit Q for a region and currentAssignedInRegion = Q When candidates are evaluated for a job in that region Then that vendor is ineligible and the rationale includes "geo quota exceeded" Given a policy defines learningSlotsPerWeek = L for rising vendors When a rising vendor already has L learning assignments in the current ISO week Then additional learning-slot assignments are not auto-assigned to that vendor
SLA and Cost Band Policy Compliance
Given at least one eligible vendor can meet the job's SLA target When a vendor is selected Then the selected vendor meets the SLA target Given policy.allowCostBandOverflow = false and the job has a costBand When candidates are evaluated Then vendors whose estimatedCost is outside the costBand are ineligible Given policy.allowCostBandOverflow = true and at least one in-band vendor exists When a vendor is selected Then the selected vendor is in-band; out-of-band vendors may appear in the shortlist with rationale "cost band overflow (penalized)" Given multiple eligible vendors are within epsilon = 0.01 compositeScore of each other When ranking is produced Then the vendor with lower travelTimeMinutes is ranked higher
Latency and Batch Throughput Thresholds
Given a single assignment request under <= 50 requests/second system load When processed end-to-end at the API boundary Then p95 latency <= 300 ms and p99 latency <= 500 ms with 0 timeouts Given a batch request of 200 jobs When processed via the batch endpoint Then 95% of jobs receive a decision within 5 seconds total wall-clock and 99% within 8 seconds Given a burst load of 200 requests/second sustained for 60 seconds When the system processes assignment requests Then decision error rate < 0.5% and p95 latency <= 400 ms
Policy-Driven Auto-Assign vs Shortlist and Approval Integration
Given policy.autoAssign = true and the job requires approval When approval is granted with idempotencyKey K Then an assignment is created within 2 seconds, exactly once per K, and an assignmentCreated event is emitted Given policy.autoAssign = true and approval has not yet been granted When an assignment is requested Then no vendor is assigned and a ranked shortlist (top K as per policy) is returned with rationales Given policy.autoAssign = false and shortlistSize = K When an assignment is requested Then no vendor is assigned and a ranked shortlist of exactly K eligible vendors is returned with scores and rationales
Fallback and Escalation Handling
Given no vendors satisfy mandatory constraints When assignment is attempted Then the engine returns selectedVendorId = null, decisionStatus = "Unassigned", and triggers escalation per policy (notification channel) within 5 seconds Given policy.permitConstraintRelaxation = true and relaxOrder = [geoQuota, costBand] When no eligible vendor exists Then the engine relaxes constraints in the specified order until a non-empty eligible set is found, includes relaxation steps in rationale, and selects from the first viable set Given constraint relaxation completes with no eligible vendors When escalation is emitted Then the escalation payload lists blockingConstraints[] with counts by type (skills, capacity, geoQuota, costBand, SLA)
Decision Logging and Auditability
Given any assignment decision (selected or unassigned) When the decision log is retrieved by decisionId Then the log includes: requestPayloadHash, vendorPoolSnapshotHash, policyVersion, engineVersion, timestamps, latencyMs, selectedVendorId (or null), shortlist with scores, constraints evaluated (mandatory/optional), and tie-breakers applied Given a decision log exists When an update is attempted Then the original record is immutable and a new revision is created with parentDecisionId linking to the original Given logs within the last 180 days When fetched via the audit API Then retrieval success rate is 100% and response includes a stable correlationId for cross-system tracing
Vendor Capacity & Calendar Caps
"As an operations lead, I want to set and enforce vendor capacity limits so that assignments respect availability and prevent overload and burnout."
Description

Configurable hard and soft capacity caps per vendor for daily, weekly, and concurrent job limits, with working hours, time-off/blackout dates, and emergency-only windows. Capacity is decremented and replenished automatically based on job lifecycle events (scheduled, in-progress, completed/cancelled) and supports manual overrides with reason codes. The module exposes UI and API for vendors and managers to set caps, enforces timezone-aware scheduling, and surfaces proactive warnings when incoming demand is projected to exceed capacity. Integrates with the assignment engine to block or penalize vendors at capacity and with notifications to prompt capacity adjustments.

Acceptance Criteria
Set Vendor Capacity Caps via UI and API
Given I am a manager with permission "Capacity:Edit" When I set hard and soft caps for daily, weekly, and concurrent jobs in the UI with vendor timezone selected Then the system validates soft cap <= hard cap for each type and persists the values, returns 200, and displays "Capacity updated" within 1 second Given I call PUT /vendors/{vendorId}/capacity with a valid payload {timezone, caps:{daily:{hard,soft}, weekly:{hard,soft}, concurrent:{hard,soft}}} and valid auth Then response is 200 and new caps are stored; otherwise return 403 when unauthorized Given caps are invalid (negative, non-integer, soft > hard, unsupported timezone) When submitted via UI or API Then response is 422 with field-level errors and no change saved Given an update is saved Then an audit log entry records actor, before/after values, timestamp, and source (UI/API)
Automatic Capacity Decrement/Replenishment by Job Lifecycle
Given a job is assigned to a vendor and an appointment is scheduled for a timeslot When status changes to Scheduled Then the vendor’s daily and weekly scheduled counters increment for the appointment date(s) Given status changes to In Progress at time T Then the vendor’s concurrent counter increments by 1 until the job is Completed or Cancelled Given a job is rescheduled from timeslot A to B Then counters for A decrement and counters for B increment atomically Given a job is Completed or Cancelled Then scheduled and concurrent counters decrement within 5 seconds and never become negative Given duplicate lifecycle events are received Then counter updates are idempotent and applied exactly once per transition
Assignment Engine Enforcement of Hard and Soft Caps
Given projected counters for the requested timeslot are at or exceed the hard cap (daily, weekly, or concurrent) When auto-assignment runs Then the vendor is excluded from consideration Given a vendor is at or above soft cap but below hard cap for the timeslot When scoring vendors Then apply at least a 30% score penalty and select only if higher-scoring options are unavailable Given a manual assignment attempts to use a vendor at hard cap and no override is provided Then block the action with an explanatory message and suggest alternatives Given emergency-only windows are configured for a vendor When the job is not flagged Emergency and its appointment falls within such a window Then the vendor is excluded
Timezone-Aware Working Hours, Time-Off, and Emergency Windows
Given a vendor has working hours, blackout dates, and emergency-only windows in timezone TZ When a manager schedules from a different timezone Then the UI shows vendor-local time and prevents bookings outside working hours or on blackout dates unless the job is Emergency Given a daylight saving time transition in TZ When creating appointments on the transition day Then times are normalized correctly and no appointment is placed in a nonexistent or duplicated hour; conflicts are flagged Given an appointment violates availability rules When saving Then return 409 Conflict with the specific violated rule (working hours, blackout, emergency-only) and do not save Given availability rules change after scheduling When a conflict is introduced Then affected appointments are highlighted and a reschedule prompt is shown without auto-cancelling
Proactive Capacity Forecasting and Notifications
Given current backlog and historical assignment rates When forecast shows any cap will exceed soft cap by >10% or hit hard cap within 7 days Then create a capacity risk warning with projected date and magnitude Given a capacity risk warning is created and notifications are enabled Then send email and in-app notifications to vendor and manager within 15 minutes including a link to adjust capacity Given multiple warnings for the same vendor and window When severity does not increase by >=10% Then rate-limit to at most 1 notification per vendor per 24 hours Given a warning condition is resolved (capacity raised or load reduced) Then auto-close the warning and update dashboards within 15 minutes
Manual Override With Reason Codes and Audit
Given a manager with permission "Capacity:Override" attempts to assign beyond a hard cap When a required reason code is selected and optional notes are provided Then the assignment succeeds, counters reflect the assignment, and an audit record stores reason, actor, timestamp, and optional expiration Given no reason code is provided When attempting an override Then block with validation error and make no changes Given an override is time-bounded When the window expires Then enforcement reverts automatically to normal rules Given a vendor user attempts to override Then return 403 and present a capacity change request option instead
Geo-Quota & Service Area Controls
"As a portfolio manager, I want assignments to honor each vendor’s defined service area and regional quotas so that travel times are reasonable and coverage remains balanced across neighborhoods."
Description

Per-vendor service area definitions using radius, zip/postal lists, or polygon geofences, coupled with optional geographic quotas to distribute assignments fairly across regions. Supports travel-time thresholds using mapping APIs, address normalization/geocoding, and real-time checks at assignment time. When a region’s quota or reachability is exceeded, the system rebalances to nearby eligible vendors or queues for manager review based on policy. Administrators can configure precedence between geographic quotas and other constraints, and the UI visualizes coverage gaps and quota utilization.

Acceptance Criteria
Vendor Service Area Definitions (Radius, ZIP, Polygon)
Given a vendor defines a 10-mile radius from a stored base coordinate, When a work order geocodes within 10.0 miles, Then the vendor is eligible; When it is >10.0 miles, Then the vendor is ineligible. Given a vendor configures a ZIP/postal allowlist [94103, 94107], When a normalized work order postal code matches the list, Then the vendor is eligible; When it does not, Then the vendor is ineligible. Given a vendor draws a polygon service area (supporting holes), When a work order point-in-polygon test is true and not within an excluded hole, Then the vendor is eligible; Otherwise, the vendor is ineligible. Given multiple service area types are configured, When eligibility is calculated, Then the union of all enabled shapes/lists determines eligibility unless the vendor selects "restrict to most specific" which limits to the intersecting area.
Address Normalization & Geocoding Accuracy
Given an input address with inconsistent casing/abbreviations, When normalized, Then the stored address conforms to the locale’s postal standards and includes a standardized postal code where available. Given a valid address, When geocoded, Then coordinates are returned with an estimated accuracy radius ≤ 50 meters; If > 50 meters, Then a fallback geocoder is attempted; If still > 50 meters or unavailable, Then the work order is flagged for manual review with reason "low_geocode_accuracy". Given geocoding rate limits or transient errors, When geocoding fails, Then the system retries up to 3 times with exponential backoff and logs attempt counts and final status.
Travel-Time Threshold Eligibility Check
Given a vendor travel-time threshold of 30 minutes, When computing drive time from the vendor’s nearest base to the job at the assignment evaluation time, Then the vendor is eligible if ETA ≤ 30 minutes; Otherwise, ineligible. Given live traffic data is unavailable, When computing travel time, Then the system falls back to typical traffic profiles and records the fallback in the assignment audit log. Given a work order scheduled for a future time T, When computing eligibility, Then time-dependent travel-time estimates use T if supported; Otherwise, typical profiles are used and noted in the audit log.
Geographic Quota Enforcement & Distribution
Given region "East Bay" with a 30-day rolling window and quotas Vendor A=40%, Vendor B=60%, When assigning the next eligible work order in that region, Then the engine selects the vendor that keeps utilization within ±5% of target, subject to other active constraints. Given assigning to a vendor would exceed its regional quota beyond the tolerance, When evaluating vendors, Then the engine skips that vendor and selects the next eligible one; If none remain, Then the assignment is sent to Manager Review with reason "quota_exceeded". Given a vendor’s quota is under target and the vendor is otherwise eligible, When ties occur, Then the assignment is awarded to the most under-target vendor; tie-breaking is deterministic (e.g., lowest last-assigned timestamp).
Constraint Precedence Configuration
Given an administrator sets precedence = [Geographic Quotas > Travel-Time > Capacity], When an assignment is evaluated, Then constraints are applied in that order and the audit log records the precedence version used. Given an administrator updates the precedence order, When saved, Then the new order takes effect for new assignments within 1 minute and the change is versioned with timestamp and user. Given constraints are mutually unsatisfiable, When assignment evaluation completes, Then the engine follows precedence to select a resolution path and, if policy "require approval on waiver" is enabled, routes to Manager Review with the waived constraint identified.
Real-time Rebalance & Manager Review Queue
Given a region’s quota or reachability is exceeded at assignment time, When the first-choice vendor is disqualified, Then the system rebalances to the nearest eligible vendors within 10% of the travel-time threshold before queueing. Given no eligible vendors remain after rebalance, When queuing, Then the assignment is placed into Manager Review with reason codes from {quota_exceeded, unreachable, capacity_full} and includes a recommended top-3 vendor list by SLA adherence. Given policy "auto-queue" is disabled, When no eligible vendor is found, Then the system presents an in-app decision modal to a manager within 3 seconds and records any override in the audit log.
Coverage Gaps & Quota Utilization Visualization
Given an administrator opens the coverage map, When the UI loads, Then vendor service areas render (radius circles, polygons, ZIP clusters) and uncovered gaps are shaded with total gap area displayed in mi²/km². Given regional quotas are configured, When viewing the utilization dashboard, Then each vendor shows current-period utilization %, remaining assignments to target, and status badges (green within ±5%, yellow outside ±5% to ±10%, red > ±10%). Given filters for date range, region, and vendor, When applied, Then both map and dashboard update within 2 seconds and totals reconcile with assignment history for the filtered period.
Learning Slots Allocation & Safety Rails
"As a vendor manager, I want to allocate controlled learning opportunities to up-and-coming vendors so that we develop bench depth without compromising service quality or SLAs."
Description

Policy-driven reservation of a configurable percentage or count of work orders as learning opportunities for rising vendors, scoped by job category and price band. Safety controls exclude critical emergencies, cap simultaneous learning jobs, and require heightened oversight (e.g., photo checkpoints, senior approval) for higher-risk categories. The system monitors outcomes (first-time fix, callbacks, tenant ratings) and dynamically adjusts learning slot availability based on performance signals. Assignment engine treats learning slots as a constraint while ensuring SLA and cost guardrails are not violated.

Acceptance Criteria
Configurable learning slot reservation by category and price band
Given an active policy defines learning slots as a percentage or count per job category and price band When work orders matching the policy scope are created or move to Ready for Assignment Then the system tags up to the configured percentage or count as learning slots within that scope for the current allocation cycle And learning slot counts (total, used, remaining) update in real time for the policy scope And each tagged work order carries a 'learning_slot=true' attribute and policy ID in the audit log
Exclusion of critical emergencies from learning slots
Given the policy specifies excluded priorities/categories and an Emergency flag When a work order is marked Emergency or matches an excluded priority/category Then it is never tagged as a learning slot nor routed to a learning vendor And any pre-reserved learning slot for that work order is released back to the pool And the audit trail records reason_code='excluded_emergency' or 'excluded_category'
Per-vendor simultaneous learning jobs cap
Given a configurable cap N of simultaneous learning jobs per vendor (global or vendor-specific) When assignment would cause a vendor’s active learning jobs (Scheduled or In Progress) to exceed N Then that vendor is ineligible for the learning-slot assignment And the engine evaluates next eligible vendor(s) until constraints are satisfied or candidates are exhausted And if no eligible vendor remains, the job is assigned via standard routing without consuming a learning slot and the exception is logged
Heightened oversight on higher-risk learning jobs
Given a job category is flagged high-risk in policy with required checkpoints and approver role When a learning slot assignment is created for that category Then pre-dispatch requires approval by a user with the configured approver role And workflow enforces photo checkpoints (arrival, pre-fix, post-fix) with minimum photo count per checkpoint before the status can advance And checkpoint approvals and timestamps are recorded; failure to meet checkpoints triggers escalation per policy
Dynamic adjustment of learning slots based on outcomes
Given configurable evaluation window W, thresholds for first-time fix rate, callback rate, and minimum tenant rating, and adjustment step/bounds When nightly evaluation runs Then the system computes metrics per vendor-category-price band over completed learning jobs in window W And if metrics meet thresholds for K consecutive evaluations, increase learning slot allocation by step up to the maximum bound And if metrics fall below thresholds, decrease allocation by step down to the minimum bound or suspend learning slots for that vendor-scope And all adjustments are persisted and auditable with before/after values and rationale
Assignment engine respects learning-slot constraint and guardrails
Given remaining learning slots exist for the job’s category and price band and guardrails for SLA target and cost ceiling are configured When the assignment engine selects a vendor Then it prioritizes eligible rising vendors to fill learning slots only if predicted completion meets SLA and estimated cost is within the ceiling And geographic eligibility and capacity caps must be satisfied And if no vendor satisfies all constraints, assign via standard routing without consuming a learning slot and record reason_code='guardrail_conflict'
Performance Scoring & Tiering
"As a regional director, I want transparent, data-driven vendor tiers that inform assignment decisions so that we reward quality and continuously improve outcomes."
Description

A time-decayed vendor performance model that computes composite scores from metrics such as first-time fix rate, on-time arrival, SLA adherence, tenant satisfaction, cost variance, and callback rate. Supports minimum sample thresholds, outlier detection, and category-specific scoring to reflect specialized skills. Produces vendor tiers that feed the assignment engine and provides weight configuration per organization. Exposes transparent score breakdowns to build trust and includes APIs for analytics and reporting, enabling continuous tuning and A/B testing of scoring weights against SLA and cost outcomes.

Acceptance Criteria
Nightly Composite Score with Time Decay
- Given an organization with configured metric weights, when the nightly scoring job runs at 02:00 in the organization’s local timezone, then a composite vendor score between 0.00 and 100.00 is produced with two-decimal precision for each vendor using only eligible metrics. - Given time-stamped metric inputs, when the composite score is computed, then an exponential time-decay with a 30-day half-life is applied to each observation before aggregation. - Given successful execution, when the scoring job starts, then all vendor scores are updated within 15 minutes and the last-computed timestamp is recorded per vendor. - Given duplicate or stale observations, when inputs are aggregated, then only the most recent final state per work order is included once.
Minimum Sample Thresholds and Insufficient Data Handling
- Given a metric and category, when the vendor has fewer than 20 completed jobs in the last 90 days for that metric-category, then that metric-category is excluded from the composite score and marked Insufficient Data. - Given excluded metric-categories, when the composite score is computed, then remaining included metric weights are re-normalized to sum to 100% for that vendor. - Given no metric-category meets the minimum sample threshold for a vendor, when scoring completes, then the vendor’s composite score is null and the vendor is flagged Unrated with reason Insufficient Data. - Given Insufficient Data exclusions, when the score breakdown is exposed, then sample sizes and exclusion reasons are shown per metric-category.
Outlier Detection and Exclusion
- Given a metric distribution per organization and category over the last 180 days, when computing aggregates, then observations with an absolute z-score greater than 3.0 are excluded from the vendor’s metric inputs. - Given outliers are excluded, when the score breakdown is generated, then the count of excluded outliers per metric-category is displayed and exposed via API. - Given outlier exclusion, when aggregates are recomputed, then the vendor’s composite score is recalculated using only inlier observations in the same run.
Category-Specific Scoring and Fallback
- Given organization-defined job categories, when a vendor meets the minimum sample threshold in a category, then a category-specific score between 0.00 and 100.00 is produced alongside the overall score. - Given a job in category X, when the assignment engine requests a score, then the category-specific score for X is returned if available; otherwise the overall score is returned. - Given the vendor lacks sufficient data in a category but has an overall score, when category scoring is requested, then the system returns Fallback to Overall with reason Insufficient Data.
Organization-Configurable Weights and A/B Testing
- Given an org admin, when weights for metrics are updated via UI or API, then each weight must be between 0 and 100 and the sum must equal 100 or the update is rejected with a validation error. - Given a successful weight update, when saved, then an audit log entry records user, timestamp, old values, new values, and change reason. - Given updated weights, when the next scoring run executes, then the new weights are applied; when the admin triggers Recompute Now, then scores are recalculated within 5 minutes. - Given an A/B test is created with Weight Set A and Weight Set B and a 50/50 split, when jobs are evaluated for assignment, then vendors’ scores are computed against their assigned variant and at least 95% of eligible jobs are correctly bucketed per split. - Given an active A/B test, when outcomes are collected, then SLA attainment and average cost per job are reported per variant daily, and the admin can end the test and promote the winning weight set in one action.
Transparent Score Breakdown UI and APIs
- Given a vendor and date range, when viewing the score breakdown, then the UI shows per metric-category: raw value, sample size, outliers removed, normalization method, applied weight, weighted contribution, and time-decay half-life used. - Given a breakdown view, when Export is clicked, then a CSV containing the breakdown plus the composite score and last-computed timestamp is downloaded within 5 seconds. - Given an authenticated API client with read scope, when calling GET /vendors/{id}/scores?category=…&range=…, then the response includes overall and category scores, breakdown details, sample sizes, outlier counts, and computation timestamp with p95 latency under 500 ms for single-vendor requests. - Given pagination parameters, when listing scores via GET /vendors/scores, then results are paginated, sortable by score, filterable by tier and category, and include total count metadata.
Tier Generation and Assignment Engine Consumption
- Given organization-defined tier thresholds (default: S ≥ 85, A 70–84.99, B 55–69.99, C < 55), when composite or category scores are updated, then each vendor is assigned a tier per overall and per category accordingly. - Given a vendor’s score crosses a tier threshold, when the scoring job completes, then the vendor’s tier changes are published to the assignment engine within 5 minutes. - Given a vendor has a null score due to insufficient data, when tiers are assigned, then the vendor is placed in Provisional tier which is eligible only for learning slot assignments. - Given an API client, when calling GET /vendors/{id}/tier, then the current overall tier, per-category tiers, thresholds used, and effective timestamp are returned.
SLA/Cost Guardrails & Conflict Resolution
"As a dispatcher, I want the system to enforce SLA and budget constraints during assignment and clearly explain trade-offs so that we avoid breaches and make informed overrides when needed."
Description

Hard and soft guardrails that ensure every assignment meets SLA deadlines and target cost bands. A constraint solver evaluates candidate vendors against due-by times, travel/ETA predictions, and estimated job costs; if fairness or quotas would breach a guardrail, the system applies policy-based conflict resolution (e.g., relax fairness first, then geo-quota, then learning slot) and surfaces an explanation. Provides what-if simulations, breach alerts, and manager override paths with audit logs. Integrates with budgeting to enforce per-category ceilings and with notifications to escalate when no feasible assignment exists under current constraints.

Acceptance Criteria
SLA Deadline Enforcement on Assignment
Given a maintenance job with a due-by timestamp D and candidate vendors with predicted ETAs When the solver evaluates candidates Then it must exclude any vendor with ETA > D And it must assign a vendor with ETA <= D or declare Infeasible-SLA within 60 seconds And it must persist ETA inputs and the selected ETA in the assignment record
Per-Category Budget Ceiling Enforcement
Given a job category with remaining budget ceiling B and candidate vendors with estimated costs E_i When the solver evaluates candidates Then it must exclude any candidate with E_i > B And if all candidates have E_i > B, it must mark the job Infeasible-Budget and alert the manager within 1 minute And it must record B, all E_i, decision code, timestamp, and actor in the audit log
Target Cost Band Selection
Given a target cost band [L,U] for the job category and candidates that meet SLA and budget ceilings When selecting a vendor Then it must choose the candidate with minimum estimated cost E where L <= E <= U And if no candidate falls within [L,U], it must mark the job Infeasible-CostBand and notify the manager; no assignment is made
Policy-Based Conflict Resolution Order and Trace
Given constraints: SLA (hard), Target Cost Band (hard), Budget Ceiling (hard), Fairness Balance, Geographic Quota, Learning Slot When no candidate satisfies all constraints Then the solver must sequentially relax constraints in this order until a feasible candidate is found: Fairness Balance, Geographic Quota, Learning Slot And it must never relax SLA, Target Cost Band, or Budget Ceiling And it must generate an explanation trace listing constraints checked, each relaxation step, candidates considered, and the final decision, stored with the assignment
Manager Override with Guardrail Enforcement and Audit
Given a user with Manager role reviews an infeasible or auto-assigned job When the user initiates Override and selects a vendor Then the system must block submission if the selection violates SLA, Target Cost Band, or Budget Ceiling and display the violated guards And it must allow overrides that only violate Fairness Balance, Geographic Quota, or Learning Slot And it must require an override reason and confirmation And it must write an audit log capturing user, timestamp, chosen vendor, violated constraints, previous recommendation, and comments
What-If Simulation for Constraint Tuning
Given a job and simulation inputs (e.g., adjust due-by, cost band, quotas) When a user runs a simulation Then the system must return a ranked list (up to 5) of feasible vendors with predicted ETA, cost, and constraint statuses And it must assign a simulation ID and persist parameters and results without altering live assignments And it must indicate when no feasible solution exists under the simulated inputs
No-Feasible-Assignment Escalation and Breach Alerts
Given the solver finds no feasible assignment after applying the relaxation policy When the decision is finalized Then the system must send a breach alert with job ID, violated constraints, and the top 3 closest candidates with trade-offs to the designated channels And it must escalate to the next tier if unacknowledged within 15 minutes and deduplicate repeated alerts for the same job And it must include a deep link to run a what-if simulation or initiate a manager override

Score Lens

Makes Vendor Radar rankings explainable with a transparent breakdown of speed, quality, cost, proximity, and scope fit. Offers ‘why ranked’ insights for managers and improvement tips for vendors—building trust, enabling coaching, and driving network‑wide performance gains.

Requirements

Transparent Scoring Engine
"As a property manager, I want a transparent scoring model with adjustable weights so that I can align vendor rankings to my portfolio’s priorities and justify selections to stakeholders."
Description

Implements a configurable, explainable algorithm that computes Vendor Radar rankings from five core criteria—speed, quality, cost, proximity, and scope fit—using normalized metrics, recency weighting, and minimum sample thresholds. Supports per-account weight configuration with sensible defaults, versioned weight profiles, and preview/sandbox mode to assess impact before publishing. Handles missing or sparse data via fallback logic and clearly marks "insufficient data" cases in the UI. Provides per-vendor score cards with criterion-level contributions, normalization details, and confidence indicators. Integrates with FixFlow data sources (work orders, tenant ratings, rework tickets, invoices, vendor catalogs, and geo data) and exposes a scoring service with P95 < 300ms latency for visible vendor lists via caching and incremental recompute. Expected outcomes: intelligible rankings that can be tuned to business goals while maintaining consistency and performance at portfolio scale.

Acceptance Criteria
Per-Account Weight Configuration & Defaults
- Given an account with no custom profile, When scores are computed, Then the system applies a default weight profile whose weights sum to 1.0 and each weight is within [0,1]. - Given two different accounts A and B, When A updates its weights, Then B’s active profile remains unchanged. - Given an invalid weight set whose sum != 1.0 or containing any negative value, When saving, Then the API returns 422 with a validation error and no changes are persisted. - Given a successful save, When retrieving the active profile, Then the response includes profileId, profileVersion, and the effective weights.
Versioned Weight Profiles & Audit Trail
- Given an existing active profile, When a new profile is created and published, Then profileVersion increments and the previous profile remains preserved and read-only. - Given a published profile, When fetching audit history, Then it includes createdBy, createdAt, publishedBy, publishedAt, and a diff from the previous profile. - Given a rollback request to a prior version, When executed, Then the selected prior version becomes the active profile and the rollback is recorded in the audit log.
Preview/Sandbox Impact Assessment Before Publish
- Given a draft weight profile, When preview is executed, Then the service returns projected overall scores, ranks, and per-criterion contributions for visible vendors along with deltas versus the current active profile. - Given a draft profile, When preview is executed repeatedly without data changes, Then results are deterministic and identical to the previous preview. - Given a draft profile that passes validation, When it is published, Then post-publish rankings match the most recent preview for the same data snapshot. - Given a draft profile that fails validation, When publish is attempted, Then publish is rejected with HTTP 422 and no changes occur to the active profile.
Explainable Score Card & "Insufficient Data" UI
- Given a vendor with complete data, When viewing the score card, Then the UI displays overall score (0–100), criterion-level contributions summing to 100%, normalized values with method and range used, sample counts, and a confidence indicator. - Given a vendor where a criterion’s sample count is below threshold, When viewing the score card, Then that criterion is labeled "Insufficient data" with a tooltip showing the threshold and the overall score excludes that criterion with weights redistributed proportionally among remaining criteria. - Given a vendor score card, When inspecting accessibility, Then all explanatory labels are screen-reader readable and meet WCAG AA contrast for text.
Data Integration & Fallback Logic
- Given connected data sources (work orders, tenant ratings, rework tickets, invoices, vendor catalogs, geo data), When the engine runs, Then each criterion is computed without runtime errors and the inputs used are listed in the score card details. - Given a temporary outage for any single data source, When the engine runs, Then only the affected criteria are flagged as "Insufficient data" and scoring proceeds using fallback redistribution with HTTP 200 responses. - Given a vendor missing geo coordinates, When computing proximity, Then the proximity criterion is flagged "Insufficient data" and the score is computed from remaining criteria.
Performance & Caching for Visible Vendor Lists
- Given a warmed cache for an account and a list of up to 100 vendors, When requesting GET /scores for that list 1000 times under load, Then P95 latency is < 300 ms as measured at the service boundary. - Given a vendor’s underlying data change, When requesting the same list, Then only the changed vendor is recomputed and the new score appears within 2 seconds while others are served from cache. - Given a cold start after deploy, When fetching the first list, Then the request succeeds and subsequent requests meet the P95 < 300 ms target after cache warm-up.
Recency Weighting & Minimum Sample Thresholds
- Given two datasets identical except event recency, When computing criterion scores, Then the dataset with more recent events yields a higher recency-weighted score for that criterion. - Given per-criterion minimum sample thresholds, When a vendor’s sample count is below threshold, Then that criterion’s contribution is excluded and redistributed; the UI shows the threshold and current count. - Given configuration for thresholds and recency decay, When changed in the active profile, Then results change accordingly and the new parameters are reflected in the score card details.
Why‑Ranked Breakdown UI
"As a property manager, I want a clear breakdown of why each vendor is ranked where they are so that I can quickly audit decisions and choose the best vendor with confidence."
Description

Adds an interactive "Why ranked" panel to Vendor Radar that visualizes each vendor’s total score, criterion breakdown, and key drivers (e.g., average time-to-first-response, first-time-fix rate, invoice variance, travel distance, category match). Enables drill‑down to underlying jobs, dates, and evidence with pagination and filters, plus quick comparisons across shortlisted vendors. Includes tooltips, plain‑language explanations, confidence badges, accessibility support (WCAG AA), and responsive layouts for mobile web. Provides inline controls to simulate weight changes (what‑if) without publishing. Integrates with the Transparent Scoring Engine and respects permissions so vendors only see their own data while managers see portfolio‑wide insights. Expected outcomes: faster, defensible vendor selection and actionable coaching conversations.

Acceptance Criteria
Score Breakdown Visibility and Accuracy
Given a manager user opens the Why‑ranked panel for a vendor from Vendor Radar When the data loads from the Transparent Scoring Engine Then the total score (0–100) and five criteria (speed, quality, cost, proximity, scope fit) are displayed with percentage contribution bars And the five criteria percentages sum to 100% ±0.1 And each criterion score matches the Transparent Scoring Engine response for the same vendor and timestamp within ±0.5 points And key driver metrics (time‑to‑first‑response, first‑time‑fix rate, invoice variance, travel distance, category match) display current values and 30‑day trend arrows And each metric includes a plain‑language tooltip ≤160 characters with units and source And the panel shows a “Data as of” timestamp and flags data older than 24h with a “Stale” badge And a confidence badge is displayed per vendor with level High (N≥50), Medium (15–49), or Low (N<15) based on jobs in the last 180 days, with a tooltip explaining the level and improvement tips And for any criterion with sample size <5 jobs, a “Limited data” indicator is shown with a cautionary tooltip And on API error a non‑blocking error state is shown with Retry and no stale data is rendered
Drill‑Down to Supporting Jobs with Filters and Pagination
Given a user clicks any criterion bar or driver metric within the panel When the drill‑down view opens Then a list of jobs is shown with columns: Job ID (link), Date, Property, Category, Outcome (e.g., first‑time‑fix), Amount, and Evidence icons (photos, invoices, messages) And the default date filter is last 90 days; user can filter by date range, property, category, outcome, and amount range And pagination returns 25 rows per page with total count; Next/Prev are disabled when not applicable And sorting by Date and Amount toggles ascending/descending and updates within 300ms And opening an evidence item loads a modal with the artifact; median time ≤800ms, P95 ≤2s on 4G And the number of jobs shown equals the count returned by the API for the applied filters
Quick Comparison Across Shortlisted Vendors
Given a manager selects 2–4 vendors from the shortlist When Comparison mode is enabled Then side‑by‑side cards are rendered with aligned axes for all criteria and identical scales And top 3 differences per criterion are highlighted with arrows and absolute/percent deltas And the comparison reflects the same weight profile as the current view, including any simulated weights And the selection persists until cleared or session ends And on mobile (<768px) the cards render as a swipeable carousel with visible page indicators and tap targets ≥44×44dp
What‑If Weight Simulation (Non‑Persisted)
Given a manager user opens the weight simulation controls When the user adjusts criterion sliders (0–100 each) Then the system enforces a total weight sum of 100% and prevents saving to the backend And recalculated criterion and total scores update locally within 300ms of interaction And a “Simulated” badge and “Reset to default” control appear; Reset restores server weights instantly And simulation state is scoped to the user session and cleared on refresh or sign‑out And vendor users cannot access the simulation controls; the controls are hidden and routes are guarded
Role‑Based Visibility and Data Partitioning
Given a vendor user views the Why‑ranked panel Then only their vendor’s data is displayed; multi‑vendor comparison and portfolio aggregates are hidden And attempts to access other vendor IDs are blocked with HTTP 403 and a friendly message Given a manager user views the panel Then all vendors in their authorized portfolios are accessible; comparison and drill‑down include only those portfolios And all panel accesses are audit‑logged with user ID, role, timestamp, and vendor IDs viewed
Accessibility (WCAG 2.1 AA) Compliance
Rule: - All interactive elements are reachable and operable via keyboard (Tab/Shift+Tab/Enter/Space); focus order is logical and visible (contrast ≥3:1) - Text contrast ≥4.5:1; icons/non‑text UI contrast ≥3:1; no information is conveyed by color alone - Charts expose values via text equivalents and ARIA; tooltips are screen‑reader accessible and dismissible - Dynamic updates announce via ARIA live regions without causing focus traps - Provide a visible “Skip to content” link on focus - No content flashes more than 3 times per second - Form controls and sliders expose labels, min/max, and current value to assistive technologies
Responsive Layout and Performance
Given a mobile device (360×640) on 4G opens the panel Then First Contentful Paint ≤1.8s and Time to Interactive ≤3.5s for this route; additional JS for this feature ≤250KB gzipped And charts and controls adapt without horizontal scroll; tap targets ≥44×44dp; CLS <0.1 on load and orientation change Given a desktop viewport ≥1280px Then the comparison grid shows at least 3 vendor cards per row and resizes smoothly within 300ms after window resize
Score Data Lineage & Audit Trail
"As an operations lead, I want end‑to‑end traceability of the inputs and settings behind each ranking so that I can resolve disputes and meet compliance requirements."
Description

Captures and surfaces full provenance for every score: data sources, timestamps, normalization methods, weight profile version, and any manual overrides. Provides a "View evidence" drawer that lists contributing events (work orders, ratings, invoices), links to source records, and hash-based integrity checks. Stores immutable audit logs for weight changes, configuration publishes, and override actions with actor, timestamp, and reason. Supports exportable snapshots of rankings with their configuration for compliance reviews and dispute resolution. Integrates with existing FixFlow logging and retention policies; redacts tenant PII from vendor-facing views. Expected outcomes: traceable, defensible rankings that withstand internal and external scrutiny.

Acceptance Criteria
Evidence Drawer Displays Full Score Provenance
Given a vendor has a computed Score Lens score for a category and time window When a manager clicks "View evidence" for that score Then a drawer opens listing all contributing events (work orders, tenant ratings, invoices) with columns: Event Type, Source ID, Timestamp, Contribution (points or percentage), Data Source, and Normalization Method And then the drawer displays the applied Weight Profile Version ID and its effective date/time And then each Source ID links to the underlying source record, opening in a new tab and returning HTTP 200 with matching ID And then the drawer renders within 2 seconds for up to 200 contributing events and provides pagination for larger sets And then items with unavailable sources show a "Source unavailable" badge and are still counted using their stored contribution And then the sum of contributions equals component subtotals and the overall score within ±0.01
Hash-Based Integrity Verification for Evidence Items
Given each evidence item is stored with its canonicalized payload and a SHA-256 digest When the evidence drawer is opened for a score Then the system recomputes the digest for each item and shows a per-item status of Verified or Integrity check failed And then Verified items display a green check icon and failed items display a red warning icon with a tooltip describing the mismatch And then failed items have their source-record link disabled and an audit event is written including item ID, expected hash, actual hash, actor (system), and timestamp within 60 seconds And then verification completes within 3 seconds for up to 200 items And then the verification endpoint returns HTTP 200 with a JSON list of item IDs and statuses; tampered items are included with status=failed
Immutable Audit Log for Weight and Configuration Changes
Given an administrator publishes a new weight profile or changes configuration affecting scoring When the change is saved Then an append-only audit entry is created with fields: eventId, actorId, actorRole, timestamp (UTC ISO 8601), environment, reason, before/after diff, versionId, and correlationId And then the audit entry is visible in the admin audit view within 5 seconds of the change And then attempts to update or delete existing audit entries via API return HTTP 405 and are themselves logged as security events And then the audit query API supports filters by date range, actorId, versionId, and change type and returns results sorted descending by timestamp And then audit data retention adheres to FixFlow policy (tagged with retention class) and entries older than policy are purged automatically with a logged purge event
Manual Override Capture, Display, and Expiry
Given a manager with override permission edits a vendor's score When the override is submitted with required fields (reason, scope, new score, optional expiration) Then the system records actorId, timestamp, scope (vendor/category), previous score, new score, reason, expiration (if any), and correlationId in an immutable audit log And then the score chip in UI displays an Override badge and a "View reason" link that reveals the reason and actor And then the evidence drawer lists the override as a top-level item with its contribution and validity window And then upon expiration the system automatically reverts to the computed score and writes a Revert override audit entry And then users without override permission cannot create, edit, or view override reasons (attempts return HTTP 403 and are logged) And then the vendor-facing view displays only the fact of an override (badge) without revealing internal reason text
Exportable, Reproducible Ranking Snapshot
Given a manager is viewing Vendor Radar rankings for a selected scope and time window When the manager requests an export Then the system generates a versioned snapshot containing: ranking list with vendor IDs and scores, weight profile version, normalization settings, time window, contributing evidence hashes, and generation timestamp And then the snapshot is available in JSON and CSV formats with a top-level SHA-256 checksum file And then the export completes within 30 seconds for up to 5,000 vendor-category rows And then an import/verify endpoint can ingest the JSON snapshot and reproduce the exact ordering and scores (within ±0.01) in a stateless environment And then the export respects role-based redaction rules (no tenant PII for vendor-facing exports) And then an audit entry records who exported, when, scope, and snapshot ID
Tenant PII Redaction in Vendor-Facing Views
Given a logged-in user has a vendor role When they open the evidence drawer or download an export Then tenant names, emails, phone numbers, full unit numbers, and free-text comments are redacted per policy (e.g., masked or removed with placeholders) And then links to tenant records are not rendered, and underlying API responses exclude PII fields And then manager and admin roles see unredacted data subject to their permissions And then attempts by a vendor to access unredacted endpoints return HTTP 403 and are logged with actorId and route And then redaction is applied server-side (verified by inspecting network responses) and not only in the client
Integration with FixFlow Logging and Retention Policies
Given any lineage or audit event (e.g., weight publish, override, integrity failure) occurs When the event is created Then it is emitted to FixFlow central logging with fields: correlationId, tenantId, feature=ScoreLens, requirement=ScoreDataLineage, eventType, severity, timestamp, and non-PII payload And then the event is queryable in the logging system within 5 seconds And then logs are tagged with the appropriate retention class and are purged per policy with a recorded purge event And then backup/restore procedures preserve audit log integrity (hashes still verify) as validated by running the verification job post-restore And then no tenant PII is present in vendor-accessible logs or exports (verified by automated scan rules)
Vendor Coaching Insights
"As a vendor, I want specific, data‑backed suggestions to improve my ranking so that I can win more jobs and grow my business."
Description

Generates personalized improvement tips for vendors based on their weakest criteria and high‑leverage behaviors (e.g., reduce first‑response time by enabling SMS alerts, improve first‑time‑fix by uploading completion photos, calibrate quotes to reduce variance). Includes impact estimates (projected score change and rank movement) and links to concrete actions in the Vendor Portal (enable feature, complete profile, expand service categories, update coverage radius). Tracks tip acknowledgment and measures post‑adoption outcomes to refine recommendations. Ensures language is constructive and data‑driven, and excludes tenant PII. Integrates with Transparent Scoring Engine and Vendor Portal notifications. Expected outcomes: vendor performance improvement and healthier marketplace quality over time.

Acceptance Criteria
Auto-Generation of Personalized Vendor Tips
Given a vendor with an active account and a score breakdown (speed, quality, cost, proximity, scope fit) updated by the Transparent Scoring Engine within the last 24 hours When Coaching Insights runs the tip generation job for that vendor Then it produces 1–5 tips prioritized by expected impact And each tip targets a specific weakest criterion or high‑leverage behavior supported by available actions in the Vendor Portal And each tip references the metric name, current baseline value, and target value required to reach the next rank quartile And no tip includes tenant PII
Impact Estimates Display
Given a generated tip with a defined action mapping and an available scoring simulation When the tip is rendered in the Vendor Portal Then it displays a projected criterion score delta (numeric) and an estimated overall rank movement (positions) And the estimates are computed using the latest scoring model version and the vendor’s current baseline And the numbers displayed match back-end calculations within 0.1 score and 1 rank position And an info tooltip shows the scoring model version and timestamp used
Action Deep Links to Vendor Portal
Given a tip includes an action (enable SMS alerts, upload completion photos, calibrate quotes, complete profile, expand service categories, update coverage radius) When the vendor clicks the action link Then the user navigates to the exact Vendor Portal page with the relevant control focused And RBAC is enforced; unauthorized users see a clear error and no changes are applied And the deep link includes a return-to parameter that brings the user back to the originating tip after completion And link click and action completion events are logged with vendorId, tipId, and timestamps
Tip Acknowledgment Tracking
Given a vendor user with permission views their coaching tips When they click Acknowledge on a tip Then the tip status becomes Acknowledged for that vendor account And an audit log records vendorId, userId, tipId, timestamp, and client metadata And the UI prevents duplicate acknowledgments by the same user for the same tip And the acknowledgment event is available in analytics within 5 minutes
Post-Adoption Outcome Measurement
Given a tip’s action has been completed (e.g., SMS alerts enabled) When 30 days have elapsed or 30 relevant events are collected, whichever comes first Then the system computes the change in the targeted metric versus the 30-day pre-action baseline And classifies the tip as Effective if observed improvement meets/exceeds the expected impact, otherwise Not Effective And the outcome is persisted and used to refine future recommendations And the vendor dashboard displays the realized change with the date range used
Notifications for New High-Impact Tips
Given a vendor has at least one new tip with expected impact above the configured threshold When notification evaluation runs Then a Vendor Portal notification is sent within 15 minutes And frequency capping limits to at most one coaching notification per 7 days per vendor And vendor notification preferences are honored; opted-out vendors receive none And delivery, open, and click events are tracked with unique IDs
Constructive Language and Data Privacy
Given coaching tips are generated for any vendor When the content is saved and rendered Then no tenant PII (names, phone numbers, emails, unit numbers) appears in the text And toxicity/sentiment checks pass configured thresholds And copy adheres to constructive tone guidelines and a reading level of grade 9 or below And each metric reference is accompanied by a timeframe (e.g., last 90 days) to ensure data-driven context
Fairness & Bias Guardrails
"As an operations administrator, I want built‑in safeguards and monitoring for bias so that our rankings remain fair and trustworthy across our vendor network."
Description

Implements guardrails that exclude protected attributes and proxies from scoring and explanations, applies geographic normalization where appropriate, and monitors fairness metrics (e.g., disparate impact across vendor segments). Flags risk conditions (insufficient sample size, extreme outliers, high override frequency) and displays neutrality notices in the UI. Requires justification notes for manual overrides and routes them to audit logs. Provides scheduled reports to admins on fairness KPIs and drift in criterion distributions. Expected outcomes: equitable, robust rankings that maintain user trust and reduce legal/reputational risk.

Acceptance Criteria
Exclusion of Protected Attributes & Proxies
Given the scoring pipeline is configured, When any feature in the denylist (e.g., race, gender, age, disability, religion, sexual orientation, marital status, pregnancy, nationality/citizenship, language, veteran status, union membership, ZIP/neighborhood, school/degree, or derived hashes thereof) is present in the feature store or model config, Then the build/deploy validation fails with an explicit error naming the blocked features and an audit event is recorded. Given a standard ranking run, When feature importance/attribution (e.g., SHAP) is generated, Then no protected or proxy features appear in the explanation outputs and their weights are zero in the model registry metadata. Given the nightly proxy scan on training data, When a candidate feature’s proxy risk score to any protected attribute >= 0.8 (correlation or MI-based threshold), Then the feature cannot be promoted to production scoring and a ticket is opened to the data owner.
Geographic Normalization in Scoring
Given market areas are configured and normalization is enabled for travel-time and labor-rate metrics, When rankings are computed for two vendors in different markets with identical within-market percentiles, Then the normalized contributions to the composite score differ by ≤ 0.01 and the run metadata logs the normalization method and parameters. Given normalization is disabled for a category, When rankings run, Then raw (non-normalized) metrics are used and the run metadata reflects normalization=off. Given the nightly data quality job, When it evaluates normalized metrics per market, Then mean≈0 (±0.1) and std≈1 (±0.1) or the job fails and alerts data ops.
Fairness Metrics Monitoring & Alerts
Given the fairness monitor is scheduled daily at 02:00 UTC, When it computes disparate impact ratios for top-3 appearances and award rates across vendor segments (e.g., small vs. large, new vs. established, minority-owned where provided by vendor), Then any ratio outside [0.8, 1.25] is flagged, persisted with confidence intervals, and an alert is sent to org admins via email and in-app within 15 minutes. Given 13 months of history, When an admin opens the fairness dashboard, Then time series for each KPI are visible with drill-down by segment and exportable to CSV. Given a flagged metric, When the admin views details, Then the system shows sample sizes, segments, definitions, and links to contributing runs and audit events.
Risk Condition Flagging & Neutrality Notices
Given a ranking event has fewer than 15 closed jobs for a vendor in the last 90 days within the category, When the ranking is displayed, Then a neutrality notice banner is shown in Score Lens and Vendor Radar indicating limited data and a link to methodology is provided. Given robust z-score > 3.5 or IQR outlier rule identifies extreme outliers in a criterion distribution, When rankings are generated, Then a neutrality notice is displayed and the outlier condition is logged with affected metrics. Given override rate exceeds 10% or ≥5 overrides in the last 14 days for a team, When Score Lens loads, Then a neutrality notice is shown and a risk flag is written to the audit log with the rolling window stats.
Manual Override Justifications & Audit Logging
Given a manager attempts to manually change a vendor rank or selection, When they click Save, Then a justification note 20–500 characters and a reason code are required; empty or too-short inputs block save with inline validation. Given the override is saved, When the audit log entry is written, Then it includes user ID, role, timestamp, request/job ID, before/after ranks, changed fields, reason text, IP/device fingerprint, and a tamper-evident hash, and is retrievable within 2 seconds by authorized auditors. Given an overridden item is viewed in the UI, When the details panel opens, Then a clear “Manual override applied” tag with tooltip and link to audit entry is displayed.
Scheduled Fairness KPI Reports to Admins
Given an admin has weekly (Mon 14:00 UTC) and monthly (1st of month 14:00 UTC) reports enabled, When the schedule triggers, Then reports are generated and delivered within 2 hours via email and available in the portal Downloads with PDF and CSV attachments. Given the report is generated, When an admin opens it, Then it contains KPIs: disparate impact ratios (top-3, awards), override rates by manager/team, drift metrics for criterion distributions (e.g., PSI and KS-test p-values), outlier counts, sample sufficiency, and trend charts with notes on any neutrality notices issued. Given a generation failure occurs, When the job retries twice and still fails, Then an incident alert is sent to on-call and admins with failure reason and the missed report is backfilled within 24 hours.
Explanation Neutrality & Safe Language
Given Score Lens renders a “why ranked” explanation, When text is generated, Then it contains no references to protected attributes or proxies and adheres to the style guide (neutral, action-focused, grade 7–10 reading level), verified by snapshot tests on a golden dataset and automated lint checks. Given a fairness risk flag is present for the current view, When the explanation is shown, Then a neutrality notice is appended with a link to methodology and guidance, localized to the user’s language settings. Given copy updates are made to explanation templates, When CI runs, Then toxicity/compliance checks pass and any blocked phrases are rejected with actionable errors.
Score Lens Export & API Access
"As an analyst, I want to export and programmatically access ranking breakdowns so that I can incorporate them into performance reviews and portfolio reports."
Description

Enables exporting vendor rankings and explanations to CSV and PDF with organization branding, date/time stamps, and configuration details. Provides authenticated API endpoints to fetch ranking lists, per‑vendor breakdowns, and lineage snapshots with pagination and field filtering. Honors role‑based access controls and tenant isolation, rate limits requests, and logs access for audit. Supports webhooks to notify downstream systems when rankings or weight profiles change. Expected outcomes: seamless integration into reporting workflows and BI tools, reducing manual effort while preserving transparency.

Acceptance Criteria
CSV Export: Branding, Timestamp, Configuration Metadata
Given a user with "Export Score Lens" permission in tenant T and a visible Vendor Radar ranking list with filters F and weight profile W When the user exports to CSV from Score Lens Then a UTF-8 CSV downloads within 5 seconds named score-lens_T_YYYYMMDD_HHMMSS.csv And the first 5 header rows include: organization_name, organization_id, report_title="Score Lens Rankings", generated_at (ISO 8601 with timezone), weight_profile_id and version, applied_filters=F, lineage_snapshot_id And the table includes columns exactly: rank, vendor_id, vendor_name, speed_score, quality_score, cost_score, proximity_score, scope_fit_score, total_score, vendor_city, vendor_state And CSV uses comma delimiter, RFC 4180 quoting, CRLF line endings And the number of data rows equals the count shown in the current UI view And all rows belong to tenant T; no cross-tenant vendor appears
PDF Export: Branding, Pagination, and Accessibility
Given a user with "Export Score Lens" permission in tenant T viewing a ranking list with filters F and weight profile W When the user exports to PDF from Score Lens Then a PDF downloads within 10 seconds named score-lens_T_YYYYMMDD_HHMMSS.pdf And each page header shows organization logo and name, report title, and generated_at timestamp; footer shows page X of Y And the first page contains configuration block with weight_profile_id and version, applied_filters=F, lineage_snapshot_id And the table shows rank and per-dimension scores plus total_score matching the UI values for the same snapshot And embedded text is selectable (no rasterized text) and tagged for accessibility (PDF/UA tag root present) And no vendors from other tenants appear
Rankings API: Auth, RBAC, Pagination, Field Filtering
Given a valid OAuth2 access token scoped scorelens.read for tenant T and role "Manager" When GET /v1/score-lens/rankings?tenantId=T&page[size]=50&page[number]=2&fields[vendor]=vendor_id,vendor_name,total_score&filter[city]=Austin is called Then the response is HTTP 200 within 800 ms at P95 under 10 rps/tenant And the data array length <= 50 and meta.pagination includes page, size, totalPages, totalItems And only the requested fields vendor_id, vendor_name, total_score are present for each item And results include only vendors within tenant T and where vendor_city == "Austin" And rate limit headers X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset are present And calling with an access token from another tenant returns 403 and no data leakage
Per-Vendor Breakdown API with Lineage and Validation
Given a valid OAuth2 token for tenant T When GET /v1/vendors/{vendorId}/score-lens?tenantId=T is called Then for an in-tenant vendorId response is 200 with fields: vendor_id, breakdown {speed, quality, cost, proximity, scope_fit}, total_score, weight_profile {id, version}, lineage_snapshot_id And total_score equals the weighted sum of breakdown values using the returned weight profile within a tolerance of 0.001 And requesting a vendorId not in tenant T returns 404 And requesting without scope scorelens.read returns 403
Lineage Snapshot Retrieval and Immutability
Given a lineage_snapshot_id obtained from an export or API response When GET /v1/score-lens/snapshots/{lineage_snapshot_id} is called with a valid token for tenant T Then response is 200 with fields: id, generated_at, algorithm_version, weight_profile {id, version, weights}, input_filters, source_checksums, tenant_id And subsequent identical requests return byte-for-byte identical payload and strong ETag And attempting to modify a snapshot returns 405 Method Not Allowed And requesting a snapshot for another tenant returns 403 or 404 with no cross-tenant identifiers leaked
Webhooks on Ranking or Weight Profile Changes
Given a tenant T has a webhook endpoint registered and verified with a shared secret S When a ranking list changes or a weight profile is updated for tenant T Then a POST is delivered to the endpoint within 30 seconds with event types ranking.updated or weights.updated, including lineage_snapshot_id and affected weight_profile_id And the request includes an HMAC-SHA256 signature header X-FixFlow-Signature computed over the body using S And the delivery is retried up to 5 times with exponential backoff if non-2xx; duplicate deliveries include the same Idempotency-Key header And events are isolated per tenant; no events from other tenants are delivered to T’s endpoint
Rate Limiting, Audit Logging, and Export Authorization
Given API consumers and UI users operate under tenant T When making requests exceeding 100 requests per minute per access token Then subsequent requests return 429 with Retry-After and standard rate limit headers And all successful and failed API calls and export actions write audit logs with fields: timestamp (ISO 8601), user_id or client_id, tenant_id, action, resource, parameters_hash, ip, user_agent, outcome, http_status And audit logs are retained for at least 365 days and are queryable by tenant_id And users without "Export Score Lens" permission cannot initiate CSV/PDF exports (UI control hidden and server returns 403 on forced calls)

ETA Confidence

Shows a live arrival window with confidence bands and reason cues (traffic, prior job overrun). Sets realistic expectations, reduces check‑in calls, and lets tenants plan around the visit while the SLA clock stays visible.

Requirements

Live ETA Computation Engine
"As a tenant, I want a live arrival window with confidence bands so that I can plan my day and reduce uncertainty around the technician’s visit."
Description

Build a server-side service that computes a rolling arrival window for each scheduled job using multi-source inputs (vendor mobile location when available, current job progress, historical task durations, traffic/incidents APIs, distance matrix, dwell times, and known building access constraints). Produce P50/P80/P95 confidence bands, update every 60–120 seconds or on significant signal change, and degrade gracefully when GPS is unavailable by switching to milestone-based estimates (checked-in at prior job, started travel, arrived). Handle multi-stop routing, time zones, and daylight savings safely. Integrate with FixFlow’s work order model and vendor assignment so every job maintains a single authoritative ETA object. Normalize reason codes (traffic, prior job overrun, parts pickup, weather) with an attribution score for UI cues and reporting. Ensure SLA timers are independent from ETA calculations and never paused by ETA changes. Log inputs/outputs for auditability and model tuning, and enforce data retention limits on location data.

Acceptance Criteria
Real-time ETA Window with Confidence Bands
- Given a scheduled job with at least one live signal (vendor GPS, traffic, job status) When the engine computes the ETA Then it returns an arrival window [start, end] in the job’s local timezone and P50/P80/P95 timestamps within that window - Given stable signals for a job When no significant signal change occurs Then the ETA object is recomputed and published every 60–120 seconds - Given a significant signal change (vendor position delta ≥ 500 m, status change to Travel Started/Arrived, or traffic incident severity change) When detected Then the ETA object is recomputed and published within 5 seconds - Then P50 ≤ P80 ≤ P95 and start < end and all times are in the future unless the job is Arrived - Given live jobs over a rolling 30‑day sample ≥ 500 When comparing P50 to actual arrival time Then median absolute error ≤ 5 minutes and ≤ 15% of travel time
Graceful Degradation without GPS
- Given GPS is unavailable for a vendor for ≥ 3 minutes while prior job milestones are updating When computing ETA Then the engine switches to milestone-based estimates using last-known job location, historical travel/dwell, and distance matrix - Then the ETA object is flagged degraded=true and includes reason_codes containing "gps_unavailable" with attribution ≥ 0.5 unless another dominant cause exists - Then the recompute cadence remains every 60–120 seconds and no blank ETA window persists > 30 seconds after degradation is detected - When GPS resumes Then the engine returns to GPS-based estimation within 10 seconds and clears degraded flag on next publish
Multi-Stop Routing, Time Zones, and Building Access Constraints
- Given a vendor route with N ≥ 2 stops possibly across time zones and a building access window for the target job When upstream dwell time or sequence changes Then ETA for the target and all downstream jobs is recomputed and published within 15 seconds - Then ETA times are presented in each job’s IANA timezone, honoring DST transitions without 1-hour duplication or omission - Then start of the ETA window is never earlier than building access start and includes reason_codes containing "building_access" when access limits push the window later - When an ad-hoc stop (e.g., parts pickup) is inserted before the target job Then ETAs reflow and attribution credits "parts_pickup" ≥ 0.5 for any additional delay to the target job
Reason Codes Normalization and Attribution
- Given an ETA delay versus baseline ≥ 3 minutes When causes are inferred from signals Then normalized reason_codes ∈ {traffic, prior_job_overrun, parts_pickup, weather, building_access, other} are emitted with attribution scores in [0,1] summing to 1.0 - Then the top reason is included in the ETA object for UI and remains stable for ≥ 10 minutes unless new contradictory evidence appears - Then reason_codes are persisted with job_id, eta_version, and published_at and are queryable for reporting within 1 second of publish
SLA Timer Independence from ETA
- Given any job with active SLA timers When ETA values change (window widens/narrows, confidence bands shift, reason_codes change) Then SLA counters (response, arrival) continue from their own event timestamps and never pause or reset - Then SLA breach evaluation uses only actual timestamps (e.g., acknowledged_at, on_site_at) and does not reference ETA fields in computation paths - When simulating 100 ETA changes across a job lifecycle Then the reported SLA remaining time deviates by 0 from a control job with identical events and no ETA changes
Audit Logging and Location Data Retention
- Given each ETA computation cycle When executed Then the system logs inputs (signal snapshot, API request IDs, model version) and outputs (window, P50/P80/P95, reasons) with a correlation_id and persists within 2 seconds - Then raw location samples are retained no longer than the configured retention_days (default 30) and are purged by a daily job; verification shows ≥ 99% of samples older than retention_days + 24h are deleted - Then audit logs are retrievable by correlation_id for ≥ 90 days without exposing raw GPS coordinates beyond retention_days (coordinates redacted to ≥ 1 km granularity thereafter)
Single Authoritative ETA Object Integration
- Given a work order with a vendor assignment When the engine publishes an ETA Then exactly one authoritative ETA object exists per job (status=active), with monotonically increasing version and last_updated - When concurrent recomputes occur from different signal sources Then only the latest version is persisted (atomic upsert) and GET /jobs/{id}/eta returns that version within 1 second to all clients - When a vendor assignment changes for a job Then the prior ETA is archived with reason "vendor_change" and a new active ETA is created and published within 30 seconds; no data from the prior vendor persists in the new ETA
Confidence Bands & Reason Cues UI
"As a tenant, I want to see an easy-to-understand ETA with why it might change so that I trust the timeline and avoid calling support."
Description

Implement a mobile-first widget on the tenant and manager portals that displays the arrival window with visual confidence bands (e.g., shaded ranges for P50/P80/P95), current countdown, and succinct reason cues with icons (traffic, prior job overrun). Provide clear states: scheduled (no movement yet), en route, delayed, arrived, and completed. Include accessibility (WCAG AA contrast, screen reader labels, keyboard navigation) and localization for time formats. Show the SLA clock alongside the ETA without conflating them, with tooltips explaining each. Design resilient fallbacks when confidence is low or data is stale, e.g., “Low confidence: awaiting vendor check-in.” Ensure responsive behavior across devices and embed capability within work order, timeline, and notification surfaces.

Acceptance Criteria
Tenant Portal: ETA Bands & Countdown
Given a scheduled work order with ETA model outputs for P50, P80, and P95, When the tenant opens the work order in the portal, Then the widget displays the arrival window with three distinct shaded bands labeled P50, P80, and P95 and a live countdown to the expected arrival. Given the widget remains open, When 60 seconds elapse, Then the countdown and arrival window update without requiring a page refresh. Given both tenant and manager portals load the same work order, When the widget renders, Then the ETA window and bands are consistent across portals within the same refresh interval. Given the ETA window spans multiple hours, When rendered on mobile (≤ 390px width), Then the bands remain legible, labels do not overlap, and the countdown is visible above the bands.
Reason Cues with Icons
Given a traffic delay signal is present, When the widget renders, Then a traffic icon and the text 'Traffic' appear adjacent to the ETA. Given a prior job overrun flag is present, When the widget renders, Then an overrun icon and the text 'Prior job overrun' appear adjacent to the ETA. Given multiple reasons are present, When the widget renders, Then up to two reasons are displayed ordered by highest impact with tooltips providing 1–2 line explanations. Given no reasons are present, When the widget renders, Then the reason area is hidden and spacing collapses with no placeholder.
State Machine: Scheduled → En Route → Delayed → Arrived → Completed
Given state is Scheduled and there is no movement, When the widget renders, Then it shows the scheduled arrival window, a 'Scheduled' badge, and no movement indicator. Given the vendor begins travel, When an En Route event is received, Then the state changes to 'En route', the movement indicator appears, and ETA bands update. Given the ETA widens or shifts per model delay flag, When a Delay event is received, Then the state changes to 'Delayed', the window adjusts, and a reason cue is shown. Given the vendor arrives at the service location, When an Arrived event is received, Then the state changes to 'Arrived', the arrival timestamp replaces the countdown. Given the job is finished, When a Completed event is received, Then the state changes to 'Completed' and the final actual arrival time is displayed while confidence bands are hidden.
SLA Clock Display and Tooltip Separation
Given an SLA due time exists, When the widget renders, Then an SLA clock labeled 'SLA' is displayed adjacent to the ETA and visually distinct. When the user hovers or focuses the ETA, Then a tooltip explains 'ETA shows the expected arrival window based on live conditions.' When the user hovers or focuses the SLA, Then a tooltip explains 'SLA is time remaining to meet the service commitment; independent of ETA.' Given ETA updates, When the ETA changes, Then the SLA clock remains unchanged unless SLA data changes. Given keyboard or assistive tech navigation, When focusing ETA and SLA, Then each is a separate focusable element with unique accessible names.
Accessibility & Localization (WCAG AA, Keyboard, Time Formats)
Given any text or graphical bands in the widget, Then color contrast meets WCAG 2.2 AA (≥ 4.5:1 for normal text and ≥ 3:1 for graphical elements and large text). Given a keyboard-only user, When navigating the widget, Then all interactive elements are reachable in a logical order with visible focus indicators and no keyboard traps. Given a screen reader user, When the widget loads or updates, Then ETA, SLA, state, and reason cues have descriptive labels and dynamic changes announce via an aria-live polite region. Given tenant locale en-US, When times are displayed, Then they use 12-hour format with AM/PM; Given tenant locale de-DE, When times are displayed, Then they use 24-hour format. Given the tenant’s timezone differs from the vendor’s, When times are displayed, Then they are shown in the tenant’s timezone consistently across surfaces.
Responsive Layout & Embedding Across Surfaces
Given device widths of 320px, 768px, and 1024px, When the widget renders, Then layout adapts without overlapping text, clipped bands, or horizontal scrolling. Given a touch device, When interacting with controls, Then touch targets are at least 44x44 px and gestures are not required. Given the work order detail page, When the widget is embedded, Then it renders the full widget with bands, countdown, reasons, and SLA. Given the job timeline feed, When the widget is embedded, Then it renders a compact version showing the current state, next expected arrival time, top reason cue, and SLA badge with a link to full view. Given the notification center panel, When the widget is embedded, Then it renders a condensed view with no loss of critical information and opens the full widget on click.
Low Confidence and Stale Data Fallback
Given model confidence is below the low-confidence threshold or telemetry is stale for more than 5 minutes, When the widget renders, Then confidence bands are hidden and the message 'Low confidence: awaiting vendor check-in.' is displayed with the last update timestamp. Given connectivity loss on the client, When network becomes unavailable, Then the widget displays 'Connection lost. Retrying…' and retries until online, after which it refreshes ETA and state. Given confidence recovers or fresh vendor check-in arrives, When the next update is received, Then the normal bands, countdown, and reasons automatically resume.
Adaptive Notifications & Thresholds
"As a tenant, I want to be notified only when the ETA meaningfully changes so that I can adjust plans without being spammed."
Description

Create server-driven notification rules that alert tenants and managers when the ETA shifts beyond configurable thresholds (e.g., >15 minutes change, P80 slips beyond appointment window) or when confidence drops below a set level. Support channels SMS, email, and push, honoring user contact preferences, quiet hours, and local time zones. Include rate limiting, digesting of rapid changes, and contextual actions (confirm availability, propose new window, contact vendor). Provide templates with localized, clear language that include reason cues and the updated window. Expose admin settings per portfolio to tune thresholds and channels. Record all notifications in the work order timeline and analytics to measure call deflection and user engagement.

Acceptance Criteria
Threshold-Based ETA Shift Notification
Given a portfolio with ETA shift threshold set to 15 minutes and active channels SMS, Email, Push And a work order with tenant and manager contact preferences saved And the initial ETA window is 1:00–3:00 PM local time When the live ETA shifts by 17 minutes from the last notified ETA Then the system sends one notification per recipient via their allowed channels within 60 seconds And the message includes the updated ETA window, the magnitude of change, and a reason cue if available And no notification is sent if the shift is 15 minutes or less And the notification uses the recipient's preferred language and local time formatting
P80 Exceeds Appointment Window Alert
Given a portfolio rule to alert when ETA P80 exceeds the scheduled appointment end time And an appointment scheduled for 2:00–4:00 PM local time When the ETA P80 extends beyond 4:00 PM Then the tenant and manager receive a notification with the new ETA window and P80 value And the notification includes a contextual action to propose a new time window And selecting the action posts the proposal back to the work order and notifies the vendor And no alert is sent if ETA P80 remains within the appointment window
Low ETA Confidence Alert
Given a portfolio confidence threshold of 60% And the ETA confidence band is currently 58% When the confidence drops below the threshold for at least 2 consecutive updates Then the system sends an alert to tenant and manager respecting contact preferences And the notification contains reason cues (e.g., traffic, prior job overrun) when available And the notification includes actions to confirm availability and contact the vendor And the alert is not sent if confidence rebounds above threshold before the consecutive update rule is satisfied
Respect Quiet Hours and Local Time Zones
Given tenant quiet hours set to 9:00 PM–7:00 AM in the tenant's local time zone And the manager has different quiet hours configured in their local time zone When an ETA shift qualifies for notification at 9:30 PM tenant local time Then the tenant's notification is queued and scheduled to send at 7:00 AM tenant local time And the manager's notification is evaluated against the manager's quiet hours independently And all timestamps in messages are rendered in each recipient's local time And the system records the queued status and eventual send timestamp in the work order timeline
Rate Limiting and Digest of Rapid Changes
Given a portfolio rate limit of 1 notification per 10 minutes and a digest window of 15 minutes And three ETA updates that each exceed the threshold occur within 12 minutes When the first qualifying update occurs Then a notification is sent immediately for the first update And subsequent qualifying updates within the rate limit window are suppressed and added to a digest And at the end of the 15-minute digest window, one digest notification is sent summarizing the number of changes, reasons, and the latest ETA window And no more than one notification per recipient is sent within any 10-minute period
Admin Portfolio Settings for Notifications
Given an admin with permissions to configure notification rules at the portfolio level When the admin updates thresholds (minutes, confidence%), P80 alert toggle, quiet hours, allowed channels, and message templates Then the changes are saved server-side with audit metadata (who, what, when) And the new rules take effect for subsequent notifications without requiring client updates And different portfolios can maintain distinct settings without cross-application And validation prevents saving conflicting or invalid configurations (e.g., quiet hours 24h, negative thresholds)
Notification Logging and Analytics Tracking
Given notifications are enabled for a work order When a notification is sent, delivered, opened, link-clicked, or replied to Then the work order timeline records an entry with timestamp, recipient role, channel, trigger condition (e.g., ETA shift >15m, P80 overrun, confidence <60%), and message snippet And analytics aggregate events to compute call deflection (reduction in inbound calls) and engagement rates by channel and scenario And failures (bounce, unsubscribed, push-disabled) are logged with reason without retrying during quiet hours And all logs are queryable by portfolio and date range
Vendor Check-in & Privacy Controls
"As a vendor technician, I want simple check-ins and control over what location data is shared so that I can provide accurate ETAs without compromising my privacy."
Description

Enable vendor technicians to contribute reliable signals via lightweight mechanisms: in-app status toggles (en route, onsite, completed), automatic background location sampling with configurable frequency, and SMS web links for vendors without the app to perform one-tap check-ins. Provide explicit consent flows for location sharing, the ability to pause sharing, and data minimization so tenants see ETA windows and reasons but never precise coordinates. Handle offline scenarios with queued updates and conflict resolution. Offer org-level policies for required check-ins and minimum signal quality. Ensure compliance with regional privacy regulations and implement retention windows to purge raw location data after aggregation.

Acceptance Criteria
App Status Toggles Update ETA and SLA Visibility
- Given a technician with the FixFlow vendor app assigned to a job, when they toggle status to En route, then the system records a timestamp, recalculates the ETA window within 5 seconds, updates the reason cues to include En route and any detected traffic delay, and keeps the SLA clock visible to tenant and ops. - Given the technician toggles Onsite, when the change is received, then tenant UI updates within 5 seconds to Technician onsite, ETA window collapses to Present–+15 mins, reason cues include Onsite, and the SLA clock remains visible. - Given the technician toggles Completed, when the change is received, then live tracking stops, ETA/confidence panel shows Visit completed, and no further location sampling occurs for that job. - Given rapid repeated taps, when duplicate status events are received within 2 seconds, then only one status transition is persisted and audit-logged with source=device and actor id. - Given each status change, when saved, then an audit log entry is created containing jobId, vendorId, previousStatus, newStatus, eventTime, receivedAt, source, and correlationId.
Configurable Background Location Sampling
- Given organization policy sets a sampling interval (2–15 minutes) and minimum accuracy (≤100 m), when a technician has consented and a job is in En route state, then the app samples in the background at the configured interval and only uploads points meeting the accuracy threshold. - Given an admin updates the sampling policy, when the device next syncs, then the new interval takes effect within 10 minutes and is reflected in device policy logs. - Given a job is not in En route or Onsite, when background sampling triggers, then no location is uploaded for that job. - Given the app is in low-power mode, when sampling occurs, then the app respects OS constraints but never exceeds the configured maximum frequency. - Given background samples are received, when ETA computation runs, then ETA window and confidence are updated within 15 seconds of receipt, and reason cues show Traffic if average speed < 15 km/h on route for ≥2 consecutive samples.
SMS Web Link One‑Tap Check‑Ins (No App)
- Given a vendor without the app is assigned a job, when FixFlow sends an SMS with a secure, single-use, job-scoped link, then tapping the link opens a web page that allows one-tap En route, Onsite, and Completed without login. - Given the vendor taps En route via the web link, when the event posts, then ETA recalculates within 10 seconds, reason cues include Vendor check-in, and the link remains valid for remaining statuses. - Given the link is used to mark Completed, when the event posts, then the link is invalidated and subsequent attempts return HTTP 410 Gone. - Given 24 hours pass after scheduled window end, when the link is used, then the system rejects with HTTP 410 Gone and no state change occurs. - Given a web check-in event, when persisted, then an audit log records source=sms_web, deviceInfo (UA), sender phone hash, eventTime, and receivedAt.
Explicit Consent and Pause/Resume Controls
- Given a technician first uses the app or SMS web flow, when location features are needed, then a region-specific consent screen is presented with explicit opt-in; without consent, no location sampling occurs and only manual status check-ins are enabled. - Given consent is granted, when recorded, then the system stores consent status, jurisdiction, policy version, timestamp, and revocation endpoint; this record is exportable for compliance. - Given a technician pauses sharing, when pause is activated, then all background sampling stops within 30 seconds, and ETA confidence degrades to Low with reason Vendor paused location sharing visible to tenant and ops. - Given a technician resumes sharing, when resume is activated, then sampling restarts per policy and confidence recalculates on next successful signal. - Given consent is revoked, when processed, then all location collection ceases immediately, any queued unsent samples are purged on device, and future attempts require a fresh consent.
Privacy: Data Minimization, Compliance, and Retention
- Given tenant-facing UI and APIs, when rendering ETA information, then only windowStart, windowEnd, and reason cues are exposed; no latitude/longitude, precise technician path, or device identifiers are present in payloads or the DOM. - Given an ops or tenant attempts to access raw coordinates via tenant APIs, when requests are made, then endpoints do not include such fields and direct access returns 404/field not found without leaking internal schemas. - Given regional privacy regulations (e.g., GDPR, CCPA), when a data subject requests export, then the export contains consent records and aggregated visit events, but no raw location samples. - Given a configured raw location retention window (e.g., 7 days), when the purge job runs daily, then all raw samples older than the window are deleted, verified by metrics showing 0 samples beyond retention, while aggregated data remains. - Given a deletion request, when processed, then raw samples for that subject are deleted within 30 days and are no longer retrievable from backups beyond the documented backup retention policy.
Offline Queueing and Conflict Resolution
- Given the device is offline, when a technician changes status or a sample is collected, then the event is queued locally with eventTime and a monotonically increasing sequence number. - Given connectivity is restored, when queued events are sent, then the server deduplicates by idempotency key, orders by eventTime, and applies business rules to prevent status regression (e.g., Completed cannot be overwritten by Onsite with a later receivedAt but earlier eventTime). - Given queued events are older than 24 hours, when sync occurs, then stale events are dropped and flagged in the audit trail with reason Stale beyond window; no state change occurs. - Given ETA computation uses late-arriving data, when events are applied, then SLA and ETA calculations use eventTime (not receivedAt), and the timeline UI shows a Late-arriving data indicator. - Given conflicting simultaneous updates from app and SMS, when received within 5 seconds with different statuses, then the system resolves by highest status precedence (Completed > Onsite > En route) if eventTimes are equal; otherwise by later eventTime.
Org Policies: Required Check‑Ins and Signal Quality Enforcement
- Given an admin sets required check-ins (e.g., En route ≥15 minutes before window start and Onsite on arrival), when a job is scheduled, then vendors are notified of requirements and the system enforces reminders and blocks completion until required check-ins are satisfied or an approved override is recorded. - Given an admin sets minimum signal quality (recency ≤10 minutes OR accuracy ≤100 m), when ETA confidence is computed, then confidence is High only if the minimum is met; otherwise it is Medium/Low with reason Insufficient signal quality. - Given a vendor misses a required check-in by more than 5 minutes, when the threshold is crossed, then an automatic reminder is sent (push/SMS) and the tenant ETA reason cues show Awaiting vendor confirmation. - Given a policy change, when published, then new jobs adopt the policy immediately and in-flight jobs adopt within 10 minutes, with all changes audit-logged (who, what, when). - Given policy violations, when a manager reviews a job, then a policy compliance report shows met/missed checks, signal quality metrics, and any overrides with approver identity.
SLA Clock Integration & Policy Rules
"As a property manager, I want the SLA clock visible and policy-driven alongside ETA so that I can intervene early before we miss commitments."
Description

Display an always-visible SLA timer next to ETA in tenant and manager views, with clear policy rules determining when the SLA starts, pauses (if ever), and stops based on work order events (e.g., tenant unavailability, building access delays). Ensure ETA updates never pause the SLA; instead, trigger configured escalations when SLA risk is detected (e.g., P80 extends past SLA). Provide manager overrides with justification logging and downstream effects on reports. Expose settings per portfolio for SLA definitions, escalation recipients, and thresholds. Synchronize with existing FixFlow approval flows and vendor dispatch states to avoid duplicate timers.

Acceptance Criteria
Always-Visible SLA Timer Adjacent to ETA
Given a tenant or manager views an active dispatched work order When the page loads Then an SLA countdown timer is displayed within 24px of the ETA component and is visible without scrolling Given the user scrolls When the ETA component leaves the viewport Then a compact sticky SLA timer remains visible at the top of the screen Given the ETA updates (window or confidence bands) When the SLA timer is running Then the SLA timer continues without pausing or resetting Given real time progresses When the SLA reaches zero Then the SLA state changes to "Breached" and the timer stops at 00:00:00 with a red status badge Given network reconnects When the page regains connectivity Then the SLA timer resynchronizes to the server time-source with <=1s drift
SLA Policy: Start, Pause, Stop Rules
Given portfolio policy 'start_on' is configured to 'dispatch_accept' When the vendor accepts the dispatch Then the SLA timer starts within 2 seconds Given portfolio policy 'start_on' is configured to 'approval' When the manager approves the work order Then the SLA timer starts within 2 seconds Given portfolio policy defines allowed pause reasons ['Tenant Unavailable','Building Access Delay'] When such an event is recorded Then the SLA timer pauses and logs reason, actor, and timestamp Given any ETA update occurs for the work order When the SLA timer is running or paused Then the ETA update does not pause or stop the SLA timer Given a job completion event is recorded When completion is saved Then the SLA timer stops and state becomes 'Met' if time remaining >= 0 else 'Breached' Given a cancellation with policy-exempt reason is recorded When saved Then the SLA timer stops with state 'Canceled (Exempt)' and is excluded from breach rate reports
Escalation on SLA Risk from ETA Confidence
Given the ETA confidence P80 time exceeds the SLA deadline by any amount When this condition is first detected Then the system creates a 'SLA Risk' event and sends notifications to configured recipients within 60 seconds Given additional ETA updates continue to project P80 beyond SLA When within the configured deduplication window (e.g., 30 minutes) Then no duplicate notifications are sent Given the SLA is at risk When viewing the work order Then a visible 'At Risk' badge appears with the reason cue (e.g., 'Traffic', 'Prior job overrun') Given the SLA is at risk When viewing the portfolio dashboard Then the work order appears in the 'At Risk' queue with remaining time and P80 overage minutes Given the SLA risk clears (P80 returns within SLA) When this occurs Then a 'Risk Cleared' event is logged and the badge is removed
Manager Override with Justification and Reporting Effects
Given a user with Manager role opens an active work order When initiating an SLA override (Pause, Resume, Extend Target, Stop) Then a justification text (min 20 characters) and reason code selection are required Given an override is submitted When saved Then the system records user, timestamp, previous state, new state, reason code, and justification in the immutable audit log Given an override extends the SLA target When reports are generated Then the order is flagged 'Override Applied' and both original and adjusted SLA metrics are available in reporting exports and UI Given an override is applied in error When a Super Admin performs a revert action Then a new audit entry is created linking the revert to the original override; the original entry remains Given an override is attempted by a non-Manager When submitted Then the action is rejected with a 403 error and no SLA state change occurs
Portfolio-Level SLA & Escalation Settings
Given an Org Admin opens Settings > SLA When saving configuration Then they can set SLA targets by category (e.g., Emergency 4h, Standard 48h), start/pause/stop rules, allowed pause reasons, escalation recipients (roles/emails), and risk thresholds (e.g., P80 beyond SLA) Given settings are saved When validated Then invalid combinations are blocked with inline errors and no partial saves occur Given new settings are saved When applied Then they take effect for new work orders immediately and for active work orders that have not yet breached within 5 minutes Given settings change When viewed in audit Then a settings change log entry exists with diff, actor, timestamp, and affected scope (portfolio ID) Given the API is used When calling GET/PUT /portfolios/{id}/sla-settings Then the returned/accepted schema matches documentation and requires ETag for concurrency control
Synchronization with Approval & Dispatch States (No Duplicate Timers)
Given a work order transitions through approval and dispatch When SLA timer creation is triggered by multiple events Then only one SLA timer record exists per work order Given idempotent timer creation is enforced When the same trigger fires again Then the existing timer is reused and no duplicate is created Given vendor dispatch is canceled and re-dispatched When triggers fire Then the original SLA timer continues per policy without creating a new timer Given background jobs process state changes When a retry occurs Then duplicate timers are not created due to unique constraint on work_order_id in SLA timers table
Cross-View Consistency and Timer Persistence
Given a tenant and a manager view the same work order concurrently When observing the SLA timer Then both see the same remaining time with <=1s difference Given the user refreshes the page or reconnects after offline When the app reloads Then the SLA timer resumes from server time with correct remaining time Given the user’s device time is incorrect When viewing the SLA timer Then the timer uses server time and is not affected by local device clock skew Given accessibility requirements When navigating via screen reader Then the SLA timer announces time remaining every 60 seconds and exposes its state (Running/Paused/Met/Breached) via ARIA live region
Auditable ETA Change Log & Reporting
"As a property manager, I want a detailed log and accuracy metrics for ETAs so that I can resolve disputes and improve vendor performance over time."
Description

Maintain an immutable timeline of ETA calculations and changes with timestamps, input signals (redacted where necessary), selected reason codes, confidence values, and who/what triggered the update (system, vendor check-in, manager override). Provide exports and APIs/webhooks for downstream BI and portfolio systems. Deliver dashboards tracking ETA accuracy (P50/P80 hit rates), average variance, notification-triggered call deflection, and vendor performance by reason code. Support filters by property, vendor, region, and time. Apply retention and PII minimization, and include sampling to support future model tuning without retaining raw location indefinitely.

Acceptance Criteria
Append-Only ETA Change Log Capture
Given a maintenance job with an existing ETA and confidence band When the ETA is recalculated by the system, a vendor checks in, or a manager issues an override Then the system appends a new immutable log entry including: event_id (UUID), job_id, prior_eta_utc, new_eta_utc, prior_confidence_band, new_confidence_band, reason_code (controlled list), trigger_source (system|vendor|manager), trigger_actor_id (if applicable), input_signals_summary (redacted), created_at_utc (ms precision), system_version And 99% of updates produce a log entry within 2 seconds of the triggering event And the API returns 201 with event_id for synchronous writes or emits a success metric for async paths
Tamper-Evident Immutability & Time Integrity
Given any existing ETA change log entry When a client attempts to update or delete it via API or admin UI Then the request is rejected with 405 or 409 and no stored fields change And the log stream exposes a verifiable digest; calling GET /eta-change-log/digest returns a SHA-256 digest that matches recomputation over the last 24h of events And all timestamps are stored in UTC with millisecond precision; 99.9% of created_at_utc values are within 200 ms of NTP-synced server time
Real-Time ETA Change Event Webhook & REST Retrieval
Given a registered webhook endpoint with a verified HMAC secret When an ETA change is logged Then a webhook is delivered within 5 seconds containing event_id, job_id, new_eta_utc, confidence_band, reason_code, trigger_source, and created_at_utc And the webhook includes an X-Signature header (HMAC-SHA256 over payload) and an Idempotency-Key; redelivery occurs at-least-once with exponential backoff for up to 24 hours until a 2xx is received And GET /eta-change-log supports filters (property_id, vendor_id, region, time_range), pagination (limit, cursor), and returns results sorted by created_at_utc desc with P95 latency under 500 ms for 100 items
Filtered CSV Export for BI/Portfolio Systems
Given a property manager with Export permission selects a date range and filters by property, vendor, and region When they request a CSV export of the ETA change log Then the file contains only matching records with header row and fields: event_id, job_id, property_id, vendor_id, region, prior_eta_utc, new_eta_utc, confidence_band, reason_code, trigger_source, created_at_utc And numeric/date formats are ISO 8601 for times and minutes for variance; commas in text are quoted; newlines are escaped And exports up to 1,000,000 rows complete within 60 seconds; larger exports stream to an expiring download URL and complete within 15 minutes And all configured PII-excluded fields do not appear in the file
KPI Dashboard: ETA Accuracy, Variance, Vendor by Reason Code
Given the KPI dashboard is loaded with a selected date range and filters (property, vendor, region) When data is presented Then the dashboard displays P50 and P80 hit rates (percentage of visits arriving within the communicated ETA window), average absolute variance (minutes), and a table of vendor performance segmented by reason_code And metrics refresh at least every 15 minutes and indicate last-updated time And clicking any metric segment drills down to the underlying ETA change log entries used to compute it
Notification-Triggered Call Deflection Tracking
Given call telemetry for the maintenance line and ETA notification events are integrated When the dashboard is filtered to a period and scope Then it shows counts of notifications sent, inbound calls within 120 minutes after a notification, and computed deflection_rate = 1 - (calls_after_notification_per_job / calls_without_notification_per_job in a matched cohort) And the calculation method and window (default 120 minutes) are displayed and configurable And metrics are filterable by property, vendor, region, and time and support drill-down to job-level events
Data Retention, PII Minimization, and Location Sampling for Model Tuning
Given data retention policies configured by region When the system processes ETA updates Then raw location traces are discarded within 24 hours; only derived features (distance_km, travel_time_min, coarse_geohash) and redacted input_signals_summary are retained in the audit log And full audit log entries are retained for 24 months by default (configurable per region) and then purged And a statistically random 1% sample of feature-only records is retained for model tuning with no raw lat/long, device identifiers, or tenant PII; the sampling seed is rotated monthly And scheduled jobs verify and report compliance daily, with failures alerting operations within 5 minutes

Doorstep Alert

Geofenced notifications fire when the vendor is 15, 5, and 1 minute away and on‑site. Offers one‑tap options to confirm access or request a short wait, preventing missed connections and easing tenant anxiety at the door.

Requirements

Live Vendor Geofencing & ETA Tracking
"As a tenant, I want accurate alerts when the vendor is approaching so that I can be ready to grant access without waiting unnecessarily."
Description

Implement consent-based, low-latency vendor location tracking via mobile web/PWA with periodic updates, using device GPS and network signals to compute ETA and proximity. Define geofences around the service address to detect 15-, 5-, and 1-minute thresholds as well as on-site arrival, aligned to the scheduled appointment window and local time zone. Use a routing service to account for traffic, apply smoothing to avoid jitter, and throttle updates to balance accuracy with battery usage. Associate live tracking with the FixFlow work order, exposing current ETA to tenants and managers while persisting only the minimum necessary data to support alerts and audit events.

Acceptance Criteria
Vendor Consent and Opt-In for Live Tracking
Given a vendor opens the FixFlow PWA for a scheduled work order within 24 hours of the appointment window, When they view the job details, Then the app requests location tracking consent with a clear purpose statement and options Allow/Don’t Allow. Given the vendor taps Allow, When tracking starts, Then a tracking session is created and linked to the work order with start timestamp, vendor ID, and consent status recorded. Given the vendor taps Don’t Allow or later revokes permission, When the tenant/manager opens tracking, Then live location is not displayed, a privacy-respecting message is shown, and only schedule-based ETA is shown. Given tracking is enabled, When the appointment window ends or the vendor marks Job Completed, Then tracking stops automatically within 10 seconds and no further location updates are collected.
Low-Latency Updates with Battery-Aware Throttling and Smoothing
Given the vendor has granted location permission and marked En Route, When the device is moving > 2 m/s, Then location updates are published at an average interval ≤ 10 seconds and standard deviation ≤ 5 seconds. Given the device is stationary (speed < 0.5 m/s) for ≥ 60 seconds, When tracking continues, Then update frequency is throttled to ≤ 1 update per 30 seconds. Given GPS jitter causes rapid back-and-forth positions within 50 meters, When computing proximity and ETA, Then a smoothing filter is applied such that reported distance does not oscillate by more than ±50 meters between consecutive updates. Given the device enters low power mode or background, When tracking is active, Then updates continue at a minimum frequency of 1 per 30 seconds or resume to ≤ 10 seconds when the app returns to foreground.
ETA Calculation with Traffic-Aware Routing
Given a valid current location and service address, When computing ETA, Then the system uses a routing service with live traffic and road constraints appropriate for the travel mode set by the vendor. Given deviation from the current route exceeds 100 meters or ETA changes by ≥ 60 seconds, When a new location update arrives, Then ETA is recomputed within 2 seconds and the UI refreshes. Given the vendor is within the last 15 minutes of travel, When comparing predicted ETA to actual arrival time, Then the median absolute ETA error across test trips is ≤ 2 minutes. Given the routing service is temporarily unavailable, When computing ETA, Then the system falls back to cached route parameters or schedule-based ETA and logs a recoverable error.
Geofence Threshold Alerts (15, 5, 1 Minutes, On-Site)
Given an active ETA for a work order, When predicted time-to-arrival first crosses 15 minutes, 5 minutes, or 1 minute, Then a single alert event is emitted for that threshold and subsequent duplicate alerts within 2 minutes are suppressed. Given on-site detection uses proximity, When the vendor is within 30 meters of the service address for at least 30 seconds, Then an On-Site arrival event is emitted. Given any threshold alert is emitted, When the tenant/manager/landlord view the work order or notifications, Then they see the alert with local timestamp and one-tap actions to Confirm Access or Request Wait (if 1 or 5-minute thresholds). Given thresholds are computed, When the vendor reverses direction increasing ETA above a previously crossed threshold, Then the system does not re-emit earlier threshold alerts.
Appointment Window and Local Time Zone Alignment
Given a work order has a scheduled appointment window with a service address time zone, When alerts and ETAs are displayed, Then all times are shown in the service address’s local time zone, accounting for daylight saving transitions. Given the vendor approaches before the window start by more than 10 minutes, When a threshold would otherwise fire, Then the system suppresses alerts until within 10 minutes before the window start and labels early arrival in the UI. Given the vendor is late beyond the window end, When ETA exceeds the window by ≥ 5 minutes, Then a Late Arrival notice is presented and an audit event is recorded.
Data Minimization and Audit Logging
Given live tracking is active, When storing data, Then only the following are persisted: consent status, tracking session start/stop timestamps, last computed ETA, threshold alert events with timestamps, and on-site arrival event. Given live tracking is active, When raw latitude/longitude samples are processed, Then no continuous raw location trail is stored beyond what is needed in volatile memory to compute ETA and smoothing (≤ 15 minutes of samples in memory, not persisted). Given an alert or arrival event occurs, When writing audit logs, Then entries include work order ID, vendor ID, event type, local timestamp, and ETA at event, and are retained per data retention policy. Given a data export request for the work order, When generated, Then no raw continuous location data is included—only the persisted minimal events.
Work Order Association and Role-Based Visibility
Given a tracking session is active for a work order, When the tenant or assigned property manager opens the work order, Then the UI displays current ETA, last updated timestamp, and vendor approach status. Given a user is not the tenant of the unit, assigned manager, assigned vendor, or an administrator, When they attempt to access live tracking, Then access is denied and no ETA or location status is shown. Given tracking ends, When the tenant/manager revisits the work order, Then the UI shows “Tracking inactive” and the last known ETA and events, updated within 10 seconds of session end. Given multiple work orders are active for the same vendor, When tracking updates arrive, Then updates are correctly associated to the targeted work order based on session token and work order ID with no cross-leakage.
ETA Threshold Alert Engine
"As a property manager, I want dependable ETA alerts at meaningful thresholds so that tenants and vendors stay synchronized and avoid missed connections."
Description

Create a server-side rules engine that evaluates live ETAs and proximity to trigger alerts precisely at 15, 5, and 1 minute out and upon on-site arrival. Include hysteresis and deduplication to prevent flapping, guardrails to avoid firing before the appointment window, and logic for early/late arrivals. Support configurable thresholds per account, idempotent event generation, and retries with backoff on transient failures. Expose events to downstream channels via a message bus for reliable fan-out to notifications and job timeline updates.

Acceptance Criteria
Threshold Alerts: 15, 5, 1 Minutes and On‑Site
Given a scheduled job with an appointment window [start, end] and default thresholds of 15, 5, and 1 minute And the vendor’s live ETA decreases as they approach the service address And current time is within [start, end] When ETA first becomes <= 15 minutes Then the engine emits exactly one ETA_THRESHOLD event of type ETA_15 for that job And when ETA first becomes <= 5 minutes Then the engine emits exactly one ETA_THRESHOLD event of type ETA_5 for that job And when ETA first becomes <= 1 minute Then the engine emits exactly one ETA_THRESHOLD event of type ETA_1 for that job And when the vendor is detected on-site (inside a 50 m geofence for >= 15 seconds) Then the engine emits exactly one ARRIVAL_ON_SITE event for that job
Hysteresis and Dedup Prevent Flapping
Given noisy ETA updates oscillating around a threshold T in {15, 5, 1} minutes When ETA crosses from > (T + 30 seconds) to <= T and remains <= T for at least 10 seconds Then exactly one ETA_THRESHOLD event for T is emitted for the job And subsequent oscillations across T within 10 minutes do not emit additional ETA_THRESHOLD events for that job And on-site detection requires being inside the 50 m geofence continuously for >= 15 seconds; brief exits/re-entries within 2 minutes do not re-emit ARRIVAL_ON_SITE
Appointment Window Guardrails and Early/Late Logic
Given a job with an appointment window [start, end] When ETA thresholds are crossed before start Then no ETA_THRESHOLD events are emitted before start And if the vendor arrives on-site before start Then an ARRIVAL_ON_SITE event is emitted with arrival_status=early When thresholds are crossed at or after start and before end Then ETA_THRESHOLD events are emitted with arrival_status=on_time When thresholds are crossed after end Then ETA_THRESHOLD events are emitted with arrival_status=late And if arrival occurs after end Then ARRIVAL_ON_SITE is emitted with arrival_status=late
Account-Level Configurable Thresholds Override Defaults
Given an account configured with ETA thresholds of 20, 10, and 2 minutes When ETA decreases and crosses 20, 10, and 2 minutes within the appointment window Then the engine emits ETA_THRESHOLD events of types ETA_20, ETA_10, and ETA_2 respectively And no ETA_THRESHOLD events are emitted for 15, 5, or 1 minute for that account And configuration changes apply to new jobs within 5 minutes and do not retroactively re-emit already fired events for in-progress jobs
Idempotent Event Generation
Given the same threshold crossing is processed more than once (e.g., duplicate ETA updates or replay) When the engine evaluates the crossing Then it computes the same idempotency_key = hash(account_id, job_id, appointment_id, event_type, threshold) And publishes at most one event with that idempotency_key to the message bus And subsequent evaluations with the same idempotency_key do not produce additional bus messages
Transient Failure Retries with Exponential Backoff
Given a transient publish failure occurs when sending an event to the message bus When the engine retries with exponential backoff starting at 200 ms, doubling each attempt up to 3 s max delay, for up to 5 attempts with jitter Then the event is eventually published once if the bus becomes available And if all attempts fail Then the event is written to a dead-letter stream with failure_reason and retriable=true And metrics and logs record attempt_count, total_latency, and final_outcome for observability
Message Bus Fan-Out and Payload Schema
Given an event is emitted by the engine When it is published to the message bus topic eta.alerts with partition key job_id Then downstream consumers for notifications and job timeline receive the message at least once And the payload includes job_id, account_id, vendor_id, event_type, threshold_minutes (nullable for on_site), timestamp_utc, arrival_status (early|on_time|late), idempotency_key, and appointment_window And the message is acknowledged by the bus and retained durably for at least 7 days
Multi-channel Notification Delivery & Preferences
"As a tenant, I want alerts sent via my preferred channel so that I reliably receive timely updates about the vendor’s arrival."
Description

Deliver approach and on-site alerts over web push, SMS, and email with tenant- and vendor-specific preferences and failover. Provide branded, localized templates with deep links for one-tap actions, track delivery/open status, and escalate to fallback channels on non-delivery. Respect quiet hours and opt-in/opt-out rules, and centralize configuration in FixFlow’s communication settings so managers can set defaults by property or portfolio. Log notification outcomes to the work order timeline for transparency.

Acceptance Criteria
Channel Selection and Failover for 15/5/1-Minute and On-Site Alerts
Given a recipient has explicit channel preferences and consents, and a property-level fallback order exists When a Doorstep Alert event (15, 5, 1-minute, or on-site) is emitted for a work order Then select the highest-priority channel that the recipient has opted into and that is permitted at this time, and send within 5 seconds using the correct event template and locale And record the attempted channel and provider request ID Given Web Push is selected When the push send returns an error or the subscription is invalid/stale Then escalate to the next allowed channel within 2 seconds and mark the push attempt as Undelivered with the provider error code Given SMS is selected When the SMS provider returns Failed/Undelivered within 60 seconds, or no Sent acknowledgement within 90 seconds Then escalate to the next allowed channel within 10 seconds and mark the SMS attempt as Undelivered with the provider status Given Email is selected as the last fallback When the email is queued with the provider Then mark the final channel as Email and stop escalation
Quiet Hours Enforcement with Urgency Overrides
Given property or portfolio defaults define quiet hours (e.g., 22:00–07:00 in the property’s local time zone) When a non-urgent Doorstep Alert occurs during quiet hours Then do not send SMS or Web Push to the tenant, send Email within 10 seconds, and queue suppressed channels until quiet hours end or the event becomes obsolete Given a Doorstep Alert is flagged Urgent by rules or manager override When it occurs during quiet hours Then allow SMS and Web Push according to the recipient’s opt-ins and preferences, and record the override reason in the log Given a recipient has opted out of a channel When quiet hours would otherwise suppress other channels Then do not violate the opt-out; only use channels with consent and allowed by quiet hours
Branded, Localized Templates with Secure One‑Tap Deep Links
Given a Doorstep Alert event type (15, 5, 1-minute, on-site) When composing the notification Then select the template ID matching the event (doorstep_approach_15, doorstep_approach_5, doorstep_approach_1, doorstep_on_site) And apply branding (property name/logo and support contact) and locale by precedence Recipient > Property > Portfolio > en-US And populate placeholders (vendor_name, eta_minutes, property_address, work_order_id) with fallbacks if data is missing Given a notification is sent via any channel When the recipient opens the deep link Then validate a signed token (expiring in 30 minutes or after first use) and route to the action page And display one‑tap actions: Confirm Access and Request 5‑minute Wait And upon tap, persist the action to the work order and respond with success within 2 seconds
Delivery and Open/Click Tracking with Non‑Delivery Escalation
Given any notification is sent When the provider responds Then capture provider message ID and status transitions (Queued, Sent, Delivered, Failed/Undelivered) with timestamps to the second Given Web Push is sent When a notificationclick event occurs Then record an Open with timestamp and user ID Given SMS is sent with a tracked link When the link is clicked Then record an Open with timestamp and user ID Given Email is sent with open tracking and tracked links When the email is opened or a link is clicked Then record an Open with timestamp and user ID Given a message status becomes Failed/Undelivered or no status update occurs within 90 seconds (timeout) When a fallback channel exists and consent allows Then escalate to the next channel and log the escalation reason and timing
Centralized Communication Settings and Defaults Precedence
Given a manager updates Communication Settings for a property When they set channel priority, quiet hours, locale default, and escalation timeouts Then the settings persist with an audit entry (who, what, when) and propagate to new events within 60 seconds Given portfolio-level defaults exist and a property overrides them When an alert is emitted for that property Then the property defaults take precedence over portfolio defaults Given a recipient has explicit preferences When an alert is emitted Then recipient-level preferences take precedence over property defaults, which take precedence over portfolio defaults
Opt‑In/Opt‑Out Compliance and Consent Management
Given a recipient has not provided SMS consent When an alert would select SMS by priority Then do not send SMS, skip to the next allowed channel, and log consent block Given a recipient replies STOP to an SMS When subsequent alerts are emitted Then block SMS delivery, record the opt‑out timestamp and source, include an Unsubscribe status in the log, and continue with other allowed channels Given an email is sent When the recipient clicks Unsubscribe Then mark email opt‑out and block future emails while still honoring other channel preferences
Work Order Timeline Logging and Transparency
Given any notification attempt or status change occurs for a work order When the event happens Then append a timeline entry with: event type, intended and attempted channels, final delivered channel, template ID, locale, deep link action IDs (if any), provider message ID, status (Queued/Sent/Delivered/Failed/Escalated), timestamps, and actor (system/user) Given quiet hours suppress a channel When suppression occurs Then add a timeline note indicating suppression window and next action time Given a one‑tap action is taken from a deep link When the action is confirmed Then record the action, source channel, and timestamp in the same timeline
One-tap Access Coordination Actions
"As a tenant, I want to confirm access or request a short wait with one tap so that we avoid missed connections at the door."
Description

Enable tenants to respond to alerts with one tap to Confirm Access or Request Short Wait (e.g., 5/10/15 minutes), and surface these actions instantly in the vendor portal and manager dashboard. Validate availability windows and suppress actions when out of scope, confirm action receipt to the tenant, and update the work order state accordingly. Notify the vendor with clear UI and optional TTS/read-out for hands-free acknowledgement, and record all actions in the job audit trail. Provide configurable limits on maximum wait time and automatic timeout behaviors.

Acceptance Criteria
One-Tap Confirm Access at 5-Minute Alert
Given a tenant receives a 5-minute Doorstep Alert during the active appointment window and actions are enabled When the tenant taps "Confirm Access" Then the system records the action with workOrderId, tenantId, actionId (UUID), and UTC timestamp And the work order state updates to "Access Confirmed" And the tenant sees a success confirmation within 2 seconds of tap (p95) And the action is de-duplicated by actionId on retries
Request Short Wait Within Configured Limit
Given maxWaitMinutes is configured for the work order (e.g., 15) and selectable options default to [5, 10, 15] but never exceed maxWaitMinutes And the selected wait will not extend beyond the appointment end time When the tenant taps "Request Short Wait" and selects 10 minutes Then the request is accepted and the work order state updates to "Short Wait Active (10m)" And a countdown timer starts for both tenant and vendor views And on expiry, the state auto-transitions to "Proceed" and both tenant and vendor are notified within 3 seconds (p95) And the action and auto-timeout are recorded in the audit trail
Actions Suppressed Outside Availability Window
Given the current time is outside the appointment window OR maxWaitMinutes would be exceeded OR the job is not in a state that allows access coordination When the tenant opens the alert action sheet Then "Confirm Access" and "Request Short Wait" controls are hidden or disabled And an inline message explains the reason (e.g., "Actions unavailable outside appointment window" or "Maximum wait reached") And no action request is sent to the server And a suppression event with reason code is logged for analytics
Real-Time Action Propagation to Vendor and Manager
Given an access action (Confirm Access or Short Wait) is accepted by the backend When the vendor portal and manager dashboard are online Then both reflect the new state, wait duration (if any), and action timestamp within 3 seconds (p95) and 5 seconds (p99) And if offline, each client receives the update within 60 seconds of reconnect And displayed state and timestamps are consistent across vendor and manager views within 1 second drift
Tenant Receipt and Error Handling for Access Actions
Given transient network issues up to 10 seconds occur When the tenant submits an access action Then the client retries up to 3 times with exponential backoff and shows a non-blocking progress indicator And if the backend confirms the action, the tenant receives a success receipt within 2 seconds (p95) and no duplicate actions are created (idempotent by actionId) And if all retries fail, the tenant sees an error message within 10 seconds and the work order state remains unchanged And the failure is logged with correlationId for support
Work Order State Updates and Audit Trail Logging
Given an access action occurs (Confirm Access, Short Wait Requested, Short Wait Expired) When the action is processed by the backend Then the work order state transitions as follows: Confirm Access -> "Access Confirmed"; Short Wait Requested (X) -> "Short Wait Active (Xm)"; Short Wait Expired -> "Proceed" And the job audit trail records an immutable entry with actor (tenant/system), action type, parameters (minutes where applicable), ISO 8601 timestamp, priorState, nextState, and source (mobile web) And audit entries are visible in the manager dashboard within 3 seconds (p95) and are filterable by action type
Vendor Notification UI and Optional TTS Read-Out
Given the vendor app/portal is running and push/in-app notifications are enabled When a tenant submits Confirm Access or a Short Wait request Then the vendor receives a clear, high-contrast banner with the action details and a single-tap "Acknowledge" control And if TTS is enabled in vendor preferences and the device is not in Do Not Disturb, the notification content is read aloud within 3 seconds (p95) And the vendor can mute or replay TTS for the job; the preference persists per user And notification deliveries (push, in-app, TTS played) are logged with timestamps
Vendor On-site Check-in & Proof-of-presence
"As a vendor, I want to confirm when I am on-site so that tenants and managers know I’ve arrived and can coordinate access smoothly."
Description

Detect on-site arrival via geofence entry or manual “I’m here” check-in with accuracy metadata, capturing timestamp and optional doorstep photo for proof-of-presence. Trigger an on-site notification to the tenant and update the work order timeline and SLA timers. Provide building access instructions to the vendor upon on-site status and highlight any tenant-provided notes collected during intake. Handle multi-unit properties by validating the exact unit and entrance details.

Acceptance Criteria
Geofence-Based Automatic On-site Detection
Given a work order with a configured geofence radius of 75 meters centered on the assigned entrance And the assigned vendor’s mobile app is sharing foreground location When the device location first enters the geofence with reported horizontal accuracy <= 50 meters Then the work order status is set to "On-site" with fields source=geofence, accuracy_m, lat, lon, device_os, and server_timestamp persisted And an "On-site" event is appended to the work order timeline And subsequent geofence entries for the same work order within 10 minutes are ignored
Manual "I’m Here" Check-in Fallback
Given the assigned vendor opens the work order in the mobile app When the vendor taps "I’m here" Then the system records an "On-site" event with source=manual, and captures lat, lon, accuracy_m, device_os, and server_timestamp And if accuracy_m > 100 meters or distance_to_assigned_entrance > 500 meters, the vendor must provide a reason and optionally add a photo before submission And if the device is offline, the event is queued and sent within 30 seconds of connectivity; upon sync, the server assigns server_timestamp and marks offline=true
Optional Doorstep Photo Proof-of-presence
Given the vendor is performing an on-site check-in (geofence or manual) When the vendor adds a doorstep photo Then the photo is accepted in JPEG or HEIC format, auto-compressed to <= 3 MB, and stored at >= 720p resolution And the photo is linked to the "On-site" event and visible in the work order timeline with a timestamp And if camera permissions are denied, the app offers file upload as fallback
Tenant On-site Notification Dispatch
Given the tenant has at least one active notification channel (push, SMS, or email) When an "On-site" event is created Then a tenant notification is dispatched within 30 seconds containing vendor_name, work_order_id, and status="On-site" And the notification includes actions "Confirm Access" and "Request 5-minute Wait" And delivery attempts and outcomes are logged; failures are retried up to 3 times with exponential backoff
Timeline and SLA Updates on On-site
Given SLA tracking is enabled for the property When an "On-site" event is created Then the Response SLA timer is stopped and recorded And the On-site-to-Start SLA timer is started And the timeline shows an immutable "On-site" entry with source and accuracy metadata; corrections are recorded as separate entries with an audit trail
Access Instructions and Intake Notes Reveal on On-site
Given the work order has stored building access instructions and tenant-provided intake notes When the "On-site" status is set Then the vendor app immediately displays the access instructions and highlights tenant notes, including unit number and entrance details And sensitive access fields (e.g., door code) are masked until "On-site" then unmasked And the vendor can re-open this panel from the work order at any time while on-site
Multi-Unit Unit and Entrance Validation
Given the property has multiple units or entrances When the vendor attempts to check in on-site Then the system validates the check-in against the assigned unit and entrance_id And if distance_to_assigned_entrance > 75 meters, the vendor must confirm location or select the correct entrance; otherwise the check-in is blocked And the event stores unit_id and entrance_id; information for other units is not displayed to the vendor
Privacy, Consent & Data Minimization
"As a vendor, I want control over how my location is used so that my privacy is protected while still enabling timely arrivals."
Description

Present clear consent flows explaining location usage and duration, allow users to start/stop sharing, and limit collection to the active appointment window. Store coarse-grained, time-bucketed locations only as needed for alerting and audit, purge detailed coordinates after a short retention period, and obfuscate positions outside the service geofence. Implement GDPR/CCPA-compliant preferences, data subject request handling, and privacy-by-default settings for all Doorstep Alert interactions.

Acceptance Criteria
Vendor Consent and Start/Stop Sharing Controls
- Given a vendor opens Doorstep Alert for an appointment without prior consent, When the consent screen is displayed, Then it clearly states the purpose of location usage, the active window duration, the data types stored (coarse/time-bucketed), retention periods, and includes links to the Privacy Policy. - Given consent is not granted, When the vendor proceeds, Then no location is collected, no geofence alerts are computed, and the UI indicates "sharing off" with a non-blocking prompt to enable later. - Given the vendor taps Start Sharing and grants OS permissions, When sharing begins, Then a timestamped consent record (user, appointment, purpose, scope) is stored and status reflects "sharing on". - Given the vendor taps Stop Sharing or revokes OS permission, When the event occurs, Then collection ceases within 5 seconds, pending alerts are canceled or downgraded to non-location workflows, and a revoke timestamp is logged. - Given OS-level permissions are limited to the minimum necessary, When requesting access, Then the app requests "While Using" location permission and does not request background access unless strictly required by the active window.
Appointment-Bound Collection Window Enforcement
- Given an appointment scheduled from T_start to T_end with a configurable pre-arrival buffer of 20 minutes, When current time is outside [T_start - 20m, T_end], Then zero location pings are accepted server-side for that appointment. - Given current time is within the active window and sharing is on, When collecting location, Then sampling frequency is no more than 1 ping per 60 seconds and collection ceases outside the window. - Given the appointment is canceled or marked complete early, When the status changes, Then collection stops within 5 seconds and any queued unsent data is discarded. - Given multiple concurrent appointments exist, When determining the active window, Then only the window for the appointment the vendor explicitly selected is eligible for collection.
Coarse-Grained Storage and Time-Bucketing for Audit
- Given location samples are received during the active window, When persisting for alerting and audit, Then coordinates are rounded to a grid of ≥200 meters and timestamps are rounded into 5-minute buckets. - Given an alert triggers (15-minute, 5-minute, 1-minute, or on-site), When storing evidence, Then only the alert type and event timestamp are saved; no raw path samples are retained. - Given an audit export is generated, When compiling the dataset, Then only bucketed coordinates and alert events are included and no raw/high-precision coordinates appear. - Given internal analytics are run, When querying location data, Then only aggregated/bucketed data is available; queries for raw coordinates return none.
Obfuscation Outside Service Geofence
- Given the vendor is outside a 1 km radius geofence around the assigned property, When processing location for tenant-facing views, Then exact coordinates are replaced with an obfuscated region (cell size ≥3 km) or an "En route" state and no street-level details are shown. - Given the vendor is outside the geofence, When computing tenant notifications, Then only ETA times are used/shown and the live map trace is suppressed. - Given the vendor enters the geofence, When updating views, Then position resolution remains coarse (≥100 m) and only the "on-site" state is revealed at arrival.
Automated Purge and Short Retention of Detailed Coordinates
- Given raw high-precision coordinates may exist transiently in processing, When persisting server-side, Then raw coordinates are not written to durable storage; only bucketed data is persisted. - Given transient raw data exists in queues or caches, When retention timers elapse, Then the data is dropped within 15 minutes of creation. - Given bucketed audit records exceed 30 days of age (configurable), When the nightly purge job runs, Then all records older than the retention period are deleted and the deletion is logged with counts and time span. - Given a purge operation fails, When monitoring runs, Then an alert is raised and a retry is attempted within 1 hour. - Given a retention configuration change is deployed, When the job runs next, Then the new retention policy is enforced without resurrecting previously purged data.
GDPR/CCPA Preferences and Data Subject Requests
- Given a user opens Privacy settings, When viewing Doorstep Alert preferences, Then location sharing is Off by default and must be opted in per appointment; data sale/share toggles are Off by default where applicable. - Given a user requests Export My Data, When identity is verified, Then a downloadable export of Doorstep Alert location/audit records (bucketed only) is provided within 30 days and the request status is trackable in-app. - Given a user requests Delete My Data, When identity is verified and legal exemptions do not apply, Then all personal Doorstep Alert data is deleted within 30 days, processing is blocked going forward until consent is re-obtained, and the user is notified on completion. - Given a user withdraws consent, When the withdrawal is submitted, Then further collection stops immediately and a tamper-evident audit log entry of withdrawal is recorded. - Given role-based access controls are configured, When staff attempt to view location data, Then only authorized roles can access bucketed records and no role can access raw coordinates.
Reliability & Fallbacks for Low-Accuracy/No-GPS
"As a property manager, I want the system to handle unreliable location gracefully so that alerts stay useful and appointments remain on track."
Description

Detect stale or low-accuracy signals and gracefully degrade by prompting time-based SMS check-ins with quick-reply ETAs or an “I’m here” link. Provide phone-call fallback cues to the tenant or manager if all digital attempts fail, and convert the workflow to time-window reminders when live tracking is unavailable. Implement robust retry, queueing, and monitoring for the alert pipeline with health dashboards and alerts, ensuring high delivery success across devices and carriers.

Acceptance Criteria
Auto-Detect Low Accuracy or Stale Location
Given an active Doorstep Alert tracking session When the latest GPS accuracy radius exceeds 200 meters or the last location update is older than 120 seconds Then the system flags tracking state as Degraded within 5 seconds and logs a reason code (LOW_ACCURACY or STALE_SIGNAL) And the ETA countdown switches to an "Approximate" state until quality recovers for at least 60 seconds And when accuracy is <= 100 meters and last update < 30 seconds for 60 consecutive seconds, tracking state returns to Normal and countdown resumes
SMS Check-in Prompt on Degraded Tracking
Given tracking state is Degraded for more than 60 seconds and the vendor has an SMS-capable number When the check-in fallback triggers Then send an SMS within 10 seconds containing quick-reply buttons for ETAs [5, 10, 15, 30] minutes, a "Running late" option, and an "I'm here" arrival link And when the vendor selects an ETA option, update the tenant and manager timelines with the selected ETA within 5 seconds and re-present the 1-tap Confirm Access option And when the vendor taps "I'm here", mark the job as On-Site, fire the On-Site alert, and store the arrival timestamp without requiring GPS And if there is no SMS response within 2 minutes, send exactly one reminder; maximum of 2 reminders per appointment
Phone-Call Fallback Escalation
Given SMS delivery is undeliverable (carrier error) or there is no vendor SMS response after 2 reminders When the escalation window of 5 minutes elapses Then initiate outbound call workflow: attempt vendor, then tenant, then manager with scripted cues and DTMF confirmation And place up to 2 call attempts per party spaced by 2 minutes; leave voicemail with a short code callback if unanswered And if any party confirms access or arrival via keypress, record confirmation, notify all parties, and halt further call attempts And log call outcomes (answered, voicemail, no answer, confirmed) with timestamps and correlation IDs
Convert to Time-Window Reminders
Given live tracking has been unavailable for more than 10 minutes prior to the scheduled arrival window start When the appointment is scheduled for Today Then switch alerting mode to Window Reminders and suppress 15/5/1-minute geofence alerts And schedule notifications at T-30 and T-10 before window start and at window start; send a vendor "Are you on track?" prompt at T-15 And if live tracking resumes at any time, automatically revert to geofence-based alerts and cancel pending window reminders
Alert Pipeline Retry and Durable Queueing
Given any outbound alert (push, SMS, call, email) experiences a transient failure (HTTP 5xx, timeout, carrier temporary error) When the delivery attempt fails Then retry up to 5 times using exponential backoff starting at 2 seconds with jitter and a cap of 120 seconds And enqueue alerts durably so that on process restart all pending alerts are delivered exactly once using idempotency keys And place irrecoverable failures into a dead-letter queue with reason code and appointment correlation And maintain P95 end-to-end alert latency <= 10 seconds for SMS/push and <= 20 seconds for calls during normal operation
Monitoring, Health Dashboards, and On-Call Alerts
Given the system is operating in production When delivery success over a 24-hour rolling window drops below 98% or P95 latency exceeds thresholds (10s SMS/push, 20s calls) or queue depth exceeds 10,000 or error rate > 2% for 5 minutes Then send on-call alerts within 60 seconds with impacted channel and carrier breakdown And provide a dashboard with real-time and 30-day trends for success rate, latency, queue depth, per-carrier failures, and incident map And run synthetic probes for SMS, push, and call paths every 5 minutes per region; page on-call upon 2 consecutive probe failures
Tenant and Manager UX Cues in Fallback Modes
Given any fallback mode (Degraded tracking, Window Reminders, Escalation) is active When the tenant or manager opens the Doorstep Alert card Then display the current status (e.g., "Tracking limited"), the last update timestamp, and primary actions: Confirm Access and Request Short Wait And when a vendor selects a short-wait ETA via SMS, reflect the countdown timer to the tenant within 5 seconds and allow Accept/Decline And persist all status transitions and user actions to the audit log with timestamps and actor IDs

Delay Transparency

If the SLA is at risk, automatically pushes a clear reason, updated ETA, and recovery choices—pick a new window, switch to a backup vendor, or authorize after‑hours. Turns frustration into control and preserves trust through honesty.

Requirements

SLA Risk Detection
"As a property manager, I want the system to detect when a work order is at risk of missing its SLA so that I can intervene early and preserve tenant trust."
Description

Continuously monitors all active work orders against property- and category-specific SLAs to predict breach risk before it occurs. Combines signals such as vendor confirmation latency, schedule drift, travel constraints, parts availability, and tenant readiness to compute a risk score. When thresholds are met, raises a structured "delay at risk" event that initiates Delay Transparency workflows. Integrates with FixFlow’s job timeline, vendor calendar sync, and SLA configuration to ensure timely detection and minimal false positives.

Acceptance Criteria
Risk Score Calculation & Threshold Trigger
Given an active work order with vendor, schedule, travel, parts, and tenant signals available When the risk engine evaluates the work order every 5 minutes Then it computes a normalized riskScore between 0 and 100 from the available signals using documented weights that sum to 100 And the riskScore and contributingWeights are persisted with a timestamp And if riskScore >= threshold configured in the SLA (default 70) a delay-at-risk event is emitted within 60 seconds of computation
Vendor Confirmation Latency Signal
Given a work order dispatched to a vendor with no confirmation recorded And the property/category response SLA is S hours When elapsed time since dispatch >= 0.5 * S and confirmation is still missing Then the riskScore contribution for vendorConfirmationLatency is at least +25 And if riskScore >= SLA threshold a delay-at-risk event is emitted with reasonCodes including VENDOR_CONFIRMATION_LATENCY And the event payload includes vendorId, dispatchedAt, and elapsedMinutes
Schedule Drift via Calendar Sync
Given a synced vendor calendar event exists for the work order And the event start time is later than the SLA deadline or the event is canceled When the calendar sync completes Then a delay-at-risk event is emitted within 5 minutes with reasonCodes including SCHEDULE_DRIFT And the event payload includes calendarEventId, currentETA, slaDeadline, and deltaMinutes And the work order timeline shows the event entry with a link to the calendar event
Parts Availability Impact
Given the work order requires parts and partsStatus indicates backorder or leadTimeMinutes > minutesUntilSLADeadline When parts status is updated Then the riskScore contribution for partsAvailability is at least +30 And a delay-at-risk event is emitted with reasonCodes including PARTS_AVAILABILITY And the event payload includes partsETA, partsStatus, and revisedETA that is not earlier than partsETA
Tenant Readiness & Access Constraints
Given tenant availability is unknown or the tenant declined the proposed window And now is within 24 hours of the SLA deadline When no confirmed access window exists Then the riskScore contribution for tenantReadiness is at least +20 And a delay-at-risk event is emitted with reasonCodes including TENANT_READINESS And the event payload includes proposedWindows, nextAvailableWindows, and tenantContactStatus
Delay-At-Risk Event Schema & Routing
Given any risk threshold is met for a work order When the delay-at-risk event is emitted Then the payload contains workOrderId, propertyId, category, riskScore, reasons, currentETA (ISO 8601), confidence (0-1), slaDeadline, createdAt, and correlationId And the event is published to topic delay.atRisk.v1 and retried on failure up to 3 times with exponential backoff And a timeline entry referencing the eventId is created within 60 seconds
Detection Quality & Lead Time
Given a 30-day dataset of resolved work orders without SLA breach When the risk engine is run retrospectively Then the false positive rate is less than 3% Given a 30-day dataset of breached SLAs When the risk engine is run retrospectively Then at least 80% of breached work orders have a delay-at-risk event published at least 120 minutes before the breach And for breached cases with calendar events scheduled before the SLA deadline, detection occurs within 10 minutes of the scheduling change that caused the breach
Delay Reason Aggregation
"As a tenant, I want a clear, verified reason for a delay so that I understand the situation and feel informed rather than ignored."
Description

Collects and normalizes delay reasons from multiple sources (vendor portal/app updates, SMS replies, phone call transcripts, parts/order systems) into a single verified explanation. Applies validation rules (e.g., vendor justification required for reschedule, proof of part backorder) and maps inputs to standardized reason codes while preserving human-readable text. Ensures one clear, concise reason is available for tenant/owner messaging and analytics, with fallbacks if data is incomplete.

Acceptance Criteria
Vendor Portal Update Normalization
Given a vendor portal update containing a free-text delay reason and a rescheduled appointment window with timezone When the update is ingested Then the system maps the reason to a standardized reason code from the approved catalog and stores the original text verbatim. Given the rescheduled appointment window When normalized Then the ETA is stored in ISO 8601 with timezone and is visible in the unified delay record. Given the ingested update When stored Then source=vendor_portal and source_timestamp are recorded.
Reschedule Requires Vendor Justification
Given a delay flagged as vendor-initiated reschedule When vendor justification text is missing or shorter than 10 characters Then validation fails, the reason is not publishable, and a justification request is sent to the vendor via their primary channel within 5 minutes. Given a justification arrives within 30 minutes that meets length >= 10 characters When re-validated Then the delay becomes publishable and the verified reason includes justification_text. Given no acceptable justification within 30 minutes When validation times out Then the reason remains unpublished to tenants/owners, an internal alert is created, and status is set to Pending Vendor Justification.
SMS Reply Parsing and Mapping
Given an SMS reply from a vendor or tenant that mentions a delay When processed by the parser Then a reason code is assigned if confidence >= 0.80 and the original text is preserved. Given confidence < 0.80 When classification fails Then reason code = UNDETERMINED and the item is queued for human review without publishing. Given a time phrase in the SMS (e.g., "arriving 3–5pm") When extracted Then the ETA window is normalized to ISO 8601 and appended to the unified delay record.
Phone Call Transcript Extraction and Validation
Given a recorded call related to an active work order When the transcript is generated Then key delay phrases are extracted and mapped to a reason code if confidence >= 0.75. Given PII in the transcript (phone numbers, emails) When stored Then PII is redacted in the evidence attached to the unified reason. Given transcript ingestion starts When processing completes Then total processing time is <= 2 minutes for 95th percentile calls (<= 10 minutes audio).
Parts/Order Backorder Verification
Given a parts/order system event indicating backorder When ingested Then a valid order_id, sku/part_number, and vendor name must be present; otherwise validation fails. Given the event passes validation When verified via API lookup Then reason code = PARTS_BACKORDER, evidence_url is stored, and ETA is updated from expected_ship_date if present. Given API lookup fails or returns ambiguous results When retried up to 3 times over 15 minutes Then the event is marked Unverified and is not publishable.
Conflict Resolution and Single Verified Reason
Given multiple candidate reasons from different sources within the same work order in the last 4 hours When the aggregator runs Then exactly one verified reason is produced using the priority order: parts/order > vendor_portal > phone_transcript > sms, breaking ties by most recent source_timestamp. Given the verified reason is selected When published Then the human-readable text is synthesized to <= 140 characters, includes the top evidence source, and the standardized reason code is attached. Given lower-priority reasons When reconciled Then they are retained as secondary_evidence with their confidence scores and timestamps.
Fallback Reason and Analytics Output Schema
Given the SLA-at-risk trigger fires and no publishable verified reason exists after 15 minutes When the aggregator runs Then a fallback reason is generated with code=UNKNOWN_DELAY, text="Delay reported, cause unconfirmed", confidence<=0.50, and is publishable. Given any new verified, publishable reason arrives after a fallback was published When processed Then the fallback is replaced within 2 minutes and an update event is emitted. Given a verified reason (or fallback) is published When stored Then the analytics record includes: reason_code (non-null), human_text (<= 280 chars), source, confidence (0–1), eta (nullable ISO 8601), evidence_refs (array), and last_updated (ISO 8601), and passes schema validation.
Real-time ETA Recalculation
"As a landlord, I want updated ETAs based on real conditions so that I can set accurate expectations and avoid repeated tenant escalations."
Description

Recomputes service ETAs when risk is detected using vendor capacity, current route conditions, historical job durations by category, and parts ETAs. Produces an updated time window and confidence level, writes updates to the work order timeline, and feeds downstream notifications. Includes hysteresis to avoid excessive ETA churn and respects time zones and property blackout periods.

Acceptance Criteria
Risk Trigger Recalculates ETA Within 30 Seconds
Given an active work order with an assigned vendor and a baseline ETA window And a valid SLA risk signal is received (vendor delay, traffic congestion spike > 15 minutes, or parts ETA slip > 15 minutes) And vendor capacity snapshot is not older than 15 minutes And live route data cache age is <= 5 minutes And historical job duration statistics (P50, P90) are available for the job category from the last 180 days When the recalculation is triggered Then the system computes a new ETA within 30 seconds And the computation uses vendor capacity, live route data, category durations, and parts ETAs And the result includes a start and end timestamp and a numeric confidence 0-100
Updated ETA Window and Confidence Formatting
Given an ETA recalculation result exists When producing the ETA for display and API Then start and end are returned as ISO 8601 with property timezone offset (e.g., 2025-09-06T14:00:00-04:00) And confidence is an integer 0-100 And if confidence >= 80, the window length is <= 2 hours And if 50 <= confidence < 80, the window length is <= 3 hours And if confidence < 50, the window length is <= 4 hours And start < end
Hysteresis Prevents ETA Churn
Given a work order has emitted an ETA update within the last 5 minutes When a new recalculation yields a shift of < 10 minutes in window midpoint and a confidence delta < 5 points Then no new ETA update is emitted And the system batches changes until either 5 minutes elapse or net shift >= 15 minutes or confidence delta >= 10 points And no more than 1 ETA update is emitted per work order per 5-minute window unless crossing a blackout boundary or an SLA-breach threshold
Local Timezone and Blackout Compliance
Given the property has a configured IANA timezone and blackout periods When recalculating a new ETA window Then all times are computed in UTC and rendered in the property's timezone And the window does not overlap any blackout period And if the computed window would overlap, it is rolled to the next available non-blackout window of equal length And the update is annotated with reason code BLACKOUT_ADJUSTED = true
Timeline and Audit Trail Update
Given a new ETA window is committed When writing to the work order timeline Then an immutable entry is appended containing: occurredAt (UTC), oldWindow, newWindow, oldConfidence, newConfidence, reasonCodes[], dataSourcesVersion, actor = system And entries are strictly ordered by occurredAt And identical updates (same window and confidence within 1 minute) are not duplicated And the entry is retrievable via API GET /work-orders/{id}/timeline within 2 seconds of commit
Downstream Notification Event Emission
Given an ETA update is accepted for emission When publishing to downstream consumers Then a single event is sent to the notification bus and to subscribed webhooks within 10 seconds And the payload includes workOrderId, correlationId, oldWindow, newWindow, oldConfidence, newConfidence, reasonCodes[], propertyTimeZone, blackoutAdjusted, occurredAt And an idempotency key equal to SHA-256(workOrderId + newWindow + newConfidence) is provided And duplicate deliveries are suppressed by consumers via the idempotency key
Recovery Options Orchestration
"As a property manager, I want clear recovery choices with their trade-offs so that I can quickly pick the best path to restore service."
Description

Generates actionable recovery choices—reschedule windows, switch to an approved backup vendor, or authorize after-hours service—based on policy rules, budget caps, service category, tenant availability, and vendor SLAs. Computes trade-offs (cost, soonest availability, SLA compliance likelihood) and highlights a recommended option. Captures explicit consent from the decision-maker (tenant or manager), enforces decision deadlines, and automatically triggers the chosen workflow.

Acceptance Criteria
SLA At-Risk: Compute Options and Recommendation
Given a work order is predicted to miss its SLA and the at‑risk threshold is set to 8 hours And policy rules, budget caps, service category, tenant availability, and vendor SLAs are available When the remaining time to SLA breach is less than 8 hours Then the system generates all viable recovery options (reschedule window, approved backup vendor, after‑hours service) And each option displays: total cost (currency), earliest completion timestamp (ISO 8601), and SLA compliance likelihood (percentage) And at least one option is marked as Recommended using the configured weighting (soonest availability 50%, cost 30%, compliance likelihood 20%) And the recommendation rationale is displayed in one sentence.
Option Compliance With Policies and Budget
Given policy rules and a budget cap are defined for the work order When recovery options are computed Then options that exceed the budget cap are excluded by default and labeled Over Budget if shown with override permissions And after‑hours options appear only if the service category permits after‑hours and remaining budget covers the after‑hours premium And backup vendor options include only vendors on the approved list for the service category with active compliance (insurance, tax documentation) And the system records a machine‑readable reason code for every option excluded due to policy or budget.
Consent Capture and Attribution
Given the decision‑maker is identified as tenant or manager When they confirm a recovery option Then the system requires explicit consent via a Confirm Choice action capturing full name, role, timestamp, and consent checkbox acknowledgment And stores an immutable audit record with work order ID, option chosen, consent data, user ID, and source IP/user agent And displays a confirmation with the chosen option summary and next steps.
Decision Deadline and Escalation
Given recovery options are presented with a decision deadline of 2 hours from initial notification When 60 minutes remain before the deadline Then the system sends a reminder to the decision‑maker via configured channels (email, SMS, in‑app) And when the deadline expires without a decision Then the system executes the configured fallback (e.g., auto‑reschedule earliest within budget) and logs an escalation event with timestamp and reason And notifies tenant, manager, and vendor of the action taken and updated ETA.
Automatic Trigger of Chosen Workflow
Given a recovery option is confirmed by the decision‑maker When the option is Reschedule Window Then the system updates the appointment to the selected window, recalculates ETA, and sends notifications to tenant and vendor within 2 minutes When the option is Switch to Approved Backup Vendor Then the system dispatches to the selected approved vendor, cancels the original dispatch, transfers all notes/attachments, and sends notifications within 5 minutes When the option is Authorize After‑Hours Service Then the system updates pricing with after‑hours premium, flags after‑hours status, and dispatches an available approved vendor within 10 minutes.
No Viable Options Path
Given constraints eliminate all recovery options When option computation returns zero viable options Then the system displays a clear message listing blocking constraints and reason codes And offers an Escalate for Override action to the property manager And upon manager approval with updated policy or budget, recomputes options immediately and records the override in the audit log.
Multi-Channel Notifications & Acknowledgment
"As a tenant, I want timely, easy-to-act-on notifications so that I can choose a convenient recovery option without calling support."
Description

Delivers delay alerts and choices via mobile web portal, SMS, and email with deep links to a frictionless, photo-first experience. Supports localization, accessibility, and rate limiting. Captures acknowledgments and option selections with secure, one-tap flows, and escalates to alternate contacts or managers if no response within configured timeframes. All messages are recorded on the work order timeline.

Acceptance Criteria
SLA-Risk Multi-Channel Alert Dispatch
Given a work order's predicted arrival breaches the SLA threshold When a delay risk is detected Then an email and SMS are sent to the primary contact within 60 seconds and a portal landing page is created And messages include the delay reason, updated ETA, and the choices: pick a new window, switch to a backup vendor, or authorize after-hours And each message contains a signed deep link to the portal flow And delivery status is tracked; failed sends are retried up to 3 times with exponential backoff And if both email and SMS fail, a portal banner is queued for the next session
Secure One-Tap Acknowledgment & Choice
Given a recipient opens a deep link When the token is valid, unexpired (<=24h), single-use, and matches the work order and recipient Then the user is auto-authenticated into the delay choices screen without additional login And the user can one-tap: pick a new window, switch to a backup vendor, or authorize after-hours And invalid or expired tokens show a secure re-auth prompt and issue a fresh link on success And selections update the work order and trigger confirmation via the same channel opened And acknowledgment and selection are stored with user identity, timestamp, and device/IP metadata
Localized Content & Fallback
Given a contact's language preference is set When generating the alert and portal page Then all copy, buttons, and date/time formats use that locale And if a translation key is missing, content falls back to English and logs a warning And recipients can switch language inline and receive subsequent messages in the chosen language
Accessible Mobile Web Flow
Given a user with assistive technology accesses the portal page When interacting with the delay choices Then the page meets WCAG 2.1 AA for contrast, focus order, labels, and keyboard navigation And actionable elements have at least 44x44 px touch targets and visible focus styles And the flow loads in under 2 seconds on a 3G connection and avoids horizontal scrolling on 320px width And optional photo capture/upload is available and does not block submission
Cross-Channel Rate Limiting & De-Dupe
Given multiple SLA-risk events occur for the same work order within 15 minutes When preparing outbound messages Then only one consolidated alert is sent per recipient across channels within the window And subsequent triggers are suppressed and appended to the portal timeline entry And per-recipient limits are enforced: maximum 4 delay alerts per 24 hours per work order And manual resend by staff bypasses suppression but is logged with actor and reason
Timed Escalation to Alternates
Given no acknowledgment is received from the primary contact within the configured window (default 30 minutes) When the window elapses Then alerts are sent to the first alternate contact via email and SMS with deep links And if still unacknowledged after the second window (default +30 minutes), escalate to the manager And escalation halts immediately once any recipient acknowledges or makes a selection And all escalation intervals and contact order are configurable per account and per work order And quiet hours settings defer non-urgent alerts; urgent after-hours authorization requests bypass deferment
End-to-End Timeline & Audit Log
Given any delay alert is sent or an acknowledgment/selection is received When updating the work order Then a timeline entry is created capturing channel, template ID, recipient, content hash, send/delivery/ack timestamps, and outcome And message bodies are stored with PII redaction for SMS/email previews; full content is accessible only to authorized roles And logs are immutable, tamper-evident, and exportable with unique event IDs And the timeline view shows a threaded history across channels in chronological order
Automated Vendor Reassignment
"As a property manager, I want delayed jobs seamlessly rerouted to an approved backup vendor so that repairs stay on schedule without manual coordination."
Description

When backup vendor is selected, automatically reassigns the job to a preferred, credentialed vendor matching category and geography, transferring photos, diagnostics, notes, and SLAs. Notifies both original and new vendors, cancels the prior dispatch per policy, recalculates ETA/SLA, and updates calendars and purchase orders as needed to avoid double-booking or orphaned tasks.

Acceptance Criteria
Backup Vendor Selection Triggers Reassignment
Given a job with an active dispatch to Vendor A and a configured list of backup vendors And the user selects "Switch to backup vendor" in Delay Transparency When the user confirms the switch Then the system creates a new assignment to a backup vendor And the reassignment completes within 60 seconds And the job shows exactly one active vendor assignment afterward
Vendor Matching by Category and Geography
Given a maintenance job with category C at property location L And a pool of vendors with preference flags, credential status, and service areas When automated reassignment runs Then the selected vendor has Preferred = true And Credential Status = valid and not expired And Service Area includes location L And Capabilities include category C And the vendor has no conflicting bookings in the chosen window And the selection rationale and timestamp are recorded in the audit log
Complete Transfer of Job Artifacts
Given the job contains photos, diagnostics, notes (internal/external), and SLA terms When the job is reassigned automatically Then 100% of photos, diagnostics, external notes, and SLA fields are available to the new vendor And timestamps and authorship metadata are preserved And internal notes remain hidden from vendors according to role permissions And the pre/post artifact counts match and are logged
Vendor Notifications and Audit Trail
Given reassignment from Vendor A to Vendor B is completed When notifications are sent Then Vendor A receives a cancellation notice with job ID, reason, and policy within 60 seconds And Vendor B receives a new dispatch with job details, photos/diagnostics links, and updated ETA within 60 seconds And notification delivery status (sent/succeeded/failed) is captured per channel in the audit log
Cancel Prior Dispatch Per Policy
Given the job had an active dispatch to Vendor A When reassignment is confirmed Then the prior dispatch status updates to "Cancelled - Reassigned" with timestamp and reason And any PO or pre-authorization for Vendor A is voided or adjusted per configured policy And the vendor's calendar hold for the job is removed immediately And any cancellation fees are applied or waived per policy and reflected on the job financials
ETA/SLA Recalculation and Schedule/PO Updates
Given reassignment to Vendor B is successful When logistics are recalculated Then a new ETA is computed from Vendor B availability and travel time and displayed to landlord and tenant And SLA countdown and breach time are recalculated and stored And Vendor A's calendar is freed and Vendor B's calendar is reserved with no double-booking And the original PO is closed/voided and a new PO is issued to Vendor B with the same scope and not-to-exceed amount And no duplicate or orphaned work orders remain linked to the job
Idempotency and Concurrent Trigger Handling
Given two or more reassignment requests for the same job occur within a 10-second window When the system processes the requests Then at most one successful reassignment is performed And the job ends with exactly one active vendor assignment And other requests are rejected gracefully with an explanatory message and no partial state changes And the audit log contains one successful reassignment entry and N-1 rejected attempts
Audit Trail & Compliance Logging
"As an owner, I want a complete audit of delay communications and approvals so that I can resolve disputes and meet compliance requirements."
Description

Maintains an immutable record of SLA risk events, reasons, ETA updates, notifications sent, user acknowledgments, selected recovery options, and vendor handoffs with timestamps, actor IDs, and policy versions. Provides exportable reports for owners and regulators, enforces retention policies, and redacts PII per privacy settings. Enables dispute resolution and continuous improvement analysis.

Acceptance Criteria
Immutable Logging of SLA Risk and ETA Updates
Given a work order transitions to "SLA at risk" and an ETA is updated by the triage service When the event is committed to the audit log Then an audit entry is created with fields: event_id, work_order_id, event_type ("SLA_RISK"|"ETA_UPDATE"), timestamp (UTC ISO-8601), actor_id, actor_role, policy_version, prior_event_id (nullable), reason_code (nullable), prior_eta (nullable), new_eta (nullable) And the entry is write-once (append-only); direct updates/deletes are disallowed within the retention window And any correction creates a new audit entry that references prior_event_id=the superseded entry And attempts to modify or delete the original entry are blocked with 409 and logged as "PROHIBITED_MUTATION"
Notification Delivery and User Acknowledgment Capture
Given notifications for an SLA risk are dispatched to recipients When each notification is processed by the messaging service Then an audit entry records: notification_id, work_order_id, template_id, channel (SMS|Email|Push|In-App), recipient_role, send_timestamp, delivery_status (SENT|DELIVERED|FAILED), delivery_timestamp (nullable), failure_reason (nullable) And user acknowledgment events capture: ack_id, actor_id, actor_role, ack_type (READ|DECISION), ack_timestamp, selected_option (nullable) And message content PII is not stored; only template_id and variable keys are retained per privacy settings And notification and acknowledgment entries are linked via correlation_id
Recovery Choice and Vendor Handoff Audit
Given a user selects a recovery option in response to an SLA risk When the option is applied (reschedule, backup vendor, or after-hours authorization) Then an audit entry records: decision_id, work_order_id, option_type (RESCHEDULE|BACKUP_VENDOR|AFTER_HOURS), prior_eta, new_eta, approver_id, approver_role, approval_timestamp, policy_version And if a vendor change occurs, a vendor handoff entry records: handoff_id, vendor_from_id (nullable), vendor_to_id, handoff_timestamp, handoff_reason_code, linked_decision_id=decision_id And downstream work order updates include correlation_id=decision_id to maintain traceability
Compliance Export with PII Redaction and Authorization
Given an authorized user (Owner Admin or Compliance Auditor) requests an audit export for a date range and property scope When the export is generated in CSV and JSON formats Then only records within the requested scope are included and ordered by timestamp asc And fields designated as PII by current privacy settings are redacted (masked or tokenized) while preserving non-PII metadata And the export includes header metadata: generated_at_utc, requested_by, policy_version, record_count, dataset_hash And the export action is audited with export_id, requester_id, requested_scope, file_checksums, and download_timestamp upon first access And unauthorized users receive 403 and no export is produced
Retention Policy Enforcement and Supervisory Logging
Given retention policies are configured (e.g., audit_log_days and export_log_days) When the scheduled retention job runs Then audit entries older than their threshold are purged or irreversibly anonymized per policy And a supervisory retention log records: retention_job_id, run_timestamp, policy_version, purged_count, anonymized_count, error_count And purged records are not retrievable via APIs or exports; attempts return 404 and are logged as "ACCESS_AFTER_RETENTION" And records within the retention window remain immutable and queryable
Tamper Detection and Integrity Verification
Given the system performs periodic integrity verification of the audit log When a verification run is triggered (daily at 02:00 UTC or on-demand by Admin) Then the system validates the append-only chain using cryptographic checksums or signatures across entries And the run outputs a report: run_id, checked_count, anomalies_count, first_bad_event_id (nullable), completed_at_utc And any anomaly triggers alerts to Security/Admin channels and creates an "AUDIT_ANOMALY" event with details for investigation

Visit Prep

Delivers an issue‑specific, property‑aware prep checklist (clear work area, secure pets, breaker/water access, elevator booking). Boosts first‑pass fixes, shortens visit time, and cuts repeat visits.

Requirements

Dynamic Issue-Specific Checklist Engine
"As a property manager, I want FixFlow to automatically generate the right prep checklist for each ticket so that technicians arrive prepared and first-pass fix rates improve."
Description

Generates visit prep checklists tailored to the reported issue, unit, and building context. Uses rules mapped to maintenance categories (e.g., plumbing leak, HVAC no-cool, appliance failure, electrical) and property attributes (shutoff locations, panel access, elevator requirements, pet presence) to assemble actionable, plain-language steps for residents and pre-visit confirmations (e.g., photos of cleared area, breaker reachability). Supports branching logic, per-trade variants, localization, and accessibility. Includes an admin UI to author templates and rules with versioning and A/B testing. Integrates with FixFlow’s photo-first intake and smart triage to auto-select the checklist, emits structured checklist data to tenant and vendor views, and persists per-step completion state with timestamps and evidence.

Acceptance Criteria
Auto-Assembled Checklist by Issue Type and Property Context
Given a maintenance request categorized as "Plumbing Leak" for a unit whose property profile includes: hallway water shutoff location, elevator booking required, and a pet on file When the checklist is generated Then the checklist includes steps: secure pets, clear area around leak (2m radius), provide access to hallway water shutoff, ensure pathway to leak is unobstructed, and elevator booking instructions with building-specific link And no steps unrelated to plumbing (e.g., reset breaker, test HVAC thermostat) are present And building-specific tokens are resolved (actual shutoff location text, elevator booking URL) And the checklist is returned in ≤ 800 ms at p95 under a load of 50 RPS And step ordering follows defined priority rules (safety > access > preparation)
Branching Logic and Per-Trade Variants
Given the tenant answers "Breaker panel is not accessible" during pre-check for an electrical issue When the flow advances Then a branch appears requesting a photo of the obstruction and a step to contact building management for access, both marked Required And if the tenant later marks the panel accessible with photo evidence, the branch resolves and resumes the main path Given triage assigns trade = HVAC for a "No-Cool" issue When the checklist is generated Then HVAC-specific steps (e.g., clear return vents, confirm thermostat settings) are included and Electrician-only steps (e.g., test specific circuits) are excluded And trade variant selection is recorded in the audit log with rule ID and timestamp
Localization and Accessibility Compliance
Given user locale = es-MX When viewing the checklist Then 100% of visible step titles, instructions, buttons, and error messages render in Spanish with locale-appropriate date/time/number formats And a language toggle persists across sessions and devices Given a screen reader user navigates the checklist When tabbing through all interactive elements Then focus order matches visual order, all controls have accessible names/roles/states, no keyboard traps exist, and color contrast meets WCAG 2.1 AA (≥ 4.5:1) And all images/evidence previews expose alt text or aria-labels And dynamic step insertions announce via ARIA live region
Admin Rule Authoring, Versioning, and A/B Testing
Given an admin with Checklist Author role When creating a new "Plumbing Leak" template Then they can add steps with conditions on maintenance category and property attributes, validate the rule graph, save as Draft, preview with a sample property, and Publish as v1.0.0 Given a published template v1.0.0 When creating v1.1.0 Then the system stores full version history, shows a step/rule diff, allows rollback to v1.0.0, and prevents publish if validations fail (e.g., dangling attribute references) Given an A/B test is configured 50/50 between v1.0.0 and v1.1.0 for category = Plumbing Leak When 1,000+ eligible checklists are generated Then observed assignment split is within ±5% of target and version is recorded per checklist for analytics And per-version outcome metrics (first-pass fix rate, repeat visit rate, average visit duration) are captured and queryable
Auto-Selection via Intake and Triage
Given intake photos and answers classify the request as "HVAC No-Cool" with confidence ≥ 0.80 When the ticket is created Then the HVAC No-Cool checklist is auto-selected and attached to the ticket Given classification confidence < 0.60 When creating the ticket Then the tenant is presented the top 3 checklist options ranked by confidence for one-tap selection, with the chosen option attached Given triage reclassifies the ticket from HVAC to Electrical later When the category changes Then the attached checklist updates to the new category, preserving completion for steps with matching stable IDs and discarding non-matching steps with an audit log entry of the change
Structured Data Emission to Tenant and Vendor Views
Given a generated checklist When emitting to the API Then the payload conforms to schema v1 with fields: checklistId, templateVersion, category, trade, locale, propertyContext, steps[id, title, required, evidenceType, status, timestamps], and signatures And schema validation rejects invalid payloads with HTTP 400 and error details When the tenant app renders the payload Then the tenant can mark steps complete, upload required evidence per step, and see real-time status updates When the vendor app renders the payload Then tenant steps are read-only, vendor pre-visit confirmations are visible, and all evidence thumbnails are retrievable within 2 seconds p95 And webhooks fire for checklist.created, step.completed, checklist.completed within 2 seconds of the event
Per-Step Completion, Evidence, and Scheduling Gate
Given a required step with evidenceType = photo When the tenant marks it complete Then at least one photo is required (jpg/png, ≤ 10 MB each), client- and server-side validated, and completion records captured with timestamp, actor, and file checksums Given all required pre-visit steps are complete When the tenant attempts to schedule a visit Then scheduling is enabled and the vendor receives the compiled evidence bundle Given any required step is incomplete When scheduling is attempted Then scheduling is blocked for tenants with a clear error and an override path is available only to staff with reason capture And the audit log records all step status changes and overrides immutably with user ID and timestamp
Property-Aware Context Profile
"As a landlord, I want property-specific access and rules stored and reused so that prep steps and visit instructions are always accurate."
Description

Creates a structured property and unit profile that captures access constraints and logistics (gate and door codes, parking instructions, concierge details), utility information (main water shutoff, breaker panel location), building rules (quiet hours, elevator booking requirements, insurance/COI needs), and resident-specific context (pets, preferred contact channels, language). Allows managers to seed data, residents to contribute missing details, and vendors to suggest updates with admin moderation. Exposes this context to the checklist engine and scheduling, ensuring prep tasks and visit instructions are accurate. Enforces role-based access controls, audit logs, and data encryption for sensitive fields.

Acceptance Criteria
Manager Seeds Property Context Data
Given an authenticated Manager with write access to a property and unit, when they open the Context Profile, then they can create or edit fields for access constraints (gate code, door code, parking instructions, concierge details), utility info (main water shutoff location, breaker panel location), building rules (quiet hours, elevator booking requirements, insurance/COI needs), and resident context (pets, preferred contact channel, language). Given the Manager enters invalid values (e.g., non-numeric gate code, code length outside 4–10), when they attempt to save, then the form shows inline validation messages and blocks save until corrected. Given the Manager saves valid changes, when the request completes, then the profile version increments, a last-updated timestamp and actor are recorded, and the save completes within 2 seconds at p95. Given sensitive fields (gate code, door code, concierge passcode), when displayed in the UI, then they are masked by default and stored encrypted at rest; reveal action is not available to non-privileged roles.
Resident Contributes Missing Details from Mobile Portal
Given a Resident is authenticated for a unit with an incomplete Context Profile, when they open the Visit Prep prompt, then they see only permitted fields (pets, parking description, preferred contact channel, language, photos of utility locations) and do not see sensitive fields (door/gate codes, concierge passcodes). Given the Resident submits additions or corrections to permitted fields, when they tap Submit, then a change request is created in Pending status for moderation, the Resident receives an in-app confirmation, and no immediate changes are applied to the live profile. Given the Resident uploads photos, when the upload completes, then the system accepts JPEG/PNG up to 10 MB per image, stores them, and links them to the pending change request. Given network loss during submission, when connectivity resumes within 60 seconds, then the app retries once and surfaces a clear error if it cannot submit.
Vendor Suggests Context Update with Admin Moderation
Given a Vendor is authenticated and assigned to a completed visit for a unit, when they suggest an update (e.g., breaker panel relocated), then they must provide a note and may attach up to 3 photos; a moderation ticket is created and routed to Property Admins. Given a Property Admin reviews a pending vendor suggestion, when they approve, then the Context Profile is updated, the suggestion is marked Approved, the vendor and manager receive notifications, and the audit log records the change with before/after values; when they reject, the profile remains unchanged and the suggestion is marked Rejected with reason. Given RBAC restrictions, when a Vendor views the Context Profile, then sensitive fields are masked and cannot be edited or revealed by the Vendor. Given conflicting pending suggestions for the same field exist, when an Admin approves one, then remaining conflicting suggestions are auto-flagged as Superseded.
Checklist Engine Uses Context to Generate Visit Prep Tasks
Given a work order is created for a unit with Context Profile data, when the Visit Prep checklist is generated, then tasks are included per mapping rules: pets=true adds Secure pets task; elevator booking required=true adds Book elevator task; breaker panel location set adds Ensure access to breaker panel at <location>; water shutoff known adds Confirm access to main water shutoff at <location>. Given a required context field for the work order type is missing (e.g., elevator booking requirement unknown for high-rise), when generating the checklist, then the engine adds a Collect missing context task targeting the appropriate role (Resident or Manager) before dispatch. Given building quiet hours are defined, when a noisy job type is selected, then the checklist includes Observe quiet hours and schedule within allowed windows. Given the checklist is generated, when viewed by the Vendor, then it includes necessary instructions but does not expose raw sensitive codes; access steps reference masked codes or temporary access procedures.
Scheduling Observes Access Constraints and Building Rules
Given a scheduler selects a visit time for a unit with quiet hours and elevator booking requirements, when they attempt to schedule outside allowed windows or without a booking, then the system blocks confirmation and displays specific reasons and next steps (e.g., Book freight elevator for 10–12 window). Given a property requires a COI, when the assigned vendor lacks a valid COI on file for the property dates, then the system prevents scheduling and prompts for COI upload or vendor reassignment. Given concierge hours and parking instructions exist, when the visit is confirmed, then vendor and resident notifications include concise access instructions and concierge hours while masking any sensitive codes. Given a time slot is selected within allowed windows, when saving the appointment, then the system confirms in under 2 seconds p95 and writes the constraints snapshot to the appointment record for later reference.
Role-Based Access Control and Sensitive Data Protection
Given RBAC is enforced, when a Manager with proper permissions accesses the Context Profile, then they can view and reveal sensitive fields after MFA and entering a justification; Residents and Vendors cannot reveal sensitive fields. Given an API request is made to fetch a Context Profile, when the caller lacks required scope or role, then the API returns 403 and no sensitive fields are included in any response. Given sensitive fields are stored, when inspecting the database at rest in a non-production test environment, then values for door codes, gate codes, concierge passcodes are encrypted (non-plaintext) using approved encryption and are transmitted only over TLS 1.2+. Given a reveal action occurs, when the code is displayed, then it auto-redacts after 60 seconds or on navigation and cannot be copied to clipboard by non-manager roles.
Audit Logging of Context Changes and Access
Given any create, update, approve, reject, or reveal action on Context Profile fields occurs, when the action completes, then an audit log entry is recorded with actor ID, role, timestamp (UTC), IP/user agent, field names, before/after values (sensitive values hashed or partially masked where applicable), and action outcome. Given an authorized Auditor views logs, when they filter by property, unit, actor, and date range, then results return within 2 seconds for the last 90 days and can be exported to CSV. Given an attempt is made to modify or delete an audit log entry, when executed by any role, then the system denies the operation with 403 and records the attempt as a security event. Given clock skew across services, when logs are written from multiple services, then entries include service ID and are ordered by timestamp with tolerance of ±2 seconds and maintain causal linking via correlation IDs.
Tenant Prep Confirmation & Smart Reminders
"As a tenant, I want clear, timely prep prompts I can confirm with one tap and photos so that the visit goes quickly and avoids a second appointment."
Description

Delivers a mobile-first prep flow where residents can review steps, one-tap confirm completion, and attach photo/video proof. Ties deadlines to appointment start times and dynamically highlights critical blockers. Sends timely, multilingual reminders via SMS, email, and in-app notifications with rate limiting and quiet hours, including deep links to specific steps. Captures explicit acknowledgments, supports accessibility, and provides a printable/PDF fallback. Logs confirmations and evidence for vendor and manager visibility, and updates readiness status in real time.

Acceptance Criteria
Mobile Prep Review & One-Tap Confirmation
Given a tenant with a scheduled maintenance appointment that has associated prep steps When the tenant opens the mobile prep flow Then the checklist displays all steps with titles, descriptions, and a visible “Critical” badge for critical steps And each step has a single-tap Mark Complete control And tapping Mark Complete updates the step to Complete within 1 second and shows a checked state And the overall progress indicator updates immediately (e.g., “3 of 7 completed”) And per-step completion persists across refresh and across devices for the same tenant account
Photo/Video Evidence Attachment & Verification
Given a step marked Evidence Required When the tenant taps Add photo/video Then the system accepts JPG/JPEG/PNG/HEIC up to 10 MB each and MP4 up to 60 seconds and 50 MB And allows up to 5 media items per step with preview thumbnails And shows upload progress and retries failed uploads up to 3 times with exponential backoff And on success, media are linked to the step, time-stamped, and associated to the tenant account And the tenant can remove attached media until the appointment start time And the vendor and manager can view the attached media from the work order
Appointment-Tied Deadlines & Critical Blocker Highlighting
Given an appointment start time T and configured per-step lead times When the tenant views the checklist Then each step shows a due time computed as T minus its lead time And steps due within 24 hours display a Due soon label and within 2 hours display an Urgent label And any incomplete critical step within 2 hours of T is surfaced at the top as a Blocker And the overall readiness cannot be Ready while any critical blocker remains incomplete And countdown timers update at least every 60 seconds without page reload
Multilingual, Rate-Limited Reminders with Quiet Hours
Given tenant language preference and enabled channels (SMS, email, in-app) And configured reminder schedule relative to T (e.g., T-48h, T-24h, T-2h) When one or more prep steps remain incomplete at a scheduled reminder time Then a reminder is sent in the tenant’s preferred language with a deep link to the specific outstanding step And no reminders are sent during the configured quiet hours window in the tenant’s timezone And reminders are rate-limited to a maximum of 3 per 24 hours per channel per work order with a minimum 2-hour spacing And delivery status is recorded and failed sends are retried outside quiet hours And STOP/UNSUBSCRIBE requests for SMS/email are honored within 1 minute and logged
Deep Links to Specific Prep Steps
Given a valid deep link containing a signed token for a specific prep step When the tenant opens the link on mobile Then the app navigates directly to the target step within 2 seconds And if the tenant is not authenticated, they are prompted to sign in and then redirected to the same step post-login And tokens expire at appointment start time plus 2 hours and are single-tenant scoped And expired or invalid tokens redirect to the checklist overview with a non-breaking message And deep links are unique per step and cannot expose another tenant’s data
Accessibility & Explicit Acknowledgments
When navigating the prep flow with a screen reader or keyboard-only input Then all actionable elements are labeled and reachable, and color contrast and focus visibility meet WCAG 2.1 AA And tap targets are at least 44x44 px and text can be scaled to 200% without loss of functionality And instructional videos include captions Given a step requiring acknowledgment When the tenant selects I acknowledge Then the system records a timestamp, tenant identifier, and a hash of the acknowledgment text version And the acknowledgment is visible to vendor and manager along with the audit trail
Real-Time Readiness Status & PDF/Printable Fallback
Given a tenant changes a step’s completion or adds/removes evidence When the change is saved Then the readiness percentage and Ready/Not Ready indicator update for tenant, vendor, and manager views within 5 seconds And an audit log records the actor, action, and timestamp And vendor/manager lists support filtering by Ready status Given the tenant selects Print/PDF When the request is made Then a localized PDF of the checklist with steps, due times, critical flags, completion states, acknowledgments, and media thumbnails is generated within 10 seconds and is downloadable and emailable
Vendor Readiness Snapshot
"As a technician, I want a concise readiness summary before I depart so that I bring the right tools and avoid wasted trips."
Description

Provides technicians with a concise pre-arrival screen summarizing appointment details, tenant prep completion status by step, latest photos, access instructions, building rules, risk flags (pets present, fragile items nearby), and tool/material suggestions derived from the issue and prep data. Enables quick actions to request missing prep, propose alternate times, or message the tenant/manager. Syncs status changes back to the work order, supports offline access to critical details, and logs technician acknowledgments.

Acceptance Criteria
Pre-Arrival Snapshot Completeness
Given a scheduled work order with assigned technician, appointment time, tenant, property address/unit, and building rules When the technician opens the Vendor Readiness Snapshot online Then the snapshot loads within 2 seconds on a 4G connection and displays: appointment date/time in the technician’s local timezone, tenant name and tap-to-call/tap-to-message contact, property address/unit with map deep link, access instructions, building rules, the latest up to 5 photos with capture timestamps, the prep checklist with each step labeled Complete/Pending/Not Applicable and last-updated timestamp, risk flags (e.g., pets present, fragile items nearby), and tool/material suggestions derived from the issue type and prep data And the snapshot shows a Last Synced timestamp that is no older than 5 minutes at time of open
Quick Actions from Snapshot
Given the snapshot is displayed and at least one prep step is Pending When the technician taps Request Missing Prep and confirms send Then a templated message including the list of Pending steps and earliest available windows is sent to the tenant via in-app messaging within 5 seconds and a Work Order Event is logged within 10 seconds Given the snapshot is displayed When the technician taps Propose Alternate Time and selects a new timeslot Then the tenant and property manager receive a proposal with at least two options, the work order status updates to Reschedule Proposed, and the action is logged with technician ID and timestamp within 10 seconds Given the snapshot is displayed When the technician sends a message to the tenant and/or manager from the snapshot Then the message is delivered to the selected recipients, read receipts are captured when opened, and the thread is linked back to the work order Given any quick action is initiated from the snapshot When the technician completes the action Then the total number of taps from the snapshot home to send is ≤ 2 and any failure presents an error with a retry option within 5 seconds
Work Order Sync from Snapshot
Given the technician performs an action from the snapshot (change prep status, request prep, propose time, send message, acknowledge snapshot) When the action completes Then the corresponding work order reflects the change in both manager and vendor portals within 10 seconds, including technician identity, timestamp, and action details Given a conflicting update to the same work order occurs from another user while the snapshot is open When the technician attempts to save changes Then the app displays a Conflict Detected notice, refreshes to the latest state within 5 seconds, and offers the technician the option to retry their change Given the snapshot has been open for more than 5 minutes without interaction When the technician returns focus to the snapshot Then the app auto-refreshes and updates the Last Synced timestamp
Offline Access and Deferred Sync
Given the device is offline and the snapshot for the appointment was fetched within the past 24 hours When the technician opens the snapshot Then critical details (appointment details, access instructions, building rules, prep checklist statuses, risk flags, last 3 photos, and tool/material suggestions) are available and labeled Offline with the last-synced time shown Given the device is offline When the technician initiates a quick action (request prep, propose time, message, acknowledgment) Then the action is queued locally with status Pending Sync and is not sent multiple times upon reconnection Given connectivity is restored When the device regains a stable connection Then all queued actions sync within 15 seconds, preserve original action timestamps, and any failed item surfaces an actionable error with retry
Technician Acknowledgment Logging
Given the snapshot is displayed When the technician checks I have reviewed the readiness snapshot and confirms Then an acknowledgment record is added to the work order history with fields: technician ID, device ID, app version, UTC and local timestamps, snapshot version hash, and geolocation when permission is granted Given an acknowledgment exists and snapshot content changes (e.g., new photos or prep status updates) When the technician reopens the snapshot Then the app prompts for re-acknowledgment and, upon confirmation, logs a new acknowledgment linked to the prior one, ensuring only the latest is marked Active Given a manager views the work order history When filtering by event type Then acknowledgment entries can be filtered and exported with all recorded fields
Risk Flags and Tool Suggestions Visibility
Given the issue type and prep data have associated risk flags and tool/material suggestion rules When the snapshot opens Then risk flags are displayed above the fold with icon and color coding, each with a concise description, and tool/material suggestions list at least 3 relevant items with quantities; both sections include accessible labels meeting WCAG AA contrast and are readable by screen readers Given the technician marks a suggested item as Packed or Not Available When the selection is saved Then the choice persists locally and syncs to the work order within 10 seconds without notifying the tenant Given no risk flags apply to the work order When the snapshot opens Then the risk section is hidden and an audit entry notes that no applicable risk flags were present
Elevator and Access Booking Assistance
"As a property manager, I want FixFlow to help secure elevator and building access when required so that appointments aren’t delayed at the door."
Description

Guides managers and residents through buildings that require elevator reservations or concierge notifications. Stores contact methods and allowed booking windows per building, generates templated requests with visit details, and optionally sends calendar holds (ICS) to the tenant, vendor, and building contact. Tracks confirmation receipts and surfaces blockers in scheduling if bookings are missing. Supports attachments such as COI documents, uses email/SMS/webhook channels without requiring third-party building API integrations, and records all communications to the work order timeline.

Acceptance Criteria
Trigger Guided Booking Flow for Buildings Requiring Elevator Reservations
Given a work order at a property marked "Booking Required" with stored building rules When the scheduler selects a visit date/time and opens Visit Prep Then the system displays the Elevator and Access Booking step as required And pre-fills building contact(s) and allowed booking windows from the property profile And prevents visit confirmation until a booking request is sent or the requirement is overridden with a reason and approver
Store and Maintain Building Booking Rules and Contacts
Given a property profile editing session When a manager adds or updates concierge contact methods (email, SMS number), optional webhook URL, booking window rules (days/hours, lead time), and notes Then the system validates formats and saves them to the property And the saved rules are retrievable and applied to new and existing open work orders And an audit history records who changed which fields and when
Generate and Send Templated Booking Requests with Visit Details
Given a scheduled visit with date/time window, vendor, unit, and work description When the user selects channels (email/SMS/webhook) and clicks Send Booking Request Then the system generates a message template including property address, unit, date/time window (with timezone), vendor name, scope summary, required elevator time, callback info, and tracked reply-to And sends via the selected channels without calling any third-party building APIs And shows per-recipient delivery status (queued, sent, delivered, failed)
Attach and Include COI and Supporting Documents
Given COI and other files are uploaded to the work order or vendor profile When a booking request is prepared Then the user can select attachments to include And the outbound messages include the selected attachments or secure download links, as channel-appropriate And attachments are recorded in the corresponding work order timeline entries
Calendar Hold (ICS) Distribution to Tenant, Vendor, and Building Contacts
Given the user enables calendar holds for the booking When the booking request is sent Then ICS files are generated with correct timezone, event title, address, unit, tentative time window, and instructions And ICS files are attached to emails or linked in SMS/webhook for tenant, vendor, and building contact as configured And recipients appear in the ICS attendee list when delivered via email
Confirmation Tracking and Scheduling Blockers
Given a booking request has been sent When a recipient replies "confirmed", clicks a confirmation link, or a webhook delivers a confirmation payload Then the system marks the booking as Confirmed with timestamp and source And if not confirmed by the rule-defined lead time, the job shows a "Booking Missing" blocker in scheduling with an action to resend or override with reason And if confirmed for a time outside allowed windows, the system flags a conflict and prompts for rescheduling or re-request
Work Order Timeline Logging of All Communications
Given any booking-related outbound or inbound communication occurs When the event is processed Then a timeline entry is created capturing timestamp, channel, recipients, subject/summary, delivery status, attachments/ICS included, and any confirmations And entries are immutable and filterable by "Access Booking" And user permissions restrict viewing of sensitive contact information according to role
Missed-Prep Escalation & Auto-Reschedule
"As a property manager, I want overdue prep to be escalated and automatically rescheduled when needed so that technicians aren’t dispatched to unready jobs."
Description

Monitors critical prep items and triggers time-based escalations when steps are incomplete by set thresholds (e.g., T-24h, T-2h). Sends final nudges to residents, notifies managers, and allows technicians to block dispatch for unready visits. Offers one-tap reschedule options aligned with vendor availability and building constraints, communicates policy-driven fees or consequences, and updates SLAs accordingly. Maintains a full audit trail of notifications, decisions, and schedule changes.

Acceptance Criteria
T-24h Auto-Escalation for Incomplete Critical Prep
Given a scheduled maintenance visit in the resident’s local timezone And one or more critical prep items remain incomplete at T-24h When the system clock reaches T-24h Then the resident receives an escalation reminder via configured channels And the work order is flagged "At Risk" for the manager And an escalation task with due date T-12h is created for the manager queue And no additional T-24h reminder is sent within the next 4 hours And if all critical prep items are complete before T-24h, no T-24h escalation is sent
T-2h Final Nudge and Manager Alert
Given a scheduled maintenance visit occurring within 2 hours And one or more critical prep items remain incomplete at T-2h When the system reaches T-2h Then the resident receives a final nudge with a one-tap CTA to mark ready or reschedule And the property manager is notified via in-app alert and email And the assigned technician/dispatcher receives a queue update indicating "Prep Unready—Action Needed" And if all critical prep items are completed before T-2h, the final nudge and alerts are not sent
Technician Blocks Dispatch for Unready Visit
Given a work order with critical prep items incomplete at dispatch time And the user has technician or dispatcher permissions for the job When the user selects "Block Dispatch—Unready Prep" and provides a reason code Then the job status updates to "Blocked—Unready Prep" And the visit is removed from the active route and capacity is freed And the resident is notified with the block reason and reschedule options And the manager receives an alert with the block reason and required next steps And if prep was marked complete in the last 2 minutes, the system prompts to refresh and prevents blocking without recheck
One-Tap Reschedule with Vendor Availability and Building Constraints
Given a resident initiates rescheduling from a nudge or block notification When the resident taps a suggested slot Then only time slots that satisfy vendor availability, travel buffers, and building constraints (elevator windows, access hours) are presented And selecting a slot creates a new appointment and cancels the prior one atomically And all parties (resident, vendor/tech, manager) receive updated confirmations And if no compliant slots exist in the next 7 days, a waitlist option is offered And reschedule actions are idempotent—repeated taps within 60 seconds do not create duplicates
Policy Fee Disclosure and Acknowledgment on Reschedule
Given organization policies define fees for missed prep or late changes And the reschedule is initiated within the policy penalty window due to missed prep When the resident proceeds to confirm reschedule Then the fee amount, rationale, and policy reference are displayed prior to confirmation And the resident must explicitly acknowledge the fee to proceed And the confirmation and notifications include the fee line item And if a configured exemption applies (e.g., first offense or grace period), no fee is shown or charged And manager override of fees updates the confirmation and notifications accordingly
SLA Recalculation After Block or Reschedule
Given SLA targets exist for response and resolution on the work order When a visit is blocked for unready prep or rescheduled Then SLA clocks are adjusted per policy (pause, extend, or breach) and new target timestamps are computed And the work order displays before/after SLA targets and the adjustment reason And SLA analytics reflect the adjustment without double-counting events And if policy dictates breach on block, the breach is recorded immediately with reason "Unready Prep"
Audit Trail for Notifications and Schedule Changes
Given any notification, decision, or schedule change related to missed-prep flow occurs When the event is processed Then an immutable audit entry is created with UTC timestamp, actor (system/user + role), event type, related entity IDs, old/new values, channel, template ID, delivery status, and reason code And audit entries are filterable by work order, resident, time range, and event type in the admin view And audit logs are exportable in CSV and JSON formats And audit entries are created even if external notification delivery fails, with failure details captured

Update Control

Lets tenants choose update channels and frequency—milestones only or play‑by‑play—and set quiet hours. Keeps them informed on their terms, reducing notification fatigue without losing visibility.

Requirements

Multi-Channel Notification Preferences
"As a tenant, I want to choose my notification channels for different update types so that I get updates where they best fit my routine."
Description

Enables tenants to select preferred notification channels per update type and per work order, including SMS, email, push, and in-app. Provides a unified preferences UI in account settings and within each maintenance request, with sensible defaults and clear labeling. Stores preferences at tenant, property, and ticket scopes and applies them consistently across the notification pipeline. Integrates with existing messaging providers, respects suppression lists and unsubscribes, and supports per-channel rate limits. Ensures preferences are inherited and overridable, and exposes preferences to the event dispatcher so only allowed channels are used for each update.

Acceptance Criteria
Account Settings: Per-Update-Type Channel Preferences with Defaults
Given a tenant with verified contact methods, When they open Account Settings > Notifications, Then toggles for SMS, Email, Push, and In-App are displayed per update type defined by the system. Given a new tenant with no prior preferences, When the page loads, Then defaults are applied as: Milestone updates = Email + In‑App enabled; Play-by-play updates = In‑App enabled only. Given a channel lacks required contact information, When the page loads, Then that channel’s toggle is disabled with a prompt to add the missing info. When the tenant changes any toggle and clicks Save, Then preferences persist and reload with the updated states and an audit record is created including actor, timestamp, scope, and diff. When the tenant clicks Restore Defaults, Then default states are applied and saved.
Work Order-Level Overrides
Given an active work order, When the tenant opens its Notifications panel, Then the panel shows the currently effective channels and allows per-channel overrides for that work order. When the tenant enables/disables a channel and saves, Then the override applies only to that work order and takes precedence over account/property settings. Given an override is present, When a subsequent event for that work order occurs, Then disallowed channels are not sent. When the tenant selects Reset to Inherited and saves, Then all overrides for that work order are removed and inherited preferences apply.
Scope Inheritance and Precedence Across Tenant, Property, and Ticket
Given tenant-level (T), property-level (P), and ticket-level (K) preferences exist, When computing effective preferences, Then precedence is K over P over T. Given a scope has no explicit value for a channel, When computing effective preferences, Then the next broader scope’s value is used. Given a property-level preference is set for Property A, When computing preferences for a work order at Property B, Then Property A’s settings are not considered. When a higher-scope preference is updated, Then effective preferences for tickets without lower-scope overrides reflect the change within 5 seconds.
Dispatch Enforcement and Compliance with Suppression/Unsubscribe
Given a notification event, When the dispatcher evaluates preferences, Then it enqueues deliveries only for channels allowed by the effective preferences. Given the messaging provider indicates the recipient is suppressed/unsubscribed on a channel, When evaluating, Then that channel is excluded regardless of preferences and the suppression reason is recorded. Given all channels are excluded after applying preferences and suppressions, When dispatching, Then no messages are sent and a no_eligible_channels log entry is written with event_id and ticket_id. Given a channel is excluded by suppression, When dispatching, Then no outbound API call is made to that provider for that event.
Per-Channel Rate Limits
Given documented rate limits per channel (e.g., SMS ≤ 3/hour per work order; Email ≤ 10/hour per work order), When events exceed the limit, Then additional messages on that channel for that work order within the window are not sent and each skipped attempt is recorded with rate_limited=true and next_allowed_at. Given rate limits are per channel and scoped per tenant and per work order, When many events occur across different work orders or tenants, Then they do not affect one another’s quotas. When a rate-limited window elapses, Then messages become eligible again and are dispatched if preferences still allow.
Event Dispatcher Applies Preferences at Send Time
Given a queued notification event, When the dispatcher prepares to send, Then it retrieves the latest effective preferences and computes allowed channels at send time. Given preferences changed after the event was queued, When the dispatcher sends, Then it honors the updated preferences and suppresses any newly disallowed channels. Given a transient error occurs retrieving preferences, When dispatching, Then the dispatcher retries up to 3 times with exponential backoff and fails the job without sending if retrieval ultimately fails.
Update Granularity Controls
"As a tenant, I want to switch between milestone-only and step-by-step updates so that I control how much detail I receive."
Description

Allows tenants to choose the level of detail for maintenance updates, offering Milestones Only (key events like scheduled, en route, started, completed) and Play-by-Play (all notable state changes). Supports global defaults and per-ticket overrides, with immediate effect on subsequent notifications. Defines and documents a milestone taxonomy and maps vendor and internal events into those milestones. Filters events at dispatch time based on the selected granularity without changing underlying workflow processing. Captures analytics on selection rates and impact on engagement to inform future defaults.

Acceptance Criteria
Global Default Granularity Selection
Given a tenant accesses Notification Preferences in FixFlow When the tenant selects "Milestones Only" or "Play-by-Play" and saves Then the selection persists to the tenant account and is retrievable via UI and API And new tickets created after save inherit the selected default And existing tickets without a per-ticket override use the new default for notifications generated within 30 seconds after save And no previously sent notifications are resent or retracted And the preference persists across logout/login and device/browser changes
Per-Ticket Granularity Override
Given a tenant views an existing maintenance ticket When the tenant sets the ticket’s granularity to a mode different from the global default and saves Then only that ticket uses the override for subsequent notifications within 30 seconds And clearing the override reverts that ticket to the global default for subsequent notifications And the override state is visible in the ticket UI and retrievable via API And the override does not modify the tenant’s global default And notifications already sent prior to the change remain unchanged
Milestones Only Dispatch Filtering
Given the tenant’s effective granularity is Milestones Only And the milestone taxonomy includes at least: Appointment Scheduled, Technician En Route, Work Started, Work Completed, Visit Rescheduled, Visit Canceled When the system receives a mix of milestone-mapped and non-milestone events for a ticket Then notifications are dispatched only for the milestone-mapped events And exactly one notification is emitted per milestone occurrence And notifications are delivered in the chronological order of the underlying events And non-milestone events do not produce tenant notifications And the underlying workflow event log remains unchanged
Play-by-Play Dispatch Completeness and Order
Given the tenant’s effective granularity is Play-by-Play And notable tenant-visible event types are defined in the event catalog When any notable event occurs for the ticket Then a notification is dispatched for each event within 60 seconds of event receipt And notifications are delivered in chronological order And duplicate notifications are prevented via idempotency keys And only tenant-visible events are sent
Milestone Taxonomy and Event Mapping Coverage
Given a versioned milestone taxonomy is documented When all vendor and internal event types in the environment are enumerated Then 100% of event types are mapped to a specific milestone or explicitly marked Non-milestone And unknown event types are routed to Unknown and do not trigger notifications in Milestones Only And unknown but tenant-visible events trigger notifications in Play-by-Play And the mapping configuration is stored under version control with change history And a machine-readable export of the mapping is available for QA And CI fails if any unmapped event types are introduced
Analytics Capture and Reporting by Granularity
Given notifications are being generated for tickets When dispatch, open, and interaction events occur Then analytics record: tenant ID, ticket ID, effective granularity, event type, send timestamp, and engagement timestamps And daily aggregates provide: selection rate by granularity, notification open rate, and average time-to-open segmented by granularity And data freshness for aggregates is under 24 hours And at least 99% of notification dispatches have corresponding analytics records And metrics are accessible in the admin dashboard or BI dataset
Dispatch Filtering Does Not Mutate Workflow
Given the event pipeline processes workflow state changes and vendor callbacks When notification filtering is applied based on granularity at dispatch time Then workflow state transitions and vendor callbacks are unaffected And a replay test shows identical workflow event counts and state transitions with filtering enabled vs disabled And filtering introduces no more than 50 ms p95 additional latency to notification dispatch And zero events are dropped from the underlying workflow store
Quiet Hours with Emergency Override
"As a tenant, I want to set quiet hours so that non-urgent updates don’t disturb me while still receiving urgent alerts when necessary."
Description

Lets tenants set daily quiet hours windows during which non-urgent notifications are held. Honors tenant time zones and supports multiple windows (e.g., nights and meetings) with a simple snooze option. Provides an emergency override that bypasses quiet hours for critical events such as safety issues, access conflicts, water leaks, or vendor arrival within a short ETA. Includes a configurable severity policy defined by property managers that classifies events as urgent or non-urgent. Records suppressed notifications and releases them at the next allowed time or into a digest if enabled.

Acceptance Criteria
Quiet Hours Respect Tenant Time Zone
Given a tenant with time zone America/Chicago and quiet hours 22:00–07:00 local When a non-urgent notification is generated at 23:30 local Then it is suppressed and not delivered immediately and a suppression record is created with reason "quiet_hours" Given quiet hours end at 07:00 local and digest is disabled When the time reaches 07:00 local Then all suppressed non-urgent notifications are released within 2 minutes in chronological order Given a daylight saving time change occurs during quiet hours When local time shifts forward or backward Then suppression honors the configured local start and end times without sending early or late Given the tenant changes their time zone When the update is saved Then subsequent suppression and releases use the new time zone from the next minute onward
Multiple Quiet Hour Windows and Overlap Handling
Given a tenant defines multiple quiet windows (e.g., 12:00–13:00 and 22:00–07:00) When a non-urgent notification occurs within any window Then it is suppressed Given two quiet windows overlap or abut (e.g., 21:00–23:00 and 22:30–07:00) When the schedule is saved Then the system treats them as a single continuous window and suppression covers the combined span Given a quiet window end time is not after its start time for the same day When the tenant attempts to save Then validation prevents saving and returns a clear error message Given windows are edited When changes are saved Then the new schedule applies to future events and does not retroactively release previously suppressed notifications before the next allowed time
Emergency Override Bypasses Quiet Hours
Given an event is classified as urgent by the severity policy (e.g., safety issue, water leak, access conflict, vendor arrival within the policy ETA threshold) When it occurs during quiet hours Then its notification is delivered immediately (≤ 1 minute) despite quiet hours and is not added to suppression or digest Given an event is non-urgent When it occurs during quiet hours Then its notification is suppressed and not delivered until the next allowed time or digest Given an urgent event is delivered during quiet hours When reviewing the event log Then an override entry exists indicating the reason "emergency_override" and the delivery timestamp
Property Manager Severity Policy Configuration
Given a property manager edits the severity policy to define which event types and conditions are urgent vs non-urgent (including ETA threshold for vendor arrival) When the policy is saved Then subsequent events are classified according to the new policy Given an event type is not explicitly defined in the custom policy When it occurs Then the system applies the default classification and records which rule was applied Given the policy is updated When a notification is already suppressed under a previous classification Then its release behavior follows the original classification at time of suppression
Tenant Snooze Temporarily Holds Notifications
Given a tenant activates Snooze for 60 minutes during an allowed notification period When non-urgent notifications are generated within the snooze window Then they are suppressed with reason "snooze" and not delivered immediately Given snooze expires and digest is disabled When the next allowed time occurs (immediately if outside quiet hours) Then suppressed snooze notifications are released within 2 minutes in chronological order Given an urgent event occurs during snooze When it is generated Then the notification bypasses snooze and is delivered immediately and is not included in suppression or digest Given a tenant cancels an active snooze When cancellation is confirmed Then new notifications follow the current quiet hours policy and previously suppressed snooze notifications are released at the next allowed time
Suppression Recording and Release Tracking
Given a notification is suppressed (quiet hours or snooze) When the suppression record is created Then it stores notification_id, tenant_id, reason (quiet_hours|snooze), created_at, scheduled_release_at, and classification (urgent|non-urgent) Given suppressed notifications exist When release occurs Then the suppression records are updated with release timestamp and delivery outcome (delivered|digested|failed) Given suppressed notifications exist When queried via the admin or audit log Then they are visible with current state and timestamps
Digest Delivery of Held Notifications
Given digest delivery is enabled for a tenant and scheduled daily at 07:05 local When non-urgent notifications are suppressed during quiet hours Then they are batched and delivered as a single digest at 07:05 local and are not released individually at quiet-hours end Given an urgent event occurs When digest is enabled Then the urgent notification is delivered immediately and is excluded from the digest Given the tenant disables digest When the next quiet hours end occurs Then suppressed notifications are released individually at the allowed time according to release rules
Digest Scheduling and Bundling
"As a tenant, I want to receive batched updates at set times so that I stay informed without constant interruptions."
Description

Adds digest delivery options that bundle multiple updates into a single message at chosen times (e.g., hourly, daily, or specific windows). Consolidates updates by work order and event type with concise summaries and links to full timelines. Ensures digests respect channel preferences, quiet hours, and urgency classifications, releasing urgent items immediately and deferring others to the next digest window. Implements deduplication and rule-based bundling to prevent redundant notifications. Provides tenant-facing previews of the next scheduled digest and a one-click switch back to real-time updates.

Acceptance Criteria
Hourly Digest Scheduling with Quiet Hours
Given a tenant has selected an Hourly digest and configured quiet hours from 21:00 to 07:00 local time When non-urgent updates are generated during quiet hours Then those updates are deferred and included in the first digest at 07:00 local time Given a tenant has selected an Hourly digest and active hours are in effect When the hour window elapses Then exactly one digest is sent summarizing all non-urgent updates created in the last hour Given no non-urgent updates occurred during the last hour When the hour window elapses Then no digest is sent
Urgent Updates Bypass Digest
Given an update is classified as Urgent When it is generated at any time Then it is delivered immediately via all enabled channels, ignoring digest schedules and quiet hours Given an urgent update is delivered immediately and non-urgent updates exist in the same period When the next digest window occurs Then only the non-urgent updates are included in the digest (the urgent update is excluded to avoid duplication) Given a tenant has disabled a delivery channel (e.g., SMS) When an urgent update is delivered Then delivery does not occur on the disabled channel and does occur on the enabled channels
Bundling and Deduplication by Work Order and Event Type
Given multiple non-urgent updates for the same work order and event type occur within a digest period When the digest is generated Then they are combined into a single line item showing the latest status, a count of occurrences, and the most recent timestamp Given two or more updates have identical content and metadata within a digest period When the digest is generated Then duplicate entries are deduplicated so only one appears Given a digest includes updates across multiple work orders When the digest is generated Then items are grouped by work order with clear separators and each item includes a concise summary (<= 140 characters) and a link to the full timeline anchored to the latest event
Respect Channel Preferences and Quiet Hours
Given a tenant has selected specific delivery channels (e.g., Email and In-App only) When a digest or urgent update is sent Then delivery occurs only via the selected channels and is suppressed on unselected channels Given quiet hours are configured When non-urgent updates are generated during quiet hours Then no notifications are delivered during quiet hours and those updates are deferred to the next eligible digest time Given content is delivered across multiple enabled channels When a digest is sent Then the item count and included links are consistent across channels (content parity within channel constraints)
Tenant Preview of Next Scheduled Digest
Given a tenant has enabled digests When they view the Update Control screen Then the UI displays the next scheduled digest local time and the count of currently queued non-urgent updates Given the tenant changes digest frequency, specific windows, or quiet hours When the settings are saved Then the next scheduled digest time and queued count in the preview update within 1 second to reflect the new configuration Given there are zero queued non-urgent updates When the tenant views the preview Then the UI shows "No updates queued" and the next scheduled send time
One-Click Switch Back to Real-Time Updates
Given a tenant currently has digest delivery enabled When they click the one-click "Switch to real-time" control and confirm Then all future non-urgent updates are delivered immediately and the next scheduled digest (if any) is canceled Given there are queued non-urgent updates at the time of switching to real-time When the switch is confirmed Then the queued updates are delivered immediately via enabled channels and are not sent again in any digest Given the tenant switched to real-time in error When they re-enable digest delivery Then the previously configured digest frequency/windows and quiet hours are restored from the last saved settings
Daily and Specific Window Scheduling with Time Zone and DST Handling
Given a tenant’s time zone is set to America/Los_Angeles and a Daily digest time of 08:00 is configured When non-urgent updates occur before 08:00 local time Then a single digest is sent at 08:00 including all non-urgent updates since the last digest Given a tenant configures specific windows of 12:00–13:00 and 18:00–19:00 local time When each window ends Then a single digest is sent for that window including non-urgent updates generated during the window; if no updates occurred, no digest is sent Given a scheduled digest time falls on a daylight saving time transition date When the transition occurs Then the digest is sent at the correct local time and the configured window duration remains consistent in local time
Property Manager Policy Defaults
"As a property manager, I want to set default notification policies and guardrails so that communication stays consistent across my properties."
Description

Allows property managers to define portfolio- and building-level defaults and constraints for Update Control, including default granularity, permitted channels, minimum required channels for emergencies, and quiet hour boundaries. Applies policies automatically to new tenants and units with the ability to push updates to existing tenants while respecting stricter tenant choices where required by policy. Includes role-based access, audit trails for policy changes, and reporting on tenant adoption and exceptions. Exposes APIs for bulk policy management and integration with existing property management systems.

Acceptance Criteria
Apply Portfolio-Level Defaults on New Tenant/Unit Onboarding
Given a portfolio-level Update Control policy exists with default granularity, permitted channels, emergency minimum channels, and quiet hour boundaries When a new unit or tenant is created under that portfolio via UI or API Then the tenant’s Update Control settings are initialized to the portfolio defaults (or the building defaults if present) within 5 seconds of creation And the tenant cannot select channels outside the permitted list (UI disables and API rejects with 422 and error code POLICY_CHANNEL_NOT_PERMITTED) And retrieving the tenant settings via API reflects the applied policy values And an audit log entry is created with actor=system, scope=portfolio, target=tenant_id, action=APPLY_DEFAULTS
Building-Level Overrides Within Portfolio Constraints
Given a portfolio policy defines permitted channels and quiet hour boundaries And a building manager sets a building-level policy When the building-level policy is saved Then it must not introduce channels disallowed at the portfolio level (reject with 422 POLICY_CHANNEL_OUT_OF_SCOPE) And its quiet hours must fall within the portfolio quiet hour boundaries (reject with 422 QUIET_HOURS_OUT_OF_RANGE) And its emergency minimum channel count must be >= the portfolio minimum (reject with 422 EMERGENCY_MIN_UNDERSPECIFIED) And upon success, new tenants created in that building inherit the building defaults And an audit record captures before/after diffs, actor, timestamp, and scope=building
Emergency Minimum Required Channels Enforcement
Given a policy sets an emergency minimum of N channels and a list of permitted channels When a tenant attempts to disable channels such that enabled_emergency_channels < N Then the UI prevents the change with an inline message and the API returns 422 EMERGENCY_MIN_NOT_MET When an emergency update is sent for a work order in that tenant’s unit Then the system delivers the notification using at least N enabled channels within 60 seconds And quiet hours are bypassed for emergency updates (logged as QUIET_HOURS_BYPASSED_REASON=EMERGENCY) And delivery receipts are recorded per channel for auditability
Quiet Hours Boundaries and Time Zone Handling
Given a portfolio/building policy defines quiet hour boundaries (e.g., earliest_start=20:00, latest_end=08:00) and the tenant sets personal quiet hours within those bounds When the tenant’s time zone differs from the building time zone Then quiet hour evaluation uses the tenant profile time zone; if unset, uses the building time zone When non-emergency updates are generated during the tenant’s quiet hours Then they are deferred until the next allowed send window and queued with a scheduled_at timestamp And emergency updates ignore quiet hours And saving quiet hours that exceed 14 hours total duration is rejected (422 QUIET_HOURS_TOO_LONG) And start=end is treated as disabled quiet hours only if within policy allows; otherwise rejected (422 QUIET_HOURS_INVALID)
Push Policy Updates to Existing Tenants While Respecting Tenant-Stricter Choices
Given a property manager updates a portfolio or building policy And selects "Push to existing tenants" with options: dry_run=true/false When dry_run=true Then the system returns counts of tenants affected by each setting change and any conflicts, without persisting changes When dry_run=false Then the system applies new defaults to existing tenants except where tenant settings are stricter and policy allows tenant-stricter choices (no downgrade of strictness) And where policy mandates higher minimums (e.g., emergency channels), tenant settings are elevated to meet the minimum And a per-tenant change log is created with before/after values and reason=POLICY_PUSH And API and UI reflect updated settings within 2 minutes for 95th percentile of tenants in scope (up to 10,000)
Role-Based Access Control and Audit Trails for Policy Management
Given roles PortfolioAdmin, BuildingManager, SupportViewer, and Vendor are defined When accessing policy endpoints or UI Then only PortfolioAdmin can create/edit/delete portfolio policies; BuildingManager can create/edit building policies within assigned buildings; SupportViewer has read-only access; Vendor is denied (403) And all policy changes require MFA confirmation if enabled for the actor And every create/update/delete action writes an immutable audit record with actor_id, role, scope (portfolio/building), target_id, timestamp (UTC), ip, before/after JSON diff, and reason And audit records are retrievable via API with filtering by actor, date range, scope, and exportable as CSV
Tenant Adoption and Exceptions Reporting
Given policy defaults are applied across the portfolio When a property manager opens the Update Control Adoption report Then they can see per-portfolio and per-building metrics: % tenants on defaults, % customized, % below emergency minimum (should be 0), average channels per tenant, quiet hours adoption rate, and number of policy conflicts prevented in last 30 days And the report supports filters by building, date range, and tenant status (active/move-out) And an Exceptions view lists tenants with pending policy elevation, failed deliveries, or missing time zones, with export to CSV And all metrics refresh at least every 24 hours and for on-demand refresh complete within 5 minutes for portfolios up to 20,000 tenants
Consent and Compliance Logging
"As a tenant, I want clear opt-in and opt-out controls with a record of my choices so that my privacy and communication preferences are respected."
Description

Implements consent capture and management for each communication channel, including double opt-in for SMS, explicit opt-in for push, and clear opt-out pathways. Stores immutable logs with timestamps, channel, method, source, and IP for auditability. Enforces compliance with regional regulations (e.g., TCPA, GDPR, CCPA) via suppression lists, per-channel throttling, and data retention policies. Provides self-service export and deletion of preference data and admin audit views. Ensures all outbound messages include legally required identifiers and unsubscribe instructions where applicable.

Acceptance Criteria
SMS Double Opt-In Capture and Enforcement
Given a tenant enters a mobile number and requests SMS updates When the system sends a verification SMS containing a unique code or link Then all non-essential SMS are suppressed until confirmation is received Given a verification SMS was sent When the tenant confirms via reply code or confirmation link within 30 minutes Then SMS consent status becomes Active and an immutable log is recorded with fields: channel=SMS, method=double_opt_in, source=tenant_self_service, timestamp (UTC), requestId, ip, userAgent/deviceId Given a verification remains unconfirmed after 30 minutes When the reminder policy is evaluated Then at most one reminder SMS is sent and status remains Pending Given SMS consent is not confirmed within 24 hours When any workflow attempts to send an SMS Then the send is blocked with reason=Unconfirmed Opt-In and the attempt is audit-logged Given SMS consent is Active When the tenant replies STOP Then consent switches to Opted-Out within 60 seconds, a confirmation SMS is sent, and future SMS are suppressed
Push Opt-In, Revocation, and Device Binding
Given a device/browser presents a push permission prompt When the tenant taps Allow Then push consent is recorded as Active with deviceId, appVersion, userAgent, ip, timestamp, method=explicit_opt_in Given the tenant taps Don’t Allow or later revokes OS permission When a push send is attempted Then no push is delivered and delivery logs record reason=Permission Denied Given OS-level permission was revoked When the app session resumes or a background check runs Then consent state is synchronized within 5 minutes and the device token is invalidated Given a tenant disables push for a specific device in preferences When notifications are sent Then only that device is suppressed; other devices remain eligible Given a provider returns an invalid/expired device token When processing feedback Then the token is de-registered and the tenant is prompted on next login to re-enable push
Outbound Message Legal Identifiers and Unsubscribe Instructions
Given an outbound email notification is generated When it is delivered Then it includes the brand name, property mailing address in the footer, a working unsubscribe link, and RFC 2369 List-Unsubscribe and List-Unsubscribe-Post headers Given a recipient clicks the email unsubscribe link When the preference page loads Then Email suppression is applied within 10 seconds and a confirmation page is shown Given an SMS notification is generated When it is delivered Then it includes a brand identifier and the text “Reply STOP to opt out”; STOP replies are processed within 60 seconds and a confirmation SMS is sent Given a user has a non-English locale When legal identifiers and opt-out instructions are rendered Then the content is localized to the user’s language with no missing strings
Per-Channel Opt-Out and Cross-System Suppression
Given a tenant opts out via any supported method (unsubscribe link, STOP keyword, preferences center, admin action) When the request is received Then the suppression list for that channel updates within 10 seconds and future non-exempt messages are blocked Given any system component attempts to send to a suppressed recipient When the send is evaluated Then the message is not sent, and an audit record is stored with reason=Suppressed, channel, templateId, and actor Given an external suppression file (CSV) is imported When processing completes Then matching contacts are suppressed with source=import and a summary report lists counts for added, updated, and invalid rows Given a tenant previously suppressed wants to re-subscribe When they complete the required action (double opt-in for SMS, explicit opt-in for push, email re-subscribe confirmation) Then suppression is lifted and a new consent log entry is recorded
Immutable Consent and Preference Audit Log
Given any consent or preference change occurs When the change is committed Then an append-only log entry is created with fields: userId, actor, channel, action, status, method, source, timestamp (UTC ISO 8601), requestId, ip, userAgent/deviceId, region, previousValue, newValue Given an audit entry exists When an admin attempts to edit or delete it Then the original entry remains unchanged; a corrective entry may be appended capturing actor, reason, and timestamp Given audit logs are exported or queried When integrity verification runs Then hash-chain integrity succeeds; any failure blocks export and triggers an alert with incidentId Given regional retention policies are configured When an entry exceeds its retention period and is not under legal hold Then it is purged by a scheduled job and a retention-purge receipt is recorded
Regional Compliance, Throttling, and Quiet Hours Enforcement
Given regional rules (TCPA, GDPR, CCPA) and property timezone are configured When attempting to send non-emergency SMS outside 08:00–21:00 local time Then the message is queued until the next allowable window and the delay is logged Given per-channel throttles are configured (e.g., SMS: max 3 per incident per 24h; Email: max 5 per 24h; Push: max 6 per 24h) When limits are exceeded Then additional messages are blocked with reason=Rate Limited, and counters reset at the window boundary Given GDPR applies to a tenant When consent is captured Then lawfulBasis and purpose are recorded and visible in audit views Given a message is marked Emergency by an authorized role When quiet hours or throttles would otherwise block delivery Then the message is delivered, and the bypass is audit-logged with actor, justification, and timestamp
Self-Service Export and Deletion of Preference Data
Given an authenticated tenant with verified email requests an export When the request is submitted Then a machine-readable export (JSON and CSV) containing current preferences and consent events is available within 5 minutes via a signed link that expires in 24 hours Given the export is generated When the tenant downloads it Then PII is limited to necessary fields; event count matches the audit view; and a checksum is provided for integrity Given an authenticated tenant requests deletion of preference data When the request is confirmed via MFA Then preference records are deleted within 7 days; operational data is minimized; immutable consent logs are retained only as required for legal compliance and marked restricted Given an export or deletion request completes When the operation finishes Then a confirmation email is sent containing requestId, timestamp, and scope of the action
Delivery Fallback and Timezone-Aware Scheduling
"As a tenant, I want messages delivered reliably and at the right local time so that I don’t miss important updates or receive them at inconvenient hours."
Description

Ensures reliable delivery by implementing per-tenant fallback rules and retries (e.g., try push, then SMS, then email for urgent messages) with configurable thresholds. Detects bounces, unsubscribes, and device uninstalls to automatically adjust future routing. Schedules notifications using tenant locale and time zone, correctly handling daylight saving transitions and travel scenarios when device-reported time zone changes. Applies throttling to avoid rapid-fire messages and provides delivery metrics and alerts when reliability drops below target thresholds.

Acceptance Criteria
Urgent fallback sequence with retries and thresholds
Given tenant A has fallback rule for urgent: Push -> SMS -> Email with retries Push=1, SMS=0, Email=0 and timeouts Push=60s, SMS=120s When an urgent notification is triggered for tenant A Then the system sends Push immediately And if no push delivery receipt is received within 60s, it retries Push once And if still not delivered, it sends SMS And if no SMS delivery confirmation within 120s, it sends Email And the first successfully delivered channel marks the notification Delivered and stops further attempts And no channel exceeds its configured retry count
Auto-adjust channels on bounce, unsubscribe, or uninstall
Given an Email hard bounce is returned for tenant A When the bounce is recorded Then Email is marked invalid for tenant A and excluded from future routing until the address is changed And a reason code and timestamp are stored in channel health Given tenant A replies STOP to an SMS When the unsubscribe webhook is received Then SMS is marked unsubscribed for tenant A and excluded from all future sends, including fallbacks, until tenant texts START And the system logs consent status change with source and time Given a Push token is reported unregistered by the push provider When a push attempt returns "uninstalled" or "unregistered" Then Push is marked inactive for tenant A and excluded from future routing until a new token is registered
Local-time scheduling with DST transitions
Given a daily digest notification is scheduled for 02:30 local time for tenant A in a region that observes spring-forward DST When the DST transition makes 02:30 a nonexistent time Then the notification fires at 03:00 local time on that day and resumes at 02:30 on subsequent days Given a notification is scheduled for 01:30 local time on the fall-back day When the clock repeats the 01:00 hour Then the notification is sent only once at the first 01:30 occurrence Given multiple tenants across time zones When the schedule is executed Then each tenant receives the notification at the configured local wall-clock time regardless of server time zone
Time zone changes due to tenant travel
Given tenant A has a recurring reminder at 09:00 local and their device time zone changes from America/Los_Angeles to America/New_York at 07:00 PT (10:00 ET) When the system ingests the new device time zone Then all future occurrences are rescheduled to 09:00 in America/New_York And if the current day's 09:00 in the old zone has not yet fired, it is canceled to avoid duplicate delivery And at most one delivery occurs per scheduled occurrence ID across the zone change Given tenant A travels back to the original zone When the device reports America/Los_Angeles again Then subsequent occurrences align to 09:00 in America/Los_Angeles without duplicates
Per-tenant configurable fallback rules
Given tenant B's preferences specify non-urgent: Email only; urgent: Push -> SMS When a non-urgent update is sent Then only Email is attempted And Push and SMS are not attempted even if Email is delayed but not failed Given an urgent update is sent to tenant B and push fails When the push attempt fails by timeout Then SMS is attempted per the tenant's rule And Email is not attempted because it is not part of the urgent fallback chain
Throttling burst notifications per tenant
Given throttle policy per tenant is max 3 notifications per 5 minutes; queue length 10; overflow strategy=delay When 10 notifications are queued for tenant C within 2 minutes Then only the first 3 are sent immediately And the next 7 are delayed and released FIFO as capacity becomes available within the next 5-minute window(s) And if queue exceeds 10, additional notifications are dropped and recorded with reason "throttled_overflow" And urgent notifications still respect a minimum 15-second spacing between sends Given the 5-minute window elapses When capacity opens Then delayed notifications resume sending until the throttle limit is reached again
Delivery metrics and reliability alerts
Given delivery metrics are aggregated per channel and per tenant over rolling 15-minute and 24-hour windows When the 15-minute successful delivery rate for Push globally drops below 98% with at least 200 attempts Then a "reliability_degradation" alert is emitted with channel=Push, window=15m, rate observed, and top error causes And the alert is visible in the dashboard and sent to the configured alert sink Given metrics collection is healthy When querying the metrics API for tenant D for the last 24 hours Then it returns counts for attempted, delivered, failed, bounced, fallback_used, average first-delivery latency, and per-channel breakdown And numbers sum consistently (delivered + failed + bounced = attempted)

Household Share

Creates a secure, expiring Trust Timer link for roommates, caretakers, or the front desk. Everyone sees the same SLA clock, status badges, ETA, and prep steps, eliminating duplicate check‑ins.

Requirements

Secure Trust Timer Link Generation
"As a tenant, I want to share a secure, time-limited link to my maintenance request so that my roommates can see status and timing without needing an account."
Description

Generate a scoped, cryptographically signed link tied to a single work order that expires automatically based on configurable TTL (e.g., 24–168 hours) or manual revocation. The link exposes a read-only, shared view of the request’s SLA clock, current status badges, vendor ETA, and prep steps. Implement short-link creation, QR rendering, and copy-to-clipboard. Support one-time view and multi-view modes, rate limiting, and device fingerprinting to mitigate abuse. Store minimal metadata (creator, recipient channel, expiry, last access) and log all access events for audit. Integrate with FixFlow’s request service, vendor ETA service, and notification pipeline.

Acceptance Criteria
Signed, Scoped Link Generation with Configurable TTL
Given a valid work order ID and a TTL between 24 and 168 hours (inclusive) and an optional recipient channel When the landlord generates a Household Share Trust Timer link Then the system creates a cryptographically signed token scoped only to that work order ID And the token includes an expiration equal to the requested TTL And generation with a TTL outside the allowed range is rejected with a validation error And the link is produced as a short URL that redirects to the signed endpoint And the server validates the token signature on every access; tampering returns 403 And metadata stored includes creator ID, recipient channel (if provided), expiry timestamp, and last access initialized to null And if a recipient channel is provided, a notification payload is enqueued to the notification pipeline and an enqueue ID is recorded
Read-Only Shared View Shows SLA/Status/ETA/Prep for a Single Work Order
Given an unexpired, valid Trust Timer link When a recipient opens the link Then the page renders the SLA clock, current status badges, vendor ETA, and prep steps for that work order And no edit controls are present and all write APIs are blocked for the session And the vendor ETA and status reflect the latest data from the Vendor ETA Service and Request Service at render time And the Trust Timer countdown matches the server-side TTL within ±1 second And attempts to access any other work order data with the token are denied with 403
One-Time View Mode Consumption Behavior
Given a Trust Timer link configured as one-time view When the first successful 200 OK view is served Then subsequent requests return 410 Gone with an explanatory message And the link’s last access timestamp and the accessing device fingerprint are recorded And aborted or incomplete requests do not consume the one-time view
Multi-View Mode with Rate Limiting and Device Fingerprinting
Given a Trust Timer link configured as multi-view When the link is accessed repeatedly from the same device or IP Then requests are allowed up to 10 per minute per device fingerprint and per IP; further requests return 429 until the window resets And a maximum of 200 total successful views are allowed over the lifetime of the link And if more than 25 distinct device fingerprints attempt access, return 403 Suspicious Activity and auto-revoke the link And each access event records device fingerprint, IP hash, user agent, timestamp, and outcome
Manual Revocation and Automatic Expiry Enforcement
Given a valid Trust Timer link When a landlord manually revokes the link or the TTL elapses Then subsequent accesses respond 410 Gone within 60 seconds of revocation or expiry And the short URL and QR code resolve to the same 410 state And an audit event is logged for the revocation or expiry with actor (if manual) and timestamp And the portal reflects the Revoked or Expired state within 5 seconds without requiring a full page reload
Short-Link, QR Code, and Copy-to-Clipboard Usability
Given a generated Trust Timer link When the UI presents sharing options Then a short URL of 28 characters or fewer is displayed and can be copied to the clipboard with a success confirmation within 1 second And a QR code is rendered as SVG and downloadable as PNG; scanning the QR opens the short URL And share actions work on the latest Chrome, Safari, Firefox, iOS Safari, and Android Chrome without console errors
Minimal Metadata Storage and Auditable Access Logs
Given link generation and subsequent accesses When inspecting stored records and logs Then only creator ID, recipient channel, expiry timestamp, and last access timestamp are stored as link metadata And no tenant PII, request body content, or vendor contact details are stored with the link record And each access event is logged with timestamp, IP hash, device fingerprint, user agent, outcome (success/expired/revoked/rate-limited), and work order ID And audit logs are retrievable via internal API, filterable by work order ID and date range
Role-Based Viewer Permissions
"As a property manager, I want shared viewers to have only the information and actions appropriate to their role so that privacy is protected and workflows remain controlled."
Description

Provide granular, role-scoped access for shared viewers (Roommate, Caretaker, Front Desk) controlling what each can see and do. All roles can view SLA, status, ETA, and prep steps; internal notes, PII, and billing are redacted. Optional capabilities by role include acknowledging prep steps (all), leaving a note to occupants (Roommate/Caretaker), and lobby-specific instructions (Front Desk). Enforce permissions server-side via policy checks tied to the link token. Ensure future extensibility for custom roles per account.

Acceptance Criteria
All Roles View Core Ticket State
Given a valid Household Share link for ticket T for any viewer role When the viewer opens the link Then the UI displays the SLA countdown, current status badges, ETA window, and prep steps for T And the values returned by the shared viewer API for these fields exactly match the canonical ticket API values for T at the time of request
Redaction of PII, Internal Notes, and Billing
Given any viewer role using a valid share link for ticket T When the viewer requests ticket details via the shared viewer API Then the response excludes or redacts PII fields (occupant full name, phone, email), internal notes and attachments, and all billing/invoice amounts And any attempt to access internal endpoints or fields via query parameters or alternate routes returns 403 Forbidden with no sensitive data in the payload And the UI does not render links or placeholders that reveal the existence of internal attachments or invoices
Prep Steps Acknowledgement by Shared Viewers
Given ticket T has at least one prep step And a viewer with any role opens the share link When the viewer acknowledges a prep step Then the server records an acknowledgment event with ticket ID T, prep step ID, viewer role, and timestamp And subsequent GETs to the shared viewer API for T return the prep step as acknowledged And repeating the acknowledgment is idempotent and does not create duplicate events
Roommate/Caretaker Can Leave Notes to Occupants
Given a share link for ticket T with role Roommate or Caretaker When the viewer submits a "Note to Occupants" Then the server accepts the request (201/200) and persists the note associated to T and the viewer role And the note is visible in the occupants' thread for T and is not returned to vendor-facing or internal notes endpoints And the same request from a Front Desk role returns 403 Forbidden
Front Desk Can Submit Lobby Instructions Only
Given a share link for ticket T with role Front Desk When the viewer submits lobby-specific instructions Then the server accepts and persists the instructions associated to T and the viewer role And attempts by Roommate or Caretaker to submit lobby instructions return 403 Forbidden And Front Desk cannot submit a "Note to Occupants" (returns 403 Forbidden)
Server-Side Policy Checks via Link Token
Given a share link token that encodes ticket ID T, viewer role R, and expiry E When requests are made to shared viewer endpoints Then authorization is evaluated server-side using the token and policy for role R And requests for any ticket other than T return 403 Forbidden And requests after E return 401/403 and do not leak resource existence And all denied requests are logged with token identifier and reason
Custom Roles Per Account Are Enforceable
Given an account-defined custom viewer role R' with a specified permission set (view core ticket state; prep step acknowledgment enabled/disabled; leave note to occupants enabled/disabled; lobby instructions enabled/disabled) When a share link is created for R' and used Then only the actions enabled for R' succeed (200/201) and disabled actions return 403 Forbidden And viewing behavior conforms to baseline redaction rules (no PII, no internal notes, no billing) And creating and using R' does not alter behavior of predefined roles
Unified SLA Clock & Real-Time Status Sync
"As a roommate, I want the shared page to update in real time so that I always see the latest ETA and status without refreshing or asking the primary tenant."
Description

Render the same SLA countdown, status badges, and vendor ETA across the primary tenant view and all Household Share views. Use server-sent events or WebSocket channels with a polling fallback to push live updates (e.g., status change, tech en route, ETA adjustments). Normalize to viewer’s local timezone and show last-updated timestamps. Degrade gracefully offline with cached state and resync logic. Integrate with SLA/dispatch services and the event bus to ensure low-latency, reliable updates.

Acceptance Criteria
Live Sync Across Primary and Household Share Views
- Given the primary tenant and at least two Household Share viewers have the same request open, When a backend status changes (e.g., Assigned -> Tech En Route), Then all open clients display the new status badge within 2 seconds (95th percentile) and none show a divergent status. - And the SLA countdown shows the same remaining time across all clients with <= 1 second drift for 95% of samples over 5 minutes. - And the vendor ETA timestamp/window is identical across clients for the same request version.
Transport Selection With Fallback
- Given a supported browser, When a session starts, Then the client establishes a WebSocket or SSE stream within 3 seconds. - Given the stream drops or is unsupported, When 2 consecutive heartbeats are missed (10 seconds), Then the client switches to polling at a 10-second interval within 5 seconds, without duplicate UI flashes. - And upon stream recovery, polling stops and the stream resumes within 5 seconds, preserving state continuity.
Timezone Normalization and Last Updated
- Given viewers in different timezones open the same request, When viewing SLA due time and vendor ETA, Then times render in each viewer’s local timezone with correct DST handling, and a clear timezone indicator is shown (e.g., GMT-5). - And a Last Updated timestamp is displayed and refreshed with each state change, accurate to the minute and in local time. - And relative time displays (e.g., “ETA in 32 min”) match the absolute times shown.
Offline Graceful Degradation and Resync
- Given connectivity loss, When offline is detected, Then the UI displays an offline indicator, freezes the countdown, and shows the last known state with Last Updated. - And upon reconnection, the client performs a full state fetch, reconciles with cache, and resumes live updates within 5 seconds, correcting the countdown to server-authoritative time. - And no stale state older than 15 minutes persists after reconnection.
Dispatch/SLA Event Integration and Latency
- Given SLA/dispatch events (status change, ETA update, SLA pause/resume) are emitted, When events arrive, Then they map to UI badges/countdowns exactly per the mapping spec. - And event handling is idempotent; duplicate events do not cause double transitions or flicker. - And end-to-end latency from event emission to visible UI update is median <= 500 ms and 95th percentile <= 2 s under 200 concurrent viewers in staging.
Household Share Parity and Link Expiry Handling
- Given an active Household Share link, When the primary view receives an update, Then the Household Share view shows identical SLA countdown, status badges, ETA, and Last Updated within the same latency bounds. - Given a Household Share link expires while open, When expiry occurs, Then the connection is revoked within 60 seconds, live data is hidden, and an expiry message is shown without further updates. - And upon refreshing with a renewed link, live sync resumes within 5 seconds with current authoritative state.
Ordering, De-duplication, and Clock Drift Control
- Given out-of-order or retried events, When multiple updates are received, Then the client applies them in server timestamp/sequence order and discards older sequence IDs. - And the SLA countdown re-syncs to the server time reference at least every 60 seconds to keep drift <= 1 second over 10 minutes. - And no more than one visual update is rendered per distinct state change.
Share Management & Revocation Console
"As a tenant, I want to see and revoke the links I’ve shared so that I can control who still has access to my maintenance request."
Description

Add a management panel within the request detail for creators (tenant/PM) to create, label, and revoke Household Share links. Show active links with recipient channel, role, expiry, last viewed, and quick actions (extend, revoke, copy, download QR). Provide bulk revoke and automatic revocation when a work order closes. Expose audit logs for compliance. Ensure actions propagate immediately to invalidate tokens and close active sessions.

Acceptance Criteria
Create & Label Household Share Link
Given I am the request creator viewing the Request Detail Share panel And I select a recipient channel (SMS, Email, or Link) and a role (Roommate, Caretaker, Front Desk, Other) And I set an expiry date/time in the future When I click Create Share Then the system generates a unique tokenized URL And the new share appears in the Active Links list with columns: Recipient Channel, Role, Expiry (absolute timestamp), Last Viewed = Never, and Quick Actions: Extend, Revoke, Copy, Download QR And Copy places the URL on my clipboard and displays a success confirmation And Download QR downloads a PNG containing the encoded URL And, if pre-existing active shares exist, they display with the same columns on panel load And the share token is immediately usable by recipients
Revoke Single Share & Invalidate Active Sessions
Given an active share exists for the request And at least one recipient is currently viewing the shared page When I click Revoke on that share and confirm the action Then the token becomes invalid within 5 seconds And any active recipient session is terminated within 5 seconds and shows an Access revoked message And subsequent requests to the link return HTTP 403 or 410 And the row updates to Revoked with a timestamp and is removed from Active Links And an audit log entry is recorded with actor, action, share ID, and timestamp
Bulk Revoke All Active Shares
Given two or more active shares exist for the request When I trigger Bulk Revoke and confirm Then all associated tokens are invalidated within 5 seconds And all active recipient sessions are terminated within 5 seconds And the Active Links list becomes empty And a consolidated audit log entry records the bulk operation plus individual link IDs
Automatic Revocation on Work Order Close
Given there are active shares for the request When the associated work order status changes to Closed Then all shares auto-revoke within 5 seconds without user action And subsequent link access shows Request closed and returns HTTP 403 or 410 And an audit log entry is created with actor = System and reason = Work order closed And the Active Links list shows zero active shares
Extend Expiry of Share Link
Given an active share with a future expiry exists When I click Extend and set a new expiry date/time in the future Then the share retains the same token And the Expiry column updates to the new timestamp immediately And recipients do not experience downtime accessing the link And an audit log entry records the old and new expiry values with timestamp and actor
Last Viewed Tracking & Display
Given an active share exists and has not been opened When a recipient opens the shared link Then Last Viewed updates to the latest access time within 10 seconds And subsequent accesses update the timestamp only when newer And the updated value is visible in the Active Links list for that share
Audit Logs Exposure & Export
Given I am viewing Audit Logs for the request When I filter by action type (Created, Revoked, Bulk Revoked, Extended, Auto Revoked) and date range Then the list updates to show only matching entries And each entry displays timestamp (UTC), actor (User or System), action, share ID, and details (e.g., old/new expiry) And logs are read-only and cannot be edited or deleted And I can export the filtered results to CSV covering the past 12 months
Multi-Channel Delivery & Recipient Verification
"As a caretaker, I want to receive the share link via my preferred channel and verify quickly so that I can prepare for the vendor’s arrival without creating an account."
Description

Enable delivery of Household Share links via SMS, email, or manual copy, with message templates that include context (request type, building, ETA). Support optional recipient verification using OTP or magic-link confirmation to reduce misdelivery risks, configurable per account. Enforce per-channel rate limits and bounce handling. Track delivery status and open events to inform read receipts. Integrate with existing comms providers and respect tenant contact preferences and opt-outs.

Acceptance Criteria
SMS Delivery with Context and Delivery/Open Tracking
Given a staff user selects SMS to send a Household Share link and the recipient has not opted out of SMS, When the user clicks Send, Then the SMS body includes request type, building name, ETA, and a single expiring Trust Timer link. Given the SMS is accepted by the provider, When a provider message ID is returned, Then the system stores the provider ID and send timestamp for the share. Given delivery callbacks are received, When status updates (queued, sent, delivered, failed) arrive, Then the system updates the share’s delivery status and timestamps and shows them in the request timeline. Given the carrier returns a permanent failure (e.g., 30003, 30005), When the callback indicates a hard bounce, Then the system marks the SMS as Failed, records the error code and reason, and suppresses further retries for that recipient and request. Given channel rate limits are configured to max 3 SMS per recipient per request per 24 hours, When a 4th send is attempted within the window, Then the system blocks the send and surfaces a rate-limit message with the time remaining. Given the recipient clicks the link in the SMS, When the link is opened, Then an open event is recorded and the read receipt is updated to Opened with timestamp.
Email Delivery with Template Context, Bounce Handling, and Open Tracking
Given a staff user selects Email to send a Household Share link and the recipient has not unsubscribed, When the user clicks Send, Then the email subject includes request type and building name and the email body includes ETA and the expiring link. Given the provider accepts the email, When a provider message ID is returned, Then the system stores the provider ID and send timestamp. Given the provider reports a hard bounce or spam complaint, When a bounce/complaint webhook is received, Then the system marks the email as Failed, records the reason, and adds the address to the suppression list for future sends. Given open tracking is enabled, When the recipient opens the email or the link is clicked, Then the system records an open event and updates the share read receipt to Opened with timestamp. Given channel rate limits are configured to max 3 emails per recipient per request per 24 hours, When a 4th send is attempted within the window, Then the system blocks the send and surfaces a rate-limit message with the time remaining.
Manual Copy Link with Expiry and Audit Logging
Given a staff user clicks Copy Link, When the link is copied, Then the copied URL contains a unique token, reflects the current Trust Timer expiry, and honors the account’s verification setting. Given the Trust Timer expires at T, When the link is opened after T, Then the recipient sees an expired page and the share content is not accessible. Given the link is opened before expiry, When the first access occurs, Then an open event is recorded and the read receipt is updated with timestamp. Given a copy action occurs, When the user triggers Copy Link, Then an audit log entry is created with user, timestamp, request ID, and channel=manual_copy.
Account-Level Verification Configuration (OTP or Magic Link)
Given verification mode is set to SMS OTP and a valid phone is provided, When the recipient requests access, Then a 6-digit OTP is sent, expires in 10 minutes, and allows a maximum of 5 attempts. Given the recipient enters the correct OTP within the expiry and attempt limit, When validation occurs, Then access to the Household Share is granted and a verification_success event is logged. Given the recipient exceeds the attempt limit or expiry, When validation occurs, Then access is denied, a temporary lock (15 minutes) is applied, and a failure event is logged. Given verification mode is set to Email Magic Link and a valid email is provided, When the recipient clicks the magic link within 15 minutes, Then access is granted and a verification_success event is logged. Given verification is disabled for the account, When the link is opened, Then the share loads without a verification step.
Per-Channel Rate Limiting and Deduplication
Given rate limits are configured (SMS: 3/recipient/request/24h; Email: 3/recipient/request/24h; OTP: 5/recipient/hour), When a send would exceed the relevant limit, Then the system blocks the send and returns a descriptive error with the retry-after time. Given the same message payload is submitted repeatedly within 60 seconds, When duplicate sends are detected by idempotency key, Then only one provider send is executed and subsequent attempts return the prior result. Given a transient provider error occurs (e.g., HTTP 5xx), When a send is attempted, Then the system retries up to 3 times with exponential backoff without violating rate limits.
Provider Integration, Webhook Security, and Fallback
Given SMS and Email providers are configured, When a message is sent, Then the system uses the account’s selected provider and includes a correlation ID in metadata for tracing. Given provider webhooks are received, When a callback arrives, Then the system validates the signature/secret and rejects unsigned or invalid callbacks. Given a send fails with a transient error and a secondary provider is configured, When retry policy is triggered, Then the system routes the retry to the fallback provider and associates events back to the same share record. Given URLs are included in messages, When the message is constructed, Then links are wrapped in a short domain that preserves UTM and does not break across clients.
Contact Preferences, Opt-Outs, and Channel Selection
Given tenant contact preferences specify allowed channels and DND windows, When a staff user initiates a send, Then the system selects the highest-priority allowed channel and blocks sends during DND unless the user has override permission and confirms. Given a recipient has opted out of SMS (STOP) or unsubscribed from email, When a send is attempted on that channel, Then the system suppresses the send, surfaces the suppression reason, and offers alternate allowed channels and manual copy. Given preferences change after a share is created, When a new send is attempted, Then the latest preferences and suppression lists are enforced.
Privacy Redaction & Data Minimization
"As a tenant, I want the shared page to hide my personal information so that roommates and front desk can help without seeing private details."
Description

Automatically redact sensitive fields (full names, phone numbers, emails, unit access codes, payment details, internal diagnostics) from the shared view while preserving necessary context (building, unit alias, request category, prep checklist). Implement server-side filters that map internal request fields to a public schema. Include progressive disclosure for building access notes with creator opt-in. Validate that logs and analytics also store only minimized data for shared sessions.

Acceptance Criteria
Shared View Redacts PII Fields
Given a valid Household Share link session for a maintenance request; When the shared page is loaded via UI or API; Then the response conforms to the public schema and the only top-level fields present are {building, unitAlias, requestId, requestCategory, status, slaEndsAt, eta, prepChecklist, attachments[], activity[]}; And any person-identifying fields (fullName, phone, email) are absent or replaced with masked values (e.g., "J*** D***", "***-***-1234", "j***@***.com"); And unitAccessCodes, paymentDetails, and internalDiagnostics fields are entirely omitted; And the UI renders building, unitAlias, requestCategory, and prepChecklist while not rendering any redacted fields; And network traces confirm no redacted fields appear in HTML, JSON, headers, or data attributes.
Server-Side Public Schema Enforcement
Given any request to a /share/{token} endpoint, including with query parameters such as expand=*, debug=true, include=internal, or X-Internal headers; When the server processes the request; Then only the whitelisted public schema fields are returned and all non-whitelisted fields are excluded; And attempts to access internal fields result in a 403 or omission without leakage; And the Content-Security-Policy and Cache-Control headers are present and do not include internal identifiers; And contract tests validate the response exactly matches the versioned public schema (e.g., X-Public-Schema: v1).
Progressive Disclosure of Access Notes with Creator Opt-In
Given a request that contains building access notes; And the share creator did or did not opt-in to include access notes; When a viewer opens the shared page; Then access notes are hidden by default and only a "Reveal access notes" control is shown if and only if the creator opted in; And when the viewer confirms reveal, the notes are shown for that session only and an audit entry is recorded with {shareId hash, requestId, timestamp, action: "access_notes_revealed"} and without note content; And analytics capture only a boolean revealed flag and a length bucket, not the raw text; And page reload resets visibility to hidden.
Minimized Logging and Analytics for Shared Sessions
Given any server log, audit trail, or analytics event emitted during a shared session; When events are recorded; Then no PII (names, phones, emails), unitAccessCodes, payment details, or raw access note text are stored; And only minimized fields are stored: {shareId hash, requestId, buildingId, unitAlias, category, status, slaSecondsRemaining bucket, httpStatus, latency ms}; And error logging redacts request/response bodies to the same allowlist; And automated allowlist regex tests pass for sampled log lines and event payloads.
Attachment and Metadata Sanitization in Shared View
Given a request with file attachments (images, PDFs, diagnostics); When viewed or downloaded via the shared link; Then only attachments tagged as shareable are shown; And image EXIF (GPS, device ID, timestamps) is stripped from served files; And attachment filenames are genericized (e.g., img-####.jpg) without tenant or staff names; And download URLs are short-lived, signed, and scoped to the share token; And internal diagnostic attachments are excluded; And a security test verifies no EXIF or PII is present in downloaded files.
Shared Link Expiration Enforces Data Minimization
Given a Household Share link with a Trust Timer expiry; When the link is accessed after expiry; Then the server returns 410 Gone with a generic message and no request metadata, and does not render any previously minimized fields; And any previously issued signed attachment URLs are invalid; And logs record only an expired event without PII; And caches/CDN entries for the share are purged or naturally expire within their TTL.
UI and DOM Parity for Redaction Across Components
Given the shared page with modules (header, status badges, SLA clock, ETA, activity feed, comments, prep checklist, attachments); When the page renders and data is embedded; Then no PII appears in any visible component, tooltip, or avatar (use generic labels like "Responder"); And no PII or internal fields are present in DOM data attributes, inline JSON, or ARIA labels; And snapshot and accessibility tests confirm parity between visible text and minimized data sources.
Read Receipts & Prep Step Acknowledgment
"As a vendor tech, I want to see that occupants have read the prep steps and acknowledged them so that I can arrive prepared and avoid unnecessary follow-ups."
Description

Provide lightweight read receipts showing when each shared viewer first opened the link and last viewed it. Allow viewers to acknowledge completion of prep steps (e.g., clear under-sink area), recording who acknowledged and when. Surface this in the thread/activity timeline and notify the vendor to reduce duplicate check-ins. Respect privacy by showing aggregated viewer labels (e.g., ‘Roommate A’) rather than full identities unless provided by the creator.

Acceptance Criteria
Read Receipt on First Open
Given an active Household Share link and a viewer opens it for the first time Then the system records a read receipt with fields viewer_label, first_opened_at (ISO 8601 UTC), last_viewed_at = first_opened_at, link_id, and ticket_id And the read receipt is visible in the thread/activity timeline within 5 seconds of server receipt And reloading the page or reopening on the same device/browser does not create additional first_opened_at entries
Last Viewed Timestamp Updates
Given a read receipt exists for a viewer on a Household Share link When the same viewer accesses the link again while it is still active Then update last_viewed_at to the current time (ISO 8601 UTC) without changing first_opened_at And do not create a new timeline entry; instead, update the existing timeline block to show the latest last_viewed_at And accessing from a different device/browser creates a separate viewer record with its own first_opened_at and last_viewed_at
Prep Step Acknowledgment Capture
Given the Household Share link displays one or more prep steps for the ticket When a viewer marks a prep step as acknowledged Then record an acknowledgment event with fields step_id, viewer_label, acknowledged_at (ISO 8601 UTC), link_id, and ticket_id And the prep step appears as completed for all viewers within 5 seconds And subsequent acknowledgments for the same step do not create duplicates and show "Already acknowledged by <viewer_label> at <timestamp>"
Thread Timeline Entries and Vendor Notification
Given a first open read receipt is recorded or a prep step is first acknowledged Then append a timeline entry showing the event type, viewer_label, and timestamp (ISO 8601 UTC) And send a vendor notification including ticket_id, event type, viewer_label (aggregated or provided), and relevant step summary And throttle notifications to a maximum of 1 "first open" notification per ticket and 1 notification per prep step; do not notify on subsequent "last viewed" updates And notifications contain no personal identifiers beyond the approved label mapping
Privacy: Aggregated Viewer Labels
Given the creator did not provide viewer identities when generating the Household Share link Then all read receipts and acknowledgments display aggregated labels (e.g., "Roommate A", "Roommate B", "Caretaker A") And the mapping from viewer to aggregated label remains stable for the lifetime of the link across sessions on the same device And no personal identifiers (name, email, phone, IP) are exposed in the UI, timeline, or notifications Given the creator did provide explicit viewer labels, Then those labels are used instead of aggregated labels in all surfaces
Offline Prep Acknowledgment Sync
Given a viewer is offline when acknowledging a prep step Then the client stores a queued acknowledgment event with device_timestamp and step_id When the device reconnects before the link expires Then the event is uploaded, deduplicated, and saved with acknowledged_at determined as device_timestamp if clock skew <= 5 minutes, otherwise server_received_at And the prep step completion state updates for all viewers and vendor notifications are sent per throttling rules
Link Expiration and Access Controls
Given a Household Share link has expired When any viewer attempts to open the link or acknowledge a prep step Then no new read receipts or acknowledgments are recorded and the viewer sees an expired-link message with instructions to request a new link And an internal "expired access attempt" event is logged without exposing viewer identifiers to vendor or other viewers And existing timeline entries remain visible to creator and vendor with no changes

Plain Status

Translates status badges into simple language with what’s happening now, who’s up next, and what the tenant can do. Removes ambiguity, lowers support load, and builds confidence at every stage.

Requirements

Status Mapping Engine
"As a tenant, I want clear plain-language updates that tell me what's happening now, who's responsible next, and what I can do so that I understand progress without contacting support."
Description

Maps internal workflow states (e.g., intake, triage, awaiting approval, scheduled, en route, in progress, completed, follow-up) to standardized Plain Status outputs composed of three parts: What’s happening now, Who’s up next, and What you can do. Supports role-based variants (tenant, owner, vendor), per-property customization, default templates, and versioned mappings with rollback. Integrates with FixFlow’s case model and event schema, provides graceful fallbacks for unknown states, and ensures safe defaults that avoid exposing sensitive information.

Acceptance Criteria
Tenant view for 'Awaiting Approval' shows Plain Status triple
Given a case with internal state "awaiting_approval", viewer role "tenant", and an active mapping for this state and role When the Plain Status is requested for the case Then the response contains exactly three non-empty fields: whatHappeningNow, whosUpNext, whatYouCanDo And each field equals the configured tenant variant for "awaiting_approval" with all placeholders resolved And no template tokens like "{{...}}" remain in any field And the response does not include the internal state key or workflow name
Owner default template is used when no property override exists (triage)
Given a case in state "triage" for property P with no owner-role override mapping and a system-wide default owner mapping exists When the Plain Status is requested by an owner for this case Then the output equals the default owner mapping for "triage" for all three fields And for a different property Q that also lacks an override, the output is identical And if a property override is later added for P, subsequent requests use the override instead of the default
Vendor per-property override applied for 'Scheduled'
Given property X has a vendor-role override mapping for state "scheduled" and property Y does not When a vendor requests Plain Status for a case at property X in "scheduled" Then the output matches the override mapping content exactly for whatHappeningNow, whosUpNext, and whatYouCanDo And when a vendor requests Plain Status for a case at property Y in "scheduled" Then the output matches the vendor default mapping for that state
Unknown internal state returns safe generic fallback
Given a case has internal state "dispatch_blocked" that has no configured mapping When any role requests the Plain Status Then the engine returns a safe generic triple: - whatHappeningNow: "We’re working on your request." - whosUpNext: "Our team" - whatYouCanDo: "No action needed right now." And the response status is 200 and the UI does not error And a single warning is logged per case-state pair without PII or internal identifiers
Versioned mapping rollback restores prior outputs
Given mapping version v1 exists and is active for tenant role in state "in_progress" and a new version v2 is published and activated with different content When an administrator triggers a rollback to v1 Then subsequent Plain Status requests for tenant in "in_progress" return v1 content within 2 seconds of the rollback action And v2 remains stored but marked inactive with audit metadata (actor, timestamp, fromVersion=v2, toVersion=v1) And an audit trail entry is written for the rollback action
Case events trigger timely Plain Status refresh
Given a case transitions from "scheduled" to "en_route" and emits event case.state.transitioned with caseId and newState=en_route When the event is received by the Status Mapping Engine Then Plain Status for all roles reflects the "en_route" mapping within 3 seconds And any cached prior outputs are invalidated for the case And no request made after 3 seconds returns the "scheduled" mapping
Plain Status excludes sensitive information
Given any role requests the Plain Status for any case and state When the engine composes whatHappeningNow, whosUpNext, and whatYouCanDo Then none of the fields contain: - internal workflow/state keys or IDs - personal data (emails, phone numbers, street addresses) - vendor legal names or direct contact details - dollar amounts or invoice references And this rule is enforced across defaults, overrides, and fallbacks
Context-Aware Message Composer
"As a property manager, I want status messages to automatically include relevant details like scheduled times and next steps so that tenants get actionable guidance without manual edits."
Description

Generates Plain Status messages by injecting live context (appointment windows, vendor ETA, parts on order, approval thresholds, SLA timers) into template tokens and selecting the best variant based on user role and available data. Enforces reading-level constraints and tone guidelines, applies redaction rules for sensitive fields, and uses deterministic fallbacks when data is missing. Outputs are consumable by the mobile web portal and notification channels while remaining consistent across channels.

Acceptance Criteria
Inject Live Context Into Plain Status for Scheduled Appointment
Given a tenant-facing template containing tokens {appointment_window}, {vendor_eta}, and {sla_remaining} And live context provides values for each token When the composer renders the message Then all tokens are replaced with human-readable, localized values (e.g., "Mon Sep 8, 1–3 PM", "about 35 minutes", "2h 15m left") And no unresolved tokens or curly-brace placeholders remain And all times are rendered in the property’s timezone And ETA is rounded to the nearest 5 minutes per formatting rules And the final text includes exactly one clear, actionable next step addressed to the tenant
Variant Selection by Role and Data Availability
Given a template set with role-scoped variants (Tenant, Landlord, Vendor) and each variant’s required fields map And a context payload with some fields present and others missing When the composer selects a variant Then it chooses the highest-priority variant whose required fields are fully satisfied by the available data And if none qualify, it uses the deterministic global fallback variant And role-based exclusions are enforced (e.g., cost estimates hidden from Tenant) And the selection is deterministic: identical inputs yield the same variant ID and content hash And the selection rationale is emitted as metadata (variant ID, satisfied fields, missing fields)
Reading Level and Tone Compliance
Given style guidelines requiring a reassuring, plain tone and a maximum 8th-grade reading level When a message is generated for any role and channel Then the computed Flesch-Kincaid Grade Level is <= 8.0 And average sentence length is <= 18 words; no sentence exceeds 28 words And banned/jargon terms list matches 0 occurrences And second-person voice is used for tenant-facing messages And capitalization, punctuation, and emoji usage follow the tone guide
Sensitive Data Redaction and Field-Level Privacy
Given a context payload that may include sensitive fields (e.g., vendor full name, direct phone, GPS location, internal approval thresholds, cost breakdown) When composing a tenant-facing message Then sensitive fields are redacted or aliased per the role privacy map (e.g., "your technician" instead of a full name; no direct phone numbers) And currency amounts and internal thresholds do not appear for tenants And outputs, logs, and analytics payloads contain no raw PII for prohibited roles (regex checks for phone, email, GPS, currency pass 100%) And a redaction audit trail is attached as metadata (fields redacted, rule IDs)
Deterministic Fallbacks When Data Is Missing or Stale
Given one or more required tokens (e.g., {parts_status}) are missing or marked stale beyond 15 minutes When rendering the message Then the composer uses the predefined fallback text for the scenario that avoids speculative commitments and includes a clear next step And all unresolved tokens are removed (no "TBD" or empty braces) And identical inputs produce identical fallback text and content hash And a machine-readable reason code (e.g., MISSING_PARTS_STATUS) is attached for analytics
Cross-Channel Consistency and Delivery Constraints
Given the same status event must be delivered via portal, email, and SMS When the composer renders channel-specific outputs Then the semantic content (facts, commitments, dates, next action, actor) is consistent across channels And channel constraints are respected (SMS <= 160 chars per segment with (1/2), (2/2) indicators; email subject <= 78 chars; portal supports rich text without altering meaning) And deep links and identifiers are identical across channels; tracking params are preserved And a shared canonical message ID and version are emitted for all channels
Landlord Approval Threshold Messaging
Given the role is Landlord and context includes approval_threshold and estimated_cost When estimated_cost >= approval_threshold Then the message includes a clear call-to-approve with formatted cost, concise work summary, and a one-click approval link And when estimated_cost < approval_threshold, the message states the work is auto-approved and no action is required And for Tenant role, neither approval_threshold nor estimated_cost are disclosed And currency formatting matches the property locale and uses max two decimals And the approval link includes a signed token and 24-hour expiry; portal access via authentication remains available after link expiry
Multi-Role Views & Permissions
"As a vendor, I want a clear view of what I need to do next without tenant-specific details so that I can act quickly while protecting privacy."
Description

Delivers tailored Plain Status content to tenants, owners, and vendors according to role-based permissions. Ensures tenants see actionable guidance without cost or sensitive details, owners see approval and budget cues, and vendors receive operational next steps. Implements permission checks, content filtering, admin override with audit logging, and deep links to permitted actions (approve, reschedule, add photos) across the portal and email/SMS surfaces.

Acceptance Criteria
Tenant Plain Status hides sensitive details and shows allowed actions
Given an authenticated tenant opens a maintenance request that contains cost estimates, vendor rates, and owner notes When the tenant views Plain Status in the portal and from an email/SMS preview page Then the status text includes what’s happening now, who’s up next, and what the tenant can do And no dollar amounts, owner notes, vendor rates, or internal comments are rendered And only tenant-permitted actions (e.g., add photos, update access window, contact support) are displayed And all action deep links in messages route to tenant-permitted pages only And attempts to access non-permitted data or actions return HTTP 403 without leaking field names and are audit logged
Owner Plain Status provides approval and budget cues with one-click approval
Given an authenticated owner opens a request awaiting approval with an estimated amount When the owner views Plain Status via the portal or a one-click approval deep link Then the status text indicates the owner is up next and shows the estimated amount and approval deadline And an Approve button is visible with the amount and budget context And clicking Approve records approver ID, timestamp, amount, and updates status and next actor And success notifications are sent per settings to tenant and vendor And owners lacking permission for the property receive HTTP 403 and the attempt is audit logged
Vendor Plain Status shows operational next steps without financials
Given an authenticated vendor assigned to a request opens the job When the vendor views Plain Status in the portal or via SMS deep link Then the status text lists operational next steps (schedule, access notes, required photos) without any cost or approval details And Add Photos and Propose Reschedule actions are available And executing these actions updates the request timeline and role-specific status messages for tenant and owner And vendors not assigned to the job receive HTTP 403 and the attempt is audit logged
Deep link access control and authentication flow across surfaces
Given a signed deep link token is generated for a role-specific action (approve, reschedule, add photos) When the link is opened by an authenticated user with matching role and permissions Then the user is taken directly to the action screen and the token is marked used as appropriate When the link is opened by an unauthenticated user Then the user is prompted to authenticate and, upon success, routed to the action if permitted When the link is opened by an authenticated user with a different role or lacking permissions Then a generic access denied screen is shown (HTTP 403) without revealing resource existence And expired or replayed tokens show a link expired screen with a path to request a new link And all link opens are audit logged with actor (if known), role, request ID, surface, outcome, and timestamp
Admin override with audit log for cross-role view and actions
Given an authenticated admin with override privileges selects “View as [Role]” on a request When override mode is activated and an optional reason is entered Then the UI clearly indicates override mode and the target role And only the target role’s permitted fields and actions are shown And all viewed fields and actions taken in override are audit logged with admin ID, target role, reason, request ID, and timestamps And disabling override returns the admin to their normal permissions
Role-based content filtering engine enforces field-level permissions
Given a role-based field permission matrix is configured for tenant, owner, and vendor When Plain Status content is generated for each role across portal, email, and SMS Then only whitelisted fields per role are used to render text and actions And blacklisted fields are never sent to the client in payloads or templates And automated tests verify the matrix by role and surface, failing on any disallowed field emission And changes to the matrix require admin privileges and are captured in audit logs
Localization & Accessibility
"As a bilingual tenant, I want to see status updates in my preferred language and accessible format so that I can understand and trust the process."
Description

Internationalizes Plain Status templates using translation keys and supports user language preferences, right-to-left scripts, and locale-aware formatting for dates, times, and currency. Ensures accessibility via aria-live announcements for dynamic updates, high-contrast compliant badges, keyboard navigation, and content readability targets suitable for mobile. Provides translation QA workflows and fallback to English when translations are incomplete.

Acceptance Criteria
Language Preference, Fallback, and Translation QA Workflow
Given a user profile language set to es-ES and all translation keys exist for es-ES When the Plain Status component loads Then 100% of visible strings resolve from es-ES keys with zero fallback occurrences and no mixed-language phrases. Given a user profile language set to fr-FR with two missing translation keys When the component loads Then only the missing keys fall back to en-US while all other strings resolve from fr-FR; And an analytics event "i18n_fallback" is emitted with locale="fr-FR" and a list of missing key IDs. Given a CI pipeline run for Plain Status translations When translation files contain missing keys, extra keys, or placeholder mismatches Then the translation QA check fails the build and outputs a report enumerating keys, locales, and discrepancies. Given pseudo-localization mode is enabled When the Plain Status page renders Then all translatable strings display with ≥30% length expansion and bracket markers without layout breakage, overlap, or truncation.
RTL Layout Support for Plain Status
Given the locale is ar-SA or he-IL When the Plain Status UI renders Then the document direction is set to rtl, status badges and chevrons/icons are mirrored appropriately, and reading/focus order matches the visual order. Given mixed LTR content (e.g., English property names) within an RTL locale When rendering status lines Then bidirectional isolation (e.g., bdi/dir attributes) prevents character order corruption and punctuation remains adjacent to the correct tokens. Given the user switches between an LTR and an RTL locale When toggling language Then layout reflows without page reload and no visual artifacts from the previous direction persist.
Locale-aware Date, Time, and Currency Formatting
Given a status message contains date and time placeholders When the locale is en-US Then dates format like "Jan 5, 2026" and times use a 12-hour clock with AM/PM according to the portal-configured time zone. Given the same message and the locale is de-DE When rendering Then dates format like "05.01.2026" and times use a 24-hour clock; currency values display with the Euro symbol and locale-appropriate spacing and decimal separators. Given a tenant locale en-GB and an estimate amount When displaying currency Then the organization-configured currency is shown with correct symbol placement, grouping, and decimal separators for en-GB. Given an unsupported or malformed locale value When rendering Then formatting gracefully falls back to en-US without runtime errors.
Aria-live Announcements for Dynamic Status Updates
Given a visible Plain Status region with aria-live="polite" When the backend status changes while the page is open Then a single concise announcement (<=120 characters) is announced by assistive technologies within 1 second and no duplicate announcements occur for the same change. Given multiple status changes occur within 3 seconds When announcements are generated Then changes are coalesced into one message to reduce verbosity. Given NVDA on Windows (Chrome), VoiceOver on iOS (Safari), and TalkBack on Android (Chrome) When the status updates Then each screen reader announces the update and focus is not forcibly moved; interactive focus remains where the user left it. Given a collapsible status details control When it is expanded or collapsed via keyboard Then aria-expanded updates and is announced by the screen reader.
High-Contrast Badges WCAG 2.1 AA Compliance
Given the default theme When measuring contrast between badge text/icon and background Then the contrast ratio is >= 4.5:1 for normal text and >= 3:1 for large text/icons, meeting WCAG 2.1 AA. Given the user preference prefers-contrast: more or a forced-colors environment When rendering badges Then outlines/fills adapt to maintain the same contrast thresholds and non-color cues (text/icons/shape) communicate status without relying on color alone. Given dark mode is enabled When rendering badges Then all contrast thresholds remain satisfied and hover/focus states also meet the required ratios.
Keyboard Navigation and Focus Management
Given a keyboard-only user When tabbing through the Plain Status area Then every interactive element (e.g., expanders, links, actions) is reachable in logical order, focus is clearly visible with >=3:1 contrast, and no keyboard traps occur. Given an actionable badge or "View details" control When Space or Enter is pressed Then the intended action triggers; When Escape is pressed from an open popover/modal Then it closes and focus returns to the invoking control. Given assistive technology users navigate via landmarks and headings When accessing the page Then the status section is exposed via a named region/landmark and contains a level-appropriate heading for quick navigation.
Readability Targets for Mobile Plain Language
Given English source templates for Plain Status When the readability linter runs in CI Then each string scores at or below 8th-grade reading level (Flesch-Kincaid Grade <= 8) and average sentence length is <= 20 words. Given numeric and procedural information in messages When rendering Then wording avoids jargon and abbreviations and uses user-centered verbs; units and numerals follow locale norms. Given a mobile viewport width of 320px When displaying status messages Then lines wrap without truncation or horizontal scrolling; critical information appears within the first 120 characters and no single line exceeds ~60 characters on average.
Real-time Event Updates & Notifications
"As a tenant, I want to be notified immediately when the status changes so that I can plan around appointments and next steps."
Description

Updates Plain Status in real time by subscribing to workflow events from triage, approvals, scheduling, and vendor systems. Applies deduplication and debouncing to prevent notification spam, ensures idempotent updates, and pushes changes via in-portal updates, email, and SMS with deep links. Respects user notification preferences, implements retry and backoff, and writes an audit trail of status changes for compliance and support.

Acceptance Criteria
In-Portal Plain Status Updates Within Seconds
Given a tenant is viewing a maintenance request in FixFlow When a triage, approval, scheduling, or vendor event is received for that request Then the Plain Status content updates in-portal without page refresh within p95 5s and p99 30s And the content includes exactly: what’s happening now, who’s up next, and what the tenant can do And the status timestamp is shown in the tenant’s local timezone And the previous status is preserved in the request’s history
Email and SMS Notifications With Deep Links
Given a status change configured for tenant notification and the tenant has at least one channel enabled (email or SMS) When the event is processed Then notifications are sent only via the enabled channels And each message contains a deep link that resolves to the exact request’s Plain Status view after authentication And deep links are single-use, expire in 24 hours, and contain no PII in the URL And p95 time from event receipt to channel provider accept is <= 60s And click-throughs are recorded with correlation to the originating event
Deduplication and Debouncing of Rapid Events
Given multiple identical or semantically equivalent events (same event_id or same status payload) arrive within a 2-minute window for the same request When processing these events Then only one user-visible Plain Status update and one multi-channel notification (per enabled channel) are produced And the audit trail contains a single status change record with a deduplication count And no additional notifications are emitted for the suppressed events
Idempotent Event Handling
Given an event with a previously processed event_id is re-delivered one or more times When the event is processed again Then the stored state, Plain Status, and audit trail remain unchanged And the event processor returns a 2xx acknowledgment without side effects
Channel Failure Retries With Backoff
Given an email or SMS send attempt fails with a retriable error (timeout or 5xx) When sending a notification Then the system retries up to 5 times with exponential backoff of 1, 2, 4, 8, and 16 minutes And no more than one successful notification per channel is delivered to the user And a non-retriable 4xx error results in no retries And all attempts and outcomes are recorded in the audit trail
Immutable Audit Trail for Status Changes
Given any Plain Status change is applied When writing the audit record Then the record includes: request_id, event_id, previous_status, new_status, actor/system, received_at, applied_at, channels_attempted, channel_outcomes, and correlation_id And audit records are append-only (no update/delete operations) and time-ordered And records are queryable by request_id and date range and exportable to CSV And audit retention is at least 24 months
Ordering and Integrity of External Vendor Events
Given external vendor webhooks may arrive out of order or be delayed When applying scheduling or work-order events Then Plain Status never regresses to an earlier state due to out-of-order delivery And later events supersede earlier ones using sequence or timestamp rules with tie-breaker on vendor-provided version And events failing signature verification or schema validation are rejected and logged without side effects
Admin Configuration Console
"As an operations admin, I want to configure and preview Plain Status messages without engineering help so that I can keep language clear and up to date."
Description

Provides an internal console for operations to manage status mappings, templates, role variants, and translations without engineering involvement. Includes live-data preview, A/B variant toggles, publish/version control with rollback, and granular access permissions. Offers a test harness to simulate workflow events and validate resulting messages before publishing to production.

Acceptance Criteria
Create Status Mapping with Live-Data Preview
Given an Ops Admin is logged into the Admin Configuration Console with access to Portfolio A When they create a new status mapping "Awaiting Vendor" with Now, Next, TenantAction, and Who’s Up Next fields for Tenant, Landlord, and Vendor roles and insert variables {vendor_name} and {eta} Then the console validates required fields and variable references and displays a live preview using the most recent open work order in Portfolio A And the preview renders role-specific copy with variables resolved and channel-specific formatting for In‑App, Email, and SMS And unsaved changes are clearly indicated and do not affect production until published
Configure A/B Template Variants and Traffic Split
Given a status mapping "Scheduled" exists with template variants A and B for the Tenant role When I enable A/B testing and set the traffic split to 70% A and 30% B Then the preview simulator shows a distribution of 70±5% for A and 30±5% for B over 1,000 simulated requests And tenants are pinned to a variant for 30 days or until the variant is disabled And disabling any variant immediately routes 100% of traffic to the remaining active variant
Publish With Versioning and Rollback
Given I have Publish permission and all validations are green When I publish the current draft with a change note Then a new version ID with author, timestamp, and diff summary is created and added to version history And the version becomes active in production within 60 seconds When I initiate a rollback to the previous version Then the previous version becomes active within 60 seconds and an audit entry records who, when, what, and why
Translations Management With Completeness and Fallbacks
Given required locales are configured as en and es, with optional locale fr When I add or update translations for a template Then the system reports missing keys per locale and blocks publish if required locale coverage is <100% And optional locales may publish with missing keys only if a fallback locale is specified And preview renders each locale correctly with fallbacks and never shows raw keys or placeholder tokens
Granular Access Permissions and Audit Trail
Given role permissions are set as Admin (full), Ops (create/edit/view, no publish/rollback), and Viewer (view-only) When a user attempts an action beyond their permissions (e.g., Ops tries to publish) Then the action is blocked with a clear reason and no changes are applied And all create, edit, publish, rollback, and permission changes are logged with user, timestamp, entity, before/after diff, and optional rationale
Test Harness: Simulate Events and Validate Outputs
Given the test harness supports events WorkOrder.Created, Appointment.Scheduled, and Job.Completed across channels In‑App, Email, and SMS When I simulate Appointment.Scheduled for Work Order WO-123 with role Tenant and locale es-MX Then the generated plain status includes Now, Next, TenantAction, and Who’s Up Next fields populated and variables resolved And the harness flags any broken links, missing variables, or SMS messages exceeding 160 characters And simulations create no production side effects and cannot be published directly And publishing is allowed only after all validations pass or waivers with justification are recorded

Dynamic Caps

Automatically adjusts auto-approval limits by issue type, unit class, seasonality, and historical spend for that property. Keeps caps tight where risk is high and flexible where routine, reducing manual tweaking and maximizing safe auto-approvals.

Requirements

Adaptive Cap Engine
"As a property manager, I want caps to adjust automatically based on context so that I can safely maximize auto-approvals without constant manual tweaking."
Description

Compute dynamic auto-approval caps per property by issue type, unit class, and season using historical spend, vendor rate cards, and risk tiers. Apply configurable floors/ceilings, inflation indices, and regional multipliers. Recalculate on a scheduled cadence (e.g., nightly) and on-demand after major data changes. Provide deterministic outputs, safe defaults when data is sparse, and hard-no lists for life/safety issues. Integrate into FixFlow’s triage so each work order receives a real-time cap used for auto-approval decisions.

Acceptance Criteria
Nightly and On-Demand Recalculation
Given the local property time is 02:00 AM and the nightly schedule is enabled When the scheduled recomputation job runs Then caps for all active properties, issue types, unit classes, and seasons are recalculated and persisted within 90 minutes And a success metric and an audit record per property are emitted And previously active caps are superseded by the new version Given a major data change occurs (vendor rate card, regional multiplier, risk tier, or season boundary updated) When an on-demand recomputation is triggered via event or API Then affected scopes are recalculated and available to triage within 10 minutes And prior caps for those scopes are marked superseded to prevent use
Deterministic Cap Calculation Outputs
Given an identical snapshot of inputs (historical spend set, vendor rate cards, risk tier, multipliers, floors/ceilings, season, unit class) and engine version When the cap is calculated multiple times Then the numeric result is identical each time And the value is rounded to the nearest whole currency using half-up rounding And the calculation breakdown lists the same ordered factors and values
Safe Defaults with Sparse Data
Given fewer than 10 matching historical work orders within the last 24 months for the property–issue–season–unit class When computing the cap Then the engine uses the regional vendor rate card median for the issue type as the base And if no rate card exists, the engine uses the configured global default cap for the issue type And floors and ceilings are enforced on the final result And the output includes source = "defaulted" and the fallback path used
Hard-No Life/Safety Exclusions
Given a work order with an issue type on the life/safety hard-no list When triage requests a cap Then the engine returns auto-approval disabled with cap = null and reason = "HARD_NO" And triage requires manual approval regardless of other settings And an audit entry records the exclusion code and issue type
Application of Floors, Ceilings, Inflation, and Regional Multipliers
Given a configured percentile (default P50) defines the baseline from historical spend for the scope When inflation index I and regional multiplier R are applied Then BaseAdjusted = round(Baseline * I * R, 2) And the final cap = clamp(BaseAdjusted, Floor, Ceiling) And the final cap is always within [Floor, Ceiling] And clamping occurs after all multipliers are applied
Risk Tier and Unit Class Adjustments
Given configured risk tier and unit class multipliers exist for the scope When calculating the cap Then CombinedMultiplier = RiskMultiplier × UnitClassMultiplier And High risk multipliers are ≤ 1.0, Medium = 1.0, Low ≥ 1.0 as configured And CombinedMultiplier is applied before inflation and regional multipliers And the final cap respects configured floors and ceilings
Triage Integration and Auto-Approval Decision
Given a work order not on the hard-no list and a computed cap for its property, issue type, unit class, and season When triage evaluates an estimated or predicted cost E Then if E ≤ cap the work order is auto-approved and routed to a preferred vendor And if E > cap the work order is sent to manual review And cap computation latency is ≤ 300 ms at the 95th percentile And the decision, cap version ID, and inputs are logged for audit
Historical Spend Feature Store
"As an operations analyst, I want trustworthy, up-to-date cost features so that cap calculations reflect real spending patterns at each property."
Description

Ingest and normalize work order costs, invoices, unit attributes, vendor rates, and seasonal calendars into a queryable store powering Dynamic Caps. Clean outliers, map issue taxonomy, and compute features like median/percentile cost by issue type × unit class × season, recency-weighted trends, and volatility. Enforce freshness SLAs, schema versioning, and data lineage to ensure reproducible cap calculations.

Acceptance Criteria
Ingestion and Normalization (Backfill, Incremental, Idempotent)
- Pipeline ingests sources (work orders, invoices, unit attributes, vendor rates, seasonal calendars) on schedule and on-demand with authenticated access. - Incremental loads process only records changed since the last successful watermark; watermark persisted transactionally. - Backfill loads N=5 years of history with overall ingestion error rate < 0.1% and zero schema-violating records. - Normalization standardizes currency to USD (latest available FX snapshot at processing time), timestamps to UTC ISO-8601, numeric types, and categorical enums to canonical sets with schema validation pass rate ≥ 99.9%. - Idempotency: re-running the same job input produces identical target state (no duplicate primary keys; checksums match per partition). - Referential integrity: ≥ 99.99% of work orders resolve to a valid unit and (if present) a vendor; unresolved records quarantined with reason codes.
Outlier Detection and Treatment
- Cost outliers are flagged per (issue_type, unit_class, season, property) using robust MAD with threshold ≥ 3.5 median absolute deviations or IQR with k=1.5, whichever is stricter. - Outlier policy is applied: excluded from primary distribution features and included in parallel "raw" metrics; each flagged record carries reason_code and method_version. - Global outlier flag rate ≤ 5% and ≤ 15% per segment; breaches trigger alerts and report. - Capping (if configured) limits contribution of extreme values to p99 of in-segment historical costs; caps are versioned and auditable. - Validation suite proves consistency on synthetic datasets with planted outliers (precision and recall ≥ 0.9 against ground truth).
Issue Taxonomy Mapping Integrity
- Free-text issue descriptions map deterministically to canonical issue_type with ≥ 98% coverage; unmatched routed to "Other" with reason and source text retained. - Mapping tables/functions are versioned; each mapped record stores mapping_version and confidence score (if ML-assisted) ≥ 0.8 for auto-accept. - Manual overrides pathway supported; override rate < 1% and emits audit entries referencing operator ID and timestamp. - Changes to mappings trigger scoped recomputation of affected features and increment mapping_version; no orphaned categories remain after deploy (0 dangling keys).
Feature Computation: Cost Distribution by Segment
- For each (issue_type × unit_class × season × property), compute p25, median (p50), p75, p90 of cleaned costs with minimum sample size n ≥ 20; if n < 20, fallback hierarchy applies: property→market/region→portfolio-global. - Each feature vector includes sample_size, fallback_level, and computation_window metadata; zero-division or empty sets return nulls with explicit reason. - Currency normalization applied prior to aggregation; FX snapshot_id captured in feature metadata. - Features materialized to the store within 30 minutes of successful ingestion completion; availability SLO: p95 ≤ 30 min, p99 ≤ 60 min. - Query p95 latency ≤ 200 ms and p99 ≤ 500 ms for single-segment lookups via API/SQL.
Recency-Weighted Trend and Volatility
- Compute EWMA trend for costs with half-life = 90 days per (issue_type × unit_class × property); volatility as EWMA standard deviation over the same window. - Require effective sample size ESS ≥ 10; otherwise fall back to unweighted rolling metrics (window=365d) or higher-level segment per fallback hierarchy. - Output includes trend_value, volatility_value, half_life_days, ess, and fallback_level; values update at least daily. - Backtests on last 24 months show stable behavior: drift in trend between adjacent days p95 ≤ 10% absent new data spikes; volatility non-negativity and finite checks always pass (0 NaN/Inf). - Unit tests validate invariants on synthetic monotonic and constant series (trend tracks direction; volatility ~0 for constants).
Freshness SLA, Schema Versioning, and Monitoring/Alerts
- Freshness: from source data arrival detection to features available, p95 ≤ 60 minutes and p99 ≤ 120 minutes daily; breaches page on-call and create incident tickets within 5 minutes. - Data completeness monitors track row counts, null rates, and key cardinalities per dataset vs 7-day baselines; anomaly z-score > 3 triggers warn, > 4 triggers page. - Schema versioning: breaking changes bump major (MAJOR.MINOR.PATCH); additive changes bump minor; last 2 MAJOR versions are simultaneously readable; consumers can request ?version=MAJOR.MINOR via API/SQL view. - Migrations include forward-fill scripts, backfill plans, and rollback; contract tests ensure backward compatibility for minor releases (100% pass) and gated releases for major. - Status endpoint exposes pipeline health, last_success_at, data_lag_minutes, current_versions; updated each run.
Data Lineage and Reproducibility
- Every feature record stores lineage: source_dataset_versions, job_run_id, code_commit_sha, mapping_version, fx_snapshot_id, processing_started_at/ended_at. - Re-running with identical inputs and code produces identical outputs within numeric tolerance (abs diff ≤ 1e-6); partition-level checksums match 100%. - Lineage API returns full provenance for any feature key within ≤ 300 ms p95; audit logs retained ≥ 13 months, exportable as CSV/JSON. - Reproducibility test suite runs nightly on sampled partitions (n ≥ 100) with 100% deterministic match rate; discrepancies auto-open tickets with attached diffs.
Rules & Overrides Console
"As a portfolio admin, I want to set and preview policy rules so that Dynamic Caps stay aligned with my risk tolerance and budget constraints."
Description

Provide an admin UI to configure global/property-level policies: floors and ceilings, excluded issue types, freeze windows, sensitivity settings, vendor-specific caps, and exception windows for events (e.g., cold snaps). Include bulk edit, search, and role-based access. Offer a preview panel that shows the resulting cap per scenario before publishing, with one-click rollout and rollback.

Acceptance Criteria
Global and Property-Level Floors and Ceilings Configuration
Given I am an Admin on the Rules & Overrides Console When I create global floors and ceilings per issue type and unit class and save as Draft Then the Draft is versioned and not active Given a global cap exists When I add a property-level override for a specific property and save as Draft Then Preview for that property uses the property override and Preview for other properties uses the global cap Given a Draft is ready When I click Publish Then the version becomes Active and both Preview and runtime cap computation use the new values Given a version is Active When I perform a Rollback to the previous version Then the previous version becomes Active and the rolled-back version no longer affects Preview or runtime computation
Preview Panel Accuracy Before Publish
Given one or more policy changes are saved as Draft When I open the Preview panel and select a scenario (property, unit class, issue type, vendor, seasonality) Then the panel displays the resulting effective cap for that scenario based on the Draft Given the same scenario is computed by the runtime cap service using the Draft When I compare the values Then the Preview value equals the runtime-computed value Given I change any scenario input (e.g., property or vendor) When I update the selection Then the Preview recalculates the effective cap without requiring a page reload Given the scenario falls under an Excluded issue type or an active Freeze window When I preview that scenario Then the panel indicates Auto-approval disabled and no numeric cap is applied
Excluded Issue Types Configuration
Given I add one or more issue types to the Global Exclusions list and Publish When tenants submit those issue types for any property Then auto-approval is disabled for those types and Preview reflects the disabled state for all properties Given I add additional excluded issue types at the Property level and Publish When I preview the affected property Then only that property shows auto-approval disabled for those additional types Given an issue type is excluded for a scope When a user attempts to set floors or ceilings for that issue type within the scope Then the UI prevents cap entry and displays that the issue type is excluded
Time-Based Controls: Freeze and Exception Windows
Given I configure a Freeze window for a property or group with a start and end timestamp and Publish When the current time falls within the Freeze window Then auto-approvals are disabled for that scope and Preview shows auto-approval disabled within the window and normal behavior outside the window Given I configure an Exception window (e.g., cold snap) that specifies affected scope and adjusted caps for selected issue types and Publish When the current time falls within the Exception window Then the effective caps for the specified issue types use the exception values for that scope and return to baseline after the window ends without manual action
Vendor-Specific Caps Application
Given a vendor-specific cap is defined for Vendor V for a selected scope (property/issue type) and Published When I preview a scenario with Vendor V selected within that scope Then the effective cap equals the defined vendor-specific cap Given no vendor-specific cap exists for Vendor V in the selected scope When I preview the scenario Then the effective cap uses the applicable base cap (property or global) Given a vendor-specific cap is removed and changes are Published When I preview the affected scenarios Then the effective cap reverts to the applicable base cap
Bulk Edit with Search and Filters
Given I search and filter rules by property, unit class, issue type, vendor, and status When results are returned Then I can multi-select N items and open Bulk Edit Given I apply a Bulk Edit to update floors and/or ceilings on the selected N items and save as Draft When I review the changes Then the Draft shows the count of items to be updated and the specific fields changed Given the Bulk Edit Draft is ready When I Publish Then only the selected N items are updated and Preview reflects the new caps for those items Given one or more selected items have validation errors (e.g., ceiling below floor) When I attempt to save or publish Then the UI highlights the invalid items and blocks the action until corrected
Role-Based Access Control
Given role-based permissions are configured When a user with Viewer role accesses the console Then they can search and Preview but cannot create Drafts, Publish, Rollback, or perform Bulk Edit (actions disabled) Given a user with Property Manager role assigned to specific properties accesses the console When they edit policies for their assigned properties Then they can save Drafts and Publish within those property scopes but cannot modify or publish Global policies Given a user with Admin role accesses the console When they perform any action Then they can create Drafts, Publish, Rollback, configure Global and Property policies, manage vendor caps, exclusions, and time-based controls
Cap Monitoring & Guardrails
"As a property manager, I want proactive alerts and automatic safeguards so that I can prevent overspend and catch anomalies before they impact budget."
Description

Continuously monitor cap utilization, anomaly spikes, and auto-approval hit rates. Trigger alerts when caps are exceeded frequently, when spend deviates from forecast, or when high-risk categories are near thresholds. Provide automatic safeguards to pause or tighten caps on anomalies and notify stakeholders via in-app, email, and SMS. Surface a dashboard with trends and drill-downs per property.

Acceptance Criteria
Frequent Cap Exceedances Alerting
Given Dynamic Caps is enabled for a property and alert recipients are configured When any cap (per issue type and property) is exceeded 3 or more times within a rolling 24-hour window Then the system generates a "Frequent Cap Exceedance" alert with severity "High" within 2 minutes of the third exceedance And the alert is sent via in-app, email, and SMS to configured recipients And the alert payload includes property, issue type, cap amount, count of exceedances, timestamps, and links to the last 5 related tickets And duplicate alerts for the same property+issue type are suppressed for 2 hours unless the exceedance count increases by 2 or more And an immutable audit record is created capturing the trigger, recipients, and delivery outcomes
Spend Deviation From Forecast Alerting
Given a 7-day rolling forecast exists for spend by property and category When actual maintenance spend exceeds the forecast by 20% or more and by at least $1,000 within the rolling 7-day window Then the system generates a "Spend Deviation" alert with severity "Medium" within 10 minutes of detection And the alert includes variance percentage, absolute variance, affected properties/categories, and a sparkline for the 7-day period And users can acknowledge the alert; acknowledged alerts stop repeat notifications for 24 hours And an audit record is stored with the variance details and acknowledgment status
High-Risk Threshold Proactive Notification
Given a category is marked High Risk and has an active cap When utilization reaches 90% of the cap OR projected utilization indicates a breach within the next 72 hours based on a trailing 14-day trend Then the system sends a proactive "Threshold Nearing" notification with severity "High" And the notification includes recommended actions (pause auto-approvals, tighten cap by a suggested percentage) and a one-click link to apply And if the cap is adjusted via the link, the change is applied within 60 seconds and reflected in the dashboard And an audit log records the recommendation, action taken, actor, and timestamp
Automatic Safeguards on Anomaly Spikes
Given anomaly detection is active for cap utilization and spend velocity When the z-score for spend velocity per property+category is ≥ 3 OR cap exceedances are ≥ 5 within any 6-hour window Then the system automatically pauses auto-approvals for the impacted property+category and tightens the cap by 25% (minimum $50 decrement) for 24 hours And in-app, email, and SMS notifications are sent immediately to stakeholders with the reason and actions taken And resumes require explicit user confirmation with role-based permission And all automated changes and confirmations are captured in an immutable audit trail
Auto-Approval Hit Rate Monitoring Alerts
Given a target auto-approval hit rate band is configured per property (e.g., 70%–90%) When the 7-day rolling hit rate falls below the lower bound for 48 consecutive hours with at least 20 auto-approval attempts in that period Then a "Hit Rate Drop" alert with severity "Medium" is generated and sent to configured recipients And the alert includes top 3 categories impacting the drop and links to recommended cap adjustments And repeated alerts are suppressed for 24 hours after acknowledgment
Cap Monitoring Dashboard Trends and Drill-Down
Given a user with the Property Manager role accesses the Cap Monitoring dashboard When they filter by property, issue type, and date range up to the past 90 days Then the dashboard displays charts for cap utilization, exceedance counts, auto-approval hit rate, and spend vs. forecast with data freshness ≤ 15 minutes And 90th percentile page load time is ≤ 2 seconds for default date range (last 30 days) And clicking a chart segment opens a drill-down list of related tickets with cap, spend, and approval outcome details And users can export the current view to CSV within 10 seconds
Multi-Channel Alert Delivery and Preferences
Given a user has verified email and phone and has set alert preferences for cap monitoring When any alert is generated by the system Then the alert is delivered via in-app, email, and/or SMS according to the user's preferences within defined SLAs (in-app ≤ 1 min, email ≤ 5 mins, SMS ≤ 2 mins) And delivery is de-duplicated so the user receives at most one notification per channel per alert And if SMS delivery fails, the system retries once and falls back to email with a failure note And delivery outcomes are recorded per channel and visible in the alert detail view
Stakeholder Escalation on Unacknowledged Critical Alerts
Given an alert with severity "High" is generated When no recipient acknowledges the alert within 30 minutes Then the system escalates to the next-tier stakeholders (e.g., portfolio owner) via all channels and marks the alert as "Escalated" And a second escalation occurs at 2 hours if still unacknowledged And escalations and acknowledgments are time-stamped in the audit log
What-if Simulation & Backtesting
"As a product owner, I want to model the impact of cap settings so that I can choose configurations that maximize safe auto-approvals and budget control."
Description

Enable users to simulate policy changes (e.g., sensitivity, floors/ceilings) and view projected auto-approval rates, cost exposure, and vendor dispatch impact. Provide backtesting against historical periods to quantify savings, approval accuracy, and tenant SLA adherence. Export scenario reports to share with stakeholders before publishing changes.

Acceptance Criteria
Simulate Dynamic Caps and View Projections
Given a property and its baseline Dynamic Caps policy are loaded And the user with edit permissions adjusts cap sensitivity, floors, and ceilings by issue type, unit class, and season When the user clicks Run Simulation Then the system computes projected auto-approval rate, total projected cost exposure, and vendor dispatch counts by preferred vendor And returns results within 5 seconds for datasets up to 10,000 historical tickets And displays both aggregate and per-segment (issue type, unit class, season) projections And clearly indicates the baseline versus scenario deltas (absolute and %).
Backtest Policy Against Historical Periods
Given the user selects a property or portfolio and a historical period up to 24 months And chooses inclusion filters (issue types, unit classes) and data completeness rules When the user runs Backtest Then the system outputs: estimated savings ($), approval accuracy (% of simulated auto-approvals whose final invoice stayed at or below the cap), tenant SLA adherence change (pp), and counts of tickets evaluated/excluded And results are reproducible for the same inputs And the run completes within 2 minutes for up to 20,000 tickets And numeric outputs match a validated reference calculation within ±1% for counts and ±0.5% for cost totals.
Compare Multiple Scenarios and Baseline
Given at least two configured simulations and the baseline are selected When the user opens Compare Then metrics are shown side-by-side with absolute and % deltas normalized to the same denominator And any metric exceeding configured risk thresholds (e.g., cost exposure increase >10% or approval accuracy decrease >2pp) is flagged And the user can pin a baseline and export the comparison view.
Export Scenario and Backtest Report
Given a completed simulation or backtest result is in view When the user selects Export and chooses PDF or CSV Then a report is generated within 30 seconds containing: inputs (parameters, filters, date ranges), methodology notes, key metrics, segment breakdowns, and baseline deltas And CSV includes machine-readable columns with headers and UTC timestamps; PDF includes charts and a summary And personally identifiable tenant data is excluded And a secure download link is provided and remains valid for 7 days.
Publish Policy Changes with Audit and Rollback
Given the user has Publisher role and a scenario is selected for publish When Publish is initiated Then the system displays an impact summary (key metrics and risk flags) and requires explicit confirmation (type-to-confirm) And upon confirmation, the new policy is applied only to the selected properties within 5 minutes And an audit log entry is recorded (who, what, when, before/after parameters, scenario ID) And a one-click rollback to the previous policy is available for 30 days.
Model Vendor Dispatch Impact and Capacity Flags
Given preferred vendor mappings and optional capacity per vendor/day are configured When a simulation or backtest runs Then the system outputs expected dispatches per vendor and flags capacity overages based on configured limits And if capacity data is missing, the UI displays Unknown capacity and excludes it from overage calculations And dispatch counts per vendor match underlying projections within ±1 dispatch for the test dataset And results can be exported in CSV with vendor_id, vendor_name, expected_dispatches, capacity, overage_flag.
Policy Audit & Versioning
"As a compliance reviewer, I want a complete audit trail of cap policies and decisions so that I can verify approvals and resolve disputes confidently."
Description

Maintain immutable version history of cap policies and computed outputs, including who changed what and why, with timestamps and property scope. Support diff views between versions, time-travel lookups for any work order decision, and exportable audit logs (CSV/JSON) for compliance and vendor disputes.

Acceptance Criteria
Immutable Version History with Who/What/Why/When and Property Scope
Given a cap policy exists with version V1 for property scope P When user U updates any policy field(s) F with a non-empty reason R at time T Then the system creates a new immutable version V2 that includes: full snapshot of policy inputs, computed outputs, metadata {user_id: U, timestamp_utc: T (ISO 8601), property_scope: P, reason: R}, and parent_version_id: V1 And any attempt to edit or delete V1 or V2 via UI or API returns 403 and no records are changed And the system logs the blocked attempt with user_id, timestamp_utc, and outcome "blocked"
Field-Level Diff View for Policies and Computed Outputs
Given two versions V_old and V_new of the same policy with differences When a user opens Compare(V_old, V_new) Then the UI renders within 2 seconds (for <=200 fields) a field-level diff showing modified fields with before/after values, inserted rules marked as added, and removed rules marked as deleted And computed outputs deltas are displayed by issue type and unit class with before/after values And the user can toggle to hide unchanged fields And a downloadable JSON diff of the same comparison is available
Time-Travel Decision Lookup for Work Orders
Given work order WO123 received an auto-approval decision at time T using policy version Vid When a user opens Decision History for WO123 Then the system displays policy_version_id = Vid with a link to the version snapshot, the computed cap at time T, the decision result (approved/denied) with explanation, and timestamp_utc in ISO 8601 And re-running the lookup at any later time returns identical values regardless of current policy state And if Vid is not found, the system returns a 404-style error message without partial data
Filtered Audit Log Export (CSV/JSON) with Performance Guarantees
Given filters {date_range: D1–D2, property_scope: P (one or many), optional user_id U} When a user requests an audit export in CSV Then the file contains a header row and rows including fields: version_id, parent_version_id, policy_snapshot, computed_outputs, property_scope, user_id, reason, timestamp_utc, related_work_order_id (nullable) And timestamps are ISO 8601 UTC, numbers are not quoted And exports up to 100,000 rows complete within 60 seconds; larger datasets stream and provide a downloadable link When the user requests JSON export Then the response is a JSON array of objects with the same fields and ordering, honoring the same filters
Mandatory Reason for Change Validation
Given a user attempts to create or update a policy When the reason for change is missing or shorter than 10 characters after trimming Then the system blocks the save, shows an inline error "Reason must be at least 10 characters", and no new version is created When a reason of 10+ characters is provided Then the save succeeds and the reason is persisted and visible in version history and exports
Property Scope Recording and Query Accuracy
Given a policy version V applies to properties {P1, P2} When the audit history is filtered by property P1 Then V appears in results; when filtered by a property not in {P1, P2}, V does not appear When querying for the active version at time T for property P1 Then the system returns the version whose effective time window includes T And any change to property scope (adding/removing properties) creates a new version and is visible in diffs and exports
Cap Evaluation API & Webhooks
"As an integration engineer, I want reliable APIs and events for cap evaluations so that downstream systems can make instant, consistent approval decisions."
Description

Expose an API for retrieving current caps and evaluating a work order against its applicable cap with idempotent requests. Provide webhooks for cap-updated events to notify triage and vendor routing services. Enforce authentication, rate limiting, and per-organization scoping. Ensure low-latency responses to keep one-click approvals fast.

Acceptance Criteria
Cap Retrieval API — Org Scoping and Performance
Given a valid access token for organization A and a property P owned by organization A When the client requests GET /v1/caps?property_id=P Then the server returns 200 with only caps applicable to organization A and property P, and the response matches the published JSON schema And the response includes cap_id, property_id, issue_type, unit_class, seasonal_window, amount, currency, rule_version, updated_at And p95 latency is <= 200 ms and p99 <= 400 ms over a sample of 1000 requests Given a valid access token for organization A and a property Q owned by organization B When the client requests GET /v1/caps?property_id=Q Then the server returns 403 with no cap data in the body
Work Order Evaluation — Decision and Low Latency
Given a work order payload with org_id, property_id, issue_type, unit_class, estimated_cost, and created_at and a valid access token When the client calls POST /v1/caps:evaluate Then the server returns 200 with evaluation_id, outcome in {within_cap, exceeds_cap, no_cap_found}, cap_amount, currency, rule_version, reason_codes And outcome is within_cap when estimated_cost <= applicable cap, exceeds_cap when estimated_cost > applicable cap, and no_cap_found when no cap exists And p95 latency is <= 200 ms and p99 <= 450 ms over a sample of 1000 requests Given the same request body and header Idempotency-Key: K within 24 hours When the client retries POST /v1/caps:evaluate Then the server returns 200 with the same evaluation_id and identical body and includes header Idempotent-Replay: true
Authentication & Authorization — Access Control
Given a request without an Authorization header When calling any /v1/caps endpoint Then the server returns 401 with error_code "unauthorized" Given a bearer token for organization A When accessing caps for a property owned by organization B Then the server returns 403 with error_code "forbidden" and no sensitive fields in the body Given a valid token with scope read:caps When calling GET /v1/caps Then access is allowed with 200 Given a valid token missing required scope for evaluation When calling POST /v1/caps:evaluate Then the server returns 403 with error_code "insufficient_scope"
Rate Limiting — Per Organization Quotas
Given a configured default limit of 600 requests per minute per organization When an organization exceeds the limit on /v1/caps endpoints Then subsequent requests in that window return 429 with a Retry-After header indicating seconds until reset Given two organizations A and B each making 500 requests per minute When calling /v1/caps endpoints concurrently Then neither organization is rate limited due to the other's traffic Given a client that was rate limited When the window resets Then the next request succeeds with 200 and headers reflect a refreshed remaining-quota
Cap Updated Webhook — Delivery and Idempotency
Given a cap change for organization A affecting property P When the change is committed Then a cap.updated event is sent to A's registered webhook URL within 2 seconds p95 and 5 seconds p99 from commit time And the payload includes event_id, event_type "cap.updated", org_id, property_id, affected_cap_ids, rule_version, occurred_at Given a transient subscriber failure (HTTP 5xx or timeout) When delivering a cap.updated event Then the system retries up to 6 times with exponential backoff of approximately 1s, 2s, 4s, 8s, 16s, 32s before marking delivery failed Given an event is delivered more than once When the subscriber compares event_id values Then the event bodies are identical across deliveries and can be safely deduplicated
Webhook Security — Signature and Replay Protection
Given a subscriber-configured signing secret When sending any cap.updated event Then the request includes headers X-FixFlow-Signature (HMAC-SHA256 over timestamp + payload) and X-FixFlow-Timestamp (epoch seconds) Given an incoming webhook with a timestamp older than 300 seconds When the subscriber validates the signature with tolerance 300 seconds Then verification fails due to staleness Given any modification to the delivered payload When the subscriber recomputes the HMAC using the shared secret Then the signature validation fails
Error Handling & Contract — Deterministic Errors
Given a validation error such as a missing estimated_cost field When calling POST /v1/caps:evaluate Then the server returns 400 with error_code "invalid_request", a field_errors array, and a correlation_id Given an unknown route or unsupported API version When calling the API Then the server returns 404 with error_code "not_found" and a documentation_url Given an unexpected server-side exception When processing a request Then the server returns 500 with error_code "internal_error" and a correlation_id, and the body contains no stack traces or internal identifiers

Evidence Gate

Blocks payouts until required evidence is complete and consistent—before/after photos, itemized receipts, and timestamps tied to the work order. Auto-validates EXIF/time windows and flags mismatches, cutting disputes and accelerating audits.

Requirements

Configurable Evidence Rules Engine
"As a property manager, I want to define what evidence is required per repair type so that payouts only occur when the right documentation is present."
Description

Provide an admin-configurable rules engine to define required artifacts per work-order type (e.g., minimum before/after photos, itemized receipt, technician timestamps), conditional logic (e.g., if cost > $300 then add supervisor approval and extra photos), and acceptable metadata (EXIF present, capture window, geofence radius, file types). Include versioned templates, per-vendor/property overrides, and JSON-based schemas. The engine evaluates each work order’s evidence in real time and exposes pass/fail status and reasons to the workflow layer, ensuring payouts are gated consistently across portfolios.

Acceptance Criteria
Admin Creates Versioned Rule Template by Work-Order Type
Given I am an admin with "Rules:Manage" permission When I create a template for work-order type "Plumbing - Leak" Then I can define required artifacts: before_photos>=2, after_photos>=2, receipt=itemized(pdf), technician_timestamps=required And I can set constraints: photo_file_types in [jpg,png,heic], receipt_file_types in [pdf], exif_required=true, capture_window=±2h of scheduled_visit, geofence_radius=150m And I can save the template and the system stores it as version "v1.0" with immutable schema_id And new work orders of type "Plumbing - Leak" created after save are evaluated against "v1.0" And the template is viewable/exportable as JSON schema and passes JSON Schema draft-07 validation
Cost Threshold Conditional Requirements
Given a rules template with condition cost_total>300 requiring supervisor_approval artifact and after_photos>=3 When the work order subtotal+tax > 300 Then the evaluation result is Fail until supervisor_approval is present and after_photos>=3 And when cost is edited to <=300, the additional requirements are removed and evaluation recalculated within 2s And reason codes include ["COND_COST_GT_300_MISSING_SUPERVISOR","COND_COST_GT_300_AFTER_PHOTOS_COUNT"]
EXIF and Capture Window Enforcement
Given photo evidence is uploaded When EXIF capture time is missing or unparsable Then evaluation fails with reason code "PHOTO_EXIF_MISSING" for each offending file And when EXIF time exists, it must be within scheduled_visit_start-2h to scheduled_visit_end+2h Then out-of-window photos fail with "PHOTO_TIME_OUT_OF_WINDOW" And timezone offsets are normalized to property timezone before comparison
Geofence Validation for Photo Evidence
Given the template sets geofence_radius=150m When a photo contains GPS coordinates in EXIF Then the distance from work-order geolocation is computed using Haversine and must be <=150m And photos without GPS fail with "PHOTO_GPS_MISSING" unless allow_no_gps=true in template And photos outside radius fail with "PHOTO_OUT_OF_GEOFENCE" including measured distance in meters
Per-Vendor and Per-Property Overrides with Precedence
Given a portfolio template v1.0 and a vendor override for Vendor A and a property override for Property X When a work order is for Vendor A at Property X Then the effective rules are resolved by precedence: property override > vendor override > portfolio template And the UI/API can preview the effective rules for a given work order id And evaluation uses the effective rules and returns which override source each rule came from
Version Locking and Migration of In-Flight Work Orders
Given template v1.0 is active and work orders WO-1, WO-2 are open under v1.0 When admin publishes v1.1 Then new work orders use v1.1, and WO-1, WO-2 remain locked to v1.0 And admin can bulk migrate selected work orders to v1.1, with a dry-run impact report listing added/removed requirements And all version changes are audit-logged with actor, timestamp, from_version, to_version
Real-Time Evaluation and Workflow Exposure
Given an open work order with pending evidence When any relevant field changes (artifact upload/delete, cost change, schedule change) Then the rules engine re-evaluates within 2 seconds at the 95th percentile and updates status pass/fail with reason codes And an API GET /work-orders/{id}/evidence/status returns {passed:boolean, reasons:[{code:string, message:string, rule_source:string}]} And the payout action remains disabled until passed=true
EXIF & Geofence Auto-Validation
"As a compliance auditor, I want photo metadata auto-validated against job time and location so that I can trust that the work was performed as claimed."
Description

Automatically extract and normalize EXIF metadata from uploaded photos/videos and validate capture time against the scheduled job window/SLA and device timezone. Verify geolocation proximity to the work address using a configurable radius and detect missing or manipulated metadata (e.g., stripped EXIF). Provide client-side pre-checks to reduce bad uploads, server-side authoritative checks, and clear failure reasons. Support fallbacks such as manual attestation with manager approval when metadata is unavailable, and persist normalized metadata for audits.

Acceptance Criteria
Client-Side EXIF and Geofence Pre-Check on Upload
Given a work order with a scheduled job window and a configured geofence radius And a user selects a photo or video for upload in the mobile web portal When the client-side pre-check runs Then it extracts EXIF or container metadata for captured_at, timezone_offset, gps_lat, gps_lng, and horizontal_accuracy if available And if any required field is missing, it shows a specific error code (exif_missing, timezone_missing, gps_missing) and disables Submit And if captured_at is outside the scheduled window including SLA tolerance, it shows time_out_of_window and disables Submit unless Manual Attestation is chosen And if distance from work address exceeds the configured radius, it shows geofence_out_of_range and disables Submit unless Manual Attestation is chosen And the pre-check completes within 300 ms for media files up to 10 MB on a mid-tier device
Server-Side Authoritative Validation and Structured Responses
Given a media upload associated to a work order When the server processes the file Then it extracts and normalizes metadata fields: captured_at_utc, device_timezone_offset, captured_at_local, gps_lat, gps_lng, horizontal_accuracy, media_type, media_hash, exif_hash, validator_version And it validates the time window against the scheduled window plus SLA tolerance and the geofence against property coordinates And on success it returns HTTP 200 with validation_result=pass and the normalized metadata And on failure it returns HTTP 422 with validation_result=fail, error_codes array (e.g., exif_missing, exif_inconsistent, time_out_of_window, geofence_out_of_range, gps_accuracy_low, metadata_tampered), and human-readable messages And 95th percentile validation latency is 2 seconds or less for media up to 25 MB
Time Window and Timezone Validation including DST
Given a work order with a scheduled window [start_local, end_local] and a configured SLA_tolerance_minutes And a media file with EXIF DateTimeOriginal or video creation_time and a timezone_offset When the validator computes captured_at_local and converts to captured_at_utc using the embedded offset Then validation passes if captured_at_local is within [start_local − SLA_tolerance_minutes, end_local + SLA_tolerance_minutes] And if timezone_offset is missing, validation fails with timezone_missing And if EXIF time differs from file modified time by more than 24 hours, validation fails with exif_inconsistent And validations across DST transitions use correct offsets and pass for in-window captures
Configurable Geofence Radius and GPS Accuracy Rules
Given a client setting geofence_radius_m between 25 and 150 with a default of 50 And a property with geocoded lat and lng When a media file includes GPS lat, lng, and horizontal_accuracy Then validation passes if haversine_distance(media_gps, property_gps) is less than or equal to geofence_radius_m And if horizontal_accuracy is greater than geofence_radius_m times 1.5, validation fails with gps_accuracy_low And if GPS is missing or equals 0,0 validation fails with gps_missing And updates to geofence_radius_m take effect within 60 seconds for new validations
Metadata Tamper and Manipulation Detection
Given a media file with EXIF and or XMP metadata When the validator compares timestamps and GPS across EXIF and XMP Then validation fails with exif_inconsistent if timestamps differ by more than 5 minutes or GPS differs by more than 100 meters And validation fails with metadata_tampered if EXIF Software indicates an editing app and DateTimeOriginal post-dates file modified time by more than 30 minutes And validation fails with exif_missing if both DateTimeOriginal and creation_time are absent And all failures include a stable error code and remediation guidance link
Manual Attestation Fallback with Manager Approval
Given a validation result of fail with error code in {exif_missing, gps_missing, timezone_missing, gps_accuracy_low} When the uploader selects Manual Attestation Then the system requires an attestation reason, a before and after photo pair, and an on-site confirmation And the server records fallback_pending, blocks payout, and notifies the assigned manager for approval And when an authorized manager approves, the status changes to pass_with_attestation, payout block is lifted, and approval user and timestamp are recorded And if the manager rejects, the status remains fail and a new upload request is generated
Metadata Normalization and Audit Persistence
Given any validation outcome of pass, fail, or pass_with_attestation When server-side processing completes Then an immutable audit record is written including media_id, work_order_id, captured_at_utc, captured_at_local, device_timezone_offset, gps_lat, gps_lng, horizontal_accuracy, distance_m, geofence_radius_m, media_type, media_hash, exif_hash, validator_version, validation_result, error_codes, reviewer_user_id, created_at And the audit record is read-only, checksum-signed, and retrievable via an audit API filterable by work_order_id, vendor_id, and date range And audit records are retained for at least 7 years
Receipt OCR & Line-Item Match
"As an accounts payable specialist, I want receipts to be parsed and matched to the work order line items so that I can detect overcharges before releasing payment."
Description

Ingest receipt images/PDFs, perform OCR to extract vendor, date, taxes, totals, and itemized lines, and normalize units and currencies. Match items to the work order scope and contracted rate cards, validate arithmetic, and compare totals to the payout request. Detect duplicates and anomalies (e.g., duplicated line items, excessive markup, date mismatch) and present actionable flags. Support multi-page receipts, handwritten notes where feasible, and structured uploads (CSV) for larger vendors.

Acceptance Criteria
Multi-Page Receipt OCR with Currency and Tax Validation
Given a work order with a submitted multi-page receipt (image or PDF) When OCR is executed Then the system extracts vendor name, invoice number (if present), receipt date, subtotal, tax, total, and all itemized lines (description, quantity, unit, unit price, line total) across all pages And the sum of line totals equals the subtotal within the greater of $0.01 or 0.1% And subtotal plus tax equals the total within the greater of $0.01 or 0.1% And detected currency is normalized to the property’s default currency using the exchange rate effective on the receipt date And each required field (vendor, date, subtotal, tax, total) has OCR confidence ≥ 0.95 or is flagged "Low OCR Confidence" with the specific field identified
Line-Item Match to Work Order Scope and Rate Cards
Given extracted itemized lines from a receipt and an associated work order with scope and a vendor rate card When line matching runs Then each line is matched to a scope item by SKU or fuzzy description match (≥0.85 similarity) with unit normalization (e.g., hr/hours, ft/feet) And any line that cannot be matched is flagged "Out-of-Scope" and listed And unit prices are validated against the contracted rate card; any line price exceeding the contracted rate by more than the greater of 2% or $1.00 is flagged "Rate Exceeds Contract" with expected vs observed values And items marked tax-exempt in the contract that include tax are flagged "Tax Policy Violation"
Payout Request Total Comparison and Evidence Gate
Given a payout request referencing one or more receipts for the same work order When totals are compared Then the payout amount must equal the sum of validated receipt totals (after discounts/credits) within $0.01 or a configured tolerance; otherwise a "Payout-Receipt Mismatch" flag is raised with both amounts shown And any critical flags (Payout-Receipt Mismatch, Rate Exceeds Contract, Out-of-Scope, Low OCR Confidence) block payout via Evidence Gate until resolved or overridden by an authorized approver And the system displays actionable flags including type, severity, source field/line, and a link to the exact receipt region, with one-click actions to request vendor correction, edit mapping, or approve exception; all actions are audit-logged
Duplicate Receipt and Duplicated Line Item Detection
Given a newly uploaded receipt for a work order When the system evaluates for duplicates Then perceptual hash of images/PDF and checksum of extracted text are compared to receipts for the same vendor/work order (and property) within the last 180 days; any match above a 0.95 similarity threshold is flagged "Duplicate Receipt" with references to prior submissions And within the same receipt, identical descriptions with identical unit price and quantity appearing on multiple lines are flagged "Duplicated Line Item" unless the vendor indicates split billing; the exception must be captured as a note And if an invoice number matches a previously stored invoice number for the same vendor, it is flagged "Invoice Number Duplicate"
Date and Timestamp Consistency with Work Window
Given a work order with technician check-in/out timestamps and a configured permissible purchase window When validating receipt timing Then the receipt date must fall between technician check-in and 7 days after check-out (or within a configured window); otherwise flag "Date Outside Work Window" And any receipt date more than 1 day in the future relative to upload time is flagged "Future Date" And EXIF timestamps from receipt photos (when present) are normalized to the property time zone and compared to the receipt date; discrepancies greater than 24 hours are flagged "EXIF Mismatch"
Structured CSV Upload Ingestion and Mapping
Given a CSV upload from a vendor with headers including vendor, invoice_number, date, line_description, quantity, unit, unit_price, tax, currency When the file is processed Then required headers and data types are validated; any missing/invalid fields produce a "Schema Error" with row and column references and the upload is rejected And rows are grouped by invoice_number and vendor, arithmetic is validated (line totals, subtotal, tax, total), and currency is normalized as per receipt rules And each row is matched to work order scope and validated against the rate card; unmatched or over-rate rows are flagged as in other scenarios And processing is idempotent using (vendor, invoice_number) as a natural key; re-uploads update existing records with a change log rather than creating duplicates
Handwritten Notes and Low-Confidence OCR Handling
Given a receipt that includes handwritten fields or low-quality scans When OCR produces any required field with confidence < 0.90 Then the field is marked "Needs Review" and the user is prompted to confirm or correct via inline editing with the original image snippet visible And upon user confirmation/correction, the system re-runs matching and validations and updates flags accordingly And all manual edits are versioned with timestamp, user, and before/after values; approvals proceed only when all required fields are confirmed or meet confidence thresholds
Payout Hold & Release Workflow
"As a property manager, I want payouts automatically held and released based on evidence status so that I reduce manual checks and payment errors."
Description

Integrate an automated payment gate that blocks payouts until all evidence checks pass, with role-based one-click override requiring reason codes and audit logging. Support partial payouts for multi-phase jobs, bulk approvals, retry on new evidence, and SLA-based escalation. Expose status badges and next steps in the work order UI and emit webhooks/events for downstream payment processors and accounting systems to synchronize state.

Acceptance Criteria
Evidence Gate Validation and UI Feedback
Given a work order with a submitted payout request and required evidence configured (min 2 before photos, min 2 after photos, itemized receipt PDF/JPG/PNG, and work timestamps), And the work order has a defined work window (scheduled start to completion), When the vendor submits the payout request, Then the system validates evidence presence, file types, and receipt totals ≤ approved amount, And EXIF timestamps of photos fall within the work window ±10 minutes, And missing/invalid items are enumerated with specific reason codes (e.g., EVIDENCE_MISSING_BEFORE, EXIF_OUT_OF_RANGE, RECEIPT_TOTAL_MISMATCH), And the payout state is set to On Hold if any check fails within 2 seconds, And the UI displays a status badge "On Hold: Evidence" and a Next Steps checklist of missing/invalid items, And the release action is disabled with a tooltip referencing the reason codes, And when all checks pass, the payout state becomes Ready to Release and the UI shows badge "Ready to Release" with an enabled Release action.
Role-Based Override with Reason and Audit
Given a user with permission Payout.OverrideGate and a work order in On Hold: Evidence, When the user clicks "Override and Release", Then a modal requires a reason code from a managed list or custom text ≥ 10 characters, And submission without a reason is blocked with inline validation, And upon confirmation the payout transitions to Released with flag overridden=true, And an audit log entry is created capturing workOrderId, payoutId, userId, timestamp (ISO 8601 UTC), priorState, newState, reasonCode/text, And users without Payout.OverrideGate cannot see the override control and receive HTTP 403 if they attempt the API, And the override event is visibly tagged in the work order history timeline.
Partial Payouts for Multi-Phase Jobs
Given a work order with multiple line items/phases each with an approved amount and evidence requirements, And evidence validation has passed for a subset of items and failed or is missing for others, When the approver initiates payout, Then the system calculates the eligible partial payout amount = sum(approved amounts of items with passed evidence) − sum(previous payouts), And requires the approver to confirm the selected items/amount, And upon confirmation creates a payout with state Released (Partial) and updates remaining balance and per-item statuses, And items without passed evidence remain On Hold with reasons, And the system prevents partial payout amounts exceeding the remaining approved total, And all ledger entries reflect item-level allocations.
Bulk Approval and Release
Given a user selects multiple work orders (up to 500) in states Ready to Release or eligible for override, When the user triggers Bulk Release, Then each work order is processed atomically and independently, And only Ready to Release items are released automatically, And items requiring override are released only if the user supplies a reason code and has Payout.OverrideGate, And ineligible items remain unchanged with retained reasons, And the system returns a summary (processed count, released count, overridden count, failed count) and per-item results, And the bulk operation completes within 60 seconds for 500 items, And repeated submissions with the same client idempotency key do not duplicate payouts.
Auto-Retry on New Evidence
Given a work order with payout state On Hold: Evidence and recorded failing reason codes, When new evidence is uploaded or existing evidence is updated for that work order, Then the system revalidates automatically within 30 seconds, And if all required checks now pass, the payout state transitions to Ready to Release and prior reason codes are cleared, And if checks still fail, the state remains On Hold and the UI Next Steps list updates to reflect current gaps, And each revalidation attempt is appended to the audit log with outcome and reasons.
SLA Escalation on Stalled Payouts
Given an organization-level SLA is configured for payout holds (e.g., 24 hours), And a work order payout has been in On Hold: Evidence continuously for the SLA duration, When the SLA threshold is reached, Then the system flags the work order as Escalated, displays a badge "Escalated: SLA Breach", and creates a task/notification to the assigned property manager, And an escalation event is recorded and emitted once per SLA breach window (no duplicate alerts every minute), And if the payout is released or overridden, the escalation badge is cleared and no further escalation events are fired for that instance.
Webhooks and Event Emission for State Changes
Given a tenant system has subscribed endpoints, When a payout changes state among OnHoldEvidence, ReadyToRelease, Released, PartiallyReleased, Overridden, Escalated, ValidationFailed, or EvidenceUpdated, Then the system POSTs a webhook within 5 seconds containing eventType, workOrderId, payoutId, previousState, newState, reasonCodes[], override{by,reason} if applicable, amounts{approved,released,remaining}, and occurredAt (ISO 8601 UTC), And includes headers X-FixFlow-Signature (HMAC-SHA256) and Idempotency-Key, And retries on 5xx with exponential backoff for up to 24 hours, stops on 2xx, and does not retry on 4xx, And duplicate deliveries carry the same Idempotency-Key for consumer de-duplication.
Guided Evidence Capture UX
"As a vendor technician, I want a guided capture flow that tells me exactly what to submit so that my job isn’t delayed by missing or invalid evidence."
Description

Deliver a mobile-first capture flow for vendors/technicians with a dynamic checklist derived from the rules engine, inline examples, and progress indicators. Utilize native camera access to preserve EXIF, enforce minimum photo counts and angles, and run local validations before upload. Support offline capture with queued sync, batch uploads, drag-and-drop on desktop, accessibility compliance, and localization. Provide immediate feedback on what’s missing to minimize resubmissions.

Acceptance Criteria
Dynamic Checklist from Rules Engine
Given a work order with applicable evidence rules When the vendor opens the capture flow Then a checklist is generated that matches the rules for that work order and is ordered per rule priority Given a checklist has conditional dependencies When the triggering condition becomes true Then the dependent items become visible and required Given a checklist has conditional dependencies When the triggering condition becomes false Then the dependent items are hidden and not required and previously captured dependent evidence is retained but excluded from submission Given rules are updated server-side during an active session When the vendor refreshes the checklist Then the latest rules are applied without loss of already captured evidence and any new requirements are clearly flagged as required
Native Camera Capture with EXIF Preservation
Given a supported mobile device When the vendor taps Take Photo Then the native camera opens and the app requests camera permission if needed Given a photo is captured via the native camera When the photo is attached to a checklist item Then EXIF DateTimeOriginal, GPS (if available and permitted), Orientation, and Make/Model are preserved in the uploaded file and metadata payload Given the checklist item requires angle tags Front, Left, Right When the vendor captures photos Then each photo is tagged with the selected angle and the app prevents completion until all required angles are provided Given the work order has an allowed time window When validating a captured photo locally Then the app flags photos outside the window with a clear error and prevents submission until replaced or an allowed override is applied
Inline Validation and Immediate Feedback
Given required evidence is missing for any checklist item When the vendor taps Submit Then inline error messages appear next to each failing item with a count of missing elements and actions to capture or attach Given a file fails validation (corrupted, wrong type, or size over limit) When attaching files Then the app rejects the file, displays the specific reason, and does not add it to the upload queue Given all local validations pass When the vendor submits the evidence Then the server accepts the payload without 4xx validation errors related to missing metadata or required artifacts Given a network error occurs during upload When retry is available Then the app allows per-file retry and does not mark the item complete until a successful upload response is received
Offline Capture with Queued Sync
Given the device has no network connectivity When the vendor captures evidence Then the evidence is stored locally encrypted at rest, added to a visible upload queue, and the checklist shows a Pending Sync state Given connectivity is restored When the app is in foreground or background Then queued items automatically sync in FIFO order and the checklist updates to Uploaded upon success Given a conflict arises (e.g., work order closed or requirements changed) When syncing queued items Then the app pauses the affected items, displays a conflict banner with resolution options, and preserves the captured evidence Given the app is closed and relaunched When there are queued items Then the queue persists and resumes syncing without data loss
Batch Uploads and Desktop Drag-and-Drop
Given a desktop browser user When the user drags and drops multiple files onto the drop zone Then the app lists previews, shows per-file progress bars, and allows cancel/remove per file before upload completes Given duplicate files (same checksum) are added in a batch When validating locally Then duplicates are flagged and only one copy is queued by default with the option to keep both Given a batch contains unsupported types for the target item When validating locally Then unsupported files are rejected with a reason and do not start uploading Given images imported via drag-and-drop contain EXIF When attaching them Then EXIF is preserved and validated according to the same rules as mobile captures
Progress Indicators and Inline Guidance
Given a multi-step capture flow When the vendor completes items Then a persistent progress indicator shows percent complete and remaining required items count, updating in real time Given a checklist item is opened When guidance is available Then example images and short tips are displayed inline and are dismissible Given all required evidence for a step is valid When the vendor navigates back to the checklist Then the step shows a completion checkmark and becomes collapsible Given the vendor returns after closing the app When reopening the capture flow Then previously completed items remain marked complete and captured data is intact
Accessibility and Localization
Given a screen reader is enabled When navigating through the capture flow Then all interactive elements expose correct roles, names, and states and focus order follows the visual layout Given high-contrast or dark mode is enabled at OS level When the capture flow loads Then text and controls meet WCAG 2.2 AA contrast ratios and remain fully visible Given keyboard-only navigation on desktop When performing upload actions Then an accessible alternative to drag-and-drop exists via tab/enter/space and file picker, and all controls are reachable without a mouse Given the language is set to Spanish When viewing the capture flow Then all UI text, validation messages, and date/time formats are localized; no untranslated strings are present Given an RTL language is selected When viewing the capture flow Then layout mirrors appropriately, and progress indicators and checklists render in RTL without visual defects
Mismatch Flagging & Remediation
"As a vendor coordinator, I want clear flags and actionable tasks when evidence fails validation so that I can quickly resolve issues and resubmit."
Description

Present real-time validation results with clear, human-readable failure reasons and links to offending artifacts. Auto-create remediation tasks (e.g., “Retake after photo within 24h window”) with due dates, notify responsible parties via email/SMS/in-app, and track SLA to closure. Re-evaluate the gate on new submissions and update status automatically. Provide conversation threads on the work order for clarification and maintain a history of failed vs. passed checks.

Acceptance Criteria
Real-time validation feedback on evidence submission
Given a work order with Evidence Gate enabled and a vendor uploads evidence (before/after photos and receipts) When any validation rule fails (e.g., after-photo EXIF timestamp is outside the allowed time window) Then the UI shows a human-readable failure reason next to each failing item within 2 seconds of submission And each failure includes a direct link to the offending artifact and highlights the specific rule that failed And the payout action is disabled with an explanation referencing the unmet checks And all passing checks are displayed as Passed, and all failing checks are grouped by rule
Automatic remediation task creation with due date
Given a validation failure is detected When the failure is recorded Then a remediation task is auto-created with a clear action (e.g., "Retake after photo within 24h window") tied to the failing rule And the task is assigned to the responsible party derived from the work order (e.g., primary vendor) And the task includes a due date calculated per rule configuration (default 24 hours from failure timestamp) And duplicate tasks for the same rule and artifact are not created within the same work order And the task appears on the work order timeline and the assignee’s task list with a deep link to the offending artifact
Multi-channel notifications on remediation creation
Given a remediation task is auto-created When notifications are dispatched Then the responsible party receives in-app, email, and SMS notifications within 60 seconds (respecting user preferences) And the notification content includes work order ID, failure reason, required action, due date, and a deep link to the task And delivery status (sent, failed, bounced) is logged for each channel And transient delivery failures are retried up to 3 times with exponential backoff
Automatic re-evaluation and gate status update on new submissions
Given one or more remediation tasks are open due to failed checks When new evidence is submitted or an assignee marks a remediation task as complete Then all relevant validations are re-run immediately And if all checks pass, the Evidence Gate status changes to Passed, payout is unblocked, and related remediation tasks auto-close And if any checks still fail, the Evidence Gate remains Blocked and new remediation tasks are created only for newly observed failures And the results summary (passed vs. failed count) updates in the work order header
Conversation threads for failed checks
Given a failed check exists on a work order When a user opens the failed check details Then the user can start or reply to a conversation thread linked to that specific check And participants can @mention users and attach files or photos And each message records author, timestamp, and is visible to the work order’s manager and assigned vendor And the thread is referenced from the related remediation task
Immutable history of validation runs
Given validations are executed for a work order When results are generated Then an immutable history entry is stored capturing timestamp, rule name, result (Pass/Fail), associated artifact IDs, and actor (system or user) And history is viewable in the work order under Evidence History and filterable by rule and result And exporting the history to CSV includes all captured fields for the selected date range
SLA tracking and escalations to closure
Given a remediation task is created with a due date When the SLA timer starts Then the task shows a real-time countdown to due date and the SLA status (On Track, Due Soon <12h, Overdue) And on SLA breach the task status changes to Overdue and an escalation notification is sent to the property manager and team inbox And when the required evidence is submitted and the check passes, the SLA stops and the task is closed with a resolution timestamp recorded And SLA metrics (time to remediate, breach flag) are available for reporting on the work order
Audit Trail & Export Package
"As an owner, I want a complete audit package I can export for disputes and insurance claims so that I can prove due diligence with minimal effort."
Description

Maintain an immutable timeline of evidence submissions, validation outcomes, overrides, and payout state changes with user IDs, timestamps, and artifact hashes. Offer one-click export of an audit bundle (PDF summary plus ZIP of originals and metadata JSON) and API access for auditors. Include retention policies, search and filter by property/vendor/date, and tamper-evident signatures to support disputes, insurance claims, and regulatory reviews.

Acceptance Criteria
Immutable Timeline Recording of Evidence and State Changes
Given a work order with evidence is created When the submission is saved Then an append-only event is recorded containing eventId, workOrderId, userId, role, eventType (submission|validation|override|payoutStateChange|export), timestamp in UTC ISO-8601 with millisecond precision, and SHA-256 hash for each artifact Given an existing timeline event When any actor attempts to modify or delete it via UI or API Then the system rejects the request with HTTP 403 and logs a separate securityAttempt event referencing the original eventId Given validation processing completes When outcomes (pass|fail|mismatch) are produced Then a validationOutcome event is recorded linking to the evidence artifactIds and includes ruleIds evaluated and result details Given a payout state changes from one state to another When the transition occurs Then a payoutStateChange event is recorded with fromState, toState, reasonCode, actor userId, and timestamp; and out-of-order transitions are blocked unless accompanied by an override event Given EXIF/time-window checks run When a mismatch is detected Then the event includes mismatchFlag=true and mismatchDeltaMinutes as a numeric value
Tamper-Evident Hashing and Signature Verification
Given artifacts and metadata are stored When hashes are recomputed offline Then each computed SHA-256 equals the stored hash for the corresponding artifact Given an audit bundle is generated When the manifest.json and signature file are produced Then the manifest lists every file name, size, SHA-256, and eventId, and a cryptographic signature over the manifest is present and verifiable using the platform public key Given any file in the bundle is altered after export When signature verification is executed Then verification fails and identifies the specific filename(s) with mismatched hash Given a bundle with up to 500 files totaling 1 GB When verification is run on standard build agents Then verification completes within 3 seconds at the 95th percentile
Search and Filter by Property, Vendor, and Date Range
Given the auditor search UI or API When filters propertyId IN [list], vendorId IN [list], and eventTimestamp BETWEEN start and end are applied Then only matching timelines and events are returned and all non-matching records are excluded Given filtered results are returned When sort by eventTimestamp desc is selected Then results are ordered newest-first deterministically (eventTimestamp, eventId) Given a result set larger than a page When requesting results Then pagination is supported via limit and cursor, and page-size defaults to 50 and supports up to 500 per page Given a dataset of 100k events with indexed fields When running a filtered query Then the API responds within 2 seconds at the 95th percentile Given a filter is applied When exporting from the list view Then the export contains only events included by the current filter
One-Click Audit Export Package (PDF + ZIP + JSON)
Given a user with audit.export permission views a work order When clicking Export Audit Then a downloadable package is produced within 10 seconds at the 95th percentile containing: (1) summary.pdf, (2) originals.zip, (3) manifest.json, and (4) signature file Given the summary.pdf is generated When opened Then it lists the full timeline table with eventId, type, actor, UTC timestamp, outcomes, reason codes, and links to artifact filenames, plus a signature verification section Given originals.zip is generated When inspected Then it contains unmodified original files with original filenames and a directory structure grouped by eventId; no re-encoding occurs Given the export pertains to a specific workOrderId or date-filtered set When generated Then only events within scope are included and all timestamps in all files are UTC Given the package is created When a download link is issued Then the link is pre-signed, expires in 7 days, and is accessible only to authorized users; access attempts are logged
API Access for Auditors with Scoped Read-Only Tokens
Given an API token with scope audit.read When calling GET endpoints for timelines, events, and exports Then the API returns full read-only data parity with the UI and includes pagination and filters for propertyId, vendorId, and date range Given an API client with audit.read scope When attempting POST/PUT/PATCH/DELETE on audit resources Then the request is rejected with HTTP 403 and an accessDenied event is logged with userId and endpoint Given high-volume access When rate limits are applied Then per-token rate limiting of 60 requests/minute is enforced and returns HTTP 429 with Retry-After headers when exceeded Given API responses are returned When inspected Then they include stable versioning via path (/v1/...), ETag and Last-Modified headers, and a JSON schema that validates against the published contract
Retention Policy Enforcement and Deletion Logging
Given a portfolio retention policy is configured to 7 years by default and adjustable between 1 and 10 years When an event exceeds its retention period and no legal hold exists Then the original artifacts are purged and a tombstone event is appended containing eventId, purgeTimestamp UTC, and hashOfHash; subsequent artifact requests return HTTP 410 Gone Given a legal hold is applied to a work order, property, or vendor When retention thresholds are reached Then no purge occurs and attempted purge actions are logged as retentionSkipped events until the hold is removed Given an export bundle was previously generated When source artifacts later age out under retention Then the manifest and tombstone remain in the audit log, prior download links are invalidated, and new exports omit purged artifacts while clearly marking them as purged in summary.pdf Given an administrator updates the retention period When the change is saved Then it applies prospectively to new events only, is recorded as a configChange event with userId and timestamp, and is reflected in system settings
Override and Annotation Logging with User Attribution
Given a validation outcome is overridden When the override is submitted Then the system requires a free-text reason of at least 15 characters and records an override event with userId, role, previousOutcome, newOutcome, justification, and timestamp Given a note or annotation is added to a timeline When saved Then it is stored as a separate note event linked to the relevant eventId(s), appears in the timeline and export, and does not modify prior events Given a user attempts to edit or delete a prior note When the action is taken Then the system creates a correction event referencing the original note and preserves the original; direct edits/deletes are not permitted

Smart Split

Separates quotes into ‘must-fix’ lines and optional add‑ons. Auto-approves essentials within caps while holding extras for review, so critical repairs proceed without overspending.

Requirements

Line-Item Classification Engine
"As a property manager, I want quotes automatically split into essential and optional lines so that critical repairs proceed quickly without me combing through details."
Description

Automatically parses vendor quotes into discrete line items and classifies each as must-fix (health/safety/code-required/restore-function) or optional add-on (upgrade/upsell) using a hybrid of configurable rules, code references, and ML signals. Supports confidence scoring, category tagging (e.g., HVAC, plumbing), and human override. Integrates with FixFlow intake, vendor portal, and pricing schema so classification occurs at upload and is re-evaluated on updates. Outputs structured items ready for Smart Split actions without altering vendor-provided pricing.

Acceptance Criteria
Quote Parsing and Line-Item Integrity
Given a vendor quote uploaded as PDF, image, or CSV with up to 100 lines When processed by the engine Then each discrete service/material is extracted as a separate line item with description, quantity, unit, unit price, total price, and vendor line reference, preserving original text and pricing And Then the sum of extracted line-item totals equals the vendor-provided grand total within ±$0.01 rounding tolerance And Then at least 98% of lines in a labeled validation set are correctly split without merging unrelated items And Then processing completes within 3 seconds for quotes with ≤100 lines
Must-Fix vs Optional Classification Rules Precedence
Given line items that match configurable must-fix rules (e.g., safety hazards, code citations, restore-function categories) When classified Then those items are labeled Must-Fix with reason codes and rule IDs, and ML signals do not downgrade them Given line items that match optional/upgrade rules and do not match must-fix rules When classified Then those items are labeled Optional with reason codes Given conflicts between ML prediction and deterministic rules When classified Then the rule outcome takes precedence and is recorded in the rationale Given items with classification confidence below the review threshold (default 0.80) When classified Then they are flagged for human review and excluded from auto-approval
Confidence Scoring and Threshold Configuration
Given any classified line item When results are returned Then a confidence score between 0.00 and 1.00 with two-decimal precision is present for classification and for category tagging Given the review threshold values are configured at the account or portfolio level When thresholds are updated Then new classifications use the updated thresholds within 1 minute and the effective thresholds are recorded for audit Given an item classified as Must-Fix or Optional with confidence below its respective review threshold When Smart Split is invoked Then the item is routed to human review and excluded from auto-approval flows
Category Tagging and Taxonomy Mapping
Given line items across common trades When processed Then each item is tagged with at least one category from the configured taxonomy (e.g., HVAC, Plumbing, Electrical) and subcategory where applicable And Then category tagging achieves ≥90% F1 score on the designated validation set Given the taxonomy is updated (add, rename, retire) When changes are published Then new classifications use the updated taxonomy and previously stored items retain legacy tags with a mapping key for reporting
Human Override and Audit Trail
Given a permitted user overrides a line item's classification or category When the override is saved Then the override persists across subsequent re-parsing and re-classification unless the vendor line's description or pricing changes And Then an audit record captures user ID, timestamp, original value, new value, and optional note Given a vendor update is non-substantive (formatting, whitespace) for a line with an override When re-evaluated Then the override remains applied Given a vendor update substantively changes description or price for a line with an override When re-evaluated Then the system flags the override for re-review and notifies the assigned reviewer
Re-evaluation on Quote Updates and Versioning
Given a vendor uploads a revised version of a quote When processed Then only changed or new lines are re-parsed and re-classified; unchanged lines retain prior classification and confidence And Then a version history is recorded with a diff of added, removed, and changed lines and reclassification reasons And Then Smart Split outputs are refreshed to reflect updated classifications without duplicating existing items
Structured Output Contract and Integration
Given classification completes successfully When results are sent to Smart Split via API Then each line item includes item_id, vendor_line_ref, description, quantity, unit, unit_price, total_price, category, classification (Must-Fix|Optional), confidence, rationale (rules and signals), code_refs[], override_flag, and version_id And Then no transformation alters vendor-provided pricing; unit_price and total_price are exactly as provided by the vendor And Then the API response is produced within 2 seconds of upload for quotes with ≤100 lines with a 99.5% success rate over a rolling 30-day window And Then error responses include actionable codes and messages, and failures do not block vendor portal upload completion
Policy-Based Auto-Approval Caps
"As an owner, I want essential repairs auto-approved within my limits so that urgent issues are fixed fast without overspending."
Description

Enables owners and managers to define approval caps and guardrails by property, portfolio, and category (e.g., plumbing essentials up to $350). Applies caps to must-fix lines at the time of classification; auto-approves those within limits and aggregates multiple essential lines against the same job cap. Handles taxes, fees, and trip charges; supports currency, rounding, and time-bound caps. Locks approved amounts for vendors and prepares payment authorization while leaving add-ons pending.

Acceptance Criteria
Auto-Approval of Single Must-Fix Line Within Category Cap (Property Level)
Given a property configured with currency USD and category cap Plumbing Essentials = 350.00 and rounding mode Half Up to 2 decimals And a quote line classified as Must-Fix with subtotal 300.00, tax 24.00, trip charge 20.00, and fees 0.00 When Smart Split evaluates the line for auto-approval Then it calculates the line total = 344.00 using the configured rounding And it auto-approves the line because total <= cap And it locks the approved amount 344.00 for the vendor And it prepares a payment authorization for 344.00 linked to the job And any Add-On lines on the same quote remain Pending Review
Aggregate Multiple Must-Fix Lines Against Job Cap
Given a job with category cap Electrical Essentials = 500.00 at the property And three Must-Fix lines: L1 total 200.00, L2 total 275.00, L3 total 75.00 (totals include taxes/fees/trip per line) When Smart Split evaluates lines in their classification order Then L1 is auto-approved (cumulative 200.00 <= 500.00) And L2 is auto-approved (cumulative 475.00 <= 500.00) And L3 is set to Pending Review because approving it would exceed the cap (projected cumulative 550.00 > 500.00) And the remaining available cap for the job is shown as 25.00 And the locked approved amount across L1 and L2 is 475.00
Cap Precedence: Property+Category Overrides Portfolio and Global Caps
Given a portfolio-level category cap Plumbing Essentials = 300.00 And a property-level category cap Plumbing Essentials = 350.00 And a property-level global must-fix cap = 400.00 And a portfolio-level global must-fix cap = 250.00 When a Must-Fix plumbing line totaling 340.00 is evaluated for that property Then Smart Split applies the most specific cap (Property + Category) = 350.00 And it auto-approves the line because 340.00 <= 350.00 And if the property-level category cap were absent, Smart Split would apply the portfolio-level category cap instead
Time-Bound Cap Selection and Expiry Behavior
Given a property with HVAC Essentials cap 600.00 effective 2025-09-01T00:00 to 2025-09-30T23:59 in the property's timezone And a fallback HVAC Essentials cap 450.00 outside that window When a Must-Fix line totaling 575.00 is evaluated on 2025-09-10T10:00 local time Then it is auto-approved under the 600.00 cap When another Must-Fix line totaling 475.00 is evaluated on 2025-10-02T11:00 local time Then the 450.00 cap applies and the line is set to Pending Review because 475.00 > 450.00 And the system records the applied cap, validity window, and decision in the audit log
Currency and Rounding Compliance in Cap Evaluation
Given property currency = CAD and rounding mode = Half Up to 2 decimals And category cap = CAD 500.00 And a Must-Fix line with computed pre-round total CAD 499.995 (e.g., subtotal 469.44, tax 30.555, fees 0.00) When Smart Split rounds and evaluates eligibility Then it rounds the total to CAD 500.00 using the configured rounding mode And it auto-approves the line because 500.00 <= 500.00 And the locked amount and prepared payment authorization reflect CAD 500.00
Vendor Locking and Edit Restrictions After Auto-Approval
Given Must-Fix lines totaling 420.00 have been auto-approved and locked for a job When the vendor attempts to edit unit price, taxes, fees, or trip charges on approved lines Then the system prevents edits and displays an error indicating the amount is locked And when the vendor submits an additional Add-On line Then the Add-On remains Pending Review and does not change the locked approved amount And any change to approved lines requires a manager override that resets approval state and re-evaluates against current caps
Add-on Hold & Review Queue
"As a small property manager, I want optional items held for review with clear context so that I can approve upsells only when they make sense."
Description

Routes optional line items to a dedicated review queue with clear cost impact, photos, vendor notes, and side-by-side outcomes. Provides one-click approve/decline, bulk actions across units, and reason codes. On action, updates the work order and notifies vendors with a revised scope while preserving previously approved essentials. Supports SLA timers and reminders to prevent stagnation.

Acceptance Criteria
Add-ons Routed to Review Queue with Context and Outcomes
Given a submitted vendor quote contains optional line items When Smart Split processes the quote Then each optional line item appears in the Add-on Review Queue within 10 seconds with property, unit, vendor, work order ID, line description, quantity, unit price, and extended cost And the entry displays cost impact as incremental cost and new order total if approved And attached photos and vendor notes for the line are visible and openable And a side-by-side outcome panel shows Approve vs Decline totals for the work order
One-Click Approve/Decline for Single Add-on
Given a reviewer has an add-on selected in the queue When they click Approve Then the add-on status changes to Approved, it is removed from the queue, the work order scope is updated, and totals are recalculated immediately And a success confirmation is displayed Given a reviewer has an add-on selected in the queue When they click Decline Then the system requires a reason code before confirming and, after confirmation, the add-on status changes to Declined and it is removed from the queue
Bulk Actions Across Units and Work Orders
Given a reviewer multi-selects add-ons across multiple work orders When they bulk Approve Then all selected add-ons are approved, corresponding work orders are updated, and a summary shows counts of succeeded and failed items When they bulk Decline Then a reason code is required, all selected add-ons are declined with the same reason unless overridden per line, and a summary shows counts of succeeded and failed items And any failures are reported with actionable error messages without blocking successful items
Reason Codes and Audit Trail Compliance
Given organization reason codes are configured When a reviewer declines an add-on Then they must select a reason code from the configured list and may add an optional note up to 500 characters And the decision is recorded in the audit log with user, timestamp, action, reason code, note, previous totals, and new totals When a reviewer approves an add-on Then the audit log records user, timestamp, action, and resulting totals
SLA Timers, Reminders, and Escalations
Given an add-on enters the review queue Then an SLA countdown is displayed based on configured policy When remaining time reaches the configured reminder threshold Then the system sends a reminder to the assigned reviewer(s) and marks the item as Due Soon When the SLA time is exceeded Then the system flags the item as Overdue and sends escalation notifications per policy And all reminder and escalation events are recorded in the item timeline
Work Order Scope Integrity and Totals Recalculation
Given essentials are previously approved on a work order When an add-on is approved or declined Then previously approved essentials remain unchanged and scheduled appointments persist And the work order totals are recalculated to include essentials plus approved add-ons only And declined add-ons are excluded from scope and totals but remain visible in history
Vendor Notification with Revised Scope
Given an add-on is approved or declined (single or bulk) When the decision is confirmed Then the vendor receives an in-app and email notification within 2 minutes containing the revised scope, added/removed lines, reason codes for declines, and updated totals And the notification includes the work order ID and a link to view details And vendor notification status and timestamp are recorded, with retries on transient failures up to 3 times
Over-Cap Escalation & Safety Override
"As an on-call approver, I want clear escalations for over-cap essentials so that emergencies aren’t blocked while spend is controlled."
Description

When a must-fix line exceeds set caps, triggers escalation to the next approver with SLA timers and multi-channel notifications (email, SMS, in-app). Provides a safety-critical override path to temporarily authorize work above cap with justification and evidence (photos, code references), recording reason codes and requiring follow-up reconciliation. Prevents vendor dispatch on over-cap essentials until approval or override is recorded.

Acceptance Criteria
Over-Cap Must-Fix Triggers Escalation and Notifications
Given a submitted vendor quote with at least one must-fix line exceeding the configured cap And an approver hierarchy is defined for the property or portfolio When the system evaluates the quote Then the system marks the line as "Over Cap - Pending Approval" And starts an SLA timer using the organization’s over-cap approval policy And sends notifications to the next approver via email, SMS, and in-app containing work order and line details, overage amount, and action links (Approve, Reject, Safety Override) And logs the escalation event with timestamp, approver target, and notification identifiers
SLA Breach Triggers Re-Escalation
Given an over-cap must-fix line is awaiting approval with an active SLA timer When the SLA timer expires without an Approve or Safety Override action Then the system escalates to the next approver level in the hierarchy And restarts the SLA timer for the new approver level And sends multi-channel notifications to the new approver with context and action links And records the SLA breach and updates the escalation path in the audit trail And if no higher approver exists, notifies the configured fail-safe approver group
Safety-Critical Override Requires Justification and Evidence
Given an approver reviews an over-cap must-fix line that is safety-critical When the approver selects Safety Override Then the system requires selection of a reason code from a controlled list And requires entry of a justification text And requires at least one evidence attachment (e.g., photo or code reference link) And upon submission, records the override decision with actor, timestamp, reason code, justification, and evidence references And immediately authorizes work above cap for that line within the platform
Dispatch Block Until Approval or Override
Given a work order contains an over-cap must-fix line without an approval or recorded safety override When any user attempts to dispatch a vendor or schedule work for that line Then the system blocks dispatch and scheduling actions for that line And displays the required next step (Approve or Safety Override) and current approver And allows dispatch of other lines that are approved or within cap
Immutable Audit Trail for Over-Cap Decisions
Given any approval, rejection, override, or escalation occurs on an over-cap must-fix line When the action is saved Then the system creates an immutable audit entry including timestamp, actor identity, role, action type, channel of action, reason code (if applicable), justification text, and evidence references And prevents editing or deletion of the audit entry And exposes the audit trail within the work order timeline and via export/API
Post-Override Reconciliation Gate
Given a safety override was used to authorize work above cap for a must-fix line When the vendor marks the work complete and the final invoice is uploaded Then the system creates a reconciliation task assigned to the designated role And requires entry of final cost and variance versus cap, plus secondary approval or exception reason And prevents closing the work order until the reconciliation task is completed And flags the reconciliation as overdue after the configured threshold and notifies the assignee
Vendor Quote Ingestion & Normalization
"As a vendor, I want to submit quotes in whatever format I have so that FixFlow can process them without extra work from me."
Description

Accepts quotes via vendor portal upload, email-to-quote, or mobile capture; extracts line items from PDF, image, or CSV using OCR and template learning; normalizes descriptions, quantities, unit costs, and taxes into FixFlow’s schema. Detects duplicates and version updates, maintaining quote lineage so Smart Split re-runs on changes without data loss.

Acceptance Criteria
Portal PDF Upload: OCR Extraction and Normalization
Given a vendor uploads a multi-page PDF quote via the vendor portal for an existing work order When the file is processed Then the system extracts all line items, quantities, units, unit costs, line taxes, and totals using OCR And normalizes fields into FixFlow’s schema (description, quantity, unit, unit_cost, tax, line_total, currency) And associates the quote with the correct property, unit, work order, and vendor And reconciles summed line totals plus taxes to the document grand total within ±1.0% or $1, whichever is greater And flags the quote as Needs Review if reconciliation fails; otherwise sets status to Normalized And completes processing within 60 seconds for files ≤10 MB
Email-to-Quote: Inbound Parsing and Attachment Handling
Given an email is received at a property's unique quote alias containing one or more attachments (PDF, CSV, or image) When the email is processed Then a new quote record is created with the email metadata saved (from, subject, received_at, message_id) And only supported attachments are parsed; inline images and unsupported files are ignored and logged And line items are extracted and normalized into FixFlow’s schema And if no parsable attachments are found, the sender receives an automatic rejection email with reason And if the email references an existing work order number in subject/body, the quote is linked accordingly; otherwise it is queued for triage And duplicate emails (same message_id) are ignored without creating additional quotes
Mobile Capture: Image OCR with Confidence and Multi-Image Support
Given a vendor captures one or more photos of a paper quote in the mobile web portal When the images are uploaded Then the system de-skews, de-noises, and OCRs all pages And merges multi-image content into a single quote for extraction And extracts and normalizes line items with a minimum field-level confidence of 90% for descriptions and 95% for numeric fields And any fields below confidence thresholds are flagged and highlighted for manual review without blocking quote creation And processing completes within 90 seconds for up to 5 images at ≤12 MP each
Template Learning: Vendor-Specific Layout Memory
Given prior corrections exist for a specific vendor’s quote template When a new quote from the same vendor is ingested Then the learned template is applied automatically And at least 95% of required fields (description, quantity, unit_cost, tax, line_total) are auto-populated without manual correction And extraction latency is ≤50% of the baseline (non-templated) extraction time And if the layout has materially changed, the system falls back to generic extraction and logs a template drift event
Schema Normalization and Tax/Currency Handling
Given extracted raw line items from any source (PDF, image, CSV) When normalizing into FixFlow’s schema Then quantities and unit costs are converted to numeric with two-decimal precision and standardized units And currency is detected from document context; if absent, defaults to account currency and is recorded And taxes are separated per-line when present; if only a grand tax exists, it is allocated proportionally to lines And canonical categories are assigned to each line item using the taxonomy with ≥90% classification confidence; lower confidence lines are flagged And the sum of normalized line totals plus taxes equals the document grand total within ±1.0% or $1; otherwise a reconciliation error is raised
Duplicate and Version Detection with Lineage and Smart Split Re-run
Given a quote already exists for a work order When an ingested quote matches the existing quote with ≥98% similarity or same vendor reference number Then it is flagged as a duplicate and no new record is created; an audit log entry is added When an ingested quote has the same vendor reference number but different totals or line items Then a new version is created, prior versions are preserved read-only, and a lineage chain is maintained And differences are identified at line-item level (added/removed/changed quantities or costs) and surfaced in the UI And Smart Split is automatically re-run on the latest version, preserving prior approvals and only recalculating impacted lines And no data loss occurs across versions; all prior comments, approvals, and attachments remain accessible
Audit Trail & Exportable Compliance Log
"As an owner, I want a clear audit trail of what was auto-approved versus reviewed so that I can justify spend and meet compliance."
Description

Captures a tamper-evident record of classification decisions, auto-approvals, overrides, reviewer actions, policy versions, timestamps, and actors per line item and job. Provides filters and export (CSV/PDF/API) for owners and auditors. Integrates with notifications and payments to show end-to-end traceability from intake to vendor dispatch.

Acceptance Criteria
Event Capture per Action and Line Item
Given a job with at least two quote line items processed by Smart Split When the system records actions: classification, auto-approval within cap, manual override, reviewer decision, policy version update, vendor dispatch, notification sent, payment authorization, and payment settlement Then an audit entry is created per action and per affected line item or job scope with fields: job_id, line_item_id (nullable for job-scope), action_type, old_value, new_value, actor_id (or "system"), actor_role, policy_version, rationale (optional), correlation_id, timestamp (UTC ISO-8601) And each entry persists with a unique sequential identifier and is visible in the audit log within 2 seconds of the action And entries for auto-approvals explicitly reference the cap policy identifier and amount applied And the total count of created entries equals the number of actions performed, including system-generated actions
Tamper-Evident Integrity Verification
Given an audit log with N entries secured by a cryptographic integrity mechanism When the integrity check runs on read, export, or scheduled verification Then the system validates the chain/digest and returns Integrity=Pass when unaltered And if any stored entry content or order is modified, deleted, or inserted outside the system, the verification returns Integrity=Fail and identifies the earliest invalid entry id And the UI displays an integrity status per job, and exports include a verification digest and IntegrityStatus column And integrity verification completes within 1 second for logs with up to 5,000 entries
Granular Filtering, Sorting, and Pagination
Given a job with at least 100 audit entries across multiple actors, action types, line items, and dates When a user applies any combination of filters (date range, actor_id, actor_role, action_type, job_id, line_item_id, policy_version, approval_state) and chooses sort by timestamp ascending or descending Then the results include only matching entries and reflect the selected sort order And the UI displays total results count and supports pagination with a default page size of 50 and configurable up to 500 And clearing filters restores the unfiltered view And filter state is preserved in the URL/query and is applied to exports and API responses
Filtered Export via CSV/PDF/API with SLA
Given a filtered audit view or corresponding API query When the user exports to CSV or PDF, or calls the GET /audit-logs endpoint with the same filters and sort Then the exported file or API response contains exactly the filtered set in the specified sort order with all required columns and a header row And CSV uses UTF-8 with comma delimiter and quoting as needed; PDF is paginated, legible, and includes job metadata and integrity digest And the API supports pagination (limit and cursor/next_token) and returns total_count And exports of up to 10,000 rows complete within 10 seconds and begin download within 3 seconds And every export event is logged with actor_id, format, filter_hash, row_count, and timestamp
End-to-End Traceability Across Intake, Approvals, Notifications, Payments, Dispatch
Given a job progressing from intake through Smart Split approval, vendor dispatch, notifications, and payments for must-fix lines When viewing the audit log detail for the job Then each event includes correlation_id values linking intake_id, quote_id, line_item_ids, approval_id(s), work_order_id, notification_id(s), and payment_transaction_id(s) where applicable And clicking any id navigates to the corresponding record subject to permission checks And the sequence of events shows an unbroken chain from intake to dispatch with no missing required steps for must-fix items And any missing linked record displays a resolvable reference state with id and timestamp
Role-Based Access, Redaction, and Export Logging
Given roles Owner, Auditor, Manager, Vendor, and Tenant When accessing the audit log Then Owner and Auditor can view and export all fields across their organization And Manager can view and export only jobs within assigned portfolios And Vendor can view only job-scoped entries for their work orders with actor identifiers redacted except role and organization And Tenant cannot access the audit log And PII fields such as tenant email and phone are redacted in Vendor exports and omitted from Vendor API responses And unauthorized access or export attempts return 403 and are logged as audit entries
Performance, Scalability, and Retention Compliance
Given normal operating conditions When audit entries are written during peak hours Then p95 write latency per entry is less than or equal to 200 ms and p99 less than or equal to 500 ms And filtered queries returning up to 10,000 entries respond in less than or equal to 2 seconds p95 And CSV exports up to 10,000 rows complete in less than or equal to 10 seconds and stream progressively And audit data is retained for 7 years by default with tenant-configurable retention policies And when retention purges older entries, a new chain anchor is created and integrity verification for remaining entries continues to pass

Early Overage Alert

Predicts cap breaches from quotes, changes, or time-and-materials burn and warns early with clear next steps: renegotiate, switch vendor, request approval, or schedule in a cheaper window.

Requirements

Real-Time Budget & Cap Tracking
"As a property manager, I want FixFlow to automatically track budget caps across quotes, changes, and T&M burn so that I can see current and projected spend against limits without manual spreadsheets."
Description

Continuously aggregates and normalizes cost data from vendor quotes, change orders, and time-and-materials logs, mapping each line item to the associated work order and budget cap. Maintains committed, actual, and forecasted spend with support for rate cards, after-hours multipliers, and tax/fees. Updates in near real-time on new events (quote revisions, logged labor, parts added) to compute current cap consumption and remaining headroom. Exposes a reliable, single source of truth for cost status within FixFlow, enabling downstream prediction, alerting, and approvals.

Acceptance Criteria
Quote Revision Updates Cap Consumption in Near Real-Time
Given work order WO-123 has a budget cap of $1,000 and a mapped vendor quote Q-1 with subtotal $800, tax 8%, fees $0, and tax/fees are included in committed spend When the vendor revises Q-1 so the subtotal becomes $900 via webhook/event Then within 10 seconds, committed spend updates to $972.00 and forecasted spend updates to $972.00 And cap consumed updates to 97.20% and remaining headroom displays $28.00 And the UI cost panel and Cost Summary API v1 return the same updated values And an audit log entry records user/vendor, timestamp, old/new values, and recalculation_id
T&M Logging Applies Rate Cards and After-Hours Multipliers
Given rate card defines Plumber labor at $100.00/hour regular with an after-hours multiplier of 1.5 and tax rate 8% applies to labor and parts; and work order WO-456 has a budget cap of $600 When vendor logs 2.0 hours of Plumber labor during after-hours and adds part P1 at $50.00 Then within 10 seconds, actual spend increases by $378.00 (labor $300.00 + parts $50.00 + tax $28.00) And cap consumed and remaining headroom recompute accordingly and display in UI and API And all amounts are rounded to 2 decimal places using half-up rounding
Approved Change Order Adjusts Committed Spend; Pending Does Not
Given work order WO-789 has a budget cap of $2,000 and current committed spend $1,200.00 with tax applied at 8% When change order CO-5 for $400.00 is created in Pending status Then committed spend remains $1,200.00 When CO-5 status changes to Approved Then within 10 seconds committed spend updates to $1,632.00 and cap consumed, headroom, and forecast recalculate When CO-5 is later Rejected or Withdrawn Then committed spend reverts to $1,200.00 and an audit entry is recorded
Line Item Mapping Enforcement to Work Order and Cap
Given a new vendor quote Q-9 with 3 line items is ingested for work order WO-222 When auto-mapping runs Then each line item is mapped to the work order and budget cap or flagged Unmapped with a reason code And unmapped line items are excluded from committed totals until mapped And the UI displays an Unmapped Line Items banner with count and the API returns mapping_status per line When a user maps the remaining items Then within 10 seconds totals include the newly mapped items and the banner disappears
Single Source of Truth Consistency Across UI and API
Given work order WO-333 has calculated committed, actual, forecast, cap consumed, and headroom values When a user opens the Cost panel and a client requests GET /v1/costs/WO-333 with calc_version header Then both surfaces return identical values for all five metrics with at most $0.01 variance And the response includes calc_version and as_of timestamps matching the UI snapshot And values remain consistent during the session via snapshotting/ETag until the user refreshes
Concurrent Events Are Idempotent and Ordered by Version
Given work order WO-444 has quote Q-2 subtotal $900.00 and cap $1,500.00 and tax rate 8% applies And events have unique event_id and monotonically increasing vendor_version When the system receives labor_logged event (1 hour at $100.00) and quote_revised event (subtotal $950.00) out of order and with retries Then final committed equals $1,026.00 (subtotal $950.00 + 8% tax) and actual increases by $108.00 (labor $100.00 + 8% tax) And no amounts are double-counted despite retries And the audit log shows both events applied once with their vendor_version order
Overage Prediction & Scenario Engine
"As a landlord, I want early warnings with clear reasons and savings estimates so that I can act before costs exceed approved caps."
Description

Forecasts potential cap breaches before they occur by analyzing burn rates, remaining scope, vendor performance history, calendar-based rate multipliers, and similar past jobs. Produces probability and timing of breach, with explainable factors and confidence. Generates scenario suggestions (renegotiate scope, switch vendor, reschedule to off-peak window) including estimated savings, impact on SLA, and risk trade-offs. Serves as the decision intelligence layer powering Early Overage Alerts.

Acceptance Criteria
Live Burn Breach Forecast Output
Given an active work order with a cost cap, current spend, and incoming time-and-materials logs When the prediction engine evaluates the job Then it returns fields: breach_probability (0.00–1.00), breach_datetime (ISO 8601 or null), time_to_breach_hours (number or null), confidence (0.00–1.00), prediction_generated_at (ISO 8601) And the prediction latency is ≤ 2 seconds from receipt of new T&M data And the prediction refreshes at least every 15 minutes or within 1 minute of a new T&M log And values conform to schema and are persisted for audit with the job ID and model version
Quote & Change Order-Informed Prediction
Given a job with an approved quote, approved change orders, vendor performance history, and a scheduled work window When the prediction engine runs Then the model ingests: quoted_amount, approved_change_orders_total, vendor_overage_rate_on_similar_jobs, and calendar_rate_multiplier for the scheduled window And these inputs are reflected in the explanation output with non-zero contributions And removing any one of these inputs in an A/B diagnostic run changes breach_probability by ≥ 2 percentage points on the provided test fixtures
Explainable Factors & Confidence Display
Given any generated prediction When the explanation is requested Then the top 3–5 contributing factors are returned with human-readable labels and signed contribution values And the confidence is returned as a value between 0.00–1.00 with a definition string referencing data freshness and model variance And re-running the prediction with identical inputs within a 10-minute window yields the same factor list and contributions within ±1%
Scenario Suggestions with Savings, SLA Impact, and Risk
Given breach_probability ≥ 0.60 or time_to_breach_hours ≤ 72 When the scenario engine generates recommendations Then it returns at minimum three scenarios: Renegotiate Scope, Switch Vendor, Reschedule to Off-Peak And each scenario includes: estimated_savings_currency, sla_impact_hours (positive or negative), risk_level (Low/Medium/High), and scenario_confidence (0.00–1.00) And estimated_savings_currency accounts for vendor rates and calendar multipliers and is within ±10% of expected on test cases with known inputs And suggested vendors are limited to the preferred vendor list and available within the proposed window
Early Overage Alert Triggering & Deduplication
Given breach_probability crosses above 0.60 OR increases by ≥ 0.10 since the last alert OR time_to_breach_hours enters ≤ 72 When the engine evaluates the job Then an Early Overage Alert is emitted within 2 minutes containing: probability, time_to_breach, confidence, top factors, and one-click actions (renegotiate, switch vendor, request approval, reschedule) And alerts are deduplicated to at most one per job per 24 hours unless probability increases by ≥ 0.10 or breach_datetime advances by ≥ 24 hours sooner And the alert is routed to the property manager of record and audit-logged with delivery status
Backtest Accuracy & Calibration
Given a labeled dataset of ≥ 500 historical jobs from the last 6 months When the engine is backtested Then Brier score ≤ 0.18 And calibration slope between 0.90 and 1.10 and intercept between −0.05 and 0.05 And ROC AUC ≥ 0.75 And at threshold 0.60, recall of breached jobs ≥ 0.75 and precision ≥ 0.60 And results, parameters, and model version are stored for audit and reproducibility
Actionable Alerts with Next-Step CTAs
"As an operations lead, I want alerts that include one-click recommended actions so that I can resolve potential overruns quickly from any device."
Description

Delivers timely, deduplicated alerts via in-app banners, email, and mobile push/SMS when predicted spend approaches thresholds. Each alert contains clear context (current spend, forecast, cap, drivers) and one-click actions: Request Increased Approval, Renegotiate Quote, Switch Vendor, Reschedule to Off-Peak. Supports confirmation, undo, and escalation rules, and respects user notification preferences and quiet hours. Ensures the alert is a complete, actionable unit that reduces time-to-decision.

Acceptance Criteria
Early Overage Alert at Threshold Bands
Given a work order with cap C, current spend S, and forecasted spend F derived from quotes/changes/T&M burn And alert thresholds are configured at 80% and 95% of C When F first reaches or exceeds a configured threshold and no alert for that band has been sent in the last 30 minutes Then generate an overage alert within 60 seconds And deliver it via the user’s enabled channels (in-app banner and email/push/SMS per preferences) And set alert status to Active and log an immutable event with timestamp and data snapshot
Cross-Channel Alert Deduplication and Update Window
Given an Active alert exists for work order X at threshold band B When additional forecast updates occur that keep F within band B within a 30-minute dedup window Then do not create a new alert And update the existing alert content in place across all channels within 15 seconds And mark the alert as Updated with the latest timestamp When F crosses into a higher threshold band Then create exactly one new alert for the higher band and close the lower-band alert as Superseded
Alert Context Completeness and Data Accuracy
Given an overage alert is rendered on any channel Then it displays: current spend (S), forecast (F), cap (C), % of cap (F/C), risk band, top 3 cost drivers with brief explanations, and a deep link to the work order And numeric values reflect the latest system state with accuracy ±1% or $5 (whichever is greater) And the alert shows data freshness timestamp and the source of the forecast (quote/change/T&M) And the same values are consistent across in-app banner, email, and push/SMS payloads
One-Click Next-Step CTAs Execution and Routing
Given an Active overage alert is visible to an authorized user Then the alert displays exactly these CTAs: Request Increased Approval, Renegotiate Quote, Switch Vendor, Reschedule to Off-Peak When Request Increased Approval is selected Then a prefilled approval request (S, F, C, delta, rationale) can be submitted in ≤2 clicks and routed to the correct approvers, recording an audit log entry When Renegotiate Quote is selected Then a prefilled vendor message thread opens with context and suggested targets and on send notifies the current vendor and logs the action When Switch Vendor is selected Then a vendor selector opens filtered to preferred vendors with availability and rates, and on confirm reassigns the work order, notifies parties, and updates forecast When Reschedule to Off-Peak is selected Then the system proposes lower-cost windows based on vendor rates/availability and, on confirm, reschedules and recalculates forecast And each CTA completes within 5 seconds backend processing and returns a success state
Confirmation, Undo Window, and Conflict Handling
Given a user initiates any CTA from an overage alert Then a confirmation step summarizes the change and impact on F, C, and timeline And after confirmation, a success toast/banner includes an Undo option available for 10 minutes unless the action is marked irreversible When Undo is clicked within the window Then the system reverts the change, restores prior assignments/schedule/approval state, and appends a Reverted entry to the audit log And conflicting CTAs are disabled while an action is Pending and re-enabled upon completion or revert
Notification Preferences, Quiet Hours, and Escalation Policy
Given user notification preferences (channels and quiet hours) are configured When an alert is generated during quiet hours Then no push/SMS/email are sent; an in-app banner is created immediately and a queued digest email is scheduled for the next allowed window When an alert remains Unacknowledged for 4 business hours Then escalate to the next role per policy (primary → team lead → owner) respecting each recipient’s channel preferences and quiet hours And acknowledgement or action on any channel marks the alert Acknowledged across all channels within 15 seconds and cancels pending escalations
Decision Velocity KPI (Time-to-Decision Reduction)
Given a baseline median time-to-decision (T0) for maintenance spend decisions over the prior 30 days When measuring the first 30 days post-release of actionable alerts Then the median time from alert creation to first decisive action (approval request, renegotiation sent, vendor switched, or reschedule confirmed) is ≤ 0.80 × T0 with 95% statistical confidence And the KPI is reported in the product analytics dashboard with daily and weekly slices
Policy & Threshold Configuration
"As an account admin, I want to configure alert thresholds and policies per property and job type so that alerts align with our budgets and escalation rules."
Description

Provides admin controls to define alert thresholds (e.g., at 60/80/95% of cap), minimum variance to trigger, and default actions per portfolio, property, vendor tier, and job type. Allows configuration of cheaper scheduling windows, rate multipliers, escalation paths, and approval policies. Ensures governance alignment by tailoring sensitivity and actions to owner preferences and property-level budgets without engineering changes.

Acceptance Criteria
Tiered Alert Thresholds by Scope
Given an admin with permission configures portfolio-level thresholds at 60%, 80%, and 95% of cap And no property-level override exists When a job’s forecasted cost reaches 80% of cap Then the system evaluates the portfolio policy and flags the 80% threshold as met And queues an Early Overage Alert with the configured next-step options And the alert payload includes the matched threshold value, policy scope, and evaluation timestamp Given a property-level threshold override exists for the same job type When both portfolio and property thresholds are present Then the property-level thresholds take precedence during evaluation Given thresholds are duplicated, non-numeric, or not strictly ascending When the admin attempts to save the policy Then the system blocks the save and displays: "Thresholds must be numeric, unique, and ascending"
Minimum Variance Trigger
Given an admin sets minimum variance to $100 and 5% When a change increases the forecast by $80 and 2% Then no threshold alert is generated When a subsequent change increases the forecast by $120 and 1% Then a threshold alert is generated because the absolute variance rule is met When a subsequent change increases the forecast by $50 and 6% Then a threshold alert is generated because the percentage variance rule is met Given minimum variance is set to 0 for both absolute and percentage When any change affects the forecast and a threshold is crossed Then alerts are evaluated solely on threshold crossing
Default Actions by Vendor Tier and Job Type
Given an admin configures default actions per vendor tier and job type at each threshold (e.g., 60%=Renegotiate, 80%=Request Approval, 95%=Switch Vendor) When a Plumbing job with vendor tier Silver hits the 80% threshold Then the alert presents Request Approval as the primary action and other configured actions as secondary And selecting the primary action initiates the corresponding workflow automatically Given a vendor tier or job type without a specific override When a threshold is hit Then the system falls back to the property-level defaults, and if absent, to portfolio-level defaults
Cheaper Scheduling Windows Configuration
Given an admin defines cheaper scheduling windows for Property A as Mon–Thu 10:00–16:00 local time When scheduling options are generated for a job at Property A Then the system proposes at least one slot within the configured cheaper window when available And each proposed slot displays estimated cost impact vs the current slot Given no cheaper windows are configured for the property or portfolio When scheduling options are generated Then no cheaper-window suggestion is shown and no error is thrown
Rate Multipliers in Forecast and Threshold Evaluation
Given an admin configures rate multipliers for Vendor Tier Gold as: Normal=1.0, After-Hours=1.5, Weekend=2.0 When a job is scheduled for Saturday at 11:00 Then the forecasted cost applies the Weekend multiplier 2.0 And threshold evaluation uses the multiplied forecast for cap percentage calculation When the schedule is changed to Monday at 11:00 (Normal) Then the forecast updates using multiplier 1.0 and threshold status is recalculated accordingly And the alert (if any) updates to reflect the new forecast and threshold state
Escalation Paths and Approval Policies
Given an admin configures approval rules: ≥80% cap requires Property Manager approval within 4 business hours; ≥95% cap requires Owner approval; escalate to Head of Ops if SLA exceeded When a job crosses 80% of cap Then the system sends an approval request to the Property Manager and starts a 4-business-hour SLA timer And if not approved within the SLA, the request escalates per policy and all transitions are timestamped Given a user without the required approval role attempts to approve When they submit an approval action Then the system blocks the action and displays: "Insufficient permissions to approve at this level"
Effective Dating, Versioning, and Audit Trail
Given an admin schedules a new policy version to take effect at 2025-09-15T00:00:00 local time When the current time is before the effective time Then evaluations use the current active policy version When the current time reaches or passes the effective time Then evaluations use the new policy version And the audit log records who made the change, when, old value, new value, and scope Given the admin rolls back to a prior version When the rollback is confirmed Then the previous values are restored and the rollback event is captured in the audit log
Approvals & Vendor Workflow Integration
"As a coordinator, I want the recommended actions to seamlessly trigger the right workflows so that I don’t have to re-enter data or chase stakeholders."
Description

Integrates alerts with FixFlow’s approvals and dispatch workflows. Prefills approval requests with cost context and forecast, routes to designated approvers, and records outcomes. For vendor switching, surfaces preferred vendors with availability, pricing, SLA, and historical performance, enabling one-click reassignment that notifies stakeholders and updates the schedule. For rescheduling, presents a calendar with cost estimates by window based on rate cards. Eliminates duplicate data entry and ensures continuity across systems.

Acceptance Criteria
Early Overage → Auto-Prefilled Approval & Routing
Given an Early Overage Alert is generated with a projected cost breach ≥ the configured threshold, When the user selects "Request Approval" from the alert, Then the system creates an approval request prefilled with work order ID, property, scope/category, current quote or T&M burn-to-date, forecasted final cost, variance amount and %, and target completion date. And Then the request is routed to designated approver(s) per approval matrix (by property, category, and amount tier) within 5 seconds, with deduplication if an open approval for the same work order and scope already exists. And Then the created approval is linked to the originating alert and work order for traceability and visible in both records.
Early Overage → Approver Notification, Context, and Decision Recording
Given an approval request is created from an Early Overage Alert, When routing completes, Then the approver receives in-app and email notifications with a deep link and a summary (forecast, variance %, vendor quote/T&M detail, and risk notes). When the approver opens the request, Then the approval view displays the prefilled context and required next steps (renegotiate, switch vendor, reschedule) with one-click actions. When the approver Approves/Rejects/Requests Changes, Then the outcome is recorded with timestamp, approver ID, comments, and any modified cost ceiling, and the status syncs to the work order and alert within 3 seconds.
Vendor Switch from Alert with Preferred Vendor Intelligence
Given an Early Overage Alert recommends switching vendor, When the user selects "Switch Vendor", Then the system displays a ranked list of preferred vendors matching category and service area with: next availability, rate card (call-out, hourly/fixed), SLA adherence %, first-fix rate, on-time %, and average ticket cost for comparable jobs. When the user confirms a new vendor, Then the work order and upcoming appointment are reassigned in one click, the prior appointment is canceled with reason "cost overage mitigation", stakeholders (outgoing/incoming vendor, tenant, internal) are notified, the schedule updates, overlapping assignments are prevented, and all updates complete within 10 seconds. Then the reassignment and rationale are logged and visible in the audit trail linked to the alert and work order.
Reschedule to Cheaper Window with Cost-by-Window Estimates
Given an Early Overage Alert offers "Schedule in a cheaper window", When opened, Then a calendar shows available windows for the current vendor (and alternates if enabled) with estimated total cost per window calculated from rate cards and assumed duration, including delta vs current plan. When the user selects a cheaper window and confirms tenant availability, Then the appointment is rescheduled, projected cost is updated on the work order and alert, conflicts are checked to avoid double-booking, and all impacted parties are notified. If rate card data is missing for a window, Then that window is marked "Estimate unavailable" and cannot be selected.
Eliminate Duplicate Data Entry Across Alerts, Approvals, and Dispatch
Given required data already exists on the work order and alert, When initiating approval, vendor switch, or reschedule from the alert, Then all corresponding forms are prefilled with existing data, requiring zero re-entry for fields already present and valid. When a prefilled field is edited during any step, Then the change propagates to all linked records (alert, approval, work order, dispatch) within 5 seconds, maintaining a single source of truth. Then form submission must not block on fields already on file unless validation fails; measured manual input per flow ≤ 1 field on average across a standard regression dataset.
Continuity, Audit Trail, and Cross-System Synchronization
Given any action completes (approval outcome, vendor switch, reschedule), Then the system writes an immutable audit entry with actor, action, before/after values, timestamps, and linkage IDs (work order, alert, approval, vendor assignment). And Then Approvals, Dispatch, Vendor Portal, and Notifications reflect the new state consistently within 10 seconds; page refresh shows the updated state with no stale data. If an integration or routing step fails, Then the user sees a clear, actionable error with retry, no partial assignments remain, and the failure is logged with a correlation ID for support.
Audit Trail, Metrics & Savings Reporting
"As a portfolio owner, I want reports showing avoided overages and savings so that I can quantify impact and refine vendor and policy decisions."
Description

Captures an immutable log of predictions, thresholds crossed, alerts sent, user actions, approvals, and final outcomes. Provides dashboards for predicted vs. actual overages, avoided overruns, realized savings, time-to-action, and vendor overage rates. Supports filtering by date range, property, vendor, and job type, with CSV export. Establishes data retention policies and access controls to support compliance and portfolio-level optimization.

Acceptance Criteria
Immutable Audit Log for Early Overage Events
Given a job with Early Overage Alert enabled and an overage prediction is generated, When the system predicts a cap breach, thresholds are crossed, alerts are sent, user actions (renegotiate, switch vendor, request approval, reschedule) are taken, approvals are granted/denied, and the job is closed, Then the system records a distinct audit event for each occurrence with fields: event_id (UUIDv4), job_id, vendor_id, property_id, actor_id (user or system), event_type, event_payload snapshot, created_at (ISO 8601 UTC with millisecond precision), sequence_number (monotonic per job), and source (UI/API/System). Given multiple events for the same job, When retrieving the audit trail via UI or API, Then events are returned in ascending sequence_number and created_at, with no gaps in sequence_number for that job. Given a recorded audit event, When attempting to update or delete it via any supported interface, Then the operation is rejected with HTTP 403 and a new security audit event is appended noting the attempt and actor.
Tamper-Evident Integrity and Immutability
Given audit storage is initialized, When an audit event is appended, Then record_hash = SHA-256(previous_record_hash + canonical_record_json) is stored and a daily anchor_hash is generated at 00:05 UTC for chain verification. Given the daily integrity scan runs, When a chain break or checksum mismatch is detected, Then a Sev2 alert is created within 5 minutes, affected records are flagged integrity_status = "Failed", and dashboards display an integrity warning banner until resolved. Given any user role, When attempting to physically delete or mutate an existing audit record through supported interfaces, Then it is not possible; only append-only compensating events are allowed and such attempts are logged as security events.
Metrics Accuracy — Predicted vs Actual, Savings, Time-to-Action, Vendor Rates
Given a completed job with approved_cap, predicted_cost_at_first_alert, predicted_cost_without_action (snapshotted at alert), final_invoice_amount, first_alert_sent_at, and first_user_action_at, When metrics are computed, Then: - predicted_overage = max(0, predicted_cost_at_first_alert - approved_cap) - actual_overage = max(0, final_invoice_amount - approved_cap) - avoided_overrun = max(0, predicted_overage - actual_overage) - realized_savings = max(0, predicted_cost_without_action - final_invoice_amount) - time_to_action_seconds = (first_user_action_at - first_alert_sent_at) in seconds - vendor_overage_rate = jobs_with_actual_overage / total_jobs for the vendor in the selection window Given a validation dataset of 100 known jobs, When the dashboard renders, Then each computed metric matches an independently calculated control within ±0.5% for currency (rounded to 2 decimals) and ±1 second for time_to_action; vendor_overage_rate matches to 4 decimal places.
Filtering, Grouping, and Drill-Down Controls
Given filters for date_range (inclusive, UTC), property (multi-select), vendor (multi-select), job_type (multi-select), and action_taken (Yes/No), When any combination is applied, Then dashboard KPIs, charts, and tables include only matching jobs and exclude all others. Given up to 50,000 jobs in the portfolio, When applying or changing filters or grouping (none, by vendor, by property, by job_type), Then 95th percentile response time ≤ 2.0s and 99th percentile ≤ 4.0s to render updated results. Given a user clicks a KPI, chart segment, or table aggregate, When invoking drill-down, Then they see a job list for that slice and can open a job to view its full audit trail within one additional click, with navigation preserving the original filter context.
CSV Export — Fidelity and Performance
Given any current filter and sort state, When the user exports to CSV, Then the file contains all visible rows and columns plus job_id, property_id, vendor_id, and computed metrics (predicted_overage, actual_overage, avoided_overrun, realized_savings, time_to_action_seconds, vendor_overage_rate), using UTF-8 encoding, comma delimiter, header row, RFC 4180 quoting, ISO 8601 UTC timestamps, and currency rounded to 2 decimals. Given up to 200,000 rows match the filters, When export is requested, Then a synchronous download completes in ≤ 15 seconds; for >200,000 rows, Then an asynchronous export job completes and provides a download link within 15 minutes and emails the requester. Given a random 50-row sample, When comparing CSV values against the UI/API for the same filter context, Then all values match exactly, subject to the defined formatting rules.
Role-Based Access and PII Controls
Given roles Owner, Manager, Vendor, and Auditor, When accessing Audit Trail and Metrics, Then: - Owner/Manager: full access within their organization’s portfolio; can export CSV; can view vendor-level savings and rates - Vendor: restricted to jobs where vendor_id matches; cannot view aggregate metrics across other vendors; savings and rates exclude non-owned vendors - Auditor: read-only access; can view integrity flags; CSV export disabled if export_restricted flag is set - Unauthorized users: receive HTTP 403 Given tenant PII fields (name, phone, email), When viewed by Vendor or Auditor roles, Then values are masked or redacted in UI and omitted from CSV exports. Given any access denial, masking, or export attempt by a restricted role, When it occurs, Then a security audit event is appended with actor, reason, and timestamp.
Data Retention, Purge, and Legal Hold
Given retention_policy_years = 7 for audit events and 3 for raw vendor invoices, When a record exceeds its retention period and is not under legal hold, Then it is purged within 30 days by a scheduled job at 02:00 UTC and a non-destructive tombstone event is appended. Given a legal_hold flag at portfolio, property, or job level, When enabled, Then purge is skipped for affected records and dashboards indicate "On Legal Hold"; When disabled, Then purge eligibility recalculates within 24 hours. Given a standard user attempts to retrieve a purged record, When requesting via UI or API, Then the response is HTTP 410 Gone; backups are inaccessible to standard users and any restoration requires break-glass admin approval and is logged as a security event.

Vendor Caps

Applies vendor-specific thresholds based on past accuracy, SLA performance, and charge variance. Trusted vendors get higher auto-approval headroom; repeat over-spenders face tighter limits—driving better behavior without micromanagement.

Requirements

Vendor Performance Scoring Engine
"As a property manager admin, I want vendor scores that reflect real performance so that auto-approval limits can adjust fairly and improve outcomes."
Description

Compute a composite, per-vendor performance score from historical data (estimate-to-invoice accuracy, SLA adherence at each stage, charge variance vs. benchmarks, first-time fix rate, callback rate, documentation completeness). Support configurable metric weights, minimum sample sizes, Bayesian smoothing to reduce volatility, and normalization by trade. Refresh scores daily; backfill 12 months. Expose results via API and internal tables for downstream use. Handle sparse/new vendors with probation defaults. Ingest data from work orders, invoices, and SLA trackers; write scores to vendor profiles. Provide alerting on significant score shifts. Outcome: an objective, resilient basis for cap decisions that encourages better behavior.

Acceptance Criteria
Composite Score Calculation Across Metrics
Given a vendor with valid historical data for estimate-to-invoice accuracy, SLA adherence by stage, charge variance vs. benchmarks, first-time fix rate, callback rate, and documentation completeness over the last 12 months, When the scoring engine runs, Then per-metric scores are computed on a 0–100 scale using trade-normalized benchmarks and recorded with their sample sizes. Given per-metric scores and the active weight profile, When the composite score is computed, Then the composite equals the weighted average of per-metric scores with weights summing to 1.0±0.001 and is rounded to 1 decimal place within [0,100].
Configurable Metric Weights with Defaults and Validation
Given an admin submits a weight profile per trade with an effective_from date, When the profile is saved, Then validation enforces each weight ∈ [0,1] and total weight sum = 1.0±0.001, otherwise the save is rejected with a clear error. Given multiple weight profiles exist for a trade, When scoring executes at time T, Then the latest effective profile at or before T is applied; if none exist, the system default profile is applied and the fallback is logged. Given a malformed or missing weight key for a required metric, When saving or loading the profile, Then the operation fails and no partial profile is used.
Minimum Sample Size and Bayesian Smoothing for Stability
Given a metric has n observations and the configured minimum sample size m and prior (mu, k) for the vendor’s trade, When n < m, Then the metric score uses posterior = (n*observed_mean + k*mu)/(n + k) and records n and k; When n ≥ m, Then the metric score equals observed_mean (k = 0). Given a vendor is new with total observations below the configured global minimum G, When scoring runs, Then a probation default composite score (per trade) is assigned, the vendor is flagged as probation, and the date of first calculated (non-probation) score is recorded once G is met. Given metric-level missing data, When computing the composite, Then missing metrics contribute via their priors only and the composite still uses a full weight vector normalized to 1.0.
Trade Normalization and Benchmarking
Given two vendors in different trades with identical standardized (z-score) performance against their respective trade benchmarks for all metrics, When composite scores are computed, Then their composites are equal within ±0.5 points. Given the charge variance metric, When calculating the per-metric score, Then variance is evaluated relative to the benchmark distribution for the vendor’s trade and property region (if configured), not a global benchmark. Given a vendor’s trade changes effective on date D, When scoring on or after D, Then the vendor’s normalization and benchmarks switch to the new trade starting D and historical scores prior to D remain unchanged.
Daily Refresh and 12-Month Backfill
Given the daily scheduler at 02:00 UTC, When the scoring job runs, Then all vendor scores are refreshed using data through 23:59:59 UTC of the previous day and last_refreshed_at is set to the job completion time. Given an environment initialization or manual backfill trigger, When backfill executes, Then 12 months of historical scores per vendor are computed and stored within 24 hours and tagged with their score_date. Given a partial failure mid-run, When the job is retried, Then processing resumes idempotently without duplicate score records for the same vendor_id and score_date.
Data Ingestion and Write-Back to Vendor Profiles
Given new or updated work orders, invoices, and SLA events ingested before 23:00 UTC, When the next daily scoring run executes, Then these records are included in metric calculations for the affected vendors. Given score computation completes for a vendor, When persisting results, Then the vendor profile is updated atomically with composite_score, per_metric_scores, sample_sizes, smoothing_parameters, effective_weights_version, trade, and last_updated_at; repeated writes with the same inputs do not create duplicates (idempotent). Given a data ingestion or transformation error for a vendor, When the scoring run executes, Then no score update is applied for that vendor, an error event is logged with vendor_id and cause, and the job continues for other vendors.
API and Internal Tables Exposure with Score Shift Alerts
Given an authenticated client, When GET /api/vendors/{id}/performance-score is called, Then the response is 200 and includes vendor_id, trade, composite_score, per_metric_scores, sample_sizes, weights_version, smoothing_info, and last_refreshed_at; P99 latency ≤ 300 ms for cached results. Given internal consumers, When querying the vendor_scores table, Then records include vendor_id, score_date, composite_score, per_metric_scores (JSON), sample_sizes, weights_version, smoothing_parameters, created_at, and are partitioned/indexed by score_date for efficient range queries. Given a vendor’s composite score changes by ≥ 10 points absolute or ≥ 20% relative within 24 hours (configurable), When the daily job completes, Then an alert event is emitted containing vendor_id, previous_score, current_score, deltas, and top contributing metrics; duplicate alerts for the same vendor within 24 hours are suppressed.
Dynamic Auto-Approval Thresholds
"As an operations lead, I want auto-approval caps to scale with vendor reliability so that routine repairs flow without review and risky spend is contained."
Description

Map vendor performance scores to per-trade, per-urgency dollar caps that control one-click approvals. Define configurable tiers and headroom bands; include defaults for new vendors. Recalculate caps on a schedule (e.g., monthly) and upon major score changes. Support manual overrides with effective/expiry dates and reason codes. Enforce caps during approval flow with clear under/over-cap indicators. Handle multi-currency, tax-in/out configurations, and caching for low-latency checks. Outcome: reward reliable vendors with more autonomy while constraining risk from inconsistent vendors.

Acceptance Criteria
Performance-Tier to Cap Mapping (Per Trade & Urgency)
Given tier configuration defines score tiers: Bronze 0–59, Silver 60–79, Gold 80–89, Platinum 90–100, and headroom bands for Plumbing/Emergency as Bronze $250, Silver $400, Gold $600, Platinum $800 (tax-exclusive) And vendor V has performanceScore 82 and currency USD When the system retrieves the auto-approval cap for trade "Plumbing" with urgency "Emergency" for vendor V Then the returned capAmount is 600 USD and capBasis is "tax-exclusive" And the cap source is "calculated-from-tier" And the evaluation uses the configured trade/urgency band for the vendor's mapped tier
Default Caps for New Vendors
Given vendor V has no performance history and no assigned tier And the default tier "Provisional" is configured with caps: Plumbing/Emergency 300 USD and Plumbing/Normal 200 USD (tax-exclusive) When the system retrieves the cap for trade "Plumbing" with urgency "Emergency" for vendor V Then capAmount is 300 USD and capBasis is "tax-exclusive" And the cap source is "default-tier" And once vendor V receives an initial performance score, subsequent cap requests use the mapped tier instead of the default
Scheduled and Event-Driven Cap Recalculation
Given a monthly recalculation schedule is configured for 01:00 UTC on the 1st of each month When the schedule triggers Then caps for all vendors are recalculated and become effective within 15 minutes And caches for updated vendor/trade/urgency keys are invalidated upon completion Given majorScoreChangeThreshold is configured as 5 points and tier boundary crossings always trigger recalculation And vendor V's performanceScore changes from 83 to 76 When the score update is saved Then caps for vendor V are recalculated within 60 seconds And updated caps are used by approval checks immediately after recalculation
Manual Overrides with Effective/Expiry and Reason Codes
Given an override exists for vendor V, trade "HVAC", urgency "Emergency" with capAmount 900 USD, effectiveStart 2025-10-01T00:00:00Z, effectiveEnd 2025-12-31T23:59:59Z, and reasonCode "SeasonalRates" When an approval check occurs on 2025-11-15T12:00:00Z Then the capAmount used is 900 USD and cap source is "manual-override" When an approval check occurs on 2026-01-01T00:00:01Z Then the capAmount reverts to the calculated/default value for vendor V When attempting to save an override without a reasonCode or with effectiveEnd earlier than effectiveStart Then the save is rejected with a validation error When attempting to create an override that overlaps an existing override for the same vendor/trade/urgency Then the save is rejected with a validation error
Approval Flow Enforcement with Under/Over-Cap Indicators
Given capAmount is 600 USD (tax-exclusive) for vendor V, trade "Plumbing", urgency "Emergency" And an estimate is submitted for 585 USD (net of tax) When the approval flow evaluates the estimate Then the request is marked "Under Cap" And one-click auto-approval is enabled And the approval result records "auto-approved-within-cap" Given capAmount is 600 USD And an estimate is submitted for 600 USD (net of tax) When evaluated Then the request is marked "Under Cap" (equality is within cap) And one-click auto-approval is enabled Given capAmount is 600 USD And an estimate is submitted for 605 USD (net of tax) When evaluated Then the request is marked "Over Cap" And one-click auto-approval is disabled And the approval flow requires manual approval or a cap increase
Multi-Currency and Tax Mode Handling
Given caps are stored/evaluated in USD and taxMode is "tax-inclusive" And vendor V submits an estimate of EUR 540 (VAT included) And the FX rate at evaluation time is 1 EUR = 1.20 USD When evaluating against capAmount 600 USD Then the converted estimate total is 648 USD and the result is "Over Cap" Given taxMode is changed to "tax-exclusive" And the same estimate has net amount EUR 500 and tax EUR 40 When evaluating against capAmount 600 USD Then the converted net amount is 600 USD and the result is "Under Cap" Given the estimate currency matches the cap currency When evaluating Then no currency conversion is performed
Low-Latency Cap Checks with Caching and Invalidation
Given a cache TTL of 15 minutes is configured for cap lookups When 10,000 approval checks are performed under normal load Then the p95 latency for cap retrieval is <= 50 ms and the p99 is <= 100 ms Given a cap recalculation or manual override occurs for vendor V/trade/urgency When the next approval check for that key is performed Then the cache entry is invalidated/refreshed, and the new cap is used within 60 seconds of the change Given a cache miss When a cap lookup occurs Then the system falls back to the source of truth and returns a result with p95 <= 200 ms
Risk-Based Exception Routing
"As a property manager, I want out-of-cap jobs to be sent to the right approver with context so that I can decide quickly and keep SLAs intact."
Description

When a request exceeds cap or triggers risk flags (e.g., atypical cost vs. historical median, low vendor score, repeat callbacks), route to the appropriate approver with contextual insights. Generate a decision card showing cap amount, score rationale, variance analysis, SLA impact forecast, and suggested alternative vendors within cap. Support configurable escalation paths and notifications. Log decisions and outcomes for continuous tuning. Outcome: faster, higher-quality human decisions on exceptions without micromanagement.

Acceptance Criteria
Route Exceptions on Cap Exceed or Risk Flags
Given a work order is created with vendor cap C and estimated cost E for a specific property and category And risk thresholds are configured for variance %, vendor score minimum, and repeat callback count When E > C or any risk flag evaluates true (variance > 25% above 12‑month median for the same category/property, vendor score < 70, or repeat callbacks ≥ 2 in the last 90 days) Then the request is not auto‑approved And it is routed to the configured approver queue corresponding to the highest severity matching rule And the route decision stores the triggering flags and their computed values in the request metadata And the request status reflects "Exception Routing" in the UI and API And an event "exception.routed" is emitted with requestId, approverQueueId, and flags[]
Decision Card Shows Contextual Insights
Given an exception‑routed request awaiting human review When an approver opens the decision card Then the card displays: vendor cap amount, current estimate, over‑cap delta and percentage, vendor risk score with rationale, variance analysis vs 12‑month median, SLA impact forecast, and a list of suggested alternative vendors within cap And all displayed numeric values match backend calculations within ±0.5% And the decision card loads within ≤ 2.0 seconds at P95 And each rationale item links to its underlying data source reference within the app
Escalation Path Triggers on Approver SLA
Given an exception is routed to Primary Approver Group A with an escalation SLA of N hours configured When no decision (approve, reject, reassign) is recorded within N hours Then the request escalates to Secondary Approver Group B per configuration And a new task is created for Group B with the prior context preserved And audit history records the escalation time, fromGroup, toGroup, and reason "SLA elapsed" And duplicate escalations do not occur for the same SLA window
Stakeholder Notifications on Exception
Given an exception routing event occurs When routing completes to an approver group Then the current approver(s) receive in‑app notification immediately and email/SMS per their preferences within 1 minute at P95 And the notification includes requestId, property, vendor, cap, estimate, top 3 risk flags, and a deep link to the decision card And on escalation, the new approver(s) are notified and prior approvers receive an FYI without action prompts And notifications are de‑duplicated so recipients do not receive more than one notification per event per channel within 10 minutes
Alternative Vendor Suggestions Within Cap
Given an exception card is generated for a request exceeding the current vendor’s cap When the system computes suggestions Then at least 2 alternative vendors are listed (if available) that meet: within‑cap projected cost, coverage of location and trade, and active status And suggestions are ranked by composite score (performance, price accuracy, SLA) descending And suspended or out‑of‑coverage vendors are excluded And the approver can reassign with one click to a suggested vendor, which updates routing and logs the reassignment action And if fewer than 2 are available, the card displays "No suitable alternatives" with the reason (e.g., none within cap)
Decision and Outcome Audit Logging
Given any human decision or routing change on an exception When an action is taken (approve, reject, request changes, reassign, escalate, override cap) Then an immutable audit record is created capturing: requestId, actorId, role, actionType, timestamp (UTC), reasonCode, free‑text comment, priorRoute, newRoute, cap at decision, estimate at decision, and flags[] And upon job completion, the final outcome fields are appended: final invoice amount, variance vs estimate, SLA met/missed, and callback occurrence (Y/N) And the audit record is retrievable via API within 5 seconds of write at P95 And fields containing PII are stored and served according to the platform’s redaction policy when accessed by non‑privileged roles
Risk Threshold Configuration and Evaluation
Given an admin updates risk thresholds (cap exceed handling, variance %, minimum vendor score, repeat callback count) at portfolio/property/vendor scopes When the configuration is saved Then validation prevents unsafe values (e.g., variance % < 5% or vendor score min > 95) and shows specific error messages And successful changes are versioned and recorded in configuration audit logs with who/when/what And new requests created ≥ 60 seconds after save are evaluated using the updated thresholds And a test harness can simulate a request to verify each threshold triggers or does not trigger as configured
Caps Change Audit & Transparency
"As a portfolio owner, I want a clear audit of how caps were set or changed so that spending controls are compliant and defensible."
Description

Maintain an immutable audit trail for cap computations and overrides, capturing timestamp, actor/system, previous/new values, score snapshot, rule version, and reason. Present a “Why this cap” explanation near approvals and vendor records. Provide export (CSV/API) and role-based access controls. Retain records for 7 years. Outcome: defensible, transparent controls that build trust and meet compliance needs.

Acceptance Criteria
Auto-Computed Cap Change Audit Record
Given the cap engine recomputes a vendor’s cap and the new value differs from the current value When the system applies the change Then it creates a single audit record containing vendor_id, cap_identifier, previous_value, new_value, timestamp_utc (ISO 8601), actor="system:cap-engine", rule_version, score_snapshot, reason="auto-recompute" And the audit record is immutable (updates and deletes are prevented at the storage layer) And the audit record is queryable via the vendor audit UI and the audit API
Manual Override Audit with Reason and Actor
Given a user with Cap Override permission edits a vendor’s cap in the UI When they submit the change with a non-empty justification Then an immutable audit record is created containing vendor_id, cap_identifier, previous_value, new_value, timestamp_utc (ISO 8601), actor_user_id, actor_role, rule_version, score_snapshot, reason="manual-override", justification_text And the override is marked as active on the vendor cap (override_flag=true) until superseded And the audit record appears at the top of the vendor’s audit history
"Why This Cap" Explanation Near Approvals and Vendor Records
Given a user views a pending approval or vendor profile that displays a vendor cap When the cap value is rendered Then a "Why this cap" control is shown adjacent to the cap value And activating it shows: current cap value, change_type (auto/manual), last_updated timestamp_utc, rule_version, reason/justification, and key drivers from score_snapshot (e.g., SLA performance, charge variance, accuracy) And the panel includes a link to "View full audit history" that opens the chronological audit log for that vendor
Audit Trail API Export with Filters and Pagination
Given an authorized user calls GET /api/audit/vendor-caps with filters (date_from, date_to, vendor_id optional, change_type optional, actor optional) and pagination (limit, cursor/offset) When matching records exist within the caller’s authorization scope Then the API responds 200 with a paginated list containing id, vendor_id, cap_identifier, previous_value, new_value, change_type, actor/actor_role, timestamp_utc, rule_version, reason, justification_text, score_snapshot And records outside the caller’s authorization scope are excluded And when no records match, the API returns 200 with an empty list
CSV Export of Cap Change Audit Records from UI
Given a user with export permission applies filters in the audit UI When they request a CSV export Then a CSV file downloads containing a header row and columns: id, vendor_id, cap_identifier, previous_value, new_value, change_type, actor, actor_role, timestamp_utc, rule_version, reason, justification_text, score_snapshot_json And the CSV encodes as UTF-8 with RFC 4180-compliant quoting and line endings And the rows in the file match the filtered result set count and order visible in the UI
Role-Based Access Controls for Viewing, Exporting, and Overriding
Given role-based permissions are configured (Admin, Manager, Staff, Auditor, Vendor) When users attempt to view audit history, export CSV/API, or perform cap overrides Then Admin and Auditor can view and export all audit records; Admin can override caps And Manager can view/export records only for vendors in their assigned portfolio and can override within that scope And Staff can view "Why this cap" panels but cannot open full audit history, export, or override And Vendor cannot access audit history or exports And unauthorized attempts receive HTTP 403 (API) and hidden/disabled controls (UI)
7-Year Retention and Immutability Enforcement
Given audit records are stored with created_at timestamps When retention policies are enforced Then audit records are preserved for 7 years from created_at and are not deletable by any user or process before expiry And after 7 years, records are eligible for automatic purge jobs that remove them from primary storage and scheduled backups And any attempt to modify or delete non-expired records is blocked and logged as a security event
Category & Property-Specific Caps
"As a regional manager, I want caps tailored to each property and trade so that policies match local realities without manual reviews."
Description

Allow caps to vary by trade (e.g., plumbing, electrical), property, region, and urgency (emergency vs. routine). Implement an inheritance model with organization defaults, group-level overrides, and per-property exceptions, plus sensible fallbacks. Support bulk edit/import, validation, and preview of effective caps. Outcome: align spending controls with local market costs and risk appetites without manual case-by-case reviews.

Acceptance Criteria
Effective Cap Inheritance and Precedence Resolution
Given organization, group, region, and property caps exist for trade T and urgency U When computing the effective cap for property P in group G and region R Then the property-level cap is used if defined Given group and organization caps exist for trade T and urgency U, and no property or region cap exists When computing the effective cap for property P in group G Then the group-level cap is used Given both group-level and region-level caps exist for trade T and urgency U, and no property cap exists When computing the effective cap for property P in group G and region R Then the stricter (lower) value between group and region caps is used Given only the organization default cap exists for trade T and urgency U When computing the effective cap for property P Then the organization default cap is used Given no cap exists at property, group, region, or organization for trade T and urgency U When computing the effective cap Then the effective cap is 0 and the request requires manual approval
Urgency-Specific Cap Selection
Given separate caps are defined for routine and emergency for trade T at any scope When a request is marked emergency Then the emergency cap is used Given only a routine cap is defined for trade T at the applicable scope When a request is marked emergency Then the routine cap is used as a fallback and the preview indicates fallback_used = true Given only an emergency cap is defined for trade T at the applicable scope When a request is marked routine Then the emergency cap is used as a fallback and the preview indicates fallback_used = true Given the urgency of a request is changed prior to decision When recalculating the effective cap Then the newly applicable cap is applied immediately
Bulk Import of Caps with Validation and Partial Apply
Given a CSV file with columns [scope, scope_id, trade, urgency, cap_amount] and 1–10,000 rows When uploaded to the bulk import endpoint Then rows with all required fields and cap_amount > 0 are applied, and processing completes within 60 seconds Given rows with missing fields, invalid scope/scope_id, unknown trade/urgency, or non-positive/non-numeric cap_amount When processing the import Then those rows are rejected with per-row error codes, and valid rows are still applied Given duplicate rows for the same [scope, scope_id, trade, urgency] appear in the file When importing Then the last occurrence overwrites earlier ones, and the summary indicates duplicates_resolved count Given the same valid file is re-imported When processing completes Then no changes are applied and the summary indicates 0 updates (idempotent) Given the import finishes When the summary is returned Then it includes counts for total_rows, applied_rows, rejected_rows, warnings, and processing_time_ms
Effective Cap Preview with Provenance
Given a user selects property P, trade T, and urgency U When requesting an effective cap preview Then the response includes effective_cap_amount, source_scope, source_identifier, and a fallback_chain array Given both group and region caps exist and no property cap exists When requesting a preview Then the response shows both candidate caps and indicates stricter_applied = true with the chosen value Given no caps exist for the selection When requesting a preview Then the response returns effective_cap_amount = 0 and decision_hint = "manual_approval_required" Given typical system load When requesting a preview Then the p95 response time is <= 500 ms Given a cap rule is updated When requesting a preview for the same selection Then the preview reflects the change immediately (strong consistency)
Save-Time Validation and Duplicate Rule Prevention
Given a user creates or edits a cap rule When cap_amount <= 0 or has more than 2 decimal places Then the save is rejected with a validation error Given a cap rule already exists for [scope, scope_id, trade, urgency] When the user attempts to create another rule via the UI Then the save is blocked and the user is prompted to edit the existing rule instead Given a cap rule references an unknown scope, scope_id, trade, or urgency When saving Then the save is rejected with specific error codes Given a cap rule is valid When saving Then the rule is persisted and visible in listings and preview within 1 second
Auto-Approval Decision Enforces Effective Cap
Given a new maintenance request for property P with trade T, urgency U, and estimated_cost E When the effective cap C is computed for P,T,U Then if E <= C the request is eligible for auto-approval, else it requires manual approval Given vendor-level auto-approval headroom H also applies When deciding auto-approval Then the effective limit used is min(H, C) Given no estimate E is available When deciding auto-approval Then the request requires manual approval Given the effective cap is 0 due to missing caps When deciding auto-approval Then the request requires manual approval
Vendor Performance Feedback
"As a vendor partner, I want to see how my performance affects my approval cap so that I know what to improve to earn more autonomous work."
Description

Provide vendors a monthly performance summary that explains their current cap tier, key metrics driving it, and specific actions to reach the next tier. Deliver via email with a link to a lightweight web view; support opt-in/opt-out and throttling. Include trend charts and benchmark comparisons. Outcome: nudge vendors toward better behavior through transparency rather than manual supervision.

Acceptance Criteria
Monthly Performance Email Delivery
- Given it is the first business day of the month at 09:00 in the vendor’s time zone, when the vendor is opted-in and had at least one work order in the previous calendar month, then the system sends exactly one “FixFlow Performance Summary — {Month YYYY}” email within 10 minutes. - Given the previous month has zero vendor activity, when the monthly job runs, then no summary email is sent and no web view is generated. - Given an email send attempt fails with a transient error, when retry policy triggers, then the system retries up to 3 times with exponential backoff and logs send outcomes. - Given an email hard-bounces or is marked as spam, then the vendor’s summary email status is set to “Email Suspended,” no further retries occur, and the account owner is notified within 1 hour.
Secure Web View Access
- Given a vendor opens the summary link from the email, when the token is valid and unexpired (<=30 days since issuance) and matches the vendor, then the web view loads in under 2.0s P95 and displays only that vendor’s data without login. - Given the token is expired, revoked, or mismatched, when the link is opened, then an expiration page is shown, no data is leaked, and a “Request New Link” action is available. - Given a tokenized link is accessed more than 5 times within 10 minutes, then subsequent requests are rate-limited with HTTP 429 and a retry-after header.
Cap Tier Explanation and Drivers
- Rule: The email and web view display the current cap tier name, approval headroom amount, effective date range, and last change date. - Rule: The drivers section shows accuracy score (%), SLA on-time (%), and charge variance (%) for the last month and rolling 3-month average, with definitions inline. - Rule: All metric values match backend calculations within ±0.1% and use the same rounding. - Given the vendor’s cap tier changed in the previous month, then the summary highlights the change (Up/Down/No Change) and lists the top 3 contributing metrics.
Actionable Path to Next Tier
- Given the vendor is not in the top tier, when the web view loads, then it shows up to 3 prioritized actions with quantified targets (e.g., “Reduce charge variance to ≤8% for 2 consecutive months”) and estimated impact on tier eligibility. - Given the vendor is already in the top tier, then the web view shows “Maintain Tier” guidance with thresholds to keep the status for the next review. - Rule: Each action displays current value, target value, and progress (0–100%), and links to supporting help content. - Rule: Action generation is deterministic given the vendor’s metrics; the same inputs produce the same actions.
Trend Charts and Benchmarks
- Rule: The web view includes mobile-responsive charts for accuracy, SLA on-time, and charge variance covering the last 6 months. - Rule: Each chart overlays portfolio median and top-quartile benchmarks with a legend and tooltip showing Month, Vendor Value, Median, and Top Quartile. - Given any month has no data, then the chart displays a gap/“No Data” marker without interpolating values. - Performance: P95 time-to-first-chart-render is ≤1.5s on 4G.
Opt-In/Opt-Out and Audit
- Given a vendor clicks “Unsubscribe” in the email footer, when the confirmation is submitted, then future monthly summaries are not sent and a confirmation email is delivered within 10 minutes. - Given a vendor opts back in via profile or confirmation page, then the status is updated immediately and the next scheduled summary will be sent. - Rule: Opt status changes are recorded with timestamp, actor, method (email/profile/admin), and previous/new values; admins can export the audit log. - Rule: Default state for new vendors is opt-in; vendors with “Email Suspended” cannot be auto re-subscribed.
Consolidation and Send Throttling
- Rule: A vendor email address receives at most one performance summary per calendar month; manual resend is limited to 3 times per 24 hours per vendor. - Given a vendor serves multiple owners/properties within FixFlow, then the summary consolidates metrics and clearly sections results by owner/property, and only one email is sent to that address. - Rule: Bulk send respects a platform-wide rate limit of ≤1,000 emails/minute; overruns are queued without loss and processed in order. - Monitoring: The system exposes send metrics (queued, sent, bounced, open rate) to the ops dashboard within 5 minutes.

Budget Buckets

Defines monthly or quarterly spend ceilings by property and category, showing real-time burn and remaining headroom. Throttles or pauses auto-approvals as budgets near limits, preventing end-of-period surprises.

Requirements

Budget Bucket Setup & Hierarchy
"As a property manager, I want to set monthly and quarterly budgets by property and category so that I can control spend and prevent overages."
Description

Enable creation and management of spend ceilings per property and maintenance category with monthly or quarterly periods. Support threshold settings (e.g., warning at 80%, hard stop at 100%), effective dates, and mid-period start handling via proration. Provide reusable templates and defaults for rapid setup across portfolios. Enforce category taxonomy alignment with FixFlow smart triage to ensure incoming work orders and purchase orders map correctly. Validate against overlapping or conflicting budgets and surface conflicts in the UI. Expose CRUD APIs and bulk import to seed budgets at scale. This foundation supplies the approval engine and vendor routing with authoritative budget data, delivering predictable spend control and portfolio consistency.

Acceptance Criteria
Create Budget Bucket by Property and Category
Given an authenticated Org Admin selects a property and a maintenance category from the FixFlow taxonomy When they create a budget bucket with period type Monthly or Quarterly and a positive ceiling amount and currency Then the system persists the bucket with a unique ID, associates it to the selected property and category, and records an audit log entry And When thresholds are not provided Then the system applies default thresholds of warn=80% and hard stop=100% And When the operation completes Then the API responds 201 with the created resource payload including periodType, ceiling, currency, thresholds, and effective dates
Threshold Warning and Hard Stop Enforcement
Given a budget bucket with ceiling, warnThreshold=80%, and hardStopThreshold=100% When cumulative period spend for the bucket (Approved + Pending approvals) reaches the warn threshold Then the bucket state is set to Warn, a UI banner is displayed on the property budget view, and a budget.threshold.warn event is emitted for downstream systems When cumulative period spend would cause the bucket to meet or exceed the hard stop threshold Then the bucket state is set to HardStop, a budget.threshold.hard_stop event is emitted, and a flag is exposed to the approval engine to pause auto-approvals for this property+category
Effective Dates and Mid-Period Proration
Given a Monthly budget bucket with effectiveStartDate within the current calendar month When calculating the current period ceiling Then the proratedCeiling equals fullCeiling × (remainingDaysInMonth ÷ totalDaysInMonth), rounded to 2 decimal places using half-up rounding Given a Quarterly budget bucket with effectiveStartDate within the current calendar quarter When calculating the current period ceiling Then the proratedCeiling equals fullCeiling × (remainingDaysInQuarter ÷ totalDaysInQuarter), rounded to 2 decimal places using half-up rounding And When effectiveStartDate is the first day of the period Then no proration is applied And When effectiveEndDate falls before the end of a period Then the ceiling for that final period is prorated by days active within the period and the bucket is inactive thereafter
Category Taxonomy Alignment with Smart Triage
Given the user is creating or importing a budget bucket When selecting a maintenance category Then only categories from the FixFlow smart triage taxonomy are available and the selected category key is validated server-side When an invalid or deprecated category key is submitted Then the operation is rejected with 422 Unprocessable Entity and error code InvalidCategory Given an incoming work order or purchase order categorized by smart triage for a property and category When the system looks up budget context Then it resolves to the active bucket for that property+category and period, or returns NoBudgetFound=false/true accordingly for downstream approval logic
Overlapping and Conflicting Budgets Detection and UI Surfacing
Given a property and category When a user attempts to save or import a bucket whose effective date range overlaps an existing active bucket of the same property+category and period type Then the operation is blocked and the UI displays an inline error listing the conflicting bucket IDs and overlapping date ranges; the API responds 409 Conflict with error code BudgetOverlap When buckets exist for the same property+category with both Monthly and Quarterly period types that have overlapping effective date ranges Then the system flags a configuration conflict and requires the user to resolve by deactivating one; conflicts are surfaced via a list filter "Conflicts" and a conflict badge on affected rows
Templates and Portfolio Defaults Application
Given an Org Admin creates a Budget Template with period type, default thresholds, and per-category default ceiling amounts When the template is applied to N selected properties Then the system creates budget buckets per property+category using the template values, applies proration based on specified effectiveStartDate, and produces a summary report with counts of created, skipped (due to conflicts), and failed (with reasons) When organization-level budget defaults are configured Then new manually created buckets pre-populate period type and thresholds from defaults, and users can override before save
CRUD APIs and Bulk Import for Budget Buckets
Given authenticated API access When calling POST /budgets with valid payload Then the service creates a bucket and returns 201 with the resource; invalid payloads return 422 with field-level errors When calling GET /budgets with filters (propertyId, categoryKey, periodType, effectiveOn) Then the service returns matching buckets, paginated and sortable When calling PATCH /budgets/{id} Then only mutable fields (thresholds, effective dates, ceiling, currency) are updated with audit logging; immutable keys (propertyId, categoryKey, periodType) are rejected with 400 When calling DELETE /budgets/{id} Then the bucket is soft-deleted or deactivated and no longer considered in budget lookups Given a CSV bulk import with required columns (propertyId, categoryKey, periodType, ceiling, currency, effectiveStartDate) When processing the file Then valid rows are created or updated idempotently using externalId if provided; invalid rows are skipped and reported with row numbers and error codes; the API returns a job result with created/updated/failed counts
Real-time Spend Aggregation & Headroom Calculation
"As an operations lead, I want real-time visibility into committed and actual spend against each budget so that I can adjust approvals and vendor scheduling proactively."
Description

Aggregate committed and actual spend in real time from work orders, approvals, change orders, purchase orders, and vendor invoices to compute burn and remaining headroom per budget bucket. Classify transactions to categories using existing FixFlow mapping and fallbacks for unmapped items. Reflect reversals, voids, and refunds, and recalculate headroom immediately. Distinguish committed versus actual spend with toggles in UI and API. Provide accuracy guards (idempotent ingestion, de-duplication) and a max-lag SLA for updates. Surface calculation details and constituent transactions for transparency. This enables accurate, up-to-the-minute decisioning across auto-approvals and manager reviews.

Acceptance Criteria
Real-Time Aggregation and Headroom SLA
Given a new or updated transaction (work order, approval, change order, purchase order, vendor invoice) relevant to a budget bucket, When it is ingested by FixFlow, Then the bucket’s burn and headroom in both UI and API reflect the change within 60 seconds for at least 95% of events and within 120 seconds for 99% of events. Given a budget bucket defined by property, category, and period, When transactions are processed, Then only transactions matching the bucket’s property, category, and period are included in burn and headroom calculations. Given network or service disruptions, When ingestion resumes, Then all missed events are processed in order and bucket values become eventually consistent within 5 minutes, meeting the above percentiles thereafter.
Idempotent Ingestion and De-duplication
Given the same source transaction is submitted multiple times with the same immutable external_id and source_type, When processed, Then it is stored once and bucket totals remain unchanged after the first successful ingestion. Given a source transaction is re-submitted with the same external_id and a higher version or updated payload, When processed, Then the prior record is superseded without creating duplicates and bucket totals reflect only the latest state. Given related documents represent the same economic commitment (e.g., a PO line linked to a WO line), When calculating committed burn, Then the amount is counted once per linkage key and not double-counted across sources.
Classification Mapping with Fallbacks and Backfill
Given a transaction contains mappable attributes, When processed, Then it is classified to a budget category using the existing FixFlow mapping rules. Given a transaction cannot be mapped, When processed, Then it is assigned to an "Uncategorized" fallback within the correct property and appears in property-level totals but not in any specific category bucket. Given a previously unmapped transaction receives a new mapping rule, When reprocessed, Then it is reclassified to the correct category and all affected bucket burns/headrooms are recalculated within the SLA. Given classification occurs, When retrieved via API, Then each transaction includes category_id and classification_source values indicating mapped or fallback.
Committed vs Actual Spend Toggle (UI and API)
Given a user selects the Committed view in the UI, When viewing a budget bucket, Then Burn = sum(committed amounts) for in-scope transactions and Headroom = Budget - Burn. Given a user selects the Actual view in the UI, When viewing a budget bucket, Then Burn = sum(actual posted amounts net of refunds/voids) for in-scope transactions and Headroom = Budget - Burn. Given a client calls the budgets API with spend_type=committed, When the response is returned, Then burn and headroom reflect only committed transactions and the constituent_transactions list contains only committed entries. Given a client calls the budgets API with spend_type=actual, When the response is returned, Then burn and headroom reflect only actual transactions and the constituent_transactions list contains only actual entries.
Reversals, Voids, and Refunds Handling
Given an actual invoice is voided or a refund is posted for a previously counted amount, When the event is processed, Then the void/refund is applied as a negative adjustment to actuals and the bucket’s burn/headroom are recalculated within the SLA. Given a committed document (e.g., PO) is canceled or reduced, When the event is processed, Then the committed burn is decreased by the canceled/reduced amount and headroom increases accordingly within the SLA. Given an adjustment occurs, When viewing constituent transactions, Then an explicit adjustment entry is visible with a reference to the source document and the net sum equals the displayed burn within rounding tolerance of ±$0.01.
Calculation Transparency and Auditability
Given a user expands a bucket detail in the UI, When details are shown, Then the formula (Budget - Burn = Headroom) and a complete list of constituent transactions (id, source type, amount, date, category, link) are displayed and the sum of amounts equals the burn within ±$0.01. Given a client requests bucket details via API, When the response is returned, Then it includes burn, headroom, budget, spend_type, and an array of constituent_transactions with amounts that sum to burn. Given a user exports details, When CSV is generated, Then it contains the same transactions and amounts as the UI/API for the selected bucket and spend_type.
Bucket Period and Scope Alignment
Given a bucket is defined for Property P, Category C, and a monthly period with start_date S and end_date E, When calculating burn, Then only transactions for Property P classified to Category C with document dates between S and E (inclusive) are included. Given a bucket is defined for a quarterly period, When calculating burn, Then only transactions whose document dates fall within the quarter start/end (inclusive) are included. Given transactions belong to other properties or categories, When calculating burn for the bucket, Then they are excluded from the bucket’s totals but remain visible in their correct buckets.
Auto-Approval Throttling & Pauses by Budget Threshold
"As a property manager, I want auto-approvals to throttle or pause as budgets near limits so that I avoid end-of-period surprises without micromanaging every request."
Description

Extend the approval engine with rules that adjust behavior as budgets approach limits: continue normal auto-approvals under a configurable threshold, require manager review between warning and hard-stop thresholds, and pause non-emergency auto-approvals at or above hard stop. Allow per-bucket overrides for emergencies defined by FixFlow smart triage (e.g., life-safety, no-heat). Provide override workflows with role-based authorization, reason capture, and time-bound exceptions. Display budget status and rule-trigger explanations inline on approval screens. Log all decisions with budget snapshots for later audit. This prevents end-of-period surprises while preserving rapid response for critical issues.

Acceptance Criteria
Normal Auto-Approval Below Warning Threshold
Given a budget bucket B for property P and category C has current period spend below its configured warning threshold And a new work order W in bucket B is classified by Smart Triage as non-emergency When the approval engine evaluates W Then W is auto-approved without human review And the auto-approval reason indicates "Under warning threshold"
Manager Review Between Warning and Hard-Stop Thresholds
Given bucket B's current period spend is greater than or equal to its warning threshold and less than its hard-stop threshold And a work order W in bucket B is classified as non-emergency When the approval engine evaluates W Then auto-approval is disabled and W is placed in Manager Review state And the approval view displays the triggering condition "Warning band" And only users with Review permission can approve or reject W
Hard-Stop Pause for Non-Emergencies
Given bucket B's current period spend is greater than or equal to its hard-stop threshold And a work order W in bucket B is classified as non-emergency When the approval engine evaluates W Then auto-approval is paused and W cannot be auto-approved And the UI surfaces the message "Hard stop reached — auto-approvals paused" And any attempt to bypass without an authorized override is blocked
Auto-Approve Emergencies When Per-Bucket Override Enabled
Given bucket B has its emergency override setting enabled for auto-approvals at/above thresholds And bucket B's current period spend is greater than or equal to its hard-stop threshold And a work order W in bucket B is classified as emergency by Smart Triage When the approval engine evaluates W Then W is auto-approved despite the hard stop And the decision record cites rule "Emergency override (auto)" including triage class and override source
Manual Emergency Override With Role-Based Authorization
Given bucket B does not auto-approve emergencies at/above thresholds And bucket B's spend is greater than or equal to the hard-stop threshold or is in the warning band And a work order W in bucket B is classified as emergency by Smart Triage When a user without Override permission attempts to approve via override Then the action is denied with a permission error When a user with Override permission initiates the override workflow Then the system requires an override reason and selection of a time-bound exception window (start and expiry) And upon confirmation, W is approved and marked "Approved via override" And subsequent requests matching the override scope in bucket B during the window follow the exception behavior And after expiry or manual revocation, approvals revert to normal budget rules
Inline Budget Status and Rule Explanation on Approval Screens
Given any approval evaluation for a work order W in bucket B When the approval screen is presented Then it displays current spend, budget ceiling, remaining headroom, warning threshold, and hard-stop threshold for B And it displays the exact rule that determined the decision (Under warning, Warning band, Hard stop, Emergency override, Manual override) And all values shown reflect the decision-time snapshot
Immutable Audit Log With Budget Snapshot For Every Decision
Given any approval decision for a work order W (auto-approval, review approval/rejection, or override) When the decision is finalized Then an immutable audit record is written capturing timestamp, user (or system), property, category, bucket ID, current spend, budget ceiling, remaining headroom, warning threshold, hard-stop threshold, triage classification, decision outcome, rule trigger, and any override reason and window And audit records are queryable by property, category, bucket, date range, decision type, and user And audit records are append-only; corrections are recorded as new entries linked to the original
Budget Burn Dashboard & Threshold Alerts
"As a portfolio owner, I want a dashboard and alerts showing budget burn and remaining headroom so that I can spot risk and intervene before we overspend."
Description

Deliver a portfolio and property-level dashboard showing burn bars, trend lines, and remaining headroom by category and period with sorting, filtering, and CSV export. Link visualizations to underlying transactions and work orders. Provide configurable alerts (in-app, email, push) for crossing warning and hard-stop thresholds, predicted overages based on open commitments, and outsized single requests that would exceed headroom. Support alert batching, snooze, and escalation rules. Ensure alerts are delivered within minutes of state change and include clear recommended actions. This gives stakeholders proactive visibility to intervene before overspend occurs.

Acceptance Criteria
Portfolio Burn Dashboard: Real-Time Visualization, Filters, and Sorting
Given a portfolio user with access to 25–200 units and 24 months of data When they load the Budget Burn dashboard at portfolio scope Then burn bars display current period spend vs budget by category and property with remaining headroom amounts and color states (green < warning, yellow between warning and hard-stop, red ≥ hard-stop) Given the dashboard is loaded When the user switches the period between Monthly and Quarterly and selects previous periods Then trend lines update to show the last 12 selected periods and all visuals recompute within 500 ms Given visuals are present When the user applies filters (property multi-select, category multi-select, period range) Then all visuals reflect the filters consistently and the applied filters are clearly indicated Given tabular summaries are visible When the user sorts by Burn % or Headroom (ASC/DESC) Then the sort order is applied and retained across pagination Given new transactions or commitments are recorded that affect a filtered view When the state changes Then dashboard aggregates refresh and display the change within 60 seconds without requiring a full page reload Given a typical portfolio (≤200 units, ≤10 categories) When the dashboard is first loaded Then time-to-interactive is ≤ 2.5 seconds on a 3G fast network and ≤ 1.0 second on broadband
Property-Level Drill-Through to Transactions and Work Orders
Given any bar, line point, or segment representing a value When the user clicks it Then a drill-through panel opens listing underlying transactions and work orders contributing to that value Given the drill-through list is open When totals are computed Then the sum of listed rows equals the clicked value within ±0.5% and any discrepancy is flagged Given the drill-through list When the user clicks a transaction or work order ID Then the system navigates to the detailed record in a new tab with correct context Given role-based access controls When the user lacks permission to view transaction amounts or vendor details Then masked values are displayed and sensitive links are disabled while aggregates remain visible Given lists may be long When there are more than 100 rows Then pagination or infinite scroll is available and remains performant (<300 ms per page)
CSV Export Reflects On-Screen State and Required Fields
Given any dashboard view with active filters and visible columns When the user clicks Export CSV Then a CSV is generated within 10 seconds that mirrors the on-screen aggregation level and includes the same filters Given an export modal is shown When the user selects "Underlying transactions" Then the CSV contains one row per transaction/commitment with columns: Property ID/Name, Category, Period, Transaction/WO ID, Vendor, Date, Amount, Currency, Status, Approval State, Budget, Headroom at time of posting Given time zones and currency formats When values are written Then dates are ISO-8601 in portfolio time zone and currency uses the portfolio currency code with two decimals Given large datasets (up to 100,000 rows) When export is requested Then the export streams and completes within 60 seconds or the user is notified via email with a download link if backgrounded Given an export is produced When sums are checked Then grand totals by Property+Category+Period match on-screen totals within ±0.1%
Threshold Configuration: Warning and Hard-Stop per Property/Category/Period
Given a settings page for Budget Buckets thresholds When a user selects a property, category, and period cadence Then they can set Warning (%) and Hard-Stop (%) values between 0 and 150 with Warning < Hard-Stop Given channel preferences exist When the user toggles in-app, email, and push for each threshold type Then preferences are saved and applied to subsequent alerts Given a preview/test function When the user clicks "Send test" Then a test alert is delivered to selected channels within 60 seconds Given thresholds are saved When actual burn crosses Warning or Hard-Stop considering posted spend plus open commitments Then a threshold-crossed alert rule is triggered Given configuration versioning When a change is saved Then the prior values and editor, timestamp, and reason are recorded in an audit trail
Alert Delivery SLA and Content Across Channels
Given a threshold crossing or predicted overage event fires When delivery occurs Then an in-app alert appears within 60 seconds and email/push are delivered within 3 minutes at the 95th percentile Given an alert is delivered When the user opens it Then the payload includes: Property, Category, Period, Budget, Spend-to-date, Open Commitments, Forecasted Total, Headroom, Burn %, Threshold breached, Time of event, and recommended actions with deep links: View Dashboard, Open related WOs, Edit Thresholds, Pause Auto-Approvals Given deduplication rules When multiple state changes occur within 30 minutes for the same Property+Category+Period at the same severity Then only one alert is sent, with updates aggregated into the existing alert Given language and clarity When the alert is rendered Then the title states the severity (Warning/Hard Stop/Predicted Overage) and the action button labels are imperative and unambiguous
Predicted Overage and Outsized Request Detection with Auto-Approval Throttle
Given current budget B, posted spend S, open commitments C, and pending request amount R When S + C + R > B Then an "Outsized Request" alert is generated before approval and auto-approval for R is paused pending review Given prediction logic When S + C is trending to exceed B by period end based on linear extrapolation of daily burn or scheduled commitments Then a "Predicted Overage" alert is generated at least 3 days before the projected breach or immediately if the breach is projected within 3 days Given hard-stop thresholds When S + C ≥ B × Hard-Stop% Then auto-approvals are paused for the affected Property+Category until headroom is restored or an override is applied Given an override permission When an authorized user overrides and documents a reason Then the system records the override and resumes auto-approval for that request only Given the triggering event is withdrawn or reduced When R is canceled or edited such that S + C + R ≤ B Then the alert is resolved automatically and auto-approval is unpaused
Alert Batching, Snooze, Escalation, and Auditability
Given multiple alerts for the same Property+Category+Period within a 10-minute window When batching is enabled Then the user receives a single batched alert summarizing all triggers with counts and the top five contributors Given snooze controls on an alert When the user selects 1h, 4h, or 24h snooze Then no further alerts of equal or lower severity for that scope are delivered during the snooze window, and a summary is sent when snooze expires if the condition persists Given escalation rules When an alert is unacknowledged for 2 hours Then it escalates to the configured role or group and the escalation is recorded Given acknowledgements When any team member with access clicks "Acknowledge" or takes a linked action Then the alert status becomes Acknowledged and escalation timers reset Given compliance needs When viewing the alert audit log Then events show creation time, deliveries (channel, recipient, timestamp, status), opens, acknowledgements, escalations, resolutions, and all configuration versions, with CSV export available
Period Rollover, Adjustments & Carryover Policies
"As an accounting admin, I want budgets to roll over correctly with options for carryover and mid-period adjustments so that financial reporting remains accurate."
Description

Automate end-of-period rollover to initialize new budgets, with options to carry over unused funds (full, capped, or none) or handle deficits according to policy. Support mid-period budget adjustments with proration, effective-dated changes, and recalculation of headroom and thresholds. Lock closed periods while allowing authorized corrections via adjustments with full traceability. Maintain historical integrity by preserving budget versions and ensuring reports reflect period-specific rules. This ensures accurate financial alignment without disrupting ongoing maintenance operations.

Acceptance Criteria
Monthly Rollover – Full Carryover
Given property "Pinecrest Apts" and category "HVAC" have a monthly base budget of $5,000 with carryover policy "Full" And at 2025-09-30 23:59 local time the remaining headroom is $1,200 When the automated rollover job executes Then a new budget for 2025-10-01–2025-10-31 is created for Pinecrest Apts/HVAC And the starting available budget equals $6,200 And auto-approval thresholds for Pinecrest Apts/HVAC recalculate based on $6,200 within 5 minutes And re-running the rollover job does not create duplicate budgets or double-apply the $1,200 carryover
Quarterly Rollover – Capped Carryover
Given property "Pinecrest Apts" and category "Electrical" have a quarterly base budget of $30,000 with carryover policy "Capped" and cap $5,000 And at 2025-09-30 23:59 local time the remaining headroom is $8,000 When the rollover job executes Then the next period starting budget equals $35,000 And an audit event records carryoverApplied=$5,000 and carryoverCapped=$3,000 And auto-approval thresholds for Pinecrest Apts/Electrical recalculate within 5 minutes based on the $35,000 starting budget
Monthly Rollover – No Carryover with Deficit Reset
Given property "Pinecrest Apts" and category "General Repairs" have a monthly base budget of $4,000 with carryover policy "None" and deficit policy "ResetToZero" And at 2025-09-30 23:59 local time the overspend is $2,000 (headroom = -$2,000) When the rollover job executes Then the next period starting budget equals $4,000 And an audit event records deficitCleared=$2,000 and deficitCarried=$0 And auto-approval thresholds for Pinecrest Apts/General Repairs recalculate within 5 minutes based on the $4,000 starting budget
Mid-Period Budget Increase – Proration with Effective Date
Given Pinecrest Apts/Plumbing has a monthly base budget of $5,000 for 2025-09 and current headroom $2,800 And a Finance Admin schedules an increase of $3,000 effective 2025-09-15 00:00 local time When the effective time is reached Then the prorated addition equals $1,600 (3,000 * 16/30) and is applied as a budget adjustment And the period total budget becomes $6,600 And headroom increases by $1,600 by 00:05 on 2025-09-15, preserving all spend posted before 00:00 And auto-approval thresholds recalculate within 5 minutes of application And an audit entry stores before/after totals, proration basis (days), scheduler, timestamp, and reason
Mid-Period Budget Decrease – Threshold and Headroom Recalculation
Given Pinecrest Apts/Plumbing has a monthly base budget of $6,000 for 2025-09 with spent/committed $5,300 and headroom $700 And a Finance Admin applies a decrease of $1,500 effective immediately on 2025-09-20 12:00 local time When the change is saved Then the prorated decrease equals $550 (1,500 * 11/30) and the period total budget becomes $5,450 And headroom recalculates to $150 (5,450 - 5,300) within 1 minute And auto-approval throttle state updates within 5 minutes; any rules requiring minimum headroom $200 are paused And an immutable audit entry is recorded with before/after totals, proration basis, user, timestamp, and reason
Closed Period Lock – Authorized Adjustment with Full Traceability
Given the August 2025 period for Pinecrest Apts/All Categories is marked Closed When a Standard User attempts to modify an August budget or carryover Then the attempt is blocked with HTTP 403 and message "Period is locked" And when a Finance Admin posts an Adjustment of +$250 to August with reason "Late vendor invoice INV-8832" Then the base period values remain unchanged and a separate adjustment record is created And reports for August display Original Budget, Adjustments (+$250), and Adjusted Total, with drill-down to the adjustment details (user, timestamp, reason, reference)
Historical Reporting – Period-Specific Policy Versioning
Given the carryover policy for Pinecrest Apts/Plumbing changes from "Full" to "Capped $5,000" effective 2025-10-01 When generating the Budget History report for September 2025 Then calculations use the "Full" policy and the reported October starting budget equals September base plus full remaining headroom as of 2025-09-30 And when generating the Budget History report for October 2025 Then calculations use the "Capped $5,000" policy and carryover is limited accordingly And no historical records for September are mutated by the October policy change
Permissions & Audit Trail for Budget Governance
"As an admin responsible for compliance, I want permissions and audit trails for budget changes and overrides so that we maintain governance and accountability."
Description

Implement role-based permissions to view, create, edit, and approve budgets, thresholds, and overrides. Record immutable audit logs for every change, including who, what, when, previous values, and rationale. Capture approval-throttling triggers and override actions with the associated budget snapshot and related work orders. Provide exportable audit reports and retention policies aligned with compliance needs. Surface audit context in UI where decisions are made to reinforce accountability. This establishes trust and compliance for spend controls across teams.

Acceptance Criteria
Role-Based Access Control for Budget Actions
Given a user with Budget Viewer role and scope to Property A, When they open the Budgets module for Property A, Then they can view budgets, thresholds, and overrides but cannot see Create, Edit, or Approve controls. Given a Budget Viewer attempts to access budgets for a property outside their scope, When they request data via UI or API, Then the system returns 403 and no budget data is exposed. Given a user with Budget Editor role scoped to Property A and Category X, When they create or edit a budget or threshold within that scope, Then the change is saved and the approval state is Pending Approval; When they attempt to approve, Then the action is blocked with a clear message and 403. Given a user with Budget Approver role scoped to Property A, When they approve a pending budget or override within configured approval limits, Then the status updates to Approved and the approver is recorded. Given an Org Admin, When they assign or revoke budget permissions (Viewer, Editor, Approver, Auditor) to a user for specific properties and categories, Then the change takes effect immediately across UI and API. Given any request to perform a budget action, When evaluated by the API, Then authorization is enforced server-side and unauthorized requests are denied with 401/403 and reason code, and the attempt is audited.
Immutable Audit Log for Budget and Permission Changes
Given any create, update, delete, approve, deny, throttle, override, or permission change related to budgets, When the action is committed, Then an audit record is written including: event_id, UTC timestamp (ISO 8601), actor user_id, actor role, actor IP, action, entity_type, entity_id, changed_fields with previous_value and new_value, rationale (if provided/required), correlation_id, and request_id. Given an audit record exists, When any user attempts to modify or delete it via UI, API, or direct storage, Then the system prevents modification and preserves the original; a separate tamper_attempt event is recorded. Given audit logging is enabled, When a new record is written, Then it contains a cryptographic hash and previous_hash to form a verifiable chain; chain verification succeeds for untampered sequences. Given the business operation and its audit write, When either fails, Then both are rolled back so no operation completes without a corresponding audit record (atomicity). Given timestamps must be reliable, When clients submit timestamps, Then the system ignores client time and records server-side NTP-synchronized UTC time.
Capture Throttling Triggers and Overrides with Budget Snapshot
Given auto-approval evaluation runs for a work order, When the relevant budget headroom is below threshold or burn% is above limit, Then the system throttles or pauses and records a throttle_event including: trigger_rule_id, budget_snapshot (period start/end, allocated, spent_to_date, commitments, remaining, burn_percent), property_id, category, work_order_id, evaluator_version. Given an authorized approver overrides a throttle for a work order, When they submit an approval, Then a rationale of at least 10 characters is required; an override_event is created linking to the throttle_event and snapshot, and the work order shows approved_by and approved_at. Given multiple work orders are evaluated in one cycle on the same budget, When throttled, Then each work order has its own event referencing the same snapshot_id for consistency. Given a throttled budget is replenished or thresholds are adjusted to restore headroom, When auto-approvals resume, Then a resume_event is written with a new budget_snapshot and affected work_order_ids.
Exportable Audit Reports with Filters and Retention Enforcement
Given a user with Auditor or Org Admin role, When they request an export of audit logs with filters (date range, property, category, user, action type, entity type), Then only matching records are included. Given an export is generated, When delivered, Then formats CSV and JSON are supported; CSV has a header row, comma delimiter, UTF-8 encoding; fields include all captured audit attributes including hash and previous_hash. Given an export request exceeds 100,000 records, When processed, Then it runs asynchronously and provides a downloadable link that expires in 7 days; export progress and completion are visible to the requester. Given a retention policy (e.g., 7 years) is configured, When a delete is requested for records within the retention window, Then the request is rejected with reason Retention Policy; when purging eligible records, Then a retention_purge audit event logging count, time window, and executor is created, and an optional certificate of destruction can be downloaded. Given an unauthorized user attempts to export, When the request is made, Then it is denied with 403 and the attempt is audited.
In-UI Audit Context at Decision Points
Given a user views a Budget Details screen, When the page loads, Then the UI shows last modified by, timestamp (absolute and relative), last rationale, and a link to View full audit history that navigates to an audit timeline for that budget. Given an approver opens the Approve/Override dialog for a throttled work order, When the dialog renders, Then it displays the triggering rule, snapshot metrics (allocated, spent, remaining, burn%), and the last 3 related audit events. Given the audit service is unavailable, When a user attempts to approve, override, or edit, Then the UI blocks submission and shows a clear, non-destructive warning until audit availability is restored. Given a user clicks View full audit history, When the timeline loads under typical load, Then the first page renders within 2 seconds and supports pagination and filtering by event type and actor.
Override Governance and Dual-Approval Controls
Given an approver attempts to submit an override, When the rationale text is fewer than 10 characters or empty, Then submission is blocked with inline validation and no event is recorded. Given an override amount exceeds the configured dual-approval threshold, When the first approver submits, Then the override enters Pending Second Approval status and notifications are sent to designated second approvers. Given dual approval is required, When the same user attempts to provide the second approval or policy disallows self-approval of own changes, Then the action is blocked with a clear error and a self_approval_blocked audit event is recorded. Given a pending second approval exceeds the configured SLA, When the threshold is met, Then the system escalates per policy (e.g., notify Org Admin) and records escalation events. Given notifications are emitted for second approvals, When sent, Then delivery status (queued, sent, failed) is recorded and visible in the audit trail.

Override Ledger

Captures reason codes, approver, amount, and evidence for any cap override and rolls them into a searchable audit trail and trend dashboard. Highlights recurring causes so teams can refine caps and playbooks.

Requirements

Override Capture & Enforcement at Approval
"As a property manager, I want the system to require a reason and evidence when approving spend above caps so that overrides are justified and fully auditable."
Description

Enforce structured capture of override details at the moment a repair approval exceeds configured caps within FixFlow’s one-click approval flow. The UI must block submission until a valid reason code is selected, approved amount is confirmed, and at least one evidence item is attached when required by policy. Automatically record approver identity, role, timestamp, work order, invoice, property/unit, vendor, original cap, variance, and policy/playbook references. Support role- and threshold-based multi-level approvals, with notifications and SLAs, and write an immutable override event upon completion. Integrates with existing approval endpoints and respects RBAC to ensure only authorized users can initiate or approve overrides.

Acceptance Criteria
Block Submission When Exceeding Cap Without Required Inputs
Given an approver is in the one-click approval flow for a work order whose estimated or quoted amount exceeds the configured cap for the property/unit When the approver attempts to submit without selecting a reason code, confirming the approved amount, or satisfying required evidence rules Then the Approve action is disabled and inline errors indicate each missing requirement, and no request is sent to the approval endpoint. Given the cap is exceeded and all required fields are valid per policy When the approver clicks Approve Then the submission proceeds to the next step (routing) and the UI shows a success state. Given the cap is not exceeded When the approver clicks Approve Then no override capture UI is shown and the submission proceeds normally.
Required Override Field Capture and Auto-Metadata Recording
Given an override submission is made When the form is submitted successfully Then the system records: approver user ID and role, UTC timestamp, work order ID, invoice ID (if present), property ID and unit, vendor ID, original cap amount and currency, approved amount and currency, variance amount and percentage, selected reason code, optional notes, and policy/playbook reference IDs. Given the override event is persisted When retrieved via API or viewed in audit UI Then all recorded values match submitted inputs and runtime context, and required fields are non-null and correctly typed.
Policy-Driven Evidence Attachment Enforcement
Given a policy that maps reason codes to evidence requirements (minimum attachment count and allowed file types) When a reason code requiring evidence is selected Then the UI enforces at least the configured minimum number of attachments of allowed types before enabling submission. Given a reason code that does not require evidence is selected When submitting Then attachments are optional and submission is allowed without evidence. Given an attachment upload fails or an attachment is removed causing the count to drop below the required minimum When attempting to submit Then the UI blocks submission and displays a clear error specifying the deficiency.
Multi-Level Approval Routing with SLAs and Escalations
Given organization approval thresholds define Level 1, Level 2, etc., by amount and required approver roles When an override exceeds the Level 1 threshold Then it is routed to the configured Level 1 approver(s) and marked Pending L1. Given the override also exceeds the Level 2 threshold When Level 1 approves Then it routes to Level 2 approver(s), honoring configured parallel/sequential and quorum rules. Given an SLA duration is configured for a level When the SLA elapses without action Then the item is flagged Breach, escalation notifications are sent to configured recipients, and breach/escalation timestamps are stored. Given any level rejects When the rejection is recorded Then the override is marked Rejected and does not advance to further levels.
RBAC Enforcement for Override Initiation and Approval
Given a user without override initiation permission views an over-cap approval When they attempt to start an override Then the UI does not present the control, and any direct API call returns 403; no override event is created. Given a user with initiation permission but lacking the required approval role/threshold When they submit an override Then the item routes to an authorized approver and the user cannot self-approve. Given a user with approval permission within their threshold acts on a pending override When they approve Then the action is accepted; if the amount exceeds their threshold, approval is blocked with a message indicating the required next approver level.
Integration with Existing Approval Endpoints and Idempotency
Given existing approval API endpoints are in use by other flows When a standard (non-override) approval is submitted Then behavior remains unchanged and existing API contracts are backward compatible. Given an override approval is submitted When the API is called Then the same endpoints are used with additional override payload fields; the server validates inputs and returns structured errors for invalid data. Given a client retry or double-click occurs with the same idempotency key When the server receives duplicate submissions within the idempotency window Then only one approval action is persisted and the client receives the same idempotent response.
Immutable Override Event and Audit Trail Availability
Given the final approval step completes When the override is finalized Then an immutable override event record with a unique ID and tamper-evident hash is written and cannot be altered by subsequent user actions. Given the event is written When the audit trail is queried Then the event is searchable within 60 seconds and filterable by property, unit, vendor, approver, reason code, date range, amount, and variance. Given a correction is needed post-finalization When a superseding event is created Then the original event remains read-only and the superseding event references the original event ID.
Override Event Data Model & Validation
"As a finance lead, I want override events to capture standardized fields so that reconciliation and audits are consistent and reliable across properties and vendors."
Description

Define and implement a normalized, versioned schema for override events including: override_id, work_order_id, invoice_id, property_id, unit_id, vendor_id, approver_user_id and role, reason_code_id and optional free-text reason, cap_amount, approved_amount, override_delta, currency, tax, timestamps, evidence_ids, source (web/mobile/api), device/IP metadata, policy_reference, playbook_id, and computed fields. Apply server-side validation for required fields, numeric constraints, allowed reason codes, and threshold mappings. Ensure immutability of core fields after creation with append-only corrections. Index for query performance and replicate to analytics storage for reporting. Tag PII fields for masking and retention policies.

Acceptance Criteria
Schema Versioned Override Event Model
Given the service is running and the schema metadata endpoint is requested, When /override-events/schema is called, Then it returns a semantic version (major.minor.patch) and a complete field catalog including override_id, work_order_id, invoice_id, property_id, unit_id, vendor_id, approver_user_id, approver_role, reason_code_id, reason_free_text, cap_amount, approved_amount, override_delta, currency, tax, created_at, updated_at, evidence_ids, source, device_ip, device_user_agent, policy_reference, playbook_id, policy_version. Given an override event is created with cap_amount=250 and approved_amount=325, When the record is persisted, Then override_delta is auto-computed as 75 and stored as read-only. Given field types are defined, When inspecting the schema, Then numeric fields are decimals with precision/scale (amounts: 18,2; tax: 18,4), timestamps are UTC ISO-8601, ids are UUIDv4, and arrays (evidence_ids) are non-nullable arrays of UUIDv4.
Required Fields and Numeric Constraints Validation
Given a create request is missing any required field (override_id, work_order_id, invoice_id, property_id, vendor_id, approver_user_id, approver_role, reason_code_id, cap_amount, approved_amount, currency, source), When submitted, Then the API responds 400 with a machine-readable error per missing field. Given cap_amount or approved_amount is negative or has more than 2 decimal places, When submitted, Then the API responds 422 with code AMOUNT_INVALID. Given tax is provided and is negative or has more than 4 decimal places, When submitted, Then the API responds 422 with code TAX_INVALID. Given currency is not a valid ISO 4217 uppercase code, When submitted, Then the API responds 422 with code CURRENCY_INVALID. Given source is not one of [web, mobile, api], When submitted, Then the API responds 422 with code SOURCE_INVALID. Given device_ip is not a valid IPv4 or IPv6 and device_user_agent exceeds 500 chars, When submitted, Then the API responds 422 with code DEVICE_META_INVALID.
Reason Codes and Threshold Mapping Enforcement
Given reason_code_id does not exist or is inactive, When submitted, Then the API responds 422 with code REASON_CODE_INVALID. Given a (property_id, vendor_id, reason_code_id) threshold mapping exists with cap_amount, When approved_amount exceeds cap_amount and approver_role lacks permission, Then the API responds 403 with code THRESHOLD_VIOLATION. Given a reason_code requires evidence, When evidence_ids is empty or contains invalid ids, Then the API responds 422 with code EVIDENCE_REQUIRED. Given an override is above cap and policy_reference or playbook_id is missing, When submitted, Then the API responds 422 with code POLICY_METADATA_REQUIRED.
Immutability and Append-Only Corrections
Given an override event exists, When attempting to update any core field (override_id, work_order_id, invoice_id, property_id, unit_id, vendor_id, approver_user_id, approver_role, reason_code_id, cap_amount, approved_amount, currency, tax, created_at, source, policy_reference, playbook_id), Then the API returns 409 with code CORE_FIELD_IMMUTABLE and no changes are persisted. Given a correction is required, When posting an append-only correction referencing reference_override_id with correction_type and evidence_ids, Then a new record is created with a unique override_id, links to the original, and the original record remains unchanged. Given a correction is appended, When querying the audit trail for the original override_id, Then both the original and correction records are returned in chronological order with actors and timestamps.
Indexing and Query Performance
Given a dataset of ≥100k override events over the last 90 days, When querying by property_id with a created_at range, Then p95 response time ≤300ms and result counts are correct. Given queries filtering by vendor_id, reason_code_id, approver_user_id, and created_at range, When executed, Then p95 response time ≤400ms and index usage is confirmed by the query plan. Given a lookup by override_id, When executed, Then p95 response time ≤50ms. Given canonical queries, When EXPLAIN is run, Then composite indexes exist and are used: (property_id, created_at), (vendor_id, created_at), (reason_code_id, created_at), and a primary index on override_id.
Replication to Analytics Store
Given change data capture is enabled, When a new override event is created, Then it appears in the analytics store with identical fields and schema_version within 5 minutes p95 and 15 minutes p99. Given a replication failure occurs, When recovery completes, Then backfill produces no duplicates (override_id primary key) and record counts match source within 0.5%. Given replication lag exceeds SLA thresholds, When monitoring evaluates, Then an alert is emitted to the on-call channel with runbook link. Given schema version increments, When data lands in analytics, Then the analytics catalog reflects the new version with backward-compatible mappings or a documented migration path.
PII Tagging, Masking, and Retention
Given PII fields are defined as approver_user_id and device_ip, When stored, Then they are tagged in the data catalog with pii=true and classification=confidential. Given a non-privileged role queries API or analytics, When results include PII fields, Then approver_user_id is SHA-256 hashed with salt and device_ip is anonymized (/24 for IPv4, /64 for IPv6). Given retention_policy_days=365, When a record’s age exceeds 365 days, Then PII fields are purged or irreversibly anonymized while non-PII fields remain, and the purge is logged with timestamp and actor. Given a privileged compliance role queries the same data, When authorized, Then full PII values are returned and access is captured in an audit log.
Evidence Upload & Tamper-Evident Storage
"As a compliance officer, I want to attach and secure evidence to each override so that decisions are provable and cannot be tampered with."
Description

Provide multi-file evidence upload (images, PDFs, emails, quotes) with progress feedback, size/type validation, and mobile-friendly capture. Store files in secure object storage using pre-signed URLs, perform antivirus scanning, compute and persist cryptographic hashes for integrity, and capture metadata (uploader, timestamp, EXIF, source). Support redaction for PII, configurable retention and legal hold, and strict RBAC-controlled access. Link evidence to override events with chain-of-custody tracking and optional OCR to make content searchable in the audit trail.

Acceptance Criteria
Mobile Multi-File Upload With Progress and Validation
Given the tenant allows evidence upload and has a configured max size of 50 MB and allowed types [jpg, jpeg, png, heic, pdf, eml, msg] When a permitted user selects or captures 5 files on a mobile device (including HEIC and PDF) Then each file displays an individual progress bar and percentage, plus an overall batch progress indicator And files exceeding 50 MB or of disallowed types are blocked client-side with a clear reason shown per file And drag-and-drop on desktop and native camera capture on mobile are supported And if the network drops mid-upload, the client retries/resumes up to 3 times using multipart upload And upon completion, the UI reports the count of successful uploads and the count of failures with reasons
Secure Direct Upload via Pre-Signed URLs
Given a user with upload permission initiates an evidence upload When the client requests a pre-signed URL for the file Then the server returns a single-use, time-limited (≤15 minutes) URL scoped to the tenant's object path with content-type and max-size constraints And the client uploads the file directly to object storage without streaming file bytes through the application API And attempts to use an expired, reused, or tampered URL fail with HTTP 403 and are logged with user and IP And the URL is invalidated after the first successful upload
Antivirus Scan and Quarantine
Given a file has finished uploading to object storage When the antivirus scan runs Then clean files are marked Clean and become available to authorized users And infected or suspicious files are quarantined within 60 seconds, marked Infected, and blocked from view/download And the uploader and tenant admins receive a notification for quarantined files And the audit trail records scanner name/version, signature timestamp, scan time, and outcome And an authorized admin can trigger a re-scan, with all outcomes preserved in history
Cryptographic Hashing and Tamper-Evidence
Given a file upload completes When the system computes a SHA-256 hash and persists it immutably with the evidence record Then the stored hash is displayed with timestamp in the audit trail And on any subsequent download, a background verification recomputes the SHA-256 and compares to the stored value And on mismatch, access is blocked, the evidence is flagged Integrity Mismatch, and a security alert is sent to designated admins And verification outcomes (time, result) are appended to the audit trail
Metadata, OCR, and Chain-of-Custody Capture
Given evidence is uploaded and linked to an Override Ledger entry Then the system captures and stores uploader ID, role, UTC ISO-8601 timestamp, client IP, user agent, original filename, MIME type, file size, EXIF (when present), and source (camera, gallery, email, drag-drop, API) And every action (upload, scan, hash, view, download, redact, link/unlink, delete, legal-hold change) is appended to the chain-of-custody with actor, timestamp, and reason code And if OCR is enabled for the tenant, images and PDFs are processed within 5 minutes, extracted text is stored and indexed for search, and OCR language configuration is saved in metadata And if OCR is disabled, no text is extracted or indexed, and the audit trail notes OCR was skipped
RBAC Access Control, Redaction, and Legal Hold
Given tenant RBAC defines permissions for view, download, redact, and delete When a user without the required permission attempts any restricted action Then the action is denied with HTTP 403 and an audit entry is recorded with user, action, and reason And when an authorized user performs redaction for PII Then a new redacted derivative is created; the original is preserved; both have distinct SHA-256 hashes and metadata linking derivative to source And by default, non-admin roles see/download the redacted version only; access to the original requires an elevated permission And when legal hold is enabled on evidence, delete is blocked and all attempts are logged with the hold reason; disabling legal hold requires admin role and a reason code
Retention Policy Enforcement and Deletion
Given the tenant has a retention policy configured for evidence type = Override Ledger (e.g., retain 24 months) When evidence reaches the end of its retention period and is not under legal hold Then the system schedules deletion, notifies tenant admins 7 days in advance, and after the window performs deletion from object storage and indexes And deletion appends a final chain-of-custody entry including who/what triggered deletion, timestamps, and purge identifiers from storage And evidence under legal hold is excluded from retention-based deletion until the hold is removed And an export of evidence metadata and hashes is available prior to deletion for compliance purposes
Searchable Override Audit Trail
"As an operations lead, I want to search and filter the history of overrides so that I can quickly answer owner questions and pass audits."
Description

Deliver a unified audit trail UI and API for all override events with fast, paginated search across date ranges, properties, vendors, approvers, reason codes, and amount ranges. Provide keyword search on notes/OCR text, sortable columns, saved filters, and export to CSV. Each record offers a detailed view showing all captured fields, evidence previews, and an event timeline. Enforce permission-based visibility and log access for auditability. Optimize with database indexing and caching to achieve sub-2s response under typical loads.

Acceptance Criteria
Multi-Filter Paginated Search (UI and API Parity)
- Given a signed-in user with permission to view override events across multiple properties and events exist with varying date, property, vendor, approver, reason code, and amount values When the user applies any combination of date range, property, vendor, approver, reason code, and amount range filters and submits the search in the UI Then the results include only records matching all selected filters, limited to the configured page size (default 50), with total result count and pagination controls reflecting the dataset - Given the same filter set When the search API is called with equivalent parameters Then it returns the same records, total count, and pagination metadata as the UI for the same query, with HTTP 200 - Given any filter set that matches zero records When the search is executed Then the UI displays 0 results and the API returns an empty list, count 0, HTTP 200 - Given typical production load and a warm cache When running 100 searches with combined filters Then p95 end-to-end response time per search is <= 2000 ms and p50 <= 800 ms; repeated identical queries return in <= 500 ms via cache - Given representative filter combinations When database query plans are captured in staging Then no full table scans occur and the primary predicates use indexes as designed
Keyword Search on Notes and OCR Text
- Given override records containing free-text notes and OCR-extracted text from evidence When the user enters a keyword or quoted phrase in the keyword search and executes the query Then results include records where the term or exact phrase appears in notes or OCR text (case-insensitive), and records without matches are excluded - Given special characters in the query (for example %, _, apostrophes, and quotation marks) When the search is executed Then the input is safely handled and the system returns HTTP 200 with correct results (no errors or injection) - Given multiple terms separated by spaces When the search is executed Then records containing all terms in any order are returned (logical AND)
Sortable Columns on Audit Results
- Given the audit results grid with columns: Event Time, Property, Vendor, Approver, Reason Code, Amount When the user clicks a column header or provides a sort parameter in the API (for example sort=amount, dir=desc) Then results are sorted by that column in the selected direction, with a stable secondary sort by Event Time descending for ties - Given a sort is applied When pagination is used Then item order is consistent across pages (no reshuffling when moving between pages) - Given an unsupported sort field is requested via API When the request is made Then the API responds with HTTP 400 and a validation error listing allowed sort fields
Saved Filters: Create, Update, Apply
- Given an authenticated user with an active filter set When they save the filter with a unique name Then the filter is stored to their profile and appears in their Saved Filters list - Given a saved filter exists When the user applies it Then all filter controls reflect the saved values and the search executes automatically with matching results - Given a saved filter name collision When the user chooses to overwrite Then the existing saved filter is updated; otherwise the save is prevented with a clear message - Given the user sets a default saved filter When they return to the audit trail Then the default filter auto-loads and runs
Export Results to CSV
- Given an active search with N results When the user clicks Export CSV Then a CSV is generated containing all matching records for the current filters (not just the current page), with column headers matching the visible grid and UTF-8 encoding - Given date and currency fields When exported Then timestamps use ISO 8601 with timezone offset and amounts retain two decimal places - Given large result sets up to 10000 rows When export is requested Then the CSV is delivered within 10 seconds without timeouts
Record Detail View with Evidence and Timeline
- Given a result row is selected When the user opens the detail view Then the view displays all captured fields (event ID, timestamp, property, vendor, approver, reason code, cap amount, override amount, notes), an evidence preview gallery (images and PDFs), and a chronological event timeline (created, submitted, approved, exported, accessed) - Given evidence files exist When the detail is opened Then thumbnails load within 2 seconds and users can open full-size previews in a modal without download - Given the API is requested for a specific event by ID When the request is authorized Then it returns all fields, evidence metadata (file name, type, size, URL), and timeline entries with timestamps and actor identifiers, with HTTP 200
Permissions, Visibility, and Access Logging
- Given a user lacks access to a property When they search or request an event from that property Then the UI shows no matching results and the API returns HTTP 403 for direct event access - Given role-based permissions When a user with an Auditor role signs in Then they have read-only access to the audit trail and cannot export or delete unless explicitly granted; write attempts are blocked with HTTP 403 - Given any user views, exports, or fetches an audit event When the action occurs Then an access log entry is recorded with user ID, event ID, action (view, export, API read), timestamp, source IP, and channel (UI or API); administrators can filter and view these logs
Override Trends Dashboard & Recurring Cause Insights
"As a portfolio owner, I want a trends dashboard that highlights recurring override causes so that I can adjust caps and playbooks to reduce exceptions and costs."
Description

Build a dashboard that aggregates override metrics: total overrides, override rate, total overridden amount, median variance, and distribution by property, vendor, asset category, and reason code. Visualize trends over time, highlight top recurring causes, and surface anomalies against baselines. Enable drill-down from charts to the audit trail with applied filters. Provide saved views, date presets, and scheduled email digests. Expose insights that recommend cap or playbook adjustments based on observed patterns.

Acceptance Criteria
KPI Aggregation & Date Presets
Given cap-eligible work orders and overrides exist across multiple dates and entities When the user opens the Override Trends Dashboard and selects a date range via presets (Last 7, 30, 90, YTD, This Month, Last Month) or a custom start/end date and applies Then the dashboard shows KPIs: Total Overrides (count), Override Rate (% = overrides ÷ cap-eligible work orders), Total Overridden Amount (currency), Median Variance (currency) computed for the selected range And each KPI displays a prior-period comparison (same length immediately preceding) with absolute and percent deltas And currency values render in the org’s default currency without decimals, with full precision on hover tooltip And KPI calculations complete within 2 seconds for datasets up to 100k override records
Time Trends & Anomaly Highlighting
Given a date range is selected When the user chooses a time granularity (Day, Week, Month) Then trend charts render for each KPI with one point per interval and zero-filled for intervals with no data And tooltips show interval label and exact metric values; legend toggle shows/hides series and persists per session And anomalies are flagged when an interval’s Override Rate or Total Overridden Amount deviates by ≥30% or ≥2 standard deviations from the trailing 8-interval baseline And anomaly points are visually highlighted with a tooltip containing baseline, current value, absolute delta, percentage delta, and z-score And the user can toggle Anomaly Highlights on or off
Distribution & Drill-Down to Audit Trail
Given the dashboard is filtered to a date range When the user selects Group By (Property, Vendor, Asset Category, Reason Code) Then a distribution chart and ranked table display counts of overrides, share %, and total overridden amount per group And clicking any chart segment or table row opens the Override Audit Trail with the same filters and group value applied, ordered by most recent And the number of records in the audit trail matches the clicked segment’s count And a Back to Dashboard action returns to the dashboard with prior filters and scroll position preserved
Top Recurring Causes Highlight
Given overrides are present within the selected range When the system computes recurring causes Then the dashboard shows the top 5 reason codes (and vendor/asset combinations) that meet recurrence thresholds (≥3 overrides and ≥15% of all overrides in range) And each item displays count, share %, 30-day vs 90-day trend indicator, and link to view related audit trail And recurrence thresholds are configurable per organization and reflected in results within 5 minutes of change
Insight Recommendations for Cap/Playbook Adjustments
Given recurring overrides exceed caps for specific properties, vendors, or asset categories over the last 60 days When the median variance for a segment exceeds the cap by ≥10% with ≥5 occurrences in the period Then an Insight card is shown recommending a cap change or playbook adjustment with rationale, impacted scope, and estimated impact on override rate And the user can Dismiss, Snooze (30/60/90 days), or Mark Implemented; actions are logged with user, timestamp, and note And dismissed insights do not reappear unless the condition worsens by ≥20% over the next evaluation period
Saved Views
Given the user has configured filters, group-bys, granularity, metric toggles, and anomaly toggle When the user saves the configuration under a unique view name Then the view stores all current settings and appears in the Saved Views list And the user can set a Default View, rename, or delete a view; loading a view restores settings exactly And the selected view persists across sessions and devices for the same user account
Scheduled Email Digests
Given at least one saved view exists When the user schedules a digest with frequency (Daily, Weekly, Monthly), send time, timezone, and recipients Then recipients receive an email at the scheduled time with KPIs, top recurring causes, anomaly summary, and deep links that open the dashboard with the saved view applied And a Test Send delivers within 2 minutes; unsubscribe from a specific schedule is available via one-click link And delivery status is recorded; failed sends retry up to 3 times and surface an in-app warning to the scheduler
Reason Code Taxonomy & Governance
"As an account admin, I want to manage standardized reason codes and requirements so that override data stays consistent and analysis remains meaningful over time."
Description

Introduce an admin interface and API to manage the reason code taxonomy: create, edit, merge, deprecate, and organize codes hierarchically. Configure per-reason requirements (e.g., mandatory evidence, minimum notes), effective dates, and mappings to cap types and playbooks. Maintain change history and prevent deletion of codes referenced by historical events. Propagate updates to approval UIs and validations, and support localization for multi-language teams.

Acceptance Criteria
Admin UI: Create Reason Code with Hierarchy and Mappings
Given an admin with Manage Reason Codes permission is on Admin > Reason Codes When they click New Code, enter a unique Name (min 3 characters), optionally select a Parent, set optional Cap Type and Playbook mappings, and click Save Then the system creates the code with a unique ID, assigned parentId (or null), active=true, effectiveStart set to current timestamp by default, and persists mappings And the new code appears in the taxonomy tree under its parent and is searchable within 5 seconds And the code becomes selectable in Approval UIs within 60 seconds of creation
Admin UI: Edit Reason Code with Effective Dating and Versioning
Given an existing active reason code When the admin edits its Name, Description, Parent, mappings, or per-reason requirements and sets an Effective Start in the future Then the changes are versioned and scheduled; the current version remains in effect until the Effective Start And Approval UIs and API validations use the version whose effective window contains the event timestamp And an audit entry is recorded capturing actor, timestamp, and field-level before/after values
Merge Reason Codes with Alias and Reference Preservation
Given two existing reason codes A (survivor) and B (source) and an admin initiates Merge B into A When the merge is confirmed with an effective timestamp (default now) Then B is marked deprecated with aliasTo=A and is removed from selection in UIs from the effective timestamp And future submissions that reference B via API or UI resolve to A with an informational note logged And historical events that referenced B remain unchanged, while analytics aggregate B under A via alias resolution And an audit entry records the merge details, including actor, timestamp, and affected IDs
Deprecate Reason Code and Block Selection Post End-Date; Prevent Deletion of Referenced Codes
Given an active reason code C When the admin sets an Effective End (or toggles Deprecated now) Then C is hidden from selection lists in Approval UIs and blocked by API validations for events occurring on/after the end timestamp, with a descriptive error message And historical records that used C remain readable and reportable When the admin attempts to delete C that has historical references Then the deletion is rejected with a 409/validation error indicating references exist When the admin deletes a code with no references Then a soft-delete is performed (removed from selection and standard listings, audit retained, ID not reusable)
Per-Reason Evidence and Notes Enforcement in Approval UI and API
Given a reason code configured with mandatory evidence types and a minimum notes length When an approver selects this code during a cap override Then the Approve action remains disabled until all required evidence is attached and notes meet the minimum length And API submission without required evidence or sufficient notes returns HTTP 422 with machine-readable error codes for each missing/invalid field When the admin updates the per-reason requirements Then Approval UIs and API validations reflect the new rules within 60 seconds of save
Localization and Fallback for Multi-Language Teams
Given translations exist for a reason code's Name and Description in en and es When a user with locale es-ES views the Approval UI or Admin UI Then the Spanish translations are displayed; if a translation is missing, the default locale (en) value is shown with no broken tokens And the Admin UI allows adding/editing translations per locale And the API returns localized fields based on the Accept-Language header (or defaults if absent)
Change History, Searchability, and Audit API
Given any create, edit, merge, deprecate, or delete attempt on a reason code When the action occurs Then an immutable audit entry is recorded with actor, timestamp, action type, target ID, and field-level diffs where applicable And Admin users can search and filter reason codes by name, status (active/deprecated), parent, mappings, effective dates, and actor, and export results to CSV And the API exposes GET /reason-codes/{id}/history returning a reverse-chronological list of audit entries with pagination

Smart Scope Builder

Create precise, time‑boxed permissions for each magic link in seconds. Choose exactly what the recipient can do—upload photos, confirm ETA, approve within caps, view status, or message—scoped to a specific unit, work order, or timeframe. Save role presets (Tenant Upload, Vendor ETA Update) to eliminate mis-permissions, speed setup, and reduce security risk.

Requirements

Granular Action Scoping
"As a property manager, I want to choose exactly which actions a recipient can take on a work order so that I can share access without exposing unrelated data or controls."
Description

Provide fine-grained permissions per magic link, allowing selection of discrete actions—Upload Photos, Confirm ETA, Approve Within Cap, View Status, and Message—scoped to specific resources (unit and/or work order) and constrained by optional time windows and usage limits. Implement a scope object that captures allowed actions, resource identifiers, monetary caps for approvals, and constraints (start/end time, max uses, IP/geo limits). Enforce scope at both API and UI layers so out-of-scope operations are blocked and hidden. Integrate with FixFlow’s work order, messaging, storage, and approvals services to evaluate scope on each request. This capability minimizes over-permissioning, reduces security risk, and speeds setup while ensuring recipients can perform only what is explicitly allowed.

Acceptance Criteria
Upload Photos Only to a Specific Work Order
Given a magic link scoped with actions=[UploadPhotos] and resource.workOrderId=WO-123 When the recipient opens the link and uploads images Then the images are stored on WO-123 and the operation returns 201 Created And the recipient cannot access messaging, approvals, ETA, or status views (UI elements absent) And any attempt to upload to a different work order or resource returns 403 ScopeViolation with reason="out_of_scope" And an audit log entry is recorded with subject=linkId, action=UploadPhotos, resource=WO-123, outcome=success or denied
Approval Within Monetary Cap for Work Order
Given a magic link scoped with actions=[ApproveWithinCap], resource.workOrderId=WO-456, and approvalCap.amount=250 USD When the recipient approves a quote of 200 USD on WO-456 Then the approval is accepted (200 OK) and approval record shows approver=linkId and cap=250 USD And when the recipient attempts to approve 251 USD on WO-456 Then the request is blocked with 403 CapExceeded and no approval record is created And UI prevents submission above cap with inline validation and disabled submit And attempts to approve any other work order return 403 ScopeViolation
Time-Window Constraints on Magic Link
Given a magic link scoped with actions=[ViewStatus], resource.workOrderId=WO-789, constraints.startAt=T1, constraints.endAt=T2 When the recipient opens the link before T1 Then access is denied with 403 NotActiveYet and no data is returned When the recipient opens the link between T1 and T2 Then the request succeeds (200 OK) and only in-scope data for WO-789 is displayed When the recipient opens the link after T2 Then the link is expired and returns 410 LinkExpired, and subsequent UI shows an expired state And all API endpoints reject the link after T2 regardless of client cache
Max Uses Constraint and Link Invalidation
Given a magic link scoped with actions=[Message], resource.workOrderId=WO-321, constraints.maxUses=3 When the recipient sends the first, second, and third messages via the link Then each request succeeds (201 Created) and usesCount increments to 1, 2, and 3 respectively When the recipient attempts a fourth message Then the request is rejected with 410 LinkConsumed and the link can no longer be used for any action And audit log records the consumption event with usesCount=3 at time of invalidation
IP/Geo Restriction Enforcement
Given a magic link scoped with actions=[ViewStatus], resource.workOrderId=WO-654, constraints.ipAllowList=[203.0.113.0/24], constraints.geoAllowList=["US"] When the recipient accesses from an IP within 203.0.113.0/24 and country=US Then the request succeeds (200 OK) When the recipient accesses from an IP outside the CIDR or from a country not in the allow list Then the request is denied with 403 IpGeoRestricted and no resource data is leaked And all denials are logged with sourceIp, geo, and reason
Unit-Level Scope Applies Across Multiple Work Orders
Given a magic link scoped with actions=[ViewStatus], resource.unitId=UNIT-100 (no workOrderId) And the unit has open work orders WO-A and WO-B When the recipient views status via the link Then statuses for WO-A and WO-B are visible (200 OK) and no other units are shown When the recipient attempts to access a work order in a different unit Then the request is denied with 403 ScopeViolation And audit entries show resources accessed are limited to unitId=UNIT-100
UI Hides and API Blocks Out-of-Scope Actions (Confirm ETA Only)
Given a magic link scoped with actions=[ConfirmETA], resource.workOrderId=WO-777 When the recipient opens the web portal Then only the ETA confirmation UI is visible; upload, approval, message, and status views are not rendered When the recipient submits a valid ETA update for WO-777 Then the update succeeds (200 OK) and is attributed to linkId in the work order timeline When the recipient attempts to call messaging, photo upload, or approval APIs using the same link Then each request is rejected with 403 ScopeViolation and no side effects occur
Time-Boxed Magic Links
"As a landlord, I want links to automatically expire after a set timeframe so that temporary collaborators don’t keep access longer than needed."
Description

Enable creation of magic links with explicit start and end times, duration presets, and optional one-time or limited-use counters. Enforce expiration server-side with token payloads that include scope and expiry, plus centralized revocation. Provide clock-skew tolerance, extend/shorten controls, and a friendly expired state with a request-extension option. Log all expirations and revocations for audit. Integrate with the token service, routing, and notifications so links deactivate predictably and recipients receive clear feedback. This reduces lingering access, aligns with security policies, and supports time-sensitive workflows.

Acceptance Criteria
Access Window Enforcement and Expired State
Given a magic link with start_at=T1 and end_at=T2 and server clock-skew tolerance S, When a recipient attempts access at time < (T1 - S), Then the server denies access with HTTP 403 and error_code="link_not_started" and no protected actions are executed. Given the same link, When a recipient attempts access at time in [T1 - S, T2 + S], Then in-scope actions are permitted and the response is HTTP 200. Given the same link, When a recipient attempts access at time > (T2 + S), Then the server denies access with HTTP 403 and error_code="link_expired" and the UI displays a friendly expired state with a request-extension option. And all denials are enforced server-side regardless of client time or cached pages.
Token Payload, Scope, and Server Enforcement
Given a magic link created for work_order_id="WO-123" and unit_id="U-10" scoped to actions=["upload_photos"] with start_at=T1 and end_at=T2, When the link is generated, Then the token payload includes claims: jti (unique), nbf=T1, exp=T2, scope=["upload_photos"], resource={work_order_id:"WO-123", unit_id:"U-10"} and is signed by the token service. Given a recipient uses the token within [T1, T2], When the recipient calls an out-of-scope operation (e.g., approve_within_caps), Then the server responds HTTP 403 with error_code="scope_insufficient" and no side effects occur. Given a recipient uses the token within [T1, T2], When the recipient calls an in-scope operation (upload_photos), Then the server responds HTTP 200 and the action is attributed to the token jti in the audit log.
Duration Presets and Custom Times
Given the Smart Scope Builder is opened, When the user selects a duration preset (e.g., 15m, 1h, 24h), Then the system auto-sets end_at = start_at + preset duration and displays a human-readable summary (e.g., "Expires in 1 hour"). Given the user selects Custom duration, When end_at is set earlier than start_at, Then the form blocks save and shows validation "End time must be after start time". Given a preset is selected and a link is saved, When a notification is sent to the recipient, Then the message includes the local date/time window (start and end) and timezone and the link routes to the correct resource. And the generated token reflects the computed nbf/exp exactly.
One-Time and Limited-Use Counters
Given a magic link with max_uses=1, When the recipient completes the first successful in-scope action, Then remaining_uses becomes 0 and all subsequent requests are denied with HTTP 403 and error_code="use_limit_exceeded". Given a magic link with max_uses=5, When the recipient performs two successful in-scope actions, Then remaining_uses becomes 3 and the third through fifth actions succeed; the sixth attempt is denied with HTTP 403 and error_code="use_limit_exceeded". Given a link with max_uses>0, When a request is denied due to auth/scope/time window, Then remaining_uses is not decremented.
Centralized Revocation and Immediate Deactivation
Given an active magic link, When an authorized user revokes the link via UI or API, Then within 5 seconds all requests using the token jti are denied with HTTP 403 and error_code="link_revoked" and active sessions are invalidated. And the recipient sees a friendly revoked state if they follow the link. And an audit record is created with fields {jti, actor_id, timestamp, reason="revoked", prior_exp}. And a notification is sent to the recipient if notification_on_revoke is enabled.
Clock-Skew Tolerance Handling
Given server-configured clock-skew tolerance S=120 seconds, When a recipient device clock is ahead or behind by up to 120 seconds, Then access decisions rely on server time and are allowed if server_now is within [nbf - S, exp + S]. When the device skew exceeds 120 seconds beyond the allowed window, Then the server denies access with HTTP 403 and error_code="link_time_window" and includes the server_now and exp in the error payload for debugging. And all decisions are logged with fields {jti, server_now, nbf, exp, skew_tolerance_s}.
Extend/Shorten Controls with Audit and Notifications
Given an active magic link, When an authorized user extends the expiry by Δt, Then the token record exp is updated by Δt, changes propagate within 5 seconds, and recipients see the new expiry upon refresh; a notification is sent if configured. Given an active magic link, When an authorized user shortens the expiry to a past time, Then the link becomes immediately expired and returns HTTP 403 with error_code="link_expired" on subsequent requests. And every change (extend/shorten) writes an audit entry with fields {jti, actor_id, old_nbf, old_exp, new_nbf, new_exp, timestamp, reason}. And natural expirations (no manual action) also emit an audit entry with reason="expired".
Approval Cap Enforcement
"As a property owner, I want to allow vendors to proceed with minor repairs up to a limit so that urgent fixes aren’t delayed but costs stay controlled."
Description

Allow approvals via magic link to be constrained by a configurable monetary cap (absolute amount or percentage of budget), with currency awareness and tax handling. Calculate and display remaining cap, track cumulative consumption across multiple approvals from the same link, and require escalation when attempts exceed the cap. Tie approvals to specific work order line items and record cap details in the audit log. Integrate with FixFlow’s approvals and payments services to ensure accurate totals and prevent overspend. This enables rapid one-click approvals for minor repairs while maintaining cost control.

Acceptance Criteria
Absolute Cap Approval Within Limit
Given a magic link scoped to Work Order WO-123 with an absolute approval cap of 200.00 USD applied to totals including tax And the remaining cap is 200.00 USD When the approver approves Line Item LI-1 for a subtotal of 180.00 USD with 10% tax (total 198.00 USD) Then the approval is accepted And the remaining cap updates to 2.00 USD And the approval is recorded against LI-1
Absolute Cap Exceed Escalation
Given a magic link scoped to WO-123 with an absolute approval cap of 200.00 USD applied to totals including tax And the remaining cap is 2.00 USD When the approver attempts to approve LI-2 for a total of 5.00 USD Then the approval is blocked And an escalation is required and shown to the user And no cap consumption is recorded And an audit log entry captures the blocked attempt with requested total 5.00 USD and remaining cap 2.00 USD
Percentage Cap Enforcement Against Budget
Given a work order budget of 1,000.00 EUR and a magic link cap of 20% of budget applied to totals including tax And the remaining cap is 200.00 EUR When the approver approves LI-3 for a total of 150.00 EUR Then the approval is accepted and the remaining cap becomes 50.00 EUR When the approver then attempts to approve LI-4 for a total of 60.00 EUR Then the approval is blocked with escalation required And the remaining cap remains 50.00 EUR And all amounts are rounded to the currency minor unit (2 decimals)
Remaining Cap Calculation and Display
Given an approver opens the magic link approval screen When the screen loads Then the remaining cap is displayed with currency and tax basis label (e.g., "Remaining: USD 50.00 incl. tax") And the value matches the Approvals Service remaining cap within 0.01 of currency minor unit When an approval is completed Then the displayed remaining cap updates within 2 seconds to the new value And a refresh from another session shows the same remaining cap
Cumulative Consumption Across Multiple Approvals
Given a magic link with an absolute cap of 300.00 USD applied to totals including tax And the remaining cap is 300.00 USD When the approver approves three line items totaling 295.00 USD Then the remaining cap becomes 5.00 USD When the approver attempts a further approval of 5.00 USD total Then the approval is accepted and the remaining cap becomes 0.00 USD When the approver attempts any additional approval Then the approval is blocked with escalation required
Line Item Binding and Audit Logging
Given approvals via magic link must be tied to specific work order line items When an approval is made Then the approval record includes the line item IDs, cap type (absolute or percent), cap value, currency, tax amounts, remaining cap before and after, and timestamp And the audit log entry is immutable and exportable And the audit trail links the approval to the magic link ID and approver identity (email or phone) captured via the link
Concurrency and Integration Overspend Prevention
Given two approvers use the same magic link with 50.00 USD remaining cap applied to totals including tax When both submit approvals nearly simultaneously for totals of 30.00 USD and 25.00 USD Then only the first processed approval is accepted And the second is blocked with escalation required And the remaining cap never goes below 0.00 USD And Approvals and Payments services reflect the final accepted totals consistently within one transaction When the Payments Service finalizes tax higher than estimated causing the total to exceed remaining cap Then the approval is held in Pending Escalation and no payment is captured until escalated
Role Presets Library
"As a small property manager, I want to pick a preset for common tasks so that I can send the right link in seconds without mistakes."
Description

Provide an organization-scoped library of reusable scope presets (e.g., Tenant Upload, Vendor ETA Update, Owner Within-$250 Approval) with versioning, cloning, and search. Each preset defines a set of actions, default duration, resource targeting (unit vs specific work order), and optional caps. Selecting a preset pre-populates the Smart Scope Builder, allowing users to adjust before issuing. Include validation and guardrails to prevent conflicting selections and to highlight risky combinations. Integrate with org settings and permission models for consistent rollout. This accelerates setup, reduces mis-permissions, and promotes consistent access patterns.

Acceptance Criteria
Create Preset with Actions, Duration, Targeting, and Caps
- Given I am an Org Admin with Manage Presets permission, When I create a preset by selecting actions (Upload Photos, Confirm ETA, Approve Within Cap, View Status, Message), setting a default duration, choosing resource targeting (Unit or Specific Work Order), and optional monetary caps, Then the preset saves with a unique ID and displays in the library with its name, actions, targeting, duration, cap, creator, and updated timestamp. - Given required fields are incomplete or invalid, When I attempt to save, Then the Save action is disabled and field-level validation messages indicate what must be corrected. - Given I enter a monetary cap, When I save, Then the cap is stored as a currency amount with two decimals and a currency code; values outside org-defined min/max bounds are rejected with an error. - Given I define a default duration, When I save, Then the duration is stored in minutes and must be between 15 minutes and 30 days; out-of-range values are blocked with an error.
Apply Preset to Smart Scope Builder
- Given I select a preset from the library, When I open Smart Scope Builder to issue a magic link, Then preset-defined actions, duration, targeting, and caps pre-populate the builder within 200 ms. - Given the preset is applied, When I adjust fields in the builder, Then adjustments affect only the pending issued link and do not change the saved preset. - Given the preset sets maximum caps or duration, When I attempt to exceed those limits in the builder, Then the UI blocks the change and shows a validation message explaining the limit.
Preset Versioning and Cloning
- Given an existing preset v1, When I edit and publish changes, Then a new immutable version v2 is created with semantic version increment, changelog note, author, and timestamp, and v1 remains available. - Given active magic links were issued from v1, When v2 is published, Then existing links retain v1 scope; new links issued from the preset default to v2. - Given I select Clone on a preset, When I confirm, Then a new preset is created with identical configuration, a new unique ID, version v1.0.0, and a prefilled name of "Copy of <original name>".
Search, Filter, and Sort Presets
- Given 1,000 presets exist, When I search by name, actions, targeting type, tags, or modified-by, Then results return within 300 ms and highlight matched tokens. - Given I apply filters (e.g., Action=Approve, Targeting=Work Order, Status=Active), When I apply them, Then only presets matching all conditions are shown and filter chips reflect the active filters. - Given no presets match my query, When the search completes, Then an empty state appears with "No presets match your filters" and a Clear Filters action. - Given I sort by Updated Desc, When I apply the sort, Then the list orders by updatedAt descending and the preference persists for my user.
Guardrails for Conflicting and Risky Selections
- Given I enable Approve Within Cap and set Cap=None, When I save, Then the system blocks save and shows "Approval requires a monetary cap" inline. - Given I target a Unit and enable Vendor ETA Update for a recipient not vendor-verified, When I save, Then the system flags the preset as Risky, shows a warning explaining the risk, and requires explicit confirmation to proceed. - Given I enable Message without View Status, When I attempt to save, Then the system blocks save with "Messaging requires View Status" and highlights both fields. - Given I set duration > 72h for a Vendor role at Unit scope, When I save, Then the system flags as Risky per org policy threshold and logs an audit event if I confirm.
Org-Scoped Access and Auditability
- Given I belong to Org A, When I view the library, Then only Org A presets are visible; other orgs' presets are neither listed nor returned by APIs. - Given my role lacks Manage Presets, When I attempt to create, edit, or delete presets, Then these actions are hidden in the UI and blocked at the API with 403 responses, and an audit log is recorded. - Given any preset is created, updated, cloned, versioned, or applied to issue a link, When the action completes, Then an immutable audit event is recorded with actor, orgId, presetId, version, action type, diff, timestamp, and requestId.
Scope Preview and Simulator
"As a coordinator, I want to preview what a recipient can see and do before sending the link so that I avoid granting the wrong access."
Description

Offer a real-time preview of the recipient’s view and an action simulator that tests allowed versus blocked operations before issuing the link. The preview renders the exact UI sections, data, and controls the recipient will see based on the configured scope, and the simulator runs backend authorization checks on representative actions. Provide inline warnings for risky or uncommon combinations and show how the experience changes when toggling options. Integrate with front-end routing and backend authorization services to guarantee accuracy. This prevents misconfiguration, reduces support requests, and builds trust in the scope model.

Acceptance Criteria
Exact UI Preview by Scope
Given a scope configured with Upload Photos=On, Confirm ETA=Off, Approve up to cap=Off, View Status=On, Message=Off for unit U123 and work order WO-456 within 2025-09-07 to 2025-09-08 When the preview loads Then only the Photos uploader and status timeline are visible And Approve, ETA update, Messaging, and cost/estimate fields are not rendered And only data for unit U123 and work order WO-456 is shown And fields not permitted by the scope (e.g., cost, contact info) are redacted
Simulator Allowed vs Blocked Parity
Given the above scope configuration When the simulator evaluates actions [UploadPhoto, ConfirmETA, Approve($250), ViewStatus, SendMessage] Then UploadPhoto and ViewStatus are marked Allowed And ConfirmETA, Approve($250), and SendMessage are marked Blocked with reason SCOPE_DENIED And each simulator result equals the backend authorization service response for the same principal, scope, and action And the simulator displays the reason code and concise explanation for each Blocked result
Inline Warnings for Risky Combinations
Given Approve up to cap=On with cap $1000 and View Estimates=Off When the configuration is saved in the builder Then an inline Risk warning appears labeled "Approvals without estimate visibility" And the warning links focus to the toggles that triggered it And removing the risky combination clears the warning immediately And warnings do not block link generation unless classified Critical
Real-time Update and Routing Guard
Given the preview and simulator panels are open When the user toggles Confirm ETA from Off to On Then the ETA section appears in the preview within 300 ms And the simulator shows ConfirmETA as Allowed within 300 ms And navigating within the preview to a disallowed route (e.g., /approve) displays a blocked state instead of rendering content And no stale controls from the prior scope remain visible
Timebox and Expiration Enforcement
Given a scope time window 2025-09-07 09:00 to 2025-09-08 17:00 America/Los_Angeles When the current time is 2025-09-07 10:00 PT Then the preview shows "Link expires Sep 8, 5:00 PM PT" And the simulator returns Allowed for in-window actions When the current time is 2025-09-08 17:01 PT Then the simulator returns Blocked with reason SCOPE_EXPIRED for all actions And the preview overlays an Expired state on all actionable controls
Pre-Issuance Validation Gate
Given a scope configuration is complete When the simulator run finishes with 0 Critical failures and backend authorization service health is OK Then the Generate Link button is enabled And a summary lists Allowed and Blocked actions with counts When any Critical failure occurs (e.g., auth service unavailable or parity mismatch) Then the Generate Link button is disabled and an actionable error is shown When only Risk warnings exist Then the button requires the user to check "Acknowledge risks" before enabling
Link Management Console
"As a property manager, I want a dashboard to view and manage all active links so that I can quickly revoke or extend access as situations change."
Description

Centralize visibility and control of all issued magic links with filters (status, recipient, unit, work order), search, and bulk actions. Display status (scheduled, active, expiring soon, expired, revoked), last access timestamp/IP, remaining uses, and scope details. Provide quick actions to revoke, extend, resend, or duplicate a link, and capture revoke reasons. Integrate notifications to inform recipients of changes and export capabilities for compliance reporting. This improves operational control, speeds incident response, and maintains clean access hygiene across the portfolio.

Acceptance Criteria
Filter and Search Across All Links
Given links with varying status, recipients, units, and work orders When the user applies multiple filters and an optional search term Then only links matching all selected filters and containing the term in recipient contact, unit address, work order ID, or link ID are listed Given the user clears all filters and search When the table refreshes Then all links are listed Given there are no matches When filters are applied Then the table shows an empty state with zero results Given a dataset of 10,000 links When applying or changing filters or search Then results render in <= 2 seconds Given the search term has mixed case or leading/trailing spaces When applied Then matching is case-insensitive and trims whitespace
Status and Metadata Display Accuracy
Given a link with start_at and end_at and not revoked and remaining_uses > 0 When now < start_at Then status = scheduled Given now is between start_at and end_at and not revoked and remaining_uses > 0 When end_at - now > 24 hours Then status = active Given now is between start_at and end_at and not revoked and remaining_uses > 0 When end_at - now <= 24 hours Then status = expiring soon Given now > end_at or remaining_uses = 0 Then status = expired Given revoked = true Then status = revoked regardless of times or uses Then status precedence is revoked > expired > expiring soon > active > scheduled Then the list displays last_access_at and last_access_ip updated on each successful access Then remaining_uses displays a number or Unlimited when not limited Then scope details display unit identifier, work order ID (if any), permitted actions, start_at, and end_at
Revoke Single Link with Reason Capture
Given a non-revoked link is selected When the user clicks Revoke Then a modal requires a revoke reason (Security concern, Scope change, Duplicate, Other) and allows optional notes up to 280 characters When the user confirms Then the link status changes to revoked, remaining_uses becomes 0, and further access is blocked immediately Then recipients receive a revocation notification within 1 minute Then an audit record is created with actor, timestamp, IP, and revoke reason and is visible in the console and export Given the link is already revoked When the user attempts to revoke Then the action is disabled and a tooltip explains why
Extend, Resend, and Duplicate Single Link
Given a link with status scheduled, active, or expired and not revoked When the user extends by a specified duration Then end_at increases by that duration and status recalculates accordingly Given a revoked link When the user attempts to extend Then the action is disabled Given any link When the user clicks Resend Then a notification is sent to the recipient containing the existing link URL and current scope, and last_sent_at is updated Given a link When the user clicks Duplicate Then a new link with a new token is created copying scope, recipient, and duration, starting at now, and the new link appears in the list Then each quick action completes in <= 2 seconds and shows a success or error message
Bulk Actions on Selected Links
Given the user has selected multiple links across pages When Bulk Revoke is executed with a required reason Then all eligible links are revoked and ineligible ones are skipped with per-link error reasons reported Given Bulk Extend is executed with a specified duration When applied to the selection Then eligible links have end_at increased and statuses updated; revoked links are skipped with reasons Given Bulk Resend is executed When applied to the selection Then notifications are sent to all recipients and last_sent_at updates per link Then a post-action summary shows counts of successes and failures and allows CSV download of results Then notifications for bulk actions are dispatched within 5 minutes
Compliance Export
Given a date range and any active filters When the user clicks Export Then a CSV is generated containing one row per link limited to the filter and date range Then the CSV includes columns: link_id, status, created_at, start_at, end_at, last_access_at, last_access_ip, remaining_uses, recipient_name, recipient_contact, unit_id, work_order_id, permitted_actions, revoke_reason, revoked_at, last_sent_at Then all timestamps are ISO 8601 UTC and fields are UTF-8 encoded Given the export would contain more than 50,000 rows When requested Then the system generates the file asynchronously and notifies the user when ready Then the export action is audit-logged with actor and timestamp
Access Audit Trail and Alerts
"As an operations lead, I want detailed logs and alerts on magic link usage so that I can investigate issues and enforce security policies."
Description

Record a detailed, immutable audit trail for every scoped link: issuance, changes, accesses, actions attempted, enforcement results (allowed/blocked), resource references, and environment data (IP, user agent). Provide in-app querying, CSV export, and webhook/SIEM integration. Add configurable alerts for anomalies such as repeated blocked attempts, access from unusual locations, or high-volume activity within short windows. Tie logs to work orders and units for end-to-end traceability. This supports compliance, accelerates investigations, and strengthens security monitoring.

Acceptance Criteria
Immutable Audit Log Entry Creation
Given a scoped magic link is issued, When the link is created, Then an audit entry is recorded with fields: event_type=issued, link_id, scope.permissions, scope.resources (work_order_id, unit_id), issuer_user_id, timestamp_utc_ms, request_ip, user_agent, correlation_id, hash, previous_hash. Given a scoped magic link exists, When its scope is changed, Then an audit entry is recorded with event_type=scope_changed and contains before_scope, after_scope, and enforcement_diffs. Given any access attempt to a scoped link, When the request is evaluated, Then an audit entry is recorded with event_type=access_attempt, result in {allowed, blocked}, rule_id, requested_action, resource_reference, timestamp_utc_ms, request_ip, geo.country, user_agent. Given audit storage, When entries are written, Then entries are immutable (append-only), a content hash is computed, previous_hash links to the prior entry for the same link_id, and a verification endpoint returns chain_status=valid for untampered chains. Given 1,000 concurrent access attempts per minute, When logging is enabled, Then no more than 0.1% events are lost and p95 write latency is ≤150 ms.
In-App Audit Log Query and Filter
Given audit data exists, When a user filters by any combination of date range (UTC), event_type, result, unit_id, work_order_id, link_id, IP, action, or permission, Then results match the filter and return within 2 seconds for up to 10,000 matching rows. Given results are displayed, When the user paginates, Then pages are stable and ordered by timestamp desc then id, and page size is selectable (25, 50, 100). Given a result row, When expanded, Then full payload fields are viewable including scope, enforcement rule, and correlation_id. Given a malformed filter, When the query is submitted, Then the user receives a validation error without executing the query.
CSV Export of Audit Logs
Given a current filter, When the user clicks Export CSV, Then a CSV is generated containing only the filtered results, with UTF-8 encoding, comma delimiter, a header row with stable column names, timestamps in ISO 8601 UTC, and LF newlines. Given up to 100,000 rows match, When export is requested, Then the file streams and completes within 60 seconds or provides an asynchronous downloadable link within 5 minutes; partial exports are not allowed. Given an export completes, When the user downloads it, Then a SHA-256 checksum is available and matches the file contents. Given the user lacks permission to export, When they click Export, Then the action is blocked and an audit entry records event_type=export_blocked with rule_id.
Webhook and SIEM Event Delivery
Given a webhook endpoint with a shared secret is configured and enabled, When new audit events occur, Then the system sends JSON payloads within 10 seconds of event time, signed with HMAC-SHA256 over the payload, including signature and timestamp headers. Given the receiver returns non-2xx, When retrying, Then exponential backoff is applied over 24 hours with at least 10 attempts; on success, retries stop; duplicates are prevented via idempotency_key and Event-ID. Given delivery failures exceed 10 consecutive attempts, When this occurs, Then an alert is raised in-app and via email to org admins with the last error and next retry time. Given SIEM forwarding is configured (syslog over TLS), When enabled, Then events are forwarded in the selected format (CEF or JSON) with required fields (timestamp, event_type, link_id, unit_id, work_order_id, result, IP) and transmit over TLS 1.2+.
Anomaly Alert: Repeated Blocked Attempts
Given alert thresholds are set (default: 5 blocked attempts within 10 minutes per link_id or IP), When the threshold is met, Then an anomaly alert is generated within 60 seconds containing link_id, rule_id, unit_id, work_order_id, IPs, geo summary, and the last 5 events. Given an alert is generated, When a user opens it, Then they can acknowledge, mute for a duration, or adjust thresholds; subsequent events within the mute window do not create new alerts but increment a visible counter on the alert. Given an IP, CIDR, or ASN is allowlisted, When blocked attempts come from allowlisted sources, Then no alert is triggered and an audit entry records suppression_reason=allowlist.
Anomaly Alert: High-Volume Activity Burst
Given an org-level threshold is set (default: >100 audit events within 5 minutes for a single link_id, unit_id, or IP), When the threshold is exceeded, Then a burst activity alert is generated within 60 seconds with counts, time window, top actions, and affected resources. Given a burst alert exists, When alert webhooks are configured, Then an alert payload is sent to the alert webhook endpoint and logged in the audit trail as event_type=alert_emitted with result in {sent, failed}. Given repeated bursts for the same entity, When alerts would be duplicative, Then alerts are throttled to at most 1 per entity per 10 minutes with an aggregated counter shown in the alert detail.
End-to-End Traceability to Work Orders and Units
Given any audit event is created for a link scoped to a unit and/or work order, When the event is stored, Then it includes work_order_id and unit_id; if either is missing for a scoped resource, the event is rejected and a system error with correlation_id is raised to on-call. Given a work order detail page, When a user opens the Audit tab, Then all related events for the work_order_id are displayed within 2 seconds for up to 5,000 rows, filterable by event_type and result, with deep links to the source link and participant profiles. Given a unit detail page, When a user opens the Audit tab, Then all events across all links scoped to that unit over the selected date range are displayed; totals and charts match counts returned by the API within 1%. Given a log entry, When the user clicks the correlation_id, Then related events across systems are shown in a timeline view ordered by timestamp with millisecond precision.

ForwardShield

Keep links safe even if they’re forwarded. Detect mismatches in phone/email, device, location, or IP and automatically downgrade to view‑only or trigger a quick step‑up check. Optional geofencing and time‑of‑day limits protect access without forcing passwords, preserving tenant trust and vendor speed.

Requirements

Context-Bound Magic Links
"As a tenant, I want a secure link that only works for me and my device so that I can quickly access my request without a password while preventing misuse if forwarded."
Description

Generate single-use, signed magic links that cryptographically bind to intended recipient identifiers (phone/email), role (tenant/vendor/manager), and observed context (device fingerprint hash, IP/ASN, coarse geolocation, user agent). Links contain no PII, expire on first use or after a configurable TTL, and rotate tokens on re-send to prevent replay. Integrates with FixFlow notifications (SMS/email) and one‑click approvals so that when a link is accessed, the system can verify the bound context before granting full privileges. Supports mobile web deep-linking, graceful degradation if the bound context cannot be fully observed, and backward compatibility for existing workflows.

Acceptance Criteria
Intended recipient opens magic link within TTL on bound device and network
Given a magic link is generated for recipient identifiers (phone/email) and role And the link is signed, single-use, with a TTL configured to 15 minutes And the link is cryptographically bound to device fingerprint hash, IP/ASN, coarse geolocation, and user agent When the intended recipient opens the link within TTL on the bound device, from the same ASN, and within the configured geolocation radius Then the system validates the signature and that all bound context signals match within thresholds And grants full role-based privileges, including any one-click approval on the target resource And records an audit event reason=full_grant without persisting PII in the URL or logs And immediately invalidates the token upon successful redemption
Forwarded link opened from unbound device/IP triggers step-up and view-only until verified
Given a magic link bound to recipient identifiers and context When it is opened on a device with mismatched fingerprint or different ASN or outside geolocation tolerance Then the system renders the target page in view-only mode and blocks write actions And prompts for step-up verification sent to the bound identifier (SMS code or email OTP) And upon successful step-up, elevates to full privileges for the current session only And upon failure or timeout, keeps access limited and records reason=step_up_failed And no PII is present in the URL at any point
Single-use enforcement and replay protection
Given a magic link token has been successfully redeemed once When the link is accessed again from any device or context Then the system responds with HTTP 410 Gone and message "Link expired or already used" And performs no state-changing actions And records an audit event reason=replay_blocked without storing PII
Resend rotates token and revokes prior links
Given a user requests a resend of a pending magic link for the same action and recipient When the system issues a new magic link Then it generates a new signed token with a fresh expiry and a different token value And immediately revokes all prior unredeemed tokens for that action and recipient And any prior link access returns HTTP 410 Gone And the new link deep-links to the same target without including PII
Graceful degradation with partial context observability
Given one or more bound context signals (e.g., device fingerprint or geolocation) are unavailable at access time When the recipient opens the magic link within TTL Then the system validates available signals (identifiers, user agent, IP/ASN) and applies the configured risk policy And if additional assurance is required, triggers step-up verification; otherwise grants least-privilege access (e.g., view-only) And existing workflows remain reachable without requiring passwords or account creation And logs indicate which signals were missing without persisting PII
Mobile web deep-linking to action target
Given a magic link targets a one-click approval or task in FixFlow When the link is opened on iOS Safari or Android Chrome Then the user lands on the specific mobile web route with state intact after verification And intermediate redirects do not expose token values in the referrer or browser history once redeemed And if the default browser cannot open, a fallback opens the same route successfully And the URL contains no PII at any step
Backward compatibility for existing notification workflows
Given notification templates created before deployment that reference existing link placeholders When messages are sent after Context-Bound Magic Links is enabled Then recipients receive functional links that open the intended workflow And no template changes are required to maintain functionality And legacy sessions are tagged so policies (e.g., step-up) can apply without breaking the flow And analytics distinguish legacy versus context-bound sessions
Mismatch Detection & Risk Scoring
"As a property manager, I want suspicious link accesses to be automatically detected so that forwarded links don’t grant full access to unauthorized parties."
Description

On link access, evaluate live context against the link’s bound attributes to detect mismatches in recipient identifier, device fingerprint, IP/ASN reputation, coarse geolocation, and time-of-day. Compute a configurable risk score with tunable weights and thresholds, accounting for benign variance (e.g., carrier IP rotation or device OS updates). Provide rule-based overrides (allow/deny lists per vendor, per property, or per portfolio) and produce a structured decision (allow full, downgrade to view‑only, or require step‑up). Designed to execute within a sub‑150ms budget to preserve vendor speed, with metrics emitted for observability and continuous tuning.

Acceptance Criteria
Baseline Low-Risk Access Allows Full Within 150ms
Given a link with bound recipient R1, device fingerprint D1, coarse geofence RegionA (CityA ±50 km), allowed hours 07:00–19:00 local, and risk policy P1 with thresholds: Allow Full <30, View-Only 30–69, Step-Up ≥70 And the IP/ASN reputation service is available and returns a reputation score ≤10 for the source IP When R1 accesses the link from device D1 within RegionA at 09:00 local time Then the computed risk score is <30 And the structured decision is Allow Full And end-to-end decision latency (from request receipt to decision) is ≤150 ms at p95 and ≤200 ms at p99 And metrics are emitted for the request including: request_id, decision, risk_score, policy_id, latency_ms, feature_vector_digest, and reason_codes
Benign Device Variance Does Not Escalate Risk
Given a previously allowed baseline event for recipient R1 on device fingerprint D1 under policy P1 (Allow <30, View-Only 30–69, Step-Up ≥70) And the same recipient R1 accesses again after a minor OS/browser update such that fingerprint similarity to D1 is ≥0.90 and the only detected change category is OS/UA patch-level When the new access occurs within the same geofence and allowed hours and from an IP with reputation score ≤20 Then the risk score delta versus the baseline is ≤5 points And the computed risk score remains <30 And the structured decision is Allow Full And no step-up is triggered
High-Risk ASN or Proxy Downgrades to View-Only
Given risk policy P1 (Allow <30, View-Only 30–69, Step-Up ≥70) And a link bound to recipient R1 and expected device fingerprint D1 When R1 accesses from device D1 but the source IP is from an ASN with reputation score ≥80 or detected as known proxy/VPN/Tor Then the computed risk score is between 30 and 69 inclusive And the structured decision is View-Only And all write or approval actions are disabled while read actions remain available And the UI presents a downgrade notice with a prompt to initiate step-up verification And metrics include reason_codes containing ip_asn_reputation_high
Out-of-Geofence and After-Hours Requires Step-Up
Given a property geofence centered on PropertyZ with radius 50 km and allowed hours 07:00–19:00 local under policy P1 (Allow <30, View-Only 30–69, Step-Up ≥70) When the link is accessed 500 km outside the geofence at 22:15 local time by the correct recipient on any device Then the computed risk score is ≥70 And the structured decision is Require Step-Up And a one-time step-up challenge (SMS or email OTP to the verified contact on file) is offered and can be completed within 5 minutes And upon successful step-up, access elevates to Allow Full for the active session; upon 3 failed attempts or timeout, access remains View-Only And metrics include step_up_offered=true, step_up_outcome, and step_up_latency_ms
Allow-List Override Grants Full Access Regardless of Score
Given VendorV is on the allow list for PropertyP And a link for PropertyP is accessed by VendorV under any contextual conditions When the decision engine evaluates the request Then the rule-based override takes precedence over risk scoring And the structured decision is Allow Full And metrics include override_applied=true, override_scope=property, override_type=allow, and policy_id And decision latency remains ≤150 ms at p95
Deny-List Override Forces Persistent View-Only
Given VendorX is on the deny list at the portfolio scope When any link under the portfolio is accessed by VendorX under any contextual conditions Then the rule-based override takes precedence over risk scoring And the structured decision is View-Only with no step-up option presented And all write/approval actions are disabled And metrics include override_applied=true, override_scope=portfolio, override_type=deny, and policy_id
Risk Policy Tuning Propagates and Is Observable
Given an admin updates the active risk policy from P1 to P2 by changing weights (e.g., increase IP reputation weight) and thresholds (e.g., View-Only lower bound from 30 to 25) When the configuration is saved Then a new immutable policy_id P2 is created and versioned And all new decisions across all nodes use P2 within ≤5 minutes of save time And metrics for decisions include the effective policy_id (P2) and reflect the updated weights/thresholds in computed risk_score And reverting to P1 is possible and takes effect across all nodes within ≤5 minutes, with metrics reflecting policy_id=P1
Adaptive Access Downgrade
"As a vendor, I want risky sessions to be safely limited to viewing details so that I can still see what’s needed without being able to approve or change jobs if a link was forwarded to me."
Description

When access risk exceeds a policy threshold, automatically transition the session to a view‑only mode that preserves transparency while protecting controls. Hide or redact sensitive fields (contact details, payment rates), disable high‑impact actions (approvals, schedule changes, vendor reassignment), and watermark pages with session identifiers. Present a clear, non‑accusatory banner explaining the downgrade and a single action to initiate step‑up verification. All downgrades are logged with reasons and linked to the originating request for audit and support.

Acceptance Criteria
Risk Threshold Triggers View-Only Downgrade
Given an authenticated user session whose computed access risk score exceeds the configured policy threshold When any protected portal page is requested Then the session transitions to view-only mode within 500 ms of risk evaluation and before any privileged controls are rendered And all privileged controls (approve, decline, schedule change, vendor reassignment, edit contact, edit rates) are not visible or interactive And the downgraded state persists across route changes, deep links, and hard refresh within the same session until step-up verification succeeds or the risk score falls below threshold And exactly one downgrade event with a unique downgrade_id is emitted per session downgrade occurrence
Sensitive Fields Redaction in Downgraded Mode
Given a downgraded session viewing a work order containing sensitive fields (tenant phone, tenant email, vendor contact details, payment rate, payout amount) When the page renders Then each sensitive value is replaced in the UI with the token "Redacted" And the corresponding API responses return masked values (e.g., null or "REDACTED") for those fields And the raw sensitive values are not present in the HTML source, DOM attributes, client-side caches, or network responses And print/PDF/CSV exports present the masked token instead of the raw values
High-Impact Actions Disabled and Server-Enforced
Given a downgraded session When the user attempts any high-impact action (approve/decline, schedule changes, vendor reassignment, editing contacts or rates) Then the related UI controls are disabled or hidden and cannot be activated via keyboard, mouse, or script And any direct invocation of the corresponding write APIs returns HTTP 403 FORBIDDEN with error code ACCESS_DOWNGRADED and a user-action hint to verify And no state change is persisted to the database And an access_denied event is logged with downgrade_id and attempted_action
Non-Accusatory Downgrade Banner with Single Step-Up CTA
Given a downgraded session When any portal page is displayed Then a persistent banner is shown at the top explaining the temporary view-only state in neutral language and without accusatory terms (e.g., excludes "fraud", "suspicious", "flagged") And exactly one prominent CTA to initiate step-up verification is present; secondary actions are not shown And activating the CTA opens the step-up flow and passes downgrade_id and return_url And upon successful verification, full privileges are restored within 2 seconds and the banner is dismissed And upon failure or cancel, the session remains downgraded and the banner persists with a non-blocking retry message And the banner meets WCAG 2.1 AA for contrast, is screen-reader labeled, and is fully keyboard accessible
Session Identifier Watermark on View-Only Pages
Given a downgraded session When any portal page (including printable views) is displayed Then a repeating, low-opacity diagonal watermark is visible and includes session_id (short), downgrade_id, and UTC timestamp And the watermark is included in print/PDF output And the watermark does not obscure primary content (opacity 5–10%) and does not reduce text contrast below WCAG AA And if the watermark DOM element is removed, it is restored automatically within 1 second
Audit Log of Downgrade Linked to Originating Request
Given a session is downgraded When the downgrade occurs Then an audit record is written within 1 second containing: downgrade_id, session_id, user_id (if available), originating_request_id (e.g., work_order_id or link_id), policy_threshold, risk_score, risk_reasons[], device_id, IP, coarse_geo, previous_access, new_access, and UTC timestamp And the audit record is immutable and queryable by support/admin via an audit endpoint within 2 seconds And the audit record includes a resolvable link or reference to the originating request And subsequent step-up attempts append outcome, verifier type, and timestamps to the same record
Step‑Up Verification Microflows
"As a tenant, I want a quick way to verify it’s me when prompted so that I can regain full access without creating or remembering a password."
Description

Provide fast, low‑friction verification flows triggered by policy or user action: one‑tap SMS/email OTP, verified call‑back, or secure rebind to a trusted device. Target sub‑30s completion with automatic context re‑evaluation on success to restore full access. Include rate limiting, retry UX, accessibility and localization, and fallback offline codes for poor connectivity. Integrate with existing FixFlow identity data (verified emails/phones) and support manager‑approved overrides with time‑boxed elevation for urgent cases.

Acceptance Criteria
One‑Tap OTP Verification Completes Under 30s
Given a step-up is triggered by ForwardShield policy, When the user selects SMS or email OTP, Then only verified phone numbers/emails from FixFlow identity are shown. Given an OTP is issued, When the user taps the magic link in the message, Then the session is verified without manual code entry and the OTP is invalidated. Given an OTP is issued, When the user enters the 6-digit code, Then verification succeeds only if the code matches, is single-use, and unexpired (TTL ≤ 5 minutes). Given normal network conditions, When users complete OTP via link or code, Then p90 completion time ≤ 30 seconds and success rate ≥ 95%. Given OTP messages are sent, Then delivery status and verification outcome are logged with timestamp, channel, and request ID.
Verified Call‑Back Step‑Up
Given a verified phone exists for the user, When call-back is selected, Then the system initiates an automated call within 5 seconds. Given the call connects, When the user confirms via DTMF challenge (press # then a 2-digit code read aloud), Then the session is verified and the code is invalidated. Given call-back is used, Then p50 completion time ≤ 25 seconds and p90 ≤ 45 seconds; abandonment rate ≤ 10%. Given multiple attempts, Then a maximum of 3 call attempts are allowed per 15 minutes per user; further attempts are blocked with guidance to try another method. Given any call-back attempt, Then an audit record captures caller ID, timestamps, attempt count, and outcome without exposing full phone numbers.
Secure Rebind to Trusted Device
Given the user has at least one trusted device, When rebind is selected, Then a push prompt is sent only to devices with an active device-binding token associated with the user. Given the prompt is displayed, When the user approves on a trusted device, Then the requesting device is bound and the session is elevated. Given device rebind completes, Then p90 end-to-end time from request to elevation ≤ 30 seconds. Given a new device is bound, Then prior bindings remain unchanged unless the user explicitly revokes; all binding changes are auditable. Given an unrecognized device attempts rebind without a trusted approver, Then the request is denied and no device state changes occur.
Context Re‑Evaluation Restores Full Access
Given the user successfully completes a step-up method, When policy is re-evaluated, Then access upgrades from view-only to full within 2 seconds if policy conditions are satisfied. Given elevation is granted, Then a step_up_satisfied claim is set with a policy-defined TTL (default 24 hours) to suppress repeat prompts unless risk increases. Given risk increases (e.g., new device fingerprint, geofence breach, or IP anomaly), When a new high-risk action is attempted, Then step-up is required again even if TTL remains. Given re-evaluation fails, Then the user remains view-only and receives a clear explanation and alternative methods to verify.
OTP Rate Limiting and Retry UX
Given a user requests an OTP, Then limit to max 5 sends per channel per 15 minutes and max 10 per day per user; excess requests return a generic throttling message. Given invalid OTP submissions, Then after 5 failed attempts per 15 minutes the channel locks for 15 minutes and alternate methods are offered. Given transient delivery errors, Then retry UI displays status, remaining attempts, and backoff timers without revealing account existence. Given any rate limit or lockout event, Then an audit entry records actor, channel, counts, IP, user agent, and reason code.
Accessibility and Localization Compliance
Given any step-up screen, Then it conforms to WCAG 2.2 AA: focus order, labels, keyboard operability, contrast ≥ 4.5:1, and 44px minimum touch targets. Given locale is en-US or es-US, When step-up begins, Then UI text, SMS/email templates, and IVR prompts are localized with auto-detection and user-selectable override. Given assistive technology is used, Then live status updates (e.g., "Code sent", errors) are announced via ARIA without stealing focus. Given time-limited prompts, Then users can request more time or switch methods without losing progress.
Offline Codes and Manager Overrides for Urgent Cases
Given a user has pre-generated offline codes, When connectivity is poor, Then entering a valid single-use offline code elevates the session and consumes the code; remaining codes are shown with counts. Given offline codes are regenerated, Then all prior unused codes are invalidated and regeneration is auditable. Given no verified channels are available or verification repeatedly fails, When a manager with Override permission approves elevation, Then a time-boxed override is granted with mandatory reason and expiry between 15 minutes and 4 hours. Given an active override, Then the user is not prompted for step-up for in-scope actions until expiry; all elevated actions are tagged with override ID and are auditable. Given an override expires or is revoked, Then elevation ends immediately and subsequent high-risk actions require step-up.
Geofencing & Time Windows
"As a small property manager, I want links to work only in expected places and times so that access stays convenient for my team but safer if someone forwards a link outside our normal operating context."
Description

Offer optional per‑role and per‑link policies that constrain access by location and schedule without requiring passwords. Support geofencing by property coordinates or last trusted location with configurable radius and country/region allowlists, plus business‑hours and contractor shift windows with holiday exceptions. Provide temporary overrides with automatic expiry and full logging. Policies are evaluated alongside risk scoring to avoid false positives and maintain fast access for trusted patterns.

Acceptance Criteria
Property Coordinates Geofence Enforcement
Given a link with a geofence policy defined by property coordinates and a configured radius R and accuracy tolerance A When a user opens the link from a device reporting GPS location with accuracy ≤ A and distance to the property ≤ R Then full access is granted without a password prompt, the decision is logged with reason geofence_pass, and policy evaluation completes within the configured SLA Given the same policy When the device location distance to the property > R and the risk score is below the trusted threshold Then the configured mismatch action is enforced (downgrade to view-only or block), no password prompt is shown, and the decision is logged with reason geofence_fail including computed distance and method Given the same policy When the device location distance to the property > R but the risk score ≥ the trusted threshold Then a step-up check is triggered (SMS or email code), and upon success full access is granted; upon failure the mismatch action is enforced; all outcomes are logged Given the same policy When precise GPS is unavailable Then IP-based geolocation is used; if confidence is below the configured minimum, a step-up check is required before granting full access; decisions and methods are logged
Country/Region Allowlist Enforcement
Given a per-link or per-role country/region allowlist and a configured mismatch action When the user’s resolved country/region (via IP geolocation or device signals) is in the allowlist Then full access is granted without a password prompt and the decision is logged with reason country_pass and the detection method Given the same allowlist When the resolved country/region is not in the allowlist and the risk score is below the trusted threshold Then the mismatch action is enforced (downgrade or block) and the decision is logged with reason country_fail Given the same allowlist When the resolved country/region is not in the allowlist but the risk score ≥ the trusted threshold Then a step-up check is triggered; on success full access is granted, on failure the mismatch action is enforced; all outcomes are logged Given VPN/Proxy detection is enabled When the user is on a known VPN/Proxy or anonymizer Then the access is treated as a location mismatch and handled per the above rules; IPv4 and IPv6 are both supported and logged
Business Hours and Shift Windows with Holiday Exceptions
Given a policy with business-hours and/or role-based shift windows defined in the property time zone TZ and a holiday calendar with optional exceptions When an access attempt occurs within the active window (accounting for DST transitions) Then full access is granted without a password prompt and the decision is logged with reason timewindow_pass including TZ and window identifier Given the same policy When an access attempt occurs outside the active window and the risk score is below the trusted threshold Then the configured mismatch action is enforced (downgrade or block) and the decision is logged with reason timewindow_fail including the next open window Given the same policy When an access attempt occurs outside the active window but the risk score ≥ the trusted threshold Then a step-up check is triggered; on success full access is granted, on failure the mismatch action is enforced; all outcomes are logged Given the same policy When the day is listed as a holiday without an exception override Then windows for that day are not active and access attempts are handled as outside the window; decisions are logged
Temporary Overrides with Auto-Expiry and Audit
Given an administrator with permission creates a temporary override with defined scope (constraints to relax or bypass), subject (role or link), and duration with expiry timestamp When the override is active and a matching access attempt would otherwise violate the scoped constraint(s) Then the system applies the override behavior (allow or require step-up) without a password prompt and logs the override_id and scope in the decision Given the same override When the override reaches its expiry timestamp Then it automatically deactivates without manual action and subsequent decisions revert to baseline policy evaluation; the expiry is logged Given override creation When an override is created, updated, or revoked Then an immutable audit entry is recorded with actor, subject, scope, reason, created_at, expires_at, and change history; attempts to create overrides beyond the maximum configured duration or with invalid scope are rejected and logged
Last Trusted Location Geofence Behavior
Given a policy using last trusted location with configured radius R and lookback window N days When a user has a recorded last trusted location from a successful full-access event within N days and opens the link within distance ≤ R of that location Then full access is granted without a password prompt and the decision is logged with reason last_trusted_pass Given the same policy When no last trusted location exists or the last record is older than N days Then the first access requires a step-up check; on success full access is granted and the last trusted location is updated to the current location; on failure the mismatch action is enforced; outcomes are logged Given the same policy When the current location is > R from the last trusted location and the risk score is below the trusted threshold Then the configured mismatch action is enforced and the decision is logged with reason last_trusted_fail including distance Given the same policy When the current location is > R but the risk score ≥ the trusted threshold Then a step-up check is triggered; on success full access is granted and the last trusted location is updated; on failure the mismatch action is enforced; outcomes are logged
Risk Scoring Co-evaluation and Step-Up Orchestration
Given policy evaluation produces zero or more mismatches and a numeric risk score S with configured thresholds for trusted, review, and high-risk When there are no policy mismatches and S ≥ the baseline threshold Then full access is granted immediately without a password prompt and the decision is logged with reason policy_and_risk_pass Given the same evaluation When exactly one mismatch is classified as minor per configuration and S ≥ the trusted threshold Then a step-up check is triggered instead of enforcing downgrade/block; on success full access is granted; on failure the mismatch action is enforced; outcomes are logged Given the same evaluation When multiple mismatches are present or S ≥ the high-risk threshold Then the stricter of the configured mismatch action and the high-risk action is enforced without offering password-based authentication; the decision is logged with reasons and contributing signals Given any step-up flow When the user completes the step-up via approved methods (e.g., SMS or email code) Then access is upgraded for the current session only; no password is required; step-up result and method are logged
Decision Logging and Transparency
Given any access decision under ForwardShield for a protected link When the decision is produced Then a structured log entry is emitted containing correlation_id, link_id, subject identifier, role, policy versions, evaluation start/end timestamps, geofence inputs and results (coordinates, method, accuracy, computed distances), country/region evaluation (source method, result), time window evaluation (TZ, window id, result), risk score and key signals, override_id if applied, final action (allow/downgrade/block/step-up), and error fields if applicable Given logging is enabled When viewing the admin audit console Then decision logs are queryable and exportable within the configured ingestion SLA and filterable by time range, property, role, action, reason, and correlation_id Given a failed or downgraded access When the tenant or vendor views the inline notice Then a human-readable reason code (e.g., outside_geofence, outside_hours, country_block, vpn_detected) is displayed without revealing sensitive coordinates; the same reason code is present in the log entry
Policy Console & Audit Trails
"As an administrator, I want clear controls and visibility into access decisions so that I can tune policies for security without slowing down tenants or vendors."
Description

Deliver an admin console and APIs to configure ForwardShield policies (risk thresholds, downgrade behaviors, step‑up methods, geofence/time rules) at portfolio, property, and role levels. Include a preview “observe‑only” mode to simulate policies before enforcement, real‑time dashboards for events (mismatches, downgrades, verifications), searchable audit logs with export/webhooks, and SIEM integration. Provide per‑event evidence packets to streamline support investigations and demonstrate compliance, with retention and redaction controls aligned to data policies.

Acceptance Criteria
Multi‑Scope Policy Configuration & Inheritance
Given an admin with Policy Admin permissions, When they create a ForwardShield policy at portfolio scope defining risk_thresholds, downgrade_behaviors, step_up_methods, geofence, and time_of_day rules, Then the policy is saved, versioned, and retrievable via UI and GET /policies?scope=portfolio with matching fields. Given an existing portfolio policy, When an admin creates a property-level override for the same fields, Then the effective policy for that property reflects property overrides and inherits unspecified fields from portfolio. Given property and role-level overrides, When computing an effective policy for a user with that role at that property, Then precedence is role > property > portfolio and GET /policies/effective returns the resolved policy details. Given invalid inputs (e.g., non-closed geofence polygon, overlapping time windows, unknown step_up_method), When saving, Then the UI/API rejects with 400 and field-level validation messages. Given a policy update, When saved, Then an audit log entry records who, when, previous vs new values (diff), scope, and version_id. Given access control, When a non-Policy Admin attempts create/update/delete, Then the UI hides actions and the API returns 403.
Observe‑Only (Simulation) Mode Enforcement Safety
Given a policy with observe_only=true, When eligible risk events occur, Then no enforcement is applied to end users (no downgrade or step-up prompt) and normal access continues. Given observe_only=true, When a decision is computed, Then the event is logged with action=simulated_decision and includes would_enforce_action and policy_version. Given dashboard access, When filters are applied, Then simulated events are clearly labeled and counted separately from enforced events. Given observe_only configuration with sample_rate=0.25, When traffic flows, Then approximately 25% (±5%) of eligible events are simulated and counted; the remainder follow enforcement settings. Given observe_only is toggled off, When saved, Then enforcement behavior takes effect within 60 seconds and is reflected in effective policy responses.
Real‑Time Events Dashboard Visibility & Drill‑Down
Given live ForwardShield traffic, When mismatches, downgrades, and verifications occur, Then corresponding events appear on the dashboard within 5 seconds at p95 latency. Given dashboard filters, When filtering by event_type, scope (portfolio/property), role, policy_version, and time range, Then the results and aggregates update accordingly and match underlying counts from the events API. Given an event row, When clicked, Then an event detail panel opens showing decision, policy_version, and evidence_packet_id with a link to evidence. Given auto-refresh is enabled, When 30 seconds elapse, Then the dashboard refreshes without duplicating counts; when disabled, no auto-refresh occurs. Given a user without Dashboard View permission, When accessing the dashboard URL, Then access is denied (403).
Audit Logs Search, Export & Webhooks Delivery
Given audit logs, When searching by actor, action, scope, policy_id, event_id, and time range (last 7 days), Then results return sorted by time desc with p90 query latency ≤ 2s and accurate counts. Given filtered results, When exporting, Then CSV and NDJSON downloads contain the same records, use UTC ISO-8601 timestamps, include headers (CSV), and row counts match the on-screen total. Given a registered webhook (URL, secret), When audit/event entries occur, Then POSTs are delivered within 10 seconds with HMAC-SHA256 signature header, idempotency-key, and application/json body; 2xx stops retries; 5xx retries up to 3 times with exponential backoff; 4xx does not retry. Given webhook monitoring, When deliveries fail, Then failures are visible with status and last_error; an admin can replay deliveries for a selected time window. Given audit immutability, When a policy is edited, Then a new audit entry is created; existing entries are never modified or deleted outside of retention policy purges.
SIEM Integration Configuration & Reliability
Given SIEM settings, When an admin configures destination (HTTPS URL, auth), Then Test Connection returns Success/Failure with diagnostics and is logged. Given enabled SIEM forwarding, When events/audits are produced, Then payloads are formatted as JSON and optionally CEF, with documented field mappings; PII fields can be disabled per destination. Given network instability, When delivery fails, Then the system retries with exponential backoff, guarantees at-least-once delivery, and preserves ordering per property stream. Given security requirements, When data is sent, Then TLS ≥ 1.2 is used and certificates are validated; if validation fails, delivery is halted and alerts are raised. Given observability, When viewing SIEM status, Then last_success_time, backlog_count, and error_rate are shown; alerts fire if backlog_count > 1000 or last_success_time > 15 minutes. Given pause is activated, When forwarding is paused, Then events buffer up to 72 hours or 5,000,000 events; older events are dropped with alerting and visible counters.
Per‑Event Evidence Packet Creation & Access
Given a decisionable event, When processed, Then an evidence_packet is created containing timestamp, event_id, policy_version, evaluated_signals (contact mismatch summary, device_fingerprint_hash, geolocation, ip, user_agent), decision, would_enforce_action (if simulated), and a cryptographic hash. Given authorized access, When opening evidence in UI or GET /evidence/{event_id}, Then the packet is returned within 1 MB, renders with sensitive fields masked by default, and unmask requires permission. Given download request, When exporting evidence, Then a signed JSON is provided with a signature that verifies against the platform public key; tamper detection shows a warning and API returns 422 if verification fails. Given navigation from dashboard, When clicking evidence_packet_id, Then the evidence view loads the matching packet; nonexistent IDs return 404 and are logged. Given retention policy, When evidence ages out, Then the packet is purged and no longer retrievable; links show Expired per retention.
Retention & Redaction Controls Enforcement
Given retention settings, When an admin sets retention_days per event_type and scope within 30–1825 days, Then save succeeds; out-of-range values are rejected with 400 and field errors. Given scheduled purge, When items reach expiry, Then events, audit entries, and evidence packets are purged daily with an audit entry summarizing counts; 99% complete within 24 hours of expiry. Given legal hold, When a hold is applied to a scope or event_id, Then purges skip held records until the hold is removed; holds are audited. Given redaction, When an authorized user redacts PII fields (email, phone, ip, precise_geo), Then the fields are irreversibly masked in UI, API, exports, webhooks, and SIEM streams, and the redaction action is logged with who/when/what. Given retention policy changes, When updated, Then changes apply prospectively and do not restore previously purged data; current data follows the new schedule from the time of change.

OmniSend Failover

Deliver magic links over the channel that actually reaches the user—SMS, email, or WhatsApp—with automatic retries and smart fallbacks. If messages bounce or go unread, FixFlow switches channels or offers a voice readout code. Deep links open mobile capture instantly, cutting support tickets and first‑click failures.

Requirements

Omni-Channel Send Orchestrator
"As a tenant, I want to receive my magic link on the channel I actually use so that I can start my maintenance request immediately without hunting for emails or missed texts."
Description

Implements a rules-driven engine to deliver magic links over SMS, email, or WhatsApp based on tenant preferences, prior deliverability, geo/regulatory constraints, and quiet hours. Supports dynamic fallback and automatic retries with exponential backoff and jitter, idempotent send keys, and per-tenant routing policies. Integrates with FixFlow identity and event bus (e.g., MagicLinkRequested), leverages provider adapters (Twilio SMS/Voice, SendGrid, WhatsApp Business API), and uses localized, branded templates. Ensures consistent personalization, templating, and tracking tokens across channels to minimize first-click failures and reduce support tickets.

Acceptance Criteria
Preference- and Deliverability-Based Channel Selection
Given a MagicLinkRequested event for a tenant with channel preferences [SMS, Email, WhatsApp] and recent email hard bounce within 30 days, When the orchestrator evaluates preferences and deliverability, Then it selects SMS via Twilio as the primary send channel. Given a recipient located in a country where WhatsApp Business messaging is disallowed by policy, When the tenant preference is WhatsApp first, Then the orchestrator skips WhatsApp and selects the next policy-allowed channel in order. Given no channels are policy-allowed (due to geo/regulatory constraints or tenant policy), When the orchestrator evaluates routing, Then no provider call is made and a DeliveryFailed event is emitted with reason "NoAllowedChannels".
Quiet Hours Deferral by Tenant Timezone
Given tenant quiet hours are set to 21:00–08:00 in the recipient's local timezone, When a MagicLinkRequested event is received at 22:15 local time, Then no outbound message is sent and a DeferredForQuietHours event is emitted with scheduledTime = next 08:00 local. Given a deferred send scheduled for 08:00 local time, When the clock reaches 08:00 ± 1 minute, Then the orchestrator initiates the primary channel send and marks the deferment as fulfilled. Given quiet hours are not active (e.g., request at 10:00 local), When a MagicLinkRequested event is received, Then the orchestrator proceeds to immediate send without deferral.
Dynamic Fallback Across Channels with Voice Code Last-Resort
Given an SMS send is marked Delivered but no click is recorded within fallbackWaitSeconds (e.g., 300s), When the wait window elapses without click/open, Then the orchestrator attempts the next channel (Email via SendGrid) using the same magic link token and orchestrationId. Given a primary channel attempt returns Undelivered or a permanent error code, When fallback policy is enabled, Then the orchestrator immediately attempts the next allowed channel without waiting for the unread timeout. Given all message channel attempts fail to deliver or remain unclicked after the configured fallbacks, When voice fallback is enabled, Then the orchestrator initiates a Twilio Voice call that reads a 6-digit one-time code and the short URL, and records a VoiceFallbackInitiated event. Given a click is recorded on any channel, When subsequent scheduled fallbacks would occur, Then all pending fallbacks and retries for that orchestrationId are canceled.
Exponential Backoff with Jitter on Transient Failures
Given a provider adapter returns a transient failure (e.g., HTTP 429 or 5xx), When scheduling a retry, Then the delay follows exponential backoff baseDelay=30s with cap=10m and jitter ±20%, for a maximum of 3 attempts per channel. Given a provider adapter returns a permanent failure code (e.g., Twilio 30007 or SendGrid 400 with hard bounce), When evaluating retry, Then no retry is scheduled and the orchestrator moves to the next allowed channel or fails if none remain. Given a retry is scheduled, When the retry is enqueued, Then the orchestrator emits a DeliveryRetryScheduled event containing orchestrationId, channel, attemptNumber, and scheduledTime.
Idempotent Send Keys Prevent Duplicate Messaging
Given two MagicLinkRequested events carry the same idempotencyKey within a TTL of 10 minutes, When the orchestrator processes the second event, Then no additional outbound message is sent and the existing send record is returned with reason "IdempotencyHit". Given cross-channel fallbacks occur under one orchestrationId, When a new channel attempt is sent, Then the same magic link token and idempotencyKey are reused across channels to avoid generating multiple valid tokens. Given duplicate deliveries are attempted by external retries, When the orchestrator receives a request with an already-completed orchestrationId, Then it performs no-op and emits an IdempotentNoOp event.
Localized Branded Templates and Personalization Integrity
Given the recipient locale is fr-FR and tenant branding is configured, When composing SMS, Email, or WhatsApp content, Then the system selects the fr-FR template variant, applies tenant branding, and substitutes all placeholders without leaving unresolved tokens. Given locale resolution fails or no localized template exists, When composing content, Then the system falls back to en-US templates and records the fallback locale in the send metadata. Given a magic link is generated, When inserted into any channel template, Then the URL uses the tenant-branded subdomain and includes the required tracking parameters without breaking the link format.
Tracking Tokens and Deep Link One-Click Access
Given any channel send is successful, When the recipient taps/clicks the magic link, Then identity validates the token, establishes a session, and deep-links the user directly to the mobile capture flow without requiring additional login steps. Given a magic link is sent across multiple channels in one orchestration, When any link is clicked, Then tracking logs include orchestrationId, channel, attemptNumber, and recipientId that match the values recorded at send time. Given a magic link token is consumed on first successful click, When subsequent clicks occur on the same token, Then the user is presented with an "already signed in or expired" safe page and no new session is created.
Delivery & Read Receipt Telemetry
"As a support agent, I want clear delivery and read status for each magic link so that I can see what happened and let the system switch channels if the message wasn’t reached."
Description

Aggregates delivery, bounce, and engagement signals across providers to drive automated failover. Consumes webhooks for SMS delivery receipts, email bounces/complaints, and WhatsApp read receipts; correlates them to a message instance and tenant identity. Normalizes statuses into a state machine (sent, delivered, opened/read, bounced, timed-out) and triggers retry/fallback workflows when messages bounce or remain unread beyond configurable SLAs. Stores telemetry for audit, dashboards, and optimization, with privacy-safe tracking for email opens and link clicks.

Acceptance Criteria
Ingest and Correlate Provider Webhooks
Given a valid signed webhook is received from an approved SMS, Email, or WhatsApp provider And the payload includes an external message ID, event type, timestamp, and recipient address/number When the webhook is processed Then the signature is verified and requests failing verification are rejected with 401 without persisting And the event is deduplicated idempotently by provider event ID and external message ID And the event is correlated to a FixFlow message instance and tenant using stored provider mapping And the processing result is persisted within 2 seconds p95 from receipt And processing failures are retried with exponential backoff up to 6 attempts, then routed to a poison queue with alerting
Normalize Telemetry to Unified State Machine
Rule: Initial state is sent upon message dispatch. Rule: Allowed transitions are sent->delivered|bounced|timed-out; delivered->opened_read|timed-out; opened_read is terminal; bounced is terminal; timed-out is terminal only after fallback is triggered. Rule: Out-of-order or duplicate events never regress state (e.g., delivered after bounced is ignored and logged with reason). Rule: Multiple delivered events coalesce without duplicating state changes. Rule: WhatsApp read receipts set opened_read; an email unique link click sets opened_read even if pixel is blocked. Rule: Every state change records event source, UTC timestamp, and correlation IDs (thread ID, external message ID, tenant ID).
Trigger Failover on Bounce
Given a message instance is in sent state on Channel A And a bounce or complaint webhook is received for that message When the event is processed Then the message state is set to bounced And Channel A is marked ineligible for this tenant for 24 hours And a retry is enqueued to the next eligible channel per routing policy within 5 seconds p95 And the new attempt references the original communication thread ID And no further retries are scheduled on Channel A for this message
Trigger Fallback on Unread Beyond SLA
Given a message instance is delivered on any channel And tenant SLA for unread fallback is configured to N minutes (1–1440) When N minutes elapse without opened_read or link click Then the message state is set to timed-out And a fallback attempt is enqueued on the next eligible channel within 60 seconds And if an opened_read arrives before execution, the scheduled fallback is canceled And all scheduling, cancellations, and outcomes are written to the audit log
Privacy-Safe Email Open and Link Click Tracking
Given email tracking is enabled for a tenant When an email is sent Then the HTML includes a tracking pixel with a random, non-PII token (no email address or phone) and short TTL And click-tracking links log only the token and anonymize IPs (/24 for IPv4, /48 for IPv6) before storage And a link click marks the message opened_read if not already And when tenant disables tracking, no pixel or redirect is injected and no opens/clicks are recorded And automated fetches (e.g., Apple Mail privacy) or Do Not Track signals do not mark opened_read; only link clicks or explicit provider read receipts do
Telemetry Storage, Audit, and Queryability
Rule: All telemetry events and state transitions are persisted immutably with actor/system, UTC timestamp, source, and message/thread IDs. Rule: Data is queryable by tenant, property, unit, recipient, channel, state, and time range within 2 seconds p95 for 30-day windows. Rule: Retention is 13 months online with automatic purge thereafter; tenant deletion requests purge within 7 days across primary and derived stores. Rule: An export endpoint provides CSV or NDJSON for a date range up to 100k events per request via signed URL, with time-to-first-byte ≤ 5 seconds p95. Rule: Access is RBAC-controlled; users without audit permission cannot retrieve telemetry or exports.
Secure Deep Links & Instant Capture
"As a tenant on my phone, I want the magic link to open straight into the capture flow so that I can submit my issue quickly without logging in or navigating menus."
Description

Generates single-use, time-bound magic links that deep link directly into FixFlow’s mobile photo-first intake. Supports Universal Links/App Links where available with graceful fallback to mobile web, device/OS detection, and automatic context handoff (unit, issue, locale). Uses signed JWT tokens with short TTL, replay protection, and brand-aligned domains to mitigate phishing. Preserves attribution parameters for analytics while preventing PII leakage. Ensures one-click access without login to reduce drop-off and first-click failures.

Acceptance Criteria
Single-Use, Time-Bound Magic Link
Given a magic link with a default TTL of 15 minutes is issued When the recipient opens the link within the TTL Then they are auto-authenticated and taken directly to the photo-first intake screen without login Given the same link after a successful redemption When it is opened again on any device Then access is denied with a "Link already used" message and a single-tap action to request a new link is offered Given the same link after TTL expiry When it is opened Then access is denied with a "Link expired" message and a single-tap action to request a new link is offered Given the link is never redeemed When the TTL elapses Then the token is invalidated server-side and cannot be redeemed thereafter Then a redemption event and any subsequent invalid-use attempts are logged with timestamp and device fingerprint
Universal/App Link Routing with Web Fallback
Given iOS with the FixFlow app installed and associated domains configured When the user taps the magic link Then the app opens to the intake screen with context applied Given iOS or Android without the app or with association unavailable When the user taps the magic link Then the mobile web intake opens with the same context Given a failure to open the app within 1 second When fallback is triggered Then the mobile web intake opens automatically without additional user action Then the final landing surface (app or web) is recorded for analytics
Device/OS Detection and Context Handoff
Given a magic link contains context: unit_id, issue_type, and locale When it is opened on any supported device/OS/browser Then the intake screen is prepopulated with that context and UI language matches locale Given an unsupported or unknown device/browser When the link is opened Then the mobile web intake opens and context is preserved Given app-to-web fallback occurs When the web intake opens Then all context values match the original token payload Then no additional login or account selection is required to proceed with photo capture
JWT Signing, TTL, and Replay Protection
Given magic links are encoded as signed JWTs (RS256) with claims: sub, jti, iat, exp <= 15 minutes, aud, iss When the token signature or claims validation fails Then the request is rejected with 401 and no context is leaked Given a valid token is redeemed When the first intake screen loads Then the jti is atomically marked as used and cannot be redeemed again Given a second redemption attempt of the same token from any device or IP When attempted Then it is rejected with a user-safe message and a path to request a new link Given acceptable clock skew of ±60 seconds When tokens are validated Then tokens outside this window are rejected Then all security events (invalid signature, expired, replay) are logged with reason codes
Brand-Aligned Domains and Anti-Phishing Controls
Given magic links are generated When they are delivered Then the link host is a brand-aligned domain (e.g., links.fixflow.com or approved custom domain) with a valid TLS certificate and HSTS enabled Given an unapproved host or plain-http link is requested When detected Then generation is blocked and existing requests are redirected to https on an approved domain or rejected Given the landing page renders When displayed Then the visible brand (logo, name, domain) matches the sending brand profile to reduce phishing risk Then all link hosts are validated against an allowlist configurable per customer
Attribution Preserved, PII Not Leaked
Given a magic link includes non-PII attribution parameters (e.g., utm_source, channel, campaign) When the link is opened and the intake is completed Then these parameters are captured in analytics and associated to the session/submission Given any PII parameters (e.g., email, phone, name) appear in the inbound querystring When processing the request Then they are stripped before redirects, not stored in logs, and not forwarded to analytics Given the JWT payload When inspected Then it contains no raw PII; only opaque identifiers (e.g., tenant_id hash) are present Then analytics exports and dashboards include attribution fields and explicitly exclude PII fields
One-Click Access and Instant Capture Load Performance
Given a user opens a valid magic link on mobile When the intake loads Then no login is required and the camera permission prompt or upload chooser is the first interactive step Given median device performance on 4G When loading the intake Then time-to-interactive is <= 2.0s at p50 and <= 4.0s at p95; image capture UI becomes usable within these thresholds Given camera permission is denied When proceeding Then the flow falls back to gallery upload without losing context Then first-click failure rate for valid tokens is < 1% over a rolling 7-day window, measured by the ratio of valid opens to successful intake loads
Voice Readout Code Fallback (IVR)
"As a tenant who can’t receive texts or email, I want a voice call that reads me a one-time code so that I can still access the maintenance flow."
Description

Provides an automated voice fallback when messaging fails or remains unread. Offers an on-demand “Call me with code” option and/or automatic outbound call after SLA breach, reading a short-lived verification code via TTS with locale support. Integrates with FixFlow’s verification screen to accept the code and continue the session. Includes retry limits, PIN attempt throttling, and detailed call logs. Uses provider adapters (e.g., Twilio Voice) and adheres to quiet hours and consent settings.

Acceptance Criteria
On-Demand Call Me With Code
Given a user is on the FixFlow verification screen with a verified phone number on file When the user selects "Call me with code" and confirms Then the system initiates an outbound voice call within 5 seconds to the verified number And the IVR reads the current session’s 6-digit verification code via TTS And the call outcome and metadata are logged And no more than 3 on-demand voice calls are allowed per user per rolling 24-hour window
Automatic Outbound Call After SLA Breach
Given messaging attempts (SMS, email, WhatsApp) are bounced or remain unread And the elapsed time since first delivery attempt is greater than or equal to the configured SLA threshold And voice consent exists and current time is outside configured quiet hours When the SLA threshold condition is met Then the system automatically places an outbound IVR call within 60 seconds And if the call is not answered within 30 seconds, mark outcome "No Answer" and schedule up to 2 retries spaced 10 minutes apart, respecting quiet hours And if answering machine/voicemail is detected, do not read the code; mark outcome "Voicemail" and do not auto-retry for at least 30 minutes
Quiet Hours and Consent Enforcement
Given quiet hours are configured per property timezone When current local time is within quiet hours Then automatic IVR calls are not placed And the on-demand "Call me with code" flow requires explicit user confirmation before dialing And if voice consent is not present, automatic calls are suppressed and on-demand calls are blocked until consent is captured And all calls outside quiet hours must record whether a quiet-hours override confirmation was given
Locale-Aware TTS and Machine Detection
Given a user’s preferred locale is known and supported When the IVR call is answered by a human Then TTS uses the matching locale voice, reads each digit with ~250ms pauses, and repeats the code once automatically And the caller can press 1 to repeat the code or 2 to hear it more slowly And if the user’s locale is unsupported, fall back to en-US And if voicemail or answering machine is detected, the system does not read the code and ends the call with outcome "Voicemail"
Verification Screen Accepts Voice Code and Continues Session
Given a valid, unexpired voice code is tied to the current verification session When the user enters the code on the verification screen and submits Then the code is validated and the session advances to the intended destination within 1 second And after 1 successful verification, the code is invalidated and cannot be reused And invalid codes trigger a clear error and show remaining attempts And submissions are rate-limited to 1 attempt every 3 seconds and locked after 5 failed attempts within 10 minutes for a 10-minute cooldown
OTP Generation, TTL, and One-Time Use
Given a verification session is active When a voice code is generated Then it is a 6-digit numeric OTP unique to the session and user And it expires 10 minutes after issuance or immediately upon first successful use, whichever occurs first And expired or previously used codes are rejected with specific reasons ("Code expired" or "Code already used") And the plaintext OTP is never stored in logs; only a hashed reference (code_id) is retained
Call Logging and Provider Adapter Failover
Given provider adapters are configured (primary: Twilio Voice, secondary: ProviderB) When an outbound IVR call is initiated Then the system logs: call_id, provider, start_time, end_time, duration, outcome (answered/no_answer/busy/voicemail), language, code_reference, retry_count, consent_source, quiet_hours_override flag, and error_code (if any) And logs do not store full OTP values or full phone numbers (only last 4 digits) And if the primary provider returns a transport/API error or fails to create the call within 10 seconds, the system retries once via the secondary provider and records both attempts And call logs are retained for 90 days and are queryable by ticket/session id
Channel Consent & Preference Management
"As a tenant, I want to choose my preferred contact channel and quiet hours so that I receive messages in a way that works for me and respects my consent."
Description

Captures and enforces per-tenant consent, opt-in/out status, preferred channel order, and quiet hours across SMS, email, and WhatsApp. Supports double opt-in for WhatsApp, STOP/UNSUBSCRIBE handling, suppression lists, and regional compliance (TCPA, CAN-SPAM, GDPR). Exposes a tenant-facing setting in the portal and an admin override with audit trails. Validates and verifies endpoints (email/SMS) and prevents sends to disallowed channels, feeding the orchestrator’s decision logic.

Acceptance Criteria
Tenant Self‑Serve Consent, Preference Order, and Quiet Hours in Portal
Given an authenticated tenant with a verified account When they update channel opt-in/out, set preferred channel order, and define quiet hours and timezone Then the new values are saved, retrievable via API within 5 seconds, and persist after page refresh Given a tenant updates preferences When the save completes Then an audit record stores tenant_id, fields changed, old->new values, timestamp, IP, and source='portal' Given invalid input (duplicate channel in order, overlapping quiet hours, timezone missing) When the tenant attempts to save Then the save is rejected with specific validation errors and no changes are persisted Given regional compliance rules for the tenant’s country When preferences are saved Then legal disclosures are displayed and consent records are stored with region_code and lawful_basis where required
WhatsApp Double Opt‑In Capture and Enforcement
Given a tenant toggles WhatsApp opt-in in portal When the tenant submits Then a WhatsApp Business template message is sent requesting confirmation and consent status is set to pending Given a pending WhatsApp opt-in When the tenant replies 'YES' or taps the Confirm button within 24 hours Then WhatsApp consent becomes true and an audit record stores template_id, message_id, timestamp, and phone Given a pending WhatsApp opt-in When no confirmation is received within 24 hours or the tenant replies 'NO' Then WhatsApp consent remains false and no WhatsApp messages are allowed Given an active WhatsApp consent When WhatsApp sends a user-block or 'STOP' signal Then WhatsApp consent becomes false immediately and is added to the suppression list
STOP/UNSUBSCRIBE Handling and Suppression List Updates
Given an inbound SMS containing 'STOP', 'UNSUBSCRIBE', or 'CANCEL' (case-insensitive) When received Then SMS consent is set to false within 5 seconds, a suppression record is created, and no further SMS are sent Given an inbound SMS containing 'START' or 'UNSTOP' When received Then SMS consent is set to true and an audit entry records re-subscription details Given a tenant clicks an email unsubscribe link When the link is processed Then email consent is set to false, the address is added to the suppression list, and future emails are blocked Given a WhatsApp inbound 'STOP' or block event When received Then WhatsApp consent is set to false and the number is added to suppression Given any channel is opted out When the portal settings are viewed Then the channel shows Opted Out and re-subscribe instructions are displayed
Email and SMS Endpoint Verification Before Use
Given a tenant adds or edits an email address When they save Then a verification email with a unique link is sent and email_verified=false until the link is confirmed Given a tenant adds or edits a mobile number When they save Then an SMS with a 6-digit verification code is sent and sms_verified=false until the code is confirmed Given a verification token or code older than 24 hours or with more than 5 failed attempts When used Then verification fails with a specific error and a new verification must be initiated Given a hard bounce, invalid destination, or carrier unreachable error during verification When detected Then the endpoint is marked invalid, verified=false, and the channel is excluded from orchestration Given an endpoint is verified When the orchestrator requests allowed channels Then the channel is eligible subject to consent, suppression, quiet hours, and regional rules
Quiet Hours Enforcement Across Channels and Timezones
Given tenant quiet hours are set (start_time, end_time, timezone) When a message is scheduled within quiet hours Then no channel (SMS, email, WhatsApp) is sent and the message is deferred to the earliest allowed time Given a deferred message due to quiet hours When quiet hours or timezone are updated before send Then the scheduled time is recalculated and updated accordingly Given any attempted send within quiet hours When logged Then an audit and delivery log entry captures suppression_reason='QUIET_HOURS' with next_attempt_at timestamp Given quiet hours end When the earliest allowed time arrives Then the send is released to the orchestrator subject to current consent and verification state
Admin Override With Audit Trail and Compliance Safeguards
Given an admin with permission 'Consent:Override' When they change tenant consent, preference order, or quiet hours Then they must provide a reason and the change is saved with admin_id, timestamp, IP, and source='admin' Given an admin attempts to set WhatsApp consent to true When no completed double opt-in exists Then the change is rejected with error 'WHATSAPP_DOUBLE_OPTIN_REQUIRED' Given an admin attempts to enable a channel with unverified endpoints When saved Then the change is rejected with error 'ENDPOINT_NOT_VERIFIED' Given an admin override is applied When the tenant next signs in Then a portal notification displays the changes and effective date Given an admin views a tenant record When they request audit history Then a chronological, immutable log of all consent and preference changes is returned
Orchestrator Decision Uses Allowed, Verified, Preferred Channels Only
Given a pending magic link delivery for a tenant When the orchestrator requests channel decision Then the system returns the first channel in the tenant’s preference order where consent=true, endpoint_verified=true, not_suppressed=true, outside_quiet_hours=true, and regional_rules_ok=true Given a candidate channel fails any check When evaluated Then the channel is skipped without sending and a reason code is appended to the decision log Given no channels meet all criteria When evaluated Then the decision returns status='NO_ALLOWED_CHANNEL' and no outbound attempt is made Given a decision is returned When logged Then a decision record includes evaluated_at, chosen_channel (or null), reasons_per_channel, and inputs snapshot (consent, verification, quiet hours, region)
Abuse Protection & Rate Limiting
"As a security-conscious product owner, I want magic links to be single-use, time-bound, and rate-limited so that tenants stay secure without sacrificing convenience."
Description

Secures the magic link and send pipeline with per-user and per-unit rate limits, IP/device throttling, signed tokens (HMAC/JWT), short expirations, single-use enforcement, and replay detection. Implements domain allowlisting for links, bot detection on verification, and provider failover with circuit breakers to avoid storms. Centralizes secrets management and emits security audit logs for SOC2 alignment. Minimizes abuse vectors while keeping the first-click experience fast.

Acceptance Criteria
Per-User and Per-Unit Magic Link Rate Limiting
Given user U requests a magic link for unit A When U has received 3 or more magic links in the last 15 minutes Then the API returns 429 with a Retry-After header and no message is sent Given user U requests a magic link for unit A When U has received 10 or more magic links in the last 24 hours Then the API returns 429 with a Retry-After header and no message is sent Given any user requests a magic link for unit A When unit A has received 8 or more magic links across all users in the last hour Then the API returns 429 with a Retry-After header and no message is sent Given any user requests a magic link for unit A When unit A has received 30 or more magic links across all users in the last 24 hours Then the API returns 429 with a Retry-After header and no message is sent Given a rate limit is enforced When the response is returned Then X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers reflect the user and unit scopes
IP and Device Throttling for Send and Verify Endpoints
Given 20 or more magic-link send requests originate from IP X within 60 seconds When the 21st request is received Then the API returns 429 and throttles IP X for 10 minutes Given deviceId D initiates 5 or more magic-link send requests within 15 minutes When the 6th request is received Then the API returns 429 and throttles deviceId D for 15 minutes Given an IP or device is throttled When subsequent requests are received during the throttle window Then no messages are sent and 429 is returned with a Retry-After header Given IP X is subject to token-bucket limits of 5 requests/second with burst 10 When 15 requests are sent within one second Then at most 10 are accepted and the remainder return 429
Signed Tokens with Short Expiration, Single-Use, and Replay Detection
Given a magic-link token is generated Then it is a JWT signed with HS256 using the active secret version and contains jti, sub (userId), unitId, iat, exp, and scope=auth Given a token is presented for verification When the signature is invalid or the secret version is inactive Then the API returns 401 Unauthorized and no session is created Given a token is presented for verification When the token is expired (exp <= now accounting for up to 60 seconds clock skew) Then the API returns 401 Unauthorized and no session is created Given token T is successfully redeemed When T is presented again on any endpoint or channel Then the API returns 410 Gone, logs a replay_detected event, and no session is created
Domain Allowlisting for All Outbound Magic Links
Given a magic link is generated Then the URL uses https and the host matches either fixflow.app, a subdomain of fixflow.app, or a configured allowlisted customer domain Given an attempt is made to send a magic link with a host not on the allowlist Then message composition is aborted and the API returns 400 with error code domain_not_allowed Given a magic link includes a redirect parameter When the redirect host is not on the allowlist Then the parameter is stripped before send Given an outbound SMS, email, or WhatsApp payload is rendered Then no URL outside the allowlist appears in the message body
Bot Detection and Challenge on Verification
Given a request to verify a magic link without prior throttle When the bot score is >= 0.5 and velocity signals are normal Then the user is verified without interactive challenge and server processing time p95 <= 300ms over a rolling 5-minute window Given a request to verify a magic link When the bot score is < 0.5 or velocity is suspicious Then a human verification challenge is required and verification is withheld until the challenge passes Given a human verification challenge is presented When the user fails or abandons the challenge Then the token remains unredeemed and expires per its TTL; no session is created
Provider Failover with Circuit Breakers and Storm Prevention
Given the primary SMS provider exhibits >= 5% error rate or p95 latency > 2 seconds over the last 60 seconds with at least 50 send attempts When a new SMS send is requested Then the SMS circuit breaker opens within 5 seconds and new sends are routed to the secondary provider Given a circuit breaker is open When the primary provider shows < 1% error rate and p95 latency <= 1 second for 5 continuous minutes Then the circuit transitions to half-open and probes 10% of traffic; on success for 2 minutes it closes Given failover is active across channels Then no user receives more than 2 total send attempts across all channels within 10 minutes, one per channel maximum, and duplicate sends for the same intentId are deduplicated via idempotency keys
Security Audit Logging and Secrets Management
Given any send, verify, throttle, challenge, replay, or failover event occurs Then a security audit log entry is written within 5 seconds including timestamp, userId, unitId, action, channel, intentId, tokenId (hashed), IP hash (salted), deviceId (hashed), provider, result, and circuit state Given audit logs are written Then they are immutable (WORM) for at least 365 days and accessible only to roles SecurityAdmin and ComplianceAuditor with read-only permissions Given signing secrets and provider API keys are required Then they are stored in a managed secrets service with KMS-backed encryption-at-rest, never committed to source control, and only retrievable by the OmniSend service role Given an active signing secret exists When 90 days have elapsed since activation Then automated rotation promotes a new secret version, updates verifiers, and deactivates the old version within 15 minutes; all events are logged
Admin Routing Controls & Analytics
"As a property manager, I want to configure channel routing and see delivery analytics so that I can optimize reach and quickly troubleshoot communication issues."
Description

Delivers an admin UI to configure channel order, retry timing, SLA thresholds, templates, and locales per portfolio. Provides per-message timelines and funnel analytics (sent, delivered, read, clicked, verified) with export and alerts to Slack/email when failure rates spike. Enables A/B testing of fallback sequences and template variants to optimize reach. Integrates with the telemetry store and respects role-based access control.

Acceptance Criteria
Configure Channel Order per Portfolio
Given I am an Org Admin with access to Portfolio A When I open Admin > Routing and reorder channels to [SMS, Email, WhatsApp, VoiceCode] Then the new order is persisted per portfolio and a versioned audit record is created with actor, timestamp, before/after values Given a valid order is saved for Portfolio A When a magic-link notification is initiated for a tenant in Portfolio A Then the system uses the configured channel order for the initial attempt within 60 seconds of the save time Given I attempt to save an invalid order (duplicate channels, empty list, or more than 4 channels) When I click Save Then validation errors are shown inline and no changes are persisted Given Portfolio A has a saved order When I call GET /routing-config?portfolio_id=A Then the API returns the exact saved order with updated_at and version Given a channel order save occurs When the save succeeds Then a telemetry event routing_config.updated is emitted with portfolio_id, channel_order, actor_id, and version
Configure Retry Timing and SLA Thresholds
Given I am an Org Admin on Portfolio B When I set per-channel max_retries (0–10) and retry_intervals in minutes (1–1440) and save Then the configuration is validated, persisted per portfolio, and versioned in the audit log Given Portfolio B has retry timing configured When a send attempt fails for SMS Then the next attempt is scheduled at the configured interval ±5% tolerance until max_retries is reached Given SLA thresholds are set (e.g., delivered <= 5 minutes, read <= 30 minutes) When a message exceeds a threshold Then it is flagged as SLA_BREACHED in telemetry and appears with a red badge in the message timeline Given retries are in-flight When I pause retries for Portfolio B Then no further attempts are executed and telemetry records routing.pause with reason Given retries are paused When I resume retries Then attempts continue from the next scheduled retry respecting remaining retry budget
Per-Message Timeline Audit View
Given I am a Portfolio Admin with access to Portfolio C When I open a specific message record Then I see a chronological timeline of events (created, sent, delivered, read, clicked, verified, channel_switched, failed) with timestamps, channel, provider message IDs, reason codes, and actor (system/user) Given the telemetry store contains events for the message When the timeline renders Then the event count and ordering match telemetry exactly and late-arriving events update the view within 60 seconds Given I enter a search by tenant contact (phone/email), message_id, and date range When I submit the search Then results return within 2 seconds for <10k records and are limited to my portfolio scope Given I click Export Timeline When the export completes Then a CSV is downloaded containing all timeline fields and matches on-screen entries 1:1
Funnel Analytics Dashboard and Export
Given I am an Analyst with read access to Portfolio D When I open Analytics and select a time range Then I see funnel metrics (sent, delivered, read, clicked, verified) by channel and overall, with conversion rates computed to two decimals Given filters for channel, portfolio, and experiment variant When I apply filters Then charts and totals update within 5 seconds and reflect only filtered data Given the dashboard is sourced from the telemetry store When new events arrive Then aggregates update within 5 minutes of event ingestion time Given I click Export CSV When the export is generated Then the CSV contains the same totals as the dashboard within 1% tolerance and includes dimension columns (portfolio_id, channel, variant, date)
Failure-Rate Spike Alerts to Slack/Email
Given an Org Admin configures an alert rule: delivery_failure_rate > 5% over 15 minutes for WhatsApp in Portfolio E with a 30-minute suppression window When the rule condition is met Then a Slack and email alert are sent within 2 minutes containing portfolio, channel, window, current rate, baseline, and top error codes Given an alert was sent for a rule When additional breaches occur within the suppression window Then no duplicate alerts are sent, and a single consolidated alert note is appended to the alert thread Given I mute the alert rule for 2 hours When a breach occurs during the mute period Then no notifications are sent and an audit entry records the mute action with actor and expiry Given an alert is emitted When the event is processed Then telemetry records alert.emitted with rule_id, portfolio_id, channel, window, metric, value, and destination
Template, Locale, and A/B Testing Controls
Given I am an Org Admin on Portfolio F When I create or edit message templates per channel with placeholders and provide translations for locales (en, es, fr) Then the UI validates required placeholders, saves versions, and allows preview rendering per locale with sample data Given Portfolio F has a default locale of en and a recipient has locale es When a magic link is sent Then the es template is used; if missing, the fallback order [recipient locale -> portfolio default -> en] is applied Given I configure an experiment to compare fallback sequences (Order A vs Order B) at a 50/50 split When the experiment is started Then recipients are deterministically bucketed per user_id and traffic splits are within ±2% of target over 1k sends Given template variants A and B exist for SMS When the experiment runs Then analytics report per-variant funnel metrics and allow stopping or declaring a winner, after which the winner becomes the default for new sends
RBAC Enforcement for Admin Controls and Analytics
Given role definitions (Org Admin: read/write all; Portfolio Admin: read/write assigned portfolios; Analyst: read-only assigned portfolios) When a user with Analyst role accesses Admin > Routing Then edit controls are disabled and API write attempts return 403 with error code RBAC_DENIED Given a user has access to Portfolios G and H only When they query analytics or timelines without specifying portfolio Then results are automatically scoped to G and H and no data from other portfolios is returned Given an unauthorized user attempts to export analytics for Portfolio I When the export is requested Then the request is rejected with 403 and an audit log entry is created with actor, resource, and outcome Given SSO group-to-role mappings are configured When the user signs in Then the assigned roles and portfolio scopes reflect the SSO groups and are visible in the user profile

Trust Handshake

Turn the first click into a fast, low‑friction device trust. A one‑tap verify pairs the device for repeat use, then silently renews within safe windows. A built‑in risk engine adds step‑up verification only when behavior looks unusual, keeping everyday access effortless for tenants and vendors while guarding against misuse.

Requirements

One-Tap Device Pairing
"As a tenant or vendor, I want to verify my device with one tap so that I can access maintenance tasks quickly without repeatedly logging in."
Description

Implements a single-tap verification flow (via magic link or deep-linked push/SMS/email) that establishes device trust on first use. On success, the client receives a signed, rotation-capable trust token bound to user account, device fingerprint, and browser context, stored securely (HTTP-only, same-site cookies or secure storage). Supports WebAuthn/passkeys when available to strengthen binding. Integrates with FixFlow auth to skip password re-entry for low-risk actions and to land users directly in their intended task (e.g., approve estimate, upload photos). Includes token rotation, replay protection, short-lived activation links, and abuse rate limiting. Expected outcome: drastically reduced friction for tenants and vendors while maintaining secure, auditable trust establishment.

Acceptance Criteria
First-Time Pairing via SMS Magic Link
Given an untrusted device and a valid single-use SMS magic link generated for a user account, When the user taps the link within 10 minutes of issuance, Then the device is marked trusted and a signed, rotation-capable trust token is issued bound to the user account, device fingerprint, and browser context, and stored in an HTTP-only, SameSite=Lax cookie (web) or secure storage (app). And Then the magic link becomes invalid immediately after first successful use. And Then the user is landed directly on the intent target from the link (e.g., Approve Estimate, Upload Photos) without intermediate authentication prompts. And Then an audit event is recorded with timestamp, user ID, device fingerprint hash, IP, user agent, and link ID. And Then the response enforces TLS, HSTS, and sets no-cache headers for activation pages.
Passwordless Task Entry on Trusted Device (Vendor Estimate Approval)
Given a device with a valid trust token within its validity window and a low-risk context per risk engine, When the vendor opens a deep link to an estimate approval, Then no password prompt is shown and the vendor is routed directly to the approval screen. And Then the approval action can be completed without additional authentication steps. And Then the trust token remains valid (or is rotated if within rotation window) with no user-visible interruption. And Then an audit log captures the trusted-session access and the completed approval action. And Then session timeout and CSRF protections remain active on the page.
Seamless Trust Token Rotation on Return Visit
Given a valid trust token approaching rotation age (e.g., 24 hours since issuance) and the device fingerprint/browser context unchanged, When the user returns to FixFlow, Then the server rotates the trust token atomically, invalidating the prior token and issuing a new signed token bound to the same identifiers without user interaction. And Then no more than one rotation occurs per session and the rotation event is logged. And Then subsequent requests accept only the new token; the old token is rejected.
Expired or Replayed Activation Link Handling
Given an activation link that is expired (older than 10 minutes) or already used, When the user taps the link, Then the request is rejected, no trust token is issued, and a non-technical error is shown with a single-tap option to resend a new link. And Then the response status is 4xx, the old link is not reactivated, and no account existence information is leaked (same wording for existing/non-existing addresses). And Then the failed attempt is logged with reason=expired|replay and device/IP metadata.
WebAuthn/Passkey Binding During Pairing (When Available)
Given the browser/device supports WebAuthn and the user consents during pairing, When the pairing flow begins, Then a new WebAuthn credential is created and associated with the user account and device, and successful assertion is required to complete pairing. And Then the trust token is issued only after a valid WebAuthn assertion and is cryptographically bound to the WebAuthn credential ID. And Then on subsequent visits, if the trust token is missing or invalid but the credential is present, a WebAuthn assertion silently re-establishes trust; otherwise the flow falls back to magic link. And Then the flow functions on latest Chrome, Safari, Edge, and Firefox on iOS, Android, and desktop for supported WebAuthn modalities.
Risk-Based Step-Up for Unusual Behavior
Given a trusted device attempting a high-impact action (e.g., vendor payout change) with unusual signals (new geo-IP, mismatched device fingerprint, TOR/VPN, or anomalous time), When the risk score exceeds the configured threshold, Then the user is required to pass a step-up verification (passkey or OTP) before proceeding. And Then on successful step-up, the action completes and the trust token is retained (or rotated) and the risk context is lowered for the session; on failure, the action is blocked and the token is not upgraded. And Then all step-up prompts, successes, and failures are audited with risk factors and scores.
Abuse Rate Limiting for Pairing Attempts
Given repeated pairing requests, When more than 5 activation links are requested for the same account within 10 minutes or more than 20 from the same IP within 1 hour, Then subsequent requests are throttled (HTTP 429) with a Retry-After header and no links are sent. And Then rate limiting counters decay over time and reset after the window; administrators can adjust thresholds via configuration. And Then responses are uniform to avoid account enumeration and all throttled events are logged with counters.
Adaptive Risk Engine & Step-Up Verification
"As a property manager, I want suspicious access attempts to require extra verification so that unauthorized users are blocked without burdening normal activity."
Description

Introduces a low-latency risk engine that evaluates each request/session using signals such as device fingerprint drift, geo-velocity, IP reputation, time-of-day anomalies, failed attempts, and role sensitivity (tenant vs. vendor vs. manager). Produces a trust score and decision (allow, allow with step-up, deny). On step-up, supports methods like passkey/biometric, OTP via email/SMS, or backup code, selected by policy and device capability. Policies are configurable by environment and role, with thresholds and cooldowns. Integrates inline with gateway/middleware to guard access to sensitive actions (e.g., one-click approvals, payment details). Ensures sub-150ms decisioning for 95th percentile to keep UX snappy. Expected outcome: everyday access remains seamless while anomalous behavior triggers proportional verification.

Acceptance Criteria
p95 Decision Latency Under 150ms
Given a production-like traffic mix across tenant, vendor, and manager roles and two regions with service warm, When 10,000 authorization requests are evaluated over 15 minutes, Then the 95th percentile end-to-end decision latency at the gateway is ≤150 ms and the 99th percentile is ≤250 ms Given the risk engine experiences a scoring timeout >120 ms for any single request, When the timeout occurs, Then the gateway returns a decision of allow with step-up within 250 ms and records the timeout metric and trace ID Given normal operation, When processing requests, Then decision error rate (5xx from risk service) is <0.1% over the test window and no request exceeds a 1 s timeout
Policy Evaluation by Role and Environment
Given environment=staging with Test Policy A (tenant step-up ≥75, deny ≥90; vendor step-up ≥70, deny ≥90; manager step-up ≥60, deny ≥85), When identical signals are evaluated for each role, Then decisions reflect the role thresholds in the active environment policy Given environment=production with Prod Policy B differing thresholds, When the same request is evaluated in production, Then the decision and trust score follow Prod Policy B and not Test Policy A Given any request is evaluated, When the decision is returned, Then the response includes trust_score (0–100), decision in {allow, allow with step-up, deny}, policy_id, and ttl seconds
Silent Trust Renewal Within Safe Windows
Given a device previously paired via strong auth and a policy trust window of 30 days per role and environment, When the same user returns within the window and signals remain below the step-up threshold, Then the decision is allow without prompting step-up and the trust window is renewed to 30 days from this event Given the same device but the trust window has expired, When the user returns with otherwise low-risk signals, Then the decision is allow with step-up and upon successful completion the device is re-trusted and a new 30-day window begins Given a device is trusted for tenant role, When the same user accesses a manager-role action, Then trusted state is not reused across roles and policy thresholds for manager are applied
Step-Up Method Selection and Fallback
Given a device supports WebAuthn and policy enables passkey/biometric as preferred, When a decision of allow with step-up is returned, Then the user is prompted with WebAuthn first and a successful assertion upgrades the decision to allow Given WebAuthn is unavailable or fails 2 consecutive attempts, When policy allows OTP via SMS/email, Then the flow falls back to OTP using the policy-preferred channel with a single-use 6-digit code that expires in 5 minutes and a maximum of 5 verification attempts Given both WebAuthn and OTP are unavailable or the user is flagged without reachable channels, When policy enables backup codes, Then a valid unused backup code completes step-up and marks that code as consumed Given a step-up is satisfied by any one permitted method, When the method verifies successfully, Then the protected action is authorized and a step-up grant token is issued with a 10-minute TTL scoped to the action
Signal Ingestion and Scoring Integrity
Given a baseline device fingerprint exists, When 3 or more high-entropy attributes change (e.g., canvas/hash, platform, IP ASN) constituting >30% fingerprint drift under Test Policy A weights, Then the risk_score increases sufficiently to require at least allow with step-up for tenant and manager roles Given prior trusted login from City A at 10:00 and current request from City B at 10:30 1,500 km away, When geo-velocity ≥500 km/h is computed, Then the decision is allow with step-up for tenant/vendor and deny for manager under Test Policy A Given the current IP is classified High Risk by the reputation provider, When the request is evaluated, Then the decision is deny regardless of other signals; if the IP is Suspicious, Then the decision is allow with step-up Given the request occurs outside the user’s typical time-of-day window (configured ±2 hours) and there were ≥3 failed attempts in the last hour, When evaluated, Then the combined anomaly raises the decision at least to allow with step-up for all roles under Test Policy A
Sensitive Actions Protected Inline
Given the one-click approval action is initiated, When the gateway requests a decision, Then allow proceeds immediately, allow with step-up presents the step-up flow inline before execution, and deny blocks the action with HTTP 403 and no side effects recorded Given a request to view or edit payment details via API or UI, When the middleware enforces policy, Then the same decisioning applies and direct API calls cannot bypass the check Given a successful step-up for a specific action, When subsequent identical action attempts occur within the 10-minute grant TTL, Then they are allowed without re-prompt; after TTL expiry, Then a fresh decision is required
Attempts, Cooldowns, and Rate Limiting
Given a user performs step-up verification, When 5 consecutive verification failures occur within 15 minutes, Then the decision returns deny and a 10-minute cooldown is applied during which further attempts are blocked Given OTP delivery is requested, When more than 3 OTP sends are requested within 10 minutes for the same user or IP, Then additional sends are rate-limited with HTTP 429 and no OTP is sent Given a cooldown has elapsed, When the next request is evaluated with no new anomalies, Then the risk score decays per policy and the decision no longer reflects the prior lockout state
Silent Trust Renewal Windowing
"As a tenant, I want my trusted device to stay recognized throughout my lease so that routine updates are seamless and fast."
Description

Provides automatic, invisible renewal of device trust within configurable rolling windows (e.g., 30 days active, 90 days max) to minimize re-prompts during a lease or vendor engagement. Renews tokens on active use, respects inactivity timeouts, and enforces absolute maximum lifetime. Handles clock skew, multi-tab, and network retries safely, and consults a server-side revocation list before renewal. Admin-configurable per role and environment. Expected outcome: sustained, low-friction access over time without compromising revocation or maximum-lifetime controls.

Acceptance Criteria
Rolling Window Renewal on Active Use
Given an admin-configured active window of 30 days and a trusted device with last_renewed_at=T0, When the tenant performs an authenticated action at T0+29d, Then the server silently renews device trust and returns a new token bound to the device with issued_at=server_now and active_expires_at=server_now+30d. Given no activity occurs for more than 30 days after T0 and the absolute maximum has not yet been reached, When the tenant returns, Then the server does not silently renew and the user is prompted to re-verify before continuing. Given multiple authenticated actions occur within the active window, When a renewal has already occurred within the current session, Then subsequent actions do not trigger additional renewals and no prompts are shown. Given a renewal is performed, When measuring server processing, Then renewal completes within 300 ms at p95 without degrading request success rate.
Absolute Maximum Lifetime Enforcement
Given an admin-configured absolute maximum lifetime of 90 days from first_trusted_at=Tfirst, When current server time is greater than or equal to Tfirst+90d, Then the server denies silent renewal and requires re-verification; no token is issued or extended beyond the maximum. Given the user is actively using the application at Tfirst+89d23h59m, When the server renews, Then the token not_after is set to Tfirst+90d and is not extended past the absolute maximum. Given the client device clock is incorrect, When renewal is evaluated, Then server time is authoritative and the absolute maximum enforcement prevents any extension beyond Tfirst+90d.
Revocation Check Before Renewal
Given the device token identifier is present on the server-side revocation list, When a renewal request is received, Then the server blocks the renewal, invalidates the current token, and the user is prompted to re-verify. Given the device token identifier is not present on the server-side revocation list, When a renewal request is received, Then the renewal proceeds as configured and returns a refreshed token. Given the revocation service or list is temporarily unavailable, When a renewal request is received, Then the server does not perform a renewal and returns a non-extended result; the client continues with the current token until its existing expiry (no extension).
Multi-Tab and Concurrent Renewal Safety
Given two or more tabs on the same device trigger renewal within a 1-second interval, When the server processes the requests, Then at most one new token is issued and concurrent requests receive the same renewal result (idempotent outcome). Given a renewal completes in one tab, When another tab attempts renewal within 30 seconds, Then the client reuses the fresh token and suppresses a duplicate renewal request. Given a network timeout causes the client to retry a renewal request, When the original renewal already succeeded, Then the retried request does not issue another renewal and returns the original result.
Clock Skew Tolerance and Server-Time Authority
Given the client device clock is skewed by up to ±10 minutes, When evaluating renewal eligibility, Then server time is authoritative and token times (issued_at, not_before, active_expires_at, not_after) are set using server time only. Given the client clock is set backwards beyond the active window by client perception, When the active window has expired by server time, Then renewal is denied and re-verification is required. Given the client clock is set forward, When within the active window by server time, Then renewal proceeds silently and the window is not shortened due to client time.
Admin Configuration per Role and Environment
Given role-based and environment-based policies are configured (e.g., Tenant active=30d/max=90d; Vendor active=14d/max=60d) in Production, When corresponding users perform eligible actions, Then the server applies the policy matching the user role and environment and enforces it on renewal decisions. Given an admin updates the active or maximum window for a role in an environment, When the next renewal occurs, Then the new values are applied and an audit record is written with actor, old values, new values, role, environment, and timestamp. Given configuration for a role/environment is temporarily unavailable, When a renewal request is processed, Then the last-known-good policy is applied and a configuration warning is logged; renewals continue to respect absolute maximum and revocation checks.
Retry, Idempotency, and Network Failure Handling
Given transient network errors (timeouts, HTTP 5xx) occur during renewal, When the client retries, Then it performs up to 2 retries with exponential backoff starting at 250 ms and includes an idempotency key per attempt. Given the server receives duplicate renewal requests with the same idempotency key, When the first request succeeds, Then subsequent requests return the same result without issuing an additional token or extending times further. Given the client is offline, When the active window elapses, Then no renewal is attempted and no extension occurs; upon next online access, renewal is attempted or a re-verification is prompted based on policy. Given all retry attempts fail and the current token is still valid, When the user continues usage, Then access continues without extension and no prompt is shown until the policy requires it (e.g., active window expiry or absolute maximum).
Trusted Device Management UI
"As a landlord or property manager, I want visibility into and control over trusted devices so that I can quickly revoke access if a phone is lost or staff changes."
Description

Delivers user-facing and admin controls to view and manage trusted devices. Users can see device nickname, last seen, rough location (city-level), and trust level; revoke any device with immediate effect. Admins/property managers can search by user/unit/vendor, force revoke, apply policy overrides, and receive alerts on high-risk trust events (e.g., rapid device proliferation). Includes notifications for new device trust established and revocation confirmations. Fully responsive, accessible, and localized. Expected outcome: transparency and rapid self-service control, reducing support tickets and risk exposure.

Acceptance Criteria
View Trusted Devices List
Given an authenticated end user (tenant or vendor) with at least one trusted device When the user navigates to Security > Trusted Devices Then the list of trusted devices loads within 2 seconds on a 3G network And each device entry displays nickname, last seen (timestamp in user's timezone), city and country, and trust level badge And up to 50 devices are supported in the list with pagination or infinite scroll And when there are no trusted devices, an empty state with guidance is shown And all text and timestamps are localized to the user’s selected language and locale
Revoke Trusted Device (Self-Service)
Given an authenticated user viewing Trusted Devices When the user selects Revoke on a device and confirms Then the device’s trust token is invalidated within 5 seconds And any active sessions on that device are signed out within 10 seconds And the device status updates in the UI immediately to Revoked or it is removed from the trusted list And the user receives an in-app confirmation immediately and an email within 60 seconds And an audit record is created including user ID, device ID, timestamp, IP, city, and action "self-revoke"
Admin Search & Force Revoke
Given an authenticated admin/property manager with appropriate role When the admin searches by user email, unit number, or vendor name Then results are returned within 1 second for datasets up to 10,000 records And selecting a result displays that entity’s trusted devices with nickname, last seen, city/country, and trust level When the admin selects one or more devices and chooses Force Revoke with a reason code Then trust tokens are invalidated within 5 seconds and active sessions sign out within 10 seconds And affected users receive revocation notifications within 60 seconds And an audit entry records admin ID, scope, reason code, and affected device IDs
High-Risk Trust Event Alerts
Given the risk engine flags a high-risk event such as ≥3 new trusted devices on an account within 24 hours or a geovelocity anomaly When the event is detected Then an alert is created within 30 seconds containing account identifier, event type, count, and latest device metadata And subscribed admins receive an email and in-app alert within 60 seconds And duplicate alerts for the same account and event type are suppressed for 2 hours And the alert detail includes one-click Force Revoke for flagged devices
New Device Trust Notification
Given a device completes one-tap verification and becomes trusted for an account When trust is established Then an in-app banner is shown immediately and an email is sent within 60 seconds containing device nickname, city, country, timestamp, and a Revoke link And notifications are localized to the user’s selected language And silent trust renewals do not trigger new device trust notifications
Accessibility, Responsiveness, and Localization Compliance
Given the Trusted Device Management UI is accessed on mobile (≥320px width), tablet, and desktop (≤1440px width) When rendering lists, details, and actions Then no horizontal scrolling occurs, content reflows appropriately, and tap targets are at least 44px And all interactive elements are operable via keyboard with visible focus states in logical order And screen-reader labels and roles are present for controls and list headers; live regions announce status changes (e.g., revoke success) And color contrast meets WCAG 2.2 AA (≥4.5:1 for text, ≥3:1 for UI components) And all strings, dates, times, and locations are localized to the selected language with fallback to English
Admin Policy Overrides
Given an authenticated admin with override permissions When the admin opens a user or account’s trust policy settings Then the admin can set overrides including trust window duration, maximum trusted devices, and step-up requirement on next access And changes require a reason code and are saved with audit metadata (admin ID, timestamp) And overrides take effect on the next access attempt and are enforced within 60 seconds And the UI displays the effective policy (default vs override) with start time and optional expiry And removing an override reverts to defaults within 60 seconds
Audit Logging & Compliance Controls
"As a compliance lead, I want detailed, exportable logs of trust events so that we can investigate incidents and meet regulatory obligations."
Description

Captures immutable, privacy-conscious logs for all trust lifecycle events: initiation, success/failure, renewal, step-up prompts and outcomes, revocations, and policy changes. Logs include timestamp, user/role, device hash, decision, and reason codes without storing raw PII. Supports retention policies, export (CSV/NDJSON), and webhook/SIEM forwarding. Provides user data access/deletion workflows to meet GDPR/CCPA and internal audit requirements. Expected outcome: defensible audit trail for incident response and compliance with minimal overhead.

Acceptance Criteria
Immutable Event Logging for Trust Lifecycle
Given a trust lifecycle event occurs (initiation, success, failure, renewal, step-up prompt, step-up outcome, revocation, policy change) When the event is processed Then an append-only log entry is written within 500 ms containing: event_type, ISO-8601 UTC timestamp with ms, tenant_id, user_id (hashed), user_role, device_hash, decision, reason_code, request_id, actor_ip (hashed), and schema_version And the entry is immutable (attempted update/delete returns 409 and is denied) And each batch of entries is chained with a cryptographic hash (SHA-256) providing tamper evidence And each entry has a unique monotonic id ensuring total order per tenant
Privacy-Conscious Log Content (No Raw PII)
Given logging is enabled When a log entry is stored Then no raw PII is persisted (no plaintext name, email, phone, address, IP); only salted hashes for user_id, device_hash, and IP And reason_code is selected from a controlled enum defined in the schema And data passes the automated PII scanner with 0 high or medium findings per release And device_hash uses a per-tenant salt and rotates at least every 12 months with 90-day backward resolution And schema validation rejects invalid records and routes them to a dead-letter queue with alert
Retention Policies Enforcement and Purge
Given tenant-configured retention policies exist When retention is set (e.g., 30, 90, 365 days) with optional legal hold Then records older than the retention window are purged daily at 02:00 UTC unless on legal hold And purge operations are irreversible and recorded as a purge_event with counts and time range And manual purge requires role "Compliance Admin" and MFA and produces a ticket_id And after purge, queries and exports return 0 records in the purged range And retention configuration changes are logged with before/after values and approver
Export Logs to CSV and NDJSON
Given an authorized user requests an export When export is requested with time range and filters (tenant_id, event_type, decision) Then system streams results in CSV and NDJSON formats, selectable via parameter And CSV contains a header row; NDJSON has one JSON object per line And results are sorted by timestamp ascending and are stable across pages And large exports are chunked with pagination cursors; no chunk exceeds 100 MB or 1,000,000 records And responses can be gzip-compressed; a SHA-256 checksum is provided for each file And export completion emits an audit event with record count and requester
Real-time Webhook and SIEM Forwarding
Given webhook/SIEM forwarding is configured When a new log entry is written Then the entry is delivered to the configured HTTPS endpoint within 2 seconds p95 And requests are authenticated via mTLS or HMAC signature; payload includes tenant_id and event_type And non-2xx responses trigger exponential backoff retries for up to 24 hours with jitter; after max attempts, message goes to a dead-letter queue with alert And delivery is at-least-once; an idempotency-key is provided for deduplication And endpoint TLS is TLS 1.2+; certificate validation failures stop delivery and alert
User Data Access and Deletion (GDPR/CCPA)
Given a data subject access request is submitted When a tenant admin initiates an Access request for a user Then the system produces an export of all logs linked to user_id/device_hash within 30 days, redacting non-necessary fields And the export is available to the requester for 14 days and requires MFA to access And all DSAR actions are logged with requester, approver, timestamps, and outcome When a Deletion request is approved Then personal identifiers in logs are irreversibly deleted or pseudonymized as permitted, preserving non-PII fields and aggregate counts And deletion scope, method, and completion timestamp are logged; queries post-deletion return 0 personal identifiers for the subject
Admin Audit Access Controls and Tamper Evidence
Given an admin user accesses the audit log interface or API When they authenticate and request data Then access is allowed only to roles Compliance Admin and Security Analyst (read-only) And all views, queries, exports, and configuration changes are logged with correlation_ids And the system exposes an integrity endpoint returning the current hash-chain head and verification proof for a supplied range And any attempt to modify or delete an entry via API/UI is denied and logged with 403 and reason immutable And system clocks are synchronized via NTP; entries have max 100 ms clock skew across nodes
Fallback & Recovery Paths
"As a vendor on-site with poor signal, I want a reliable backup verification method so that I can access the work order without delays."
Description

Ensures access continuity when primary channels fail. Offers alternative step-up and recovery options: email OTP, SMS OTP, voice call code, backup codes, and support-assisted verification with expiring, single-use admin-issued links. Includes rate limiting, lockout safeguards, and clear UX for retry/cooldown states. Designed for low-connectivity scenarios common at job sites and older buildings. Avoids insecure knowledge-based questions. Expected outcome: fewer blocked users and truck-roll delays while maintaining strong anti-abuse controls.

Acceptance Criteria
Email OTP Fallback on Device Trust Failure
Given a user fails device trust verification and selects Email OTP When the user requests an email code Then the system submits the email to the provider within 2 seconds and receives provider acceptance within 5 seconds (p95) And the email contains a 6-digit numeric OTP, valid for 10 minutes, single-use And a maximum of 3 email OTP sends per user per hour and 10 per day is enforced And after 5 consecutive invalid email OTP entries, the account enters a 15-minute cooldown for email OTP And all sends, verifications, and lockouts are audit-logged with user ID, channel, IP, device, and timestamps And if the email address is unverified or bounces, the UI blocks email OTP and surfaces alternate channels (SMS, Voice, Backup Codes, Support Link)
SMS OTP Fallback with Delivery Retries
Given a user with a verified mobile number selects SMS OTP When the user requests an SMS code Then the system submits the SMS to the provider within 2 seconds and receives provider acceptance within 5 seconds (p95) And the SMS contains a 6-digit numeric OTP, valid for 10 minutes, single-use And the UI enables a Resend after 30 seconds; a maximum of 3 resends per attempt, 5 SMS sends per hour, and 15 per day is enforced And after 5 consecutive invalid SMS OTP entries, a 15-minute cooldown is applied for SMS OTP And rate limiting errors are clearly messaged without revealing whether a phone number exists on an account And all events are audit-logged with user ID, channel, IP, device, and timestamps
Voice Call Code for Low-Connectivity Environments
Given a user cannot receive SMS or email and selects Voice Call When the user requests a voice code Then the system initiates an outbound call within 15 seconds (p95) to the verified number And an automated voice reads a 6-digit numeric code twice, offers DTMF 1 to repeat, and DTMF 9 to cancel And the code is valid for 10 minutes, single-use And call attempt limits are enforced: 3 calls per attempt, 5 per hour, 10 per day per user And if the call fails (busy/no-answer after 45 seconds), the UI offers alternate channels and records the failure reason And transcript/metadata of the call attempt is logged (no code content), including provider status and duration
Backup Codes Issuance and Redemption
Given an authenticated user opts to generate backup codes When the user generates codes Then exactly 10 one-time backup codes are created, each 10-character upper-case alphanumeric, stored as salted hashes server-side And codes are shown once with copy/download/print options; they are not retrievable later And regenerating backup codes invalidates all previously unused codes immediately And backup code entry successfully completes step-up without requiring network messaging, subject to the same risk checks And entering an invalid code 5 times triggers a 15-minute cooldown for backup code entry And the UI displays remaining unused backup codes count after successful redemption
Support-Assisted Verification via Admin-Issued Link
Given a support agent verifies user identity off-channel per policy and opens the Admin Console When the agent issues a verification link Then the system generates a single-use, cryptographically signed link bound to the user ID and intended device, expiring in 15 minutes And issuing a link requires the agent to pass step-up verification and records a case ID and reason And a maximum of 1 active link per user at a time is enforced; links can be revoked before use And clicking a valid link allows the user to complete step-up and pair the device; after first use, the link becomes invalid And all actions (issue, revoke, use, expire) are audit-logged with actor, user, timestamps, IPs, and reason codes And the flow prevents role elevation or account linking; it only completes verification for the specified user
Lockout, Cooldown, and Clear Retry UX
Given a user exceeds invalid attempt thresholds across any channel When thresholds are met (5 consecutive invalid entries or 10 total attempts in 15 minutes) Then a 15-minute cooldown is applied to step-up attempts for that user, with inputs disabled and a visible countdown timer And after 3 cooldowns within 24 hours, a 24-hour lockout is applied and support contact options are presented And messages are plain language, localized, and accessible (WCAG AA), avoiding disclosure of which factors are valid And the UI offers safe alternatives (backup codes, support link) when available during cooldown/lockout where policy permits And all lockout state changes are audit-logged and reset upon successful verification
Risk-Based Step-Up Selection Without Knowledge-Based Questions
Given device trust appears unusual (e.g., new device + IP geovelocity > 500 km within 24h or TOR exit node) When the risk score meets or exceeds the configured threshold Then the system requires step-up using allowed fallback channels and never presents knowledge-based questions And for high-risk scores, the system requires two distinct channels (e.g., Email OTP + SMS OTP or Backup Code + Support Link) to succeed And for normal risk, a single successful channel suffices And all risk inputs, decisions, and selected channels are recorded for audit and tuning, without storing OTP values And false-positive rate is monitored with a target of ≤2% week-over-week, with configuration flags to adjust thresholds without code changes
Trust Decision API for Internal Features
"As a FixFlow developer, I want a simple API to check and consume trust status so that product features can request step-up only when necessary."
Description

Provides a standardized internal API that returns signed trust assertions (level, score, expiry, reason codes) for use by core workflows such as photo upload, estimate approval, and vendor dispatch. Supports idempotent evaluation, short-term caching, and streaming updates of trust state to the client. Enables features to request step-up on-demand and to tailor UX (e.g., hide OTP prompt if already trusted). Includes SDK helpers for web and mobile web. Expected outcome: consistent, reusable trust decisions across FixFlow with minimal integration effort.

Acceptance Criteria
Retrieve Signed Trust Assertion for Core Workflow
Given an authenticated internal client with a valid device/session context When it calls POST /api/trust/decision with required identifiers Then the response status is 200 and the body includes decisionId, level, score, expiry (ISO-8601 UTC), reasonCodes (array), and assertion (JWS) And the JWS signature validates against the platform JWKS (matching kid and algorithm) And the JWS payload includes sub (device or session id), decisionId, level, score, iat (within 5s of server time), and exp equal to the expiry field And level and score are consistent with configured mappings (e.g., level >= required threshold implies score >= its minimum) And reasonCodes are present when level is below the maximum configured trust level And the response includes a unique decisionId for traceability
Idempotent Decision Evaluation With Idempotency-Key
Given a request to POST /api/trust/decision with an Idempotency-Key header and a normalized equivalent request body When the same Idempotency-Key and body are replayed within the configured idempotency window Then the API returns 200 with the identical decisionId and assertion as the original response And the response includes Idempotent-Replay: true And no duplicate side effects (e.g., step-up triggers) are created When the same Idempotency-Key is replayed after the idempotency window Then a new decision is evaluated and a new decisionId is returned When the body differs materially under the same Idempotency-Key within the window Then the API returns 409 Conflict with an error indicating body mismatch
Short-Term Caching of Trust Decisions
Given a prior successful decision for a device/session When the client calls POST /api/trust/decision with unchanged risk context within the configured cache TTL and before expiry Then the API returns 200 with cacheHit: true and the same decisionId and assertion as the cached entry And the returned expiry is not past current server time When the cache TTL has elapsed or the risk context has materially changed (e.g., IP, fingerprint, behavior flags) Then the API returns cacheHit: false and a newly evaluated decision with a new decisionId And the cache is not used when the previous decision is expired
Streaming Trust State Updates to Client (SSE)
Given an authenticated client subscribes to GET /api/trust/stream via Server-Sent Events with a valid device/session context When the connection is established Then an initial event trust.snapshot is delivered containing decisionId, level, score, expiry, and reasonCodes And heartbeat events are sent at the configured interval to keep the stream alive When trust state changes (e.g., step-up completed, risk downgrade) Then a trust.updated event is delivered containing the new decisionId, level, score, expiry, and reasonCodes within 3 seconds of the backend change When the client reconnects providing Last-Event-ID Then only missed events are replayed once and no duplicates are delivered And events are only delivered to authorized clients for the associated device/session
On-Demand Step-Up Request and Outcome Propagation
Given a feature requests a higher trust level via POST /api/trust/step-up with minimumLevel and reason When the current trust already meets or exceeds minimumLevel Then the API returns 204 No Content and no user challenge is initiated When the current trust is below minimumLevel Then the API returns 200 with challengeId, allowedMethods, and expiry, and no trust upgrade is applied until verified When the user completes the challenge successfully before expiry Then the next /api/trust/decision returns an upgraded level/score with reasonCodes including step_up_success and method used And a trust.updated event reflecting the upgrade is emitted on the stream within 3 seconds When the challenge expires or fails Then the decision is not upgraded and reasonCodes include step_up_failed or step_up_expired
SDK Helpers Enable Minimal Integration and UX Tailoring
Given the FixFlow JS/TS SDK is installed in a reference web/mobile-web app When the app calls getTrustDecision() via the SDK Then it receives typed data including decisionId, level, score, expiry, reasonCodes, and a verified assertion or verification helper returns true When the app subscribes with subscribeTrustUpdates() Then it receives trust.snapshot and trust.updated events and auto-reconnects with Last-Event-ID without duplicates When the app calls requestStepUp({ minimumLevel }) Then it either receives a 204-equivalent no-op when already trusted or a challenge descriptor and completion promise that resolves the upgrade When the app evaluates shouldRenderOTP(minLevel) Then it returns false if the current decision meets/exceeds minLevel, enabling the UI to hide OTP prompts And a reference implementation integrates decision gating and streaming in ≤ 10 lines of feature code excluding imports

Scan‑to‑Start

Generate dynamic QR codes tied to a unit or work order. Tenants scan from lobby signage to report with a pre‑scoped link; vendors scan on arrival to check in and update status. Codes can be single‑use, time‑limited, and geofenced to the property—removing typing, reducing errors, and speeding first actions.

Requirements

Dynamic QR Code Generation Service
"As a property manager, I want to generate QR codes tied to a unit or work order so that tenants and vendors start the correct flow without manual data entry."
Description

Build a service that generates dynamic QR codes associated with a property, unit, or work order. Each code resolves to a short URL containing a signed token with scope (tenant intake or vendor job), identifiers, configurable constraints (expiry, single-use, geofence), and minimal metadata. Provide REST endpoints and internal SDK methods to create, refresh, and invalidate codes; support PNG/SVG output with selectable size and error correction for print and digital use. Ensure idempotent creation, collision-resistant code space, and analytics hooks. Integrates with FixFlow’s property/unit/work order data model and notification pipelines.

Acceptance Criteria
Create Tenant Intake QR via REST and SDK
Given a valid propertyId and unitId exist in FixFlow and scope=tenant_intake with constraints {expiry, singleUse, geofenceId} When a client calls POST /qr-codes with the payload or invokes SDK.create({propertyId, unitId, scope, constraints}) Then the service responds 201 with {codeId, shortUrl, token, images[]} where token is a signed JWT/JWS carrying only {scope, propertyId, unitId, constraints, iat, exp} and no PII And shortUrl is resolvable under the configured domain and <= 12 path characters excluding domain And images include PNG and SVG variants honoring requested size (e.g., 256–2048 px) and error correction level (L/M/Q/H), defaulting to 512px and M And invalid size or ecc parameters yield 400 with a descriptive error And referencing a non-existent property/unit yields 404 And the internal SDK exposes create(), returning the same fields as the REST response
Idempotent Creation and Collision Resistance
Given a unique Idempotency-Key header (or requestId in SDK) and identical request body When the client retries POST /qr-codes (or SDK.create) multiple times within 24 hours Then the same codeId, shortUrl, and token are returned, only one code is persisted, and only one analytics.create event is emitted Given high-volume creation of 100,000 codes in a test namespace When codes are generated sequentially Then 0 URL path collisions occur, and the service guarantees uniqueness by regenerating on detected collision before persisting
Time-Limited and Single-Use Enforcement
Given a QR code created with expiry=T+30m and singleUse=true When the shortUrl is visited before T+30m Then the token validates and the user is routed to the tenant intake flow pre-scoped to {propertyId, unitId} When the same shortUrl is visited after the first successful redemption Then access is blocked with HTTP 410 Gone and a user-facing "Code already used" message When the shortUrl is visited after T+30m without prior redemption Then access is blocked with HTTP 410 Gone and a user-facing "Code expired" message Given a valid code that is refreshed via POST /qr-codes/{codeId}/refresh with expiry=T+60m When the new shortUrl is scanned Then the previous shortUrl is invalid and the new expiry applies
Geofence Enforcement at Scan
Given a QR code created with geofenceId referencing the property boundary When the shortUrl is visited and the client shares a location within the geofence polygon (or within a configured tolerance of 25m) Then access is allowed and the flow proceeds When the client location is outside the geofence Then access is blocked with HTTP 403 and a user-facing "Out of property range" message and analytics.scan_blocked{reason:"geofence"} is emitted When the client denies location permissions Then the page requests retry and offers manual address entry; if location is still unavailable, access is blocked with HTTP 403 and reason:"no_location"
Vendor Check-In via QR Updates Work Order
Given a QR code created with scope=vendor_job and workOrderId linked to a scheduled job When a vendor scans the code on arrival Then the system records a check-in timestamp on the work order, updates status to "On Site", and attributes the scan to the vendor identity (if authenticated) or marks as unauthenticated And the notification pipeline sends check-in notifications per configuration (e.g., to landlord and tenant) within 60 seconds And the vendor is presented with job status update actions (e.g., Start, Pause, Complete) pre-scoped to the work order
Invalidate and Refresh Behavior
Given an active QR code When POST /qr-codes/{codeId}/invalidate is called (or SDK.invalidate(codeId)) Then any subsequent visit to the shortUrl returns HTTP 410 Gone with a user-facing "Code invalidated" message and analytics.invalidate event is emitted Given an active QR code When POST /qr-codes/{codeId}/refresh is called with new constraints Then a new shortUrl and token are issued, previous shortUrl is deactivated, and new images are returned; prior URLs cannot be redeemed And attempting to refresh an already invalidated code returns 409 Conflict
Analytics and Observability Hooks
Given analytics hooks are enabled When a code is created, refreshed, invalidated, successfully scanned, or a scan is blocked Then events {qr.created, qr.refreshed, qr.invalidated, qr.scanned, qr.scan_blocked} are emitted with fields {codeId, scope, propertyId, unitId?, workOrderId?, reason?, geo?, userAgent, timestamp} and delivered to the analytics pipeline within 5 seconds (p95) And events are at-least-once with idempotency keys to prevent double-counting And operational metrics are exposed (e.g., creation rate, scan success/block rate, average latency) via /metrics for scraping
Single-use and Time-limited Token Enforcement
"As an admin, I want QR links to expire or be single-use so that shared links cannot be abused and workflows remain accurate."
Description

Implement server-side validation to enforce single-use and time-limited constraints on scan tokens. Support configurable expiry windows and usage limits (one-time, N uses, per role) set at creation. On first valid redemption, record association and invalidate according to policy; subsequent attempts receive a friendly error and guidance to request a new code. Include atomic checks to prevent race conditions, admin override to reissue/revoke, and thorough audit logging. Handle clock skew, shared links, and device changes gracefully.

Acceptance Criteria
Single-Use Token: First Redemption and Subsequent Attempt
Given a single-use token T created for unit U with role=Tenant and expires_at in the future When a tenant scans and requests /redeem?token=T within the validity window Then the server validates T, associates redemption to the target (intake/work order), marks T consumed atomically, and returns 200 with the pre-scoped link And an audit log entry is written with token_id, policy=single-use, subject_role=Tenant, unit_id, redeemed_at (server time), ip, user_agent, and outcome=success When any subsequent request uses token T after consumption Then the server returns 410 Gone with a friendly message and a CTA to request a new code, and an audit log entry is written with outcome=reject and reason=already_consumed
Time-Limited Token Expiry With Clock Skew Tolerance
Given a token T with expires_at=2025-09-06T12:00:00Z and skew_tolerance=120s configured at creation When the server receives a redemption at server_time <= expires_at + 120s Then redemption succeeds and an audit log entry records redeemed_at and expires_at When the server receives a redemption at server_time > expires_at + 120s Then the server returns 410 Gone with friendly expiry messaging and CTA to request a new code, and an audit log entry records outcome=reject and reason=expired And configuration of expires_at and skew_tolerance is retrievable via admin audit view for verification
N-Use Token Consumption Limit
Given a token T created with max_uses=3 and role=Any When three valid redemption requests occur within the validity window Then each of the first three requests returns 200, increments use_count atomically, and associates as configured And each success is logged with use_count after increment and outcome=success When a fourth redemption request occurs Then the server returns 410 Gone with usage-limit messaging and CTA to request a new code, and logs outcome=reject and reason=usage_limit_reached And the final recorded use_count for token T is 3 with no over-consumption observed in logs
Per-Role Enforcement (Vendor-Only Token)
Given a token T configured with role=Vendor and expires_at in the future When a Tenant attempts to redeem token T Then the server returns 403 Forbidden with guidance to obtain a tenant-appropriate code, and logs outcome=reject and reason=role_mismatch When a Vendor redeems token T within the validity window Then the server returns 200 and proceeds to the vendor check-in/update flow, logging outcome=success and subject_role=Vendor
Atomicity Under Concurrent Scans
Given a single-use token T and two devices D1 and D2 When D1 and D2 submit redemption requests for token T concurrently (within 100 ms) Then exactly one request succeeds (200) and marks T consumed, and the other fails with 409 Conflict or 410 Gone (reason=already_consumed) without creating a duplicate association And audit logs show one success and one reject with a shared correlation_id or token_id, and use_count remains 1
Admin Override: Revoke and Reissue
Given an active token T1 When an admin revokes T1 via the dashboard/API Then T1 becomes invalid immediately, subsequent redemptions return 410 Gone (reason=revoked), and an audit log entry captures admin_id, action=revoke, token_id, and revoked_at When the admin reissues a replacement token T2 for the same target with specified policy (single-use/N-uses, role, expires_at) Then T2 redeems successfully under its policy, T1 remains invalid, and audit logs link T2 to T1 via replacement_of token reference
Shared Link and Device Change Handling
Given a single-use token T redeemed by Tenant A on Device D1 When Tenant A opens the same token URL on Device D2 within 15 minutes Then the server offers a secure continuation flow by verifying Tenant A via a one-time code sent to the contact on file; upon success, the session continues on D2 without consuming additional uses, and is logged with outcome=continue_on_new_device When a different user attempts to use the shared token link Then the server denies access with 410 Gone or 403 Forbidden and presents a CTA to request a new code, and logs outcome=reject with reason=shared_link_not_authorized
Geofenced Scan Validation
"As a property manager, I want scans validated on-site so that check-ins and reports originate from the correct property."
Description

Request device location at scan and validate proximity to property geofences before allowing sensitive actions like vendor check-in. Support configurable radius per property, role-based bypass for approved remote workflows, and fallbacks when permission is denied (e.g., verification via code or manager approval). Store location and timestamps with clear consent messaging and privacy controls. Provide immediate user feedback for out-of-bounds scans and log all outcomes for auditing and anomaly detection.

Acceptance Criteria
Vendor Check-In Within Geofence
Given a vendor scans a property QR code tied to a work order and grants location permission And the device location is timestamped within 30 seconds and has horizontal accuracy <= 100 meters And the computed distance to any active geofence polygon for the property is <= the configured radius R When the vendor taps Check In Then the system approves the check-in, shows a success state within 2 seconds, and records {lat, lon, accuracy, distance, geofenceId, propertyId, workOrderId, timestampUTC, userId, role, outcome:"success"}
Out-of-Bounds Scan Blocks Sensitive Actions
Given a vendor scans the property QR code and location permission is granted And the computed distance to all active geofence polygons is > R + 20 meters When the vendor attempts to check in Then the system blocks the action, displays "You are outside the allowed area" with the measured distance and property name within 2 seconds, offers Retry Location, and does not allow check-in And the system records an event with outcome:"blocked_out_of_bounds" and {lat, lon, accuracy, distance, propertyId, workOrderId, timestampUTC, userId, role}
Role-Based Bypass for Approved Remote Workflows
Given a user with a role that has Geofence Bypass permission for vendor check-in and a work order flagged as eligible for remote workflows When a check-in attempt is outside the geofence or no valid location is available Then the UI presents a "Bypass Geofence" option requiring a reason code and manager approval or a preapproved remote flag on the work order And upon successful bypass, the system completes check-in labeled "Remote Check-In", and records {bypass:true, method, approverId or preapprovalId, reasonCode, outcome:"bypass_remote"} And if the user lacks bypass permission, the bypass option is not shown and the action remains blocked
Fallback When Location Permission Is Denied
Given the OS returns location permission status of Denied or Restricted at scan When the user attempts a sensitive action (e.g., vendor check-in) Then the system presents options: Open Settings to enable location, Enter Verification Code, or Request Manager Approval And upon correct code entry or manager approval, the system completes the action and records {method:"code" or "manager_approval", outcome:"bypass_permission_denied"} And upon incorrect code entry, the system displays an error and does not complete the action And all outcomes are logged with {permissionStatus, propertyId, workOrderId, timestampUTC, userId, role}
Configurable Geofence Radius and Accuracy Handling
Given a property has a configurable geofence radius R (50–500 meters, default 150 meters) When validating a scan, Then the system uses the property-specific R And if location accuracy > 200 meters or the fix is older than 30 seconds, Then the system prompts retry and does not decide pass/fail until a valid fix or a permitted bypass occurs And after two unsuccessful retries, the system offers eligible fallback/bypass options per role and configuration
Consent Messaging, Data Storage, and Privacy Controls
Given a first-time attempt to use geofenced validation on a device When requesting location, Then the system shows consent text stating what data is collected, why, retention period, and links to the privacy policy, with choices Allow or Not Now And upon any outcome (success, blocked, or bypass), the system stores {lat, lon to 6 decimals, accuracy, timestampUTC, propertyId, workOrderId, userId, role, outcome, method} And given a default data retention of 180 days (configurable 30–730 days), when records exceed retention and are not on legal hold, Then the system purges location fields And an administrator can export and delete a user’s geofence records upon verified request
Audit Trail and Anomaly Detection for Scan Validation
Given any scan attempt tied to a property QR code When validation completes, Then the system writes an immutable audit event within 1 second with fields {eventId, propertyId, workOrderId, userId, role, outcome in [success, blocked_out_of_bounds, bypass_remote, bypass_permission_denied, accuracy_insufficient, error], lat, lon, accuracy, distance, timestampUTC, clientInfo} And an admin can filter and export events by date range, property, role, outcome, and user And if >3 out-of-bounds attempts occur for the same work order or device within 10 minutes, Then the system flags an anomaly and notifies the assigned property manager And if the platform detects mock-location indicators, Then the event is tagged "suspected_spoofing" and included in anomaly reports
Tenant Scan Intake with Pre-scoped Link
"As a tenant, I want to scan a code and be taken directly to a pre-filled report form so that I can submit an issue quickly without typing my address or unit details."
Description

When a tenant scans a property or unit code, open a mobile-first, photo-led intake flow pre-filled with property and unit context to eliminate address and unit typing. Offer quick category suggestions, description field, photo/video capture, contact verification, and duplicate detection against open issues for the same unit with an option to attach. Provide localized language, accessibility compliance, and a confirmation screen with status tracking after submission. Seamlessly posts to FixFlow’s triage and notification systems.

Acceptance Criteria
Pre-Scoped Property and Unit on Scan
Given a valid tenant QR code tied to Property A, Unit 3B When the tenant scans the code on a mobile device Then the intake opens in a mobile web view And the property address and unit are pre-filled and non-editable And no address or unit typing is required And the session is associated with Property A, Unit 3B for all subsequent steps
Mobile Photo-First Intake Flow
Given the pre-scoped intake is opened on a mobile device When the tenant proceeds to the first step Then the camera interface is prompted by default with an option to upload from gallery And at least one photo or one video is required to continue And up to 5 photos and 1 video can be attached And if camera permission is denied, the tenant can still upload from gallery And media uploads succeed over 3G or better with progress indication
Category Suggestions and Description Entry
Given the tenant has added media in the intake When the category step is displayed Then the system shows at least 3 suggested categories based on media, keywords, or unit history And the tenant can select a suggested category in a single tap or open the full category list And the selected category is captured in the submission And a free-text description field is available (0–500 characters) and saved if provided
Tenant Contact Verification
Given the tenant reaches the contact step When FixFlow has prior contact info for the unit Then the phone and/or email is pre-filled for review And the tenant must verify via OTP (SMS or email) before submission And OTP codes expire in 10 minutes with a maximum of 3 attempts And upon successful verification, the submission button is enabled and a verification timestamp is stored
Duplicate Detection and Attach to Existing Issue
Given the tenant selects a category and provides media/description When there are open issues for the same unit created within the last 60 days Then the system displays potential matches with title, date, and status And the tenant can choose "Attach to existing" to update the matched issue with new media and comment And if "Attach to existing" is chosen, no new issue is created And if the tenant proceeds anyway, a new issue is created and flagged as a potential duplicate
Localization and Accessibility Compliance
Given the intake is opened on a device with a non-English locale (e.g., es-ES) When the intake loads Then all UI text, dates, and messages appear in the device language with English as fallback And right-to-left rendering is supported for RTL locales And the flow meets WCAG 2.1 AA: contrast ≥ 4.5:1, focus order logical, elements labeled for screen readers, and tap targets ≥ 44x44 px
Submission Confirmation, Triage, and Notifications
Given the tenant submits a new issue from the intake When the submission is accepted Then a confirmation screen displays a tracking ID, summary, and a link to real-time status And the issue appears in FixFlow’s triage queue within 5 seconds with property and unit context And notifications are sent to the configured property contacts within 60 seconds And the tenant receives a confirmation message via the verified channel within 60 seconds
Vendor Scan Check-in and Status Updates
"As a vendor, I want to scan a job code to check in and update status so that my arrival and progress are recorded without navigating the full portal."
Description

Enable vendors to scan a job-specific code to authenticate or verify via one-time code, check in on arrival, and update the work order status with one-tap actions (start, pause, complete) plus notes and photos. Capture timestamp, geolocation, and device info to build an auditable trail and feed SLA metrics. Support partial completion, required prompts like parts needed, and offline capture with automatic sync when connectivity is restored. Propagate updates to FixFlow timelines, notifications, and dashboards.

Acceptance Criteria
On-site QR Scan Check-in
Given an assigned vendor has a valid, job-specific QR code When the vendor scans the code within 150 meters of the property and within the code’s validity window Then the system authenticates the vendor and records a Check-In event with fields: workOrderId, vendorId, eventType=check-in, timestamp (UTC ISO-8601), latitude, longitude, locationAccuracy<=50m, device.os, device.browser, device.userAgent, ipAddress And the work order status updates to On Site And duplicate scans within 2 minutes are ignored as idempotent (no additional events created)
One-Time Code Fallback Authentication
Given the vendor cannot scan the QR code When the vendor enters a valid one-time code tied to the work order Then the code is accepted only if not expired (<=15 minutes) and not previously used, after which it is consumed And after successful entry, a Check-In event is recorded with the same fields as QR check-in And after 5 consecutive invalid attempts, the flow is rate-limited for 5 minutes and an alert is logged
One-Tap Status Updates with Required Inputs
Given the vendor is checked in When the vendor taps Start Then the system records a Start event with startedAt timestamp and sets status to In Progress When the vendor taps Pause Then the system requires a pauseReason (from configured list) and optional partsNeeded notes, records a Pause event with pausedAt timestamp, and sets status to Paused When the vendor taps Complete Then the system requires at least 1 photo and completionNotes (>=10 characters), records a Complete event with completedAt timestamp, and sets status to Completed And the system prevents Complete if Start has not occurred and prevents Start if not checked in
Partial Completion and Parts Needed Workflow
Given the vendor cannot fully resolve the issue on the first visit When the vendor selects Partial Complete or Needs Parts Then the system requires: partsList (at least 1 item), restockOrOrder choice, ETA date, and technician notes (>=10 characters) And the work order status updates to Pending Parts and an expected return window is created And tenants and managers receive notifications of partial completion with ETA And subsequent visits can Resume work, preserving prior notes and photos
Offline Mode and Automatic Sync
Given the vendor’s device is offline When the vendor performs check-in and status updates Then the app stores events locally with clientEventId and actionTimestamp and displays an Offline queued state And upon connectivity restoration, the app syncs all queued events within 30 seconds, preserving original actionTimestamp order And the server deduplicates by clientEventId and ensures a consistent state; in case of invalid transitions, the server rejects the event and the app shows a retry or discard prompt without data loss
Event Propagation to Timeline, Notifications, and Dashboards
Given a Check-In, Start, Pause, Partial Complete, or Complete event is recorded When the event is accepted by the server Then the work order timeline displays the event within 5 seconds And subscribed stakeholders receive notifications according to their preferences within 10 seconds And dashboards and SLA metrics update within 60 seconds, including: timeToOnSite, timeInProgress, pausedDuration, totalResolutionTime
Audit Trail Integrity and Access Control
Given any vendor-facing action that modifies work order state When the action is recorded Then the event is immutable; edits create a new corrective event linked to the original And only the assigned vendor (or explicitly authorized substitute) can check in or update status; unauthorized scans or codes return a 403 and are logged And check-in is blocked if the device is more than 300 meters from the property unless a manager override is approved; overrides are captured as separate events with approverId
QR Code Management Console and Audit
"As a property manager, I want a console to manage and audit all QR codes so that I can deploy signage at scale and monitor usage and security."
Description

Provide an admin console to create, bulk-generate, organize, and manage QR codes for properties, buildings, and units with parameters such as role, scope, expiry, usage limits, and geofence settings. Offer print-ready label templates, batch exports, and download options. Display real-time usage analytics, redemption history, and anomaly flags like repeated out-of-bounds scans. Include revoke/rotate actions, webhook subscriptions to scan events, and role-based permissions for safe delegation.

Acceptance Criteria
Bulk QR Code Generation with Scoped Parameters
- Given I am an authenticated Admin with permission "QR:Manage", When I select a property/building/unit list and request bulk generation of 1,000 tenant-intake QR codes with role=tenant, scope=unit, expiry=30 days, usageLimit=1, geofence=200m, Then the system generates exactly 1,000 unique codes and persists all parameters to each record. - Given invalid inputs (e.g., expiry in the past, negative usageLimit, unknown role), When I attempt to generate, Then I see inline validation errors and no codes are created. - Given a valid bulk request up to 5,000 codes, When I submit, Then processing completes within 10 seconds P95 and a success toast and downloadable batch are provided. - Given codes are generated, When I view the batch detail, Then I see a CSV listing with codeId, propertyId, buildingId, unitId, role, scope, expiryUTC, usageLimit, geofenceCenter, geofenceRadius, createdBy, createdAt. - Given any bulk operation, When it completes, Then an audit log entry is written with actor, parameters summary, counts, and checksum of generated IDs.
Single-Use, Time-Limited, and Geofenced Scan Enforcement
- Given a QR configured with usageLimit=1 and expiry=2025-12-31T23:59:59Z, When a tenant scans within the validity window and within 200m of the geofence, Then the pre-scoped intake opens and usageCount becomes 1. - Given the same QR after usageCount reaches usageLimit, When scanned again, Then the response is HTTP 410 Gone with an "Expired/Used" page and no workflow access. - Given device location permission is denied or unavailable, When scanning a geofenced QR, Then the user is shown a "Location required" prompt and the scan is not redeemed. - Given three scan attempts from geolocations >500m outside the geofence within 30 minutes, When they occur, Then an anomaly flag "Out-of-bounds" is attached to the QR and surfaced in the console.
Print-Ready Label Templates and Batch Export
- Given a generated batch, When I open "Print Labels", Then I can select template sizes (US Letter, A4, 2x3 inch label) and preview with bleed, margins, and alignment grid. - Given I download, When I choose PDF and 300 DPI, Then a single PDF with one code per label including property/building/unit text and optional instructions is downloaded and all codes pass a standard smartphone QR scan at arm's length. - Given I choose "Batch Export", When I export as ZIP, Then the ZIP contains one PNG (512px, transparent background) and one SVG per QR with filenames including codeId and unitId. - Given I toggle "Human-readable code", When enabled, Then the short code (e.g., FF-AB12) prints beneath the QR.
Usage Analytics and Redemption History with Anomaly Flags
- Given active codes, When I open Analytics, Then I see real-time totals for scans, unique devices, and redemptions for Today, 7D, 30D with refresh latency under 5 seconds. - Given I filter by property/building/unit and role, When applied, Then charts and tables reflect the filter and the URL encodes the state for sharing. - Given a specific code, When I open its History, Then I see a chronological log with timestamp (property TZ), device fingerprint hash, IP country, lat/long (if provided), outcome (success/blocked/expired), and anomaly tags. - Given a table view, When I click Export CSV, Then a CSV downloads with the visible columns and applied filters.
Revoke and Rotate Actions
- Given an active code, When I click Revoke and confirm with a required reason, Then the code status becomes Revoked, future scans return HTTP 410 with a Revoked page, and a webhook "qr.revoked" is emitted. - Given a code, When I click Rotate, Then a new code is generated with the same scope/role and the old code is automatically revoked; the batch and label preview update to the new code. - Given selected codes in bulk, When I choose Revoke > Bulk, Then up to 1,000 codes are revoked in a single operation and a summary email/audit entry is recorded.
Role-Based Permissions and Delegation
- Given roles OwnerAdmin, PropertyManager, VendorCoordinator, and Viewer, When a user without "QR:Manage" attempts to create/revoke/rotate, Then the action is blocked with a 403 and a UI toast "Insufficient permissions". - Given a PropertyManager scoped to Property A, When they access codes for Property B, Then codes are hidden from listings and direct access attempts are blocked and audited. - Given a role change, When updated, Then permissions take effect within 60 seconds across the console and API.
Webhook Subscriptions for Scan Events
- Given I have Admin rights, When I create a webhook with URL using HTTPS, subscribe to events [qr.scanned, qr.redeemed, qr.anomaly, qr.revoked], and provide a secret, Then the webhook saves and a test delivery returns 2xx and is marked "Delivered". - Given an event occurs, When delivering, Then each request includes HMAC-SHA256 signature header over the raw payload and a unique eventId with createdAt. - Given a webhook returns 5xx or times out after 5 seconds, When retrying, Then the system retries up to 6 times with exponential backoff and marks status accordingly; redelivery can be triggered manually from the UI. - Given a webhook is paused, When events occur, Then deliveries are queued but not sent until resumed.

Camera‑Only Link

Send a capture‑focused link that opens directly to the camera with optional gallery lock, EXIF validation, and subtle watermarking. Auto‑stamp time, unit, and (optional) GPS, queue offline if signal is weak, and upload when connected. Coordinators get clean, compliant evidence the first time—fewer callbacks and faster approvals.

Requirements

Deep-Link Camera Launch
"As a tenant, I want a link that opens straight to my camera so that I can quickly capture the issue without navigating menus or logging in."
Description

Provide a short, mobile-friendly URL that opens directly into a capture-first web view with the device camera preselected. Support iOS Safari and Android Chrome constraints via getUserMedia with graceful fallback to a standard uploader if camera access is denied or unsupported. Pre-bind request context (unit, request ID, contact) via a secure token so the user does not need to sign in. Present a minimal capture UI for photo and short video, enforce sensible defaults (resolution, orientation lock, file-size caps), and display lightweight guidance to frame the issue. Handle permission prompts, orientation, and retry flows, and surface clear error messages if hardware or browser blocks access. Localize UI text and ensure WCAG AA accessibility for buttons and instructions.

Acceptance Criteria
Mobile Deep-Link Camera Launch (iOS Safari and Android Chrome)
Given a valid FixFlow camera deep-link opened on a camera-capable device in iOS Safari 15+ or Android Chrome 100+ When the user taps the link Then the capture-first web view renders over HTTPS without requiring sign-in And the browser prompts for camera permission if not previously granted And upon grant, a live rear-camera preview is visible within 2 seconds And photo and short-video capture controls are visible and operable And lightweight framing guidance is visible and does not obstruct the preview
Short Link Generation and Redirection
Given a maintenance request and contact context exist When a coordinator generates a camera-only link Then the system returns a short HTTPS URL 80 characters or fewer And opening the short URL on mobile resolves within one redirect to the capture view And the short URL is unique per request and contact and not guessable with at least 128 bits of entropy
Graceful Fallback When Camera Unavailable or Denied
Given camera access is denied by the browser or unsupported or no camera hardware is present When the deep-link is opened Then a standard uploader UI is displayed with clear explanatory text And the user can select photos and videos from the device gallery And uploads proceed successfully subject to the same file validations as camera capture And a Retry Camera option is available and functional after the user changes permissions
Secure Token Context Binding Without Sign-In
Given a deep-link containing a signed token bound to a specific request ID, unit, and contact When the link is opened Then the capture view pre-binds the context and displays non-sensitive identifiers read-only And any media submitted is associated only with that bound request ID And any tampering with the token results in an invalid link state and no data exposure And the user is never prompted to authenticate
Capture UI Defaults and Constraints
Given the capture-first web view is active When the user captures a photo Then the photo is constrained to a maximum of 1920 pixels on the longest edge with JPEG compression applied And if the captured photo exceeds 5 MB, the user is prompted to retake or accept automatic compression before submit When the user records a video Then the video is constrained to 720p resolution with a maximum duration of 20 seconds And if the video exceeds 50 MB, submission is blocked with guidance to trim or retake And the UI remains orientation-locked to portrait during capture and submission
Permission, Error Messaging, and Retry Flow
Given the user opens the deep-link When camera initialization fails due to permission, browser policy, or hardware error Then a specific, human-readable error message is displayed naming the cause And the user is offered actions to resolve: Open Settings, Retry Camera, Use Uploader And selecting Retry Camera re-requests permission and, upon grant, starts the live preview without full page reload
Localization and WCAG AA Accessibility
Given the device locale is supported When the deep-link view loads Then all visible text, buttons, and guidance are displayed in that locale (at minimum English and Spanish) And all interactive elements have descriptive accessible names and logical focus order And text and component contrast ratios meet WCAG 2.1 AA (text >= 4.5:1, UI components >= 3:1) And the capture, uploader, and error flows are operable with screen readers and switch controls
Secure Tokenized Capture Link
"As a maintenance coordinator, I want expiring, single-use capture links tied to a unit so that evidence is secure and mapped to the correct request."
Description

Generate signed, tamper-resistant capture links bound to a specific property unit and request, with configurable expiration (e.g., 24–72 hours), optional single-use enforcement, and revocation. Exclude PII from the URL and require HTTPS on a dedicated capture subdomain. Implement rate limiting, bot mitigation, and open-redirect protection. Log creation, sends, opens, and submissions for auditability. Provide admin controls to set defaults at account level and override per request. Ensure links are compatible with SMS, email, and messaging apps, including proper preview metadata and deep-link handling on mobile.

Acceptance Criteria
Signed HTTPS Capture Link Without PII
Given a coordinator generates a capture link for unit U and request R When the link is created Then the URL uses the dedicated capture subdomain over HTTPS only And the URL contains no PII (no names, emails, phone numbers, or street addresses) And the link includes a signed token bound to U and R And token validation succeeds only for the original bound payload and fails on any alteration Given the link is accessed over HTTP or a non-capture host When the request is made Then HTTP 308 redirects to the HTTPS capture subdomain if same root domain And otherwise the request is rejected with 403 and reveals no unit/request metadata Given a missing or malformed token When the link is opened Then a 401/403 error screen is shown and no capture UI or metadata is returned
Expiration, Single‑Use, and Revocation Enforcement
Given account defaults set expiration E hours (24–72) and single‑use flag S When a new link is generated Then E and S are embedded and signed into the token Given current time exceeds expiration (with ±5 minute clock skew tolerance) When the link is opened Then an "Link expired" view is shown and submission is blocked And an "expired" audit event is recorded Given single‑use is enabled When a first successful submission completes Then subsequent opens show "Link already used" and submission is blocked And a "consumed" audit event is recorded Given a coordinator revokes the link When revocation is saved Then within 60 seconds all new or active sessions are blocked with "Link revoked" And a "revoked" audit event is recorded
Rate Limiting and Bot Mitigation
Given default limits of 20 opens per IP per 5 minutes and 3 submissions per IP per hour When a client exceeds a limit Then HTTP 429 is returned with a Retry‑After header And no additional submissions are accepted during the window And a rate‑limit audit event is recorded Given traffic shows bot indicators (e.g., >5 opens/sec from same IP or known bot UA) When accessing the link Then a bot‑mitigation challenge is presented And failure blocks access while success restores access within limits Given per‑request overrides are configured within allowed bounds When the link is accessed Then the overridden limits take effect for that link only
Open‑Redirect and URL Parameter Hardening
Given the link includes optional next/return parameters When redirecting after submission Then only allowlist internal domains/paths are permitted And any external domain is rejected and a safe default route is used Given extra or altered query parameters (including duplicated token fields) When the link is processed Then signature verification is performed only on signed fields And unknown parameters are ignored for security decisions Given attempts to alter scheme/host/path via parameters When processing the request Then the app rejects the redirect with a safe default and logs a security event
Audit Logging of Link Lifecycle
Given a link is created When creation occurs Then an audit log entry is written with timestamp, actor id, unit id, request id, token id, expiration, single‑use flag, and originating IP And the entry is immutable and queryable within 5 seconds Given the link is sent via SMS/email/messaging When the send action occurs Then a send event is logged with channel, timestamp, delivery id (if available), and masked recipient Given the link is opened by a human user When the page loads Then an open event is logged with timestamp, IP (hashed), and user agent Given a submission occurs When files/data are uploaded Then a submission event is logged with timestamp, success/failure, and file count Given a link is revoked or expires When that state change happens Then a corresponding event is recorded with actor (if any) and reason
Admin Defaults and Per‑Request Overrides
Given an account admin views capture link settings When saving changes Then defaults for expiration (24–72 hours), single‑use (on/off), and rate‑limit thresholds can be configured And changes are validated, persisted, and audit‑logged Given a coordinator generates a link for a specific request When overriding defaults Then values are validated within allowed ranges and applied only to that link And the resulting configuration is displayed in UI and exposed via API response Given a non‑authorized role attempts to change settings When the action is attempted Then the request is rejected with HTTP 403 and no changes are made Given defaults are updated When new links are created Then new links use updated defaults while existing links remain unchanged
Cross‑Channel Compatibility and Deep‑Link Handling
Given the link is shared via SMS, email, iMessage, or WhatsApp When the recipient views the message Then a preview shows a generic title/description with no PII and the capture icon And the token remains intact after tap/click despite link‑wrapping Given the link is opened on iOS or Android When the FixFlow app is installed Then the deep link opens the in‑app capture route bound to the token Else the mobile web capture on the HTTPS capture subdomain is opened Given messaging/email platforms prefetch for link previews When crawlers request metadata Then OG/Twitter metadata is served without advancing link state or consuming single‑use And crawler fetches are logged as preview events, not user opens
Offline Queue & Background Upload
"As a tenant with weak signal, I want my captures to save and upload automatically when I’m back online so that I don’t have to repeat the process."
Description

Allow users to capture when connectivity is poor by queueing photos/videos locally with encryption at rest (IndexedDB) and uploading automatically when a network is available. Use service workers, chunked uploads, and resumable transfers with exponential backoff, conflict detection, and checksum validation. Provide clear UI for queued, uploading, and completed states, with the ability to cancel or retry. Respect device constraints (battery saver, metered data), compress media on-device where possible, and enforce per-item and total queue limits. Preserve request context so queued captures remain associated even after the browser is closed.

Acceptance Criteria
Encrypted offline queue persists across sessions
Given the device has no or poor connectivity When the user captures a photo or video via the Camera‑Only Link Then the media is saved to IndexedDB encrypted at rest with its request context (request ID, unit, coordinator ID, timestamp, optional GPS) And the item appears in the queue with state "Queued" showing capture time, size, and context badges And after closing and reopening the browser, the queued item is still present with the same context and is ready for upload
Background upload respecting connectivity and device constraints
Given one or more items are queued and background sync is permitted And the device is not blocked by battery saver policies unless the user has explicitly overridden When network connectivity becomes available (Wi‑Fi or user‑approved metered) Then the service worker starts uploading items in the background without requiring the page to be open And each item transitions through states "Queued" → "Uploading" → "Completed" (or "Failed") And items remain in "Queued" if battery saver or metered policies are not overridden
Chunked, resumable uploads with checksum validation
Given an item is uploading in 5 MB chunks When a network interruption occurs mid‑transfer Then upon reconnection the client queries the server for the last confirmed chunk index and resumes from the next chunk And each chunk includes a checksum validated by the server And the final file checksum matches on client and server before the item is marked "Completed" And if a checksum mismatch is detected, the item is marked "Failed" with an error code indicating integrity failure
Exponential backoff and retry policy
Given a transient upload error occurs (HTTP 408/429/5xx or timeout) When retrying the upload Then the client applies exponential backoff starting at 2s, doubling up to a 64s cap, with a maximum of 6 attempts per item And the UI displays the next retry ETA and current attempt count And after the maximum attempts the item is marked "Failed" and offers a "Retry" action
Conflict detection and deduplication
Given an item with the same content checksum already exists for the same request context on the server When the client attempts to upload the duplicate Then the server indicates a conflict (e.g., 409) or returns the existing asset ID And the client marks the local item as "Completed (Duplicate)" without creating a new server record And the item links to the existing asset in the request timeline
Queue limits and on‑device compression
Given per‑item limits of 12 MB for photos and 200 MB for videos and a total queue cap of 1 GB or 100 items (whichever is reached first) When a capture exceeds a per‑item limit Then the client attempts on‑device compression to fit within the limit while preserving EXIF/timestamp/watermark And if still over limit, the item is not queued and the user sees an error with the file size and limits And when adding a new item would exceed the total queue cap, queueing is blocked with guidance to cancel or upload existing items first
Queue UI states and user controls
Given items exist in various states When the user opens the upload queue UI Then each item displays state badges: "Queued", "Uploading" with percentage, "Completed", "Failed", or "Canceled" And the user can Cancel queued/uploading items, which stops transfer and removes the encrypted blob, marking the item "Canceled" with timestamp And the user can Retry a "Failed" item, which resumes from the last confirmed chunk and updates progress accordingly And error details are viewable for failed items to support triage
Gallery Lock & Capture Policy
"As a coordinator, I want to enforce camera-only capture when needed so that submissions are fresh and accurately represent current conditions."
Description

Offer an admin-configurable policy to allow camera-only capture or permit gallery selection. When gallery lock is enabled, hide or disable file-picker access to existing media and clearly message the policy and rationale to the user. Where browser limitations prevent strict enforcement, tag the submission as “source unverified” and require coordinator review. Detect multiple rapid submissions and obvious duplicates to discourage recycling old images. Expose policy settings at the account and per-request level, and persist the chosen policy in the signed link token.

Acceptance Criteria
Account vs Request Policy Precedence
Given an Admin with Manage Account permission, when they set the Account Default Capture Policy to "Camera Only", then new capture links created thereafter default to "Camera Only". Given the Account Default is "Allow Gallery", when a Request creator sets a per-request policy to "Camera Only", then the issued signed link enforces "Camera Only" for that request regardless of the account default. Given a per-request policy is unset, when a link is generated, then the policy in the link equals the account default at time of generation. Given a non-admin user attempts to change the Account Default, then the UI hides the control and the API returns 403 on update. Given an existing link, when the Account Default is changed later, then the existing link's enforced policy remains the one embedded in its signed token.
Enforced Camera‑Only in Supported Browsers
Given policy "Camera Only" and a browser that supports input capture and MediaDevices.getUserMedia, when the user opens the link, then gallery/file-picker controls are not rendered or are disabled. Given the user taps "Add Photo", when the permission prompt is accepted, then the device camera opens directly without a gallery chooser. Given the user attempts to paste, drag-and-drop, or select a file from storage, then the UI prevents selection and shows an inline notice "Gallery selection disabled by policy". Given policy "Allow Gallery", when the user opens the link, then both camera and gallery selection are available.
Fallback Tagging for Unenforceable Browsers
Given policy "Camera Only" and a browser/device where gallery lock cannot be fully enforced, when the user submits an image, then the system evaluates source verification using supported signals (e.g., EXIF capture timestamp within ±2 minutes of upload, presence of camera model, and, if permission granted, GPS tag). Given the verification signals all pass, then the submission is marked source_status="verified". Given any required signal is missing or inconsistent, then mark source_status="unverified", display a banner in coordinator review requiring manual approval, and disable auto-approval until manual review occurs. Given a coordinator exports the submission, then the source_status value is included in the export and audit trail.
Signed Token Persists and Protects Policy
Given a generated link, when decoded on the server, then the token contains a policy claim (policy=camera_only|allow_gallery) and expiry. Given a user modifies the URL policy parameter or token, then the server rejects it as invalid (HTTP 401/403) or ignores the change and enforces the original policy; the event is logged. Given the token is valid and unmodified, when the capture UI loads, then it enforces the policy from the token even if the per-request record has since changed. Given the token is expired, then the link cannot be used to submit and shows an "expired link" message.
Rapid Submission and Duplicate Detection
Given a single link session, when more than 3 photos are submitted within 60 seconds, then show a confirmation prompt warning about rapid submissions and allow the user to proceed or cancel. Given a newly captured photo is byte-identical to a previously queued or uploaded photo in the same request, then block upload by default and inform the user "This photo appears to be a duplicate" with a one-time override option per session. Given a newly captured photo is near-duplicate (perceptual hash Hamming distance ≤ 5) to a prior photo in the same request, then label it "Possible duplicate" and require user confirmation before upload. Given duplicates are detected, then the coordinator UI groups them and includes duplicate flags in the submission metadata.
User‑Facing Policy Messaging
Given policy "Camera Only", when the capture UI loads, then an inline message appears above the capture control stating that gallery selection is disabled and why, including a "Learn more" link. Given the user taps "Learn more", then a non-modal sheet displays the rationale text and privacy notes without leaving the flow. Given policy "Allow Gallery", when the capture UI loads, then both camera and gallery options are visible and no restriction message appears. Given device accessibility settings for larger text are enabled, then the policy message scales without truncation or overlap.
EXIF Validation & Integrity Checks
"As a coordinator, I want automated EXIF checks so that I can trust when and where the media was captured and spot tampering without manual review."
Description

Extract and validate EXIF and related metadata client-side (when accessible) and server-side to confirm capture time, device source, orientation, and (optional) GPS. Normalize to UTC with timezone offsets, allow reasonable clock skew, and flag anomalies (missing EXIF where expected, edited software tags, impossible GPS jumps, or time outside a defined window). Compute content hashes for duplicate detection, store originals unmodified, and strip sensitive EXIF from distributable versions while retaining required fields for compliance. Provide configurable validation rules and severity (warn vs. block) per account.

Acceptance Criteria
Client-Side EXIF Extraction and Preliminary Validation
Given a Camera‑Only Link is opened in a browser that exposes EXIF on captured images When the user captures a photo and submits it Then the client extracts EXIF fields: DateTimeOriginal, OffsetTimeOriginal (or equivalent), Orientation, Make, Model, Software, and GPSLatitude/GPSLongitude if permission is granted And the client validates that Orientation is present and matches pixel dimensions (portrait vs landscape) And the client includes the extracted EXIF in the upload payload And if EXIF cannot be read client-side, the client sets exif_status=unavailable and proceeds with original bytes
UTC Normalization and Clock Skew Handling
Given account setting clock_skew_minutes=10 and time_window_minutes=120 And the upload includes EXIF DateTimeOriginal and optional OffsetTimeOriginal When the server processes the upload Then the server converts capture_time_utc = DateTimeOriginal + OffsetTimeOriginal (or uses account time zone if offset missing) and stores timezone_offset_minutes And the server marks valid=true if capture_time_utc is within ±time_window_minutes of the job/link window, else flags anomaly code time_out_of_window And the server tolerates capture times within ±clock_skew_minutes of the window edge before flagging And all decisions are included in the validation report returned to the client
GPS Validation and Anomaly Detection
Given account setting gps_required=false and gps_jump_threshold_meters=500 and gps_jump_threshold_seconds=60 When an image contains EXIF GPSLatitude/GPSLongitude Then the server normalizes coordinates to WGS84 decimal degrees and stores gps_accuracy if available And if the same session’s previous photo exists, the server flags anomaly gps_jump when distance > gps_jump_threshold_meters and time delta < gps_jump_threshold_seconds And if the property has a known geofence and the point is outside it, the server flags anomaly gps_out_of_geofence And if gps_required=true and GPS is missing, the server applies severity (warn or block) per account rule and returns a machine-readable code gps_missing
EXIF Integrity and Editing-Software Detection
Given the client sends extracted EXIF and the server re-extracts EXIF from the uploaded bytes When comparing client vs server EXIF values Then mismatches in DateTimeOriginal, Orientation, Make, Model, or GPS fields are flagged as exif_mismatch with configured severity And if EXIF Software contains a known editing application, the server flags edited_software_detected with configured severity And if DateTimeOriginal is missing where gallery is locked and capture is required, the server applies configured severity for missing_capture_time And images with no EXIF at all are flagged exif_missing with configured severity
Content Hashing and Duplicate Detection
Given the server computes SHA-256 over the exact original bytes on receipt When the computed hash matches an existing image for the same work order within the last 30 days Then the server flags duplicate_image and applies the account duplicate_policy severity (warn or block) And if block, the image is not attached to the work order and the API responds 422 with code duplicate_image And the hash is stored and indexed for future deduplication
Original Preservation and Distributable EXIF Stripping
Given an image is accepted (not blocked) When the server stores the file Then the original bytes are stored immutably and unmodified with content_hash_sha256 recorded And a distributable derivative is generated that strips sensitive EXIF (GPS*, SerialNumber, OwnerName, UserComment, XP*, MakerNotes, ImageUniqueID, Software) And the system retains required fields for compliance (DateTimeOriginal normalized to UTC, Orientation, Make, Model) in metadata storage, not embedded in the distributable And only authorized roles can download originals; all others receive the distributable version
Configurable Validation Rules, Severity, and Audit Log
Given an account-level rule set where each check has severity warn or block and a version number When an image is validated Then the server evaluates all rules and returns a per-check validation report with codes, severities, and outcomes And if any block-severity check fails, the upload is rejected with HTTP 422 and machine-readable error codes And every validation event is written to an immutable audit log with timestamp (UTC), user, upload id, account id, rule set version, and decision And changes to the rule set increment the version and apply only to subsequent uploads
Auto-Stamp Metadata & Subtle Watermarking
"As a coordinator, I want captures auto-stamped with time, unit, and optional GPS so that evidence is compliant and traceable without extra steps."
Description

Generate a derivative of each image/video with an unobtrusive overlay that includes timestamp, unit ID, request ID, and optional GPS coordinates. Ensure overlays adapt to orientation, avoid obscuring key content, and meet accessibility and privacy guidelines (hide GPS when disabled). Support adjustable opacity and placement presets. For video, render overlays across frames without degrading playback quality. Preserve the original file alongside the stamped copy, and expose both in the coordinator view. Include a small FixFlow watermark for provenance on derivatives only.

Acceptance Criteria
Correct Metadata Overlay on Photos
Given a photo is captured via Camera‑Only Link, when the stamped derivative is generated, then the overlay includes timestamp (capture time in ISO 8601 with local timezone), unit ID, request ID, and GPS coordinates only if permitted and enabled. Given GPS is permitted and enabled, when the overlay is rendered, then GPS displays as latitude and longitude with 5 decimal places and no address or altitude. Given the overlay is applied, when inspecting the image, then a FixFlow watermark is present and occupies ≤1.5% of total image area. Given the overlay is applied, when measuring layout, then overlay content (text + watermark) occupies ≤12% of image area and stays inside a 4% safe margin from all edges.
Privacy‑Respecting GPS Redaction
Given device location permission is denied or GPS is disabled in link settings, when the stamped derivative is generated, then no GPS coordinates, icons, or location labels appear in the overlay. Given GPS is disabled or denied, when inspecting the stamped file’s metadata, then no geolocation EXIF/XMP tags are added to the derivative while the original remains unchanged. Given GPS is disabled or denied, when viewing the request audit trail, then an entry is recorded indicating GPS not captured with reason (permission denied or setting disabled) and timestamp.
Orientation‑Aware Placement
Given a portrait or landscape photo or video, when the overlay is rendered, then it adheres to the selected placement preset (top‑left, top‑right, bottom‑left, bottom‑right) and remains within a 4% edge safe margin. Given the asset orientation varies (portrait, landscape, square, ultra‑wide), when the overlay is rendered, then its relative text size is 1.5–2.5% of the shorter edge per line and it never overlaps the central 50% of the frame. Given thumbnails and full‑size previews are generated, when viewing them, then the overlay remains fully visible without cropping in both contexts.
Adjustable Opacity and Legibility
Given an admin selects an overlay opacity preset, when a derivative is generated, then the applied opacity equals the preset (60%, 75%, or 90%) within ±2% tolerance. Given overlay text appears over varying backgrounds, when viewing the derivative, then text contrast meets WCAG 2.2 AA (≥4.5:1) using automatic outline or background chip as needed. Given the shortest image/video dimension is ≤720 px, when rendering the overlay, then font height is ≥14 px with line spacing ≥1.2× to maintain readability.
Video Overlay Rendering Quality
Given a source video up to 60 seconds and up to 1080p, when the stamped derivative is produced, then the overlay is present on all frames with fixed position and consistent opacity. Given a baseline re‑encode without overlay, when comparing baseline vs stamped outputs, then resolution and frame rate match the source, audio is unchanged, and SSIM ≥ 0.98. Given the stamped video is produced, when measuring processing, then total processing time is ≤2× the baseline re‑encode and file size increase is ≤10% at matched codec settings.
Preserve Originals and Expose Both Versions
Given a capture is uploaded, when storage is committed, then both the original file and the stamped derivative are saved under the same request ID with distinct file IDs and checksums. Given a coordinator opens the media in the request, when viewing the item, then both versions are available with clear labels (“Original” and “Stamped”), with the stamped version previewed by default. Given a download is initiated for either version, when the file is delivered, then the correct content is returned with appropriate content‑type and filename suffixes (_orig, _stamped), and the original bytes match the uploaded file exactly.
Derivative‑Only FixFlow Watermark
Given derivatives are generated, when viewing the original file, then no FixFlow watermark or overlay artifacts are present. Given derivatives are generated, when viewing the stamped version, then a subtle FixFlow watermark logo/text is present, occupies ≤1% of frame area, uses 60–75% opacity, and is at least 4% from edges. Given overlay placement presets are changed, when generating the stamped version, then the watermark is positioned in the corner opposite the metadata block by default and never overlaps the metadata text.
Coordinator Evidence Viewer & One-Click Approval
"As a coordinator, I want a streamlined viewer with one-click approval so that I can make decisions quickly and reduce back-and-forth with tenants and vendors."
Description

Provide a review workspace that displays submitted media, derived overlays, and validated metadata in a single view. Surface integrity flags (e.g., missing EXIF, clock skew) and location on a mini map when GPS is present. Enable inline actions: request more photos, reject with reasons, or approve with one click to advance triage and attach media to the work order. Maintain a complete audit log of actions and status changes, and notify tenants and vendors according to existing FixFlow workflow rules. Optimize for fast loading with thumbnails, lazy loading, and streaming for larger videos.

Acceptance Criteria
Unified Media & Metadata Review with Integrity Flags
Given a coordinator opens the Evidence Viewer for a submission containing photos and videos When the viewer loads Then a thumbnail grid of all submitted media is displayed with filename, captured timestamp, and video duration where applicable And selecting any item loads a large preview with derived overlays (time, unit, and optional GPS) visible on the preview And validated metadata (EXIF capture time, device model, GPS lat/long if present) is shown in a details panel And integrity flags are surfaced with icon and tooltip for: missing EXIF and clock skew where |server_received_time − EXIF_capture_time| > 10 minutes And items with integrity flags are visually badged in both the grid and details panel
Mini Map Rendering When GPS Metadata Present
Given a media item with GPS metadata is selected in the Evidence Viewer When the details panel is displayed Then a mini map renders a pin at the EXIF coordinates with the capture timestamp in the map caption And if GPS accuracy/altitude are present, they are shown in the map tooltip And if no GPS metadata exists, the mini map container is hidden and a "No GPS data" label appears in the metadata panel
One-Click Approval Action
Given the coordinator has Approver permission on the work order When the Approve button is clicked once Then the work order advances to the next triage state as defined by existing FixFlow workflow rules And all currently submitted media are attached to the work order record And a success confirmation appears within 1 second And an audit log entry is written capturing actor, timestamp (UTC), previous→new status, and attached media IDs And notifications are dispatched to tenant and vendor per workflow rules And repeated clicks while processing do not create duplicate attachments, state changes, or notifications (idempotent)
Reject With Reason and Notifications
Given the coordinator chooses to reject the evidence When the Reject action is invoked Then the user must select at least one standardized reason or enter free-text of 10–500 characters And upon confirmation, the work order status updates to the configured rejected/needs-review state per workflow rules And an audit log entry records actor, timestamp (UTC), reasons, and previous→new status And notifications are sent to the tenant (and vendor if applicable) with the provided reasons per workflow rules And the UI reflects the new status without requiring a page reload
Request More Photos via Camera‑Only Link
Given the coordinator needs additional evidence When the Request More Photos action is selected Then a modal prompts for instructions (required, 10–500 characters) and optional settings (gallery lock on/off, GPS required on/off) And upon sending, the tenant receives a Camera‑Only Link configured per the selected settings And the work order status changes to Awaiting Evidence (or equivalent) per existing workflow rules And an audit log entry records actor, timestamp (UTC), requested instructions, and link ID And the Evidence Viewer shows the request as pending until new media is received
Audit Log Completeness and Immutability
Given any evidence action occurs (Approve, Reject, Request More Photos) When the action completes Then an audit log entry is created within 2 seconds including: action type, actor ID and role, timestamp (UTC), work order ID, previous→new status, related media IDs, and any user-entered reasons/instructions And audit entries are append-only and cannot be edited or deleted by end users And audit entries are visible in chronological order in the work order timeline
Evidence Viewer Performance and Media Streaming
Given a 4G network (≥10 Mbps down, 150 ms RTT) and a submission with 30 photos and 2 videos (each ≤200 MB) When the Evidence Viewer is opened Then above-the-fold thumbnails render within 1.5 seconds (FCP) and the page is interactive within 3 seconds (TTI) And only the first 12 thumbnails load initially; remaining thumbnails lazy-load on scroll And each thumbnail payload is ≤120 KB and is served via optimized formats (e.g., WebP/AVIF) when supported And videos larger than 20 MB use streaming/progressive playback with play start ≤2 seconds and seek without re-downloading from start And skeleton loaders are shown while content loads; no blocking spinner persists >500 ms

Product Ideas

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

Midnight Auto-Dispatch

Classifies after-hours emergencies and auto-dispatches the on-call vendor with lockbox PIN access. Sends live ETA to tenants and a recap for morning review.

Idea

Turnboard

Drag-and-drop timeline for unit turns with task dependencies. Batch-approve punchlist items, auto-request quotes, and flag delays with photo proof.

Idea

Proof Pack

One-click export of timestamped photos, estimates, approvals, and messages into a signed, shareable PDF. Satisfies insurers and auditors without digging through threads.

Idea

Vendor Radar

Scores vendors by speed, quality, and cost using job history. Auto-suggests the best match per issue and location, with backup when schedules conflict.

Idea

Trust Timer

Tenant-facing live SLA clock with status badges and vendor ETA. Pushes proactive updates to cut check-in calls and rebuild confidence.

Idea

Spend Guardrails

Set auto-approval caps by issue and property, with alerts for overages. Require photographic proof for payouts to curb overspend.

Idea

Magic Link Login

Passwordless email/SMS links for tenants and vendors to submit photos and update status. Per-link scopes and expiry boost security while slashing login friction.

Idea

Press Coverage

Imagined press coverage for this groundbreaking product concept.

P

FixFlow Debuts Vendor Radar: Data‑Driven Dispatch That Gets the Right Vendor On‑Site Faster

Imagined Press Article

Austin, TX — FixFlow today announced the general availability of Vendor Radar, a new intelligence layer that transforms how independent landlords and small property managers select and dispatch maintenance vendors. Built for portfolios with 25–200 units, Vendor Radar uses live context, historical performance, and transparent scoring to route each job to the vendor most likely to arrive on time, fix it on the first visit, and protect budgets. Vendor Radar extends FixFlow’s photo‑first intake and smart triage with a unified, explainable ranking across speed, quality, scope fit, compliance, and cost. It works inside the same mobile web portal where managers already intake issues, approve work with one click, and keep tenants informed in real time. What’s new in Vendor Radar - GeoScore: Dynamically weighs travel time, service‑area familiarity, traffic, and multi‑stop routes to prioritize the vendor most likely to get on‑site fastest. - ScopeMatch: Analyzes tenant photos and triage tags to detect exact skills, tools, and certifications required, elevating vendors verified for the scope so the right tech shows up once. - PriceGuard: Normalizes quotes and job history by issue type and region to flag outliers and enable auto‑approvals within guardrails. - SLA Forecaster: Predicts the probability each vendor will meet your SLA given property, time of day, and backlog context, reducing escalations. - Compliance Gate: Continuously verifies licenses, insurance, certifications, and expirations and attaches proof to the work order. - FairShare: Balances assignments across high‑performers to prevent overload and single‑vendor dependency while honoring capacity caps and geographic quotas. - Score Lens: Makes rankings explainable with a transparent breakdown of speed, quality, cost, proximity, and scope fit, along with coaching tips vendors can act on. Why it matters Independent landlords and small property managers often juggle vendor relationships across dozens of trades and neighborhoods. The result is too much guesswork, phone tag, and repeat visits that erode tenant trust. With Vendor Radar, FixFlow replaces intuition with objective, real‑time decisioning that reduces time to dispatch and increases first‑contact resolution—without sacrificing cost control or compliance. Customers already using FixFlow report cutting response time by 40%, slashing administrative work by 60%, and reducing repeat vendor visits, all while giving tenants clear, timely updates. “Maintenance dispatch shouldn’t feel like rolling the dice,” said Maya Cortez, CEO of FixFlow. “Vendor Radar turns every choice into a data‑driven decision. Our customers tell us they feel confident sending the right pro the first time, and tenants notice the difference when ETAs are reliable and issues are resolved in a single visit.” “Score Lens is a quiet game‑changer for our team,” said Victor Lee, owner‑operator at a 120‑unit portfolio in the Northeast and an early adopter of Vendor Radar. “When I can see why a vendor is ranked—and what could improve their standing—it builds trust on both sides. We’re seeing smoother conversations, better adherence to SLAs, and fewer surprises on invoices.” How it works Vendor Radar activates automatically when a work order is created. As photos and context come in through FixFlow’s mobile intake, ScopeMatch identifies the skills and compliance requirements. GeoScore and live traffic conditions update every few minutes, while the SLA Forecaster combines historical performance with current backlog cues. PriceGuard and Budget Guardrails ensure quotes align with expectations, and FairShare maintains a healthy spread of work. The result is a ranked short list that can auto‑dispatch based on customer rules or be approved in one tap from mobile. Designed for real‑world complexity - Multi‑property, multi‑region: Vendor Radar respects on‑call rosters, holidays, and coverage gaps, and cascades to verified backups if an invite isn’t accepted in time. - Transparent by design: Every recommendation includes a Why Ranked panel and the evidence behind it, reducing second‑guessing and audit effort. - Tenant‑centric: Paired with FixFlow’s Trust Timer and ETA Confidence, tenants see realistic windows and reason cues, reducing check‑in calls and anxiety. Availability and packaging Vendor Radar is available today to all FixFlow customers. Advanced auto‑dispatch and FairShare controls can be toggled at the portfolio or property level. Existing customers can enable Vendor Radar in Settings without additional setup; new customers can request white‑glove onboarding to import vendors, verify compliance, and tune guardrails. Early results Early adopters report faster dispatch decisions, fewer repeat visits, and improved on‑time arrival rates. Managers also highlight reduced back‑and‑forth with vendors thanks to shared visibility into rankings and requirements. About FixFlow FixFlow centralizes rental maintenance into a mobile web portal, using photo‑first intake, automated smart triage, and one‑click approvals that route repairs to preferred vendors. Independent landlords and small property managers cut response time by 40%, slash administrative work by 60%, and reduce repeat vendor visits while restoring tenant trust. Supporting quotes “Vendor Radar complements our compliance and audit features, such as Compliance Gate and Audit Seal, so managers can move fast without sacrificing controls,” said Lena Park, VP of Product at FixFlow. “It’s the missing link between triage and tenant experience.” Media contact Press: press@fixflow.com Sales: sales@fixflow.com Phone: +1 (415) 555‑0137 Website: https://www.fixflow.com Trademarks FixFlow and Vendor Radar are trademarks of FixFlow, Inc. All other trademarks are property of their respective owners.

P

FixFlow Launches Turnboard: Keep Unit Turns On Time With Critical Path Guard, Auto‑Leveler, and Real‑Time Vendor Slotting

Imagined Press Article

Austin, TX — FixFlow today introduced Turnboard, a purpose‑built coordination layer for unit turns that keeps move‑in dates on track and trims idle time across trades. Turnboard makes it simple for small property managers handling 50–200 units to plan, sequence, and execute make‑ready work with confidence—from first notice to final photo sign‑off. Turnboard builds on FixFlow’s mobile, photo‑first maintenance platform to bring reliability and transparency to one of the industry’s most chaotic workflows. Instead of juggling spreadsheets, group texts, and paper clipboards, coordinators now see a live, dependency‑aware timeline that automatically reacts to delays, parts lead times, and vendor availability. What’s in Turnboard - Critical Path Guard: Automatically calculates and visualizes the critical path for each unit turn, flags risks that jeopardize the move‑in date, and recommends the smallest resequencing to recover time. - Auto‑Leveler: Balances work across units, trades, and crews by resolving resource conflicts and smoothing start times, keeping crews utilized and reducing idle gaps. - Vendor Slotting: Requests real‑time time slots from preferred vendors and proposes windows based on dependency completion and travel time, with one‑tap accepts and backup escalation. - Lead‑Time Radar: Tracks parts and appliance SKUs, compares supplier ETAs to needed‑by dates, and suggests alternates or rental stock when risk rises. - Turn Playbooks: Reusable templates for light, standard, and full turns that preload dependencies, checklists, and photo QA steps, adapted to each building and unit type. - Bulk Shift: Portfolio‑wide rescheduling for storms, utility shutdowns, or elevator outages that preserves dependencies and automatically notifies vendors and tenants. Why it matters now Vacancy days are expensive, and coordination errors create a hidden tax on NOI. For small teams, one late delivery or missed handoff can cascade across trades and delay revenue. Turnboard gives coordinators the same level of control airlines use to manage gates and crews, but without complexity. With clear timelines, proactive nudges, and one‑click approvals, Turnboard helps FixFlow customers meet SLAs more consistently while cutting administrative work by up to 60%—in line with results already seen on maintenance operations. “Unit turns are where portfolios win or lose time,” said Maya Cortez, CEO of FixFlow. “Turnboard surfaces the few tasks that truly matter for the move‑in date, then gives managers fast, safe levers to recover time. It’s about getting keys back in doors sooner without burning out teams or blowing budgets.” “Critical Path Guard has changed our morning stand‑ups,” said Tasha Nguyen, make‑ready lead at a 96‑unit operator and an early Turnboard pilot. “We start the day with a single view of what’s at risk, who’s on point, and the exact fix to pull the date back in. Our vendors love the slot requests because they’re based on real readiness, not wishful thinking.” How it works Turnboard sits on top of FixFlow’s work order engine. When a turn is created, managers choose a Playbook that lays out trades, durations, and required photo proof. As work progresses, Vendor Slotting pulls in availability and travel windows; Auto‑Leveler resolves conflicts; and Critical Path Guard runs continuously to flag risks and propose rescheduling options. Lead‑Time Radar ties in procurement details and suggests alternates before parts become blockers. Bulk Shift can be used to adapt schedules when external events hit, preserving dependencies and pushing reason‑coded updates to vendors and tenants. Designed for small teams - Setup in minutes: Import past turns to tailor Playbooks and durations automatically. - Mobile‑first: Coordinators and field crews confirm tasks, upload photos, and receive nudges on the go—no new app to install. - Transparent to stakeholders: Owners receive a clean, read‑only view; tenants see clear status, ETAs, and prep steps when access is needed. Availability and pricing Turnboard is available today to all FixFlow customers. Existing customers can enable Turnboard from Settings and start with out‑of‑the‑box Playbooks for light, standard, and full turns. For teams with complex vendor networks, FixFlow offers white‑glove onboarding to connect preferred vendors, set capacity caps, and calibrate guardrails. Early impact Pilot customers report fewer last‑minute scrambles, better adherence to promised move‑in dates, and more predictable vendor calendars. Coordinators highlight fewer phone calls and cleaner photo proof thanks to guided checklists baked into each task. About FixFlow FixFlow centralizes rental maintenance into a mobile web portal, using photo‑first intake, automated smart triage, and one‑click approvals that route repairs to preferred vendors. Independent landlords and small property managers cut response time by 40%, slash administrative work by 60%, and reduce repeat vendor visits while restoring tenant trust. Supporting quotes “Auto‑Leveler has been particularly helpful for portfolios with mixed garden‑style and mid‑rise assets,” said Lena Park, VP of Product at FixFlow. “It quietly removes conflicts across painters, cleaners, and handymen so each crew stays productive and the overall turn duration drops.” Media contact Press: press@fixflow.com Sales: sales@fixflow.com Phone: +1 (415) 555‑0137 Website: https://www.fixflow.com Trademarks FixFlow and Turnboard are trademarks of FixFlow, Inc. All other trademarks are property of their respective owners.

P

FixFlow Introduces Trust Timer 2.0: Real‑Time ETA Confidence, Doorstep Alerts, and Plain‑Language Updates That Restore Tenant Trust

Imagined Press Article

Austin, TX — FixFlow today announced Trust Timer 2.0, a major update to its tenant experience layer that brings airline‑style transparency to rental repairs. Trust Timer 2.0 pairs live ETA Confidence and Doorstep Alerts with clear status explanations and customizable update preferences, so tenants know what’s happening, when help will arrive, and what they can do next—without calling the office. Trust Timer has always displayed a live SLA clock, letting tenants see progress from intake to resolution. With Trust Timer 2.0, FixFlow adds a set of features designed to set accurate expectations, reduce anxiety at the door, and minimize missed connections that lead to repeat visits. What’s new in Trust Timer 2.0 - ETA Confidence: Shows a live arrival window with confidence bands and reason cues like traffic, prior job overrun, or weather. Tenants get realistic timing and can plan around the visit. - Doorstep Alert: Geofenced notifications fire when the vendor is 15, 5, and 1 minute away and upon arrival, with one‑tap options to confirm access or request a short wait. - Delay Transparency: If an SLA is at risk, tenants receive a clear explanation, an updated ETA, and easy choices: pick a new window, switch to a backup vendor, or authorize after‑hours service. - Visit Prep: Issue‑specific, property‑aware prep checklists (clear work area, secure pets, breaker/water access, elevator booking) improve first‑pass fixes and shorten visit time. - Update Control: Tenants choose channels (SMS, email, WhatsApp) and frequency—milestones only or play‑by‑play—and set quiet hours. - Household Share: Creates a secure, expiring link for roommates, caretakers, or the front desk so everyone sees the same status, ETA, and prep steps. - Plain Status: Translates system badges into simple language with what’s happening now, who’s up next, and what the tenant can do. Why it matters Tenants want clarity, not platitudes. In an environment where maintenance touches satisfaction scores more than almost any other factor, the difference between a confident 30‑minute window and a vague “sometime today” is trust. For small property teams already using FixFlow to cut response time by 40% and administrative work by 60%, Trust Timer 2.0 closes the loop with honest, context‑rich communication that reduces check‑in calls and prevents avoidable no‑shows. “We designed Trust Timer 2.0 to treat tenant time with the same respect airlines give departure boards,” said Maya Cortez, CEO of FixFlow. “When the ETA changes, we explain why and offer choices. That turns frustration into control and protects the relationship, even when things don’t go perfectly.” “Doorstep Alerts have eliminated our most common missed connection: the last‑minute dog walk,” said Felix Morales, regional property manager who handles after‑hours coverage for a 70‑unit portfolio. “Tenants confirm access as we’re pulling up, and if they need five minutes, we know before we’re knocking.” How it works Trust Timer 2.0 lives on the same secure, passwordless links FixFlow uses for tenant intake and vendor updates. The moment a work order is approved, tenants receive a link with a live SLA clock, current status, and the next milestone. As the assigned vendor travels, ETA Confidence updates the arrival window using live traffic, prior‑job signals, and historical accuracy. Doorstep Alerts and Household Share ensure the right people are informed at the right moments, while Update Control respects quiet hours and channel preferences. If a delay arises, Delay Transparency pushes the reason and gives the tenant choices, including switching to a pre‑qualified backup via Fallback Cascade. Secure and inclusive by design - Smart Scope Builder: Creates time‑boxed, role‑appropriate permissions for each link, so tenants can see and do what’s intended—nothing more. - ForwardShield: Detects if a link is forwarded and downgrades access or triggers a quick step‑up check to keep information protected without forcing passwords. - OmniSend Failover: Delivers updates over the channel that actually reaches the tenant, with smart retries and a voice readout option when needed. - Trust Handshake: Pairs a device with one tap for repeat use, adding step‑up verification only when behavior looks unusual. Early impact Early users report fewer check‑in calls, lower anxiety at the door, and better first‑pass fix rates due to clearer prep steps. Managers highlight cleaner after‑hours handoffs when paired with Night Triage and Morning Digest, and vendors appreciate the reduction in surprise no‑access outcomes. Availability Trust Timer 2.0 is rolling out today to all FixFlow customers at no additional charge. Tenants will automatically see the new features on their next work order. Managers can customize quiet hours, notification cadence, and escalation behavior by property. About FixFlow FixFlow centralizes rental maintenance into a mobile web portal, using photo‑first intake, automated smart triage, and one‑click approvals that route repairs to preferred vendors. Independent landlords and small property managers cut response time by 40%, slash administrative work by 60%, and reduce repeat vendor visits while restoring tenant trust. Supporting quotes “Transparency is a performance tool,” said Lena Park, VP of Product at FixFlow. “When tenants are prepared and informed, vendors move faster and spend more time fixing and less time coordinating.” Media contact Press: press@fixflow.com Sales: sales@fixflow.com Phone: +1 (415) 555‑0137 Website: https://www.fixflow.com Trademarks FixFlow and Trust Timer are trademarks of FixFlow, Inc. All other trademarks are property of their respective owners.

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.