[2ff88f42] einfügen
einfügen
This commit is contained in:
@@ -1 +1 @@
|
|||||||
{"task_id": "31188f42-8544-80f0-b21a-c6beaa9ea3a1", "token": "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", "session_start_time": "2026-02-24T06:47:22.751414"}
|
{"task_id": "2ff88f42-8544-8050-8245-c3bb852058f4", "token": "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", "session_start_time": "2026-02-24T07:13:35.422817"}
|
||||||
11
GEMINI.md
11
GEMINI.md
@@ -105,6 +105,17 @@ 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.
|
* **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.
|
* **Solution:** Implementing a polling mechanism (`setInterval`) tied to a `isProcessing` state is superior to static timeouts for long-running AI tasks.
|
||||||
|
|
||||||
|
7. **Hyper-Personalized Marketing Engine (v3.2) - "Deep Persona Injection":**
|
||||||
|
* **Problem:** Marketing texts were too generic and didn't reflect the specific psychological or operative profile of the different target roles (e.g., CFO vs. Facility Manager).
|
||||||
|
* **Solution (Deep Sync & Prompt Hardening):**
|
||||||
|
1. **Extended Schema:** Added `description`, `convincing_arguments`, and `kpis` to the `Persona` database model to store richer profile data.
|
||||||
|
2. **Notion Master Sync:** Updated the synchronization logic to pull these deep insights directly from the Notion "Personas / Roles" database.
|
||||||
|
3. **Role-Centric Prompts:** The `MarketingMatrix` generator was re-engineered to inject the persona's "Mindset" and "KPIs" into the prompt.
|
||||||
|
* **Example (Healthcare):**
|
||||||
|
- **Infrastructure Lead:** Focuses now on "IT Security", "DSGVO Compliance", and "WLAN integration".
|
||||||
|
- **Economic Buyer (CFO):** Focuses on "ROI Amortization", "Reduction of Overtime", and "Flexible Financing (RaaS)".
|
||||||
|
* **Verification:** Verified that the transition from a company-specific **Opener** (e.g., observing staff shortages at Klinikum Erding) to the **Role-specific Intro** (e.g., pitching transport robots to reduce walking distances for nursing directors) is seamless and logical.
|
||||||
|
|
||||||
## Metric Parser - Regression Tests
|
## Metric Parser - Regression Tests
|
||||||
To ensure the stability and accuracy of the metric extraction logic, a dedicated test suite (`/company-explorer/backend/tests/test_metric_parser.py`) has been created. It covers the following critical, real-world bug fixes:
|
To ensure the stability and accuracy of the metric extraction logic, a dedicated test suite (`/company-explorer/backend/tests/test_metric_parser.py`) has been created. It covers the following critical, real-world bug fixes:
|
||||||
|
|
||||||
|
|||||||
@@ -339,6 +339,19 @@ PERSÖNLICHE HERAUSFORDERUNGEN: {persona_pains}
|
|||||||
|
|
||||||
**Konzept:** Strikte Trennung zwischen `[Primary Product]` und `[Secondary Product]` zur Vermeidung logischer Brüche.
|
**Konzept:** Strikte Trennung zwischen `[Primary Product]` und `[Secondary Product]` zur Vermeidung logischer Brüche.
|
||||||
|
|
||||||
|
### 17.9 Deep Persona Injection (Update Feb 24, 2026)
|
||||||
|
|
||||||
|
**Ziel:** Maximale Relevanz durch Einbezug psychografischer und operativer Rollen-Details ("Voll ins Zentrum").
|
||||||
|
|
||||||
|
**Die Erweiterung:**
|
||||||
|
- **Vollständiger Daten-Sync:** Übernahme von `Beschreibung/Denkweise`, `Was ihn überzeugt` und `KPIs` aus der Notion "Personas / Roles" Datenbank in das lokale Schema.
|
||||||
|
- **Rollenspezifische Tonalität:** Die KI nutzt diese Details, um den "Ton" der jeweiligen Persona perfekt zu treffen (z.B. technischer Fokus beim Infrastruktur-Leiter vs. betriebswirtschaftlicher Fokus beim CFO).
|
||||||
|
|
||||||
|
**Beispiel-Kaskade (Klinikum Erding):**
|
||||||
|
1. **Opener:** "Klinikum Erding trägt maßgeblich zur regionalen Versorgung bei... Dokumentation lückenloser Hygiene stellt eine operative Herausforderung dar."
|
||||||
|
2. **Matrix-Anschluss (Infrastruktur):** "...minimieren Ausfallzeiten um 80-90% durch proaktives Monitoring... planbare Wartung und Transparenz durch feste **SLAs**." (Direkter Bezug auf hinterlegte Überzeugungsargumente).
|
||||||
|
3. **Matrix-Anschluss (Wirtschaftlich):** "...Reduktion operativer Personalkosten um 10-25%... wirkt sich direkt auf **ROI** und **Amortisationszeit** aus." (Direkter Bezug auf hinterlegte KPIs).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 18. Next Steps & Todos (Post-Migration)
|
## 18. Next Steps & Todos (Post-Migration)
|
||||||
|
|||||||
16
check_erding_openers.py
Normal file
16
check_erding_openers.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
DB_PATH = "/app/companies_v3_fixed_2.db"
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute("SELECT name, ai_opener, ai_opener_secondary, industry_ai FROM companies WHERE name LIKE '%Erding%'")
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
print(f"Company: {row[0]}")
|
||||||
|
print(f"Industry: {row[3]}")
|
||||||
|
print(f"Opener Primary: {row[1]}")
|
||||||
|
print(f"Opener Secondary: {row[2]}")
|
||||||
|
else:
|
||||||
|
print("Company not found.")
|
||||||
|
conn.close()
|
||||||
16
check_klinikum_erding.py
Normal file
16
check_klinikum_erding.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
DB_PATH = "/app/companies_v3_fixed_2.db"
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute("SELECT name, ai_opener, ai_opener_secondary, industry_ai FROM companies WHERE name LIKE '%Klinikum Landkreis Erding%'")
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
print(f"Company: {row[0]}")
|
||||||
|
print(f"Industry: {row[3]}")
|
||||||
|
print(f"Opener Primary: {row[1]}")
|
||||||
|
print(f"Opener Secondary: {row[2]}")
|
||||||
|
else:
|
||||||
|
print("Company not found.")
|
||||||
|
conn.close()
|
||||||
23
check_matrix_indoor.py
Normal file
23
check_matrix_indoor.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
DB_PATH = "/app/companies_v3_fixed_2.db"
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
query = """
|
||||||
|
SELECT i.name, p.name, m.subject, m.intro, m.social_proof
|
||||||
|
FROM marketing_matrix m
|
||||||
|
JOIN industries i ON m.industry_id = i.id
|
||||||
|
JOIN personas p ON m.persona_id = p.id
|
||||||
|
WHERE i.name = 'Leisure - Indoor Active'
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(query)
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
print(f"Industry: {row[0]} | Persona: {row[1]}")
|
||||||
|
print(f" Subject: {row[2]}")
|
||||||
|
print(f" Intro: {row[3]}")
|
||||||
|
print(f" Social Proof: {row[4]}")
|
||||||
|
print("-" * 50)
|
||||||
|
conn.close()
|
||||||
24
check_matrix_results.py
Normal file
24
check_matrix_results.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import sqlite3
|
||||||
|
import json
|
||||||
|
|
||||||
|
DB_PATH = "/app/companies_v3_fixed_2.db"
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
query = """
|
||||||
|
SELECT i.name, p.name, m.subject, m.intro, m.social_proof
|
||||||
|
FROM marketing_matrix m
|
||||||
|
JOIN industries i ON m.industry_id = i.id
|
||||||
|
JOIN personas p ON m.persona_id = p.id
|
||||||
|
WHERE i.name = 'Healthcare - Hospital'
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(query)
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
print(f"Industry: {row[0]} | Persona: {row[1]}")
|
||||||
|
print(f" Subject: {row[2]}")
|
||||||
|
print(f" Intro: {row[3]}")
|
||||||
|
print(f" Social Proof: {row[4]}")
|
||||||
|
print("-" * 50)
|
||||||
|
conn.close()
|
||||||
@@ -205,8 +205,12 @@ class Persona(Base):
|
|||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
name = Column(String, unique=True, index=True) # Matches the 'role' string in JobRolePattern
|
name = Column(String, unique=True, index=True) # Matches the 'role' string in JobRolePattern
|
||||||
|
|
||||||
|
description = Column(Text, nullable=True) # NEW: Role description / how they think
|
||||||
pains = Column(Text, nullable=True) # JSON list or multiline string
|
pains = Column(Text, nullable=True) # JSON list or multiline string
|
||||||
gains = Column(Text, nullable=True) # JSON list or multiline string
|
gains = Column(Text, nullable=True) # JSON list or multiline string
|
||||||
|
convincing_arguments = Column(Text, nullable=True) # NEW: What convinces them
|
||||||
|
typical_positions = Column(Text, nullable=True) # NEW: Typical titles
|
||||||
|
kpis = Column(Text, nullable=True) # NEW: Relevant KPIs
|
||||||
|
|
||||||
created_at = Column(DateTime, default=datetime.utcnow)
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|||||||
@@ -93,9 +93,16 @@ def generate_prompt(industry: Industry, persona: Persona) -> str:
|
|||||||
persona_pains = [persona.pains] if persona.pains else []
|
persona_pains = [persona.pains] if persona.pains else []
|
||||||
persona_gains = [persona.gains] if persona.gains else []
|
persona_gains = [persona.gains] if persona.gains else []
|
||||||
|
|
||||||
|
# Advanced Persona Context
|
||||||
|
persona_context = f"""
|
||||||
|
BESCHREIBUNG/DENKWEISE: {persona.description or 'Nicht definiert'}
|
||||||
|
WAS DIESE PERSON ÜBERZEUGT: {persona.convincing_arguments or 'Nicht definiert'}
|
||||||
|
RELEVANTE KPIs: {persona.kpis or 'Nicht definiert'}
|
||||||
|
"""
|
||||||
|
|
||||||
prompt = f"""
|
prompt = f"""
|
||||||
Du bist ein kompetenter Lösungsberater und brillanter Texter.
|
Du bist ein kompetenter Lösungsberater und brillanter Texter für B2B-Marketing.
|
||||||
AUFGABE: Erstelle 3 Textblöcke (Subject, Introduction_Textonly, Industry_References_Textonly) für eine E-Mail an einen Entscheider.
|
AUFGABE: Erstelle 3 hoch-personalisierte Textblöcke (Subject, Introduction_Textonly, Industry_References_Textonly) für eine E-Mail an einen Entscheider.
|
||||||
|
|
||||||
--- KONTEXT ---
|
--- KONTEXT ---
|
||||||
ZIELBRANCHE: {industry.name}
|
ZIELBRANCHE: {industry.name}
|
||||||
@@ -106,20 +113,27 @@ FOKUS-PRODUKT (LÖSUNG):
|
|||||||
{product_context}
|
{product_context}
|
||||||
|
|
||||||
ANSPRECHPARTNER (ROLLE): {persona.name}
|
ANSPRECHPARTNER (ROLLE): {persona.name}
|
||||||
PERSÖNLICHE HERAUSFORDERUNGEN DES ANSPRECHPARTNERS (PAIN POINTS):
|
{persona_context}
|
||||||
|
|
||||||
|
SPEZIFISCHE HERAUSFORDERUNGEN (PAIN POINTS) DER ROLLE:
|
||||||
{chr(10).join(['- ' + str(p) for p in persona_pains])}
|
{chr(10).join(['- ' + str(p) for p in persona_pains])}
|
||||||
|
|
||||||
|
SPEZIFISCHE NUTZEN (GAINS) DER ROLLE:
|
||||||
|
{chr(10).join(['- ' + str(g) for g in persona_gains])}
|
||||||
|
|
||||||
--- DEINE AUFGABE ---
|
--- 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.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
2. **Introduction_Textonly:** Formuliere einen prägnanten Einleitungstext (max. 2 Sätze).
|
2. **Introduction_Textonly:** Formuliere einen prägnanten Einleitungstext (max. 2 Sätze).
|
||||||
- **WICHTIG:** Gehe davon aus, dass die spezifische Herausforderung des Kunden bereits im Satz davor [Opener] genannt wurde. **Wiederhole die Herausforderung NICHT.**
|
- **WICHTIG:** Gehe davon aus, dass die spezifische Herausforderung des Kunden bereits im Satz davor [Opener] genannt wurde. **Wiederhole die Herausforderung NICHT.**
|
||||||
- **Satz 1 (Die Lösung & der Gain):** Beginne direkt mit der Lösung. Nenne die im Kontext `FOKUS-PRODUKT` definierte **Produktkategorie** (z.B. "automatisierte Reinigungsroboter") und verbinde sie mit dem zentralen Nutzen (Gain) aus den `BRANCHEN-HERAUSFORDERUNGEN`. Beispiel: "Genau hier setzen unsere automatisierten Reinigungsroboter an, indem sie eine lückenlose und auditsichere Hygiene gewährleisten."
|
- **Satz 1 (Die Lösung & der Gain):** Beginne direkt mit der Lösung. Nenne die im Kontext `FOKUS-PRODUKT` definierte **Produktkategorie** (z.B. "automatisierte Reinigungsroboter") und verbinde sie mit einem Nutzen, der für diese Rolle (siehe `WAS DIESE PERSON ÜBERZEUGT` und `GAINS`) besonders kritisch ist.
|
||||||
- **Satz 2 (Die Relevanz):** Stelle die Relevanz für die Zielperson her, indem du einen ihrer `PERSÖNLICHE HERAUSFORDERUNGEN` adressierst. Beispiel: "Für Sie als Infrastruktur-Verantwortlicher bedeutet dies vor allem eine reibungslose Integration in bestehende Abläufe, ohne den Betrieb zu stören."
|
- **Satz 2 (Die Relevanz):** Stelle die Relevanz für die Zielperson her, indem du eine ihrer `PERSÖNLICHE HERAUSFORDERUNGEN` oder `KPIs` adressierst. Beispiel: "Für Sie als [Rolle] bedeutet dies vor allem [Nutzen bezogen auf KPI oder Pain]."
|
||||||
|
|
||||||
3. **Industry_References_Textonly:** Formuliere einen **strategischen Referenz-Block (ca. 2-3 Sätze)** nach folgendem Muster:
|
3. **Industry_References_Textonly:** Formuliere einen **strategischen Referenz-Block (ca. 2-3 Sätze)** nach folgendem Muster:
|
||||||
- **Satz 1 (Social Proof):** Beginne direkt mit dem Nutzen, den vergleichbare Unternehmen in der Branche {industry.name} bereits erzielen. (Erfinde keine Firmennamen, sprich von "Führenden Einrichtungen" oder "Vergleichbaren Häusern").
|
- **Satz 1 (Social Proof):** Beginne direkt mit dem Nutzen, den vergleichbare Unternehmen in der Branche {industry.name} bereits erzielen. (Erfinde keine Firmennamen, sprich von "Führenden Einrichtungen" oder "Vergleichbaren Häusern").
|
||||||
- **Satz 2 (Rollen-Relevanz):** Schaffe den direkten Nutzen für die Zielperson. Formuliere z.B. 'Dieser Wissensvorsprung hilft uns, Ihre [persönlicher Pain Point der Rolle] besonders effizient zu lösen.'
|
- **Satz 2 (Rollen-Relevanz):** Schaffe den direkten Nutzen für die Zielperson. Nutze dabei die Informationen aus `BESCHREIBUNG/DENKWEISE`, um den Ton perfekt zu treffen.
|
||||||
|
|
||||||
--- BEISPIEL FÜR EINEN PERFEKTEN OUTPUT ---
|
--- BEISPIEL FÜR EINEN PERFEKTEN OUTPUT ---
|
||||||
{{
|
{{
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
NOTION_TOKEN_FILE = "/app/notion_token.txt"
|
NOTION_TOKEN_FILE = "/app/notion_token.txt"
|
||||||
# Sector & Persona Master DB
|
# Sector & Persona Master DB
|
||||||
PERSONAS_DB_ID = "2e288f42-8544-8113-b878-ec99c8a02a6b"
|
PERSONAS_DB_ID = "30588f42-8544-80c3-8919-e22d74d945ea"
|
||||||
|
|
||||||
VALID_ARCHETYPES = {
|
VALID_ARCHETYPES = {
|
||||||
"Wirtschaftlicher Entscheider",
|
"Wirtschaftlicher Entscheider",
|
||||||
"Operativer Entscheider",
|
"Operativer Entscheider",
|
||||||
"Infrastruktur-Verantwortlicher",
|
"Infrastruktur-Verantwortlicher",
|
||||||
"Innovations-Treiber"
|
"Innovations-Treiber",
|
||||||
|
"Influencer"
|
||||||
}
|
}
|
||||||
|
|
||||||
def load_notion_token():
|
def load_notion_token():
|
||||||
@@ -65,6 +66,10 @@ def extract_title(prop):
|
|||||||
if not prop: return ""
|
if not prop: return ""
|
||||||
return "".join([t.get("plain_text", "") for t in prop.get("title", [])])
|
return "".join([t.get("plain_text", "") for t in prop.get("title", [])])
|
||||||
|
|
||||||
|
def extract_rich_text(prop):
|
||||||
|
if not prop: return ""
|
||||||
|
return "".join([t.get("plain_text", "") for t in prop.get("rich_text", [])])
|
||||||
|
|
||||||
def extract_rich_text_to_list(prop):
|
def extract_rich_text_to_list(prop):
|
||||||
"""
|
"""
|
||||||
Extracts rich text and converts bullet points/newlines into a list of strings.
|
Extracts rich text and converts bullet points/newlines into a list of strings.
|
||||||
@@ -94,7 +99,8 @@ def sync_personas(token, session):
|
|||||||
|
|
||||||
for page in pages:
|
for page in pages:
|
||||||
props = page.get("properties", {})
|
props = page.get("properties", {})
|
||||||
name = extract_title(props.get("Name"))
|
# The title property is 'Role' in the new DB, not 'Name'
|
||||||
|
name = extract_title(props.get("Role"))
|
||||||
|
|
||||||
if name not in VALID_ARCHETYPES:
|
if name not in VALID_ARCHETYPES:
|
||||||
logger.debug(f"Skipping '{name}' (Not a target Archetype)")
|
logger.debug(f"Skipping '{name}' (Not a target Archetype)")
|
||||||
@@ -105,6 +111,11 @@ def sync_personas(token, session):
|
|||||||
pains_list = extract_rich_text_to_list(props.get("Pains"))
|
pains_list = extract_rich_text_to_list(props.get("Pains"))
|
||||||
gains_list = extract_rich_text_to_list(props.get("Gains"))
|
gains_list = extract_rich_text_to_list(props.get("Gains"))
|
||||||
|
|
||||||
|
description = extract_rich_text(props.get("Rollenbeschreibung"))
|
||||||
|
convincing_arguments = extract_rich_text(props.get("Was ihn überzeugt"))
|
||||||
|
typical_positions = extract_rich_text(props.get("Typische Positionen"))
|
||||||
|
kpis = extract_rich_text(props.get("KPIs"))
|
||||||
|
|
||||||
# Upsert Logic
|
# Upsert Logic
|
||||||
persona = session.query(Persona).filter(Persona.name == name).first()
|
persona = session.query(Persona).filter(Persona.name == name).first()
|
||||||
if not persona:
|
if not persona:
|
||||||
@@ -116,6 +127,10 @@ def sync_personas(token, session):
|
|||||||
|
|
||||||
persona.pains = json.dumps(pains_list, ensure_ascii=False)
|
persona.pains = json.dumps(pains_list, ensure_ascii=False)
|
||||||
persona.gains = json.dumps(gains_list, ensure_ascii=False)
|
persona.gains = json.dumps(gains_list, ensure_ascii=False)
|
||||||
|
persona.description = description
|
||||||
|
persona.convincing_arguments = convincing_arguments
|
||||||
|
persona.typical_positions = typical_positions
|
||||||
|
persona.kpis = kpis
|
||||||
|
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
|
|||||||
24
inspect_persona_db.py
Normal file
24
inspect_persona_db.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
NOTION_TOKEN_FILE = "/app/notion_token.txt"
|
||||||
|
PERSONAS_DB_ID = "2e288f42-8544-8113-b878-ec99c8a02a6b"
|
||||||
|
|
||||||
|
def load_notion_token():
|
||||||
|
with open(NOTION_TOKEN_FILE, "r") as f:
|
||||||
|
return f.read().strip()
|
||||||
|
|
||||||
|
def query_notion_db(token, db_id):
|
||||||
|
url = f"https://api.notion.com/v1/databases/{db_id}/query"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Notion-Version": "2022-06-28"
|
||||||
|
}
|
||||||
|
response = requests.post(url, headers=headers)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
token = load_notion_token()
|
||||||
|
data = query_notion_db(token, PERSONAS_DB_ID)
|
||||||
|
print(json.dumps(data.get("results", [])[0], indent=2))
|
||||||
30
inspect_persona_db_v2.py
Normal file
30
inspect_persona_db_v2.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
NOTION_TOKEN_FILE = "/app/notion_token.txt"
|
||||||
|
PERSONAS_DB_ID = "30588f42-8544-80c3-8919-e22d74d945ea"
|
||||||
|
|
||||||
|
def load_notion_token():
|
||||||
|
with open(NOTION_TOKEN_FILE, "r") as f:
|
||||||
|
return f.read().strip()
|
||||||
|
|
||||||
|
def query_notion_db(token, db_id):
|
||||||
|
url = f"https://api.notion.com/v1/databases/{db_id}/query"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Notion-Version": "2022-06-28"
|
||||||
|
}
|
||||||
|
response = requests.post(url, headers=headers)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
token = load_notion_token()
|
||||||
|
data = query_notion_db(token, PERSONAS_DB_ID)
|
||||||
|
results = data.get("results", [])
|
||||||
|
for res in results:
|
||||||
|
props = res.get("properties", {})
|
||||||
|
role = "".join([t.get("plain_text", "") for t in props.get("Role", {}).get("title", [])])
|
||||||
|
print(f"Role: {role}")
|
||||||
|
print(json.dumps(props, indent=2))
|
||||||
|
print("-" * 40)
|
||||||
12
list_industries_db.py
Normal file
12
list_industries_db.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
DB_PATH = "/app/companies_v3_fixed_2.db"
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute("SELECT name FROM industries")
|
||||||
|
industries = cursor.fetchall()
|
||||||
|
print("Available Industries:")
|
||||||
|
for ind in industries:
|
||||||
|
print(f"- {ind[0]}")
|
||||||
|
conn.close()
|
||||||
30
migrate_personas_v2.py
Normal file
30
migrate_personas_v2.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
|
||||||
|
DB_PATH = "/app/companies_v3_fixed_2.db"
|
||||||
|
|
||||||
|
def migrate_personas():
|
||||||
|
print(f"Adding new columns to 'personas' table in {DB_PATH}...")
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
columns_to_add = [
|
||||||
|
("description", "TEXT"),
|
||||||
|
("convincing_arguments", "TEXT"),
|
||||||
|
("typical_positions", "TEXT"),
|
||||||
|
("kpis", "TEXT")
|
||||||
|
]
|
||||||
|
|
||||||
|
for col_name, col_type in columns_to_add:
|
||||||
|
try:
|
||||||
|
cursor.execute(f"ALTER TABLE personas ADD COLUMN {col_name} {col_type}")
|
||||||
|
print(f" Added column: {col_name}")
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
print(f" Column {col_name} already exists.")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print("Migration complete.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
migrate_personas()
|
||||||
13
verify_db.py
Normal file
13
verify_db.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
DB_PATH = "/app/companies_v3_fixed_2.db"
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT name, description, convincing_arguments FROM personas")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
print(f"Persona: {row[0]}")
|
||||||
|
print(f" Description: {row[1][:100]}...")
|
||||||
|
print(f" Convincing: {row[2][:100]}...")
|
||||||
|
print("-" * 20)
|
||||||
|
conn.close()
|
||||||
Reference in New Issue
Block a user