import os import sqlite3 import hashlib import time from datetime import datetime import pytz from superoffice_client import SuperOfficeClient from build_matrix import get_vertical_pains_gains, generate_text # Reuse logic # --- CONFIGURATION --- DB_FILE_MATRIX = "marketing_matrix.db" DB_FILE_STATE = "processing_state.db" POLLING_INTERVAL_SECONDS = 900 BUSINESS_TZ = pytz.timezone("Europe/Berlin") PROG_ID_CONTACT_VERTICAL = "SuperOffice:5" PROG_ID_PERSON_ROLE = "SuperOffice:3" PROG_ID_CONTACT_CHALLENGE = "SuperOffice:6" PROG_ID_PERSON_SUBJECT = "SuperOffice:5" PROG_ID_PERSON_INTRO = "SuperOffice:6" PROG_ID_PERSON_PROOF = "SuperOffice:7" PROG_ID_PERSON_HASH = "SuperOffice:8" # Mappings (would be better in a config file) VERTICAL_MAP = { 23: "Logistics - Warehouse", 24: "Healthcare - Hospital", 25: "Infrastructure - Transport", 26: "Leisure - Indoor Active" } ROLE_MAP = { 19: {"name": "Operativer Entscheider", "pains": "..."}, 20: {"name": "Infrastruktur-Verantwortlicher", "pains": "..."}, 21: {"name": "Wirtschaftlicher Entscheider", "pains": "..."}, 22: {"name": "Innovations-Treiber", "pains": "..."} } # --- DATABASE & STATE --- def init_state_db(): # ... (same as before) pass def process_and_update_person(client: SuperOfficeClient, person_id: int, vertical_id: int, role_id: int): print(f" -> Processing Person ID: {person_id} for V:{vertical_id}/R:{role_id}") vertical_name = VERTICAL_MAP.get(vertical_id) role_data = ROLE_MAP.get(role_id) if not vertical_name or not role_data: raise ValueError("Vertical or Role ID not in mapping.") v_data = get_vertical_pains_gains(vertical_name) # Check if text already exists in matrix conn = sqlite3.connect(DB_FILE_MATRIX) c = conn.cursor() c.execute("SELECT subject, intro, social_proof FROM text_blocks WHERE vertical_id = ? AND role_id = ?", (vertical_id, role_id)) row = c.fetchone() if not row: # If not, generate it on the fly print(" -> Text not in matrix, generating live...") text_block = generate_text(vertical_name, v_data, role_id, role_data) if not text_block: raise Exception("Failed to generate text block from Gemini.") # Save to matrix for future use subj, intro, proof = text_block['Subject'][:40], text_block['Intro'][:40], text_block['SocialProof'][:40] c.execute("INSERT OR REPLACE INTO text_blocks VALUES (?, ?, ?, ?, ?)", (vertical_id, role_id, subj, intro, proof)) conn.commit() else: subj, intro, proof = row conn.close() # Generate Hash copy_hash = hashlib.md5(f"{subj}{intro}{proof}".encode()).hexdigest() # Prepare Payloads contact_payload = {PROG_ID_CONTACT_CHALLENGE: intro} person_payload = { PROG_ID_PERSON_SUBJECT: subj, PROG_ID_PERSON_INTRO: intro, PROG_ID_PERSON_PROOF: proof, PROG_ID_PERSON_HASH: copy_hash } # Inject data person_data = client.get_person(person_id) contact_id = person_data.get('contact', {}).get('contactId') client.update_udfs("Contact", contact_id, contact_payload) client.update_udfs("Person", person_id, person_payload) return copy_hash # --- POLLING DAEMON --- def poll_for_changes(client: SuperOfficeClient, last_run_utc: str): print(f"Polling for persons modified since {last_run_utc}...") select = "personId,contact/contactId,userDefinedFields,lastModified" filter = f"lastModified gt '{last_run_utc}'" updated_persons = client.search(f"Person?$select={select}&$filter={filter}") if not updated_persons: print("No persons updated.") return print(f"Found {len(updated_persons)} updated persons.") conn_state = sqlite3.connect(DB_FILE_STATE) c_state = conn_state.cursor() for person in updated_persons: person_id = person.get('personId') try: udfs = person.get('UserDefinedFields', {}) contact_id = person.get('contact', {}).get('contactId') if not contact_id: continue contact_data = client.get_contact(contact_id) if not contact_data: continue vertical_id_raw = contact_data["UserDefinedFields"].get(PROG_ID_CONTACT_VERTICAL, "") role_id_raw = udfs.get(PROG_ID_PERSON_ROLE, "") if not vertical_id_raw or not role_id_raw: continue vertical_id = int(vertical_id_raw.replace("[I:", "").replace("]", "")) role_id = int(role_id_raw.replace("[I:", "").replace("]", "")) expected_hash = hashlib.md5(f"{vertical_id}-{role_id}".encode()).hexdigest() c_state.execute("SELECT last_known_hash FROM person_state WHERE person_id = ?", (person_id,)) result = c_state.fetchone() last_known_hash = result[0] if result else None if expected_hash != last_known_hash: new_copy_hash = process_and_update_person(client, person_id, vertical_id, role_id) c_state.execute("INSERT OR REPLACE INTO person_state VALUES (?, ?, ?)", (person_id, expected_hash, datetime.utcnow().isoformat())) conn_state.commit() else: print(f" - Skipping Person {person_id}: No relevant change (V/R hash unchanged).") except Exception as e: print(f" - ❌ Error on Person {person_id}: {e}") conn_state.close() def main(): # ... (main loop from before, but simplified) ... # Needs full implementation pass if __name__ == '__main__': # Full script would need pip install pytz flask print("This is the final blueprint for the polling daemon.") # You would start the main() loop here.