SPB Git forge

spb/home-ka

Public
10commits 1branches 0releases
793.0 KBsize
maindefault branch
20 days agolast push
Python 49.6% TypeScript 25.5% CSS 24.1%
2.7 KB · 70 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Home-Ka — US real-estate aggregator (Groupe KA)3# Author: Simon-Pierre Boucher — contact@spboucher.ai4# connectors/_maputil.py : shared helpers for config-driven field mapping.5#6# Family connectors (json, xml, csv, sftp, reso) turn a raw record into a7# Listing through a declarative mapping stored in the source config:8#   "mapping": {"external_id": "ListingId", "list_price": "price.amount",9#               "images": "photos[].url", "city": "address.city", ...}10# Paths are dot-separated; "[]" fans out over a list. Unmapped raw fields are11# preserved in Listing.details for the RESO passthrough in schema.finalize().12# -----------------------------------------------------------------------------13from __future__ import annotations1415from ..schema import Listing1617__all__ = ["dig", "apply_mapping"]1819_LIST_FIELDS = {"images", "features"}202122def dig(record, path: str):23    """Resolve a dot path in a nested dict/list structure.2425    "photos[].url" → [p["url"] for p in record["photos"]];26    "address.city" → record["address"]["city"]. Returns None when absent.27    """28    cur = record29    for part in str(path).split("."):30        if cur is None:31            return None32        fan = part.endswith("[]")33        key = part[:-2] if fan else part34        if key:35            if isinstance(cur, dict):36                cur = cur.get(key)37            elif isinstance(cur, list):38                cur = [c.get(key) if isinstance(c, dict) else None for c in cur]39            else:40                return None41        if fan and not isinstance(cur, list):42            cur = [cur] if cur is not None else []43    return cur444546def apply_mapping(source_id: str, record: dict, mapping: dict,47                  static: dict | None = None) -> Listing | None:48    """Build a Listing from a raw record and a declarative mapping."""49    kw: dict = {"source": source_id, "external_id": "", "url": ""}50    for field, path in (mapping or {}).items():51        v = dig(record, path)52        if v is None:53            continue54        if field in _LIST_FIELDS:55            v = [x for x in (v if isinstance(v, list) else [v]) if x]56        kw[field] = v57    for field, v in (static or {}).items():58        kw.setdefault(field, v)59    if not kw.get("external_id"):60        return None61    kw["external_id"] = str(kw["external_id"])62    kw.setdefault("details", {})63    # keep flat raw scalars for the RESO passthrough / debugging64    if isinstance(record, dict):65        for k, v in record.items():66            if isinstance(v, (str, int, float, bool)) and k not in kw["details"]:67                kw["details"][k] = v68    allowed = set(Listing.__dataclass_fields__)69    return Listing(**{k: v for k, v in kw.items() if k in allowed})70