Synthetic data engine for a mobile telecom operator.
It generates a small, consistent “mini data warehouse” of dimensions and facts:
customers, segments, tariffs, locations, time, network KPIs, usage, and NOC tickets.
The code is designed to be:
- Config‑driven (central settings in
config/settings.py) - Reproducible (fixed random seeds)
- Modular (separate dimension and fact generators)
- Easy to extend (add new dimensions/facts without breaking the pipeline)
Tested with:
- Python 3.11 (recommended)
- Should also work with Python 3.10+
The project uses pyproject.toml. From the project root:
# create and activate a virtual environment (recommended)
python -m venv .venv
source .venv/bin/activate # Linux / macOS
# or:
.\.venv\Scripts\activate # Windows PowerShell
# install dependencies
pip install -e .Key libraries used:
pandas– tabular data manipulationnumpy– random generation, numerical helpers
(Any other packages listed in pyproject.toml are installed automatically.)
All global “knobs” live in config/settings.py:
- Simulation horizon:
SIM_START_DATESIM_END_DATE
- Random seeds:
NETWORK_RANDOM_SEEDfor network KPIsNOC_RANDOM_SEEDfor NOC tickets
- Network KPI thresholds:
BUSY_MIN_DL_MBPS,BUSY_MIN_RRC_SR_PCT,BUSY_MAX_DROP_PCTNB_MIN_DL_MBPS,NB_MIN_RRC_SR_PCT,NB_MAX_DROP_PCT
- NOC logic:
NOC_BASE_ISSUE_PROB(random issue probability per hour)
Each module also has a dataclass config with sensible defaults:
NetworkConfig(telecom_core/facts/network_kpi.py)SegmentUsageConfig(telecom_core/facts/segment_usage.py)NocConfig(telecom_core/facts/noc_ticket.py)TimeConfig(telecom_core/dimensions/time.py)TariffConfig(telecom_core/dimensions/tariff.py)CustomerConfig(telecom_core/dimensions/customer.py)
You can either rely on the defaults or pass your own instances when calling the generators.
data_engine/
├── config/
│ ├── __init__.py
│ └── settings.py # global simulation settings (dates, seeds, thresholds)
│
├── telecom_core/
│ ├── __init__.py
│ ├── main.py # orchestrates the full pipeline
│ │
│ ├── dimensions/
│ │ ├── __init__.py
│ │ ├── segment.py # dim_customer_segment / segment_id
│ │ ├── location.py # dim_location / location_id
│ │ ├── time.py # dim_time / time_id
│ │ ├── tariff.py # dim_tariff / tariff_id
│ │ └── customer.py # dim_customer / customer_key
│ │
│ └── facts/
│ ├── __init__.py
│ ├── network_kpi.py # fact_network_kpi_hourly
│ ├── segment_usage.py # fact_segment_usage_hourly
│ ├── network_noc_daily.py # (optional aggregation, not in main pipeline)
│ └── noc_ticket.py # fact_noc_ticket
│
├── output/ # generated CSV files (created at runtime)
└── pyproject.toml
All ID columns follow a consistent naming convention: xxxxx_id
(e.g. segment_id, location_id, time_id, tariff_id, customer_key).
From the project root (where telecom_core/ lives):
# inside your virtual environment
python -m telecom_core.mainThis will:
- Create an
output/directory if it does not exist. - Generate all dimensions and facts.
- Save them as CSV files in
output/. - Print detailed logs showing each step and row counts, e.g.:
[STEP 1] Building dimension tables...
- Generating dim_customer_segment ...
- Generating dim_location ...
- Generating dim_time ...
- Generating dim_tariff ...
- Generating dim_customer ...
[STEP 2] Generating fact_network_kpi_hourly ...
[STEP 3] Preparing SegmentUsageConfig ...
[STEP 4] Generating fact_segment_usage_hourly ...
[STEP 5] Generating fact_noc_ticket ...
Generation complete. CSV files written to: .../output
- Function:
generate_dim_segment(config: SegmentConfig | None = None) - Grain: one row per segment (High, Normal, Low)
- Key columns:
segment_id(1=High, 2=Normal, 3=Low)segment_nameglobal_target_share(target customer mix)revenue_contributionavg_monthly_revenuedata_usage_pattern(Heavy/Moderate/Light)complaint_likelihoodsensitivity_to_outage
- Most values are hard-coded business assumptions, but the target shares are controlled by
SegmentConfig.
- Function:
generate_dim_location(config: LocationConfig | None = None) - Grain: one row per location_id (20 locations)
- Key columns:
location_idcity(e.g. “Düsseldorf”)location_namelocation_type(Commercial, Residential, Industrial, Transport)revenue_potential(High, Medium, Low)population_densitycoverage_quality
- Locations are hard-coded;
LocationConfigcontrols global attributes like default city.
- Function:
generate_dim_time(config: TimeConfig) -> pd.DataFrame - Grain: one row per hour between
start_dateandend_date(exclusive) - Key columns:
time_id(YYYYMMDDHH integer)datehouris_busy_houris_working_houris_weekendis_holiday
- Busy and working hours are rule‑based.
- Dates are read from
TimeConfig, which inmain.pyis built fromsettings.SIM_START_DATEandsettings.SIM_END_DATE.
- Function:
build_dim_tariff(config: TariffConfig | None = None) - Grain: one row per tariff_id
- Represents 4 tariffs with increasing quota, speed, and price.
- Key columns:
tariff_id,tariff_name,segment_targetdata_quota_gb,price_eurdl_speed_mbps,ul_speed_mbpsnetwork_tech(default'4G')
- Values are hard‑coded inside the function;
TariffConfigmostly holds shared attributes (tech).
- Function:
build_dim_customer(dim_location, dim_segment, dim_tariff, config: CustomerConfig | None) - Grain: one row per synthetic customer
- Key columns:
customer_key(surrogate key)segment_idlocation_idtariff_idsignup_datestatus
- Logic:
- Uses
CustomerConfig.num_customers,signup_start_date,signup_end_date,random_seed. - Segment mix is driven by
dim_segment.global_target_share(so changingSegmentConfigpropagates to customers). - Location distribution is biased by
population_densityandrevenue_potential(different patterns for each segment). - Tariffs are assigned probabilistically per segment (e.g. High Value → more premium tariffs).
- Uses
- Function:
generate_network_kpi(dim_location, dim_time, config: NetworkConfig | None = None) - Grain: one row per (location_id, time_id)
- Key columns:
location_id,time_idactive_users,load_ratiorrc_setup_success_pct,rrc_drop_rate_pctavg_throughput_dl_mbps,avg_throughput_ul_mbpsux_state(Good,Degraded,Poor)hasissue(bool;ux_state != "Good")
- Logic:
- Active users driven by
population_density,revenue_potential, andis_busy_hour. - Load ratio =
active_users / capacity(capacity depends on revenue tier). - Throughput baseline + penalties depending on load + random noise.
- RRC KPIs degrade under congestion + small random noise.
ux_stateis classified from throughput and RRC KPIs.hasissueis derived in one place here and reused downstream.
- Active users driven by
- Function:
generate_segment_usage_hourly(dim_customer_segment, dim_location, dim_time, fact_network_kpi_hourly, cfg: SegmentUsageConfig, random_seed=42) - Grain: one row per (segment_id, location_id, time_id)
- Inputs:
- Segments: segment attributes and global mix.
- Time: busy vs non‑busy.
- Location: revenue tier, density (indirectly via KPIs).
- KPIs:
hasissueper location/hour.
- Outputs (per row):
- Usage:
totalsessionstotaldatagbavgsessiondurationsec
- Charging:
quota_gb_houroverage_data_gbcharge_amount_euris_throttledbill_shock_flag
- Experience:
affectedbyissue(network issue or throttling)qualityofexperiencescore(0–1)
- Usage:
Logic:
- For each hour and location, loop over all segments.
- Simulate sessions and data using Poisson / exponential / lognormal distributions, scaled by segment and busy‑hour flags.
- Convert monthly quotas and prices (from
SegmentUsageConfig) into hourly equivalents. - Compute overage, charges, throttling, and bill‑shock flags.
- Compute QoE starting from
baseline_qoe_no_issueand subtracting penalties forhasissueand throttling. - Assemble the final fact table.
- Function:
generate_noc_tickets(fact_network_kpi_hourly, dim_time, config: NocConfig | None = None) - Grain: one row per NOC ticket (incident spanning multiple hours)
- Key columns:
noc_ticket_id(e.g.NOC0010001)location_idstart_time_id,end_time_idduration_minutesissue_type(e.g. Power outage, Random failure, Capacity congestion)severity(Minor, Major, Critical)
- Logic:
- Join
fact_network_kpi_hourlywithdim_timeto getis_busy_hour. - For each location:
- Flag hours where KPIs breach thresholds (from
NocConfig) or where a random issue is drawn usingNOC_BASE_ISSUE_PROB. - Group consecutive “bad” hours into tickets.
- Flag hours where KPIs breach thresholds (from
- Classify each ticket’s severity and type based on worst KPIs in the ticket window.
- Join
High‑level data flow:
config/settings.py
│
▼
TimeConfig ──────────► dim_time
SegmentConfig ───────► dim_customer_segment (= dim_segment)
LocationConfig ──────► dim_location
TariffConfig ────────► dim_tariff
CustomerConfig ──────► dim_customer (uses dim_location, dim_segment, dim_tariff)
(dim_location, dim_time) ──► fact_network_kpi_hourly (with hasissue)
(dim_customer_segment, dim_location, dim_time, fact_network_kpi_hourly)
───────────────────────► fact_segment_usage_hourly
(fact_network_kpi_hourly, dim_time)
───────────────────────► fact_noc_ticket
- Hard‑coded assumptions: segment profiles, location list, tariff catalog, thresholds.
- Config‑driven values: simulation dates, seeds, KPI/NOC thresholds, segment quotas/prices.
- Dynamic values: all simulated metrics (usage, KPIs, tickets) depend on random draws and config.
- To change the simulation period: edit
SIM_START_DATE/SIM_END_DATEinconfig/settings.py. - To change segment mix or behavior: update
SegmentConfiginsegment.pyor the hard‑codedsegmentslist. - To change tariffs: modify the list in
build_dim_tariff. - To add a new fact table:
- Create a new module under
telecom_core/facts/. - Implement a generator function that takes the needed dimension/fact DataFrames.
- Wire it into
telecom_core/main.py(add imports, add a new STEP with logs, write CSV).
- Create a new module under
If you paste this into README.md in your repo, readers should understand how to install, run, and reason about the whole pipeline.
Which part of this README would you like to go deeper into—configuration, the statistical models, or the way facts depend on dimensions?