Gridlock / src /config.py
parvmittal07's picture
Initial Demo complete
01eb82e
Raw
History Blame Contribute Delete
10.6 kB
"""Central configuration: paths, column groupings, and modeling constants.
The column groupings here are the single most important guard against data
leakage. Any column produced *after* an event is reported (resolution times,
status, post-hoc edits, assigned officers) must never become a model feature.
"""
from __future__ import annotations
import os
from pathlib import Path
# --------------------------------------------------------------------------- #
# Paths
# --------------------------------------------------------------------------- #
ROOT = Path(__file__).resolve().parents[1]
DATA_DIR = ROOT / "data"
RAW_DIR = DATA_DIR / "raw"
PROCESSED_DIR = DATA_DIR / "processed"
MODELS_DIR = ROOT / "models"
REPORTS_DIR = ROOT / "reports"
FIGURES_DIR = REPORTS_DIR / "figures"
RAW_CSV = RAW_DIR / "astram_events.csv"
CLEAN_PARQUET = PROCESSED_DIR / "events_clean.parquet"
FEATURES_PARQUET = PROCESSED_DIR / "features.parquet"
EMBED_CACHE = PROCESSED_DIR / "text_embeddings.npy"
# Persisted artefacts that make the causal/spatial features reproducible at
# inference time (a new event must see the SAME geo bins and the accumulated
# historical target rates the model was trained on).
HISTORY_PARQUET = PROCESSED_DIR / "history.parquet"
GEO_KMEANS_PATH = MODELS_DIR / "geo_kmeans.joblib"
for _d in (PROCESSED_DIR, MODELS_DIR, REPORTS_DIR, FIGURES_DIR):
_d.mkdir(parents=True, exist_ok=True)
RANDOM_STATE = 42
# --------------------------------------------------------------------------- #
# Raw column groupings
# --------------------------------------------------------------------------- #
# Identifiers / opaque tokens -> dropped from features entirely.
ID_COLUMNS = [
"id",
"kgid",
"veh_no",
"client_id",
"created_by_id",
"last_modified_by_id",
"assigned_to_police_id", # post-assignment -> would leak the resourcing label
"citizen_accident_id",
"closed_by_id",
"resolved_by_id",
"map_file",
"meta_data",
"gba_identifier",
]
# Columns only known AFTER the event unfolds -> never features. Some are used
# strictly to construct duration / status labels in targets.py.
LEAKAGE_COLUMNS = [
"status",
"modified_datetime",
"end_datetime", # used to derive duration label only
"closed_datetime", # used to derive duration label only
"resolved_datetime", # used to derive duration label only
"resolved_at_address",
"resolved_at_latitude",
"resolved_at_longitude",
"comment", # frequently appended after resolution
# The end-point / diversion-route geometry is only populated when a closure
# or diversion SEGMENT is drawn -> it encodes the closure decision itself
# (has_end_point alone gives AP~0.98 on closure). Treat as leakage so the
# model must forecast from genuine report-time signal instead.
"endlatitude",
"endlongitude",
"end_address",
"route_path",
]
# The three prediction targets (also excluded from the feature matrix).
TARGET_CLOSURE = "y_road_closure" # binary (barricading / diversion need)
TARGET_PRIORITY = "y_high_priority" # binary (manpower tier)
TARGET_DURATION = "y_duration_min" # numeric (impact duration, minutes)
TARGET_DURATION_LOG = "y_duration_log" # log1p transform actually modeled
TARGET_COLUMNS_RAW = ["requires_road_closure", "priority"]
# Timestamp columns parsed to tz-aware datetimes.
DATETIME_COLUMNS = [
"start_datetime",
"end_datetime",
"modified_datetime",
"created_date",
"closed_datetime",
"resolved_datetime",
]
# --------------------------------------------------------------------------- #
# Feature columns (available at event-creation time)
# --------------------------------------------------------------------------- #
CATEGORICAL_FEATURES = [
"event_type",
"event_cause",
"event_family", # engineered grouping of event_cause
"veh_type",
"direction",
"corridor",
"zone",
"junction",
"police_station",
"reason_breakdown",
"cargo_material",
"authenticated",
"pincode", # engineered from address
"geo_cluster", # engineered unsupervised spatial bin
]
NUMERIC_FEATURES = [
"latitude",
"longitude",
"age_of_truck",
# temporal
"start_hour",
"start_dow",
"start_month",
"start_weekofyear",
"start_day",
"is_weekend",
"is_morning_peak",
"is_evening_peak",
"is_night",
"lead_time_hours",
"has_advance_notice",
# cyclical encodings
"hour_sin",
"hour_cos",
"dow_sin",
"dow_cos",
"month_sin",
"month_cos",
# spatial (report-time location only; end-point/route geometry is leakage)
"hist_hotspot_count",
"loc_event_density",
# recurrence
"same_loc_cause_hist",
"same_day_loc_reports",
# causal target-rate encodings (past-only smoothed closure rate per group;
# adapts to drift and gives the rare closure target far more signal than the
# static category alone). See feature_engineering._causal_target_features.
"clo_rate_family",
"clo_rate_cause",
"clo_rate_corridor",
"clo_rate_police",
"clo_rate_junction",
"clo_rate_geocluster",
"clo_rate_zone",
"clo_rate_pincode",
# ambient duration level: rolling mean log-duration of recently-resolved
# events (tracks the heavy non-stationarity in clearance times).
"dur_recent_level",
# text-derived scalar features
"desc_len",
"desc_word_count",
"desc_is_kannada",
"desc_has_text",
"lex_closed",
"lex_slow",
"lex_normal",
"lex_diversion",
"lex_blocked",
"lex_heavy",
"lex_festival",
# missingness flags
"veh_type_missing",
"desc_missing",
]
# Free-text column for embeddings / lexicon.
TEXT_COLUMN = "description"
# --------------------------------------------------------------------------- #
# event_cause -> event_family mapping
# Keys are lowercased/stripped event_cause values (the raw data mixes case,
# e.g. "Debris"/"debris", and has 17 distinct causes).
# --------------------------------------------------------------------------- #
EVENT_FAMILY_MAP = {
# event-driven / planned gatherings (the core of the problem statement)
"public_event": "gathering",
"procession": "gathering",
"protest": "gathering",
"vip_movement": "vip",
"construction": "construction",
# unplanned incidents
"vehicle_breakdown": "breakdown",
"accident": "accident",
"tree_fall": "obstruction",
"water_logging": "obstruction",
"debris": "obstruction",
"fog / low visibility": "obstruction",
"pot_holes": "road_condition",
"road_conditions": "road_condition",
"congestion": "congestion",
"test_demo": "other",
"others": "other",
}
EVENT_FAMILY_DEFAULT = "other"
# Features excluded from the PRIORITY model only: `corridor` is ~99.8%
# deterministic of `priority` (High == named corridor, Low == Non-corridor),
# so using it makes the task trivially leaky. We force the model to infer
# priority from geography / cause / text / time instead.
PRIORITY_EXCLUDE_FEATURES = ["corridor"]
# --------------------------------------------------------------------------- #
# Multilingual impact lexicon (English + transliterated + Kannada)
# Used both as interpretable features and as a fallback when transformer
# embeddings are unavailable.
# --------------------------------------------------------------------------- #
LEXICON = {
"lex_closed": ["closed", "close", "block", "blocked", "shut", "ಮುಚ್ಚ", "ಬಂದ್", "ಕ್ಲೋಸ್"],
"lex_slow": ["slow", "slowly", "moment", "movement", "nidhana", "ನಿಧಾನ", "ಸ್ಲೋ", "slow moment"],
"lex_normal": ["normal", "no problem", "no traffic", "clear", "ಸಮಸ್ಯ ಇಲ್ಲ", "ಯಾವುದೇ ತೊಂದರೆ"],
"lex_diversion": ["diversion", "divert", "u turn", "uturn", "alternate", "ಡೈವರ್ಶನ್", "ಬದಲಾವಣೆ"],
"lex_blocked": ["jam", "gridlock", "heavy traffic", "congestion", "ಟ್ರಾಫಿಕ್ ಜಾಮ್", "ದಟ್ಟಣೆ"],
"lex_heavy": ["lorry", "truck", "bus", "heavy", "container", "ಲಾರಿ", "ಬಸ್", "ಟ್ರಕ್"],
"lex_festival": ["festival", "utsava", "uthsava", "palakki", "pallakki", "procession", "rally", "ಉತ್ಸವ", "ಪಲ್ಲಕ್ಕಿ", "ಹಬ್ಬ"],
}
# --------------------------------------------------------------------------- #
# Modeling constants
# --------------------------------------------------------------------------- #
# Fraction of the (time-ordered) data held out as the final temporal test set.
TEST_FRACTION = 0.20
N_FOLDS = 5
# Number of unsupervised spatial bins (KMeans on report-time coordinates).
GEO_CLUSTERS = 40
# Duration label handling
DURATION_MIN_MINUTES = 1.0 # drop non-positive / sub-minute durations
DURATION_WINSOR_UPPER_Q = 0.99 # cap extreme long-tail durations
# Causal target-rate encoding. Each (group key -> column) pair becomes a
# past-only smoothed mean of the closure label within that group. Smoothing
# shrinks low-count groups toward the running global rate (empirical-Bayes).
CAUSAL_TARGET_SMOOTHING = 25
CLOSURE_RATE_KEYS = {
"event_family": "clo_rate_family",
"event_cause": "clo_rate_cause",
"corridor": "clo_rate_corridor",
"police_station": "clo_rate_police",
"junction": "clo_rate_junction",
"geo_cluster": "clo_rate_geocluster",
"zone": "clo_rate_zone",
"pincode": "clo_rate_pincode",
}
# Window (in recently-resolved events) for the ambient duration-level feature.
DUR_RECENT_WINDOW = 200
# Transformer embedding model (multilingual, supports Kannada). Set the env var
# GRIDLOCK_NO_TRANSFORMER=1 to force the lightweight TF-IDF fallback.
EMBED_MODEL_NAME = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
# Default PCA width for the 384-dim embeddings. Kept modest (32) for the
# duration / priority models: duration has only ~2.5k valid rows, so wide
# embeddings overfit (validated: R2_log 0.139@32 -> 0.10@128). The closure
# model, trained on all rows, benefits from a wider projection and overrides
# this with CLOSURE_EMB_PCA_COMPONENTS (validated: AP 0.314@32 -> 0.335@96).
EMBED_PCA_COMPONENTS = 32
CLOSURE_EMB_PCA_COMPONENTS = 96
USE_TRANSFORMER = os.environ.get("GRIDLOCK_NO_TRANSFORMER", "0") != "1"
# Optuna
OPTUNA_TRIALS = int(os.environ.get("GRIDLOCK_OPTUNA_TRIALS", "40"))
OPTUNA_TIMEOUT = int(os.environ.get("GRIDLOCK_OPTUNA_TIMEOUT", "600")) # seconds/target
NULL_TOKENS = {"NULL", "null", "None", "none", "", "nan", "NaN", "[]"}