153 lines
5.6 KiB
Python
153 lines
5.6 KiB
Python
import sqlite3
|
|
import os
|
|
import json
|
|
import requests
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv(override=True)
|
|
|
|
DB_FILE = "marketing_matrix.db"
|
|
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
|
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
|
|
NOTION_DB_INDUSTRIES = "2ec88f4285448014ab38ea664b4c2b81"
|
|
|
|
# --- MAPPINGS ---
|
|
# SuperOffice ID -> Notion Vertical Name
|
|
VERTICAL_MAP = {
|
|
23: "Logistics - Warehouse"
|
|
}
|
|
|
|
# SuperOffice ID -> Persona Name & Pains (aus unserer Definition)
|
|
ROLE_MAP = {
|
|
19: {"name": "Operativer Entscheider", "pains": "Zuverlässigkeit, einfache Bedienbarkeit, Personaleinsatz-Optimierung, minimale Störungen"},
|
|
20: {"name": "Infrastruktur-Verantwortlicher", "pains": "Technische Machbarkeit, IT-Sicherheit, Integration, Brandschutz"},
|
|
21: {"name": "Wirtschaftlicher Entscheider", "pains": "ROI, Amortisationszeit, Kostenstruktur, Einsparpotenziale"},
|
|
22: {"name": "Innovations-Treiber", "pains": "Wettbewerbsfähigkeit, Modernisierung, Employer Branding, Kundenerlebnis"}
|
|
}
|
|
|
|
# --- DATABASE SETUP ---
|
|
def init_db():
|
|
conn = sqlite3.connect(DB_FILE)
|
|
c = conn.cursor()
|
|
c.execute('''CREATE TABLE IF NOT EXISTS text_blocks
|
|
(vertical_id INTEGER, role_id INTEGER,
|
|
subject TEXT, intro TEXT, social_proof TEXT,
|
|
PRIMARY KEY (vertical_id, role_id))''')
|
|
conn.commit()
|
|
conn.close()
|
|
print("✅ Database initialized.")
|
|
|
|
# --- NOTION FETCHER ---
|
|
def get_vertical_pains_gains(vertical_name):
|
|
url = f"https://api.notion.com/v1/databases/{NOTION_DB_INDUSTRIES}/query"
|
|
headers = {
|
|
"Authorization": f"Bearer {NOTION_API_KEY}",
|
|
"Notion-Version": "2022-06-28",
|
|
"Content-Type": "application/json"
|
|
}
|
|
payload = {
|
|
"filter": {
|
|
"property": "Vertical",
|
|
"title": {"contains": vertical_name}
|
|
}
|
|
}
|
|
resp = requests.post(url, headers=headers, json=payload)
|
|
if resp.status_code == 200:
|
|
results = resp.json().get("results", [])
|
|
if results:
|
|
props = results[0]['properties']
|
|
pains = props.get('Pains', {}).get('rich_text', [])
|
|
gains = props.get('Gains', {}).get('rich_text', [])
|
|
return {
|
|
"pains": pains[0]['plain_text'] if pains else "",
|
|
"gains": gains[0]['plain_text'] if gains else ""
|
|
}
|
|
print(f"⚠️ Warning: No data found for {vertical_name}")
|
|
return {"pains": "N/A", "gains": "N/A"}
|
|
|
|
# --- GEMINI GENERATOR ---
|
|
def generate_text(vertical_name, v_data, role_id, role_data):
|
|
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}"
|
|
|
|
prompt = f"""
|
|
Du bist ein B2B-Copywriter. Erstelle 3 Textbausteine für eine Cold-Outreach E-Mail.
|
|
|
|
KONTEXT:
|
|
Branche: {vertical_name}
|
|
Branchen-Pains: {v_data['pains']}
|
|
Lösung-Gains: {v_data['gains']}
|
|
|
|
Rolle: {role_data['name']}
|
|
Rollen-Pains: {role_data['pains']}
|
|
|
|
AUFGABE:
|
|
1. Subject: Betreffzeile (max 40 Zeichen!). Knackig, Pain-bezogen.
|
|
2. Intro: Einleitungssatz (max 40 Zeichen!). Brücke Pain -> Lösung.
|
|
3. SocialProof: Referenzsatz (max 40 Zeichen!). "Wir arbeiten mit X..."
|
|
|
|
FORMAT (JSON):
|
|
{{ "Subject": "...", "Intro": "...", "SocialProof": "..." }}
|
|
"""
|
|
|
|
payload = {
|
|
"contents": [{"parts": [{"text": prompt}]}],
|
|
"generationConfig": {"responseMimeType": "application/json"}
|
|
}
|
|
|
|
try:
|
|
resp = requests.post(url, json=payload)
|
|
if resp.status_code == 200:
|
|
return json.loads(resp.json()['candidates'][0]['content']['parts'][0]['text'])
|
|
except Exception as e:
|
|
print(f"Gemini Error: {e}")
|
|
return None
|
|
|
|
# --- MAIN ---
|
|
def run_matrix():
|
|
init_db()
|
|
conn = sqlite3.connect(DB_FILE)
|
|
c = conn.cursor()
|
|
|
|
# Iterate Verticals
|
|
for v_id, v_name in VERTICAL_MAP.items():
|
|
print(f"\nProcessing Vertical: {v_name} (ID: {v_id})")
|
|
v_data = get_vertical_pains_gains(v_name)
|
|
|
|
# Iterate Roles
|
|
for r_id, r_data in ROLE_MAP.items():
|
|
print(f" > Generating for Role: {r_data['name']} (ID: {r_id})...", end="", flush=True)
|
|
|
|
# Check if exists (optional: skip if exists)
|
|
# ...
|
|
|
|
text_block = generate_text(v_name, v_data, r_id, r_data)
|
|
|
|
if text_block:
|
|
# Robustness: Handle list return from Gemini
|
|
if isinstance(text_block, list):
|
|
if len(text_block) > 0 and isinstance(text_block[0], dict):
|
|
text_block = text_block[0] # Take first item if list of dicts
|
|
else:
|
|
print(" ❌ Failed (Unexpected JSON format: List without dicts).")
|
|
continue
|
|
|
|
# Cut to 40 chars hard limit (Safety)
|
|
subj = text_block.get("Subject", "")[:40]
|
|
intro = text_block.get("Intro", "Intro")[:40] # Fallback key name check
|
|
if "Introduction_Textonly" in text_block: intro = text_block["Introduction_Textonly"][:40]
|
|
proof = text_block.get("SocialProof", "")[:40]
|
|
if "Industry_References_Textonly" in text_block: proof = text_block["Industry_References_Textonly"][:40]
|
|
|
|
c.execute("INSERT OR REPLACE INTO text_blocks VALUES (?, ?, ?, ?, ?)",
|
|
(v_id, r_id, subj, intro, proof))
|
|
conn.commit()
|
|
print(" ✅ Done.")
|
|
else:
|
|
print(" ❌ Failed.")
|
|
|
|
conn.close()
|
|
print("\nMatrix generation complete.")
|
|
|
|
if __name__ == "__main__":
|
|
run_matrix()
|