[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:
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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.")
|
||||
@@ -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.")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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.")
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user