[30388f42] Infrastructure Hardening: Repaired CE/Connector DB schema, fixed frontend styling build, implemented robust echo shield in worker v2.1.1, and integrated Lead Engine into gateway.

This commit is contained in:
2026-03-07 14:08:42 +00:00
parent 35c30bc39a
commit d1b77fd2f6
415 changed files with 24100 additions and 13301 deletions

View File

@@ -0,0 +1,112 @@
import os
import requests
import json
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_VERTICALS = "2ec88f4285448014ab38ea664b4c2b81"
NOTION_DB_PRODUCTS = "2ec88f42854480f0b154f7a07342eb58"
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
def fetch_all_pages(db_id):
pages = []
has_more = True
start_cursor = None
while has_more:
url = f"https://api.notion.com/v1/databases/{db_id}/query"
payload = {"page_size": 100}
if start_cursor:
payload["start_cursor"] = start_cursor
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200:
print(f"Error fetching DB {db_id}: {response.status_code} - {response.text}")
break
data = response.json()
pages.extend(data.get("results", []))
has_more = data.get("has_more", False)
start_cursor = data.get("next_cursor")
return pages
def get_property_text(page, prop_name):
props = page.get("properties", {})
prop = props.get(prop_name)
if not prop:
return ""
prop_type = prop.get("type")
if prop_type == "title":
return "".join([t["plain_text"] for t in prop.get("title", [])])
elif prop_type == "rich_text":
return "".join([t["plain_text"] for t in prop.get("rich_text", [])])
elif prop_type == "select":
select = prop.get("select")
return select.get("name") if select else ""
elif prop_type == "multi_select":
return ", ".join([s["name"] for s in prop.get("multi_select", [])])
elif prop_type == "relation":
return [r["id"] for r in prop.get("relation", [])]
else:
return f"[Type: {prop_type}]"
def main():
print("--- 1. Fetching Product Categories ---")
product_pages = fetch_all_pages(NOTION_DB_PRODUCTS)
product_map = {}
for p in product_pages:
p_id = p["id"]
# Product Category name is likely the title property
# Let's find the title property key dynamically
title_key = next((k for k, v in p["properties"].items() if v["id"] == "title"), "Name")
name = get_property_text(p, title_key)
product_map[p_id] = name
# print(f"Product: {name} ({p_id})")
print(f"Loaded {len(product_map)} products.")
print("\n--- 2. Fetching Verticals ---")
vertical_pages = fetch_all_pages(NOTION_DB_VERTICALS)
print("\n--- 3. Analysis ---")
for v in vertical_pages:
# Determine Title Key (Vertical Name)
title_key = next((k for k, v in v["properties"].items() if v["id"] == "title"), "Vertical")
vertical_name = get_property_text(v, title_key)
# Primary Product
pp_ids = get_property_text(v, "Primary Product Category")
pp_names = [product_map.get(pid, f"Unknown ({pid})") for pid in pp_ids] if isinstance(pp_ids, list) else []
# Secondary Product
sp_ids = get_property_text(v, "Secondary Product")
sp_names = [product_map.get(pid, f"Unknown ({pid})") for pid in sp_ids] if isinstance(sp_ids, list) else []
# Pains & Gains
pains = get_property_text(v, "Pains")
gains = get_property_text(v, "Gains")
print(f"\n### {vertical_name}")
print(f"**Primary Product:** {', '.join(pp_names)}")
print(f"**Secondary Product:** {', '.join(sp_names)}")
print(f"**Pains:**\n{pains.strip()}")
print(f"**Gains:**\n{gains.strip()}")
print("-" * 40)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,117 @@
import os
import requests
import json
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81" # ID from the user's link
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found in environment.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
def get_vertical_data(vertical_name):
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"filter": {
"property": "Vertical",
"title": {
"contains": vertical_name
}
}
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200:
print(f"Error fetching data for '{vertical_name}': {response.status_code} - {response.text}")
return None
results = response.json().get("results", [])
if not results:
print(f"No entry found for vertical '{vertical_name}'")
return None
# Assuming the first result is the correct one
page = results[0]
props = page["properties"]
# Extract Pains
pains_prop = props.get("Pains", {}).get("rich_text", [])
pains = pains_prop[0]["plain_text"] if pains_prop else "N/A"
# Extract Gains
gains_prop = props.get("Gains", {}).get("rich_text", [])
gains = gains_prop[0]["plain_text"] if gains_prop else "N/A"
# Extract Ops Focus (Checkbox) if available
# The property name might be "Ops. Focus: Secondary" based on user description
# Let's check keys to be sure, but user mentioned "Ops. Focus: Secondary"
# Actually, let's just dump the keys if needed, but for now try to guess
ops_focus = "Unknown"
if "Ops. Focus: Secondary" in props:
ops_focus = props["Ops. Focus: Secondary"].get("checkbox", False)
elif "Ops Focus" in props: # Fallback guess
ops_focus = props["Ops Focus"].get("checkbox", False)
# Extract Product Categories
primary_product = "N/A"
secondary_product = "N/A"
# Assuming these are Select or Multi-select fields, or Relations.
# User mentioned "Primary Product Category" and "Secondary Product Category".
if "Primary Product Category" in props:
pp_data = props["Primary Product Category"].get("select") or props["Primary Product Category"].get("multi_select")
if pp_data:
if isinstance(pp_data, list):
primary_product = ", ".join([item["name"] for item in pp_data])
else:
primary_product = pp_data["name"]
if "Secondary Product Category" in props:
sp_data = props["Secondary Product Category"].get("select") or props["Secondary Product Category"].get("multi_select")
if sp_data:
if isinstance(sp_data, list):
secondary_product = ", ".join([item["name"] for item in sp_data])
else:
secondary_product = sp_data["name"]
return {
"name": vertical_name,
"pains": pains,
"gains": gains,
"ops_focus_secondary": ops_focus,
"primary_product": primary_product,
"secondary_product": secondary_product
}
verticals_to_check = [
"Krankenhaus",
"Pflege", # Might be "Altenheim" or similar
"Hotel",
"Industrie", # Might be "Manufacturing"
"Logistik",
"Einzelhandel",
"Facility Management"
]
print("-" * 60)
for v in verticals_to_check:
data = get_vertical_data(v)
if data:
print(f"VERTICAL: {data['name']}")
print(f" Primary Product: {data['primary_product']}")
print(f" Secondary Product: {data['secondary_product']}")
print(f" Ops. Focus Secondary: {data['ops_focus_secondary']}")
print(f" PAINS: {data['pains']}")
print(f" GAINS: {data['gains']}")
print("-" * 60)

View File

@@ -0,0 +1,90 @@
import os
import requests
import json
from dotenv import load_dotenv
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81" # Verticals DB
PRODUCT_DB_ID = "2ec88f42854480f0b154f7a07342eb58" # Product Categories DB (from user link)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
# 1. Fetch Product Map (ID -> Name)
product_map = {}
def fetch_products():
url = f"https://api.notion.com/v1/databases/{PRODUCT_DB_ID}/query"
response = requests.post(url, headers=headers, json={"page_size": 100})
if response.status_code == 200:
results = response.json().get("results", [])
for p in results:
p_id = p["id"]
# Name property might be "Name" or "Product Category"
props = p["properties"]
name = "Unknown"
if "Name" in props:
name = props["Name"]["title"][0]["plain_text"] if props["Name"]["title"] else "N/A"
elif "Product Category" in props:
name = props["Product Category"]["title"][0]["plain_text"] if props["Product Category"]["title"] else "N/A"
product_map[p_id] = name
# Also map the page ID itself if used in relations
else:
print(f"Error fetching products: {response.status_code}")
# 2. Check Verticals with Relation Resolution
def check_vertical_relations(search_term):
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"filter": {
"property": "Vertical",
"title": {
"contains": search_term
}
}
}
resp = requests.post(url, headers=headers, json=payload)
if resp.status_code == 200:
results = resp.json().get("results", [])
if not results:
print(f"❌ No vertical found for '{search_term}'")
return
for page in results:
props = page["properties"]
title = props["Vertical"]["title"][0]["plain_text"]
# Resolve Primary
pp_ids = [r["id"] for r in props.get("Primary Product Category", {}).get("relation", [])]
pp_names = [product_map.get(pid, pid) for pid in pp_ids]
# Resolve Secondary
sp_ids = [r["id"] for r in props.get("Secondary Product", {}).get("relation", [])]
sp_names = [product_map.get(pid, pid) for pid in sp_ids]
print(f"\n🔹 VERTICAL: {title}")
print(f" Primary Product (Rel): {', '.join(pp_names)}")
print(f" Secondary Product (Rel): {', '.join(sp_names)}")
# Pains/Gains short check
pains = props.get("Pains", {}).get("rich_text", [])
print(f" Pains Length: {len(pains[0]['plain_text']) if pains else 0} chars")
else:
print(f"Error fetching vertical: {resp.status_code}")
# Run
print("Fetching Product Map...")
fetch_products()
print(f"Loaded {len(product_map)} products.")
print("\nChecking Verticals...")
targets = ["Hospital", "Hotel", "Logistics", "Manufacturing", "Retail", "Reinigungs", "Dienstleister", "Facility"]
for t in targets:
check_vertical_relations(t)

View File

@@ -0,0 +1,87 @@
import os
import requests
import json
from dotenv import load_dotenv
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81"
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
def get_vertical_details(vertical_name_contains):
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"filter": {
"property": "Vertical",
"title": {
"contains": vertical_name_contains
}
}
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200:
print(f"Error: {response.status_code}")
return
results = response.json().get("results", [])
if not results:
print(f"❌ No entry found containing '{vertical_name_contains}'")
return
for page in results:
props = page["properties"]
# safely extract title
title_list = props.get("Vertical", {}).get("title", [])
title = title_list[0]["plain_text"] if title_list else "Unknown Title"
# Pains
pains_list = props.get("Pains", {}).get("rich_text", [])
pains = pains_list[0]["plain_text"] if pains_list else "N/A"
# Gains
gains_list = props.get("Gains", {}).get("rich_text", [])
gains = gains_list[0]["plain_text"] if gains_list else "N/A"
# Ops Focus
ops_focus = props.get("Ops Focus: Secondary", {}).get("checkbox", False)
# Products
# Primary is select
pp_select = props.get("Primary Product Category", {}).get("select")
pp = pp_select["name"] if pp_select else "N/A"
# Secondary is select
sp_select = props.get("Secondary Product", {}).get("select")
sp = sp_select["name"] if sp_select else "N/A"
print(f"\n🔹 VERTICAL: {title}")
print(f" Primary: {pp}")
print(f" Secondary: {sp}")
print(f" Ops Focus Secondary? {'✅ YES' if ops_focus else '❌ NO'}")
print(f" PAINS:\n {pains}")
print(f" GAINS:\n {gains}")
print("-" * 40)
targets = [
"Hospital",
"Hotel",
"Logistics",
"Manufacturing",
"Retail",
"Facility Management"
]
for t in targets:
get_vertical_details(t)

View File

@@ -0,0 +1,38 @@
import os
import requests
import json
from dotenv import load_dotenv
load_dotenv()
# Check for API Key
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
if not NOTION_API_KEY:
try:
with open("/app/n8n_api_Token_git.txt", "r") as f:
content = f.read()
if "secret_" in content:
NOTION_API_KEY = content.strip().split('\n')[0]
except:
pass
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found.")
exit(1)
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81"
headers = {"Authorization": f"Bearer {NOTION_API_KEY}", "Notion-Version": "2022-06-28", "Content-Type": "application/json"}
def list_db_properties():
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}"
resp = requests.get(url, headers=headers)
if resp.status_code == 200:
props = resp.json().get("properties", {})
print("Database Properties:")
for name, data in props.items():
print(f"- {name} (Type: {data['type']})")
else:
print(f"Error getting DB: {resp.text}")
if __name__ == "__main__":
list_db_properties()

View File

@@ -0,0 +1,66 @@
import os
import requests
import json
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81"
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found in environment.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
def list_pages_and_keys():
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"page_size": 10 # Just list a few to see structure
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200:
print(f"Error fetching data: {response.status_code} - {response.text}")
return
results = response.json().get("results", [])
if not results:
print("No pages found.")
return
print(f"Found {len(results)} pages.")
# Print keys from the first page
first_page = results[0]
props = first_page["properties"]
print("\n--- Property Keys Found ---")
for key in props.keys():
print(f"- {key}")
print("\n--- Page Titles (Verticals) ---")
for page in results:
title_prop = page["properties"].get("Vertical", {}).get("title", []) # Assuming title prop is named "Vertical" based on user input
if not title_prop:
# Try finding the title property dynamically if "Vertical" is wrong
for k, v in page["properties"].items():
if v["id"] == "title":
title_prop = v["title"]
break
if title_prop:
title = title_prop[0]["plain_text"]
print(f"- {title}")
else:
print("- (No Title)")
if __name__ == "__main__":
list_pages_and_keys()

View File

@@ -0,0 +1,89 @@
import os
import requests
import json
from dotenv import load_dotenv
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81"
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
# COMPLETE LIST OF UPDATES
updates = {
"Infrastructure - Transport": { # Airports, Stations
"Pains": "Sicherheitsbereiche erfordern personalintensives Screening von externen Reinigungskräften. Verschmutzte Böden (Winter/Salz) erhöhen das Rutschrisiko für Passagiere und Klagerisiken.",
"Gains": "Autonome Reinigung innerhalb der Sicherheitszonen ohne externe Personalwechsel. Permanente Trocknung von Nässe (Schneematsch) in Eingangsbereichen."
},
"Leisure - Indoor Active": { # Bowling, Cinema, Gym
"Pains": "Personal ist rar und teuer, Gäste erwarten aber Service am Platz. Reinigung im laufenden Betrieb stört den Erlebnischarakter.",
"Gains": "Service-Roboter als Event-Faktor und Entlastung: Getränke kommen zum Gast, Personal bleibt an der Bar/Theke. Konstante Sauberkeit auch bei hoher Frequenz."
},
"Leisure - Outdoor Park": { # Zoos, Theme Parks
"Pains": "Enorme Flächenleistung (Wege) erfordert viele Arbeitskräfte für die Grobschmutzbeseitigung (Laub, Müll). Sichtbare Reinigungstrupps stören die Immersion der Gäste.",
"Gains": "Autonome Großflächenreinigung (Kehren) in den frühen Morgenstunden vor Parköffnung. Erhalt der 'heilen Welt' (Immersion) für Besucher."
},
"Leisure - Wet & Spa": { # Pools, Thermen
"Pains": "Hohes Unfallrisiko durch Nässe auf Fliesen (Rutschgefahr). Hoher Aufwand für permanente Desinfektion und Trocknung im laufenden Betrieb bindet Aufsichtspersonal.",
"Gains": "Permanente Trocknung und Desinfektion kritischer Barfußbereiche. Reduktion der Rutschgefahr und Haftungsrisiken. Entlastung der Bademeister (Fokus auf Aufsicht)."
},
"Retail - Shopping Center": { # Malls
"Pains": "Food-Court ist der Schmutz-Hotspot: Verschüttete Getränke und Essensreste wirken unhygienisch und binden Personal dauerhaft. Dreckige Böden senken die Verweildauer.",
"Gains": "Sofortige Beseitigung von Malheuren im Food-Court. Steigerung der Aufenthaltsqualität und Verweildauer der Kunden durch sichtbare Sauberkeit."
},
"Retail - Non-Food": { # DIY, Furniture
"Pains": "Riesige Gangflächen verstauben schnell, Personal ist knapp und soll beraten, nicht kehren. Verschmutzte Böden wirken im Premium-Segment (Möbel) wertmindernd.",
"Gains": "Staubfreie Umgebung für angenehmes Einkaufsklima. Roboter reinigen autonom große Flächen, während Mitarbeiter für Kundenberatung verfügbar sind."
},
"Infrastructure - Public": { # Fairs, Schools
"Pains": "Extrem kurze Turnaround-Zeiten zwischen Messetagen oder Events. Hohe Nachtzuschläge für die Endreinigung der Hallengänge oder Klassenzimmer.",
"Gains": "Automatisierte Nachtreinigung der Gänge/Flure stellt die Optik für den nächsten Morgen sicher. Kalkulierbare Kosten ohne Nachtzuschlag."
},
"Hospitality - Gastronomy": { # Restaurants
"Pains": "Servicepersonal verbringt Zeit auf Laufwegen statt am Gast ('Teller-Taxi'). Personalmangel führt zu langen Wartezeiten und Umsatzverlust.",
"Gains": "Servicekräfte werden von Laufwegen befreit und haben Zeit für aktive Beratung und Verkauf (Upselling). Steigerung der Tischumschlagshäufigkeit."
}
}
def update_vertical(vertical_name, new_data):
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"filter": {
"property": "Vertical",
"title": {
"contains": vertical_name
}
}
}
resp = requests.post(url, headers=headers, json=payload)
if resp.status_code != 200: return
results = resp.json().get("results", [])
if not results:
print(f"Skipping {vertical_name} (Not found)")
return
page_id = results[0]["id"]
update_url = f"https://api.notion.com/v1/pages/{page_id}"
update_payload = {
"properties": {
"Pains": {"rich_text": [{"text": {"content": new_data["Pains"]}}]},
"Gains": {"rich_text": [{"text": {"content": new_data["Gains"]}}]}
}
}
requests.patch(update_url, headers=headers, json=update_payload)
print(f"✅ Updated {vertical_name}")
print("Starting FULL Notion Update...")
for v_name, data in updates.items():
update_vertical(v_name, data)
print("Done.")

View File

@@ -0,0 +1,94 @@
import os
import requests
import json
from dotenv import load_dotenv
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81"
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
# Define the updates with "Sharp" Pains/Gains
updates = {
"Healthcare - Hospital": {
"Pains": "Fachpflegekräfte sind bis zu 30% der Schichtzeit mit logistischen Routinetätigkeiten (Wäsche, Essen, Laborproben) gebunden ('Hände weg vom Bett'). Steigende Hygienerisiken bei gleichzeitigem Personalmangel im Reinigungsteam führen zu lückenhafter Dokumentation und Gefährdung der RKI-Konformität.",
"Gains": "Rückgewinnung von ca. 2,5h Fachkraft-Kapazität pro Schicht durch automatisierte Stationslogistik. Validierbare, RKI-konforme Reinigungsqualität rund um die Uhr, unabhängig vom Krankenstand des Reinigungsteams."
},
"Hospitality - Hotel": {
"Pains": "Enorme Fluktuation im Housekeeping gefährdet die pünktliche Zimmer-Freigabe (Check-in 15:00 Uhr). Hohe Nachtzuschläge oder fehlendes Personal verhindern, dass die Lobby und Konferenzbereiche morgens um 06:00 Uhr perfekt glänzen.",
"Gains": "Lautlose Nachtreinigung der Lobby und Flure ohne Personalzuschläge. Servicekräfte im Restaurant werden von Laufwegen ('Teller-Taxi') befreit und haben Zeit für aktives Upselling am Gast."
},
"Logistics - Warehouse": {
"Pains": "Verschmutzte Fahrwege durch Palettenabrieb und Staub gefährden die Sensorik von FTS (Fahrerlosen Transportsystemen) und erhöhen das Unfallrisiko für Flurförderzeuge. Manuelle Reinigung stört den 24/7-Betrieb und bindet Fachpersonal.",
"Gains": "Permanente Staubreduktion im laufenden Betrieb schützt empfindliche Anlagentechnik (Lichtschranken). Saubere Hallen als Visitenkarte und Sicherheitsfaktor (Rutschgefahr), ohne operative Unterbrechungen."
},
"Industry - Manufacturing": {
"Pains": "Hochbezahlte Facharbeiter unterbrechen die Wertschöpfung für unproduktive Such- und Holzeiten von Material (C-Teile). Intransparente Materialflüsse an der Linie führen zu Mikrostillständen und gefährden die Taktzeit.",
"Gains": "Just-in-Time Materialversorgung direkt an die Linie. Fachkräfte bleiben an der Maschine. Stabilisierung der Taktzeiten und OEE durch automatisierten Nachschub."
},
"Reinigungsdienstleister": { # Facility Management
"Pains": "Margendruck durch steigende Tariflöhne bei gleichzeitigem Preisdiktat der Auftraggeber. Hohe Fluktuation (>30%) führt zu ständiger Rekrutierung ('No-Show'-Quote), was Objektleiter bindet und die Qualitätskontrolle vernachlässigt.",
"Gains": "Kalkulationssicherheit durch Fixkosten statt variabler Personalkosten. Garantierte Reinigungsleistung in Objekten unabhängig vom Personalstand. Innovationsträger für Ausschreibungen."
},
"Retail - Food": { # Supermarkets
"Pains": "Reinigungskosten steigen linear zur Fläche, während Kundenfrequenz schwankt. Sichtbare Reinigungsmaschinen blockieren tagsüber Kundenwege ('Störfaktor'). Abends/Nachts schwer Personal zu finden.",
"Gains": "Unsichtbare Reinigung: Roboter fahren in Randzeiten oder weichen Kunden dynamisch aus. Konstantes Sauberkeits-Level ('Lobby-Effekt') steigert Verweildauer."
}
}
def update_vertical(vertical_name, new_data):
# 1. Find Page ID
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"filter": {
"property": "Vertical",
"title": {
"contains": vertical_name
}
}
}
resp = requests.post(url, headers=headers, json=payload)
if resp.status_code != 200:
print(f"Error searching {vertical_name}: {resp.status_code}")
return
results = resp.json().get("results", [])
if not results:
print(f"Skipping {vertical_name} (Not found)")
return
page_id = results[0]["id"]
# 2. Update Page
update_url = f"https://api.notion.com/v1/pages/{page_id}"
update_payload = {
"properties": {
"Pains": {
"rich_text": [{"text": {"content": new_data["Pains"]}}]
},
"Gains": {
"rich_text": [{"text": {"content": new_data["Gains"]}}]
}
}
}
upd_resp = requests.patch(update_url, headers=headers, json=update_payload)
if upd_resp.status_code == 200:
print(f"✅ Updated {vertical_name}")
else:
print(f"❌ Failed to update {vertical_name}: {upd_resp.text}")
print("Starting Notion Update...")
for v_name, data in updates.items():
update_vertical(v_name, data)
print("Done.")

View File

@@ -0,0 +1,194 @@
import os
import requests
import json
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_VERTICALS = "2ec88f4285448014ab38ea664b4c2b81"
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
# The approved changes from ANALYSIS_AND_PROPOSAL.md
UPDATES = {
"Automotive - Dealer": {
"Pains": """[Primary Product: Security]
- Teile-Diebstahl: Organisierte Banden demontieren nachts Katalysatoren und Räder enormer Schaden und Versicherungsstress.
- Vandalismus: Zerkratzte Neuwagen auf dem Außenhof mindern den Verkaufswert drastisch.
- Personalkosten: Lückenlose menschliche Nachtbewachung ist für viele Standorte wirtschaftlich kaum darstellbar.
[Secondary Product: Cleaning Outdoor]
- Image-Verlust: Ein verschmutzter Außenbereich (Laub, Müll) passt nicht zum Premium-Anspruch der ausgestellten Fahrzeuge.
- Manueller Aufwand: Verkaufspersonal oder teure Hausmeisterdienste binden Zeit mit unproduktivem Fegen.""",
"Gains": """[Primary Product: Security]
- Abschreckung & Intervention: Permanente Roboter-Präsenz wirkt präventiv; bei Alarm schaltet sich sofort eine Leitstelle auf.
- Asset-Schutz: Reduktion von Versicherungsschäden und Selbstbehalten durch lückenlose Dokumentation.
[Secondary Product: Cleaning Outdoor]
- Premium-Präsentation: Der Hof ist bereits morgens bei Kundenöffnung makellos sauber.
- Automatisierung: Täglich gereinigte Flächen ohne manuellen Eingriff."""
},
"Industry - Manufacturing": {
"Pains": """[Primary Product: Cleaning Indoor]
- Prozess-Sicherheit: Staub und Abrieb auf Fahrwegen gefährden empfindliche Sensorik (z.B. von FTS) und die Produktqualität.
- Arbeitssicherheit: Rutschgefahr durch feine Staubschichten oder ausgelaufene (nicht-chemische) Flüssigkeiten erhöht das Unfallrisiko.
- Ressourcen-Verschwendung: Hochbezahlte Fachkräfte müssen Maschinen stoppen, um ihr Umfeld zu reinigen.
[Secondary Product: Transport]
- Intransparenz & Suchzeiten: Facharbeiter unterbrechen die Wertschöpfung für unproduktive Materialbeschaffung ("C-Teile holen").
- Mikrostillstände: Fehlendes Material an der Linie stoppt den Takt.""",
"Gains": """[Primary Product: Cleaning Indoor]
- Konstante Bodenqualität: Definierte Sauberkeitsstandards (Audit-Ready) rund um die Uhr.
- Unfallschutz: Reduktion von Arbeitsunfällen durch rutschfreie Verkehrswege.
[Secondary Product: Transport]
- Just-in-Time Logistik: Automatisierter Nachschub hält die Fachkraft wertschöpfend an der Maschine.
- Fluss-Optimierung: Stabilisierung der Taktzeiten und OEE durch verlässliche Materialflüsse."""
},
"Healthcare - Hospital": {
"Pains": """[Primary Product: Cleaning Indoor]
- Hygienerisiko & Kreuzkontamination: Manuelle Reinigung ist oft fehleranfällig und variiert stark in der Qualität (Gefahr für Patienten).
- Dokumentationspflicht: Der Nachweis RKI-konformer Reinigung bindet wertvolle Zeit und ist bei Personalmangel lückenhaft.
- Personalnot: Fehlende Reinigungskräfte führen zu gesperrten Bereichen oder sinkendem Hygienelevel.
[Secondary Product: Service]
- Berufsfremde Tätigkeiten: Pflegekräfte verbringen bis zu 30% der Schichtzeit mit Hol- und Bringdiensten (Essen, Wäsche, Labor).
- Physische Überlastung: Lange Laufwege in großen Kliniken erhöhen die Erschöpfung des Fachpersonals.""",
"Gains": """[Primary Product: Cleaning Indoor]
- Validierbare Hygiene: Robotergarantierte, protokollierte Desinfektionsleistung audit-sicher auf Knopfdruck.
- 24/7 Verfügbarkeit: Konstantes Hygienelevel auch nachts und am Wochenende, unabhängig vom Dienstplan.
[Secondary Product: Service]
- Zeit für Patienten: Rückgewinnung von ca. 2,5 Stunden Fachkraft-Kapazität pro Schicht für die Pflege.
- Mitarbeiterzufriedenheit: Reduktion der Laufwege ("Schrittzähler") entlastet das Team spürbar."""
},
"Logistics - Warehouse": {
"Pains": """[Primary Product: Cleaning (Sweeper/Dry)]
- Grobschmutz & Palettenreste: Holzspäne und Verpackungsreste gefährden Reifen von Flurförderzeugen und blockieren Lichtschranken.
- Staubbelastung: Aufgewirbelter Staub legt sich auf Waren und Verpackungen (Reklamationsgrund) und schadet der Gesundheit.
- Manuelle Bindung: Mitarbeiter müssen große Flächen manuell kehren, statt zu kommissionieren.
[Secondary Product: Cleaning (Wet)]
- Hartnäckige Verschmutzungen: Eingefahrene Spuren, die durch reines Kehren nicht lösbar sind.""",
"Gains": """[Primary Product: Cleaning (Sweeper/Dry)]
- Anlagenschutz: Sauberer Boden verhindert Störungen an Fördertechnik und Sensoren durch Staub/Teile.
- Staubfreie Ware: Produkte verlassen das Lager in sauberem Zustand (Qualitätsanspruch).
[Secondary Product: Cleaning (Wet)]
- Grundsauberkeit: Gelegentliche Nassreinigung für Tiefenhygiene in Fahrgassen."""
},
"Retail - Food": {
"Pains": """[Primary Product: Cleaning Indoor]
- "Malheur-Management": Zerbrochene Gläser oder ausgelaufene Flüssigkeiten (Haverien) bilden sofortige Rutschfallen und binden Personal.
- Optischer Eindruck: Grauschleier und verschmutzte Böden senken das Frische-Empfinden der Kunden massiv.
- Personal-Engpass: Marktpersonal soll Regale füllen und kassieren, nicht mit der Scheuersaugmaschine fahren.
[Secondary Product: Service]
- Fehlende Beratung: Kunden finden Produkte nicht und brechen den Kauf ab, da kein Personal greifbar ist.""",
"Gains": """[Primary Product: Cleaning Indoor]
- Sofortige Sicherheit: Roboter beseitigt Rutschgefahren autonom und schnell.
- Frische-Optik: Permanent glänzende Böden ("Lobby-Effekt") unterstreichen die Qualität der Lebensmittel.
[Secondary Product: Service]
- Umsatz-Boost: Roboter führt Kunden direkt zum gesuchten Produkt oder bewirbt Aktionen aktiv am POS."""
},
"Hospitality - Gastronomy": {
"Pains": """[Primary Product: Cleaning Indoor]
- Klebrige Böden: Verschüttete Getränke und Speisereste wirken unhygienisch und stören das Ambiente.
- Randzeiten-Problem: Nach Schließung ist es schwer, Personal für die Grundreinigung zu finden (Nachtzuschläge).
[Secondary Product: Service]
- "Teller-Taxi": Servicekräfte verbringen 80% der Zeit mit Laufen (Küche <-> Gast) statt mit Verkaufen/Betreuung.
- Personalmangel: Zu wenig Kellner führen zu langen Wartezeiten, kalten Speisen und genervten Gästen.""",
"Gains": """[Primary Product: Cleaning Indoor]
- Makelloses Ambiente: Sauberer Boden als Visitenkarte des Restaurants.
- Zuverlässigkeit: Die Grundreinigung findet jede Nacht garantiert statt.
[Secondary Product: Service]
- Mehr Umsatz am Gast: Servicekraft hat Zeit für Empfehlungen (Wein, Dessert) und Upselling.
- Entlastung: Roboter übernimmt das schwere Tragen (Tabletts), Personal bleibt im Gastraum präsent."""
},
"Leisure - Outdoor Park": {
"Pains": """[Primary Product: Cleaning Outdoor]
- Immersion-Breaker: Müll und Laub auf den Wegen stören die perfekte Illusion ("Heile Welt") des Parks.
- Enorme Flächen: Kilometerlange Wegenetze binden ganze Kolonnen von Reinigungskräften.
- Sicherheit: Rutschgefahr durch nasses Laub oder Abfall.
[Secondary Product: Service]
- Versorgungslücken: An abgelegenen Attraktionen fehlt oft Gastronomie-Angebot.""",
"Gains": """[Primary Product: Cleaning Outdoor]
- Perfekte Inszenierung: Unsichtbare Reinigung in den frühen Morgenstunden sichert das perfekte Erlebnis bei Parköffnung.
- Effizienz: Ein Roboter schafft die Flächenleistung mehrerer manueller Kehrer.
[Secondary Product: Service]
- Mobiler Verkauf: Roboter bringen Getränke/Eis direkt zu den Warteschlangen (Zusatzumsatz)."""
},
"Energy - Grid & Utilities": {
"Pains": """[Primary Product: Security]
- Sabotage & Diebstahl: Kupferdiebstahl in Umspannwerken verursacht Millionenschäden und Versorgungsausfälle.
- Reaktionszeit: Entlegene Standorte sind für Interventionskräfte oft zu spät erreichbar.
- Sicherheitsrisiko Mensch: Alleinarbeit bei Kontrollgängen in Hochspannungsbereichen ist gefährlich.""",
"Gains": """[Primary Product: Security]
- First Responder Maschine: Roboter ist bereits vor Ort, verifiziert Alarm und schreckt Täter ab.
- KRITIS-Compliance: Lückenlose, manipulationssichere Dokumentation aller Vorfälle für Behörden.
- Arbeitsschutz: Roboter übernimmt gefährliche Routinekontrollen (z.B. Thermografie an Trafos)."""
}
}
def get_page_id(vertical_name):
url = f"https://api.notion.com/v1/databases/{NOTION_DB_VERTICALS}/query"
payload = {
"filter": {
"property": "Vertical",
"title": {
"equals": vertical_name
}
}
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 200:
results = response.json().get("results", [])
if results:
return results[0]["id"]
return None
def update_page(page_id, pains, gains):
url = f"https://api.notion.com/v1/pages/{page_id}"
payload = {
"properties": {
"Pains": {
"rich_text": [{"text": {"content": pains}}]
},
"Gains": {
"rich_text": [{"text": {"content": gains}}]
}
}
}
response = requests.patch(url, headers=headers, json=payload)
if response.status_code == 200:
print(f"✅ Updated {page_id}")
else:
print(f"❌ Failed to update {page_id}: {response.text}")
def main():
print("Starting update...")
for vertical, content in UPDATES.items():
print(f"Processing '{vertical}'...")
page_id = get_page_id(vertical)
if page_id:
update_page(page_id, content["Pains"], content["Gains"])
else:
print(f"⚠️ Vertical '{vertical}' not found in Notion.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,162 @@
import os
import requests
import json
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_VERTICALS = "2ec88f4285448014ab38ea664b4c2b81"
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
# The approved changes from ANALYSIS_AND_PROPOSAL.md for Phase 2
UPDATES = {
"Energy - Solar/Wind": {
"Pains": """[Primary Product: Security]
- Kupfer-Diebstahl: Professionelle Banden plündern abgelegene Parks in Minuten; der Schaden durch Betriebsunterbrechung übersteigt den Materialwert oft weit.
- Interventionszeit: Bis der Wachdienst eintrifft ("Blaulicht-Fahrt"), sind die Täter längst verschwunden.
- Kostenfalle Falschalarm: Wildtiere oder wetterbedingte Störungen lösen teure, unnötige Polizeieinsätze aus.""",
"Gains": """[Primary Product: Security]
- Sofort-Verifikation: KI-gestützte Erkennung unterscheidet zuverlässig zwischen Tier und Mensch und liefert Live-Bilder in Sekunden.
- Präventive Abschreckung: Autonome Patrouillen signalisieren "Hier wird bewacht" und verhindern den Versuch.
- Lückenlose Beweissicherung: Gerichtsfeste Dokumentation von Vorfällen für Versicherung und Strafverfolgung."""
},
"Infrastructure - Public": {
"Pains": """[Primary Product: Cleaning Indoor]
- Zeitdruck (Turnaround): Zwischen Messe-Ende und Öffnung am nächsten Tag liegen nur wenige Stunden für eine Komplettreinigung.
- Kostenspirale: Nacht- und Wochenendzuschläge für manuelles Personal belasten das Budget massiv.
- Personalverfügbarkeit: Für Spitzenlasten (Messezeiten) ist kurzfristig kaum ausreichendes Personal zu finden.
[Secondary Product: Cleaning Outdoor]
- Erster Eindruck: Vermüllte Vorplätze und Zufahrten schaden dem Image der Veranstaltung schon bei Ankunft.""",
"Gains": """[Primary Product: Cleaning Indoor]
- Planbare Kapazität: Roboter reinigen autonom die Kilometer langen Gänge ("Gang-Reinigung"), Personal fokussiert sich auf Stände und Details.
- Kosteneffizienz: Fixe Kosten statt variabler Zuschläge für Nachtarbeit.
[Secondary Product: Cleaning Outdoor]
- Repräsentative Außenwirkung: Sauberer Empfangsbereich ohne permanenten Personaleinsatz."""
},
"Infrastructure - Transport": {
"Pains": """[Primary Product: Cleaning Indoor]
- Sicherheits-Checks: Jede externe Reinigungskraft im Sicherheitsbereich erfordert aufwändige Überprüfungen (ZÜP) und Begleitung.
- Passagier-Störung: Laute, manuelle Reinigungsmaschinen behindern Laufwege und Durchsagen im 24/7-Betrieb.
- Hochfrequenz-Verschmutzung: Kaffee-Flecken und Nässe (Winter) müssen sofort beseitigt werden, um Rutschunfälle zu vermeiden.
[Secondary Product: Cleaning Outdoor]
- Müll-Aufkommen: Raucherbereiche und Taxi-Spuren verkommen schnell durch Zigarettenstummel und Kleinmüll.""",
"Gains": """[Primary Product: Cleaning Indoor]
- "Approved Staff": Roboter verbleibt im Sicherheitsbereich kein täglicher Check-in/Check-out nötig.
- Silent Cleaning: Leise, autonome Navigation zwischen Passagieren stört den Betriebsablauf nicht.
[Secondary Product: Cleaning Outdoor]
- Sauberer Transfer: Gepflegte Außenanlagen als Visitenkarte der Mobilitätsdrehscheibe."""
},
"Retail - Shopping Center": {
"Pains": """[Primary Product: Cleaning Indoor]
- Food-Court-Chaos: Zu Stoßzeiten kommen Reinigungskräfte mit dem Wischen von verschütteten Getränken und Essensresten kaum nach.
- Rutschfallen: Nasse Eingänge (Regen) und verschmutzte Zonen sind Haftungsrisiken für den Betreiber.
- Image-Faktor: Ein "grauer" oder fleckiger Boden senkt die Aufenthaltsqualität und damit die Verweildauer der Kunden.
[Secondary Product: Cleaning Outdoor]
- Parkplatz-Pflege: Müll auf Parkplätzen und in Parkhäusern ist der erste negative Touchpoint für Besucher.""",
"Gains": """[Primary Product: Cleaning Indoor]
- Reaktionsschnelligkeit: Roboter sind permanent präsent und beseitigen Malheure sofort, bevor sie antrocknen.
- Hochglanz-Optik: Konstante Pflege poliert den Steinboden und sorgt für ein hochwertiges Ambiente.
[Secondary Product: Cleaning Outdoor]
- Willkommens-Kultur: Sauberer Außenbereich lädt zum Betreten ein."""
},
"Leisure - Wet & Spa": {
"Pains": """[Primary Product: Cleaning Indoor]
- Rutsch-Unfälle: Staunässe auf Fliesen ist die Unfallursache Nummer 1 in Bädern hohes Haftungsrisiko.
- Hygiene-Sensibilität: Im Barfußbereich (Umkleiden/Gänge) erwarten Gäste klinische Sauberkeit; Haare und Fussel sind "Ekel-Faktor".
- Personal-Konflikt: Fachangestellte für Bäderbetriebe sollen die Beckenaufsicht führen (Sicherheit), nicht wischen.""",
"Gains": """[Primary Product: Cleaning Indoor]
- Permanente Sicherheit: Roboter trocknen Laufwege kontinuierlich und minimieren das Rutschrisiko aktiv.
- Entlastung der Aufsicht: Bademeister können sich zu 100% auf die Sicherheit der Badegäste konzentrieren.
- Hygiene-Standard: Dokumentierte Desinfektion und Reinigung sichert Top-Bewertungen."""
},
"Corporate - Campus": {
"Pains": """[Primary Product: Cleaning Indoor]
- Repräsentativität: Empfangshallen und Atrien sind das Aushängeschild sichtbarer Staub oder Schlieren wirken unprofessionell.
- Kostendruck Facility: Enorme Flächen (Flure/Verbindungsgänge) erzeugen hohe laufende Reinigungskosten.
[Secondary Product: Cleaning Outdoor]
- Campus-Pflege: Weitläufige Außenanlagen manuell sauber zu halten, bindet unverhältnismäßig viele Ressourcen.""",
"Gains": """[Primary Product: Cleaning Indoor]
- Innovations-Statement: Einsatz von Robotik unterstreicht den technologischen Führungsanspruch des Unternehmens gegenüber Besuchern und Bewerbern.
- Konstante Qualität: Einheitliches Sauberkeitsniveau in allen Gebäudeteilen, unabhängig von Tagesform oder Krankenstand.
[Secondary Product: Cleaning Outdoor]
- Gepflegtes Erscheinungsbild: Automatisierte Kehrleistung sorgt für repräsentative Wege und Plätze."""
},
"Reinigungsdienstleister": {
"Pains": """[Primary Product: Cleaning Indoor]
- Personal-Mangel & Fluktuation: Hohe "No-Show"-Quoten und ständige Neurekrutierung binden Objektleiter massiv und gefährden die Vertragserfüllung.
- Margen-Verfall: Steigende Tariflöhne bei gleichzeitigem Preisdruck der Auftraggeber lassen kaum noch Gewinn zu.
- Qualitäts-Schwankungen: Wechselndes, ungelernte Personal liefert oft unzureichende Ergebnisse, was zu Reklamationen und Kürzungen führt.""",
"Gains": """[Primary Product: Cleaning Indoor]
- Kalkulations-Sicherheit: Roboter bieten fixe Kosten statt unkalkulierbarer Krankheits- und Ausfallrisiken.
- Wettbewerbsvorteil: Mit Robotik-Konzepten punkten Dienstleister bei Ausschreibungen als Innovationsführer.
- Entlastung Objektleitung: Weniger Personal-Management bedeutet mehr Zeit für Kundenpflege und Qualitätskontrolle."""
}
}
def get_page_id(vertical_name):
# Try to find the page with a filter on "Vertical" property
url = f"https://api.notion.com/v1/databases/{NOTION_DB_VERTICALS}/query"
payload = {
"filter": {
"property": "Vertical",
"title": {
"equals": vertical_name
}
}
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 200:
results = response.json().get("results", [])
if results:
return results[0]["id"]
return None
def update_page(page_id, pains, gains):
url = f"https://api.notion.com/v1/pages/{page_id}"
payload = {
"properties": {
"Pains": {
"rich_text": [{"text": {"content": pains}}]
},
"Gains": {
"rich_text": [{"text": {"content": gains}}]
}
}
}
response = requests.patch(url, headers=headers, json=payload)
if response.status_code == 200:
print(f"✅ Updated {page_id}")
else:
print(f"❌ Failed to update {page_id}: {response.text}")
def main():
print("Starting update Phase 2...")
for vertical, content in UPDATES.items():
print(f"Processing '{vertical}'...")
page_id = get_page_id(vertical)
if page_id:
update_page(page_id, content["Pains"], content["Gains"])
else:
print(f"⚠️ Vertical '{vertical}' not found in Notion.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,97 @@
import os
import requests
import json
from dotenv import load_dotenv
load_dotenv()
# Check for API Key
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
if not NOTION_API_KEY:
try:
with open("/app/n8n_api_Token_git.txt", "r") as f:
content = f.read()
if "secret_" in content:
NOTION_API_KEY = content.strip().split('\n')[0]
except:
pass
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found.")
exit(1)
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81"
headers = {"Authorization": f"Bearer {NOTION_API_KEY}", "Notion-Version": "2022-06-28", "Content-Type": "application/json"}
# Updates Definition
updates = {
"Energy - Grid & Utilities": {
"Pains": "[Primary Product: Security]\n- Sabotage & Diebstahl: Kupferdiebstahl in Umspannwerken verursacht Millionenschäden und Versorgungsausfälle.\n- Reaktionszeit: Entlegene Standorte sind für Interventionskräfte oft zu spät erreichbar.\n- Sicherheitsrisiko Mensch: Alleinarbeit bei Kontrollgängen in Hochspannungsbereichen ist gefährlich.\n\n[Secondary Product: Cleaning Indoor]\n- Verschmutzung in Umspannwerken: Staubablagerungen auf Böden und in technischen Bereichen können die Betriebssicherheit gefährden.\n- Manuelle Reinigung in Sicherheitsbereichen: Externes Reinigungspersonal benötigt aufwändige Sicherheitsunterweisungen und Begleitung.\n- Große Distanzen: Die Reinigung weitläufiger, oft unbemannter Anlagen ist logistisch aufwändig und wird häufig vernachlässigt.",
"Gains": "[Primary Product: Security]\n- First Responder Maschine: Roboter ist bereits vor Ort, verifiziert Alarm und schreckt Täter ab.\n- KRITIS-Compliance: Lückenlose, manipulationssichere Dokumentation aller Vorfälle für Behörden.\n- Arbeitsschutz: Roboter übernimmt gefährliche Routinekontrollen (z.B. Thermografie an Trafos).\n\n[Secondary Product: Cleaning Indoor]\n- Permanente Sauberkeit: Autonome Reinigung gewährleistet staubfreie Böden und reduziert das Risiko von technischen Störungen.\n- Zugang ohne Sicherheitsrisiko: Der Roboter ist \"Teil der Anlage\" und benötigt keine externe Sicherheitsfreigabe oder Begleitung.\n- Ressourceneffizienz: Kosteneffiziente Reinigung großer Flächen ohne Anreisezeiten für Dienstleister.",
"Secondary_Product_Name": "Cleaning Indoor (Wet Surface)"
},
"Retail - Non-Food": {
"Pains": "[Primary Product: Cleaning Indoor]\n- Optischer Eindruck: Verschmutzte Böden, insbesondere im Premium-Segment (Möbel, Elektronik), mindern die Wertwahrnehmung der ausgestellten Produkte massiv.\n- Staubentwicklung auf großen Flächen: In Möbelhäusern und Baumärkten sammelt sich auf den riesigen Gangflächen schnell Staub, der das Einkaufserlebnis trübt.\n- Personalbindung: Verkaufsberater sollen Kunden betreuen und Umsatz generieren, statt wertvolle Zeit mit unproduktiven Kehr- oder Wischtätigkeiten zu verbringen.\n\n[Secondary Product: Service]\n- Unübersichtlichkeit: Kunden finden in großen Märkten oft nicht sofort das gesuchte Produkt und binden Personal für einfache Wegbeschreibungen.\n- Fehlende Interaktion: Passive Verkaufsflächen bieten wenig Anreiz für Kunden, sich länger aufzuhalten oder zu interagieren.",
"Gains": "[Primary Product: Cleaning Indoor]\n- Perfektes Einkaufserlebnis: Stets makellos saubere Böden unterstreichen den Qualitätsanspruch des Sortiments und laden zum Verweilen ein.\n- Fokus auf Beratung: Mitarbeiter werden von routinemäßigen Reinigungsaufgaben befreit und können sich voll auf den Kunden und den Verkauf konzentrieren.\n- Kosteneffizienz auf der Fläche: Autonome Reinigung großer Quadratmeterzahlen ist deutlich günstiger als manuelle Arbeit, besonders außerhalb der Öffnungszeiten.\n\n[Secondary Product: Service]\n- Innovativer Kundenservice: Roboter führen Kunden autonom zum gesuchten Produktregal (\"Guide-Funktion\").\n- Wow-Effekt: Der Einsatz von Robotik modernisiert das Markenimage und zieht Aufmerksamkeit auf sich."
},
"Tech - Data Center": {
"Pains": "[Primary Product: Security]\n- Sicherheitsrisiko Zutritt: Unbefugter Zutritt in Hochsicherheitsbereiche (Serverräume, Cages) muss lückenlos detektiert und dokumentiert werden, um Zertifizierungen (ISO 27001) nicht zu gefährden.\n- Fachkräftemangel Security: Qualifiziertes Wachpersonal mit Sicherheitsüberprüfung ist extrem schwer zu finden und teuer im 24/7-Schichtbetrieb.\n- Dokumentationslücken: Manuelle Patrouillen sind fehleranfällig und Protokolle können unvollständig sein, was bei Audits zu Problemen führt.\n\n[Secondary Product: Cleaning Indoor]\n- Gefahr durch Staubpartikel: Feinstaub in Serverräumen kann Kühlsysteme verstopfen und Kurzschlüsse verursachen, was die Hardware-Lebensdauer verkürzt.\n- Sicherheitsrisiko Reinigungspersonal: Externes Reinigungspersonal in Sicherheitsbereichen erfordert ständige Begleitung und Überwachung (Vier-Augen-Prinzip), was Personal bindet.",
"Gains": "[Primary Product: Security]\n- Lückenloser Audit-Trail: Automatisierte, manipulationssichere Dokumentation aller Kontrollgänge und Ereignisse sichert Compliance-Anforderungen.\n- 24/7 Präsenz: Der Roboter ist immer im Dienst, wird nicht müde und garantiert eine konstante Überwachungsqualität ohne Schichtwechsel-Risiken.\n- Sofortige Alarmierung: Bei Anomalien (offene Rack-Tür, Wärmeentwicklung) erfolgt eine Echtzeit-Meldung an die Leitzentrale.\n\n[Secondary Product: Cleaning Indoor]\n- Maximale Hardware-Verfügbarkeit: Staubfreie Umgebung optimiert die Kühleffizienz und reduziert das Ausfallrisiko teurer Komponenten.\n- Autonome \"Trusted\" Cleaning: Der Roboter reinigt sensibelste Bereiche ohne das Risiko menschlichen Fehlverhaltens oder unbefugten Zugriffs.",
"Secondary_Product_Name": "Cleaning Indoor (Wet Surface)"
}
}
def get_product_page_id(product_name):
url = "https://api.notion.com/v1/search"
payload = {"query": product_name, "filter": {"value": "page", "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 update_vertical(vertical_name, new_data):
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {"filter": {"property": "Vertical", "title": {"contains": vertical_name}}}
resp = requests.post(url, headers=headers, json=payload)
if resp.status_code != 200:
print(f"Error searching for {vertical_name}: {resp.text}")
return
results = resp.json().get("results", [])
if not results:
print(f"Skipping {vertical_name} (Not found)")
return
page_id = results[0]["id"]
print(f"Found {vertical_name} (ID: {page_id})")
props_update = {
"Pains": {"rich_text": [{"text": {"content": new_data["Pains"]}}],},
"Gains": {"rich_text": [{"text": {"content": new_data["Gains"]}}]}
}
if "Secondary_Product_Name" in new_data:
prod_name = new_data["Secondary_Product_Name"]
prod_id = get_product_page_id(prod_name)
if prod_id:
print(f" Found Product ID for '{prod_name}': {prod_id}")
props_update["Secondary Product Category"] = {"relation": [{"id": prod_id}]}
props_update["Ops Focus Secondary"] = {"checkbox": True}
else:
print(f" WARNING: Product '{prod_name}' not found.")
update_url = f"https://api.notion.com/v1/pages/{page_id}"
update_payload = {"properties": props_update}
resp_patch = requests.patch(update_url, headers=headers, json=update_payload)
if resp_patch.status_code == 200:
print(f"✅ Successfully updated {vertical_name}")
else:
print(f"❌ Failed to update {vertical_name}: {resp_patch.text}")
print("Starting Targeted Notion Update...")
for v_name, data in updates.items():
update_vertical(v_name, data)
print("Done.")

View File

@@ -0,0 +1,88 @@
import os
import requests
import json
from dotenv import load_dotenv
load_dotenv()
# Check for API Key
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
if not NOTION_API_KEY:
try:
with open("/app/n8n_api_Token_git.txt", "r") as f:
content = f.read()
if "secret_" in content:
NOTION_API_KEY = content.strip().split('\n')[0]
except:
pass
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found.")
exit(1)
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81"
headers = {"Authorization": f"Bearer {NOTION_API_KEY}", "Notion-Version": "2022-06-28", "Content-Type": "application/json"}
targets = [
"Energy - Grid & Utilities",
"Tech - Data Center",
"Retail - Non-Food"
]
def check_vertical(vertical_name):
print(f"\n--- Checking: {vertical_name} ---")
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {"filter": {"property": "Vertical", "title": {"contains": vertical_name}}}
resp = requests.post(url, headers=headers, json=payload)
if resp.status_code != 200:
print(f"Error: {resp.text}")
return
results = resp.json().get("results", [])
if not results:
print("Not found.")
return
page = results[0]
props = page["properties"]
# Check Pains (Start)
pains = props.get("Pains", {}).get("rich_text", [])
pains_text = "".join([t["text"]["content"] for t in pains])
print(f"PAINS (First 100 chars): {pains_text[:100]}...")
# Check Gains (Start)
gains = props.get("Gains", {}).get("rich_text", [])
gains_text = "".join([t["text"]["content"] for t in gains])
print(f"GAINS (First 100 chars): {gains_text[:100]}...")
# Check Ops Focus Secondary
ops_focus = props.get("Ops Focus: Secondary", {}).get("checkbox", False)
print(f"Ops Focus Secondary: {ops_focus}")
# Check Secondary Product
sec_prod_rel = props.get("Secondary Product", {}).get("relation", [])
if sec_prod_rel:
prod_id = sec_prod_rel[0]["id"]
# Fetch Product Name
prod_url = f"https://api.notion.com/v1/pages/{prod_id}"
prod_resp = requests.get(prod_url, headers=headers)
if prod_resp.status_code == 200:
prod_props = prod_resp.json()["properties"]
# Try to find Name/Title
# Usually "Name" or "Product Name"
# Let's look for title type
prod_name = "Unknown"
for k, v in prod_props.items():
if v["type"] == "title":
prod_name = "".join([t["text"]["content"] for t in v["title"]])
print(f"Secondary Product: {prod_name}")
else:
print(f"Secondary Product ID: {prod_id} (Could not fetch name)")
else:
print("Secondary Product: None")
for t in targets:
check_vertical(t)