A production-grade web application built for Airtel Nigeria's Field Maintenance Engineers (FMEs) to generate Root Cause Analysis (RCA) reports for network site outages. The app processes Huawei RNATracker .xlsb availability data alongside WhatsApp-pasted fault descriptions and produces a formatted Excel report suitable for SLA compliance filing.
Airtel Nigeria FMEs manage thousands of cell sites across 37 states (including FCT). When a site goes down, the engineer must:
- Pull availability data from Huawei's RNATracker (
.xlsbformat) - Read fault descriptions pasted in WhatsApp group chats (cascade format, partner headers, etc.)
- Classify each fault into a standardized RCA category and bucket (e.g., "I H S PASSIVE FAULT / I H S_GENERATOR")
- Produce an Excel report matching Airtel's SLA template format
- Track chronic sites and FME performance over time
Doing this manually for hundreds of sites per report is error-prone and slow. This app automates the entire pipeline — from .xlsb parsing through AI-assisted fault classification to styled .xlsx export.
A 6-step wizard GUI:
| Step | Component | What You Do |
|---|---|---|
| 1 | Upload | Drop in a Huawei RNATracker .xlsb file (or split files by network) |
| 2 | Configure | Pick report date, networks (2G/3G/4G), states to include, and availability threshold |
| 3 | Preview | See the filtered site list with availability values; proceed when ready |
| 4 | RCA Input | Paste fault descriptions from WhatsApp. The app parses them (single-site lines, ROOT+N(SITE,SITE) cascade patterns, bulk N sites (ID,ID): desc). Sends unparsed cascades to Google Gemini for AI extraction. Classifies each event using partner-first keyword engine → learned mappings → fuzzy fallback → Gemini AI |
| 5 | Review | Browse all sites with their AI-assigned categories/buckets. Edit inline, filter by state/site/status, use bulk auto-correct, mark contradictions, train corrections. Voice-controlled classification also works here |
| 6 | Export | Preview the Excel output per network sheet. Download a fully styled .xlsx with color-coded rows (red=unmatched, amber=low availability, green=high avail), auto-filter headers, chronic sites sheet, and summary statistics |
| Layer | Technology |
|---|---|
| Framework | React 19 + TypeScript |
| Routing | TanStack Router v1 (file-based, src/routes/) |
| Meta-framework | TanStack Start (SSR-ready) |
| Styling | Tailwind CSS v4 + Radix UI primitives |
| State | React hooks + TanStack React Query |
| Charts | Recharts (dashboard analytics) |
| Voice | Web Speech API (webkitSpeechRecognition) |
| Icons | Lucide React |
| Layer | Technology |
|---|---|
| Database | Supabase (PostgreSQL) |
| Auth | Supabase Auth (email/password) |
| AI | Google Gemini API (server-side proxy) |
| Excel | SheetJS (xlsx-js-style) for read/write |
| Server | TanStack Start server functions (functions.ts) |
| Offline | localStorage queue + auto-retry on reconnect |
@supabase/supabase-js— database client + auth@tanstack/react-query— server state, caching, retrytailwindcssv4 — utility-first CSSrecharts— analytics bar/area chartsxlsx-js-style— styled Excel generation with fonts, fills, bordersradix-ui— accessible UI primitives (dialog, select, tooltip, dropdown, etc.)date-fns— date formattingsonner— toast notificationsvaul— drawer component (mobile)vitest+jsdom— unit/integration testing
| Path | Component | Purpose |
|---|---|---|
/ |
Wizard | Main 6-step report builder |
/dashboard |
DashboardPage | Analytics from export history (Recharts) |
/train |
TrainPage | AI training — CRUD keyword→category mappings |
/login |
LoginPage | Email/password auth |
/admin |
AdminPage | Database statistics, user info |
The classification pipeline runs in strict priority order:
1. FIBER check → "FIBER / FIBER" (absolute override)
2. Active signal check → "HUAWEI ACTIVE FAULT / BTS FAULT or MICROWAVE"
3. Partner lock → Partner name determines the category family
4. Bucket pick → Keyword rules within locked category
5. Learned mappings → User-trained keyword overrides (Supabase-backed)
6. Fuzzy fallback → Token scoring + Levenshtein ≤ 2
7. Gemini AI → Server-side Google Gemini for anything still unmatched
| Partner | Category | Buckets |
|---|---|---|
| IHS / SWAP | I H S PASSIVE FAULT | GENERATOR, DC POWER, AMF PANEL, THEFT, PLANNED/PROJECT WORK, AGEING DG, ACCESS |
| ATC / HELIOS | ATC PASSIVE FAULT | GENERATOR, AMF PANEL, AGEING DG, PLANNED/PROJECT WORK, DC POWER, THEFT, ACCESS, FUEL OUTAGE |
| PAT | PAT PASSIVE FAULT | GENERATOR, AMF PANEL, AGEING DG, PLANNED/PROJECT WORK, DC POWER, THEFT, PASSIVE MAINTENANCE, FUEL OUTAGE |
| HUAWEI / ZTE / NOKIA / ERICSSON | HUAWEI ACTIVE FAULT | BTS FAULT, MICROWAVE, SPARES, PLANNED/PROJECT WORK |
| AIRTEL / IPT | AIRTEL PASSIVE FAULT | GENERATOR, PLANNED/PROJECT WORK |
| Others (ATL, EASTCASTLE, COLOPLUS, etc.) | OTHER COLLO FAULT | OTHER COLLO |
The VoiceController component (Chrome/Edge only) uses webkitSpeechRecognition for hands-free operation:
- Navigation — "go to step 3", "next", "back", "go to review"
- State selection — "pick Anambra and Lagos", "remove Delta"
- Threshold — "set availability to 95"
- Filter — "show Anambra", "show site AB123", "show only unmatched"
- Classify — "site AB123 is ATC passive fault"
- Bulk classify — "classify all unmatched as ATC passive fault"
- Read aloud — "read unmatched sites", "what's unmatched"
- Summary — "how many sites", "my progress"
- Database — "how many in supabase", "how many saved"
RNATracker .xlsb ──→ xlsbParser.ts ──→ rna.worker.ts (Web Worker)
│
SiteRow[] with availability, state, partner
│
Step3Preview (filter by network/state/threshold)
│
WhatsApp text ──→ textParser.ts ──→ RcaEvent[] (single, cascade, bulk)
│ │
unparsed cascades partnerClassify.ts
│ │ (fiber→active→partner→bucket)
geminiClassify.ts │
(Google Gemini AI) learned mappings (Supabase)
│
fallback.ts (fuzzy Levenshtein)
│
ReviewRow[] with category + bucket
│
exporter.ts ──→ styled .xlsx report
- Failed Supabase writes (classifications, corrections) are queued in
localStorage - Queue auto-retries when the browser detects connectivity (
navigator.onLine+online/offlineevents) - UI shows offline badge with pending count
- Draft state auto-saved to
localStorageviauseWizardDrafthook — no data loss on refresh
| Table | Purpose | RLS |
|---|---|---|
rca_learned_mappings |
User-trained keyword→category/bucket mappings | User-scoped |
rca_corrections |
User corrections (for audit trail) | User-scoped |
rca_export_history |
Past export data for dashboard analytics | User-scoped |
All tables use Row-Level Security — users only see their own data. Auth is email/password via Supabase Auth.
At /dashboard, pulls from rca_export_history with:
- Filterable by date range (30/60/90d presets), network, state
- Stat cards: total rows, unique sites, reports run, match rate
- High-level split: Active / Passive / Others
- Category breakdown bar chart (Recharts)
- Top 8 recurring fault buckets
- Trend area chart (sites per report, last 14)
- FME performance table (sites handled, avg availability, unresolved)
- Chronic sites table (appeared in 3+ reports)
- CSV/Excel export
- View all learned keyword→category mappings with hit counts
- Add single mapping manually (keyword + category + bucket selectors)
- Bulk import from Excel/CSV/TSV files
- Import patterns from past export history (requires 2+ occurrences)
- Search, sort, paginate (50/page), inline edit, remove
- Real-time sync to Supabase
npm test # vitest run (21 tests across 3 files)
npm run test:watch # watch mode
| Test file | What it covers |
|---|---|
tests/storage.test.ts |
normaliseDescription (abbreviation expansion, phonetic typos), getLearnedMap, getFmeMap |
tests/chronicDetection.test.ts |
detectChronicSites (threshold, empty, bad data) |
tests/integration.test.ts |
Full end-to-end: parse WhatsApp text with cascades → classify by partner → detect contradictions → build preview rows → build workbook. Plus baseSiteId normalization and classification edge cases |
- Node.js ≥ 20
- npm ≥ 10
- A Supabase project (free tier works)
- A Google Gemini API key
# 1. Install dependencies
npm install
# 2. Configure environment
# Copy src/.env to .env and fill in:
# SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, GEMINI_API_KEY
# 3. Run database migration
# Apply supabase-migration-rls.sql in your Supabase SQL editor
# 4. Start dev server
npm run dev
# Opens at http://localhost:5173npm run build
npm run previewsrc/
├── components/
│ ├── rca/ # RCA-specific components (Wizard, steps, VoiceController)
│ └── ui/ # Shared UI primitives (Radix-based: button, dialog, select, etc.)
├── routes/ # TanStack Router file-based routes
│ ├── __root.tsx # Root layout (header, auth, theme toggle)
│ ├── index.tsx # Wizard (main page)
│ ├── dashboard.tsx # Analytics dashboard
│ ├── train.tsx # AI training page
│ ├── login.tsx # Auth page
│ └── admin.tsx # Admin panel
├── lib/
│ ├── rca/ # Core business logic
│ │ ├── textParser.ts # WhatsApp text → RcaEvent (cascade, bulk, single)
│ │ ├── partnerClassify.ts # Partner-first classification engine
│ │ ├── geminiClassify.ts # Google Gemini AI classification
│ │ ├── geminiProxy.functions.ts # Server-side Gemini proxy
│ │ ├── fallback.ts # Fuzzy token + Levenshtein fallback
│ │ ├── exporter.ts # Styled Excel workbook builder
│ │ ├── xlsbParser.ts # RNATracker .xlsb parser
│ │ ├── rna.worker.ts # Web Worker for xlsb parsing
│ │ ├── storage.ts # localStorage learned mappings + Supabase sync
│ │ ├── offlineQueue.ts # Offline write queue + auto-retry
│ │ ├── chronicDetection.ts # Chronic site detection (3+ appearances)
│ │ ├── whatsapp.ts # WhatsApp summary builder
│ │ ├── whatsappPreprocess.ts # WhatsApp text pre-processing
│ │ ├── suggestions.ts # Auto-suggestions engine
│ │ ├── auditTrail.ts # User correction audit trail
│ │ ├── seeds.ts # RCA_BUCKETS, highLevel mapping
│ │ ├── types.ts # TypeScript interfaces
│ │ └── useWizardDraft.ts # localStorage draft save/restore
│ ├── auth-context.tsx # Supabase auth context provider
│ ├── config.server.ts # Server-side config (env vars)
│ └── utils.ts # cn() utility (clsx + tailwind-merge)
├── integrations/
│ └── supabase/ # Supabase client, auth middleware, types
├── hooks/ # Custom hooks (use-mobile)
├── styles.css # Tailwind CSS v4 + custom theme
├── router.tsx # TanStack Router config
├── main.tsx # Entry point
└── server.ts # TanStack Start server entry
| Decision | Rationale |
|---|---|
| Partner-first classification | Airtel's SLA is partner-based; the same description "DG fail to start" maps to different buckets depending on whether IHS, ATC, or PAT manages the tower |
| Gemini via server proxy | API key stays server-side; client calls geminiProxy server function, not Gemini directly |
| Local + Supabase storage | Learned mappings sync in real-time but also cache locally for instant offline access |
| Web Worker for xlsb parsing | Large .xlsb files (1000s of rows) don't block the UI thread |
| Voice control via webkitSpeechRecognition | FMEs in the field need hands-free operation; Chrome-only due to browser API limitations |
| Cascade-aware text parser | WhatsApp data frequently uses ROOT+N(SITE,SITE): desc format; the parser handles 4+ variants with balanced-paren support |
| Chronic site detection | Sites appearing in 3+ reports get flagged automatically — key for SLA tracking |
Manual RCA report creation for Airtel Nigeria's Huawei network was taking hours per report — parsing WhatsApp text, classifying each fault, formatting Excel. This app automates the entire workflow, reducing a 2-hour manual task to ~5 minutes with AI assistance.
TanStack Start for SSR-ready React with file-based routing. Supabase for auth + database without managing servers. Tailwind CSS for rapid UI development. Google Gemini for AI fallback classification. SheetJS for Excel generation. Each choice prioritizes low operational overhead and fast iteration.
It's a cascading pipeline: fiber keywords override everything → active signals (BTS/microwave) override partner → partner name locks the category family → keyword rules pick the bucket → user-trained mappings from Supabase → fuzzy Levenshtein fallback → Gemini AI as last resort. This ensures the most common cases (90%+) are handled instantly without API calls.
Gemini API key lives server-side only — never sent to the client. Supabase RLS ensures users only see their own data. Auth is email/password with session management. No sensitive data is logged or exposed.
21 tests across 3 files covering text parsing, cascade detection, partner classification, contradiction detection, site ID normalization, preview row building, workbook generation, and storage operations. Vitest with jsdom environment.
Yes. Failed Supabase writes queue to localStorage and auto-retry. Wizard drafts auto-save to localStorage. Learned mappings cache locally. Gemini AI is the only feature that requires connectivity.
Via the /train page: add keyword→category mappings manually, import from Excel/CSV, or scan past export history for patterns. Mappings sync to Supabase in real-time and are checked first during classification.
Input: Huawei RNATracker .xlsb, WhatsApp pasted text, Excel/CSV/TSV for bulk training.
Output: Styled .xlsx with per-network sheets, chronic sites sheet, summary statistics, color-coded rows, auto-filters.
Airtel Nigeria Huawei network FMEs who need to file weekly/monthly SLA compliance reports. The app is tailored to their specific partner structure (IHS, ATC, PAT, Huawei, Airtel) and RCA category taxonomy.
