#!/usr/bin/env python3 """Backfill Asterisk Master.csv CDR rows into the MySQL `cdr` table. CSV column mapping for Asterisk 16 default cdr_csv.conf: calldate=9, clid=4, src=1, dst=2, dcontext=3, channel=5, dstchannel=6, lastapp=7, lastdata=8, duration=12, billsec=13, disposition=14, amaflags=15, accountcode=0, uniqueid=16, userfield=17 peeraccount/linkedid/sequence are not in the CSV -> NULL Idempotent: skips rows whose uniqueid already exists; for rows with no uniqueid (early Asterisk) dedups on a row-content hash. Run as asterisk: sudo -u asterisk /opt/vm-transcribe/venv/bin/python3 /opt/vm-transcribe/vm_backfill_cdr.py """ import csv import hashlib import os import sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import vm_store CSV_PATH = "/var/log/asterisk/cdr-csv/Master.csv" # CSV positions (0-based) as parsed from a real Master.csv row POS = { "accountcode": 0, "src": 1, "dst": 2, "dcontext": 3, "clid": 4, "channel": 5, "dstchannel": 6, "lastapp": 7, "lastdata": 8, "calldate": 9, "duration": 12, "billsec": 13, "disposition": 14, "amaflags": 15, "uniqueid": 16, "userfield": 17, } # Column order matching the MySQL cdr table COLS = [ "calldate", "clid", "src", "dst", "dcontext", "channel", "dstchannel", "lastapp", "lastdata", "duration", "billsec", "disposition", "amaflags", "accountcode", "uniqueid", "peeraccount", "linkedid", "sequence", "userfield", ] # Which of those are NULL because not in the CSV NULL_AFTER = {"peeraccount", "linkedid", "sequence"} def parse(v): if v is None or v == "": return None try: return int(v) except ValueError: return v def main(): if not os.path.exists(CSV_PATH): print("csv not found:", CSV_PATH) return con = vm_store.connect() seen = set() total = 0 skip = 0 with open(CSV_PATH, newline="", encoding="utf-8", errors="replace") as f: reader = csv.reader(f, quotechar='"', escapechar="\\", doublequote=False) for row in reader: if not row or len(row) < 17: continue uid = (row[POS["uniqueid"]] if len(row) > POS["uniqueid"] else "") or "" key = uid if not key: key = hashlib.sha1(("\x00".join(row)).encode("utf-8", errors="replace")).hexdigest()[:32] if key in seen: skip += 1 continue seen.add(key) total += 1 vals = [] for c in COLS: if c in NULL_AFTER: vals.append(None) continue v = row[POS[c]] if POS[c] < len(row) else None vals.append(parse(v)) ph = ",".join(["%s"] * len(COLS)) try: con.execute( "INSERT IGNORE INTO cdr (%s) VALUES (%s)" % (",".join(COLS), ph), vals, ) except Exception as e: print(" skip key=%s: %s" % (key, e)) con.commit() mysql_total = con.execute("SELECT COUNT(*) c FROM cdr").fetchone()["c"] print("csv scanned: %d skipped: %d -> MySQL cdr total: %d" % (total, skip, mysql_total)) if __name__ == "__main__": main()