[2ea88f42] schaut gut aus
schaut gut aus
This commit is contained in:
@@ -1 +1 @@
|
|||||||
{"task_id": "2ea88f42-8544-806f-8946-e61ada8cc059", "token": "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", "session_start_time": "2026-01-29T08:21:44.176590"}
|
{"task_id": "2ea88f42-8544-806f-8946-e61ada8cc059", "token": "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", "session_start_time": "2026-01-29T10:55:09.010737"}
|
||||||
@@ -18,3 +18,21 @@ View your app in AI Studio: https://ai.studio/apps/drive/1vJMxbT1hW3SiMDUeEd8cXG
|
|||||||
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
||||||
3. Run the app:
|
3. Run the app:
|
||||||
`npm run dev`
|
`npm run dev`
|
||||||
|
|
||||||
|
## Importing Single Competitors
|
||||||
|
|
||||||
|
To import a specific competitor from a JSON analysis file into Notion, use the `import_single_competitor.py` script. This script allows you to selectively import data for a single company without affecting other entries.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 import_single_competitor.py --file <path_to_json_file> --name "<Exact Competitor Name>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
To import "FENKA Robotics GmbH" from `Roboplanet_Competitive_Analysis_01_2026.json`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 import_single_competitor.py --file Roboplanet_Competitive_Analysis_01_2026.json --name "FENKA Robotics GmbH"
|
||||||
|
```
|
||||||
|
|||||||
320
import_single_competitor.py
Normal file
320
import_single_competitor.py
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
|
||||||
|
# --- CONFIGURATION ---
|
||||||
|
NOTION_TOKEN = "" # Will be loaded from file
|
||||||
|
HEADERS = {
|
||||||
|
"Authorization": f"Bearer {NOTION_TOKEN}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Notion-Version": "2022-06-28",
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- DATABASE IDs ---
|
||||||
|
COMPANIES_DB_ID = "2e688f42-8544-8158-8673-d8b1e3eca5b5"
|
||||||
|
CANONICAL_PRODUCTS_DB_ID = "2f088f42-8544-81d5-bec7-d9189f3bacd4"
|
||||||
|
PORTFOLIO_DB_ID = "2e688f42-8544-81df-8fcc-f1d7f8745e00"
|
||||||
|
LANDMINES_DB_ID = "2e688f42-8544-81aa-94f8-d6242be4d0cd"
|
||||||
|
REFERENCES_DB_ID = "2e688f42-8544-81df-8d83-f4d7f57d8168"
|
||||||
|
INDUSTRIES_DB_ID = "2ec88f42-8544-8014-ab38-ea664b4c2b81"
|
||||||
|
|
||||||
|
|
||||||
|
# --- API HELPERS ---
|
||||||
|
def query_db(db_id, filter_payload=None):
|
||||||
|
"""Retrieves all pages from a Notion database, with optional filter."""
|
||||||
|
url = f"https://api.notion.com/v1/databases/{db_id}/query"
|
||||||
|
all_pages = []
|
||||||
|
start_cursor = None
|
||||||
|
|
||||||
|
while True:
|
||||||
|
payload = {}
|
||||||
|
if start_cursor:
|
||||||
|
payload["start_cursor"] = start_cursor
|
||||||
|
if filter_payload:
|
||||||
|
payload["filter"] = filter_payload
|
||||||
|
|
||||||
|
response = requests.post(url, headers=HEADERS, json=payload)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
print(f"Error querying DB {db_id}: {response.status_code}")
|
||||||
|
print(response.json())
|
||||||
|
return None
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
all_pages.extend(data["results"])
|
||||||
|
|
||||||
|
if data.get("has_more"):
|
||||||
|
start_cursor = data["next_cursor"]
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
return all_pages
|
||||||
|
|
||||||
|
def create_page(db_id, properties):
|
||||||
|
"""Creates a new page in a Notion database."""
|
||||||
|
url = "https://api.notion.com/v1/pages"
|
||||||
|
payload = {"parent": {"database_id": db_id}, "properties": properties}
|
||||||
|
|
||||||
|
response = requests.post(url, headers=HEADERS, data=json.dumps(payload))
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"Error creating page in DB {db_id}: {response.status_code}")
|
||||||
|
print(response.json())
|
||||||
|
return None
|
||||||
|
|
||||||
|
def update_page(page_id, properties):
|
||||||
|
"""Updates properties of an existing page in Notion."""
|
||||||
|
url = f"https://api.notion.com/v1/pages/{page_id}"
|
||||||
|
payload = {"properties": properties}
|
||||||
|
|
||||||
|
response = requests.patch(url, headers=HEADERS, data=json.dumps(payload))
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"Error updating page {page_id}: {response.status_code}")
|
||||||
|
print(response.json())
|
||||||
|
return None
|
||||||
|
|
||||||
|
# --- STATE AWARENESS HELPERS ---
|
||||||
|
def get_existing_items_map(db_id, name_property="Name"):
|
||||||
|
"""Fetches all items from a DB and returns a map of {name: id}."""
|
||||||
|
print(f"Fetching existing items from DB {db_id} to build cache...")
|
||||||
|
pages = query_db(db_id)
|
||||||
|
if pages is None:
|
||||||
|
sys.exit(f"Could not fetch items from DB {db_id}. Aborting.")
|
||||||
|
|
||||||
|
item_map = {}
|
||||||
|
for page in pages:
|
||||||
|
try:
|
||||||
|
# Handle cases where title might be empty or malformed
|
||||||
|
title_list = page["properties"][name_property].get("title", [])
|
||||||
|
if title_list:
|
||||||
|
item_name = title_list[0].get("text", {}).get("content", "").strip()
|
||||||
|
if item_name:
|
||||||
|
item_map[item_name] = page["id"]
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
continue
|
||||||
|
print(f" - Found {len(item_map)} existing items.")
|
||||||
|
return item_map
|
||||||
|
|
||||||
|
|
||||||
|
def get_existing_relations(db_id, relation_property_name, target_relation_id_prop_name):
|
||||||
|
"""Fetches all items from a DB and returns a set of (item_name, related_id) tuples."""
|
||||||
|
print(f"Fetching existing relations from DB {db_id}...")
|
||||||
|
pages = query_db(db_id)
|
||||||
|
if pages is None:
|
||||||
|
sys.exit(f"Could not fetch relations from DB {db_id}. Aborting.")
|
||||||
|
|
||||||
|
relation_set = set()
|
||||||
|
for page in pages:
|
||||||
|
try:
|
||||||
|
item_name = page["properties"]["Name"]["title"][0]["text"]["content"]
|
||||||
|
related_ids = [rel["id"] for rel in page["properties"][relation_property_name].get("relation", [])]
|
||||||
|
target_related_ids = [rel["id"] for rel in page["properties"][target_relation_id_prop_name].get("relation", [])]
|
||||||
|
|
||||||
|
if related_ids and target_related_ids:
|
||||||
|
relation_set.add((item_name, related_ids[0], target_related_ids[0]))
|
||||||
|
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
continue
|
||||||
|
print(f" - Found {len(relation_set)} existing relations.")
|
||||||
|
return relation_set
|
||||||
|
|
||||||
|
def inspect_database(db_id):
|
||||||
|
"""Retrieves and prints the properties of a specific Notion database."""
|
||||||
|
print(f"🔍 Inspecting properties for database ID: {db_id}")
|
||||||
|
url = f"https://api.notion.com/v1/databases/{db_id}"
|
||||||
|
|
||||||
|
response = requests.get(url, headers=HEADERS)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
print(f"Error retrieving database properties: {response.status_code}")
|
||||||
|
print(response.json())
|
||||||
|
return
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
properties = data.get("properties", {})
|
||||||
|
|
||||||
|
if not properties:
|
||||||
|
print("No properties found for this database.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\n--- Database Properties ---")
|
||||||
|
for prop_name, prop_data in properties.items():
|
||||||
|
print(f"- Property Name: '{prop_name}'")
|
||||||
|
print(f" Type: {prop_data.get('type')}\n")
|
||||||
|
print("---------------------------\n")
|
||||||
|
|
||||||
|
# --- MAIN LOGIC ---
|
||||||
|
def main():
|
||||||
|
global NOTION_TOKEN, HEADERS
|
||||||
|
try:
|
||||||
|
with open("notion_token.txt", "r") as f:
|
||||||
|
NOTION_TOKEN = f.read().strip()
|
||||||
|
HEADERS["Authorization"] = f"Bearer {NOTION_TOKEN}"
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Error: `notion_token.txt` not found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Import a single competitor from a JSON analysis file into Notion.")
|
||||||
|
parser.add_argument('--file', help="Path to the JSON analysis file.")
|
||||||
|
parser.add_argument('--name', help="Exact name of the competitor to import.")
|
||||||
|
parser.add_argument('--inspect', help="Database ID to inspect.")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.inspect:
|
||||||
|
inspect_database(args.inspect)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not args.file or not args.name:
|
||||||
|
parser.error("--file and --name are required.")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# --- Phase 1: State Awareness ---
|
||||||
|
print("\n--- Phase 1: Reading current state from Notion ---")
|
||||||
|
companies_map = get_existing_items_map(COMPANIES_DB_ID)
|
||||||
|
products_map = get_existing_items_map(CANONICAL_PRODUCTS_DB_ID)
|
||||||
|
industries_map = get_existing_items_map(INDUSTRIES_DB_ID, name_property="Vertical")
|
||||||
|
|
||||||
|
# For relations, we create a unique key to check for existence
|
||||||
|
existing_landmines = {f'{page["properties"]["Question"]["title"][0]["text"]["content"]}_{page["properties"]["Related Competitor"]["relation"][0]["id"]}' for page in query_db(LANDMINES_DB_ID) if "Question" in page["properties"] and page["properties"]["Question"]["title"] and page["properties"]["Related Competitor"]["relation"]}
|
||||||
|
print(f" - Found {len(existing_landmines)} existing landmines.")
|
||||||
|
existing_references = {f'{page["properties"]["Customer"]["title"][0]["text"]["content"]}_{page["properties"]["Related Competitor"]["relation"][0]["id"]}' for page in query_db(REFERENCES_DB_ID) if "Customer" in page["properties"] and page["properties"]["Customer"]["title"] and page["properties"]["Related Competitor"]["relation"]}
|
||||||
|
print(f" - Found {len(existing_references)} existing references.")
|
||||||
|
|
||||||
|
|
||||||
|
json_file_path = args.file
|
||||||
|
target_competitor_name = args.name
|
||||||
|
|
||||||
|
# --- Phase 2: Processing JSON ---
|
||||||
|
print(f"\n--- Phase 2: Processing local JSON file: {json_file_path} for {target_competitor_name} ---")
|
||||||
|
try:
|
||||||
|
with open(json_file_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: `{json_file_path}` not found.")
|
||||||
|
return
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"Error decoding JSON from {json_file_path}: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Find the correct analysis and reference data for the target competitor
|
||||||
|
target_analysis = None
|
||||||
|
for analysis in data.get('analyses', []):
|
||||||
|
if analysis['competitor']['name'] == target_competitor_name:
|
||||||
|
target_analysis = analysis
|
||||||
|
break
|
||||||
|
|
||||||
|
# Find references from the separate reference_analysis block
|
||||||
|
target_references_data = None
|
||||||
|
if 'reference_analysis' in data:
|
||||||
|
for ref_block in data.get('reference_analysis', []):
|
||||||
|
if ref_block.get('competitor_name') == target_competitor_name:
|
||||||
|
target_references_data = ref_block.get('references', [])
|
||||||
|
break
|
||||||
|
|
||||||
|
target_battlecard = None
|
||||||
|
if 'battlecards' in data:
|
||||||
|
for bc in data.get('battlecards', []):
|
||||||
|
if bc['competitor_name'] == target_competitor_name:
|
||||||
|
target_battlecard = bc
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
if not target_analysis:
|
||||||
|
print(f"Error: Competitor '{target_competitor_name}' not found in 'analyses' list in {json_file_path}.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\nProcessing target competitor: {target_competitor_name}")
|
||||||
|
|
||||||
|
# --- Phase 3: "Upsert" Company ---
|
||||||
|
if target_competitor_name not in companies_map:
|
||||||
|
print(f" - Company '{target_competitor_name}' not found. Creating...")
|
||||||
|
props = {"Name": {"title": [{"text": {"content": target_competitor_name}}]}}
|
||||||
|
new_company = create_page(COMPANIES_DB_ID, props)
|
||||||
|
if new_company:
|
||||||
|
companies_map[target_competitor_name] = new_company["id"]
|
||||||
|
else:
|
||||||
|
print(f" - Failed to create company '{target_competitor_name}'. Halting.")
|
||||||
|
return
|
||||||
|
company_id = companies_map[target_competitor_name]
|
||||||
|
|
||||||
|
# --- Phase 4: Create and Link Target Industries ---
|
||||||
|
print("\n--- Processing Target Industries ---")
|
||||||
|
target_industry_relation_ids = []
|
||||||
|
if INDUSTRIES_DB_ID:
|
||||||
|
for industry_name in target_analysis.get('target_industries', []):
|
||||||
|
if industry_name not in industries_map:
|
||||||
|
print(f" - Industry '{industry_name}' not found in Notion DB. Creating...")
|
||||||
|
props = {"Vertical": {"title": [{"text": {"content": industry_name}}]}}
|
||||||
|
new_industry = create_page(INDUSTRIES_DB_ID, props)
|
||||||
|
if new_industry:
|
||||||
|
industries_map[industry_name] = new_industry["id"]
|
||||||
|
target_industry_relation_ids.append({"id": new_industry["id"]})
|
||||||
|
else:
|
||||||
|
target_industry_relation_ids.append({"id": industries_map[industry_name]})
|
||||||
|
|
||||||
|
if target_industry_relation_ids:
|
||||||
|
print(f" - Linking company to {len(target_analysis.get('target_industries', []))} industries...")
|
||||||
|
# Format for multi-select is a list of objects with names
|
||||||
|
multi_select_payload = [{"name": name} for name in target_analysis.get('target_industries', [])]
|
||||||
|
update_props = {
|
||||||
|
"Target Industries": {"multi_select": multi_select_payload}
|
||||||
|
}
|
||||||
|
update_page(company_id, update_props)
|
||||||
|
else:
|
||||||
|
print(" - INDUSTRIES_DB_ID not set. Skipping.")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Phase 5: Import Landmines ---
|
||||||
|
if target_battlecard and LANDMINES_DB_ID:
|
||||||
|
print("\n--- Processing Landmines ---")
|
||||||
|
for landmine in target_battlecard.get('landmine_questions', []):
|
||||||
|
unique_key = f"{landmine}_{company_id}"
|
||||||
|
if unique_key not in existing_landmines:
|
||||||
|
print(f" - Landmine '{landmine}' not found. Creating...")
|
||||||
|
props = {
|
||||||
|
"Question": {"title": [{"text": {"content": landmine}}]},
|
||||||
|
"Related Competitor": {"relation": [{"id": company_id}]}
|
||||||
|
}
|
||||||
|
new_landmine = create_page(LANDMINES_DB_ID, props)
|
||||||
|
if new_landmine:
|
||||||
|
existing_landmines.add(unique_key)
|
||||||
|
else:
|
||||||
|
print(f" - Landmine '{landmine}' already exists for this competitor. Skipping.")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Phase 6: Import References ---
|
||||||
|
if target_references_data and REFERENCES_DB_ID:
|
||||||
|
print("\n--- Processing References ---")
|
||||||
|
for ref in target_references_data:
|
||||||
|
ref_name = ref.get("name", "Unknown Reference")
|
||||||
|
unique_key = f"{ref_name}_{company_id}"
|
||||||
|
if unique_key not in existing_references:
|
||||||
|
print(f" - Reference '{ref_name}' not found. Creating...")
|
||||||
|
|
||||||
|
props = {
|
||||||
|
"Customer": {"title": [{"text": {"content": ref_name}}]},
|
||||||
|
"Related Competitor": {"relation": [{"id": company_id}]},
|
||||||
|
"Quote": {"rich_text": [{"text": {"content": ref.get("testimonial_snippet", "")[:2000]}}]}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle Industry as a select property
|
||||||
|
ref_industry_name = ref.get("industry")
|
||||||
|
if ref_industry_name:
|
||||||
|
props["Industry"] = {"select": {"name": ref_industry_name}}
|
||||||
|
|
||||||
|
new_ref = create_page(REFERENCES_DB_ID, props)
|
||||||
|
if new_ref:
|
||||||
|
existing_references.add(unique_key)
|
||||||
|
else:
|
||||||
|
print(f" - Reference '{ref_name}' already exists for this competitor. Skipping.")
|
||||||
|
|
||||||
|
print("\n--- ✅ Import script finished ---")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user