99 lines
6.3 KiB
Markdown
99 lines
6.3 KiB
Markdown
## Purpose
|
|
|
|
Defines stream consumption, safe parsing, schema normalization, micro-batching into ClickHouse, and at-least-once offset management for raw telecom CDRs.
|
|
|
|
## ADDED Requirements
|
|
|
|
### Requirement: Safe Parsing and Fault Tolerance
|
|
The Processing Worker SHALL consume messages from `cdr.raw` and process them without crashing, routing malformed or unprocessable payloads to `cdr.raw.dlq`.
|
|
|
|
#### Scenario: Malformed or unparseable JSON payload
|
|
- **WHEN** a message consumed from `cdr.raw` cannot be parsed as valid JSON
|
|
- **THEN** the worker wraps the raw message in a DLQ envelope with `failure_reason: "malformed_json"` and publishes it to `cdr.raw.dlq` without terminating the process
|
|
|
|
#### Scenario: Missing required field
|
|
- **WHEN** a consumed record is missing any required field (`raw_id`, `device_id`, `caller_num`, `svc`, `rat`, `epoch_start`, `epoch_end`, `up_bytes`, `down_bytes`, `lac`, `ci`) or contains null for a required field
|
|
- **THEN** the worker routes the record to `cdr.raw.dlq` with `failure_reason: "missing_required_field"` and `failed_field` set to the missing field name
|
|
|
|
#### Scenario: Inverted epoch timestamps
|
|
- **WHEN** a consumed record has `epoch_start > epoch_end`
|
|
- **THEN** the worker routes the record to `cdr.raw.dlq` with `failure_reason: "invalid_epoch"` and `failed_field: "epoch_start"`
|
|
|
|
#### Scenario: Negative byte counts
|
|
- **WHEN** a consumed record has `up_bytes < 0` or `down_bytes < 0`
|
|
- **THEN** the worker routes the record to `cdr.raw.dlq` with `failure_reason: "normalization_error"` and `failed_field` set to the negative byte field
|
|
|
|
### Requirement: E.164 Phone Number Normalization
|
|
The Processing Worker SHALL normalize `caller_num` into a valid E.164 format `msisdn` using the `phonenumbers` library.
|
|
|
|
#### Scenario: Leading international prefix normalization
|
|
- **WHEN** a valid record contains `caller_num` starting with `00` or `011`
|
|
- **THEN** the worker replaces the prefix with `+`, parses the number using `phonenumbers`, and sets `msisdn` to the standardized E.164 string
|
|
|
|
#### Scenario: Existing E.164 number preserved
|
|
- **WHEN** a record contains `caller_num` already formatted with a leading `+`
|
|
- **THEN** the worker parses and validates the number using `phonenumbers` and outputs the verified E.164 string
|
|
|
|
#### Scenario: Unparseable phone number routed to DLQ
|
|
- **WHEN** `caller_num` cannot be normalized or validated by `phonenumbers`
|
|
- **THEN** the worker routes the record to `cdr.raw.dlq` with `failure_reason: "normalization_error"` and `failed_field: "caller_num"`
|
|
|
|
### Requirement: Radio Access Type Mapping
|
|
The Processing Worker SHALL map the integer `rat` field to standard string representations.
|
|
|
|
#### Scenario: Supported RAT values mapped
|
|
- **WHEN** a record has `rat` equal to `1`, `6`, or `9`
|
|
- **THEN** the worker maps `radio_access_type` to `"3G"`, `"LTE_4G"`, or `"5G_NR"` respectively
|
|
|
|
#### Scenario: Unsupported RAT value routed to DLQ
|
|
- **WHEN** a record has `rat` not equal to 1, 6, or 9
|
|
- **THEN** the worker routes the record to `cdr.raw.dlq` with `failure_reason: "invalid_rat_value"` and `failed_field: "rat"`
|
|
|
|
### Requirement: Schema Transformation and Deterministic Identity
|
|
The Processing Worker SHALL transform valid raw CDRs into the normalized schema with deterministic identity derivation.
|
|
|
|
#### Scenario: Deterministic UUIDv5 cdr_id generation
|
|
- **WHEN** a raw record with `raw_id` is normalized
|
|
- **THEN** the worker computes `cdr_id` as `uuid.uuid5(uuid.NAMESPACE_DNS, raw_id)` ensuring identical IDs for reprocessed duplicate records
|
|
|
|
#### Scenario: Computed metrics and cell identification
|
|
- **WHEN** raw fields are mapped to the normalized schema
|
|
- **THEN** `cell_id` is set to `lac + "_" + ci`, `total_bytes` is computed as `up_bytes + down_bytes`, `duration_seconds` is computed as `epoch_end - epoch_start`, `session_start_time` and `session_end_time` are converted to UTC DateTimes, and `source_ip` defaults to `"0.0.0.0"` if absent
|
|
|
|
### Requirement: Micro-Batching Buffer Strategy
|
|
The Processing Worker SHALL buffer normalized records in memory and flush batches to ClickHouse based on size or elapsed time thresholds.
|
|
|
|
#### Scenario: Batch flushed on count threshold
|
|
- **WHEN** the in-memory normalized record buffer reaches `BATCH_SIZE` (default 1,000 records)
|
|
- **THEN** the worker immediately flushes the batch to ClickHouse
|
|
|
|
#### Scenario: Batch flushed on time interval threshold
|
|
- **WHEN** `BATCH_FLUSH_INTERVAL_SECONDS` (default 2 seconds) elapses since the last flush and the buffer contains at least one record
|
|
- **THEN** the worker flushes the accumulated buffer to ClickHouse
|
|
|
|
### Requirement: At-Least-Once Delivery and Offset Management
|
|
The Processing Worker SHALL commit Kafka consumer offsets only after ClickHouse successfully acknowledges the batch write.
|
|
|
|
#### Scenario: Offsets committed after successful write
|
|
- **WHEN** a micro-batch is written and acknowledged by ClickHouse
|
|
- **THEN** the worker commits the highest offsets corresponding to the records in that batch
|
|
|
|
#### Scenario: ClickHouse write timeout retry with exponential backoff
|
|
- **WHEN** a batch write to ClickHouse fails or times out
|
|
- **THEN** the worker retries the write up to `CH_RETRY_MAX_ATTEMPTS` (default 3) with exponential backoff (`CH_RETRY_INITIAL_DELAY_MS` multiplied by `CH_RETRY_BACKOFF_FACTOR`); if all attempts fail, the worker routes all records in the batch to `cdr.raw.dlq` and commits offsets to avoid pipeline deadlock
|
|
|
|
### Requirement: Internal Runtime Statistics Endpoint
|
|
The Processing Worker SHALL expose a lightweight internal HTTP endpoint (e.g., `GET /internal/stats`) to provide its runtime statistics to the Ingestion API.
|
|
|
|
#### Scenario: Internal stats queried
|
|
- **WHEN** an HTTP request is made to the worker's internal stats endpoint
|
|
- **THEN** the worker returns HTTP status `200 OK` with JSON containing `total_processed`, `total_dlq`, `in_flight_buffer`, `last_flush_at`, and breakdown counts by service type, RAT, and DLQ reason
|
|
|
|
### Requirement: Graceful Shutdown Without Data Loss
|
|
The Processing Worker SHALL handle `SIGTERM` and `SIGINT` signals to flush buffered records before terminating.
|
|
|
|
#### Scenario: SIGTERM received during operation
|
|
- **WHEN** a `SIGTERM` signal is received by the worker process
|
|
- **THEN** the worker stops consuming new messages, flushes any remaining in-memory buffered records to ClickHouse, commits the associated Kafka offsets, and exits cleanly with exit code 0
|
|
|