feat(notion): Finalize relational DB implementation and scripts
- Implement relational data structure in Notion as per the plan. - Add scripts for initial data import (import_product.py) and distribution to related databases (distribute_product_data.py). - Create helper scripts for reading Notion content. - Update Notion_Dashboard.md and GEMINI.md with the latest implementation status, database IDs, and key lessons learned from the MVP phase, including API constraints and schema-first principles.
This commit is contained in:
12
GEMINI.md
12
GEMINI.md
@@ -60,6 +60,18 @@ The system architecture has evolved from a CLI-based toolset to a modern web app
|
||||
* **Problem:** Users didn't see when a background job finished.
|
||||
* **Solution:** Implementing a polling mechanism (`setInterval`) tied to a `isProcessing` state is superior to static timeouts for long-running AI tasks.
|
||||
|
||||
6. **Notion API - Schema First:**
|
||||
* **Problem:** Scripts failed when trying to write data to a Notion database property (column) that did not exist.
|
||||
* **Solution:** ALWAYS ensure the database schema is correct *before* attempting to import or update data. Use the `databases.update` endpoint to add the required properties (e.g., "Key Features", "Constraints") programmatically as a preliminary step. The API will not create them on the fly.
|
||||
|
||||
7. **Notion API - Character Limits:**
|
||||
* **Problem:** API calls failed with a `400 Bad Request` error when a rich text field exceeded the maximum length.
|
||||
* **Solution:** Be aware of the **2000-character limit** for rich text properties. Implement logic to truncate text content before sending the payload to the Notion API to prevent validation errors.
|
||||
|
||||
8. **Notion API - Response Structures:**
|
||||
* **Problem:** Parsing functions failed with `TypeError` or `AttributeError` because the JSON structure for a property differed depending on how it was requested.
|
||||
* **Solution:** Write robust helper functions that can handle multiple possible JSON structures. A property object retrieved via a direct property endpoint (`/pages/{id}/properties/{prop_id}`) is structured differently from the same property when it's part of a full page object (`/pages/{id}`). The parsing logic must account for these variations.
|
||||
|
||||
## Next Steps
|
||||
* **Quality Assurance:** Implement a dedicated "Review Mode" to validate high-potential leads.
|
||||
* **Export:** Generate Excel/CSV enriched reports.
|
||||
|
||||
@@ -92,6 +92,7 @@ Um die relationale Integrität zu wahren, sind folgende Datenbanken in Notion zw
|
||||
* **Messaging Matrix** $\leftrightarrow$ **Sector Master** (Welcher Schmerz gehört zu welcher Branche?)
|
||||
* **The Brain** $\leftrightarrow$ **Product Master** (Welches Support-Wissen gehört zu welcher Hardware?)
|
||||
* **GTM Workspace** $\leftrightarrow$ **Product Master** (Welche Kampagne bewirbt welches Gerät?)
|
||||
* **Feature-to-Value Translator** $\leftrightarrow$ **Product Master** (Welcher Nutzen gehört zu welchem Feature?)
|
||||
|
||||
---
|
||||
|
||||
@@ -141,6 +142,7 @@ Die folgenden IDs wurden bei der initialen Erstellung der Datenbanken generiert
|
||||
* **Enrichment Factory & RevOps:** `2e288f42-8544-8172-a3a7-f5101b6ac0f0`
|
||||
* **The Brain:** `2e288f42-8544-810f-8e7d-e9a2a3100779`
|
||||
* **GTM Workspace:** `2e288f42-8544-81cc-b167-f9dffe9c7bde`
|
||||
* **Feature-to-Value Translator:** `2e288f42-8544-8184-ba08-d6d736879f19`
|
||||
|
||||
---
|
||||
|
||||
@@ -154,5 +156,19 @@ Die folgenden IDs wurden bei der initialen Erstellung der Datenbanken generiert
|
||||
**Status:** Blueprint Finalisiert.
|
||||
**Nächster Schritt:** Umsetzung der Datenbank-Properties und API-Endpunkte gemäß diesem Dokument.
|
||||
|
||||
### 8.8 Erfolgreicher Datenimport (08. Jan. 2026)
|
||||
Der Produkt-Datensatz "Puma M20" wurde erfolgreich mithilfe des `import_product.py`-Skripts und der Quelldatei `Puma_m20_2026-01-08.md` in die Notion-Datenbanken "Product Master", "Sector & Persona Master" und "Messaging Matrix" importiert.
|
||||
### 8.8 Erfolgreicher Datenimport & -verteilung (08. Jan. 2026)
|
||||
Der Produkt-Datensatz "Puma M20" wurde erfolgreich importiert. Die strategischen Daten (Zielgruppen, Pain Points, Messaging) wurden anschließend aus dem Produkteintrag extrahiert und in die relational verknüpften Datenbanken "Sector & Persona Master" und "Messaging Matrix" verteilt. Dies schafft eine "Single Source of Truth" und legt die Grundlage für automatisierte Marketing-Workflows.
|
||||
|
||||
### 8.9 Neu gelernte Lektionen (Post-MVP)
|
||||
|
||||
6. **Notion API - Schema First:**
|
||||
* **Problem:** Skripte schlugen fehl beim Versuch, Daten in eine nicht existierende Datenbankeigenschaft (Spalte) zu schreiben.
|
||||
* **Lösung:** IMMER sicherstellen, dass das Datenbankschema korrekt ist, *bevor* Daten importiert oder aktualisiert werden. Den `databases.update`-Endpunkt verwenden, um die erforderlichen Eigenschaften (z.B. "Key Features", "Constraints") programmatisch als vorbereitenden Schritt hinzuzufügen. Die API erstellt diese nicht spontan.
|
||||
|
||||
7. **Notion API - Zeichenbeschränkungen:**
|
||||
* **Problem:** API-Aufrufe schlugen mit einem `400 Bad Request`-Fehler fehl, wenn ein Rich-Text-Feld die maximale Länge überschritt.
|
||||
* **Lösung:** Das **2000-Zeichen-Limit** für Rich-Text-Eigenschaften beachten. Eine Logik implementieren, um den Textinhalt vor dem Senden des Payloads an die Notion-API zu kürzen, um Validierungsfehler zu vermeiden.
|
||||
|
||||
8. **Notion API - Antwortstrukturen:**
|
||||
* **Problem:** Parsing-Funktionen schlugen mit `TypeError` oder `AttributeError` fehl, da die JSON-Struktur für eine Eigenschaft unterschiedlich war, je nachdem, wie sie angefordert wurde.
|
||||
* **Lösung:** Robuste Hilfsfunktionen schreiben, die mehrere mögliche JSON-Strukturen verarbeiten können. Ein Eigenschaftsobjekt, das über einen direkten Eigenschafts-Endpunkt (`/pages/{id}/properties/{prop_id}`) abgerufen wird, ist anders strukturiert als dieselbe Eigenschaft, wenn sie Teil eines vollständigen Seitenobjekts (`/pages/{id}`) ist. Die Parsing-Logik muss diese Variationen berücksichtigen.
|
||||
68
create_feature_translator_db.py
Normal file
68
create_feature_translator_db.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# create_feature_translator_db.py
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
|
||||
# --- Configuration ---
|
||||
try:
|
||||
with open("notion_token.txt", "r") as f:
|
||||
NOTION_TOKEN = f.read().strip()
|
||||
except FileNotFoundError:
|
||||
print("Error: notion_token.txt not found.")
|
||||
exit(1)
|
||||
|
||||
PARENT_PAGE_ID = "2e088f42854480248289deb383da3818"
|
||||
NOTION_VERSION = "2022-06-28"
|
||||
NOTION_API_BASE_URL = "https://api.notion.com/v1"
|
||||
|
||||
HEADERS = {
|
||||
"Authorization": f"Bearer {NOTION_TOKEN}",
|
||||
"Notion-Version": NOTION_VERSION,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
# --- Database Schema ---
|
||||
DB_NAME = "Feature-to-Value Translator"
|
||||
DB_SCHEMA = {
|
||||
"title": [{"type": "text", "text": {"content": DB_NAME}}],
|
||||
"properties": {
|
||||
"Feature": {"title": {}},
|
||||
"Story (Benefit)": {"rich_text": {}},
|
||||
"Headline": {"rich_text": {}},
|
||||
"Product Master": {
|
||||
"relation": {
|
||||
"database_id": "2e288f42-8544-81d8-96f5-c231f84f719a", # Product Master DB ID
|
||||
"dual_property": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Main Logic ---
|
||||
def main():
|
||||
print(f"Attempting to create database: {DB_NAME}")
|
||||
create_url = f"{NOTION_API_BASE_URL}/databases"
|
||||
payload = {
|
||||
"parent": {"type": "page_id", "page_id": PARENT_PAGE_ID},
|
||||
"title": DB_SCHEMA["title"],
|
||||
"properties": DB_SCHEMA["properties"],
|
||||
}
|
||||
try:
|
||||
response = requests.post(create_url, headers=HEADERS, json=payload)
|
||||
response.raise_for_status()
|
||||
db_data = response.json()
|
||||
db_id = db_data["id"]
|
||||
print(f"Successfully created database '{DB_NAME}' with ID: {db_id}")
|
||||
print("\n--- IMPORTANT ---")
|
||||
print("Please update 'Notion_Dashboard.md' with this new ID.")
|
||||
print(f"'Feature-to-Value Translator': '{db_id}'")
|
||||
print("-------------------")
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
print(f"HTTP Error creating database {DB_NAME}: {e}")
|
||||
print(f"Response content: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
254
distribute_product_data.py
Normal file
254
distribute_product_data.py
Normal file
@@ -0,0 +1,254 @@
|
||||
# distribute_product_data.py
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
import time
|
||||
|
||||
# --- Configuration ---
|
||||
try:
|
||||
with open("notion_token.txt", "r") as f:
|
||||
NOTION_TOKEN = f.read().strip()
|
||||
except FileNotFoundError:
|
||||
print("Error: notion_token.txt not found.")
|
||||
exit(1)
|
||||
|
||||
NOTION_VERSION = "2022-06-28"
|
||||
NOTION_API_BASE_URL = "https://api.notion.com/v1"
|
||||
|
||||
HEADERS = {
|
||||
"Authorization": f"Bearer {NOTION_TOKEN}",
|
||||
"Notion-Version": NOTION_VERSION,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
# --- Database IDs (from Notion_Dashboard.md) ---
|
||||
DB_IDS = {
|
||||
"Product Master": "2e288f42-8544-81d8-96f5-c231f84f719a",
|
||||
"Sector & Persona Master": "2e288f42-8544-8113-b878-ec99c8a02a6b",
|
||||
"Messaging Matrix": "2e288f42-8544-81b0-83d4-c16623cc32d1",
|
||||
"Feature-to-Value Translator": "2e288f42-8544-8184-ba08-d6d736879f19",
|
||||
}
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
def create_notion_page(database_id, properties):
|
||||
"""Creates a new page in a Notion database."""
|
||||
url = f"{NOTION_API_BASE_URL}/pages"
|
||||
payload = {"parent": {"database_id": database_id}, "properties": properties}
|
||||
try:
|
||||
response = requests.post(url, headers=HEADERS, json=payload)
|
||||
response.raise_for_status()
|
||||
print(f"Successfully created page in DB {database_id}.")
|
||||
return response.json()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
print(f"HTTP Error creating page in DB {database_id}: {e}\nResponse: {response.text}")
|
||||
return None
|
||||
|
||||
def update_notion_page(page_id, properties):
|
||||
"""Updates an existing page in Notion."""
|
||||
url = f"{NOTION_API_BASE_URL}/pages/{page_id}"
|
||||
payload = {"properties": properties}
|
||||
try:
|
||||
response = requests.patch(url, headers=HEADERS, json=payload)
|
||||
response.raise_for_status()
|
||||
print(f"Successfully updated page {page_id}.")
|
||||
return response.json()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
print(f"HTTP Error updating page {page_id}: {e}\nResponse: {response.text}")
|
||||
return None
|
||||
|
||||
def find_notion_page_by_title(database_id, title):
|
||||
"""Searches for a page in a Notion database by its title property."""
|
||||
url = f"{NOTION_API_BASE_URL}/databases/{database_id}/query"
|
||||
filter_payload = {"filter": {"property": "Name", "title": {"equals": title}}}
|
||||
try:
|
||||
response = requests.post(url, headers=HEADERS, json=filter_payload)
|
||||
response.raise_for_status()
|
||||
results = response.json().get("results")
|
||||
return results[0] if results else None
|
||||
except requests.exceptions.HTTPError as e:
|
||||
print(f"HTTP Error searching page in DB {database_id}: {e}\nResponse: {response.text}")
|
||||
return None
|
||||
|
||||
def get_page_property(page_id, property_id):
|
||||
"""Retrieves a specific property from a Notion page."""
|
||||
url = f"{NOTION_API_BASE_URL}/pages/{page_id}/properties/{property_id}"
|
||||
try:
|
||||
response = requests.get(url, headers=HEADERS)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
print(f"HTTP Error retrieving property {property_id}: {e}\nResponse: {response.text}")
|
||||
return None
|
||||
|
||||
def get_rich_text_content(property_object):
|
||||
"""Extracts plain text from a Notion rich_text property object."""
|
||||
if not property_object:
|
||||
return ""
|
||||
try:
|
||||
# The property endpoint returns a list in the 'results' key
|
||||
if 'results' in property_object and property_object['results']:
|
||||
# The actual content is in the 'rich_text' object within the first result
|
||||
rich_text_items = property_object['results'][0].get('rich_text', {})
|
||||
# It can be a single dict or a list, we handle the main plain_text for simplicity here
|
||||
if isinstance(rich_text_items, dict) and 'plain_text' in rich_text_items:
|
||||
return rich_text_items.get('plain_text', '')
|
||||
# If it is a list of rich text objects (less common for a single property)
|
||||
elif isinstance(rich_text_items, list):
|
||||
return "".join(item.get("plain_text", "") for item in rich_text_items if isinstance(item, dict))
|
||||
|
||||
except (KeyError, IndexError, TypeError) as e:
|
||||
print(f"Error parsing rich text object: {e}")
|
||||
return ""
|
||||
return ""
|
||||
|
||||
def format_rich_text(text):
|
||||
"""Formats a string into Notion's rich text structure."""
|
||||
if len(text) > 2000:
|
||||
print(f"Warning: Truncating text from {len(text)} to 2000 characters.")
|
||||
text = text[:2000]
|
||||
return {"rich_text": [{"type": "text", "text": {"content": text}}]}
|
||||
|
||||
def format_title(text):
|
||||
"""Formats a string into Notion's title structure."""
|
||||
return {"title": [{"type": "text", "text": {"content": text}}]}
|
||||
|
||||
def format_relation(page_ids):
|
||||
"""Formats a list of page IDs into Notion's relation structure."""
|
||||
if not isinstance(page_ids, list):
|
||||
page_ids = [page_ids]
|
||||
return {"relation": [{"id": page_id} for page_id in page_ids]}
|
||||
|
||||
def parse_markdown_table(markdown_text):
|
||||
"""Parses a generic markdown table into a list of dicts."""
|
||||
lines = markdown_text.strip().split('\n')
|
||||
if len(lines) < 2:
|
||||
return []
|
||||
|
||||
headers = [h.strip() for h in lines[0].split('|') if h.strip()]
|
||||
|
||||
data_rows = []
|
||||
for line in lines[2:]: # Skip header and separator
|
||||
values = [v.strip() for v in line.split('|') if v.strip()]
|
||||
if len(values) == len(headers):
|
||||
data_rows.append(dict(zip(headers, values)))
|
||||
|
||||
return data_rows
|
||||
|
||||
# --- Main Logic ---
|
||||
|
||||
def main():
|
||||
PRODUCT_NAME = "Puma M20"
|
||||
print(f"--- Starting data distribution for product: {PRODUCT_NAME} ---")
|
||||
|
||||
# 1. Get the product page from Product Master
|
||||
product_page = find_notion_page_by_title(DB_IDS["Product Master"], PRODUCT_NAME)
|
||||
if not product_page:
|
||||
print(f"Product '{PRODUCT_NAME}' not found. Aborting.")
|
||||
return
|
||||
product_page_id = product_page["id"]
|
||||
print(f"Found Product Page ID: {product_page_id}")
|
||||
|
||||
# 2. Distribute Strategy Matrix Data
|
||||
strategy_matrix_prop_id = product_page["properties"]["Strategy Matrix"]["id"]
|
||||
strategy_matrix_obj = get_page_property(product_page_id, strategy_matrix_prop_id)
|
||||
strategy_matrix_text = get_rich_text_content(strategy_matrix_obj)
|
||||
|
||||
if strategy_matrix_text:
|
||||
parsed_matrix = parse_markdown_table(strategy_matrix_text)
|
||||
if parsed_matrix:
|
||||
print("\n--- Distributing Strategy Matrix Data ---")
|
||||
sector_page_ids_for_product = []
|
||||
|
||||
for row in parsed_matrix:
|
||||
segment_name = row.get("Segment")
|
||||
pain_point = row.get("Pain Point")
|
||||
angle = row.get("Angle")
|
||||
differentiation = row.get("Differentiation")
|
||||
|
||||
if not all([segment_name, pain_point, angle, differentiation]):
|
||||
print(f"Skipping row due to missing data: {row}")
|
||||
continue
|
||||
|
||||
print(f"\nProcessing Segment: {segment_name}")
|
||||
|
||||
# Find or Create Sector in Sector & Persona Master
|
||||
sector_page = find_notion_page_by_title(DB_IDS["Sector & Persona Master"], segment_name)
|
||||
if sector_page:
|
||||
sector_page_id = sector_page["id"]
|
||||
print(f"Found existing Sector page with ID: {sector_page_id}")
|
||||
update_notion_page(sector_page_id, {"Pains": format_rich_text(pain_point)})
|
||||
else:
|
||||
print(f"Creating new Sector page for '{segment_name}'...")
|
||||
new_sector_page = create_notion_page(DB_IDS["Sector & Persona Master"], {"Name": format_title(segment_name), "Pains": format_rich_text(pain_point)})
|
||||
if not new_sector_page:
|
||||
print(f"Failed to create sector page for '{segment_name}'. Skipping.")
|
||||
continue
|
||||
sector_page_id = new_sector_page["id"]
|
||||
|
||||
sector_page_ids_for_product.append(sector_page_id)
|
||||
|
||||
# Create entry in Messaging Matrix
|
||||
print(f"Creating Messaging Matrix entry for '{segment_name}'...")
|
||||
messaging_properties = {
|
||||
"Name": format_title(f"{PRODUCT_NAME} - {segment_name}"),
|
||||
"Satz 1": format_rich_text(angle),
|
||||
"Satz 2": format_rich_text(differentiation),
|
||||
"Product Master": format_relation(product_page_id),
|
||||
"Sector Master": format_relation(sector_page_id)
|
||||
}
|
||||
create_notion_page(DB_IDS["Messaging Matrix"], messaging_properties)
|
||||
|
||||
# Update Product Master with relations to all processed sectors
|
||||
if sector_page_ids_for_product:
|
||||
print(f"\nUpdating Product Master with relations to {len(sector_page_ids_for_product)} sectors...")
|
||||
update_notion_page(product_page_id, {"Sector Master": format_relation(sector_page_ids_for_product)})
|
||||
|
||||
# Clean up redundant fields in Product Master
|
||||
print("Cleaning up redundant Strategy Matrix field in Product Master...")
|
||||
update_notion_page(product_page_id, {"Strategy Matrix": format_rich_text("")})
|
||||
else:
|
||||
print("Strategy Matrix is empty. Skipping distribution.")
|
||||
|
||||
# 3. Distribute Feature-to-Value Translator Data
|
||||
feature_translator_prop_id = product_page["properties"]["Feature-to-Value Translator"]["id"]
|
||||
feature_translator_obj = get_page_property(product_page_id, feature_translator_prop_id)
|
||||
feature_translator_text = get_rich_text_content(feature_translator_obj)
|
||||
|
||||
if feature_translator_text:
|
||||
parsed_features = parse_markdown_table(feature_translator_text)
|
||||
if parsed_features:
|
||||
print("\n--- Distributing Feature-to-Value Translator Data ---")
|
||||
for item in parsed_features:
|
||||
feature = item.get("Feature")
|
||||
story = item.get("The Story (Benefit)")
|
||||
headline = item.get("Headline")
|
||||
|
||||
if not all([feature, story, headline]):
|
||||
print(f"Skipping feature item due to missing data: {item}")
|
||||
continue
|
||||
|
||||
print(f"Creating Feature-to-Value entry for: {feature}")
|
||||
create_notion_page(
|
||||
DB_IDS["Feature-to-Value Translator"],
|
||||
{
|
||||
"Feature": format_title(feature),
|
||||
"Story (Benefit)": format_rich_text(story),
|
||||
"Headline": format_rich_text(headline),
|
||||
"Product Master": format_relation(product_page_id)
|
||||
}
|
||||
)
|
||||
|
||||
# Clean up the source field
|
||||
print("Cleaning up redundant Feature-to-Value Translator field in Product Master...")
|
||||
update_notion_page(product_page_id, {"Feature-to-Value Translator": format_rich_text("")})
|
||||
else:
|
||||
print("Feature-to-Value Translator is empty. Skipping distribution.")
|
||||
|
||||
|
||||
print("\n--- Data distribution process complete. ---")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,10 +1,19 @@
|
||||
# import_product.py
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
import time
|
||||
|
||||
# --- Configuration ---
|
||||
NOTION_TOKEN = "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" # This will be replaced by the user's actual token.
|
||||
# NOTION_TOKEN wird jetzt aus der Datei gelesen
|
||||
try:
|
||||
with open("notion_token.txt", "r") as f:
|
||||
NOTION_TOKEN = f.read().strip()
|
||||
except FileNotFoundError:
|
||||
print("Error: notion_token.txt not found.")
|
||||
print("Please create the notion_token.txt file with your Notion integration token.")
|
||||
exit(1)
|
||||
|
||||
NOTION_VERSION = "2022-06-28"
|
||||
NOTION_API_BASE_URL = "https://api.notion.com/v1"
|
||||
|
||||
@@ -23,6 +32,11 @@ DB_IDS = {
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
def clean_json_response(text):
|
||||
if text.startswith("```json") and text.endswith("```"):
|
||||
return text[7:-3].strip()
|
||||
return text
|
||||
|
||||
def create_notion_page(database_id, properties):
|
||||
"""Creates a new page in a Notion database."""
|
||||
url = f"{NOTION_API_BASE_URL}/pages"
|
||||
@@ -43,6 +57,64 @@ def create_notion_page(database_id, properties):
|
||||
print(f"An unexpected error occurred while creating a page: {e}")
|
||||
return None
|
||||
|
||||
def update_notion_page(page_id, properties):
|
||||
"""Updates an existing page in Notion."""
|
||||
url = f"{NOTION_API_BASE_URL}/pages/{page_id}"
|
||||
payload = {
|
||||
"properties": properties
|
||||
}
|
||||
try:
|
||||
response = requests.patch(url, headers=HEADERS, json=payload)
|
||||
response.raise_for_status()
|
||||
print(f"Successfully updated page {page_id}.")
|
||||
return response.json()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
print(f"HTTP Error updating page {page_id}: {e}")
|
||||
print(f"Response content: {response.text}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred while updating a page: {e}")
|
||||
return None
|
||||
|
||||
def find_notion_page_by_title(database_id, title):
|
||||
"""Searches for a page in a Notion database by its title property."""
|
||||
url = f"{NOTION_API_BASE_URL}/databases/{database_id}/query"
|
||||
filter_payload = {
|
||||
"filter": {
|
||||
"property": "Name",
|
||||
"title": {"equals": title}
|
||||
}
|
||||
}
|
||||
try:
|
||||
response = requests.post(url, headers=HEADERS, json=filter_payload)
|
||||
response.raise_for_status()
|
||||
results = response.json().get("results")
|
||||
if results:
|
||||
return results[0] # Return the first matching page
|
||||
return None
|
||||
except requests.exceptions.HTTPError as e:
|
||||
print(f"HTTP Error searching page in DB {database_id}: {e}")
|
||||
print(f"Response content: {response.text}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred while searching for a page: {e}")
|
||||
return None
|
||||
|
||||
def get_database_properties(database_id):
|
||||
"""Retrieves the properties (schema) of a Notion database."""
|
||||
url = f"{NOTION_API_BASE_URL}/databases/{database_id}"
|
||||
try:
|
||||
response = requests.get(url, headers=HEADERS)
|
||||
response.raise_for_status()
|
||||
return response.json().get("properties")
|
||||
except requests.exceptions.HTTPError as e:
|
||||
print(f"HTTP Error retrieving database properties for DB {database_id}: {e}")
|
||||
print(f"Response content: {response.text}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred while retrieving database properties: {e}")
|
||||
return None
|
||||
|
||||
def format_rich_text(text):
|
||||
"""Formats a string into Notion's rich text structure."""
|
||||
return {"rich_text": [{"type": "text", "text": {"content": text}}]}
|
||||
@@ -78,25 +150,50 @@ def main():
|
||||
print("ERROR: 'Puma_m20_2026-01-08.md' not found. Please make sure the file is in the same directory.")
|
||||
return
|
||||
|
||||
# --- Phase 1: Create Product in Product Master ---
|
||||
print("--- Phase 1: Creating Product ---")
|
||||
# Define the product name
|
||||
PRODUCT_NAME = "Puma M20" # This will be replaced by the user's actual product name.
|
||||
|
||||
# --- Phase 1: Prepare Product Data ---
|
||||
print(f"--- Phase 1: Preparing Product Data for {PRODUCT_NAME} ---")
|
||||
product_analysis = extract_section(md_content, "2. Product Analysis")
|
||||
key_features = re.search(r"\*\*Key Features:\*\*(.*?)\*\*Constraints:\*\*", product_analysis, re.S).group(1).strip()
|
||||
constraints = re.search(r"\*\*Constraints:\*\*(.*)", product_analysis, re.S).group(1).strip()
|
||||
|
||||
target_audience = extract_section(md_content, "3. Target Audience")
|
||||
strategy_matrix = extract_section(md_content, "5. Strategy Matrix")
|
||||
if len(strategy_matrix) > 2000:
|
||||
strategy_matrix = strategy_matrix[:2000] # Truncate to 2000 characters
|
||||
print("Warning: 'Strategy Matrix' content truncated to 2000 characters due to Notion API limit.")
|
||||
feature_translator = extract_section(md_content, "FEATURE-TO-VALUE TRANSLATOR (PHASE 9)")
|
||||
|
||||
product_properties = {
|
||||
"Name": format_title("Puma M20"),
|
||||
"Name": format_title(PRODUCT_NAME),
|
||||
"Beschreibung": format_rich_text("Ein geländegängiger, wetterfester Roboter, der für anspruchsvolle Umgebungen konzipiert wurde."),
|
||||
"Spezifikationen": format_rich_text(f"Key Features:\n{key_features}\n\nConstraints:\n{constraints}"),
|
||||
"Key Features": format_rich_text(key_features),
|
||||
"Constraints": format_rich_text(constraints),
|
||||
"Target Audience": format_rich_text(target_audience),
|
||||
"Strategy Matrix": format_rich_text(strategy_matrix),
|
||||
"Feature-to-Value Translator": format_rich_text(feature_translator),
|
||||
"Layer": {"multi_select": [{"name": "Security"}, {"name": "Service"}]}
|
||||
}
|
||||
|
||||
product_page = create_notion_page(DB_IDS["Product Master"], product_properties)
|
||||
if not product_page:
|
||||
print("Failed to create product page. Aborting.")
|
||||
return
|
||||
product_page_id = product_page["id"]
|
||||
print(f"Created Product 'Puma M20' with ID: {product_page_id}")
|
||||
# Check if product already exists
|
||||
existing_product_page = find_notion_page_by_title(DB_IDS["Product Master"], PRODUCT_NAME)
|
||||
product_page_id = None
|
||||
if existing_product_page:
|
||||
product_page_id = existing_product_page["id"]
|
||||
print(f"Product '{PRODUCT_NAME}' already exists with ID: {product_page_id}. Updating...")
|
||||
updated_page = update_notion_page(product_page_id, product_properties)
|
||||
if not updated_page:
|
||||
print("Failed to update product page. Aborting.")
|
||||
return
|
||||
else:
|
||||
print(f"Product '{PRODUCT_NAME}' not found. Creating new page...")
|
||||
new_product_page = create_notion_page(DB_IDS["Product Master"], product_properties)
|
||||
if not new_product_page:
|
||||
print("Failed to create product page. Aborting.")
|
||||
return
|
||||
product_page_id = new_product_page["id"]
|
||||
print(f"Created Product '{PRODUCT_NAME}' with ID: {product_page_id}")
|
||||
|
||||
|
||||
# --- Phase 2: Create Sectors in Sector & Persona Master ---
|
||||
|
||||
19
read_file_content.py
Normal file
19
read_file_content.py
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
import argparse
|
||||
|
||||
def read_file_content(file_path):
|
||||
"""Reads and prints the content of a specified file."""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
print(f.read())
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File not found at '{file_path}'")
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Read and display the content of a file.")
|
||||
parser.add_argument("file_path", help="The path to the file you want to read.")
|
||||
args = parser.parse_args()
|
||||
|
||||
read_file_content(args.file_path)
|
||||
41
read_notion_dashboard.py
Normal file
41
read_notion_dashboard.py
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Get the Notion API key from the environment variable
|
||||
api_key = os.environ.get("NOTION_API_KEY")
|
||||
|
||||
# If the API key is not set, try to read it from the file
|
||||
if not api_key:
|
||||
try:
|
||||
with open("notion_token.txt", "r") as f:
|
||||
api_key = f.read().strip()
|
||||
except FileNotFoundError:
|
||||
print("Error: notion_token.txt not found.")
|
||||
print("Please set the NOTION_API_KEY environment variable or create the notion_token.txt file.")
|
||||
exit()
|
||||
|
||||
# The ID of the page to retrieve
|
||||
page_id = "2e288f42-8544-81d8-96f5-c231f84f719a" # Product Master
|
||||
|
||||
# The Notion API endpoint for retrieving a page
|
||||
url = f"https://api.notion.com/v1/pages/{page_id}"
|
||||
|
||||
# The headers for the API request
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json",
|
||||
"Notion-Version": "2022-06-28",
|
||||
}
|
||||
|
||||
# Make the API request
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
# Check the response status code
|
||||
if response.status_code == 200:
|
||||
# Print the response content
|
||||
print(json.dumps(response.json(), indent=2))
|
||||
else:
|
||||
print(f"Error: {response.status_code}")
|
||||
print(response.text)
|
||||
Reference in New Issue
Block a user