4.1 NFR-001: Performance Targets

Requirement: The system shall meet specified performance targets for processing throughput, API latency, and rendering speed to support high-volume Nordic utilities operations.

Priority: HIGH

Performance Targets:

MetricTargetMeasurement MethodAcceptance ThresholdTest Scenario
Batch Processing Throughput10M invoices/monthMonthly invoice count in Application Insights≥ 10M in peak monthProduction monitoring
100K Batch Processing Time< 2 hoursTimestamp diff (queuedAt to completedAt)≤ 120 minutesLoad test TC-200
API Response Time (p50)< 200msApplication Insights percentiles≤ 200msLoad test TC-201
API Response Time (p95)< 500msApplication Insights percentiles≤ 500msLoad test TC-202
API Response Time (p99)< 1000msApplication Insights percentiles≤ 1000msLoad test TC-203
PDF Generation Time (p95)< 5 seconds/invoiceCustom metric tracking≤ 5 secondsRender test TC-204
Handlebars Rendering (p95)< 2 seconds/invoiceCustom metric tracking≤ 2 secondsRender test TC-205
Queue Processing Lag< 5 minutesQueue depth / throughput calculation≤ 5 minutesQueue monitoring
Database Query Time (p95)< 100msPostgreSQL slow query log≤ 100msQuery analysis
ParserService: 10K batch< 2 minutesParse duration measurement≤ 120 secondsParser test TC-206

Load Testing Scenarios:

Scenario 1: Steady State (Normal Month)

Scenario 2: Peak Load (Heating Season)

Scenario 3: Spike Test

Acceptance Criteria:

CriterionValidation MethodTarget
Load test with 100K batch completesEnd-to-end test≤ 2 hours
API maintains p95 < 500ms under loadConcurrent API requests (1000 RPS)≤ 500ms
System processes 10M in peak monthProduction monitoring (Oct-Mar)≥ 10M
No performance degradation with 50 orgs50 orgs upload simultaneouslyAll SLAs met
Worker auto-scaling maintains lag < 5 minMonitor queue depth during peaksLag ≤ 5 min
PDF generation stays within targetRender 1000 PDFs, measure p95≤ 5 seconds

Dependencies:

Risks & Mitigation (Nordic Peak Season):

RiskLikelihoodImpactMitigation StrategyOwner
Heating season peaks exceed capacity (Oct-Mar)MEDIUMHIGH- Historical data analysis for peak prediction
- Pre-warm workers 1st/last week of month
- Priority queue for SLA customers
- Customer communication: off-peak scheduling
- Capacity planning review quarterly
Operations Manager
Template complexity slows renderingMEDIUMHIGH- Template performance guidelines
- POC testing with customer templates
- Recommend simple templates
- Compiled template caching
- Parallel rendering for 32 items
Technical Architect
Playwright memory issues at scaleHIGHHIGH- Semaphore: max 10 concurrent PDFs
- Worker memory limit: 2GB
- Browser instance pooling
- Monitor memory usage
- Scale horizontally (more workers)
Technical Architect
PostgreSQL connection exhaustionMEDIUMMEDIUM- Connection pooling (max 50 per service)
- Monitor active connections
- Timeout settings (30 seconds)
- Consider read replicas for heavy queries
Technical Architect


4.2 NFR-002: Scalability & Auto-Scaling

Requirement: The system shall scale horizontally without manual intervention to handle peak loads during Nordic heating season (October-March) and monthly invoice cycles.

Priority: HIGH

Scaling Configuration:

ComponentMin InstancesMax InstancesTrigger MetricThresholdScale Up TimeScale Down Time
CoreApiService520CPU Utilization OR Request Rate70% OR 1000 RPS2 minutes10 minutes
ParserService210Queue Length (batch-upload-queue)Length > 01 minute5 minutes
DocumentGenerator2100Queue Length (batch-items-queue)Length > 321 minute5 minutes
EmailService550Queue Length (email-queue)Length > 501 minute5 minutes
PostalService13Scheduled (not queue-based)12:00, 20:00 CETN/AAfter completion

Peak Load Capacity:

Normal Load (non-heating season, mid-month):

Peak Load (heating season, first/last week):

Scaling Calculation:

Peak Day: 314,000 invoices
Processing time per invoice: 10 seconds (parse + render + PDF + deliver)
Total processing time: 314,000 × 10s = 872 hours
Target completion: 8 hours
Required workers: 872 / 8 = 109 workers
With 32 items/worker: 109 × 32 = 3,488 items processing simultaneously


 

Acceptance Criteria:

CriterionValidation MethodTarget
Auto-scaling triggered on queue depthMonitor scaling eventsScales within 2 min
Scaling up completes within 2 minutesMeasure from trigger to ready≤ 2 minutes
Scaling down after 10 min low loadMonitor scale-down timing≥ 10 minutes
Performance maintained during scalingMonitor API latency during scale eventsNo degradation
No message loss during scalingCount messages before/after100% preserved
Pre-warming for known peaksSchedule scale-up 1st/last weekWorkers ready
Max 100 DocumentGenerator instancesVerify max instance count≤ 100

Pre-Warming Strategy (Heating Season):

Monthly Schedule:
- Day 1-7: Pre-warm to 50 instances at 00:00
- Day 8-23: Scale based on queue (2-20 instances)
- Day 24-31: Pre-warm to 50 instances at 00:00

Heating Season (Oct-Mar): Double pre-warm levels
- Day 1-7: Pre-warm to 80 instances
- Day 24-31: Pre-warm to 80 instances


 

Dependencies:

Risks & Mitigation:

RiskLikelihoodImpactMitigation StrategyOwner
Scale-up too slow for sudden spikeMEDIUMHIGH- Pre-warm during known peaks
- Keep min instances higher during peak season
- Queue backpressure (503) if overloaded
- Customer scheduling guidance
Operations Manager
Azure Container Apps 100-instance limitLOWHIGH- Priority queue for SLA customers
- Queue backpressure to throttle intake
- Consider split by organization
- Plan for Phase 2: dedicated worker pools
Technical Architect
Cost escalation during sustained peaksMEDIUMMEDIUM- Cost alerts at thresholds
- Auto-scale down aggressively
- Reserved instances for base load
- Monitor cost per invoice
Finance Controller


4.3 NFR-003: Availability & Reliability (Nordic 24/7 Operations)

Requirement: The system shall maintain 99.9% uptime with automatic failover, multi-region deployment, and recovery procedures to support Nordic utilities' 24/7 invoice delivery operations.

Priority: HIGH

Availability Targets:

MetricTargetAllowed DowntimeMeasurementConsequences of Breach
System Uptime99.9%43 min/monthAzure MonitorSLA credit to customers
Batch Success Rate> 99.5%50 failures per 10KProcessing logsInvestigation required
Delivery Success Rate> 98%200 failures per 10KDelivery trackingAlert to organization
API Availability99.9%43 min/monthHealth check monitoringIncident escalation
MTTR (Mean Time To Recovery)< 30 minutesN/AIncident timestampsProcess improvement
MTBF (Mean Time Between Failures)> 720 hours (30 days)N/AIncident trackingRoot cause analysis

Multi-Region Deployment:

Primary Region: West Europe (Azure westeurope)
- Sweden: Primary processing
- Denmark: Primary processing

Secondary Region: North Europe (Azure northeurope)
- Norway: Primary processing
- Finland: Primary processing
- Failover for Sweden/Denmark

Traffic Routing:
- Azure Traffic Manager with Performance routing
- Health check: /health endpoint every 30 seconds
- Auto-failover on 3 consecutive failed health checks
- Failover time: < 2 minutes


 

Recovery Time Objectives:

ScenarioRTO (Recovery Time)RPO (Data Loss)Recovery MethodResponsible Team
Worker Instance Crash< 5 minutes0 (idempotent)Automatic queue retryAutomatic
Database Failure< 15 minutes< 5 minutesAuto-failover to read replicaAutomatic + Ops verification
Primary Region Failure< 30 minutes< 15 minutesTraffic Manager failover to secondary regionOps Manager
Blob Storage Corruption< 1 hour< 1 hourRestore from blob version/snapshotOps Team
Queue Service Outage< 15 minutes0 (messages preserved)Wait for Azure recovery, messages retainedOps Manager
SendGrid Complete Outage< 2 hours0 (fallback to postal)Route all email invoices to postal queueOps Team
21G SFTP Unavailable< 4 hours0 (retry scheduled)Retry at next scheduled time (12:00/20:00)Ops Team

Backup & Recovery Strategy:

Blob Storage:

Replication: Geo-Redundant Storage (GRS)
  - Primary: West Europe
  - Secondary: North Europe
  - Automatic replication

Soft Delete: 7 days retention
  - Recover accidentally deleted blobs within 7 days

Blob Versioning: 30 days retention
  - Previous versions accessible
  - Rollback capability

Point-in-Time Restore: Not needed (blob versioning sufficient)


 

PostgreSQL:

Backup Schedule: Daily automated backups
Retention: 35 days
Backup Window: 02:00-04:00 CET (low traffic period)
Point-in-Time Restore: 7 days
Geo-Redundant: Enabled
Read Replica: North Europe (for failover)


 

Acceptance Criteria:

CriterionValidation MethodTarget
Multi-region deployment operationalVerify services in both regionsBoth regions active
Traffic Manager routes to healthy regionSimulate West Europe failureRoutes to North Europe
Database auto-failover testedSimulate primary DB failureFailover < 15 min
Blob geo-replication verifiedWrite to primary, read from secondaryData replicated
Health checks on all servicesGET /health on all endpointsAll return 200
Automated incident alerts configuredSimulate service failureAlert received within 5 min
Worker auto-restart on crashKill worker processNew instance starts
Queue message retry testedSimulate worker crash mid-processingMessage reprocessed
Disaster recovery drill quarterlySimulate complete region lossRecovery within RTO
Backup restoration tested monthlyRestore database from backupSuccessful restore

Dependencies:

Risks & Mitigation (Nordic Context):

RiskLikelihoodImpactMitigation StrategyOwner
Both Azure regions fail simultaneouslyVERY LOWCRITICAL- Extremely rare (Azure multi-region SLA 99.99%)
- Accept risk (probability vs cost of 3rd region)
- Communication plan for extended outage
- Manual failover to Azure Germany (emergency)
Executive Sponsor
Network partition between regionsLOWHIGH- Each region operates independently
- Eventual consistency acceptable
- Manual reconciliation if partition >1 hour
- Traffic Manager handles routing
Technical Architect
Database failover causes brief downtimeLOWMEDIUM- Accept 1-2 minutes downtime during failover
- API returns 503 with Retry-After
- Queue-based processing unaffected
- Monitor failover duration
Operations Manager
Swedish winter storms affect connectivityLOWMEDIUM- Azure datacenter redundancy within region
- Monitor Azure status dashboard
- Communication plan for customers
- No physical office connectivity required
Operations Manager


4.4 NFR-004: Security Requirements

Requirement: The system shall implement comprehensive security controls including OAuth 2.0 authentication, role-based access control, encryption, audit logging, and protection against OWASP Top 10 vulnerabilities.

Priority: CRITICAL

4.4.1 Authentication & Authorization


OAuth 2.0 Implementation:

Grant Type: Client Credentials Flow (machine-to-machine)
Token Provider: Microsoft Entra ID
Token Lifetime: 1 hour
Refresh Token: 90 days
Token Format: JWT (JSON Web Token)
Algorithm: RS256 (RSA signature with SHA-256)


 

Required Claims in JWT:

{
  "aud": "api://eg-flow-api",
  "iss": "https://login.microsoftonline.com/{tenant}/v2.0",
  "sub": "user-object-id",
  "roles": ["Batch.Operator"],
  "organization_id": "123e4567-e89b-12d3-a456-426614174000",
  "exp": 1700226000,
  "nbf": 1700222400
}


 

Role Definitions & Permissions:

RoleScopePermissionsUse Case
Super AdminGlobal (all organizations)Full CRUD on all resources, cross-org visibilityEG internal support team
Organization AdminSingle organizationManage org users, configure settings, view all batchesUtility IT manager
Template AdminSingle organizationCreate/edit templates, manage template versionsUtility design team
Batch OperatorSingle organizationUpload batches, start processing, view statusUtility billing team
Read-Only UserSingle organizationView batches, download invoices, view reportsUtility customer service
API ClientSingle organizationProgrammatic batch upload and status queriesBilling system integration

Acceptance Criteria:

CriterionValidation MethodTarget
OAuth 2.0 token required for all endpoints (except /health)Call API without token401 Unauthorized
JWT token validated (signature, expiration, audience)Tampered token, expired token401 Unauthorized
Refresh tokens work for 90 daysUse refresh token after 30 daysNew access token issued
All 6 roles implemented in PostgreSQLQuery roles table6 roles present
Users can only access their organizationUser A calls Org B endpoint403 Forbidden
All actions logged to audit_logs tablePerform action, query audit_logsEntry created
API authentication middleware on all routesAttempt bypassAll protected
MFA enforced for Super AdminLogin as Super AdminMFA challenge
MFA enforced for Org AdminLogin as Org AdminMFA challenge
Failed logins logged3 failed login attempts3 entries in audit_logs
Account lockout after 5 failed attempts6 failed login attempts15-minute lockout
API key rotation every 90 daysCheck Key Vault secret ageAlert at 80 days


4.4.2 Data Protection

Encryption Standards:

In Transit:
- TLS 1.3 minimum (TLS 1.2 acceptable)
- Cipher suites: AES-256-GCM, ChaCha20-Poly1305
- Certificate: Wildcard cert for *.egflow.com
- HSTS: max-age=31536000; includeSubDomains

At Rest:
- Azure Blob Storage: AES-256 (Microsoft-managed keys)
- PostgreSQL: AES-256 (Microsoft-managed keys)
- Backups: AES-256 encryption
- Customer-managed keys (CMK): Phase 2 option

Sensitive Data Fields (extra protection):
- Personnummer: Encrypted column in database (if stored)
- API keys: Azure Key Vault only
- Email passwords: Never stored
- Customer addresses: Standard blob encryption sufficient


 

Acceptance Criteria:

CriterionValidation MethodTarget
All API traffic over HTTPSAttempt HTTP requestRedirect to HTTPS or reject
TLS 1.3 or 1.2 enforcedCheck TLS version in trafficTLS ≥ 1.2
Data encrypted at rest (blob)Verify Azure encryption settingsEnabled
Data encrypted at rest (PostgreSQL)Verify DB encryptionEnabled
Secrets in Azure Key Vault onlyCode scan for hardcoded secretsZero secrets in code
No credentials in source controlGit history scanZero credentials
Database connections use managed identityCheck connection stringsNo passwords
Personnummer not in URLsURL pattern analysisNo personnummer patterns
Personnummer not in logsLog analysisNo personnummer found


4.4.3 Application Security (OWASP Top 10)

Security Measures:

OWASP RiskMitigationValidation
A01: Broken Access ControlOrganization middleware, RBAC enforcementPenetration testing
A02: Cryptographic FailuresTLS 1.3, AES-256, Key VaultSecurity scan
A03: InjectionParameterized queries, input validationSQL injection testing
A04: Insecure DesignThreat modeling, security reviewArchitecture review
A05: Security MisconfigurationAzure security baseline, CIS benchmarksConfiguration audit
A06: Vulnerable ComponentsDependabot, automated scanningWeekly scan
A07: Authentication FailuresOAuth 2.0, MFA, rate limitingPenetration testing
A08: Software/Data IntegrityCode signing, SRI, checksumsBuild verification
A09: Logging FailuresComprehensive audit loggingLog completeness review
A10: SSRFURL validation, allowlistSecurity testing

Input Validation:

// Example: Batch upload validation with FluentValidation
public class BatchUploadValidator : AbstractValidator<BatchUploadRequest>
{
    public BatchUploadValidator()
    {
        RuleFor(x => x.File)
            .NotNull().WithMessage("File is required")
            .Must(BeValidXml).WithMessage("File must be valid XML")
            .Must(BeLessThan100MB).WithMessage("File must be less than 100MB");
        
        RuleFor(x => x.Metadata.BatchName)
            .NotEmpty().WithMessage("Batch name is required")
            .Length(1, 255).WithMessage("Batch name must be 1-255 characters")
            .Must(NotContainPathSeparators).WithMessage("Batch name cannot contain / or \\")
            .Must(NoSQLInjectionPatterns).WithMessage("Invalid characters in batch name");
        
        RuleFor(x => x.Metadata.Priority)
            .Must(x => x == "normal" || x == "high")
            .WithMessage("Priority must be 'normal' or 'high'");
    }
    
    private bool NoSQLInjectionPatterns(string input)
    {
        var sqlPatterns = new[] { "--", "/*", "*/", "xp_", "sp_", "';", "\";" };
        return !sqlPatterns.Any(p => input.Contains(p, StringComparison.OrdinalIgnoreCase));
    }
}


 

Acceptance Criteria:

CriterionValidation MethodTarget
Input validation on all API endpointsSend malicious inputRejected with error
SQL injection preventedAttempt SQL injection in batch nameSanitized/rejected
XSS prevented in templatesInject script tags in templateSanitized on render
XML external entity (XXE) attack preventedUpload XXE payloadParsing rejects
Billion laughs attack preventedUpload billion laughs XMLParsing rejects/times out safely
File upload size enforcedUpload 101MB fileRejected at API gateway
Rate limiting prevents abuse1000 rapid API calls429 after limit
CSRF protection (future web UI)Attempt CSRF attackBlocked by token
Dependency vulnerabilities scanned weeklyRun DependabotAlerts for high/critical
Security headers presentCheck HTTP responseX-Frame-Options, CSP, etc.


4.4.4 Network Security

Acceptance Criteria:

CriterionStatusPhase
DDoS protection enabled (Azure basic)✅ IncludedPhase 1
IP whitelisting support for API clients✅ Optional featurePhase 1
VNet integration for Container Apps⚠️ Phase 2Phase 2
Private endpoints for Blob Storage⚠️ Phase 2Phase 2
Network Security Groups (NSGs)⚠️ Phase 2Phase 2
Azure Firewall for egress filtering⚠️ Phase 2Phase 2

Dependencies:

Risks & Mitigation (Nordic/EU Security Context):

RiskLikelihoodImpactMitigation StrategyOwner
NIS2 Directive compliance (EU critical infrastructure)MEDIUMCRITICAL- Energy sector falls under NIS2
- Incident reporting procedures (24h to authorities)
- Security measures documentation
- Annual security audit
- CISO designated
Legal/Compliance
Swedish Säkerhetspolisen (SÄPO) requirementsLOWHIGH- Enhanced security for critical infrastructure
- Incident reporting to MSB (Swedish Civil Contingencies)
- Employee background checks for production access
- Security clearance for key personnel
Security Officer
API key theft/leakageMEDIUMHIGH- Rotate keys every 90 days
- Monitor for leaked keys (GitHub scanning)
- Revoke compromised keys immediately
- API key hashing in database
- Never log full API keys
Security Officer
Insider threat (privileged access abuse)LOWCRITICAL- Least privilege principle
- All actions audited
- Regular access reviews
- Separation of duties
- Anomaly detection in audit logs
Security Officer
Third-party vendor breach (SendGrid, 21G)LOWHIGH- Data Processing Agreements (DPAs) signed
- Regular vendor security assessments
- Minimal data sharing
- Encryption in transit to vendors
- Vendor breach response plan
Legal/Compliance


4.5 NFR-005: Data Retention & Lifecycle Management

Requirement: The system shall manage data retention according to Swedish accounting law (7-year invoice retention) with automated lifecycle policies for cost optimization.

Priority: HIGH

Retention Policies:

Data TypeLegal RequirementRetention PeriodStorage Tier TransitionDisposal Method
Invoices (PDF/HTML/JSON)Bokföringslagen (Swedish Accounting Act)7 years from fiscal year endDay 0-365: Hot
Day 366-2555: Cool
Day 2556+: Archive
Permanent deletion after 7 years
Batch Source Files (XML)None (internal processing)90 daysDay 0-30: Hot
Day 31-90: Cool
Day 91+: Delete
Automatic deletion
Batch Metadata JSONAudit trail90 daysDay 0-90: Hot
Day 91+: Delete
Automatic deletion
Audit Logs (PostgreSQL)GDPR, Swedish law7 yearsYear 0-1: PostgreSQL
Year 1-7: Blob (compressed)
Deletion after 7 years
Application LogsOperational90 daysApplication InsightsAutomatic deletion
TemplatesBusiness continuityIndefinite (archived versions)Hot (active)
Cool (archived)
Never deleted
Organization ConfigBusiness continuityIndefiniteHotNever deleted (updated in place)

Azure Blob Lifecycle Policy:

{
  "rules": [
    {
      "enabled": true,
      "name": "invoice-lifecycle",
      "type": "Lifecycle",
      "definition": {
        "filters": {
          "blobTypes": ["blockBlob"],
          "prefixMatch": ["invoices-"]
        },
        "actions": {
          "baseBlob": {
            "tierToCool": {
              "daysAfterModificationGreaterThan": 365
            },
            "tierToArchive": {
              "daysAfterModificationGreaterThan": 2555
            },
            "delete": {
              "daysAfterModificationGreaterThan": 2920
            }
          }
        }
      }
    },
    {
      "enabled": true,
      "name": "batch-source-cleanup",
      "type": "Lifecycle",
      "definition": {
        "filters": {
          "blobTypes": ["blockBlob"],
          "prefixMatch": ["batches-"]
        },
        "actions": {
          "baseBlob": {
            "tierToCool": {
              "daysAfterModificationGreaterThan": 30
            },
            "delete": {
              "daysAfterModificationGreaterThan": 90
            }
          }
        }
      }
    }
  ]
}


 

Storage Growth Projection:

Assumptions:

Growth Over Time:

YearNew Data/MonthCumulative TotalPrimary Storage TierSecondary Tier
Year 1850 GB10.2 TBHot (10.2 TB)-
Year 2850 GB20.4 TBHot (10.2 TB)Cool (10.2 TB)
Year 3850 GB30.6 TBHot (10.2 TB)Cool (20.4 TB)
Year 7850 GB71.4 TBHot (10.2 TB)Cool (10.2 TB), Archive (51 TB)

Storage Tier Pricing Impact:

With lifecycle policies (Hot → Cool → Archive):

Acceptance Criteria:

CriterionValidation MethodTarget
Automated lifecycle policies configuredCheck Azure policyPolicies active
Data transitions to Cool after 1 yearVerify tier of 13-month-old invoiceCool tier
Data transitions to Archive after 7 yearsVerify tier of 7-year-old invoiceArchive tier
7-year invoice retention enforcedAttempt to access 8-year-old invoiceDeleted (404)
Old batch files deleted after 90 daysCheck for 91-day-old batch fileDeleted (404)
Retention policy exceptions supportedTag invoice with legal holdNot deleted despite age
Legal hold prevents deletionSet legal hold, verify no deletionInvoice retained
Data restoration from Archive within 24hRequest archived invoiceRetrieved within 24h
Templates never automatically deletedCheck template ageOld templates present (archived)

Legal Hold Functionality:

{
  "invoiceId": "uuid",
  "legalHold": {
    "enabled": true,
    "reason": "Customer dispute - case #12345",
    "appliedBy": "user-uuid",
    "appliedAt": "2025-11-21T10:00:00Z",
    "expiresAt": null
  }
}


 

Dependencies:

Risks & Mitigation (Swedish Legal Context):

RiskLikelihoodImpactMitigation StrategyOwner
Bokföringslagen (Accounting Act) non-complianceLOWCRITICAL- 7-year retention strictly enforced
- Legal opinion obtained
- Retention policy reviewed by auditor
- Automated compliance reporting
- Skatteverket (Tax Agency) audit trail
Legal/Compliance
Premature invoice deletionLOWHIGH- Lifecycle policy testing in staging
- Deletion logging and alerts
- Soft delete (7-day recovery)
- Annual retention audit
Operations Manager
Storage costs exceed budgetMEDIUMMEDIUM- Lifecycle policies reduce costs 85%
- Cost monitoring and alerts
- Quarterly cost review
- Consider compression for PDFs
Finance Controller
Archive retrieval SLA breachLOWMEDIUM- Document 24-hour SLA for archive
- Test archive retrieval monthly
- Maintain critical invoices in Cool (not Archive)
Operations Manager


4.6 NFR-006: Monitoring, Logging & Observability

Requirement: The system shall provide comprehensive monitoring through Application Insights, structured logging with Serilog, real-time dashboards with 5-minute refresh, and automated alerting with escalation procedures.

Priority: HIGH

4.6.1 Structured Logging Standards


Serilog Configuration:

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
    .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
    .Enrich.FromLogContext()
    .Enrich.WithProperty("Application", "EGU.Flow")
    .Enrich.WithProperty("Environment", environment)
    .Enrich.WithProperty("Region", azureRegion)
    .Enrich.WithMachineName()
    .Enrich.WithThreadId()
    .WriteTo.ApplicationInsights(
        connectionString,
        TelemetryConverter.Traces,
        LogEventLevel.Information)
    .WriteTo.Console(new CompactJsonFormatter())
    .CreateLogger();


 

Log Entry Structure:

{
  "timestamp": "2025-11-21T10:30:00.123Z",
  "level": "Information",
  "messageTemplate": "Batch {BatchId} processing started for organization {OrganizationId}. Items: {ItemCount}",
  "message": "Batch 550e8400... processing started for organization 123e4567.... Items: 5000",
  "properties": {
    "BatchId": "550e8400-e29b-41d4-a716-446655440000",
    "OrganizationId": "123e4567-e89b-12d3-a456-426614174000",
    "ItemCount": 5000,
    "CorrelationId": "corr-abc-123",
    "Application": "EGU.Flow",
    "Environment": "Production",
    "Region": "westeurope",
    "MachineName": "worker-001"
  }
}


 

PII Masking Rules:

NEVER log:
- Personnummer (Swedish social security numbers)
- Full customer names (log customer ID only)
- Email addresses (log domain only: user@***@example.se)
- Phone numbers (log prefix only: +4670****)
- Street addresses (log city only)
- Bank account numbers
- API keys or tokens

SAFE to log:
- Organization IDs (UUIDs)
- Batch IDs (UUIDs)
- Invoice IDs (UUIDs)
- Invoice numbers (business references)
- Processing statistics
- Error codes and messages
- Performance metrics


 

4.6.2 Application Insights Dashboards

Dashboard 1: Operations (Real-Time)

Refresh: Every 5 minutes

Metrics:

Dashboard 2: Performance

Refresh: Every 5 minutes

Metrics:

Dashboard 3: Business

Refresh: Hourly

Metrics:

Dashboard 4: Vendor Formats

Refresh: Hourly

Metrics:


4.6.3 Alert Rules & Escalation

Critical Alerts (5-minute evaluation window):

Alert NameCondition (Kusto Query)SeverityRecipientsEscalation (after 15 min)
High Error Ratetraces | where severityLevel >= 3 | count > 50HighOps teamDev team + On-call
Queue Depth CriticalcustomMetrics | where name == 'Queue.Depth' and value > 10000HighOps teamProduct Owner
Worker Crash Spiketraces | where message contains 'Worker crashed' | count > 3CriticalOps + Dev teamsCTO
Delivery Failure RatecustomMetrics | where name startswith 'Delivery' and value < 0.9MediumOps teamCustomer success
API Response Degradedrequests | summarize p95=percentile(duration, 95) | where p95 > 1000MediumOps teamTechnical Architect
Batch Processing TimeoutcustomMetrics | where name == 'Batch.Duration' and value > 120HighOps teamProduct Owner
Database Connection Errorsexceptions | where type contains 'Npgsql' | count > 10CriticalOps + DBACTO
Blob Storage Throttlingexceptions | where message contains '503 Server Busy' | count > 20HighOps teamTechnical Architect
SendGrid Deliverability DropSendGrid webhook: bounceRate > 10%HighOps teamEmail deliverability specialist
21G SFTP Connection FailureSFTP connection exceptionsHighOps team21G account manager

Alert Delivery:

On-Call Rotation:

Acceptance Criteria:

CriterionValidation MethodTarget
Dashboards refresh every 5 minutesCheck dashboard timestamp≤ 5 min old
Data retained for 90 daysCheck oldest data in dashboard90 days accessible
Dashboards accessible to authorized usersLogin as different rolesAppropriate access
Critical alerts trigger within 5 minSimulate high error rateAlert within 5 min
Alert escalation after 15 minDon't acknowledge alertEscalation triggered
PII masked in logsSearch logs for personnummer regexZero matches
Correlation IDs trace requestsFollow request across servicesSame ID throughout
Log retention 90 daysCheck Application Insights retention90 days
Structured logging in JSON formatParse log entriesValid JSON

Custom Metrics Tracked:

public class MetricsService
{
    private readonly TelemetryClient _telemetry;
    
    public void TrackBatchProcessing(BatchMetadata batch)
    {
        _telemetry.TrackMetric("Batch.TotalItems", 
            batch.Statistics.TotalItems,
            new Dictionary<string, string> {
                ["OrganizationId"] = batch.OrganizationId,
                ["VendorCode"] = batch.VendorInfo.VendorCode
            });
        
        _telemetry.TrackMetric("Batch.Duration", 
            (batch.Timestamps.CompletedAt - batch.Timestamps.StartedAt)?.TotalMinutes ?? 0);
        
        _telemetry.TrackMetric("Batch.SuccessRate", 
            (double)batch.Statistics.SuccessfulItems / batch.Statistics.TotalItems * 100);
    }
    
    public void TrackDelivery(string channel, bool success, string organizationId)
    {
        _telemetry.TrackMetric($"Delivery.{channel}.Success",
            success ? 1 : 0,
            new Dictionary<string, string> {
                ["OrganizationId"] = organizationId,
                ["Channel"] = channel
            });
    }
    
    public void TrackQueueDepth(string queueName, int depth)
    {
        _telemetry.TrackMetric("Queue.Depth", depth,
            new Dictionary<string, string> {
                ["QueueName"] = queueName
            });
    }
    
    public void TrackVendorParsing(string vendorCode, bool success, long durationMs)
    {
        _telemetry.TrackMetric("Parser.Duration", durationMs,
            new Dictionary<string, string> {
                ["VendorCode"] = vendorCode,
                ["Success"] = success.ToString()
            });
    }
}


 

Kusto Queries for Common Operations:

Query 1: Failed Batches (Last 24 Hours)

traces
| where timestamp > ago(24h)
| where customDimensions.BatchId != ""
| where severityLevel >= 3
| summarize 
    ErrorCount = count(),
    FirstError = min(timestamp),
    LastError = max(timestamp)
    by 
    BatchId = tostring(customDimensions.BatchId),
    OrganizationId = tostring(customDimensions.OrganizationId),
    ErrorMessage = message
| order by ErrorCount desc
| take 50


 

Query 2: Queue Depth Trending

customMetrics
| where name == "Queue.Depth"
| where timestamp > ago(24h)
| extend QueueName = tostring(customDimensions.QueueName)
| summarize 
    AvgDepth = avg(value),
    MaxDepth = max(value),
    MinDepth = min(value)
    by QueueName, bin(timestamp, 5m)
| render timechart


 

Query 3: Vendor Format Performance

customMetrics
| where name == "Parser.Duration"
| where timestamp > ago(7d)
| extend VendorCode = tostring(customDimensions.VendorCode)
| summarize 
    p50 = percentile(value, 50),
    p95 = percentile(value, 95),
    p99 = percentile(value, 99),
    BatchCount = count()
    by VendorCode
| order by p95 desc


 

Dependencies:



4.7 NFR-007: Disaster Recovery & Business Continuity

Requirement: The system shall have documented disaster recovery procedures with defined RTO/RPO targets, tested quarterly, to ensure business continuity for Nordic utility customers.

Priority: HIGH

Disaster Recovery Scenarios:

Scenario 1: Worker Instance Crash


Trigger: DocumentGenerator worker crashes during 32-item batch processing

Detection: Worker stops sending heartbeats, Azure Container Apps detects unhealthy instance

Recovery Procedure:

1. Azure Container Apps automatically starts new instance (2 min)
2. Queue message visibility timeout expires (5 min)
3. Message becomes visible in batch-items-queue again
4. New worker instance picks up message
5. Worker checks for already-processed invoices (idempotency)
6. Skips completed invoices, processes remaining items
7. Blob lease ensures no concurrent processing


 

RTO: < 5 minutes (automatic)
RPO: 0 (no data loss, idempotent operations)
Responsible: Automatic + Operations Manager (monitoring)

Scenario 2: PostgreSQL Database Failure


Trigger: Primary PostgreSQL instance becomes unresponsive

Detection: Health check failures, connection timeout errors in logs

Recovery Procedure:

1. Azure detects primary failure via health probes (30 seconds)
2. Auto-failover to read replica in secondary region (5 min)
3. DNS updated to point to new primary
4. Application reconnects automatically (connection retry logic)
5. Verify data integrity post-failover
6. Notify stakeholders of failover event
7. Investigate root cause


 

RTO: < 15 minutes
RPO: < 5 minutes (replication lag)
Responsible: Operations Manager (monitoring), DBA (verification)

Scenario 3: Azure Region Failure (West Europe)


Trigger: Complete West Europe region outage

Detection: Traffic Manager health checks fail for all West Europe endpoints

Recovery Procedure:

1. Traffic Manager detects 3 consecutive health check failures (90 seconds)
2. Traffic Manager routes all traffic to North Europe (2 min)
3. North Europe region activates read replica database as primary
4. North Europe workers process queues (messages replicated via GRS)
5. Verify system operational in North Europe
6. Communicate to customers about region failover
7. Monitor for West Europe recovery
8. Plan failback when West Europe restored


 

RTO: < 30 minutes
RPO: < 15 minutes (blob replication lag)
Responsible: Operations Manager (execution), CTO (decision), Communications (customer notification)

Scenario 4: Blob Storage Corruption


Trigger: Critical blob (organization config, template) becomes corrupted or accidentally deleted

Detection: Blob read errors, validation failures, user reports

Recovery Procedure:

1. Identify corrupted blob path and organization
2. Check soft delete (7-day retention):
   - If within 7 days: Undelete blob immediately
3. If soft delete expired, check blob versions:
   - Restore from previous version
4. If no versions, restore from geo-redundant copy:
   - Access secondary region blob storage
   - Copy to primary region
5. Verify restored blob integrity
6. Test with sample batch
7. Root cause analysis


 

RTO: < 1 hour
RPO: < 1 hour (version interval)
Responsible: Operations Manager (execution), Technical Architect (verification)

Scenario 5: Complete Data Loss (Catastrophic)


Trigger: Theoretical scenario - both regions and all backups lost

Detection: N/A (highly unlikely with Azure GRS)

Recovery Procedure:

1. Declare disaster
2. Restore PostgreSQL from geo-redundant backup (35-day retention)
3. Organizations re-upload batch files (source systems have copies)
4. Customers re-notified about invoices
5. Incident post-mortem and Azure investigation


 

RTO: < 4 hours
RPO: < 24 hours (last backup)
Responsible: CTO (declaration), All teams (execution)

Note: This scenario has probability < 0.001% given Azure GRS + geo-redundant backups + multi-region deployment.

Disaster Recovery Testing:

Test TypeFrequencyScopePass Criteria
Worker Crash TestMonthlyKill random worker mid-processingRecovery < 5 min, no data loss
Database Failover TestQuarterlyForce failover to replicaRecovery < 15 min, queries work
Region Failover DrillAnnuallySimulate West Europe outageRecovery < 30 min, all services operational
Backup Restoration TestMonthlyRestore PostgreSQL from backupSuccessful restore, data integrity verified
Blob Undelete TestQuarterlyDelete critical blob, restoreSuccessful recovery within 1 hour

Acceptance Criteria:

CriterionValidation MethodTarget
Multi-region deployment activeVerify services in both regionsBoth regions operational
Traffic Manager failover testedSimulate region failureFailover < 2 min
Database auto-failover testedForce primary DB failureFailover < 15 min
Blob geo-replication verifiedWrite to primary, read from secondaryData present
Disaster recovery procedures documentedReview runbook completeness100% complete
DR drill conducted quarterlyCheck last drill dateWithin 90 days
Backup restoration tested monthlyCheck last restore testWithin 30 days
Recovery procedures automated where possibleReview manual steps< 5 manual steps


4.8 NFR-008: Data Consistency (Eventual Consistency Model)

Requirement: The system shall maintain data consistency using blob leases for exclusive access, ETag-based optimistic concurrency for metadata updates, and idempotent operations for safe retries.

Priority: HIGH

Consistency Guarantees:

Strong Consistency (Within Single Operation):

Eventual Consistency (Across System):

Consistency Mechanisms:

1. Blob Lease for Exclusive Access:

// Ensures only one worker processes 32-item batch
var lease = await AcquireBlobLeaseAsync(
    container: "{org}-batches-{year}",
    blob: "locks/{batch-id}/{message-id}.lock",
    duration: TimeSpan.FromMinutes(5));

try {
    // Process 32 invoices exclusively
    await ProcessBatchItemsAsync(message);
}
finally {
    await ReleaseBlobLeaseAsync(lease);
}


 

2. ETag-Based Optimistic Concurrency:

// Prevents lost updates to batch metadata
var download = await blobClient.DownloadContentAsync();
var etag = download.Value.Details.ETag;
var metadata = JsonSerializer.Deserialize<BatchMetadata>(download.Value.Content);

// Update metadata
metadata.Statistics.ProcessedItems += 32;
metadata.Metadata.UpdatedAt = DateTime.UtcNow;
metadata.Metadata.Version++;

// Upload with ETag condition
await blobClient.UploadAsync(content, new BlobUploadOptions {
    Conditions = new BlobRequestConditions { IfMatch = etag }
});
// Throws if ETag doesn't match (another worker updated)


 

3. Idempotent Operations:

// Safe to retry without duplicates
public async Task ProcessInvoiceAsync(string invoiceId)
{
    // Check if already processed
    var metadata = await TryGetInvoiceMetadataAsync(invoiceId);
    if (metadata?.Status == "delivered")
    {
        _logger.LogInformation("Invoice {InvoiceId} already processed, skipping", invoiceId);
        return; // Idempotent: safe to skip
    }
    
    // Process invoice (creates new blobs, doesn't update existing)
    await RenderInvoiceAsync(invoiceId);
    await GeneratePdfAsync(invoiceId);
    await EnqueueForDeliveryAsync(invoiceId);
}


 

Acceptance Criteria:

CriterionValidation MethodTarget
Blob leases prevent concurrent processingSend same message to 2 workersOnly 1 processes
ETags prevent lost updates2 workers update same metadataNo lost updates
Retry operations are idempotentRetry invoice processing 3xProcessed once only
No duplicate invoices generatedCrash during processing, retryOne PDF created
Concurrent batch updates handled10 workers update statisticsAll updates applied
Race conditions preventedConcurrent access testingNo race conditions
Data integrity after crashKill worker, verify data stateConsistent state

Dependencies:



4.9 NFR-009: Multi-Region Data Residency (Nordic Compliance)

Requirement: The system shall enforce data residency requirements for Nordic countries, ensuring Swedish/Danish data stays in West Europe and Norwegian/Finnish data stays in North Europe, with no cross-border transfers except encrypted backups.

Priority: HIGH

Data Residency Rules:

CountryCustomer BasePrimary RegionData ResidencyBackup RegionRationale
Sweden (SE)~10M population, largest marketWest EuropeEnforcedNorth Europe (encrypted)GDPR, Swedish Data Protection Law
Denmark (DK)~6M populationWest EuropeEnforcedNorth Europe (encrypted)GDPR, Danish data laws
Norway (NO)~5M populationNorth EuropeEnforcedWest Europe (encrypted)GDPR, Norwegian data laws, EEA regulations
Finland (FI)~5M populationNorth EuropeEnforcedWest Europe (encrypted)GDPR, Finnish data laws

Traffic Routing Logic:

public class RegionRoutingService
{
    public string GetProcessingRegion(string organizationId)
    {
        var org = await _orgService.GetOrganizationAsync(organizationId);
        
        // Route based on organization's country
        return org.CountryCode switch
        {
            "SE" => "westeurope",    // Sweden → West Europe
            "DK" => "westeurope",    // Denmark → West Europe
            "NO" => "northeurope",   // Norway → North Europe
            "FI" => "northeurope",   // Finland → North Europe
            _ => "westeurope"        // Default: West Europe
        };
    }
    
    public async Task<bool> ValidateDataResidencyAsync(string organizationId, string requestRegion)
    {
        var requiredRegion = GetProcessingRegion(organizationId);
        
        if (requestRegion != requiredRegion)
        {
            _logger.LogWarning(
                "Data residency violation attempted. Org: {OrgId}, Required: {Required}, Attempted: {Attempted}",
                organizationId, requiredRegion, requestRegion);
            
            return false;
        }
        
        return true;
    }
}


 

Organization Configuration:

{
  "organizationId": "uuid",
  "organizationCode": "VATTENFALL-SE",
  "countryCode": "SE",
  "dataResidency": {
    "primaryRegion": "westeurope",
    "allowedRegions": ["westeurope"],
    "backupRegions": ["northeurope"],
    "crossRegionProcessing": false,
    "crossRegionBackup": true
  }
}


 

Acceptance Criteria:

CriterionValidation MethodTarget
Swedish orgs process in West EuropeVerify blob container regionwesteurope
Norwegian orgs process in North EuropeVerify blob container regionnortheurope
Cross-region processing blockedAttempt to process Swedish org in North EuropeRejected
Cross-region backup allowedVerify geo-redundant replicationEnabled
Latency < 100ms within regionAPI latency from Nordic countries< 100ms
Automatic failover to secondary regionSimulate primary region failureFailover works
Data residency config per organizationUpdate org configSetting honored
Audit trail for cross-region accessAttempt cross-region, check logsAttempt logged

Dependencies:

Risks & Mitigation (Nordic Legal Context):

RiskLikelihoodImpactMitigation StrategyOwner
Schrems II implications (EU-US data transfer)LOWHIGH- No US region usage
- All data in EU (West/North Europe only)
- Azure EU Data Boundary compliance
- Standard Contractual Clauses with vendors
Legal/Compliance
Norwegian data sovereignty concernsLOWMEDIUM- North Europe primary for Norwegian orgs
- Option for Norway-only processing
- No data transfer to other Nordics without consent
- Compliance with Norwegian regulations
Legal/Compliance
Data residency audit by Datatilsynet (NO) or Datatilsynet (DK)LOWHIGH- Documented data flows
- Data residency configuration auditable
- Logs prove compliance
- Annual self-assessment
Legal/Compliance


4.10 NFR-010: Maintainability & Code Quality

Requirement: The system shall be designed for long-term maintainability with clear code standards, comprehensive documentation, high test coverage, and adherence to .NET best practices.

Priority: MEDIUM

Code Quality Standards:

Project Structure (Updated per Oct 27 decision):

EGU.Flow/
├── EGU.Flow.AppHost/                    # .NET Aspire orchestration
│   └── Program.cs
│
├── EGU.Flow.Core/                       # Shared contracts, DTOs, interfaces
│   ├── DTOs/
│   │   ├── BatchUploadRequest.cs
│   │   ├── CanonicalInvoice.cs
│   │   └── DeliveryRequest.cs
│   ├── Enums/
│   │   ├── BatchStatus.cs
│   │   ├── DeliveryChannel.cs
│   │   └── VendorCode.cs
│   ├── Interfaces/
│   │   ├── IXmlParserService.cs
│   │   ├── ITemplateRenderingService.cs
│   │   └── IDeliveryService.cs
│   └── Common/
│       ├── Constants.cs
│       └── ErrorCodes.cs
│
├── EGU.Flow.Domain/                     # Domain models and business logic
│   ├── Models/
│   │   ├── Organization.cs
│   │   ├── Batch.cs
│   │   ├── BatchItem.cs
│   │   ├── Template.cs
│   │   └── TemplateCategory.cs
│   └── DomainServices/
│       ├── VendorDetectionService.cs
│       └── DistributionRoutingService.cs
│
├── EGU.Flow.BusinessLogic/              # Business services
│   ├── Services/
│   │   ├── BatchService.cs
│   │   ├── OrganizationService.cs
│   │   ├── TemplateService.cs
│   │   └── SchemaRegistryService.cs
│   └── Mappers/
│       ├── GaselMapper.cs
│       ├── XellentMapper.cs
│       └── ZynergyMapper.cs
│
├── EGU.Flow.CoreApiService/             # ASP.NET Core REST API
│   ├── Controllers/
│   │   ├── BatchController.cs
│   │   ├── OrganizationController.cs
│   │   ├── TemplateController.cs
│   │   └── SchemaController.cs
│   ├── Middleware/
│   │   ├── OrganizationContextMiddleware.cs
│   │   ├── ErrorHandlingMiddleware.cs
│   │   └── RequestLoggingMiddleware.cs
│   └── Program.cs
│
├── EGU.Flow.ParserService/              # Console app: XML → JSON
│   ├── Workers/
│   │   └── BatchParserWorker.cs
│   ├── Parsers/
│   │   ├── GaselParser.cs
│   │   ├── XellentParser.cs
│   │   └── ZynergyParser.cs
│   └── Program.cs
│
├── EGU.Flow.DocumentGenerator/          # Console app: JSON → PDF
│   ├── Workers/
│   │   └── DocumentGeneratorWorker.cs
│   ├── Services/
│   │   ├── HandlebarsRenderingService.cs
│   │   └── PlaywrightPdfService.cs
│   └── Program.cs
│
├── EGU.Flow.EmailService/               # Console app: Email delivery
│   ├── Workers/
│   │   └── EmailDeliveryWorker.cs
│   └── Program.cs
│
├── EGU.Flow.PostalService/              # Console app: 21G integration
│   ├── Workers/
│   │   └── PostalBulkProcessor.cs
│   ├── Services/
│   │   ├── SftpService.cs
│   │   └── ZipArchiveService.cs
│   └── Program.cs
│
├── EGU.Flow.Web/                        # Future: Blazor UI
│   └── (Phase 2)
│
└── EGU.Flow.Tests/                      # Test projects
    ├── EGU.Flow.UnitTests/
    ├── EGU.Flow.IntegrationTests/
    └── EGU.Flow.LoadTests/


 

Coding Standards:

Acceptance Criteria:

CriterionValidation MethodTarget
Code follows .NET conventionsEditorConfig + Roslyn analyzersZero warnings
XML documentation on public APIsDocumentation coverage report100% of public members
Unit test coverageCode coverage report> 70%
Integration test coverageTest execution report> 25% of critical paths
Swagger/OpenAPI for all endpointsVerify Swagger UIAll endpoints documented
Correlation IDs in all logsTrace request through systemSame ID across services
Health check endpoints presentGET /health on all servicesAll respond
Feature flags for gradual rolloutVerify feature flag configurationFlags configurable
Database migration scripts versionedCheck migrations folderSequential numbering
Infrastructure as Code (Bicep)All resources in Bicep templates100% infrastructure
No hardcoded valuesCode scanAll config in appsettings/KeyVault

Documentation Requirements:

Document TypeLocationUpdate FrequencyOwner
API DocumentationSwagger UI at /swaggerEvery API changeDev Team
Architecture DecisionsConfluence ADR pagePer decisionTechnical Architect
Deployment ProceduresConfluence + Git (docs/)Per changeOps Team
Operations RunbookConfluenceMonthly reviewOps Manager
Disaster Recovery PlanConfluence (restricted)QuarterlyOps Manager
GDPR DocumentationConfluence (restricted)Annual reviewLegal/Compliance
Test Data Generation GuideGit (docs/)Per updateQA Team

Dependencies:



4.11 NFR-011: Usability & Developer Experience

Requirement: The API shall be intuitive, well-documented, easy to integrate, with clear error messages, code samples, and Postman collections.

Priority: MEDIUM

API Design Principles:

RESTful Standards:

Error Message Quality:

Bad Example:

{
  "error": "Invalid input"
}


 

Good Example:

{
  "success": false,
  "errors": [
    {
      "code": "INVALID_XML",
      "message": "XML file is not well-formed",
      "field": "file",
      "details": {
        "line": 142,
        "column": 23,
        "error": "Unexpected end tag: </Invoice>. Expected: </InvoiceHeader>",
        "suggestion": "Check that all opening tags have matching closing tags in the correct order",
        "documentationUrl": "https://docs.egflow.com/errors/INVALID_XML"
      }
    }
  ]
}


 

Acceptance Criteria:

CriterionValidation MethodTarget
RESTful design principles followedAPI design reviewAll principles followed
Consistent request/response structureReview all endpointsSame envelope
Error messages include suggestionsTest error scenariosActionable guidance
Error messages include documentation linksCheck error responseURLs present
Line/column numbers for XML errorsUpload invalid XMLPosition info present
Comprehensive Swagger documentationReview Swagger UIAll endpoints, examples
Code samples for common operationsCheck documentationC#, curl examples
Postman collection availableImport collection, run requestsAll requests work
API versioning clearCheck URL structure/v1/ in all paths
Deprecation warnings 6 months advanceDeprecate endpointWarning in response

Postman Collection Contents:

EG Flow API Collection/
├── Authentication/
│   └── Get Access Token
├── Batch Management/
│   ├── Upload Batch
│   ├── Start Batch Processing
│   ├── Get Batch Status
│   ├── List Batch Items
│   └── Get Batch Item Details
├── Organization/
│   ├── Get Organization
│   └── Update Organization Config
├── Templates/
│   ├── List Templates
│   ├── Get Template
│   └── List Template Categories
└── Schema Management/
    ├── List Supported Formats
    └── Validate XML


 

Dependencies:



4.12 NFR-012: Internationalization (Nordic Languages)

Requirement: The system shall support Swedish language for invoices, email notifications, error messages, and UI elements, with foundation for future Norwegian, Danish, and Finnish support.

Priority: MEDIUM

Localization Requirements:

Phase 1: Swedish Only

Phase 2: Multi-Language (Future)

Swedish-Specific Formatting:

Dates:
- Invoice date: "2025-11-21" (ISO format, universal)
- Display: "21 november 2025" (Swedish long format)

Numbers:
- Amount: 1 234,56 (space separator, comma decimal)
- Percentage: 25,0% (comma decimal)
- Quantity: 1 234 (integer, space separator)

Currency:
- Symbol: "kr" or "SEK"
- Position: After amount "1 234,56 kr"

Swedish Terms:
- Faktura (Invoice)
- Förfallodatum (Due date)
- Mätpunkt (Metering point)
- Elförbrukning (Electricity consumption)
- Att betala (Amount to pay)
- Moms (VAT)


 

Handlebars Helpers for Swedish Formatting:

{{!-- Swedish number formatting --}}
{{formatNumber amount decimals=2}}
→ "1 234,56"

{{!-- Swedish currency --}}
{{formatCurrency amount}}
→ "1 234,56 kr"

{{!-- Swedish date --}}
{{formatDate date format="long"}}
→ "21 november 2025"

{{!-- Swedish percentage --}}
{{formatPercent rate}}
→ "25,0%"


 

Acceptance Criteria:

CriterionValidation MethodTarget
Invoice templates in SwedishReview rendered PDFSwedish text
Email notifications in SwedishReceive test emailSwedish subject/body
Error messages in SwedishTrigger various errorsSwedish messages
Numbers formatted Swedish styleCheck invoice amounts"1 234,56" format
Dates in ISO 8601Check invoice JSON"2025-11-21" format
Currency symbol positioned correctlyCheck rendered invoice"kr" after amount
Swedish characters (åäö) render correctlyPDF with åäöCharacters correct
Time zone CET/CEST usedCheck timestampsEurope/Stockholm

Dependencies:



4.13 NFR-013: API Backward Compatibility

Requirement: API versions shall maintain backward compatibility for minimum 12 months after new version release, with clear deprecation warnings and migration guides.

Priority: HIGH

Versioning Strategy:

URL Path Versioning:

Current: https://api.egflow.com/v1/organizations/{id}/batches
Future: https://api.egflow.com/v2/organizations/{id}/batches

Both versions run simultaneously during transition period


 

Version Lifecycle:

v1.0 Released: January 2026
  ↓
v2.0 Released: January 2027
  ↓ (v1 deprecation announced)
v1 Supported: Until January 2028 (12 months)
  ↓
v1 Sunset: January 2028


 

Deprecation Warning (HTTP Header):

HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 31 Jan 2028 23:59:59 GMT
Link: <https://docs.egflow.com/api/v2/migration>; rel="successor-version"


 

Acceptance Criteria:

CriterionValidation MethodTarget
v1 supported 12 months after v2 releaseVerify both versions workBoth return 200
Breaking changes only in major versionsReview v1.1, v1.2 changesNo breaking changes
Deprecation warnings 6 months advanceCheck headers 6 months before sunsetDeprecation header present
Migration guide publishedReview documentationComplete guide available
v1 clients continue working during v2 rolloutTest v1 client after v2 deployNo disruption