Case Study · WOM Chile · 2018–2019

Platform
Reconciliation

Six telecom platforms. 4.5 million subscribers. Weekly billing discrepancies surfaced by an automated pipeline built on BAT, FTP, Oracle SQL and SQL Server — before they became silent revenue losses.

4.5M Valid subscribers
6 Platforms reconciled
30K Billing gaps per run
658 Rate plans catalogued

Weekly · 1h automated run · Revenue Assurance · Telecom · Data Analyst → Analytics Engineer

Oracle SQL (BSCS) Netezza SQL SQL Server BAT scripting WinSCP / FTP gawk + sed 7-zip Stored Procedures
01 · Context

A telecom doesn't lose revenue from outages. It loses it from mismatches no one is watching.

WOM Chile was growing fast — new subscribers, new rate plans, constant network expansion across BSCS, HSS, ICC, SYMSOFT, DWH and PCRF. Each platform maintained its own subscriber records. None of them talked to each other automatically.

A subscriber active in the network (HSS) but not billed correctly (BSCS) is silent revenue leakage. Without a cross-platform view, the only way to find those mismatches was to notice them — usually too late, usually by accident.

The problem wasn't missing data. It was no process that connected it weekly.

Before

Platform discrepancies discovered ad hoc, weeks after the fact. No systematic view across BSCS · HSS · ICC · SYMSOFT · PCRF. Escalation by email when someone noticed an anomaly.

After

Weekly automated run. 1 hour of execution: FTP extraction, parsing, SQL Server load, stored-procedure cross-join, dashboard view. Discrepancies routed to Backend and IT Monitoring teams with context.

02 · Architecture

One BAT file that orchestrated four runtimes, six platforms and two FTP servers.

The pipeline ran from a single Windows BAT file (SCRIPT_CONCILIACION_PARQUE.bat), which was the conductor. It handled FTP via WinSCP to pull compressed dumps from HSS and PCRF, decompressed them with 7-zip, parsed them using gawk + sed (the Unix tools running on Windows), then fired Oracle SQL*Plus to extract BSCS and Netezza SQL to extract DWH traffic data. All of it landed in SQL Server, where stored procedures ran the cross-platform joins and built the reconciliation view.

The insight buried in the design: this was not a database problem — it was a runtime integration problem. No single database could talk to all six sources. The BAT layer was the ETL.

Execution sequence · ~1 hour end-to-end
Trigger
SCRIPT_
CONCILIACION
.bat
FTP Pull
WinSCP → HSS,
PCRF dumps
(.gz / .tar)
Parse
7-zip → gawk
+ sed → flat
files
Extract
SQL*Plus
(BSCS Oracle)
+ Netezza DWH
Load + Cross
SQL Server
→ Stored Procs
→ view
Six platforms · three layers
DWH (Netezza)
Source of truth. 4.5M valid subscribers. 658 rate plans mapped to LK_PLN_PLAN. Defines what billing should see.
BSCS (Oracle)
Billing system. 7.2M records total (facturable + no-facturable). Extracted via SQL*Plus SPOOL dump. Source of most discrepancies.
Δ 30,513 vs DWH
ICC
SIM card inventory. FTP dump pulled weekly. Cross-matched against BSCS active contracts.
HSS
Network authentication. 11.1M records. Parsed from compressed dump using gawk + sed. The network said these SIMs were active.
Δ 22 vs DWH ✓
SYMSOFT
SMS platform. Traffic pulled from BT_TRAFICO_SMSC_SYMSOFT. Validated against active subscriber park.
PCRF
Policy and data control. ~4.9M records. Parsed from compressed dump. Cross-matched to validate data service provisioning.
-- PARQUE_BSCS.sql — Oracle SQL*Plus
-- Extracts all active subscribers from BSCS billing system.
-- CH_STATUS != 'd' → excludes deactivated contracts.
-- CS_DEACTIV_DATE IS NULL → only lines with no deactivation date (active).
-- Output: pipe-delimited flat file for SQL Server import.

WHENEVER SQLERROR EXIT SQL.SQLCODE
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYYMMDD';
SET colsep '|';
SPOOL D:\03_DUMP\BSCS\DUMP_BSCS.txt;

SELECT
    CA.CUSTOMER_ID -- unique customer
  , CA.CUSTCODE
  , CA.CSCOMPTAXNO   -- RUT / tax ID
  , CNT.CO_ID        -- contract ID
  , CNT.CO_ACTIVATED
  , CNT.TMCODE       -- rate plan code (joins to 658-plan catalog)
  , CNT.RATEPLAN_CHANGE_DATE
  , CNT.TMCODE_OLD
  , CNT.CO_CODE
  , CNT.CH_STATUS
  , CSC.DN_ID
  , TO_CHAR(CSC.CS_ACTIV_DATE, 'YYYYMMDD')
  , TO_CHAR(CSC.CS_DEACTIV_DATE, 'YYYYMMDD')
  , DN.DN_NUM        -- MSISDN / phone number
  , BCY.BILLCYCLE
  , CNT.CH_STATUS_VALIDFROM
FROM CUSTOMER_ALL CA
  LEFT JOIN CONTRACT_ALL      CNT ON CA.CUSTOMER_ID = CNT.CUSTOMER_ID
  LEFT JOIN CONTR_SERVICES_CAP CSC ON CNT.CO_ID = CSC.CO_ID
  LEFT JOIN DIRECTORY_NUMBER    DN  ON CSC.DN_ID = DN.DN_ID
  LEFT JOIN CUSTOMER_BCH        BCY ON CA.CUSTCODE = BCY.CUSTCODE
WHERE
  TO_CHAR(CSC.CS_DEACTIV_DATE, 'YYYYMMDD') IS NULL  -- no deactivation date = still active
  AND CNT.CH_STATUS != 'd';                         -- exclude deleted contracts

SPOOL OFF;
-- DUMP_DWH — Netezza SQL
-- Pulls yesterday's subscriber park from DWH with full plan classification.
-- Joins to LK_PLN_PLAN for plan type, market segment, commercial objective.
-- This is the source of truth for what BSCS and HSS should reflect.

SELECT
  A.NUMERO_ABONADO,
  A.ESTADO_CONTRATO,
  A.ID_PLAN,
  B.DESC_CORTA_PLAN,
  CASE
    WHEN B.ID_TIPO_PLAN = 3 THEN 'UNLIMITED'
    WHEN B.ID_TIPO_PLAN = 5 THEN 'Prepago'
    WHEN B.ID_TIPO_PLAN = 6 THEN 'Controlado'
    ELSE 'NA'
  END AS TIPO_PLAN,
  CASE
    WHEN B.ID_MERCADO_PLAN = 2 THEN 'Consumer'
    WHEN B.ID_MERCADO_PLAN = 5 THEN 'Business'
    WHEN B.ID_MERCADO_PLAN = 4 THEN 'Prepago'
    ELSE 'NA'
  END AS MERCADO,
  A.ICCID, A.IMSI, A.FECHA_ALTA
FROM
  nz_prod.DWH_WOM.BT_CLE_PARQUE A
  LEFT JOIN NZ_PROD.DWH_WOM.LK_PLN_PLAN B ON A.ID_PLAN = B.ID_PLAN
WHERE
  A.PERIODO_PARQUE = TO_CHAR(NOW()-1, 'YYYYMM')
  AND A.estado_contrato != 'd';
03 · The Finding

HSS had 22 differences. BSCS had 30,513. The network was fine. Billing wasn't.

Every week the reconciliation produced one number that mattered: the delta between what each platform believed was active. HSS — the network authentication layer — showed only 22 records diverging from DWH. That meant provisioning was nearly clean: subscribers were correctly registered in the network.

BSCS — the billing system — showed 30,513 records diverging. Subscribers active in the network, correctly in DWH, but with billing records that didn't align: wrong plan codes, missing contracts, status mismatches. Every one of those records was a line item that billing couldn't process correctly.

Platform discrepancy vs DWH baseline
weekly snapshot — records off from DWH truth
HSS (network)
22
ICC (SIM inventory)
~1.3K
BSCS (billing)
30,513

The network provisioned correctly. The billing system didn't follow. Without the cross-platform view, these 30,513 records would have been invisible — appearing as "active" in every platform audit, while silently failing to generate correct billing.

Subscriber park composition · DWH baseline
4.5M valid + 2.7M under review
Consumer Voice
1,832,786
Prepaid (no-fact)
4,086,117
Consumer Data
130,519
Business Voice
224,831
Prepaid Data
24,827
Revenue at risk · reference estimate
30,513 mismatches · ~CLP 13,990/plan/month
Records at risk
30,513
BSCS divergences per weekly run vs DWH baseline
Monthly exposure
~USD 427K
Reference: ~CLP 13,990 plan / month. Actual varies by plan mix.
Annual projection
~USD 5.1M
At constant volume. Identifying the gap is the first condition to recovering it.

This is a conservative reference estimate, not a recovered revenue figure. The actual monetary impact depended on plan mix, billing cycle timing and whether records were corrected within the same period. What the pipeline guaranteed was that the discrepancy was found weekly — not quarterly.

04 · Traffic Enrichment

Knowing a subscriber is miscoded is table stakes. Knowing they're active on the network is the proof.

Beyond the static park reconciliation, the pipeline joined 30 days of voice, data, SMS and VAS traffic to the subscriber base. A record showing a billing mismatch becomes much more actionable when you can also say: this line had 340 minutes of voice and 2.8GB of data this month — it's not dormant, it's being used without being correctly billed.

Voice Traffic
30-day aggregation from AGG_TRF_VOZ_MED_ABONADO. Minutes of voice per subscriber, last traffic date. FULL JOIN with data traffic for unified view.
Data Traffic
30-day MB from AGG_TRF_DATOS_ABONADO (KB_DOWN + KB_UP / 1024). Aggregated per subscriber, last active date.
SMS Traffic
Delivered messages from SYMSOFT BT_TRAFICO_SMSC_SYMSOFT. State: 'Delivered' or 'Delivered direct'. Hit count + last delivery date.
VAS / Shortcode
Value-added service charges from BT_TRAFICO_SHRTC. Net amount extracted via SUBSTR + INSTR position parsing on ADDITIONAL_ORIGCHARGEINFO.
Reconciliation view
All four traffic sources joined to the park cross in SQL Server. Output: subscribers with platform status + usage evidence, sorted by gap priority.
Reporting
Results sent by email to Backend (Héctor Leiva) and IT Monitoring (Theo Soto, César Barrios, Jorge Durán) for investigation and correction.
-- TRAFICO_NZ — Netezza SQL (NZ_PROD)
-- FULL JOIN voice + data so a subscriber with ONLY voice or ONLY data is not lost.
-- DISTINCT + CASE WHEN handles the NULL from either side of the join.

SELECT DISTINCT
  CASE WHEN A.NUMERO_ABONADO IS NULL
       THEN B.NUMERO_ABONADO
       ELSE A.NUMERO_ABONADO
  END AS NUM_ABONADO,
  SUM(A.CANTIDAD_VOZ)  MIN_VOZ,
  A.ULTIMO_TRAFICO_VOZ,
  SUM(B.CANTIDAD_MB)   MB_DATOS,
  B.ULTIMO_TRAFICO_DATOS
FROM
  (SELECT
     Z.NUMERO_ABONADO,
     SUM(CAST(Z.DURACION AS INT) / 60) CANTIDAD_VOZ,
     MAX(Z.ID_DWH_DIA) ULTIMO_TRAFICO_VOZ
   FROM NZ_PROD.STG_TRAF.AGG_TRF_VOZ_MED_ABONADO Z
   WHERE Z.ID_DWH_DIA >= TO_CHAR(NOW()-30, 'YYYYMMDD')
   GROUP BY Z.NUMERO_ABONADO) A

FULL JOIN

  (SELECT
     X.NUMERO_ABONADO,
     ROUND(SUM((X.KB_DOWN_IX + X.KB_UP_IX) / 1024), 2) CANTIDAD_MB,
     MAX(X.ID_DWH_DIA) ULTIMO_TRAFICO_DATOS
   FROM NZ_PROD.STG_TRAF.AGG_TRF_DATOS_ABONADO X
   WHERE X.ID_DWH_DIA >= TO_CHAR(NOW()-30, 'YYYYMMDD')
   GROUP BY X.NUMERO_ABONADO) B

ON A.NUMERO_ABONADO = B.NUMERO_ABONADO
GROUP BY NUM_ABONADO, A.ULTIMO_TRAFICO_VOZ, B.ULTIMO_TRAFICO_DATOS;
05 · What I Owned

The decisions I'd defend — and the constraints I worked around.

Decisions I owned

  • BAT as the orchestration layer. No modern orchestrator existed in the environment. A single .bat file sequencing WinSCP → 7-zip → gawk/sed → SQL*Plus → sqlcmd was the right tool: auditable, runnable by anyone, no dependencies to install.
  • FULL JOIN on voice + data traffic. An INNER JOIN would have silently dropped subscribers using only voice or only data. The FULL JOIN + CASE WHEN pattern ensures every active subscriber surfaces regardless of which traffic type they generate.
  • Pipe-delimited flat file as the integration format. BSCS Oracle couldn't write directly to SQL Server. SQL*Plus SPOOL to |-delimited text, then BULK INSERT in SQL Server, was the only format that crossed both runtimes reliably.
  • Stored procedures for the cross-platform join logic. Keeping the reconciliation logic inside SQL Server stored procedures meant the join rules were versioned in one place — not scattered across shell scripts or ad hoc queries.

Constraints + what I'd change

  • Resolution by email — not automated. Discrepancies were sent to Backend and IT Monitoring teams by email. The fix depended on human response time. A ticketing integration or automated correction workflow would have closed the loop faster.
  • Credentials embedded in the BAT file. FTP credentials and SQL connection strings lived inside the script. Acceptable for a local-desktop tool in 2018; the modern fix is a secrets manager or at minimum environment variables.
  • No historical trend — only weekly snapshots. The process produced a point-in-time view, not a time-series. Adding a weekly append-to table in SQL Server would have let you track whether gaps were growing, shrinking, or recurring seasonally.
  • Manual trigger — not scheduled. The BAT had to be run manually each week. A SQL Server Agent job or Task Scheduler entry would have made it truly automatic and removed the single point of human failure.
"A 30,000-record billing gap doesn't announce itself. Someone has to build the system that looks for it every week." — Working principle from this project

Revenue assurance pipelines for telecom, AI adoption analytics for aviation — different domains, same discipline: build the instrumentation before you need it.

Contact Javier