gigafibre-fsm/scripts/migration/migrate_provisioning_data.py
louispaulb 607ea54b5c refactor: reduce token count, DRY code, consolidate docs
Backend services:
- targo-hub: extract deepGetValue to helpers.js, DRY disconnect reasons
  lookup map, compact CAPABILITIES, consolidate vision.js prompts/schemas,
  extract dispatch scoring weights, trim section dividers across 9 files
- modem-bridge: extract getSession() helper (6 occurrences), resetIdleTimer(),
  consolidate DM query factory, fix duplicate username fill bug, trim headers
  (server.js -36%, tplink-session.js -47%, docker-compose.yml -57%)

Frontend:
- useWifiDiagnostic: extract THRESHOLDS const, split processDiagnostic into
  6 focused helpers (processOnlineStatus, processWanIPs, processRadios,
  processMeshNodes, processClients, checkRadioIssues)
- EquipmentDetail: merge duplicate ROLE_LABELS, remove verbose comments

Documentation (17 → 13 files, -1,400 lines):
- New consolidated README.md (architecture, services, dependencies, auth)
- Merge ECOSYSTEM-OVERVIEW into ARCHITECTURE.md
- Merge MIGRATION-PLAN + ARCHITECTURE-COMPARE + FIELD-GAP + CHANGELOG → MIGRATION.md
- Merge COMPETITIVE-ANALYSIS into PLATFORM-STRATEGY.md
- Update ROADMAP.md with current phase status
- Delete CONTEXT.md (absorbed into README)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 08:39:58 -04:00

203 lines
7.8 KiB
Python

"""
migrate_provisioning_data.py — Migrate WiFi and VoIP provisioning data
from GenieACS MariaDB into ERPNext Service Equipment custom fields.
Sources:
- provisioning-data.json (exported from GenieACS MariaDB at legacy-db)
Contains: wifi[] (1,713 entries) and voip[] (797 entries)
Matching strategy:
- WiFi: wifi.serial (Deco MAC) → Service Equipment.mac_address
- VoIP: voip.serial (RCMG serial) → Service Equipment.serial_number
Run inside erpnext-backend-1:
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/migrate_provisioning_data.py
"""
import frappe
import json
import os
os.chdir("/home/frappe/frappe-bench/sites")
frappe.init(site="erp.gigafibre.ca", sites_path=".")
frappe.connect()
frappe.local.flags.ignore_permissions = True
print("Connected:", frappe.local.site)
# ═══════════════════════════════════════════════════════════════
# Load provisioning data export
# ═══════════════════════════════════════════════════════════════
# This file should be copied to the bench directory from:
# scripts/migration/genieacs-export/provisioning-data.json
PROV_FILE = "/home/frappe/frappe-bench/provisioning-data.json"
if not os.path.exists(PROV_FILE):
# Try the git repo path
PROV_FILE = os.path.expanduser(
"~/frappe-bench/apps/gigafibre-fsm/scripts/migration/genieacs-export/provisioning-data.json"
)
if not os.path.exists(PROV_FILE):
print("ERROR: provisioning-data.json not found. Copy it to /home/frappe/frappe-bench/")
print("Source: scripts/migration/genieacs-export/provisioning-data.json")
exit(1)
with open(PROV_FILE) as f:
prov_data = json.load(f)
wifi_entries = prov_data.get("wifi", [])
voip_entries = prov_data.get("voip", [])
print("WiFi entries: {}".format(len(wifi_entries)))
print("VoIP entries: {}".format(len(voip_entries)))
# ═══════════════════════════════════════════════════════════════
# Build MAC → Equipment Name map
# ═══════════════════════════════════════════════════════════════
print("\nBuilding MAC address and serial number maps...")
# MAC address → Service Equipment name (normalized to uppercase, no colons)
mac_map = {}
rows = frappe.db.sql("""
SELECT name, mac_address FROM "tabService Equipment"
WHERE mac_address IS NOT NULL AND mac_address != ''
""", as_dict=True)
for r in rows:
mac_clean = r["mac_address"].upper().replace(":", "").replace("-", "").replace(".", "")
mac_map[mac_clean] = r["name"]
print(" MAC map: {} entries".format(len(mac_map)))
# Serial number → Service Equipment name
serial_map = {}
rows = frappe.db.sql("""
SELECT name, serial_number FROM "tabService Equipment"
WHERE serial_number IS NOT NULL AND serial_number != ''
""", as_dict=True)
for r in rows:
serial_map[r["serial_number"].upper()] = r["name"]
print(" Serial map: {} entries".format(len(serial_map)))
# ═══════════════════════════════════════════════════════════════
# 1. WiFi provisioning → Service Equipment
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 60)
print("1. WiFi PROVISIONING")
print("=" * 60)
# Group wifi entries by serial (keep latest / instance=1)
wifi_by_serial = {}
for w in wifi_entries:
serial = w.get("serial", "")
if not serial:
continue
instance = w.get("instance", 1)
# Prefer instance 1 (primary SSID)
if serial not in wifi_by_serial or instance == 1:
wifi_by_serial[serial] = w
print("Unique WiFi serials: {}".format(len(wifi_by_serial)))
matched_wifi = 0
unmatched_wifi = 0
for serial, w in wifi_by_serial.items():
ssid = w.get("ssid", "")
password = w.get("password", "")
if not ssid:
continue
# WiFi serial is typically a MAC address (Deco) — try MAC map
mac_clean = serial.upper().replace(":", "").replace("-", "").replace(".", "")
eq_name = mac_map.get(mac_clean)
# Also try serial number map
if not eq_name:
eq_name = serial_map.get(serial.upper())
# Try with common prefix patterns (TPLG...)
if not eq_name:
for prefix in ["TPLG", "TPLINK"]:
eq_name = serial_map.get(prefix + serial.upper())
if eq_name:
break
if not eq_name:
unmatched_wifi += 1
continue
frappe.db.sql("""
UPDATE "tabService Equipment"
SET wifi_ssid = %(ssid)s, wifi_password = %(password)s
WHERE name = %(name)s
""", {"name": eq_name, "ssid": ssid, "password": password})
matched_wifi += 1
frappe.db.commit()
print("WiFi matched: {} / unmatched: {}".format(matched_wifi, unmatched_wifi))
# ═══════════════════════════════════════════════════════════════
# 2. VoIP provisioning → Service Equipment
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 60)
print("2. VoIP PROVISIONING")
print("=" * 60)
# Group voip entries by serial
voip_by_serial = {}
for v in voip_entries:
serial = v.get("serial", "")
if not serial:
continue
instance = v.get("instance", 1)
if serial not in voip_by_serial or instance == 1:
voip_by_serial[serial] = v
print("Unique VoIP serials: {}".format(len(voip_by_serial)))
matched_voip = 0
unmatched_voip = 0
for serial, v in voip_by_serial.items():
username = v.get("username", "")
password = v.get("password", "")
if not username:
continue
# VoIP serial is typically RCMG physical serial — try serial map
eq_name = serial_map.get(serial.upper())
# Also try with RCMG prefix
if not eq_name and not serial.upper().startswith("RCMG"):
eq_name = serial_map.get("RCMG" + serial.upper())
# Try MAC map (some CWMP serials encode MAC)
if not eq_name:
# Extract last 12 chars as potential MAC
if len(serial) >= 12:
potential_mac = serial[-12:].upper()
eq_name = mac_map.get(potential_mac)
if not eq_name:
unmatched_voip += 1
continue
frappe.db.sql("""
UPDATE "tabService Equipment"
SET sip_username = %(username)s, sip_password = %(password)s
WHERE name = %(name)s
""", {"name": eq_name, "username": username, "password": password})
matched_voip += 1
frappe.db.commit()
print("VoIP matched: {} / unmatched: {}".format(matched_voip, unmatched_voip))
# ═══════════════════════════════════════════════════════════════
# 3. DONE
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 60)
print("PROVISIONING MIGRATION COMPLETE")
print("=" * 60)
print("WiFi: {} matched, {} unmatched".format(matched_wifi, unmatched_wifi))
print("VoIP: {} matched, {} unmatched".format(matched_voip, unmatched_voip))
print("")
print("Unmatched devices will be resolved after OLT query tagging (see ANALYSIS.md)")