Technology Stack & Component Architecture
Overview
This document provides a comprehensive mapping of all application components to their selected technologies, integration patterns, and implementation guidelines. It serves as the authoritative reference for technology decisions across Pomp's Dispatch Center Application.
Table of Contents
Technology Summary
Full Stack Overview
flowchart TB
subgraph presentation["PRESENTATION LAYER"]
web["WEB (Vue.js 3)
• TypeScript
• Tailwind CSS
• Vue I18n
• Azure Maps SDK
• SignalR Client
• Power BI Embedded"]
mobile["MOBILE (React Native)
• TypeScript
• SQLite (Offline)
• react-i18next
• React Native Maps
• Azure Notification Hubs SDK"]
end
subgraph gateway["API GATEWAY LAYER"]
api_gateway["Azure API Management • OAuth 2.0 • Rate Limiting • Caching"]
end
subgraph application["APPLICATION LAYER"]
dotnet[".NET 10
ASP.NET Core Web API
SignalR
Entity Framework"]
rules["Rules Engine
Microsoft RulesEngine
FluentValidation"]
ai["AI Services
Azure OpenAI
AI Vision
Bot Service"]
end
subgraph messaging["MESSAGING LAYER"]
service_bus["Service Bus
Events
Commands
Dead Letter"]
signalr["SignalR Service
Real-time
WebSocket"]
notification["Notification Hubs
Push (iOS)
Push (Android)"]
end
subgraph data["DATA LAYER"]
sql["Azure SQL Database
Master Data
Transactional
Audit Logs"]
redis["Redis Cache
Session
API Cache
Reference"]
blob["Blob Storage
Photos
Signatures
Documents"]
end
subgraph integration["INTEGRATION LAYER"]
maddenco["MaddenCo Proxy API"]
reach["REACH Proxy API"]
dayforce["Dayforce Proxy API"]
genesys["Genesys Proxy API"]
geotab["GeoTab API"]
end
subgraph observability["OBSERVABILITY"]
monitoring["App Insights • Log Analytics • Azure Monitor • Power BI • Serilog"]
end
subgraph security["SECURITY"]
sec["Azure AD (Entra ID) • Key Vault • WAF • Network Security Groups"]
end
presentation --> gateway
gateway --> application
application --> messaging
messaging --> data
application --> integration
Quick Reference Table
| Layer |
Component |
Technology |
Version |
| Backend |
Application Framework |
.NET |
10.0 |
| Backend |
Web API |
ASP.NET Core |
10.0 |
| Backend |
ORM |
Entity Framework Core |
10.0 |
| Backend |
Rules Engine |
Microsoft.RulesEngine |
5.x |
| Backend |
Validation |
FluentValidation |
11.x |
| Frontend (Web) |
Framework |
Vue.js |
3.x |
| Frontend (Web) |
Language |
TypeScript |
5.x |
| Frontend (Web) |
Styling |
Tailwind CSS |
3.x |
| Frontend (Web) |
i18n |
Vue I18n |
9.x |
| Frontend (Web) |
Maps |
Azure Maps Web SDK |
Latest |
| Frontend (Web) |
Charts |
Chart.js / ApexCharts |
Latest |
| Mobile |
Framework |
React Native |
0.73+ |
| Mobile |
Language |
TypeScript |
5.x |
| Mobile |
Offline DB |
SQLite (react-native-sqlite-storage) |
Latest |
| Mobile |
i18n |
react-i18next |
Latest |
| Mobile |
Maps |
react-native-maps |
Latest |
| Database |
Primary |
Azure SQL Database |
Latest |
| Database |
Cache |
Azure Cache for Redis |
6.x |
| Storage |
Files |
Azure Blob Storage |
Latest |
| Messaging |
Async |
Azure Service Bus |
Premium |
| Messaging |
Real-time |
Azure SignalR Service |
Latest |
| Messaging |
Push |
Azure Notification Hubs |
Latest |
| AI/ML |
LLM |
Azure OpenAI |
GPT-4o |
| AI/ML |
Vision |
Azure AI Vision |
4.0 |
| AI/ML |
Bot |
Azure Bot Service |
Latest |
| Analytics |
BI |
Power BI Embedded |
Latest |
| Analytics |
APM |
Azure Application Insights |
Latest |
| Security |
Identity |
Azure AD (Entra ID) |
Latest |
| Security |
Secrets |
Azure Key Vault |
Latest |
| Security |
Gateway |
Azure API Management |
Latest |
| Infrastructure |
IaC |
Terraform |
1.x |
| Infrastructure |
CI/CD |
Atlassian Bamboo |
Latest |
| Infrastructure |
Source Control |
Atlassian Bitbucket |
Latest |
Data Storage & Management
Primary Database: Azure SQL Database
| Aspect |
Configuration |
Notes |
| Service Tier |
Business Critical |
Geo-redundant, zone-redundant |
| Max Size |
4 TB |
Auto-grow enabled |
| Compute |
8 vCores |
Auto-scale based on load |
| Backup |
Point-in-time (35 days) |
Geo-redundant backup storage |
| Encryption |
TDE (AES-256) |
Always encrypted for PII |
Data Categories
| Category |
Description |
Retention |
Encryption |
| Master Data |
Customers, Stores, Technicians, Parts |
Indefinite |
Standard TDE |
| Transactional Data |
Work Orders, Invoices, Payments |
7 years |
Standard TDE |
| Audit Data |
User actions, system events |
7 years |
Standard TDE |
| Configuration |
System settings, rules, templates |
Indefinite |
Standard TDE |
| PII |
Contact info, addresses, phone numbers |
Per policy |
Always Encrypted |
Caching: Azure Cache for Redis
| Aspect |
Configuration |
Use Case |
| Tier |
Premium P1 |
Production workload |
| Size |
6 GB |
Session + API cache |
| Clustering |
Enabled |
Horizontal scaling |
| Geo-Replication |
Enabled |
DR support |
Cache Patterns
public interface ICacheService
{
Task<T?> GetAsync<T>(string key);
Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
Task RemoveAsync(string key);
Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null);
}
// Cache key patterns
public static class CacheKeys
{
public static string Customer(string customerId) => $"customer:{customerId}";
public static string Store(string storeId) => $"store:{storeId}";
public static string Technician(string techId) => $"tech:{techId}";
public static string WorkOrder(string workOrderId) => $"wo:{workOrderId}";
public static string ReferenceData(string type) => $"ref:{type}";
}
Master Data Sync & Reconciliation
| Sync Type |
Technology |
Frequency |
Source → Target |
| Customer Master |
Azure Data Factory |
Every 15 min |
MaddenCo → Pomp's |
| Technician Data |
Azure Data Factory |
Every 30 min |
Dayforce → Pomp's |
| Parts/Inventory |
Azure Service Bus |
Real-time |
MaddenCo → Pomp's |
| Work Orders |
Azure Service Bus |
Real-time (events) |
Bidirectional |
| Billing Data |
Azure Data Factory |
Hourly |
MaddenCo → Pomp's |
Reconciliation Pattern
public class ReconciliationService
{
public async Task<ReconciliationResult> ReconcileAsync(
string dataType,
DateTimeOffset fromDate,
DateTimeOffset toDate)
{
// 1. Fetch records from source system
var sourceRecords = await _sourceProxy.GetRecordsAsync(dataType, fromDate, toDate);
// 2. Fetch records from local database
var localRecords = await _repository.GetRecordsAsync(dataType, fromDate, toDate);
// 3. Compare and identify discrepancies
var discrepancies = CompareRecords(sourceRecords, localRecords);
// 4. Apply conflict resolution based on Source of Truth
var resolutions = await ResolveConflictsAsync(discrepancies, dataType);
// 5. Log reconciliation results
await _auditService.LogReconciliationAsync(dataType, resolutions);
return new ReconciliationResult(resolutions);
}
}
Documentation Reference: INTEGRATION_ARCHITECTURE.md
Business Logic & Rules Engine
Microsoft RulesEngine
Package: Microsoft.RulesEngine (NuGet)
The Rules Engine enables externalized, configurable business rules that can be modified without code deployment.
Use Cases
| Rule Category |
Examples |
Configuration |
| Customer SLA |
Response time by customer tier, escalation thresholds |
JSON/Database |
| Store Assignments |
Default technician assignments, service area mapping |
JSON/Database |
| Work Order Routing |
Priority calculation, auto-assignment rules |
JSON/Database |
| Documentation Requirements |
Required photos by service type, signature requirements |
JSON/Database |
| Billing Rules |
Pricing tiers, discount calculations, tax rules |
JSON/Database |
Implementation Pattern
// Rule definition (stored in database/JSON)
{
"WorkflowName": "CustomerSLAWorkflow",
"Rules": [
{
"RuleName": "PlatinumCustomerSLA",
"Expression": "customer.Tier == \"Platinum\" AND workOrder.Priority == \"Emergency\"",
"SuccessEvent": "SLA_1_HOUR",
"Actions": {
"OnSuccess": {
"Name": "SetSLAMinutes",
"Context": { "Minutes": 60 }
}
}
},
{
"RuleName": "StandardCustomerSLA",
"Expression": "customer.Tier == \"Standard\"",
"SuccessEvent": "SLA_4_HOURS",
"Actions": {
"OnSuccess": {
"Name": "SetSLAMinutes",
"Context": { "Minutes": 240 }
}
}
}
]
}
// C# Usage
public class SLAService
{
private readonly RulesEngine.RulesEngine _rulesEngine;
public async Task<SLAResult> CalculateSLAAsync(Customer customer, WorkOrder workOrder)
{
var inputs = new[] {
new RuleParameter("customer", customer),
new RuleParameter("workOrder", workOrder)
};
var results = await _rulesEngine.ExecuteAllRulesAsync("CustomerSLAWorkflow", inputs);
return MapToSLAResult(results);
}
}
Rule Configuration Storage
CREATE TABLE BusinessRules (
Id UNIQUEIDENTIFIER PRIMARY KEY,
WorkflowName NVARCHAR(100) NOT NULL,
RuleDefinition NVARCHAR(MAX) NOT NULL, -- JSON
Version INT NOT NULL,
IsActive BIT NOT NULL DEFAULT 1,
EffectiveFrom DATETIME2 NOT NULL,
EffectiveTo DATETIME2 NULL,
CreatedBy NVARCHAR(100) NOT NULL,
CreatedAt DATETIME2 NOT NULL,
ModifiedBy NVARCHAR(100) NULL,
ModifiedAt DATETIME2 NULL
);
FluentValidation
Package: FluentValidation (NuGet)
Used for input validation across all API endpoints.
public class WorkOrderValidator : AbstractValidator<CreateWorkOrderRequest>
{
public WorkOrderValidator()
{
RuleFor(x => x.CustomerId)
.NotEmpty().WithMessage("Customer is required");
RuleFor(x => x.ServiceType)
.NotEmpty().WithMessage("Service type is required")
.Must(BeValidServiceType).WithMessage("Invalid service type");
RuleFor(x => x.Priority)
.IsInEnum().WithMessage("Invalid priority level");
RuleFor(x => x.ScheduledDate)
.GreaterThan(DateTime.UtcNow).WithMessage("Scheduled date must be in the future");
RuleFor(x => x.ContactPhone)
.Matches(@"^\+?[1-9]\d{1,14}$").WithMessage("Invalid phone number format");
}
}
Store Assignments
public class StoreAssignmentService
{
private readonly RulesEngine.RulesEngine _rulesEngine;
private readonly IStoreRepository _storeRepository;
public async Task<Store> DetermineStoreAsync(WorkOrder workOrder)
{
// 1. Get customer's primary store assignment
var primaryStore = await _storeRepository.GetPrimaryStoreForCustomerAsync(workOrder.CustomerId);
// 2. Check store capacity and availability
var storeCapacity = await _storeRepository.GetCurrentCapacityAsync(primaryStore.Id);
// 3. Execute assignment rules
var inputs = new[] {
new RuleParameter("workOrder", workOrder),
new RuleParameter("primaryStore", primaryStore),
new RuleParameter("capacity", storeCapacity)
};
var results = await _rulesEngine.ExecuteAllRulesAsync("StoreAssignmentWorkflow", inputs);
return MapToAssignedStore(results);
}
}
Tech Assignment / On-Call
Integration with Dayforce for technician scheduling and on-call rotations.
public class TechnicianAssignmentService
{
private readonly IDayforceProxy _dayforceProxy;
private readonly ITechnicianRepository _techRepository;
private readonly RulesEngine.RulesEngine _rulesEngine;
public async Task<TechnicianAssignment> AssignTechnicianAsync(WorkOrder workOrder)
{
// 1. Get available technicians from Dayforce schedule
var availableTechs = await _dayforceProxy.GetAvailableTechniciansAsync(
workOrder.StoreId,
workOrder.ScheduledDate);
// 2. Filter by skills/certifications
var qualifiedTechs = availableTechs
.Where(t => t.Certifications.Contains(workOrder.RequiredCertification))
.ToList();
// 3. Check on-call status if after hours
if (IsAfterHours(workOrder.ScheduledDate))
{
var onCallTech = await _dayforceProxy.GetOnCallTechnicianAsync(
workOrder.StoreId,
workOrder.ScheduledDate);
qualifiedTechs.Insert(0, onCallTech);
}
// 4. Apply assignment rules (proximity, workload, skills match)
var inputs = new[] {
new RuleParameter("workOrder", workOrder),
new RuleParameter("technicians", qualifiedTechs)
};
var results = await _rulesEngine.ExecuteAllRulesAsync("TechAssignmentWorkflow", inputs);
return MapToAssignment(results);
}
}
Documentation Reference: INTEGRATION_ARCHITECTURE.md, DISPATCH_ALERTS.md
Business Process Engine & AI
Workflow Management
Azure Service Bus provides the foundation for event-driven workflow orchestration.
Intake Workflow
flowchart LR
reach["REACH
Portal"]
service_bus["Service
Bus"]
intake["Intake
Processor"]
assignment["Assignment
Engine"]
events["Events:
• NewOrder
• Update
• Cancel"]
actions_intake["Actions:
• Validate
• Enrich
• Store"]
actions_assign["Actions:
• SLA Calc
• Assign
• Notify"]
reach --> service_bus
service_bus --> intake
intake --> assignment
service_bus --> events
intake --> actions_intake
assignment --> actions_assign
Assignment Workflow
public class AssignmentOrchestrator
{
private readonly IServiceBus _serviceBus;
private readonly ITechnicianAssignmentService _assignmentService;
private readonly INotificationService _notificationService;
public async Task ProcessAssignmentAsync(WorkOrder workOrder)
{
// 1. Calculate SLA based on customer and priority
var sla = await _slaService.CalculateSLAAsync(workOrder);
// 2. Find best technician match
var assignment = await _assignmentService.AssignTechnicianAsync(workOrder);
// 3. Update work order
workOrder.AssignedTechnicianId = assignment.TechnicianId;
workOrder.SLADeadline = sla.Deadline;
await _workOrderRepository.UpdateAsync(workOrder);
// 4. Publish events
await _serviceBus.PublishAsync(new WorkOrderAssignedEvent(workOrder, assignment));
// 5. Send notifications
await _notificationService.NotifyTechnicianAsync(assignment);
await _notificationService.NotifyCustomerAsync(workOrder, assignment.ETA);
}
}
Azure OpenAI Integration
Model: GPT-4o (via Azure OpenAI Service)
| Use Case |
Description |
Input |
Output |
| Smart Assignment |
Suggest optimal technician based on skills, proximity, workload |
Work order details, tech list |
Ranked tech recommendations |
| Customer Communication |
Generate professional customer notifications |
Context, template |
Personalized message |
| Issue Triage |
Analyze customer description to determine service type |
Customer description |
Service type, priority suggestion |
| Knowledge Base |
Answer technician questions about procedures |
Question + context |
Procedure guidance |
public class AzureOpenAIService
{
private readonly OpenAIClient _client;
public async Task<TechnicianRecommendation[]> GetSmartAssignmentAsync(
WorkOrder workOrder,
IEnumerable<Technician> availableTechnicians)
{
var prompt = BuildAssignmentPrompt(workOrder, availableTechnicians);
var response = await _client.GetChatCompletionsAsync(
deploymentName: "gpt-4o",
new ChatCompletionsOptions
{
Messages = { new ChatRequestUserMessage(prompt) },
Temperature = 0.3f,
MaxTokens = 500
});
return ParseRecommendations(response);
}
}
Azure AI Vision (Photo Analysis)
Service: Azure AI Vision 4.0
| Use Case |
Description |
Trigger |
| Tire Condition Analysis |
Assess tread depth, damage, wear patterns |
Photo upload |
| Damage Assessment |
Identify and categorize vehicle/equipment damage |
Before/after photos |
| Parts Verification |
Verify correct parts installed |
Completion photos |
| Quality Control |
Validate work quality from photos |
Completion photos |
public class PhotoAnalysisService
{
private readonly ImageAnalysisClient _visionClient;
private readonly OpenAIClient _openAIClient;
public async Task<TireConditionReport> AnalyzeTirePhotoAsync(Stream photoStream)
{
// 1. Extract visual features using Azure AI Vision
var analysisResult = await _visionClient.AnalyzeAsync(
BinaryData.FromStream(photoStream),
VisualFeatures.Tags | VisualFeatures.Objects | VisualFeatures.Caption);
// 2. Use GPT-4o Vision for detailed analysis
var visionPrompt = BuildTireAnalysisPrompt(analysisResult);
var openAIResponse = await _openAIClient.GetChatCompletionsAsync(
deploymentName: "gpt-4o",
new ChatCompletionsOptions
{
Messages = {
new ChatRequestUserMessage(
new ChatMessageImageContentItem(photoUri),
new ChatMessageTextContentItem(visionPrompt))
},
Temperature = 0.2f
});
return ParseTireConditionReport(openAIResponse);
}
}
Azure Bot Service
Purpose: Conversational interface for customers and internal users via Genesys Cloud CCaaS integration.
| Bot Capability |
Description |
Channel |
| Status Inquiry |
"Where is my technician?" |
SMS, Voice IVR |
| Appointment Scheduling |
Schedule/reschedule service |
Web Chat, SMS |
| FAQ/Knowledge Base |
Answer common questions |
All channels |
| Escalation to Agent |
Transfer to human agent |
All channels |
public class DispatchBot : ActivityHandler
{
private readonly IWorkOrderService _workOrderService;
private readonly OpenAIClient _openAIClient;
protected override async Task OnMessageActivityAsync(
ITurnContext<IMessageActivity> turnContext,
CancellationToken cancellationToken)
{
var userMessage = turnContext.Activity.Text;
// Determine intent using Azure OpenAI
var intent = await DetermineIntentAsync(userMessage);
switch (intent)
{
case "StatusInquiry":
var status = await _workOrderService.GetLatestStatusForCustomerAsync(
turnContext.Activity.From.Id);
await turnContext.SendActivityAsync(BuildStatusResponse(status));
break;
case "Schedule":
await StartSchedulingDialogAsync(turnContext);
break;
case "EscalateToAgent":
await TransferToGenesysAgentAsync(turnContext);
break;
default:
await HandleGeneralInquiryAsync(turnContext, userMessage);
break;
}
}
}
Documentation Reference: INTEGRATION_ARCHITECTURE.md
User Interfaces
Web Application (Vue.js 3)
| View |
Description |
Key Components |
| Customer View |
Customer relationship management |
Profile, History, Billing, Communication |
| Store View |
Store-level operations |
Dashboard, Technicians, Work Orders, Alerts |
| Billing View |
Invoice and payment management |
Invoice List, Detail, MaddenCo Sync |
| Dispatch View |
Central dispatch command center |
Map, Queue, Assignments, Real-time Feed |
| Configuration View |
System administration |
Rules, Templates, Users, Settings |
Vue.js Project Structure
src/
├── assets/ # Static assets
├── components/ # Shared components
│ ├── common/ # Buttons, inputs, modals
│ ├── layout/ # Header, sidebar, footer
│ ├── data/ # Tables, grids, charts
│ └── maps/ # Azure Maps components
├── composables/ # Shared composition functions
│ ├── useAuth.ts
│ ├── useApi.ts
│ ├── useRealtime.ts
│ └── useI18n.ts
├── views/ # Page-level components
│ ├── customer/
│ ├── store/
│ ├── billing/
│ ├── dispatch/
│ └── configuration/
├── stores/ # Pinia stores
│ ├── auth.ts
│ ├── workOrders.ts
│ └── notifications.ts
├── services/ # API services
├── types/ # TypeScript types
├── i18n/ # Internationalization
│ ├── en.json
│ └── es.json
└── router/ # Vue Router config
Key Dependencies
{
"dependencies": {
"vue": "^3.4.0",
"vue-router": "^4.2.0",
"pinia": "^2.1.0",
"vue-i18n": "^9.8.0",
"azure-maps-control": "^3.0.0",
"@microsoft/signalr": "^8.0.0",
"powerbi-client-vue-js": "^1.0.0",
"chart.js": "^4.4.0",
"vue-chartjs": "^5.3.0",
"@vueuse/core": "^10.7.0",
"axios": "^1.6.0"
}
}
Mobile Application (React Native)
| Feature |
Description |
Offline Support |
| Work Order List |
Today's assignments |
✅ SQLite cache |
| Work Order Detail |
Full service details |
✅ SQLite cache |
| Navigation |
Turn-by-turn directions |
✅ Cached routes |
| Photo Capture |
Before/after photos |
✅ Local queue |
| Signature Capture |
Customer signatures |
✅ Local storage |
| Time Tracking |
Clock in/out |
✅ Local queue |
| Parts Scanning |
Barcode scanning |
✅ Offline lookup |
React Native Project Structure
src/
├── components/ # Shared components
│ ├── common/
│ ├── forms/
│ └── maps/
├── screens/ # Screen components
│ ├── WorkOrderListScreen.tsx
│ ├── WorkOrderDetailScreen.tsx
│ ├── NavigationScreen.tsx
│ ├── CameraScreen.tsx
│ ├── SignatureScreen.tsx
│ └── ProfileScreen.tsx
├── services/ # Business logic
│ ├── api/
│ ├── sync/
│ ├── offline/
│ └── location/
├── stores/ # State management
├── hooks/ # Custom hooks
├── i18n/ # Translations
│ ├── en.json
│ └── es.json
├── database/ # SQLite schema/queries
└── types/ # TypeScript types
Key Dependencies
{
"dependencies": {
"react-native": "0.73.x",
"react-native-sqlite-storage": "^6.0.0",
"react-native-maps": "^1.8.0",
"react-native-camera": "^4.2.0",
"react-native-signature-capture": "^0.4.0",
"react-i18next": "^13.5.0",
"i18next": "^23.7.0",
"@react-native-async-storage/async-storage": "^1.21.0",
"@notifee/react-native": "^7.8.0",
"react-native-vision-camera": "^3.6.0"
}
}
Configuration View
Administrative interface for system configuration.
| Section |
Features |
Access |
| Business Rules |
View/edit rule definitions, versioning |
System Admin |
| Form Templates |
Create/edit dynamic forms |
System Admin, Store Manager |
| User Management |
User accounts, role assignments |
System Admin |
| Store Configuration |
Store settings, service areas |
System Admin, Store Manager |
| SLA Configuration |
Customer tier SLAs, escalation rules |
System Admin |
| Notification Templates |
SMS/Email templates |
System Admin |
| Integration Settings |
API configurations, sync schedules |
System Admin |
Exception Management
Integrated into Dispatch View with dedicated exception queue.
| Exception Type |
Handling |
Notification |
| SLA Breach |
Auto-escalate, require acknowledgment |
Alert + Email |
| Sync Failure |
Retry queue, manual intervention |
Dashboard alert |
| Assignment Failure |
Manual assignment required |
Dispatcher alert |
| Payment Failure |
Flag for review |
Billing alert |
| Integration Error |
Circuit breaker, fallback |
Ops team alert |
Dynamic form system for configurable work order documentation.
| Feature |
Technology |
Storage |
| Form Designer |
Vue.js + FormKit |
Azure SQL |
| Field Types |
Text, Number, Date, Photo, Signature, Dropdown, Checkbox |
JSON schema |
| Conditional Logic |
Show/hide based on values |
Rules Engine |
| Validation |
Required, patterns, ranges |
FluentValidation |
| Offline Forms |
React Native + SQLite |
Local DB |
// Form schema example
interface FormTemplate {
id: string;
name: string;
serviceTypes: string[]; // Which service types use this form
sections: FormSection[];
validationRules: ValidationRule[];
}
interface FormSection {
id: string;
title: string;
fields: FormField[];
conditions?: ConditionalRule[];
}
interface FormField {
id: string;
type: 'text' | 'number' | 'date' | 'photo' | 'signature' | 'select' | 'checkbox';
label: string;
required: boolean;
options?: SelectOption[]; // For dropdowns
validation?: FieldValidation;
}
Documentation Reference: VIEWS.md
Notifications
Notification Channels
| Channel |
Technology |
Use Cases |
Delivery |
| SMS |
Genesys Cloud (Pomp's Proxy) |
Customer ETA, tech alerts |
Real-time |
| Voice Call |
Genesys Cloud (Pomp's Proxy) |
Urgent escalations, SLA breaches |
Real-time |
| Email |
Azure Communication Services |
Invoices, summaries, reports |
Batched/Real-time |
| Web Push |
Azure SignalR Service |
Dispatcher alerts, status updates |
Real-time |
| Mobile Push |
Azure Notification Hubs |
Tech assignments, schedule changes |
Real-time |
| In-App |
SignalR + Vue/React |
All real-time updates |
Real-time |
Azure Communication Services (Email)
public class EmailNotificationService
{
private readonly EmailClient _emailClient;
private readonly ITemplateService _templateService;
public async Task SendInvoiceEmailAsync(Invoice invoice, Customer customer)
{
var template = await _templateService.GetTemplateAsync("InvoiceEmail", customer.PreferredLanguage);
var content = await _templateService.RenderAsync(template, new { invoice, customer });
var message = new EmailMessage(
senderAddress: "dispatch@pomps.com",
recipientAddress: customer.Email,
content: new EmailContent(template.Subject) { Html = content });
await _emailClient.SendAsync(WaitUntil.Started, message);
}
}
Azure Notification Hubs (Mobile Push)
public class PushNotificationService
{
private readonly NotificationHubClient _hubClient;
public async Task SendTechnicianAssignmentAsync(Technician tech, WorkOrder workOrder)
{
var notification = new FcmNotification(JsonSerializer.Serialize(new
{
notification = new
{
title = "New Assignment",
body = $"Work Order {workOrder.Number} assigned to you"
},
data = new
{
workOrderId = workOrder.Id,
action = "VIEW_WORK_ORDER"
}
}));
await _hubClient.SendNotificationAsync(notification, $"tech:{tech.Id}");
}
}
Notification Template Engine
public interface INotificationTemplate
{
string Id { get; }
string Channel { get; } // SMS, Email, Push
string Language { get; } // en, es
string Subject { get; }
string BodyTemplate { get; }
Dictionary<string, string> Variables { get; }
}
// Example template
{
"id": "tech_assignment_sms",
"channel": "SMS",
"language": "en",
"bodyTemplate": "New assignment: {{workOrderNumber}} at {{customerName}}, {{address}}. Scheduled: {{scheduledTime}}. Reply ACCEPT or DECLINE."
}
Documentation Reference: DISPATCH_ALERTS.md, INTEGRATION_ARCHITECTURE.md
Audit & Logging
Logging Framework: Serilog
| Log Type |
Destination |
Retention |
Format |
| Application Logs |
Azure Log Analytics |
90 days |
JSON structured |
| Audit Logs |
Azure Log Analytics + SQL |
7 years |
JSON structured |
| Security Logs |
Azure Security Center |
7 years |
Native format |
| Performance Metrics |
Application Insights |
90 days |
Native format |
Audit Trail Requirements
| Event Category |
Events Logged |
Data Captured |
| User Actions |
Login, logout, profile changes |
User, timestamp, IP, action details |
| Work Orders |
Create, update, assign, complete |
User, before/after values, timestamp |
| Billing |
Invoice create, payment, adjustments |
User, amounts, approval chain |
| Configuration |
Rule changes, template edits |
User, before/after, effective date |
| Integration |
API calls, sync operations |
Request/response, duration, status |
public class AuditService
{
private readonly ILogger<AuditService> _logger;
private readonly IAuditRepository _repository;
public async Task LogAsync<T>(
AuditAction action,
string entityType,
string entityId,
T? oldValue,
T? newValue,
string userId)
{
var auditEntry = new AuditEntry
{
Id = Guid.NewGuid(),
Action = action,
EntityType = entityType,
EntityId = entityId,
OldValue = oldValue != null ? JsonSerializer.Serialize(oldValue) : null,
NewValue = newValue != null ? JsonSerializer.Serialize(newValue) : null,
UserId = userId,
Timestamp = DateTimeOffset.UtcNow,
CorrelationId = Activity.Current?.Id
};
await _repository.InsertAsync(auditEntry);
_logger.LogInformation(
"Audit: {Action} on {EntityType}/{EntityId} by {UserId}",
action, entityType, entityId, userId);
}
}
Documentation Reference: LOGGING.md, MONITORING.md
Analytics & Reporting
Power BI Embedded
| Report Type |
Description |
Refresh Rate |
| Executive Dashboard |
KPIs, trends, regional performance |
15 minutes |
| Store Performance |
Store-level metrics, technician productivity |
15 minutes |
| Technician Productivity |
Completion rates, time tracking, quality |
15 minutes |
| SLA Compliance |
SLA breach analysis, trends |
Real-time |
| Financial Reports |
Revenue, AR aging, profitability |
Hourly |
| Customer Analytics |
Customer satisfaction, service patterns |
Daily |
Dashboard Integration
// Vue.js Power BI Embedded component
<template>
<div class="report-container">
<PowerBIReportEmbed
:embed-config="embedConfig"
:css-class-name="'report-frame'"
@report-loaded="handleReportLoaded"
@error="handleError"
/>
</div>
</template>
<script setup lang="ts">
import { PowerBIReportEmbed } from 'powerbi-client-vue-js';
import { ref, onMounted } from 'vue';
const embedConfig = ref({
type: 'report',
embedUrl: '',
accessToken: '',
tokenType: 1, // Embed token
settings: {
panes: { filters: { visible: false } },
background: 1
}
});
onMounted(async () => {
const token = await reportService.getEmbedToken('store-performance');
embedConfig.value.embedUrl = token.embedUrl;
embedConfig.value.accessToken = token.token;
});
</script>
Canned Reports
| Report |
Format |
Schedule |
Distribution |
| Daily Operations Summary |
PDF |
Daily 6 AM |
Email to Store Managers |
| Weekly Performance Report |
PDF/Excel |
Monday 7 AM |
Email to Regional Managers |
| Monthly Financial Report |
PDF/Excel |
1st of month |
Email to Finance Team |
| SLA Breach Report |
Excel |
Real-time |
On-demand |
| Technician Timesheet |
PDF |
Weekly |
Email to Payroll |
Custom Charts (Chart.js)
For real-time dashboards where Power BI latency is not acceptable:
// Real-time dispatch dashboard chart
<template>
<Line :data="chartData" :options="chartOptions" />
</template>
<script setup lang="ts">
import { Line } from 'vue-chartjs';
import { useRealTimeData } from '@/composables/useRealTimeData';
const { data: workOrderData } = useRealTimeData('work-order-stats');
const chartData = computed(() => ({
labels: workOrderData.value.timestamps,
datasets: [
{
label: 'Active Work Orders',
data: workOrderData.value.active,
borderColor: '#3B82F6'
},
{
label: 'Completed Today',
data: workOrderData.value.completed,
borderColor: '#10B981'
}
]
}));
</script>
Documentation Reference: MONITORING.md
Devices & Internationalization
Supported Devices
| Platform |
Minimum Version |
Target Version |
Screen Sizes |
| iOS |
14.0 |
17.0 |
iPhone 8+ |
| Android |
10 (API 29) |
14 (API 34) |
5" - 7" |
| Web Desktop |
Chrome 90+, Edge 90+, Safari 15+ |
Latest |
1280px+ |
| Web Tablet |
Chrome 90+, Edge 90+, Safari 15+ |
Latest |
768px - 1279px |
Device Capabilities
| Capability |
Technology |
Fallback |
| Camera |
React Native Vision Camera |
Device camera app |
| GPS Location |
React Native Geolocation |
Manual address entry |
| Barcode Scanning |
ML Kit Barcode Scanner |
Manual entry |
| Signature Capture |
react-native-signature-capture |
Photo of signature |
| Offline Storage |
SQLite |
Graceful degradation |
| Push Notifications |
Azure Notification Hubs |
In-app polling |
Internationalization (i18n)
| Language |
Code |
Coverage |
Status |
| English (US) |
en-US |
100% |
Primary |
| Spanish (US) |
es-US |
100% |
Required |
Web (Vue I18n)
// i18n/index.ts
import { createI18n } from 'vue-i18n';
import en from './locales/en.json';
import es from './locales/es.json';
export const i18n = createI18n({
legacy: false,
locale: 'en',
fallbackLocale: 'en',
messages: { en, es }
});
// Usage in component
<template>
<h1>{{ t('workOrder.title') }}</h1>
<p>{{ t('workOrder.assignedTo', { name: techName }) }}</p>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
Mobile (react-i18next)
// i18n/index.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import en from './locales/en.json';
import es from './locales/es.json';
i18n.use(initReactI18next).init({
resources: { en: { translation: en }, es: { translation: es } },
lng: 'en',
fallbackLng: 'en',
interpolation: { escapeValue: false }
});
// Usage in component
import { useTranslation } from 'react-i18next';
function WorkOrderCard({ workOrder }) {
const { t } = useTranslation();
return (
<View>
<Text>{t('workOrder.title')}</Text>
<Text>{t('workOrder.status', { status: workOrder.status })}</Text>
</View>
);
}
Translation File Structure
// en.json
{
"common": {
"save": "Save",
"cancel": "Cancel",
"submit": "Submit",
"loading": "Loading..."
},
"workOrder": {
"title": "Work Order",
"number": "Work Order #{{number}}",
"status": "Status: {{status}}",
"assignedTo": "Assigned to {{name}}",
"priority": {
"emergency": "Emergency",
"high": "High",
"standard": "Standard",
"low": "Low"
}
},
"notifications": {
"assignmentReceived": "New work order assigned",
"etaUpdate": "Technician arriving in {{minutes}} minutes"
}
}
// es.json
{
"common": {
"save": "Guardar",
"cancel": "Cancelar",
"submit": "Enviar",
"loading": "Cargando..."
},
"workOrder": {
"title": "Orden de Trabajo",
"number": "Orden de Trabajo #{{number}}",
"status": "Estado: {{status}}",
"assignedTo": "Asignado a {{name}}",
"priority": {
"emergency": "Emergencia",
"high": "Alta",
"standard": "Estándar",
"low": "Baja"
}
},
"notifications": {
"assignmentReceived": "Nueva orden de trabajo asignada",
"etaUpdate": "Técnico llegando en {{minutes}} minutos"
}
}
File Management & Storage
Azure Blob Storage
| Container |
Purpose |
Access |
Retention |
| work-order-photos |
Before/after service photos |
Private |
7 years |
| signatures |
Customer signature images |
Private |
7 years |
| invoices |
Generated invoice PDFs |
Private |
7 years |
| documents |
Attachments, reports |
Private |
Per policy |
| templates |
Form templates, email templates |
Private |
Indefinite |
| public-assets |
UI assets, icons |
Public (CDN) |
Indefinite |
Storage Architecture
flowchart TB
mobile["Mobile App
• Capture
• Queue
• Upload"]
api["API Service
• Validate
• Process
• Metadata"]
blob["Blob Storage
• Store
• CDN
• Lifecycle"]
ai["AI Vision
(Analysis)"]
workorder["Work Order
(Metadata)"]
mobile --> api
api --> blob
api --> ai
blob --> ai
ai --> workorder
mobile --> workorder
Photo Upload Service
public class PhotoStorageService
{
private readonly BlobServiceClient _blobClient;
private readonly IPhotoAnalysisService _analysisService;
public async Task<PhotoUploadResult> UploadWorkOrderPhotoAsync(
string workOrderId,
Stream photoStream,
PhotoType photoType,
string mimeType)
{
// 1. Generate blob name
var blobName = $"{workOrderId}/{photoType}/{Guid.NewGuid()}.jpg";
// 2. Upload to blob storage
var container = _blobClient.GetBlobContainerClient("work-order-photos");
var blob = container.GetBlobClient(blobName);
await blob.UploadAsync(photoStream, new BlobHttpHeaders
{
ContentType = mimeType
});
// 3. Trigger AI analysis (async)
if (photoType == PhotoType.TireCondition)
{
await _analysisService.QueueForAnalysisAsync(blobName);
}
// 4. Return result
return new PhotoUploadResult
{
BlobName = blobName,
Url = blob.Uri.ToString(),
UploadedAt = DateTimeOffset.UtcNow
};
}
}
Azure CDN Integration
For static assets and cached photo thumbnails:
| Origin |
CDN Profile |
Caching |
| public-assets |
Azure CDN Standard |
7 days |
| Thumbnails |
Azure CDN Standard |
24 hours |
Integration Layer
Azure API Management
| Policy |
Purpose |
Configuration |
| Rate Limiting |
Prevent abuse |
1000 req/min per subscription |
| Caching |
Reduce backend load |
5 min for reference data |
| Authentication |
Validate JWT tokens |
Azure AD validation |
| Transformation |
Request/response shaping |
Header manipulation |
| CORS |
Cross-origin requests |
Configured origins |
API Design Standards
# OpenAPI specification pattern
openapi: 3.0.3
info:
title: Pomp's Dispatch API
version: 1.0.0
paths:
/api/v1/work-orders:
get:
summary: List work orders
parameters:
- name: storeId
in: query
schema:
type: string
- name: status
in: query
schema:
type: string
enum: [pending, assigned, in-progress, completed]
- name: page
in: query
schema:
type: integer
default: 1
- name: pageSize
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
'200':
description: Paginated list of work orders
content:
application/json:
schema:
$ref: '#/components/schemas/WorkOrderListResponse'
Asynchronous Messaging (Azure Service Bus)
| Queue/Topic |
Purpose |
Consumer |
| work-order-events |
Work order lifecycle events |
Multiple subscribers |
| notification-queue |
Outbound notifications |
Notification service |
| sync-commands |
Sync operations |
Sync service |
| photo-analysis |
Photo processing requests |
AI Vision service |
| integration-dlq |
Dead letter queue |
Manual review |
External Integration Summary
Documentation Reference: INTEGRATION_ARCHITECTURE.md, INTEGRATION_PATTERNS.md
Maps & Location Services
Azure Maps (Web)
| Feature |
Use Case |
API |
| Technician Map |
Real-time technician locations |
Render + GeoTab data |
| Route Display |
Show technician route to customer |
Route API |
| Geofencing |
Store service areas |
Spatial API |
| Address Validation |
Customer address verification |
Search API |
// Vue.js Azure Maps component
import * as atlas from 'azure-maps-control';
export function useAzureMaps(containerId: string) {
const map = ref<atlas.Map | null>(null);
const initializeMap = async () => {
map.value = new atlas.Map(containerId, {
authOptions: {
authType: atlas.AuthenticationType.subscriptionKey,
subscriptionKey: import.meta.env.VITE_AZURE_MAPS_KEY
},
center: [-89.6501, 39.7817], // Central Illinois
zoom: 10
});
await map.value.ready;
};
const addTechnicianMarker = (tech: TechnicianLocation) => {
const marker = new atlas.HtmlMarker({
position: [tech.longitude, tech.latitude],
htmlContent: `<div class="tech-marker ${tech.status}">${tech.initials}</div>`
});
map.value?.markers.add(marker);
};
return { map, initializeMap, addTechnicianMarker };
}
React Native Maps (Mobile)
| Feature |
Use Case |
Integration |
| Navigation |
Turn-by-turn to customer |
Google/Apple Maps deep link |
| Current Location |
Technician position |
Device GPS |
| Customer Location |
Destination marker |
Geocoded address |
| Traffic |
Real-time traffic layer |
Google/Apple Maps |
// React Native Maps component
import MapView, { Marker, PROVIDER_GOOGLE } from 'react-native-maps';
function TechnicianMapScreen() {
const { currentLocation } = useLocation();
const { workOrder } = useCurrentWorkOrder();
const openNavigation = () => {
const destination = `${workOrder.latitude},${workOrder.longitude}`;
const url = Platform.select({
ios: `maps://app?daddr=${destination}`,
android: `google.navigation:q=${destination}`
});
Linking.openURL(url);
};
return (
<View style={styles.container}>
<MapView
provider={PROVIDER_GOOGLE}
style={styles.map}
region={{
latitude: currentLocation.latitude,
longitude: currentLocation.longitude,
latitudeDelta: 0.05,
longitudeDelta: 0.05
}}
>
<Marker
coordinate={currentLocation}
title="You"
pinColor="blue"
/>
<Marker
coordinate={{
latitude: workOrder.latitude,
longitude: workOrder.longitude
}}
title={workOrder.customerName}
pinColor="red"
/>
</MapView>
<Button title="Navigate" onPress={openNavigation} />
</View>
);
}
GeoTab Integration
Real-time vehicle tracking for dispatch view.
public class GeoTabLocationService
{
private readonly GeoTabClient _geoTabClient;
private readonly IHubContext<DispatchHub> _hubContext;
public async Task StartLocationStreamAsync(CancellationToken cancellationToken)
{
await foreach (var location in _geoTabClient.StreamLocationsAsync(cancellationToken))
{
// Update local cache
await _cacheService.SetAsync(
$"tech-location:{location.VehicleId}",
location,
TimeSpan.FromSeconds(60));
// Broadcast to dispatch clients
await _hubContext.Clients
.Group("dispatchers")
.SendAsync("TechnicianLocationUpdate", location);
}
}
}
Security
Identity & Access Management
| Component |
Technology |
Configuration |
| Identity Provider |
Azure AD (Entra ID) |
OIDC, SAML 2.0 |
| Single Sign-On |
Azure AD |
Configured applications |
| Multi-Factor Auth |
Azure AD Conditional Access |
Required for admin roles |
| Role Management |
Custom RBAC + Azure AD Groups |
Claims-based authorization |
RBAC Implementation
// Permission-based authorization
[Authorize(Policy = "CanManageWorkOrders")]
[HttpPost("work-orders")]
public async Task<ActionResult<WorkOrder>> CreateWorkOrder(CreateWorkOrderRequest request)
{
// Implementation
}
// Policy configuration
services.AddAuthorization(options =>
{
options.AddPolicy("CanManageWorkOrders", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim("permission", "work_orders.create") ||
context.User.IsInRole("Admin")));
options.AddPolicy("CanViewBilling", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim("permission", "billing.view") ||
context.User.IsInRole("Finance") ||
context.User.IsInRole("Admin")));
});
Secrets Management
| Secret Type |
Storage |
Rotation |
| API Keys |
Azure Key Vault |
90 days |
| Connection Strings |
Azure Key Vault |
Manual |
| Certificates |
Azure Key Vault |
Auto-renewal |
| Encryption Keys |
Azure Key Vault HSM |
Annual |
Documentation Reference: SECURITY.md, ROLES.md
Disaster Recovery & High Availability
High Availability Architecture
flowchart TB
traffic_mgr["Azure Traffic Manager
(Global LB)"]
subgraph primary["PRIMARY REGION (Central US)"]
app1["• App Service
• Azure SQL (Primary)
• Redis Cache
• Service Bus
• Blob Storage"]
end
subgraph secondary["SECONDARY REGION (East-US 2)"]
app2["• App Service
• Azure SQL (Secondary)
• Redis Cache
• Service Bus
• Blob Storage (Read-only)"]
end
traffic_mgr --> primary
traffic_mgr --> secondary
primary -->|Geo-Repl| secondary
primary -->|GRS| secondary
Recovery Objectives
| Component |
RTO |
RPO |
Strategy |
| Web Application |
15 min |
0 |
Active-passive with auto-failover |
| API Services |
10 min |
0 |
Active-passive with auto-failover |
| Database |
5 min |
< 5 sec |
Auto-failover groups |
| Blob Storage |
1 hour |
15 min |
RA-GRS |
| Service Bus |
30 min |
0 |
Geo-DR paired namespace |
Documentation Reference: NFR.md
Job Scheduling
Azure Functions (Timer Triggers)
| Job |
Schedule (CRON) |
Description |
| SLA Check |
0 */5 * * * * |
Check for SLA breaches every 5 min |
| Sync Reconciliation |
0 0 2 * * * |
Daily data reconciliation at 2 AM |
| Report Generation |
0 0 6 * * * |
Daily reports at 6 AM |
| Cleanup Old Data |
0 0 3 * * 0 |
Weekly cleanup Sunday 3 AM |
| Health Check |
0 */1 * * * * |
Integration health check every minute |
public class ScheduledJobs
{
[FunctionName("SLACheck")]
public async Task RunSLACheck(
[TimerTrigger("0 */5 * * * *")] TimerInfo timer,
ILogger log)
{
log.LogInformation($"SLA check started at {DateTime.UtcNow}");
var breaches = await _slaService.CheckForBreachesAsync();
foreach (var breach in breaches)
{
await _alertService.CreateSLABreachAlertAsync(breach);
}
log.LogInformation($"SLA check completed. {breaches.Count} breaches found.");
}
[FunctionName("DailyReconciliation")]
public async Task RunReconciliation(
[TimerTrigger("0 0 2 * * *")] TimerInfo timer,
ILogger log)
{
log.LogInformation($"Daily reconciliation started at {DateTime.UtcNow}");
await _reconciliationService.ReconcileCustomersAsync();
await _reconciliationService.ReconcileTechniciansAsync();
await _reconciliationService.ReconcileWorkOrdersAsync();
log.LogInformation("Daily reconciliation completed.");
}
}
Azure Data Factory (Batch ETL)
| Pipeline |
Schedule |
Source |
Target |
| Customer Sync |
Every 15 min |
MaddenCo |
Pomp's SQL |
| Technician Sync |
Every 30 min |
Dayforce |
Pomp's SQL |
| Billing Export |
Hourly |
Pomp's SQL |
MaddenCo |
| Analytics Load |
Daily |
Pomp's SQL |
Power BI Dataset |
DevOps & Infrastructure
CI/CD Pipeline (Bamboo)
# Bamboo build configuration
stages:
- Build
- Test
- Security Scan
- Deploy Dev
- Integration Tests
- Deploy Staging
- Deploy Production
Build:
tasks:
- script: dotnet restore
- script: dotnet build --configuration Release
- script: npm ci && npm run build # Vue.js
Test:
tasks:
- script: dotnet test --logger trx
- script: npm run test:unit
Security Scan:
tasks:
- script: dotnet tool run snyk test
- script: npm audit
Deploy:
tasks:
- script: az webapp deploy --name pomps-dispatch-$env
# Main infrastructure modules
module "networking" {
source = "./modules/networking"
vnet_name = "pomps-dispatch-vnet"
address_space = ["10.0.0.0/16"]
location = var.location
resource_group_name = azurerm_resource_group.main.name
}
module "database" {
source = "./modules/sql-database"
server_name = "pomps-dispatch-sql"
database_name = "DispatchDB"
sku_name = "BC_Gen5_8"
location = var.location
resource_group_name = azurerm_resource_group.main.name
geo_redundant_backup_enabled = true
zone_redundant = true
}
module "app_service" {
source = "./modules/app-service"
name = "pomps-dispatch-api"
sku_name = "P2v3"
location = var.location
resource_group_name = azurerm_resource_group.main.name
app_settings = {
"ASPNETCORE_ENVIRONMENT" = var.environment
}
}
Document Version: 1.0
Last Updated: January 2026
Next Review: April 2026