[2ff88f42] feat: Integrated Miller-Heiman strategic context into Marketing Matrix

This commit is contained in:
2026-03-01 18:43:47 +00:00
parent 6a7135a314
commit 4a2cfc5756
5 changed files with 220 additions and 0 deletions

View File

@@ -140,6 +140,7 @@ class Industry(Base):
notes = Column(Text, nullable=True)
priority = Column(String, nullable=True) # Replaces old status concept ("Freigegeben")
ops_focus_secondary = Column(Boolean, default=False)
strategy_briefing = Column(Text, nullable=True) # NEW: Strategic context (Miller Heiman)
# NEW SCHEMA FIELDS (from MIGRATION_PLAN)
metric_type = Column(String, nullable=True) # Unit_Count, Area_in, Area_out

View File

@@ -121,8 +121,12 @@ SPEZIFISCHE HERAUSFORDERUNGEN (PAIN POINTS) DER ROLLE:
SPEZIFISCHE NUTZEN (GAINS) DER ROLLE:
{chr(10).join(['- ' + str(g) for g in persona_gains])}
HINTERGRUNDWISSEN & STRATEGIE (Miller Heiman):
{industry.strategy_briefing or 'Kein spezifisches Briefing verfügbar.'}
--- DEINE AUFGABE ---
Deine Texte müssen "voll ins Zentrum" der Rolle treffen. Vermeide oberflächliche Floskeln. Nutze die Details zur Denkweise, den KPIs und den Überzeugungsargumenten, um eine tiefgreifende Relevanz zu erzeugen.
Nutze das Strategie-Briefing, um typische Einwände vorwegzunehmen oder "Red Flags" zu vermeiden.
1. **Subject:** Formuliere eine kurze Betreffzeile (max. 6 Wörter). Richte sie **direkt an einem der persönlichen Pain Points** des Ansprechpartners oder dem zentralen Branchen-Pain. Sei scharfsinnig, nicht werblich.

View File

@@ -156,6 +156,9 @@ def sync_industries(token, session):
# New Field: Ops Focus Secondary (Checkbox)
industry.ops_focus_secondary = props.get("Ops Focus: Secondary", {}).get("checkbox", False)
# New Field: Strategy Briefing (Miller Heiman)
industry.strategy_briefing = extract_rich_text(props.get("Strategy Briefing"))
# Relation: Primary Product Category
relation = props.get("Primary Product Category", {}).get("relation", [])

View File

@@ -0,0 +1,24 @@
import sqlite3
DB_PATH = "/app/companies_v3_fixed_2.db"
def add_column():
try:
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
print(f"Adding column 'strategy_briefing' to 'industries' table in {DB_PATH}...")
cursor.execute("ALTER TABLE industries ADD COLUMN strategy_briefing TEXT;")
conn.commit()
print("Success.")
except sqlite3.OperationalError as e:
if "duplicate column name" in str(e):
print("Column 'strategy_briefing' already exists. Skipping.")
else:
print(f"Error: {e}")
except Exception as e:
print(f"Error: {e}")
finally:
if conn: conn.close()
if __name__ == "__main__":
add_column()

View File

@@ -0,0 +1,188 @@
import sys
import os
import csv
import requests
import json
import logging
from collections import defaultdict
# Setup Logger
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
logger = logging.getLogger("NotionPusher")
# Config
CSV_PATH = "./docs/miller_heiman_augmented.csv"
NOTION_TOKEN_FILE = "/app/notion_token.txt"
INDUSTRIES_DB_ID = "2ec88f4285448014ab38ea664b4c2b81"
def load_notion_token():
try:
with open(NOTION_TOKEN_FILE, "r") as f:
return f.read().strip()
except:
logger.error("Token file not found.")
sys.exit(1)
def add_strategy_property(token):
"""Try to add the 'Strategy Briefing' property to the database schema."""
url = f"https://api.notion.com/v1/databases/{INDUSTRIES_DB_ID}"
headers = {
"Authorization": f"Bearer {token}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
# First, get current schema to check if it exists
resp = requests.get(url, headers=headers)
if resp.status_code != 200:
logger.error(f"Failed to fetch DB schema: {resp.text}")
return False
current_properties = resp.json().get("properties", {})
if "Strategy Briefing" in current_properties:
logger.info("Property 'Strategy Briefing' already exists.")
return True
logger.info("Property 'Strategy Briefing' missing. Attempting to create...")
payload = {
"properties": {
"Strategy Briefing": {
"rich_text": {}
}
}
}
update_resp = requests.patch(url, headers=headers, json=payload)
if update_resp.status_code == 200:
logger.info("Successfully added 'Strategy Briefing' property.")
return True
else:
logger.error(f"Failed to add property: {update_resp.text}")
return False
def get_pages_map(token):
"""Returns a map of Vertical Name -> Page ID"""
url = f"https://api.notion.com/v1/databases/{INDUSTRIES_DB_ID}/query"
headers = {
"Authorization": f"Bearer {token}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
mapping = {}
has_more = True
next_cursor = None
while has_more:
payload = {}
if next_cursor:
payload["start_cursor"] = next_cursor
resp = requests.post(url, headers=headers, json=payload)
if resp.status_code != 200:
break
data = resp.json()
for page in data.get("results", []):
props = page.get("properties", {})
name_parts = props.get("Vertical", {}).get("title", [])
if name_parts:
name = "".join([t.get("plain_text", "") for t in name_parts])
mapping[name] = page["id"]
has_more = data.get("has_more", False)
next_cursor = data.get("next_cursor")
return mapping
def update_page(token, page_id, pains, gains, strategy):
url = f"https://api.notion.com/v1/pages/{page_id}"
headers = {
"Authorization": f"Bearer {token}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
# Construct Payload
props = {}
# Only update if content exists (safety)
if pains:
props["Pains"] = {"rich_text": [{"text": {"content": pains[:2000]}}]} # Limit to 2000 chars to be safe
if gains:
props["Gains"] = {"rich_text": [{"text": {"content": gains[:2000]}}]}
if strategy:
props["Strategy Briefing"] = {"rich_text": [{"text": {"content": strategy[:2000]}}]}
if not props:
return
payload = {"properties": props}
resp = requests.patch(url, headers=headers, json=payload)
if resp.status_code != 200:
logger.error(f"Failed to update page {page_id}: {resp.text}")
else:
logger.info(f"Updated page {page_id}")
def main():
token = load_notion_token()
# 1. Ensure Schema
if not add_strategy_property(token):
logger.warning("Could not add 'Strategy Briefing' column. Please add it manually in Notion as a 'Text' property.")
# We continue, maybe it failed because of permissions but column exists?
# Actually if it failed, the update later will fail too if key is missing.
# But let's try.
# 2. Map Notion Verticals
logger.info("Mapping existing Notion pages...")
notion_map = get_pages_map(token)
logger.info(f"Found {len(notion_map)} verticals in Notion.")
# 3. Read CSV
logger.info(f"Reading CSV: {CSV_PATH}")
csv_data = {}
with open(CSV_PATH, "r", encoding="utf-8-sig") as f:
reader = csv.DictReader(f, delimiter=";")
for row in reader:
vertical = row.get("Vertical")
if not vertical:
continue
# Aggregate Strategy
parts = []
if row.get("MH Coach Hypothesis"):
parts.append(f"🧠 MH Coach: {row.get('MH Coach Hypothesis')}")
if row.get("MH Early Red Flags"):
parts.append(f"🚩 Red Flags: {row.get('MH Early Red Flags')}")
if row.get("MH Adjustments / Ergänzungen"):
parts.append(f"🔧 Adjustments: {row.get('MH Adjustments / Ergänzungen')}")
strategy = "\n\n".join(parts)
# Store (last row wins if duplicates, but they should be identical for vertical data)
csv_data[vertical] = {
"pains": row.get("Pains (clean)", ""),
"gains": row.get("Gains (clean)", ""),
"strategy": strategy
}
# 4. Push Updates
logger.info("Starting Batch Update...")
count = 0
for vertical, data in csv_data.items():
if vertical in notion_map:
page_id = notion_map[vertical]
logger.info(f"Updating '{vertical}'...")
update_page(token, page_id, data["pains"], data["gains"], data["strategy"])
count += 1
else:
logger.warning(f"Skipping '{vertical}' (Not found in Notion)")
logger.info(f"Finished. Updated {count} verticals.")
if __name__ == "__main__":
main()