- 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.
255 lines
11 KiB
Python
255 lines
11 KiB
Python
# 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()
|