SkyBrief - Product Architecture¶
System Overview¶
Architecture Pattern: Hexagonal (Ports & Adapters)¶
The system uses Clean Architecture with hexagonal pattern to ensure: - Testability: Domain logic independent of infrastructure - Extensibility: Easy to add NOTAMs later (new adapter) - Compliance: Clear audit boundaries for EASA requirements
┌─────────────────────────────────────────────────────────────┐
│ ADAPTERS (Infrastructure) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ HTTP API │ │ PostgreSQL │ │ External Services │ │
│ │ (Primary) │ │ (Database) │ │ (Weather, Email) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
│ │ │ │ │
└─────────┼────────────────┼────────────────────┼─────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ PORTS (Interfaces) │
│ Repository │ External Service │
│ Interfaces │ Interfaces │
└───────────────────────────┼─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ APPLICATION LAYER (Use Cases) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Generate │ │ Check │ │ Predict │ │
│ │ Briefing │ │ Compliance │ │ Weather │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Analyze │ │ Create │ │ Send │ │
│ │ Trends │ │ Report │ │ Alerts │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ DOMAIN LAYER (Core Logic) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Flight │ │ Weather │ │ Compliance │ │
│ │ Entity │ │ Reading │ │ Rules │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Alert │ │ Route │ │ Operator │ │
│ │ Entity │ │ Entity │ │ Entity │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘ Core Components¶
1. Domain Layer (Business Logic)¶
Entities:
// Flight represents a planned flight
type Flight struct {
ID uuid.UUID
OperatorID uuid.UUID
Callsign string
Origin Airport
Destination Airport
ETD time.Time
ETA time.Time
Aircraft Aircraft
Status FlightStatus
CreatedAt time.Time
}
// WeatherReading represents captured weather data
type WeatherReading struct {
ID uuid.UUID
FlightID uuid.UUID
Type WeatherType // METAR, TAF, SIGMET
Station string // ICAO code
Raw string // Original text
Parsed WeatherData // Structured data
Timestamp time.Time
Source string // API source
}
// ComplianceReport represents EASA audit documentation
type ComplianceReport struct {
ID uuid.UUID
FlightID uuid.UUID
GeneratedAt time.Time
WeatherReviewed []WeatherReview
Valid bool
EASASection string // e.g., "CAT.OP.MPA.175"
Document Document // PDF/JSON
} Domain Services: - ComplianceValidator: Validates EASA compliance rules - WeatherAnalyzer: Analyzes weather trends and patterns - AlertEngine: Generates predictive alerts
2. Application Layer (Use Cases)¶
Primary Use Cases:
A. Generate Weather Briefing
type GenerateBriefingRequest struct {
FlightID uuid.UUID
UserID uuid.UUID
}
type GenerateBriefingResponse struct {
Briefing WeatherBriefing
ComplianceDoc ComplianceReport
Alerts []Alert
} B. Check Compliance Status
// Validates all required weather elements are present
// Generates compliance documentation C. Predict Weather Deterioration
// Uses Fireworks.ai to analyze trends
// Generates alerts: "Conditions deteriorating faster than forecast" 3. Adapter Layer (Infrastructure)¶
A. Weather Data Adapters
// WeatherSource interface (Port)
type WeatherSource interface {
FetchMETAR(ctx context.Context, icao string) (*METAR, error)
FetchTAF(ctx context.Context, icao string) (*TAF, error)
FetchSIGMET(ctx context.Context, region string) ([]SIGMET, error)
}
// AviationWeatherAdapter (US - FREE)
type AviationWeatherAdapter struct {
baseURL string
client *http.Client
}
// OpenMeteoAdapter (EU - €150/month)
type OpenMeteoAdapter struct {
apiKey string
baseURL string
client *http.Client
}
// Future: NOTAMSource interface (v2)
type NOTAMSource interface {
FetchNOTAMs(ctx context.Context, route Route) ([]NOTAM, error)
} B. Database Adapter
type FlightRepository interface {
Create(ctx context.Context, flight *Flight) error
GetByID(ctx context.Context, id uuid.UUID) (*Flight, error)
ListByOperator(ctx context.Context, operatorID uuid.UUID) ([]Flight, error)
}
type ComplianceRepository interface {
SaveReport(ctx context.Context, report *ComplianceReport) error
GetReportByFlight(ctx context.Context, flightID uuid.UUID) (*ComplianceReport, error)
ListByDateRange(ctx context.Context, operatorID uuid.UUID, start, end time.Time) ([]ComplianceReport, error)
} C. Intelligence Adapter (Fireworks.ai)
type IntelligenceService interface {
AnalyzeTrend(ctx context.Context, readings []WeatherReading) (*TrendAnalysis, error)
GenerateAlert(ctx context.Context, analysis TrendAnalysis) (*Alert, error)
}
type FireworksAdapter struct {
apiKey string
model string // e.g., "accounts/fireworks/models/mixtral-8x7b"
client *http.Client
} Technology Stack¶
Backend¶
| Component | Technology | Rationale |
|---|---|---|
| Language | Go 1.21+ | Your expertise, single binary, fast |
| Framework | Echo or Fiber | Lightweight, fast, good middleware support |
| Architecture | Hexagonal/Clean | Testable, extensible, compliance-friendly |
| Database | PostgreSQL 15+ | ACID compliance (audit trails), PostGIS for spatial |
| Cache | Redis (optional v1.1) | Session + weather data caching |
| Queue | PostgreSQL (initially) | Background jobs, simple to start |
| PDF Generation | Gotenberg (Docker) | Open source, API-based, EU-hosted |
| SendGrid/Postmark | Reliable delivery |
Frontend¶
| Component | Technology | Rationale |
|---|---|---|
| Framework | React 18+ | Component-based, ecosystem |
| Language | TypeScript | Type safety |
| Styling | Tailwind CSS | Fast development |
| State | React Query + Zustand | Server + client state management |
| Maps | Leaflet + OpenStreetMap | Free, no API keys needed |
Infrastructure¶
| Component | Technology | Rationale |
|---|---|---|
| Hosting | Hetzner Cloud (Germany) | EU data residency, cost-effective, you know it |
| Server | CX42 (4 vCPU, 16GB RAM) | €20.76/month, room to grow |
| Storage | Block storage + S3 (Hetzner) | Reports, backups |
| Database | Self-hosted PostgreSQL | Full control, compliance |
| Monitoring | Grafana + Prometheus | Open source, EU-hosted |
| CI/CD | GitHub Actions | Free for public repos |
| DNS | Cloudflare | CDN + security |
Data Sources¶
| Data | Source | Cost | Adapter |
|---|---|---|---|
| METAR/TAF | AviationWeather.gov | FREE | AviationWeatherAdapter |
| EU Forecasts | Open-Meteo Professional | €150/month | OpenMeteoAdapter |
| Airspace | OpenAIP | FREE | OpenAIPAdapter |
| Airports | OurAirports | FREE | OurAirportsAdapter (static import) |
| NOTAMs (v2) | EUROCONTROL EAD | €100-300/mo | NOTAMAdapter (future) |
| Intelligence | Fireworks.ai | ~$5/month | FireworksAdapter |
Database Schema¶
Core Tables¶
-- Operators (Part-CAT, Part-NCC)
CREATE TABLE operators (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
country VARCHAR(2) NOT NULL, -- ISO code
operator_type VARCHAR(50) NOT NULL, -- Part-CAT, Part-NCC
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Aircraft
CREATE TABLE aircraft (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
operator_id UUID REFERENCES operators(id),
registration VARCHAR(20) NOT NULL,
type VARCHAR(100) NOT NULL, -- e.g., "Cessna Citation X"
icao_type VARCHAR(10), -- e.g., "C56X"
home_base VARCHAR(4) -- ICAO airport code
);
-- Flights
CREATE TABLE flights (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
operator_id UUID REFERENCES operators(id),
aircraft_id UUID REFERENCES aircraft(id),
callsign VARCHAR(20),
origin VARCHAR(4) NOT NULL, -- ICAO
destination VARCHAR(4) NOT NULL, -- ICAO
etd TIMESTAMP NOT NULL, -- Estimated Time of Departure
eta TIMESTAMP, -- Estimated Time of Arrival
status VARCHAR(50) DEFAULT 'planned',
created_by UUID NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Weather Readings (compliance audit trail)
CREATE TABLE weather_readings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
flight_id UUID REFERENCES flights(id),
reading_type VARCHAR(20) NOT NULL, -- METAR, TAF, SIGMET
station VARCHAR(4) NOT NULL, -- ICAO
raw_text TEXT NOT NULL, -- Original format
parsed_data JSONB, -- Structured data
timestamp TIMESTAMP NOT NULL,
source VARCHAR(100) NOT NULL, -- Which adapter fetched this
fetched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Compliance Reports (EASA audit documentation)
CREATE TABLE compliance_reports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
flight_id UUID REFERENCES flights(id),
generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
generated_by UUID NOT NULL,
easa_regulation VARCHAR(50) NOT NULL, -- e.g., "CAT.OP.MPA.175"
is_compliant BOOLEAN NOT NULL,
compliance_data JSONB, -- Details of what was checked
document_path VARCHAR(500), -- Path to PDF
retention_until TIMESTAMP -- 12 months per EASA
);
-- Alerts (predictive intelligence)
CREATE TABLE alerts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
flight_id UUID REFERENCES flights(id),
alert_type VARCHAR(50) NOT NULL, -- DETERIORATION, BELOW_MINIMA, etc.
severity VARCHAR(20) NOT NULL, -- LOW, MEDIUM, HIGH, CRITICAL
message TEXT NOT NULL,
recommendation TEXT,
triggered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
acknowledged_at TIMESTAMP,
acknowledged_by UUID
);
-- Users (operators, dispatchers, chief pilots)
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
operator_id UUID REFERENCES operators(id),
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL, -- admin, dispatcher, pilot, readonly
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); Future Tables (v2 - NOTAMs)¶
-- NOTAMs (placeholder for future implementation)
CREATE TABLE notams (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
notam_id VARCHAR(50) UNIQUE NOT NULL,
location VARCHAR(4) NOT NULL,
notam_type VARCHAR(20) NOT NULL, -- NEW, REPLACE, CANCEL
subject VARCHAR(100),
condition TEXT,
valid_from TIMESTAMP,
valid_until TIMESTAMP,
raw_text TEXT
);
-- Flight-NOTAM associations
CREATE TABLE flight_notams (
flight_id UUID REFERENCES flights(id),
notam_id UUID REFERENCES notams(id),
is_relevant BOOLEAN DEFAULT true,
PRIMARY KEY (flight_id, notam_id)
); Hexagonal Architecture Implementation¶
Directory Structure¶
/
├── cmd/
│ └── api/ # Application entry point
│ └── main.go
├── internal/
│ ├── domain/ # Domain layer (entities, business rules)
│ │ ├── flight.go
│ │ ├── weather.go
│ │ ├── compliance.go
│ │ └── alert.go
│ ├── application/ # Application layer (use cases)
│ │ ├── briefing/
│ │ │ ├── generate.go
│ │ │ └── service.go
│ │ ├── compliance/
│ │ │ └── check.go
│ │ └── intelligence/
│ │ └── predict.go
│ ├── ports/ # Interface definitions
│ │ ├── weather.go # WeatherSource interface
│ │ ├── repository.go // Repository interfaces
│ │ └── intelligence.go // IntelligenceService interface
│ └── adapters/ # Infrastructure implementations
│ ├── http/
│ │ ├── handlers/
│ │ └── middleware/
│ ├── persistence/
│ │ ├── postgres/
│ │ │ ├── flight_repo.go
│ │ │ └── compliance_repo.go
│ │ └── redis/
│ ├── weather/
│ │ ├── aviationweather.go
│ │ ├── openmeteo.go
│ │ └── composite.go // Aggregates multiple sources
│ └── intelligence/
│ └── fireworks.go
├── pkg/ # Shared utilities
│ ├── pdf/
│ └── validation/
├── migrations/ # Database migrations
└── configs/ # Configuration files Example: Adding NOTAMs Later (v2)¶
When adding NOTAM support, you only need to:
-
Create the port interface:
// internal/ports/notam.go type NOTAMSource interface { FetchNOTAMs(ctx context.Context, route Route) ([]NOTAM, error) } -
Implement the adapter:
// internal/adapters/notam/eurocontrol.go type EurocontrolAdapter struct { // Implementation } func (a *EurocontrolAdapter) FetchNOTAMs(...) ([]NOTAM, error) { // Call EUROCONTROL EAD API } -
Update the composite briefing service:
// Just add NOTAMSource to the list of sources // No changes to domain or application layers
The domain logic for compliance remains unchanged.
Authentication & Authorization¶
Provider: Clerk (Cloud OIDC)¶
Why Clerk: - FREE tier: 50,000 monthly retained users, 100 organizations, 20 members per org - B2B features: Built-in organization management, roles, permissions - Time to implement: 30 minutes vs 2-3 days for self-hosted - Cost: FREE until 100 operators × 20 users = 2,000 users - EU compliance: Data Privacy Framework (DPF) certified, GDPR compliant
Alternative considered: Auth0 (25,000 users free, but B2B org features limited)
Role Structure¶
// Organization Roles (Clerk built-in + custom)
const (
RoleAdmin = "admin" // Chief pilot, ops manager
RoleDispatcher = "dispatcher" // Creates flights, generates briefings
RolePilot = "pilot" // Views briefings, acknowledges alerts
RoleReadOnly = "readonly" // Auditors, trainees
) Permissions Matrix:
| Feature | Admin | Dispatcher | Pilot | ReadOnly |
|---|---|---|---|---|
| Create flights | ✅ | ✅ | ❌ | ❌ |
| Generate briefings | ✅ | ✅ | ❌ | ❌ |
| View briefings | ✅ | ✅ | ✅ | ✅ |
| Download compliance PDF | ✅ | ✅ | ✅ | ✅ |
| Acknowledge alerts | ✅ | ✅ | ✅ | ❌ |
| Manage aircraft | ✅ | ✅ | ❌ | ❌ |
| Manage users | ✅ | ❌ | ❌ | ❌ |
| View billing | ✅ | ❌ | ❌ | ❌ |
| API access | ✅ | ✅ | ❌ | ❌ |
Architecture¶
┌─────────────────────────────────────────┐
│ CLERK (Cloud OIDC) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Organization│ │ User Mgmt │ │
│ │ (Operator) │ │ + Roles │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ JWT Tokens │ │ Sessions │ │
│ │ (Custom │ │ (7-day free │ │
│ │ claims) │ │ lifetime) │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────┬───────────────────────┘
│
▼ JWT + Org ID
┌─────────────────────────────────────────┐
│ YOUR GO BACKEND │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Auth │ │ Role │ │
│ │ Middleware │ │ Middleware │ │
│ │ (verify │ │ (check │ │
│ │ JWT) │ │ perms) │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ │
│ │ Org Context │ Injects org_id │
│ │ (tenancy) │ into requests │
│ └──────────────┘ │
└─────────────────────────────────────────┘ Backend Implementation (Go)¶
JWT Verification Middleware:
// internal/adapters/http/middleware/auth.go
type ClerkClaims struct {
UserID string `json:"sub"`
OrgID string `json:"org_id"` // Custom claim
Role string `json:"org_role"` // Custom claim
Permissions []string `json:"org_permissions"`
Metadata map[string]interface{} `json:"public_metadata"`
}
func ClerkAuthMiddleware(clerkSecret string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
token := extractBearerToken(c)
claims, err := verifyClerkJWT(token, clerkSecret)
if err != nil {
return echo.NewHTTPError(401, "Invalid token")
}
// Add to context
c.Set("user_id", claims.UserID)
c.Set("org_id", claims.OrgID)
c.Set("role", claims.Role)
c.Set("permissions", claims.Permissions)
return next(c)
}
}
} Role-Based Access Middleware:
// internal/adapters/http/middleware/rbac.go
func RequireRole(allowedRoles ...string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
userRole := c.Get("role").(string)
for _, role := range allowedRoles {
if role == userRole {
return next(c)
}
}
return echo.NewHTTPError(403, "Insufficient permissions")
}
}
}
// Usage:
// e.POST("/flights", createFlight, RequireRole("admin", "dispatcher")) Organization Context (Multi-tenancy):
// Every request is scoped to an organization
// Database queries automatically filter by org_id
func GetFlights(ctx context.Context, orgID uuid.UUID) ([]Flight, error) {
query := `
SELECT * FROM flights
WHERE operator_id = $1
ORDER BY created_at DESC
`
// orgID comes from JWT context
return db.Query(ctx, query, orgID)
} Frontend Implementation (React)¶
Setup:
npm install @clerk/clerk-react Configuration:
// src/main.tsx
import { ClerkProvider } from '@clerk/clerk-react';
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;
ReactDOM.createRoot(document.getElementById('root')!).render(
<ClerkProvider publishableKey={PUBLISHABLE_KEY}>
<App />
</ClerkProvider>
); Protected Routes:
// src/components/ProtectedRoute.tsx
import { useAuth, useOrganization } from '@clerk/clerk-react';
import { Navigate } from 'react-router-dom';
export function ProtectedRoute({
children,
allowedRoles
}: {
children: React.ReactNode;
allowedRoles?: string[];
}) {
const { isLoaded, userId } = useAuth();
const { organization, membership } = useOrganization();
if (!isLoaded) return <Loading />;
if (!userId) return <Navigate to="/sign-in" />;
if (!organization) return <Navigate to="/select-org" />;
if (allowedRoles && !allowedRoles.includes(membership?.role)) {
return <Navigate to="/unauthorized" />;
}
return <>{children}</>;
} Custom JWT Claims (Clerk Dashboard)¶
In Clerk Dashboard → JWT Templates:
{
"org_id": "{{org.id}}",
"org_role": "{{org.role}}",
"org_permissions": "{{org.permissions}}",
"operator_metadata": {
"plan": "{{org.public_metadata.plan}}",
"max_aircraft": "{{org.public_metadata.max_aircraft}}"
}
} Security Features¶
Clerk Security (Free Tier): - ✅ Brute force protection - ✅ Bot detection - ✅ Breached password detection - ✅ Session management - ✅ 2FA/MFA (available in Pro)
Implementation Security: - ✅ Always verify JWT on backend - ✅ HTTPS only - ✅ Secure, httpOnly cookies - ✅ CSRF protection - ✅ Audit logging (who did what when)
Cost Analysis¶
| Scenario | Users | Cost |
|---|---|---|
| MVP (Month 1-6) | 50 users across 10 operators | FREE |
| Growth (Month 6-12) | 200 users across 40 operators | FREE |
| Scale (Year 2) | 500 users across 100 operators | FREE |
| Beyond | 2,000+ users | $20/month (Clerk Pro) |
API Design¶
REST Endpoints¶
# Authentication (handled by Clerk, endpoints for reference)
POST /api/v1/auth/login # Clerk hosted UI
POST /api/v1/auth/refresh # Automatic via Clerk SDK
POST /api/v1/auth/logout # Clerk SDK
# Flights
GET /api/v1/flights # List flights
POST /api/v1/flights # Create flight
GET /api/v1/flights/{id} # Get flight details
PUT /api/v1/flights/{id} # Update flight
DELETE /api/v1/flights/{id} # Delete flight
# Weather Briefings
POST /api/v1/flights/{id}/briefing # Generate briefing
GET /api/v1/flights/{id}/briefing # Get latest briefing
GET /api/v1/briefings/{briefingId}/pdf # Download PDF
# Compliance
GET /api/v1/flights/{id}/compliance # Check compliance status
GET /api/v1/compliance/reports # List compliance reports
GET /api/v1/compliance/export # Export for audit
# Intelligence
GET /api/v1/flights/{id}/alerts # Get active alerts
POST /api/v1/alerts/{id}/acknowledge # Acknowledge alert
GET /api/v1/alerts/history # Alert history
# Airports (reference data)
GET /api/v1/airports # Search airports
GET /api/v1/airports/{icao} # Get airport details
GET /api/v1/airports/{icao}/weather # Current weather Security & Compliance¶
GDPR (EU Data Protection)¶
| Requirement | Implementation |
|---|---|
| Data residency | Hetzner Germany (EU servers) |
| Encryption in transit | TLS 1.3 |
| Encryption at rest | PostgreSQL encryption, file system encryption |
| Access control | JWT tokens, role-based access |
| Audit logging | All actions logged with user + timestamp |
| Right to erasure | API endpoint + automated process |
| Data minimization | Only collect required fields |
EASA Compliance Features¶
- Immutable audit trail: All weather readings timestamped and signed
- Retention management: Automatic 12-month retention per ORO.MLR.100
- Data integrity: Checksums on compliance reports
- Access logs: Who accessed what weather data when
Deployment Architecture¶
Single Server Setup (MVP)¶
Hetzner CX42 (Germany)
├── Docker Compose
│ ├── app (Go binary)
│ ├── postgres (PostgreSQL 15)
│ ├── gotenberg (PDF generation)
│ └── redis (optional)
├── Nginx (reverse proxy + SSL)
└── Let's Encrypt (auto SSL renewal) Scaling Path¶
| Phase | Users | Infrastructure | Cost |
|---|---|---|---|
| MVP | 1-10 | Single CX42 | €30/month |
| Growth | 10-50 | CX42 + managed DB | €100/month |
| Scale | 50-200 | Load balancer + 2x app servers | €300/month |
Monitoring & Observability¶
Metrics to Track¶
Business Metrics: - Briefings generated per day - Compliance reports created - Alerts triggered and acknowledged - Time saved per briefing (user-reported)
Technical Metrics: - API response times (p50, p95, p99) - Weather API latency and error rates - Database query performance - PDF generation time
Alerts: - Weather API failures - High error rates - Database connection issues - Disk space warnings
Tools¶
- Metrics: Prometheus + Grafana
- Logging: Structured JSON logs (Go slog)
- Error tracking: Sentry
- Uptime: UptimeRobot or Grafana Cloud
Related Documents¶
- Build Plan - SkyBrief
- Go-To-Market Strategy (updated)
- Action Items (updated)
- EU Aviation Weather Regulatory Research Report
Document Version: 2.0
Last Updated: March 2026
Next Review: After Week 4 validation gate