import os import requests import json from dotenv import load_dotenv load_dotenv(dotenv_path="/home/node/clawd/.env") NOTION_TOKEN = os.getenv("NOTION_API_KEY") HEADERS = { "Authorization": f"Bearer {NOTION_TOKEN}", "Content-Type": "application/json", "Notion-Version": "2022-06-28" } # --- Load Product Mapping --- try: with open("data/product_mapping.json", "r") as f: PRODUCT_MAP = json.load(f) except FileNotFoundError: print("❌ Product mapping not found. Run fetch_product_mapping.py first.") exit(1) # Helper to find DB ID def find_db_id(query_name): url = "https://api.notion.com/v1/search" payload = {"query": query_name, "filter": {"value": "database", "property": "object"}} resp = requests.post(url, headers=HEADERS, json=payload) if resp.status_code == 200: results = resp.json().get("results", []) if results: return results[0]['id'] return None def get_page_by_vertical(db_id, vertical_name): url = f"https://api.notion.com/v1/databases/{db_id}/query" # Using 'Vertical' as the title property name based on previous audit payload = { "filter": { "property": "Vertical", "title": {"equals": vertical_name} } } resp = requests.post(url, headers=HEADERS, json=payload) if resp.status_code == 200: results = resp.json().get("results", []) if results: return results[0] return None def update_page(page_id, properties): url = f"https://api.notion.com/v1/pages/{page_id}" payload = {"properties": properties} resp = requests.patch(url, headers=HEADERS, json=payload) if resp.status_code == 200: print(f"✅ Updated '{page_id}'") else: print(f"❌ Error updating '{page_id}': {resp.text}") def create_page(db_id, properties): url = "https://api.notion.com/v1/pages" payload = {"parent": {"database_id": db_id}, "properties": properties} resp = requests.post(url, headers=HEADERS, json=payload) if resp.status_code == 200: print(f"✅ Created new page") else: print(f"❌ Error creating page: {resp.text}") # --- CONTENT DEFINITION --- # Format: Vertical -> { props... } UPDATES = { "Healthcare - Care Home": { "product": "Cleaning Indoor Roboter (Wet Surface)", "pains": """[Primary Product: Cleaning] - Infektionsrisiko: Mangelnde Bodenhygiene und Keimverschleppung in sensiblen Bereichen gefährden Bewohner. - Dokumentationspflicht: Lückenlose Nachweise für Hygiene-Audits binden wertvolle Pflegezeit. - Personalmangel: Reinigungskräfte fehlen, Standards können manuell kaum gehalten werden. [Secondary Product: Service] - Pflegeressourcen: Fachkräfte binden bis zu 30% ihrer Zeit mit nicht-pflegerischen Transportwegen (Essen/Wäsche). - Körperliche Belastung: Schweres Heben und weite Wege führen zu krankheitsbedingten Ausfällen im Pflegeteam.""", "gains": """[Primary Product: Cleaning] - Audit-Sicherheit: Automatisierte, protokollierte Reinigung sichert Compliance ohne Mehraufwand. - Entlastung Housekeeping: Personal konzentriert sich auf Sichtreinigung und Desinfektion statt Bodenfläche. [Secondary Product: Service] - Mehr Zeit am Patienten: Reduktion der Laufwege gibt Pflegekräften 2-3 Std./Schicht zurück. - Mitarbeiterzufriedenheit: Reduktion körperlicher Belastung senkt Krankenstand.""", "ops_focus": True, "status": "Freigegeben", "notes": "Prio 1: Reinigung. Prio 2: Service (Essen). Fokus auf Fachkräftemangel & Hygiene." }, "Healthcare - Hospital": { "product": "Cleaning Indoor Roboter (Wet Surface)", "pains": """[Primary Product: Cleaning] - Infektionsschutz: Hohe Frequenz an Patientenbewegungen erfordert permanente Desinfektion der Böden. - Audit-Druck: Behördliche Auflagen verlangen lückenlose Dokumentation, die manuell kaum leistbar ist. - Kostendruck: Steigende Personalkosten bei fixen Fallpauschalen zwingen zur Effizienzsteigerung. [Secondary Product: Service] - Logistik-Aufwand: Transport von Proben, Wäsche und Essen bindet Pflegepersonal in unproduktiven Wegezeiten.""", "gains": """[Primary Product: Cleaning] - Hygiene-Standard: 24/7 gleichbleibende Reinigungsqualität reduziert Keimbelastung messbar. - Compliance: Automatische Protokollierung aller Reinigungsfahrten für Audits. [Secondary Product: Service] - Prozess-Effizienz: Automatisierter Warentransport entlastet Fachpersonal für medizinische Aufgaben.""", "ops_focus": True, "status": "Freigegeben", "notes": "Prio 1: Reinigung (Alex Veto). Service ist 'nice to have'. KPI: Hygiene-Sicherheit." }, "Leisure - Entertainment": { "product": "Service Roboter", # FIX: Changed from Cleaning to Service "pains": """[Primary Product: Service] - Service-Engpass: Umsatzverlust zu Stoßzeiten, da Personal nicht schnell genug Getränke/Snacks nachliefert. - Personalmangel: Schwierige Besetzung von Spätschichten führt zu geschlossenen Stationen/Bahnen. - Wartezeiten: Gäste sind unzufrieden, wenn Bestellungen zu lange dauern. [Secondary Product: Cleaning] - Bodenverschmutzung: Klebrige Böden (Getränke/Popcorn) im Foyer stören das Gästeerlebnis.""", "gains": """[Primary Product: Service] - Umsatzsteigerung: Permanente Verfügbarkeit von Snacks/Getränken direkt am Platz (Cross-Selling). - Erlebnis-Faktor: Innovative Roboter begeistern Gäste und fördern Social-Media-Sichtbarkeit. - Entlastung: Servicepersonal hat mehr Zeit für Gästebetreuung statt Laufwege.""", "ops_focus": True, # Keep secondary focus plausible "status": "Freigegeben", "notes": "Prio 1: Service Robotik (BellaBot). Cleaning nur Prio 2 (Foyer/Gänge)." }, "Logistics - Warehouse": { "product": "Cleaning Outdoor Roboter (Sweeper)", "pains": """[Primary Product: Sweeper] - Prozesssicherheit: Staub auf Sensoren und Lichtschranken führt zu Anlagenstörungen und Produktionsstopps. - Arbeitssicherheit: Verschmutzte Fahrwege durch Palettenreste/Staub erhöhen das Unfallrisiko. - Manuelle Bindung: Fachkräfte müssen kehren statt kommissionieren. [Secondary Product: Cleaning Wet] - Hartnäckige Verschmutzungen: Öl/Reifenabrieb erfordern Nassreinigung, die manuell zeitintensiv ist.""", "gains": """[Primary Product: Sweeper] - Staubfreie Umgebung: Werterhalt des Hallenbodens und Schutz empfindlicher Ware/Anlagen. - Produktivität: Reinigung erfolgt parallel zum Betrieb oder nachts, ohne Störung. - Sicherheit: Saubere Fahrwege reduzieren Unfallrisiko für Flurförderzeuge.""", "ops_focus": True, "status": "Freigegeben", "notes": "Prio 1: Sweeper (Staub). Prio 2: Wet. Transport schwierig wegen Paletten." }, "Tech - Data Center": { "product": "Security Roboter", "pains": """[Primary Product: Security] - Sicherheitsrisiko: Unbefugter Zutritt in Sicherheitsbereiche muss lückenlos detektiert werden (24/7). - Personalbindung: Wachpersonal ist teuer und kann nicht überall gleichzeitig sein. [Secondary Product: Cleaning] - Feinstaub: Staubpartikel in Serverräumen gefährden Hardware und Kühlung.""", "gains": """[Primary Product: Security] - Lückenlose Überwachung: Permanente Patrouille und sofortige Alarmierung ohne Personalbindung. - Dokumentation: Video- und Sensorprotokolle aller Ereignisse. [Secondary Product: Cleaning] - Ausfallsicherheit: Staubfreie Umgebung verlängert Hardware-Lebensdauer.""", "ops_focus": True, "status": "Klärrungsbedarf", # New, needs review "notes": "Neu angelegt. Prio 1 Security (lt. Transkript). Prio 2 Cleaning (Staub)." }, "Reinigungsdienstleister": { "product": "Cleaning Indoor Roboter (Wet Surface)", "pains": """[Primary Product: Cleaning] - Personalmangel: Schwierigkeit, zuverlässiges Personal für alle Objekte zu finden. - Kostendruck: Geringe Margen bei Ausschreibungen erfordern hohe Effizienz. - Qualitätsschwankungen: Manuelle Reinigung variiert stark, Kunden beschweren sich. - Fluktuation: Hoher Aufwand für ständige Neueinarbeitung.""", "gains": """[Primary Product: Cleaning] - Skalierbarkeit: Roboter übernehmen Flächenleistung, Personal macht Detailreinigung. - Innovation: Wettbewerbsvorteil bei Ausschreibungen durch Technologie-Einsatz. - Kalkulationssicherheit: Fixe Kosten statt variabler Personalkosten/Krankheitstage.""", "ops_focus": False, "status": "Klärrungsbedarf", "notes": "Neu angelegt. Zielgruppe: Wisag, Dussmann etc. (Alex: Größter Markt)." }, "Infrastructure - Communities": { "product": "Cleaning Indoor Roboter (Wet Surface)", "pains": """[Primary Product: Cleaning] - Großflächen-Reinigung: Sporthallen, Aulen und Flure binden enorm viele Personalstunden. - Budget-Druck: Kommunen müssen sparen, Reinigungskosten sind großer Posten. - Nutzungs-Konflikte: Reinigung muss in engen Zeitfenstern zwischen Schul/Vereinsnutzung erfolgen.""", "gains": """[Primary Product: Cleaning] - Kosteneffizienz: Reduktion der Reinigungskosten pro Quadratmeter. - Flexibilität: Reinigung kann nachts oder in Randzeiten erfolgen. - Werterhalt: Schonende, regelmäßige Reinigung verlängert Lebensdauer von Sportböden.""", "ops_focus": False, "status": "Klärrungsbedarf", "notes": "Neu angelegt (Schulen, Gemeinden)." }, "Infrastructure Parking": { "product": "Cleaning Outdoor Roboter (Sweeper)", "pains": """[Primary Product: Sweeper] - Außenwirkung: Verschmutzte Parkflächen/Müll schaden dem Image (erster Eindruck). - Manuelle Arbeit: Fegen von großen Außenflächen ist personalintensiv und unbeliebt. - Umwelt: Müll gelangt in die Umgebung/Kanalisation.""", "gains": """[Primary Product: Sweeper] - Gepflegtes Erscheinungsbild: Täglich saubere Außenanlagen. - Autonomie: Roboter reinigt selbstständig, auch bei schlechtem Wetter. - Entlastung: Hausmeister kann sich um Wartung kümmern statt Fegen.""", "ops_focus": False, "status": "Klärrungsbedarf", "notes": "Neu angelegt (Parkplätze, Außenanlagen)." } } def run_enrichment(): db_id = find_db_id("Industries") if not db_id: print("❌ Industries DB not found.") return print(f"--- Enriching Verticals in DB {db_id} ---") for vertical, data in UPDATES.items(): # Resolve Product ID prod_id = PRODUCT_MAP.get(data["product"]) if not prod_id: print(f"❌ Product '{data['product']}' not found in map. Skipping {vertical}.") continue # Prepare Properties props = { "Pains": {"rich_text": [{"text": {"content": data["pains"]}}]}, "Gains": {"rich_text": [{"text": {"content": data["gains"]}}]}, "Primary Product Category": {"relation": [{"id": prod_id}]}, "Notes": {"rich_text": [{"text": {"content": data["notes"]}}]}, # Handle Status (Try Select first, then Status) # We assume "Freigabe" exists } # Add checkbox if present in logic if "ops_focus" in data: props["Ops Focus: Secondary"] = {"checkbox": data["ops_focus"]} # Check if page exists page = get_page_by_vertical(db_id, vertical) if page: # Update existing # Add Status Update # (Note: Logic to detect Select vs Status type is needed, but we assume Select/Status name is consistent) # For robustness, we check the property type in the page object status_type = page['properties'].get("Freigabe", {}).get("type") if status_type == "status": props["Freigabe"] = {"status": {"name": data["status"]}} elif status_type == "select": props["Freigabe"] = {"select": {"name": data["status"]}} print(f"Updating '{vertical}'...") update_page(page['id'], props) else: # Create new print(f"Creating new vertical '{vertical}'...") props["Vertical"] = {"title": [{"text": {"content": vertical}}]} # Guess status type (usually Select or Status) - Try Status first as default in new Notion DBs # Or omit status if unsure, but we want to set it. # We'll try Status format. props["Freigabe"] = {"status": {"name": data["status"]}} create_page(db_id, props) if __name__ == "__main__": run_enrichment()