SPB Git forge

spb/rent-ka

Public
8commits 1branches 0releases
7.4 MBsize
maindefault branch
19 days agolast push
Python 68.8% TypeScript 18.6% CSS 8.7% JavaScript 3.3% HTML 0.6%
4.0 KB · 97 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Rent-Ka — Rental listings aggregator (Canada, outside Québec)3# Author: Simon-Pierre Boucher — contact@spboucher.ai4# scripts/merge_candidates.py : merge data/<platform>_candidates.json (output5#   of the discovery campaigns) into the live registries + sources.json.6#   Platforms: appfolio (apf_), buildium (bld_). Dedup by id/subdomain and by7#   normalized name across every platform registry (a manager already covered8#   via LiftSystem/RentCafe/etc. must not be ingested twice). Idempotent.9# Usage: python scripts/merge_candidates.py10# -----------------------------------------------------------------------------11from __future__ import annotations1213import json14import re15from pathlib import Path1617ROOT = Path(__file__).resolve().parents[1]18SOURCES = ROOT / "data" / "sources.json"19PLATFORMS = {  # candidates file -> (registry file, source_id prefix)20    "appfolio": ("appfolio_clients.json", "apf_"),21    "buildium": ("buildium_clients.json", "bld_"),22}232425def norm(s: str) -> str:26    return re.sub(r"[^a-z0-9]", "", (s or "").lower())272829def main() -> None:30    sources = json.loads(SOURCES.read_text("utf-8"))31    have_names = {norm(s.get("name")) for s in sources["sources"]}32    all_regs = ["appfolio_clients.json", "buildium_clients.json",33                "liftsystem_clients.json", "rentcafe_clients.json",34                "rentsyncgw_clients.json"]35    for reg in all_regs:36        try:37            d = json.loads((ROOT / "data" / reg).read_text("utf-8"))38            have_names |= {norm(c.get("name")) for c in d.get("clients") or []}39        except (OSError, ValueError):40            pass4142    total_added = 043    for plat, (reg_name, prefix) in PLATFORMS.items():44        cand_path = ROOT / "data" / f"{plat}_candidates.json"45        if not cand_path.exists():46            print(f"[merge] {plat}: no candidates file, skipped")47            continue48        cands = json.loads(cand_path.read_text("utf-8")).get("clients") or []49        reg_path = ROOT / "data" / reg_name50        reg = json.loads(reg_path.read_text("utf-8"))51        have_ids = {c.get("id") for c in reg["clients"]}52        have_subs = {c.get("subdomain") for c in reg["clients"]}53        added = 054        for c in cands:55            if c.get("status") != "valide" or not c.get("subdomain"):56                continue57            if c["id"] in have_ids or c["subdomain"] in have_subs or \58                    norm(c.get("name")) in have_names:59                continue60            reg["clients"].append(c)61            have_ids.add(c["id"])62            have_subs.add(c["subdomain"])63            have_names.add(norm(c.get("name")))64            if plat == "appfolio":65                listing_url = f"https://{c['subdomain']}.appfolio.com/listings"66            else:67                listing_url = (f"https://{c['subdomain']}.managebuilding.com"68                               f"/Resident/public/rentals")69            regions = c.get("regions") or []70            sources["sources"].append({71                "id": f"{prefix}{c['id']}",72                "name": c.get("name") or c["id"],73                "url": c.get("site") or listing_url,74                "listing_url": listing_url,75                "sectors": ", ".join(regions) if isinstance(regions, list)76                           else str(regions),77                "connector": f"{prefix}{c['id']}",78                "status": "actif",79                "region": "Canada (outside Québec)",80                "notes": f"canada-expansion 2026-08-28 — {plat} — "81                         f"{c.get('notes', '')}"[:300],82            })83            added += 184        reg["updated"] = "2026-08-28"85        reg_path.write_text(json.dumps(reg, ensure_ascii=False, indent=1))86        print(f"[merge] {plat}: +{added} clients "87              f"(registry now {len(reg['clients'])})")88        total_added += added8990    SOURCES.write_text(json.dumps(sources, ensure_ascii=False, indent=2))91    print(f"[merge] sources.json now {len(sources['sources'])} entries "92          f"(+{total_added})")939495if __name__ == "__main__":96    main()97