SPB Git

spb/toit-ka Public

Toit-Ka — louer ou acheter un toit au Québec, un seul endroit (fusion Lou-Ka × Immo-Ka) — www.toit-ka.com

Python 40.2% TypeScript 39% CSS 20.2% HTML 0.7%
4.4 KB · 107 lines python
Raw Blame History
1# Author: Simon-Pierre Boucher2# Contact: contact@spboucher.ai3# Project: Toit-Ka4# -----------------------------------------------------------------------------5# db.py : BD unifiée toitka.db (SQLite WAL) — construite par l'ETL, lue par l'API.6#   Une seule table `listings` bi-modale : la colonne `transaction_type`7#   ('louer' | 'acheter') distingue les univers Lou-Ka et Immo-Ka.8#   Les BD sources (répliques louka.db / immoka.db) ne sont JAMAIS écrites.9# -----------------------------------------------------------------------------10from __future__ import annotations1112import os13import sqlite314from pathlib import Path1516ROOT = Path(__file__).resolve().parent.parent17DATA = ROOT / "data"18DB_PATH = Path(os.environ.get("TOITKA_DB", DATA / "toitka.db"))1920# Répliques lecture seule des BD de prod (voir toitka/replicate.py).21# En dev, pointer directement les BD locales via .env :22#   TOITKA_LOUKA_DB=/Users/.../Desktop/lou-ka/data/louka.db23#   TOITKA_IMMOKA_DB=/Users/.../Desktop/agent-courtage/data/immoka.db24LOUKA_DB = Path(os.environ.get("TOITKA_LOUKA_DB", DATA / "replicas" / "louka.db"))25IMMOKA_DB = Path(os.environ.get("TOITKA_IMMOKA_DB", DATA / "replicas" / "immoka.db"))2627SCHEMA = """28CREATE TABLE IF NOT EXISTS listings (29    uid              TEXT PRIMARY KEY,   -- "{origin}:{source}:{external_id}"30    origin           TEXT NOT NULL,      -- 'louka' | 'immoka'31    transaction_type TEXT NOT NULL,      -- 'louer' | 'acheter'32    source           TEXT NOT NULL,33    external_id      TEXT NOT NULL,34    url              TEXT,35    title            TEXT,36    address          TEXT,37    sector           TEXT,38    city             TEXT,               -- ville CANONIQUE (table de mapping)39    city_raw         TEXT,               -- valeur brute de la source (audit)40    type             TEXT,               -- type unifié (3½, 4½, Condo, Maison, Terrain…)41    price            REAL,               -- loyer mensuel (louer) ou prix demandé (acheter)42    price_label      TEXT,43    bedrooms         INTEGER,44    bathrooms        INTEGER,45    area_sqft        REAL,46    lot_sqft         REAL,47    year_built       INTEGER,48    pets             TEXT,               -- oui | non | conditions (louer)49    furnished        INTEGER,            -- 1/0 (louer)50    availability_date TEXT,              -- ISO ou 'now' (louer)51    mls              TEXT,52    broker_name      TEXT,53    agency           TEXT,54    description      TEXT,55    images           TEXT,               -- JSON [url…]56    lat              REAL,57    lng              REAL,58    first_seen       REAL,59    updated_at       REAL,60    etl_run          INTEGER,            -- id de la passe ETL qui a vu l'annonce61    active           INTEGER DEFAULT 162);63CREATE INDEX IF NOT EXISTS idx_l_tx        ON listings (transaction_type, active);64CREATE INDEX IF NOT EXISTS idx_l_city      ON listings (transaction_type, city);65CREATE INDEX IF NOT EXISTS idx_l_type      ON listings (transaction_type, type);66CREATE INDEX IF NOT EXISTS idx_l_source    ON listings (source);67CREATE INDEX IF NOT EXISTS idx_l_geo       ON listings (lat, lng);68CREATE INDEX IF NOT EXISTS idx_l_price     ON listings (transaction_type, price);6970CREATE TABLE IF NOT EXISTS city_map (71    raw       TEXT PRIMARY KEY,          -- valeur brute rencontrée dans les sources72    canonical TEXT NOT NULL,             -- ville canonique ('' = irrécupérable)73    sector    TEXT DEFAULT '',           -- secteur extrait de la valeur brute74    n         INTEGER DEFAULT 0          -- occurrences à la dernière passe (audit)75);7677CREATE TABLE IF NOT EXISTS etl_log (78    id      INTEGER PRIMARY KEY AUTOINCREMENT,79    ts      REAL,80    origin  TEXT,81    found   INTEGER,82    kept    INTEGER,83    ok      INTEGER,84    message TEXT85);86"""878889def connect(path: Path | None = None) -> sqlite3.Connection:90    """Connexion à toitka.db (créée au besoin), Row factory + WAL."""91    p = path or DB_PATH92    p.parent.mkdir(parents=True, exist_ok=True)93    con = sqlite3.connect(p, timeout=60)94    con.row_factory = sqlite3.Row95    con.execute("PRAGMA journal_mode=WAL")96    con.execute("PRAGMA synchronous=NORMAL")97    con.execute("PRAGMA busy_timeout=120000")98    con.executescript(SCHEMA)99    return con100101102def connect_source(path: Path) -> sqlite3.Connection:103    """Connexion LECTURE SEULE à une réplique source (louka.db / immoka.db)."""104    con = sqlite3.connect(f"file:{path}?mode=ro", uri=True, timeout=60)105    con.row_factory = sqlite3.Row106    return con107