Download unified_ingest.py from JohnAlbarkaIbrahim/SentimentScope: direct link, hf CLI and curl.
- Browser
- Download file 7.41 kB
-
https://huggingface.co/spaces/JohnAlbarkaIbrahim/SentimentScope/resolve/main/unified_ingest.py
- Command line
-
hf download hf://spaces/JohnAlbarkaIbrahim/SentimentScope/unified_ingest.py
-
curl -L -o unified_ingest.py https://huggingface.co/spaces/JohnAlbarkaIbrahim/SentimentScope/resolve/main/unified_ingest.py
7.41 kB
| """ | |
| unified_ingest.py | |
| βββββββββββββββββ | |
| Idempotent synchronization and seeding of local numpy embeddings and Supabase knowledge bases. | |
| """ | |
| import json | |
| import os | |
| import uuid | |
| import numpy as np | |
| from services.embedding_service import get_embedding_model | |
| from kb_docs import KB_DOCS | |
| from supabase import create_client, Client | |
| from config import SUPABASE_URL, SUPABASE_KEY | |
| from services.observability import setup_logger | |
| logger = setup_logger(__name__) | |
| def _fetch_remote_kb(supabase: Client) -> dict: | |
| """Fetches existing records from Supabase re_knowledge_base indexed by section title.""" | |
| try: | |
| remote_res = supabase.table("re_knowledge_base").select("id, section_title, chunk_content").execute() | |
| return {row["section_title"]: row for row in (remote_res.data or [])} | |
| except Exception as e: | |
| logger.error(f"Failed to fetch existing records from Supabase re_knowledge_base: {e}") | |
| return {} | |
| def _insert_chunk(supabase: Client, embedder, title: str, content: str) -> bool: | |
| """Computes embedding and inserts a new chunk into re_knowledge_base.""" | |
| logger.info(f"Adding new chunk: {title}") | |
| deterministic_id = str(uuid.uuid5(uuid.NAMESPACE_URL, title)) | |
| vector = embedder.encode(content).tolist() | |
| payload = { | |
| "id": deterministic_id, | |
| "section_title": title, | |
| "chunk_content": content, | |
| "embedding": vector, | |
| } | |
| try: | |
| supabase.table("re_knowledge_base").insert(payload).execute() | |
| return True | |
| except Exception as e: | |
| logger.error(f"Failed to insert {title}: {e}") | |
| return False | |
| def _update_chunk(supabase: Client, embedder, title: str, content: str) -> bool: | |
| """Recomputes embedding and updates an existing chunk in re_knowledge_base.""" | |
| logger.info(f"Updating modified chunk: {title}") | |
| vector = embedder.encode(content).tolist() | |
| payload = { | |
| "chunk_content": content, | |
| "embedding": vector, | |
| } | |
| try: | |
| supabase.table("re_knowledge_base").update(payload).eq("section_title", title).execute() | |
| return True | |
| except Exception as e: | |
| logger.error(f"Failed to update {title}: {e}") | |
| return False | |
| def _sync_single_doc(supabase: Client, embedder, doc: dict, remote_row: dict | None) -> str: | |
| """Syncs a single document against remote state, returning change status.""" | |
| title = doc.get("section_title") | |
| content = doc.get("chunk_content") | |
| if not title or not content: | |
| return "skipped" | |
| if remote_row is None: | |
| return "added" if _insert_chunk(supabase, embedder, title, content) else "failed" | |
| remote_content = remote_row.get("chunk_content", "") | |
| if remote_content.strip() != content.strip(): | |
| return "updated" if _update_chunk(supabase, embedder, title, content) else "failed" | |
| return "unchanged" | |
| def _cleanup_remote_orphans(supabase: Client, remote_data: dict, local_titles: set) -> int: | |
| """Deletes deprecated records from Supabase that no longer exist locally.""" | |
| deleted_count = 0 | |
| for remote_title, remote_row in remote_data.items(): | |
| if remote_title not in local_titles: | |
| logger.info(f"Deleting deprecated chunk from Supabase: {remote_title}") | |
| try: | |
| supabase.table("re_knowledge_base").delete().eq("id", remote_row["id"]).execute() | |
| deleted_count += 1 | |
| except Exception as e: | |
| logger.error(f"Failed to delete orphan {remote_title}: {e}") | |
| return deleted_count | |
| def reconcile_real_estate_kb(supabase: Client, embedder, methodology_docs: list) -> dict: | |
| """Performs 3-way idempotent reconciliation of methodology docs against Supabase.""" | |
| stats = {"added": 0, "updated": 0, "deleted": 0, "unchanged": 0} | |
| remote_data = _fetch_remote_kb(supabase) | |
| local_titles = set() | |
| for doc in methodology_docs: | |
| title = doc.get("section_title") | |
| if title: | |
| local_titles.add(title) | |
| outcome = _sync_single_doc(supabase, embedder, doc, remote_data.get(title)) | |
| if outcome in stats: | |
| stats[outcome] += 1 | |
| stats["deleted"] = _cleanup_remote_orphans(supabase, remote_data, local_titles) | |
| logger.info(f"Reconciliation complete: {stats}") | |
| return stats | |
| def _is_amara_kb_valid() -> bool: | |
| """Checks whether kb_embeddings.npy exists and has expected shape.""" | |
| if not os.path.exists("kb_embeddings.npy"): | |
| return False | |
| try: | |
| arr = np.load("kb_embeddings.npy") | |
| return len(arr) == len(KB_DOCS) | |
| except Exception: | |
| return False | |
| def _ensure_amara_kb(embedder): | |
| """Generates local numpy embeddings for Amara KB if absent or outdated.""" | |
| if _is_amara_kb_valid(): | |
| return | |
| logger.info("Regenerating local embeddings in kb_embeddings.npy...") | |
| texts = [d["text"] for d in KB_DOCS] | |
| kb_embeddings = embedder.encode(texts, normalize_embeddings=True) | |
| np.save("kb_embeddings.npy", kb_embeddings) | |
| logger.info(f"Embedded {len(texts)} KB docs and saved to kb_embeddings.npy") | |
| def _load_methodology_docs() -> list: | |
| """Loads methodology_kb.json if the file is present.""" | |
| if not os.path.exists("methodology_kb.json"): | |
| return [] | |
| try: | |
| with open("methodology_kb.json", "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| except Exception as e: | |
| logger.error(f"Failed to read methodology_kb.json: {e}") | |
| return [] | |
| def _should_regenerate_section_titles(section_titles: list, stats: dict) -> bool: | |
| """Determines if section title embeddings must be re-computed.""" | |
| if not os.path.exists("section_title_embeddings.npy") or not os.path.exists("section_titles.json"): | |
| return True | |
| try: | |
| with open("section_titles.json", "r", encoding="utf-8") as f: | |
| saved_titles = json.load(f) | |
| has_changes = stats.get("added", 0) > 0 or stats.get("deleted", 0) > 0 or stats.get("updated", 0) > 0 | |
| return saved_titles != section_titles or has_changes | |
| except Exception: | |
| return True | |
| def _ensure_section_title_embeddings(embedder, methodology_docs: list, stats: dict): | |
| """Saves synchronized section title embeddings for pre-routing.""" | |
| section_titles = [doc["section_title"] for doc in methodology_docs if "section_title" in doc] | |
| if not section_titles or not _should_regenerate_section_titles(section_titles, stats): | |
| return | |
| logger.info("Regenerating synchronized section_title_embeddings.npy...") | |
| title_embeddings = embedder.encode(section_titles, normalize_embeddings=True) | |
| np.save("section_title_embeddings.npy", title_embeddings) | |
| with open("section_titles.json", "w", encoding="utf-8") as f: | |
| json.dump(section_titles, f, indent=2) | |
| logger.info(f"Saved {len(section_titles)} synchronized section title embeddings for pre-routing.") | |
| def ensure_ingested(): | |
| """Seeds both local numpy embeddings and remote Supabase database idempotently.""" | |
| logger.info("Verifying ingestion state...") | |
| embedder = get_embedding_model() | |
| _ensure_amara_kb(embedder) | |
| methodology_docs = _load_methodology_docs() | |
| supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) | |
| stats = reconcile_real_estate_kb(supabase, embedder, methodology_docs) | |
| _ensure_section_title_embeddings(embedder, methodology_docs, stats) | |
| if __name__ == "__main__": | |
| ensure_ingested() | |