teleprocessinggh / telecom_anomaly /utils /pdf_processor.py
winsweb's picture
tcom add
d229285
Raw History Blame Contribute Delete
5.32 kB
import tabula
import pandas as pd
import io
import re
import os
from pathlib import Path
# --- CONFIGURATION ---
# Match telecom_anomaly_detection's pipeline exactly: every stage writes a CSV
# and the next stage reads it back. The CSV roundtrip normalizes dtypes and
# whitespace, which is what produces the anomaly counts the user expects.
DATA_BASE = Path("data")
RAW_CSV_DIR = DATA_BASE / "raw_csv_temp"
PROCESS_OUTPUT_DIR = DATA_BASE / "output_process_data"
STRICT_CLEAN_DIR = DATA_BASE / "strictly_cleaned_data"
def setup_folders():
"""Ensure all necessary output directories exist."""
for folder in [RAW_CSV_DIR, PROCESS_OUTPUT_DIR, STRICT_CLEAN_DIR]:
if not os.path.exists(folder):
os.makedirs(folder)
def extract_pdf_to_csv(pdf_path, base_name):
"""Stage 1: Convert PDF tables to a raw CSV file."""
csv_path = os.path.join(RAW_CSV_DIR, f"{base_name}_raw.csv")
try:
tabula.convert_into(pdf_path, csv_path, output_format="csv", pages="all", lattice=True)
return csv_path
except Exception as e:
print(f"Error extracting {pdf_path}: {e}")
return None
def process_telecom_data_complete(input_path, base_name):
"""Stage 2: Parse raw CSV and repair fragmented rows."""
try:
with open(input_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
except Exception as e:
print(f"Error reading {input_path}: {e}")
return None
header_idx = -1
for i, line in enumerate(lines):
if "event_date_time" in line:
header_idx = i
break
if header_idx == -1:
return None
# Customer info section (before the call-records header)
customer_text = "".join(lines[:header_idx])
df_customer = pd.read_csv(io.StringIO(customer_text))
df_customer.columns = [re.sub(r'[\r\n\s]+', ' ', c).strip() for c in df_customer.columns]
cust_path = os.path.join(PROCESS_OUTPUT_DIR, f'{base_name}_customer_info.csv')
df_customer.to_csv(cust_path, index=False)
# Call records
call_text = "".join(lines[header_idx:])
df_raw = pd.read_csv(io.StringIO(call_text))
df_raw.columns = [c.strip() for c in df_raw.columns]
main_col = df_raw.columns[0]
df_raw = df_raw[df_raw[main_col].astype(str).str.contains("event_date_time") == False]
fixed_rows = []
current_row = None
for _, row in df_raw.iterrows():
val = str(row[main_col]).strip()
if re.match(r'^\d{4}-\d{2}-\d{2}', val):
if current_row is not None:
fixed_rows.append(current_row)
current_row = row.copy()
elif current_row is not None:
current_row[main_col] = (str(current_row[main_col]) + " " + val).replace('\r', '').replace('\n', '')
for col in df_raw.columns:
if col != main_col and pd.api.types.is_string_dtype(df_raw[col]):
fragment = str(row[col]).strip()
if fragment.lower() != 'nan':
current_row[col] = (str(current_row[col]) + " " + fragment).replace('\r', '').replace('\n', ' ')
if current_row is not None:
fixed_rows.append(current_row)
df_calls = pd.DataFrame(fixed_rows)
df_calls[main_col] = df_calls[main_col].str.replace(r'[\r\n]+', '', regex=True).str.replace(r'\s+', ' ', regex=True).str.strip()
dt_series = pd.to_datetime(df_calls[main_col], errors='coerce')
df_calls['event_date'] = dt_series.dt.date
df_calls['event_time'] = dt_series.dt.time
df_calls.drop(columns=[main_col], inplace=True)
uppercase_cols = ['district', 'city', 'usage_type', 'usage_sub_type', 'calling_no', 'called_no', 'imei', 'location_id', 'msisdn']
for col in df_calls.columns:
c_low = col.lower()
if df_calls[col].dtype == 'object':
df_calls[col] = df_calls[col].astype(str).str.replace(r'[\r\n\s]+', ' ', regex=True).str.strip()
if c_low in uppercase_cols:
df_calls[col] = df_calls[col].str.upper()
elif c_low == 'region':
df_calls[col] = df_calls[col].str.title()
elif c_low == 'call_direction':
df_calls[col] = df_calls[col].str.title().replace({'Incomingmt': 'Incoming (MT)', '\(\(': '(', '\)\)': ')'}, regex=True)
ordered_cols = ['event_date', 'event_time'] + [c for c in df_calls.columns if c not in ['event_date', 'event_time']]
df_calls = df_calls[ordered_cols]
interim_path = os.path.join(PROCESS_OUTPUT_DIR, f'{base_name}_call_records_final.csv')
df_calls.to_csv(interim_path, index=False)
return interim_path
def strict_clean_data(file_path, base_name):
"""Stage 3: Final strict cleaning."""
df = pd.read_csv(file_path)
if 'duration' in df.columns and df['duration'].dtype == 'object':
df['duration'] = df['duration'].str.replace(',', '.').astype(float)
df['event_date'] = pd.to_datetime(df['event_date'], errors='coerce')
df['event_time_check'] = pd.to_datetime(df['event_time'], format='%H:%M:%S', errors='coerce')
df_cleaned = df.dropna().copy()
df_cleaned = df_cleaned.drop(columns=['event_time_check'])
final_path = os.path.join(STRICT_CLEAN_DIR, f'{base_name}_strictly_cleaned.csv')
df_cleaned.to_csv(final_path, index=False)
return final_path