CoolFace
Datasetpublic

SafeVixAI/SafeVixAI-Dataset-Hub

SafeVixAI Dataset Hub 🛡️ The Intelligence Layer for the SafeVixAI platform — IIT Madras Road Safety Hackathon 2026 This repository hosts all datasets, pre-trained models, notebooks, and reproducible data acquisition scripts that power the SafeVixAI application. It is designed to be cloned directly into Google Colab or any research environment. Main Application Repo: SafeVixAI/SafeVixAI ⚡ Quickstart (Google Colab) # Clone the entire intelligence layer !git… See the full description on the dataset page: https://huggingface.co/datasets/SafeVixAI/SafeVixAI-Dataset-Hub.

sourceHugging Faceapache-2.0updated 4mo agoView on Hugging Face
1likes147downloads
bootstrap_local_data.py557 linesDownload Raw Back to data
1from __future__ import annotations2 3import argparse4import csv5from io import BytesIO6import json7import math8import shutil9import struct10import sys11import zipfile12from pathlib import Path13 14 15PROJECT_ROOT = Path(__file__).resolve().parents[1]16BACKEND_ROOT = PROJECT_ROOT / 'backend'17 18import importlib.util as _ilu19 20 21def _load_backend_module(rel_path: str, module_name: str):22    """Load a module from backend/ by explicit file path and register it in23    sys.modules under *module_name*.  This makes the import fully transparent24    to Pylance/Pyright (no opaque sys.path mutation) while still satisfying25    Python internals that need __module__ to be resolvable (e.g. dataclasses26    with slots=True)."""27    abs_path = BACKEND_ROOT / rel_path28    spec = _ilu.spec_from_file_location(module_name, abs_path)29    mod = _ilu.module_from_spec(spec)  # type: ignore[arg-type]30    sys.modules[module_name] = mod  # register BEFORE exec so __module__ resolves31    spec.loader.exec_module(mod)  # type: ignore[union-attr]32    return mod33 34 35_seed_viol = _load_backend_module("scripts/seed_violations.py", "scripts.seed_violations")36DEFAULT_RULES = _seed_viol.DEFAULT_RULES37OVERRIDE_COLUMNS = _seed_viol.OVERRIDE_COLUMNS38RULE_COLUMNS = _seed_viol.RULE_COLUMNS39_load_override_rows = _seed_viol._load_override_rows40_load_rule_rows = _seed_viol._load_rule_rows41_rule_to_row = _seed_viol._rule_to_row42_write_csv = _seed_viol._write_csv43 44_emerg_catalog = _load_backend_module("services/local_emergency_catalog.py", "services.local_emergency_catalog")45load_local_emergency_catalog = _emerg_catalog.load_local_emergency_catalog46 47 48 49CHATBOT_DATA_DIR = PROJECT_ROOT / 'chatbot_service' / 'data'50FRONTEND_OFFLINE_DIR = PROJECT_ROOT / 'frontend' / 'public' / 'offline-data'51BACKEND_CHALLAN_DIR = PROJECT_ROOT / 'backend' / 'datasets' / 'challan'52ROADS_DIR = CHATBOT_DATA_DIR / 'roads'53PMGSY_MAX_POINTS_PER_SEGMENT = 2454 55OFFLINE_CITY_CENTERS: dict[str, tuple[float, float]] = {56    'chennai': (13.0827, 80.2707),57    'coimbatore': (11.0168, 76.9558),58    'madurai': (9.9252, 78.1198),59    'thiruvananthapuram': (8.5241, 76.9366),60    'kochi': (9.9312, 76.2673),61    'bengaluru': (12.9716, 77.5946),62    'mumbai': (19.0760, 72.8777),63    'pune': (18.5204, 73.8567),64    'nagpur': (21.1458, 79.0882),65    'hyderabad': (17.3850, 78.4867),66    'delhi': (28.6139, 77.2090),67    'jaipur': (26.9124, 75.7873),68    'ahmedabad': (23.0225, 72.5714),69    'surat': (21.1702, 72.8311),70    'vadodara': (22.3072, 73.1812),71    'kolkata': (22.5726, 88.3639),72    'patna': (25.5941, 85.1376),73    'bhopal': (23.2599, 77.4126),74    'indore': (22.7196, 75.8577),75    'lucknow': (26.8467, 80.9462),76    'agra': (27.1767, 78.0081),77    'varanasi': (25.3176, 82.9739),78    'chandigarh': (30.7333, 76.7794),79    'visakhapatnam': (17.6868, 83.2185),80    'bhubaneswar': (20.2961, 85.8245),81}82CITY_RADIUS_METERS = 80_00083 84 85def sync_challan_assets() -> None:86    rules_source = CHATBOT_DATA_DIR / 'violations_seed.csv'87    overrides_source = CHATBOT_DATA_DIR / 'state_overrides.csv'88    rule_map = {rule.violation_code: _rule_to_row(rule) for rule in DEFAULT_RULES}89    if rules_source.exists():90        for row in _load_rule_rows(rules_source):91            rule_map[row['violation_code']] = row92    override_rows = _load_override_rows(overrides_source) if overrides_source.exists() else []93 94    sorted_rules = [rule_map[key] for key in sorted(rule_map)]95    sorted_overrides = sorted(96        override_rows,97        key=lambda row: (row['state_code'], row['violation_code'], row['vehicle_class']),98    )99 100    BACKEND_CHALLAN_DIR.mkdir(parents=True, exist_ok=True)101    FRONTEND_OFFLINE_DIR.mkdir(parents=True, exist_ok=True)102    _write_csv(BACKEND_CHALLAN_DIR / 'violations.csv', RULE_COLUMNS, sorted_rules)103    _write_csv(BACKEND_CHALLAN_DIR / 'state_overrides.csv', OVERRIDE_COLUMNS, sorted_overrides)104    _write_csv(FRONTEND_OFFLINE_DIR / 'violations.csv', RULE_COLUMNS, sorted_rules)105    _write_csv(FRONTEND_OFFLINE_DIR / 'state_overrides.csv', OVERRIDE_COLUMNS, sorted_overrides)106    print(f'Challan assets synced: rules={len(sorted_rules)} overrides={len(sorted_overrides)}')107 108 109def sync_first_aid_bundle() -> None:110    """Always sync first-aid.json from frontend (canonical 20-article source) to chatbot data.111 112    The chatbot_service/data/first_aid.json was historically only 4 entries.113    The frontend/public/offline-data/first-aid.json contains the full 20 WHO-based articles114    and is the ground truth. This function overwrites unconditionally so the chatbot is never115    left with the stale 4-entry version.116    """117    source = FRONTEND_OFFLINE_DIR / 'first-aid.json'118    target = CHATBOT_DATA_DIR / 'first_aid.json'119    if not source.exists():120        print(f'WARNING: first-aid.json source not found at {source} — skipping sync')121        return122    shutil.copyfile(source, target)123    print(f'Synced first aid bundle ({source.stat().st_size:,} bytes) -> {target}')124 125 126def build_emergency_geojson() -> None:127    catalog = load_local_emergency_catalog(PROJECT_ROOT)128    features = []129    for entry in catalog:130        city, distance = _nearest_city(entry.lat, entry.lon)131        if city is None or distance > CITY_RADIUS_METERS:132            continue133        features.append(134            {135                'type': 'Feature',136                'id': entry.id,137                'geometry': {'type': 'Point', 'coordinates': [entry.lon, entry.lat]},138                'properties': {139                    'city': city.title(),140                    'name': entry.name,141                    'category': entry.category,142                    'sub_category': entry.sub_category,143                    'phone': entry.phone,144                    'phone_emergency': entry.phone_emergency,145                    'address': entry.address,146                    'has_trauma': entry.has_trauma,147                    'has_icu': entry.has_icu,148                    'is_24hr': entry.is_24hr,149                    'source': entry.source,150                },151            }152        )153 154    payload = {155        'type': 'FeatureCollection',156        'properties': {157            'generated_from': 'chatbot_service/data local CSV catalog',158            'feature_count': len(features),159            'cities': [city.title() for city in OFFLINE_CITY_CENTERS],160        },161        'features': features,162    }163    output_path = FRONTEND_OFFLINE_DIR / 'india-emergency.geojson'164    output_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding='utf-8')165    print(f'Emergency GeoJSON written: features={len(features)} path={output_path}')166 167 168def export_pmgsy_geojson() -> None:169    source = ROADS_DIR / 'pmgsy-geosadak-master.zip'170    target = ROADS_DIR / 'pmgsy_roads.geojson'171    if not source.exists():172        print('PMGSY archive not found; skipping pmgsy_roads.geojson export')173        return174 175    planned_states: list[str] = []176    skipped_archives: list[str] = []177    feature_count = 0178 179    with zipfile.ZipFile(source) as outer, target.open('w', encoding='utf-8') as handle:180        planned_states = _list_pmgsy_state_members(outer)181        skipped_archives = _list_pmgsy_split_archives(outer)182        properties = {183            'generated_from': source.name,184            'geometry_generalization': f'max {PMGSY_MAX_POINTS_PER_SEGMENT} points per segment',185            'planned_states': planned_states,186            'skipped_archives': skipped_archives,187        }188        handle.write('{"type":"FeatureCollection","properties":')189        json.dump(properties, handle, ensure_ascii=False, separators=(',', ':'))190        handle.write(',"features":[')191 192        is_first_feature = True193        exported_states: list[str] = []194        for state_name, archive_bytes in _iter_pmgsy_state_archives(outer):195            try:196                shp_bytes, dbf_bytes = _read_shapefile_bundle(archive_bytes)197            except ValueError:198                continue199 200            exported_states.append(state_name)201            for row, geometry in zip(_iter_dbf_rows(dbf_bytes), _iter_polyline_geometries(shp_bytes)):202                if geometry is None:203                    continue204                feature = {205                    'type': 'Feature',206                    'id': f'pmgsy-{state_name}-{row.get("ER_ID") or feature_count + 1}',207                    'geometry': geometry,208                    'properties': _build_pmgsy_properties(row, state_name),209                }210                if not is_first_feature:211                    handle.write(',')212                json.dump(feature, handle, ensure_ascii=False, separators=(',', ':'))213                is_first_feature = False214                feature_count += 1215 216        handle.write(']}')217 218    print(219        'PMGSY GeoJSON exported: '220        f'rows={feature_count} states={len(exported_states)} skipped={len(skipped_archives)} path={target}'221    )222 223 224def export_national_highways_csv() -> None:225    target = ROADS_DIR / 'national_highways.csv'226    if target.exists() and target.stat().st_size > 0:227        print(f'National highways CSV already present: {target}')228        return229 230    candidates = sorted(231        path for path in ROADS_DIR.glob('*.csv')232        if path.name != target.name and any(token in path.stem.lower() for token in ('nh', 'highway', 'nhai'))233    )234    if not candidates:235        summary_rows = _build_road_summary_rows()236        if not summary_rows:237            print('No usable local road CSVs found; skipping national_highways.csv export')238            return239 240        target.parent.mkdir(parents=True, exist_ok=True)241        with target.open('w', encoding='utf-8', newline='') as handle:242            writer = csv.DictWriter(243                handle,244                fieldnames=[245                    'source_file',246                    'geography_level',247                    'geography_name',248                    'period',249                    'metric_name',250                    'value',251                    'unit',252                    'notes',253                ],254            )255            writer.writeheader()256            writer.writerows(summary_rows)257        print(258            'National highways CSV synthesized from local road tables: '259            f'rows={len(summary_rows)} path={target}'260        )261        return262 263    shutil.copyfile(candidates[0], target)264    print(f'National highways CSV copied from {candidates[0].name} to {target}')265 266 267def _nearest_city(lat: float, lon: float) -> tuple[str | None, float]:268    best_city = None269    best_distance = float('inf')270    for city, (city_lat, city_lon) in OFFLINE_CITY_CENTERS.items():271        distance = _distance_meters(lat, lon, city_lat, city_lon)272        if distance < best_distance:273            best_city = city274            best_distance = distance275    return best_city, best_distance276 277 278def _distance_meters(lat1: float, lon1: float, lat2: float, lon2: float) -> float:279    radius = 6_371_000280    phi1 = math.radians(lat1)281    phi2 = math.radians(lat2)282    delta_phi = math.radians(lat2 - lat1)283    delta_lambda = math.radians(lon2 - lon1)284    a = (285        math.sin(delta_phi / 2) ** 2286        + math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2) ** 2287    )288    return 2 * radius * math.atan2(math.sqrt(a), math.sqrt(1 - a))289 290 291def _list_pmgsy_state_members(outer: zipfile.ZipFile) -> list[str]:292    return [293        Path(member).stem294        for member in sorted(name for name in outer.namelist() if '/Road_DRRP/' in name and name.endswith('.zip'))295        if not member.endswith('-split.zip')296    ]297 298 299def _list_pmgsy_split_archives(outer: zipfile.ZipFile) -> list[str]:300    return [301        Path(member).stem302        for member in sorted(name for name in outer.namelist() if '/Road_DRRP/' in name and name.endswith('-split.zip'))303    ]304 305 306def _iter_pmgsy_state_archives(outer: zipfile.ZipFile):307    for member in sorted(name for name in outer.namelist() if '/Road_DRRP/' in name and name.endswith('.zip')):308        if member.endswith('-split.zip'):309            continue310        yield Path(member).stem, outer.read(member)311 312 313def _read_shapefile_bundle(archive_bytes: bytes) -> tuple[bytes, bytes]:314    with zipfile.ZipFile(BytesIO(archive_bytes)) as archive:315        shp_names = [name for name in archive.namelist() if name.lower().endswith('.shp')]316        dbf_names = [name for name in archive.namelist() if name.lower().endswith('.dbf')]317        if not shp_names or not dbf_names:318            raise ValueError('Missing shapefile members')319        return archive.read(shp_names[0]), archive.read(dbf_names[0])320 321 322def _iter_dbf_rows(dbf_bytes: bytes) -> list[dict[str, object]]:323    header_length = struct.unpack('<H', dbf_bytes[8:10])[0]324    record_length = struct.unpack('<H', dbf_bytes[10:12])[0]325    field_specs = []326    pos = 32327    offset = 1328    while pos < header_length - 1:329        field = dbf_bytes[pos:pos + 32]330        if field[0] == 0x0D:331            break332        field_specs.append(333            (334                field[:11].split(b'\x00', 1)[0].decode('ascii', 'ignore'),335                chr(field[11]),336                field[16],337                field[17],338                offset,339            )340        )341        offset += field[16]342        pos += 32343 344    records = struct.unpack('<I', dbf_bytes[4:8])[0]345    row_start = header_length346    for _ in range(records):347        record = dbf_bytes[row_start:row_start + record_length]348        row_start += record_length349        if not record or record[0:1] == b'*':350            continue351        row: dict[str, object] = {}352        for name, field_type, field_len, decimals, value_offset in field_specs:353            raw = record[value_offset:value_offset + field_len]354            text = raw.decode('latin1', 'ignore').strip()355            if not text:356                continue357            if field_type == 'N':358                if decimals:359                    try:360                        row[name] = float(text)361                    except ValueError:362                        row[name] = text363                else:364                    try:365                        row[name] = int(text)366                    except ValueError:367                        row[name] = text368            else:369                row[name] = text370        yield row371 372 373def _iter_polyline_geometries(shp_bytes: bytes) -> list[dict[str, object] | None]:374    pos = 100375    total_size = len(shp_bytes)376    while pos + 8 <= total_size:377        content_length_words = struct.unpack('>i', shp_bytes[pos + 4:pos + 8])[0]378        record_end = pos + 8 + content_length_words * 2379        record = shp_bytes[pos + 8:record_end]380        pos = record_end381        if len(record) < 44:382            yield None383            continue384 385        shape_type = struct.unpack('<i', record[:4])[0]386        if shape_type == 0:387            yield None388            continue389        if shape_type not in {3, 13, 23}:390            yield None391            continue392 393        num_parts = struct.unpack('<i', record[36:40])[0]394        num_points = struct.unpack('<i', record[40:44])[0]395        parts_offset = 44396        points_offset = parts_offset + 4 * num_parts397        parts = [398            struct.unpack('<i', record[parts_offset + index * 4:parts_offset + (index + 1) * 4])[0]399            for index in range(num_parts)400        ]401        points = [402            struct.unpack('<2d', record[points_offset + index * 16:points_offset + (index + 1) * 16])403            for index in range(num_points)404        ]405 406        coordinates = []407        for index, start in enumerate(parts):408            end = parts[index + 1] if index + 1 < len(parts) else len(points)409            line = _downsample_line(points[start:end], max_points=PMGSY_MAX_POINTS_PER_SEGMENT)410            if len(line) < 2:411                continue412            coordinates.append([[round(lon, 6), round(lat, 6)] for lon, lat in line])413 414        if not coordinates:415            yield None416        elif len(coordinates) == 1:417            yield {'type': 'LineString', 'coordinates': coordinates[0]}418        else:419            yield {'type': 'MultiLineString', 'coordinates': coordinates}420 421 422def _downsample_line(points: list[tuple[float, float]], *, max_points: int) -> list[tuple[float, float]]:423    if len(points) <= max_points:424        return points425    last_index = len(points) - 1426    indexes = {427        0,428        last_index,429        *(430            min(last_index, round(step * last_index / (max_points - 1)))431            for step in range(1, max_points - 1)432        ),433    }434    return [points[index] for index in sorted(indexes)]435 436 437def _build_pmgsy_properties(row: dict[str, object], state_name: str) -> dict[str, object]:438    props: dict[str, object] = {'pmgsy_state': state_name}439    field_map = {440        'ER_ID': 'er_id',441        'STATE_ID': 'state_id',442        'BLOCK_ID': 'block_id',443        'DISTRICT_I': 'district_id',444        'DRRP_ROAD_': 'road_code',445        'RoadCatego': 'road_category',446        'RoadName': 'road_name',447        'RoadOwner': 'road_owner',448    }449    for source_key, target_key in field_map.items():450        value = row.get(source_key)451        if value not in (None, ''):452            props[target_key] = value453    return props454 455 456def _build_road_summary_rows() -> list[dict[str, str]]:457    rows: list[dict[str, str]] = []458    for path in sorted(ROADS_DIR.glob('*.csv')):459        if path.name in {'national_highways.csv', 'tolls-with-metadata.csv'}:460            continue461        if path.name.endswith('-metadata-hotosm_ind_roads_lines_geojson-zip.csv'):462            continue463        rows.extend(_normalize_road_summary_table(path))464    return rows465 466 467def _normalize_road_summary_table(path: Path) -> list[dict[str, str]]:468    with path.open('r', encoding='utf-8-sig', newline='') as handle:469        reader = csv.DictReader(handle)470        if reader.fieldnames is None:471            return []472 473        geography_column = _detect_geography_column(reader.fieldnames)474        serial_columns = {'Sr. No.', 'Sl. No.', 'Sl.No.', 'S.No.', 'S. No.'}475        notes = (476            'Generated from local road programme CSVs because no direct NHAI/NH master CSV '477            'was present in chatbot_service/data/roads.'478        )479        rows: list[dict[str, str]] = []480        for raw in reader:481            geography_name = (raw.get(geography_column) or '').strip() if geography_column else ''482            if not geography_name:483                continue484            for column, value in raw.items():485                if column in serial_columns or column == geography_column:486                    continue487                metric_value = _normalize_metric_value(value or '')488                if metric_value is None:489                    continue490                metric_name, period = _split_metric_column(column)491                rows.append(492                    {493                        'source_file': path.name,494                        'geography_level': 'district' if geography_column == 'District Name' else 'state',495                        'geography_name': geography_name,496                        'period': period,497                        'metric_name': metric_name,498                        'value': metric_value,499                        'unit': 'km_or_count',500                        'notes': notes,501                    }502                )503        return rows504 505 506def _detect_geography_column(fieldnames: list[str]) -> str | None:507    candidates = ['District Name', 'State/UT', 'State', 'District', 'State/UT ']508    for candidate in candidates:509        if candidate in fieldnames:510            return candidate511    return None512 513 514def _normalize_metric_value(value: str) -> str | None:515    cleaned = value.strip()516    if not cleaned or cleaned.upper() in {'NA', 'N/A', '-'}:517        return None518    try:519        return str(int(cleaned))520    except ValueError:521        try:522            return str(float(cleaned))523        except ValueError:524            return None525 526 527def _split_metric_column(column: str) -> tuple[str, str]:528    cleaned = column.strip()529    period_match = None530    for token in ('2024-25', '2023-24', '2022-23', '2021-22', '2020-21', '2019-20'):531        if token in cleaned:532            period_match = token533            break534    if period_match is None:535        return cleaned, ''536 537    metric_name = cleaned.replace(period_match, '').replace(' - ', ' ').replace('(as on 14.07.2022)', '').strip()538    metric_name = ' '.join(metric_name.split()) or cleaned539    return metric_name, period_match540 541 542def main() -> None:543    parser = argparse.ArgumentParser(description='Build app-facing assets from chatbot_service/data local datasets.')544    parser.add_argument('--skip-pmgsy', action='store_true', help='Skip extracting PMGSY shapefiles into GeoJSON.')545    args = parser.parse_args()546 547    sync_challan_assets()548    sync_first_aid_bundle()549    build_emergency_geojson()550    export_national_highways_csv()551    if not args.skip_pmgsy:552        export_pmgsy_geojson()553 554 555if __name__ == '__main__':556    main()557