Implement comprehensive weather parsing capabilities following Clean Architecture principles with composite parser pattern for routing between aviation and weather messages. ## Features Added - Weather report parsing (METAR, SPECI, TAF) - Composite parser pattern for message routing - Lenient parsing with warnings for unrecognized tokens - Support for PROB and RMK sections in TAF - Rich domain modeling with typed weather elements ## Architecture **Domain Layer** (internal/domain/weather/): - WeatherMessage interface with Metar and Taf implementations - Weather elements: Wind, Visibility, Cloud, Temperature, Altimeter, Phenomenon - Domain errors: ErrInvalidFormat, ErrMissingStation, ErrMissingTime **Port Layer** (internal/port/weather_parser.go): - WeatherParser interface with CanParse and Parse methods **Adapter Layer** (internal/adapter/parser/weather/): - WeatherParserImpl with classification and parsing logic - Comprehensive regex patterns for weather elements - METAR/SPECI parser with element extraction - TAF parser with period handling (FM, TEMPO, BECMG, PROB) - Helper functions for time parsing and unit conversions **Composite Parser** (internal/adapter/parser/composite.go): - Routes weather reports to weather parser - Falls back to aviation parser for telegrams - Converts WeatherMessage to ParsedTelegram format ## Integration - Updated ProvideParser to create composite parser with weather parser - Added weather parser to Wire DI configuration - Updated processor_bench_test.go for weather parser integration - Documentation added in docs/weather-parser.md ## Testing - 29 comprehensive tests for weather parsing (all passing) - Tests for classification, METAR, SPECI, TAF, and composite routing - Benchmark compatibility maintained ## Fixes Applied - TAF PROB parsing: Include PROB/RMK in special section detection - Composite test: Updated to use properly formatted AFTN telegram - Linter issues: Switch statement refactor, removed unused patterns - Ineffective break statement fixed in TAF parser ## Coverage ~1,743 lines of new code with: - Complete METAR/SPECI parsing - TAF parsing with period support - Lenient error handling with warnings - Unit conversions and time utilities 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
9.4 KiB
Development Guide
This document describes how to run the full development stack—database, NATS, and observability tooling—using docker-compose.dev.yml. All commands assume you are at the repository root.
Core Services (TimescaleDB + NATS)
Spin up PostgreSQL/TimescaleDB and NATS JetStream in the background:
docker compose -f docker-compose.dev.yml up -d postgres nats nats-box
NATS Mode: The application uses JetStream mode exclusively for message persistence, ACK/NAK, automatic retries, and DLQ support. This ensures consistent behavior across development, testing, and production environments.
postgresseeds theaviationschema usinginternal/infra/postgres/telegrams.ddland exposes port5432.natsenables JetStream with client port4222and monitoring/UI on8222.nats-boxprovides a toolbox container (docker compose exec nats-box sh) for publishing test messages or inspecting JetStream.nats-exporterscrapes the monitoring endpoints (/varz,/connz,/routez,/subz) and exposes them as Prometheus metrics on port7777for the Grafana dashboards.
Prefer to run the Go application on your host for quick iteration while keeping infra in Docker:
GO_ENV=dev \
CAATSM_POSTGRES_URL=postgres://caatsm:caatsm@localhost:5432/aviation?sslmode=disable \
go run ./cmd/main listen
Stop and clean the stack when finished:
docker compose -f docker-compose.dev.yml down -v
In development (GO_ENV=dev or unset), if you run the application while
stopping and recreating the NATS/JetStream containers (for example:
docker compose -f docker-compose.dev.yml down -v
docker compose -f docker-compose.dev.yml up -d postgres nats nats-box
), the JetStream state will be reset. The processor behaves as follows:
- The NATS client keeps retrying the connection and automatically reconnects when NATS is back.
- The JetStream consumer expects streams and consumers to exist.
- In development, you may need to create them manually or ensure they exist before starting the application.
Using Taskfile shortcuts
The Taskfile.yml includes helper targets that wrap the commands above:
task up– starts PostgreSQL, NATS (JetStream, toolbox, and Prometheus exporter), and the observability stack (OpenTelemetry Collector, Jaeger, Prometheus, Grafana) using Docker Compose.task dev-run– ensurestask uphas run, exports the necessaryCAATSM_*environment variables, and executesgo run ./cmd/main listenwith telemetry enabled.task down– stops the entire stack and removes containers/volumes.
Use these tasks if you prefer a one-command workflow instead of invoking docker compose and environment exports manually.
Publishing Sample Telegrams
Use the helper CLI in cmd/seed-telegrams to push realistic payloads onto NATS. The tool supports both aviation telegrams (FPL, ARR, DEP, etc.) and weather reports (METAR, SPECI, TAF).
Publishing to JetStream (Recommended)
When using JetStream mode, publish messages to the JetStream stream:
# Publish to JetStream stream (messages are persisted)
GO_ENV=dev go run ./cmd/seed-telegrams \
--nats-url nats://127.0.0.1:4222 \
--jetstream \
--stream TELEGRAM \
--js-subject telegram.serial \
--count 20 \
--category mixed \
--status random
Using Makefile/Taskfile Tasks
For convenience, you can use the provided tasks:
# Quick seed (10 messages, JetStream)
make seed
# or
task seed
# Continuous slow seeding (until Ctrl-C, JetStream)
make seed-slow
# or
task seed-slow
# Customize slow seeding
make seed-slow INTERVAL_MIN=3s INTERVAL_MAX=8s CATEGORY=ARR
task seed-slow INTERVAL_MIN=1s INTERVAL_MAX=2s STATUS=parsed
Continuous Slow Seeding
For long-running tests and monitoring, use the seed-slow task to continuously send messages at a configurable interval until you press Ctrl-C:
# Default: 2-5 second intervals, JetStream mode
make seed-slow
# Custom interval and category
make seed-slow INTERVAL_MIN=5s INTERVAL_MAX=10s CATEGORY=DEP
This is equivalent to running:
go run ./cmd/seed-telegrams \
--nats-url nats://localhost:4222 \
--jetstream \
--stream TELEGRAM \
--js-subject telegram.serial \
--mode interval \
--interval-min 2s \
--interval-max 5s \
--count 0
Setting --count 0 makes it run indefinitely until interrupted.
Common Options
--postgres-url: Insert rows intoaviation.telegrams_raw(omit to skip DB writes)--dry-run: Print telegrams without publishing to NATS/Postgres--category: Choose message type (ARR|DEP|CNL|DLA|FPL|mixed)--status: Control stored/published status (parsed|header_error|body_error|publish_error|repository_error|random)--no-nats: Disable publishing to NATS--jetstream: Enable JetStream publishing (requires--streamand--js-subject)--stream: JetStream stream name (default:TELEGRAM)--js-subject: Subject within the JetStream stream--mode: Seed mode (burst|interval|mixed)--interval-min/--interval-max: Time interval between messages in interval/mixed modes--count: Number of messages to send (0 = infinite, until Ctrl-C)
Inspecting Messages
# View messages in JetStream stream
docker compose exec nats-box nats stream view TELEGRAM
# Subscribe to messages (JetStream)
docker compose exec nats-box nats sub 'telegram.>'
# View consumer status and pending messages
docker compose exec nats-box nats consumer info TELEGRAM telegram-consumer
The main processor keeps consuming subscription.topic (defaults to telegram.>). Use the seeder to simulate parser failures, publish errors, or replay raw telegrams directly from the database.
Tracing with Jaeger
-
Start the observability stack
docker compose -f docker-compose.dev.yml up -d otel-collector jaeger prometheus grafana- Jaeger UI runs at http://localhost:16686.
- The OTLP HTTP collector endpoint is available at
http://localhost:4318.
-
Run the processor with telemetry enabled
CAATSM_TELEMETRY_ENABLED=true \ CAATSM_TELEMETRY_ENDPOINT=localhost:4318 \ CAATSM_TELEMETRY_INSECURE=true \ GO_ENV=dev \ CAATSM_POSTGRES_URL=postgres://caatsm:caatsm@localhost:5432/aviation?sslmode=disable \ go run ./cmd/main listen- The service name reported to Jaeger is
caatsm.
- The service name reported to Jaeger is
-
Generate traffic
task seed COUNT=5or publish manually with
go run ./cmd/seed-telegrams. -
Inspect traces
- Open http://localhost:16686, choose the
caatsmservice, and click “Find Traces”. - Filter by operation name (e.g.,
Consumer.processMessage) or by time range to drill into individual telegram processing flows.
- Open http://localhost:16686, choose the
Observability Dashboard Stack
The dev compose file also includes OpenTelemetry Collector, Jaeger, Prometheus, and Grafana so you can inspect traces and metrics emitted by the processor.
docker compose -f docker-compose.dev.yml up -d \
postgres nats otel-collector jaeger prometheus grafana
Services:
otel-collector- Loads
configs/otel-collector.dev.yaml - Ports: OTLP gRPC
4317, OTLP HTTP4318, Prometheus scrape8888, Prometheus exporter8889, health13133, zPages55679 - Exports traces to Jaeger via the built-in OTLP gRPC exporter (secured with
tls.insecure: true)
- Loads
jaeger- Receives OTLP traffic forwarded from the collector on
14250gRPC and serves the UI at http://localhost:16686
- Receives OTLP traffic forwarded from the collector on
prometheus- Uses
configs/prometheus.dev.ymlto scrape the collector,nats-exporter(http://nats-exporter:7777/metrics), and application OTLP metrics forwarded via the collector; UI available at http://localhost:9090
- Uses
grafana- Persists data in
grafana-data, provisions datasources viaconfigs/grafana-datasources.dev.yml, and listens on http://localhost:3000 (loginadmin/admin) - Automatically loads dashboards from
configs/grafana-dashboards.dev/, including OpenTelemetry Collector and NATS/JetStream overviews (find them under the Dev Observability folder) - The OpenTelemetry dashboard also charts the CAATSM-specific metrics
caatsm_messages_processed_total,caatsm_publish_failures_total, andcaatsm_parse_duration_ms(percentiles) so you can track throughput and parsing latency. - Note:
caatsm_parse_duration_mshas been renamed tocaatsm_parse_duration_secondsto align with Prometheus_secondsconventions.
- Persists data in
Customizing Collections & Dashboards
- Adjust
configs/prometheus.dev.ymlto add/remove scrape jobs—for example, include your application’s/metricsendpoint. - Add more Grafana provisioning files (dashboards, alert rules) under
configs/and mount them indocker-compose.dev.yml. - To ingest telemetry from local services, configure their OTLP exporters to target
http://localhost:4318(HTTP) orgrpc://localhost:4317.
Troubleshooting
- PostgreSQL init errors: ensure
internal/infra/postgres/telegrams.ddlis valid SQL and thepostgres-datavolume is removed (docker volume rm go-caatsm_postgres-data) before restarting. - NATS connection failures: confirm ports
4222/8222are free and JetStream is enabled; usedocker compose logs nats. - Prometheus scrape failures: verify endpoints listed in
configs/prometheus.dev.ymlmatch the service names defined in Docker Compose. - Grafana provisioning issues: check container logs (
docker compose logs grafana) to ensure the datasources file was read; correct file permissions or YAML formatting if provisioning is skipped.