Ë
    qýuj9 ã                   óB  — d dl Z d dl mZ d dlmZ d dlZd dlmZ d dlZd dlZd dl	m
Z
 d dlZ ej                  ej                  d¬«        ej                  d«      Zd	Zd
Zdedefd„ZdPd„ZdPd„Zdefd„Zdefd„Zdefd„Zdedededefd„Zdededefd„Zdededefd„ZdQdede
e   de fd „Z!dedefd!„Z"ded"efd#„Z#ded"efd$„Z$dedefd%„Z%dRded&ed'efd(„Z&ded'efd)„Z'ded*ed+efd,„Z(ded*ed-efd.„Z)ded*efd/„Z*defd0„Z+ded*ed1ed2efd3„Z,ded*efd4„Z-defd5„Z.ded6efd7„Z/ded8efd9„Z0ded:efd;„Z1ded<edefd=„Z2dSded>ed?ed@e dAe dBe ded'efdC„Z3dTdede fdD„Z4dedEefdF„Z5defdG„Z6dedefdH„Z7dI„ Z8defdJ„Z9defdK„Z:dRdefdL„Z;defdM„Z<dRdefdN„Z=dRdefdO„Z>y)Ué    N)Úsql)ÚJson)Úsettings)ÚListz4%(asctime)s - %(name)s - %(levelname)s - %(message)s)ÚlevelÚformatÚdatabaseÚAivoraa,  
    (
        s."isActive" = TRUE
        OR LOWER(COALESCE(s."status", '')) IN ('active', 'trial', 'trialing')
        OR (
            s."trialStart" IS NOT NULL
            AND s."trialEnd" IS NOT NULL
            AND NOW() >= s."trialStart"
            AND NOW() <= s."trialEnd"
        )
    )
Úproduct_nameÚreturnc                 óN   ‡— t        | t        «      syt        ˆfd„| D «       «      S )NFc              3   ó�   •K  — | ]=  }t        |t        «      xr' |j                  d «      ‰k(  xr |j                  d«      –— Œ? y­w)ÚproductNameÚfeaturesN)Ú
isinstanceÚdictÚget)Ú.0Úproductr   s     €ú4/var/www/html/strategist-ai/app/services/database.pyú	<genexpr>z$_plan_has_product.<locals>.<genexpr>    sN   øè ø€ ò ð ô 	�7œDÓ!ò 	$Ø�K‰K˜Ó&¨,Ñ6ò	$à�K‰K˜
Ó#ó	$ñùs   ƒAA)r   ÚlistÚany)Úproduct_featuresr   s    `r   Ú_plan_has_productr      s-   ø€ ÜÐ&¬Ô-ØÜó ð (ô	ó ð ó    c                 ó0  — t        | «      D ]g  }	 t        j                  t        j                  t        j
                  t        j                  t        j                  t        j                  d¬«      }|c S  y# t        j                  $ rc}t        j                  d|dz   › d|› d|› d�«       || dz
  k  rt        j                  |«       nt        j                  dd	¬
«       |‚Y d}~ŒÜd}~wt        $ r!}t        j                  d|› �d	¬
«       |‚d}~ww xY w)z=
    Establishes a database connection with retry logic.
    é
   ©ÚhostÚportÚuserÚpasswordÚdbnameÚconnect_timeoutzDatabase connection attempt é   ú	 failed: ú. Retrying in ús...z7Max retries reached. Could not connect to the database.T©Úexc_infoNz)Unexpected error connecting to database: )ÚrangeÚpsycopg2Úconnectr   ÚDB_HOSTÚDB_PORTÚDB_USERÚDB_PASSWORDÚDB_NAMEÚOperationalErrorÚloggerÚwarningÚtimeÚsleepÚerrorÚ	Exception©ÚretriesÚdelayÚattemptÚconnÚes        r   Úget_db_connectionrA   '   s  € ô ˜“>ò ˆð	Ü×#Ñ#Ü×%Ñ%Ü×%Ñ%Ü×%Ñ%Ü!×-Ñ-Ü×'Ñ'Ø "ôˆDð ŠKñøô ×(Ñ(ò 	Ü�N‰NÐ9¸'ÀA¹+¸ÀiÐPQÈsÐR`ÐafÐ`gÐgkÐlÔmØ˜ 1™Ò$Ü—
‘
˜5Õ!ä—‘ÐVÐae�ÔfØ�ô "ûô ò 	Ü�L‰LÐDÀQÀCÐHÐSWˆLÔXØˆGûð	úó%   �A"A7Á7DÂ
AC(Ã(DÃ4DÄDc                 ó0  — t        | «      D ]g  }	 t        j                  t        j                  t        j
                  t        j                  t        j                  t        j                  d¬«      }|c S  y# t        j                  $ rc}t        j                  d|dz   › d|› d|› d�«       || dz
  k  rt        j                  |«       nt        j                  dd	¬
«       |‚Y d}~ŒÜd}~wt        $ r!}t        j                  d|› �d	¬
«       |‚d}~ww xY w)zK
    Establishes a connection to the master database with retry logic.
    r   r   zMaster DB connection attempt r&   r'   r(   r)   z>Max retries reached. Could not connect to the master database.Tr*   Nz0Unexpected error connecting to master database: )r,   r-   r.   r   r/   r0   r1   r2   ÚMASTER_DB_NAMEr4   r5   r6   r7   r8   r9   r:   r;   s        r   Úget_master_db_connectionrE   A   s  € ô ˜“>ò ˆð	Ü×#Ñ#Ü×%Ñ%Ü×%Ñ%Ü×%Ñ%Ü!×-Ñ-Ü×.Ñ.Ø "ôˆDð ŠKñøô ×(Ñ(ò 	Ü�N‰NÐ:¸7ÀQ¹;¸-ÀyÐQRÐPSÐSaÐbgÐahÐhlÐmÔnØ˜ 1™Ò$Ü—
‘
˜5Õ!ä—‘Ð]Ðhl�ÔmØ�ô "ûô ò 	Ü�L‰LÐKÈAÈ3ÐOÐZ^ˆLÔ_ØˆGûð	úrB   Ú	tenant_idc           	      óT  — | j                  dd«      j                  dd«      j                  «       s#t        j                  d| › �«       t	        d«      ‚t        j                  d| › �«       t        «       }	 |j                  «       5 }|j                  d«       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d	«      j                  t        j                  | «      «      «       |j                  t        j                  d
«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  t        j                  d «      j                  t        j                  | «      «      «       |j                  t        j                  d!«      j                  t        j                  | «      «      «       |j                  t        j                  d"«      j                  t        j                  | «      «      «       |j                  t        j                  d#«      j                  t        j                  | «      «      «       |j                  t        j                  d$«      j                  t        j                  | «      «      «       |j                  t        j                  d%«      j                  t        j                  | «      «      «       |j                  t        j                  d&«      j                  t        j                  | «      «      «       |j                  t        j                  d'«      j                  t        j                  | «      «      «       |j                  t        j                  d(«      j                  t        j                  | «      «      «       |j                  t        j                  d)«      j                  t        j                  | «      «      «       |j                  t        j                  d*«      j                  t        j                  | «      «      «       |j                  t        j                  d+«      j                  t        j                  | «      «      «       |j                  t        j                  d,«      j                  t        j                  | «      «      «       |j                  t        j                  d-«      j                  t        j                  | «      «      «       t        | |«       |j                  «        t        j                  d.| › �«       	 t        | «      }|rŒt        j                  d/| › d0�«       |j                  «       5 }t        j                  d1«      j                  t        j                  | «      «      }|j                  ||f«       d2d2d2«       |j                  «        	 d2d2d2«       |j%                  «        y5# 1 sw Y   Œ3xY w# t         $ r%}t        j#                  d3| › d4|› �«       Y d2}~ŒOd2}~ww xY w# 1 sw Y   nxY wn@# t         $ r4}|j'                  «        t        j                  d6| › d4|› �d5¬7«       |‚d2}~ww xY w	 |j%                  «        y2# |j%                  «        w xY w)8zw
    Creates the schema for the tenant if it doesn't exist and
    creates the relevant tables within that schema.
    Ú_Ú ú-zInvalid tenant_id format: zFInvalid tenant_id format. Must be alphanumeric, underscore, or hyphen.zBootstrapping tenant: z%CREATE EXTENSION IF NOT EXISTS vectorzCREATE SCHEMA IF NOT EXISTS {}aj  
                CREATE TABLE IF NOT EXISTS {}.strategist_knowledge_base (
                    id SERIAL PRIMARY KEY,
                    content TEXT NOT NULL,
                    embedding vector(3072) NOT NULL,
                    metadata JSONB,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            zALTER TABLE {}.strategist_knowledge_base ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMPz^ALTER TABLE {}.strategist_knowledge_base ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMPz_UPDATE {}.strategist_knowledge_base SET created_at = CURRENT_TIMESTAMP WHERE created_at IS NULLa>  
                CREATE TABLE IF NOT EXISTS {}.strategist_summaries (
                    id SERIAL PRIMARY KEY,
                    ingestion_type TEXT NOT NULL,
                    summary TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            zzALTER TABLE {}.strategist_summaries ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMPzYALTER TABLE {}.strategist_summaries ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMPzZUPDATE {}.strategist_summaries SET created_at = CURRENT_TIMESTAMP WHERE created_at IS NULLa	  
                CREATE TABLE IF NOT EXISTS {}.strategist_settings (
                    key TEXT PRIMARY KEY,
                    value JSONB NOT NULL,
                    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            aõ  
                CREATE TABLE IF NOT EXISTS {}.strategist_products (
                    id           SERIAL PRIMARY KEY,
                    product_key  TEXT NOT NULL UNIQUE,
                    name         TEXT NOT NULL,
                    description  TEXT,
                    image_url    TEXT,
                    product_url  TEXT NOT NULL,
                    category     TEXT,
                    ctas         JSONB NOT NULL DEFAULT '[]',
                    options      JSONB NOT NULL DEFAULT '[]',
                    raw          JSONB NOT NULL DEFAULT '{{}}',
                    related_keys TEXT[] NOT NULL DEFAULT '{{}}',
                    extracted_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            z^CREATE INDEX IF NOT EXISTS strategist_products_url_idx ON {}.strategist_products (product_url)z`CREATE INDEX IF NOT EXISTS strategist_products_category_idx ON {}.strategist_products (category)a®  
                CREATE TABLE IF NOT EXISTS {}.strategist_tickets (
                    id SERIAL PRIMARY KEY,
                    ticket_id TEXT UNIQUE NOT NULL,
                    thread_id TEXT,
                    user_name TEXT NOT NULL,
                    heading TEXT NOT NULL,
                    content TEXT NOT NULL,
                    priority TEXT NOT NULL,
                    status TEXT DEFAULT 'Open',
                    email TEXT,
                    contact_no TEXT,
                    contact_medium TEXT,
                    contact_details TEXT,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            zxALTER TABLE {}.strategist_tickets ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMPzWALTER TABLE {}.strategist_tickets ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMPzXUPDATE {}.strategist_tickets SET created_at = CURRENT_TIMESTAMP WHERE created_at IS NULLzIALTER TABLE {}.strategist_tickets ADD COLUMN IF NOT EXISTS thread_id TEXTa¢  
                CREATE TABLE IF NOT EXISTS {}.strategist_ticket_comments (
                    id SERIAL PRIMARY KEY,
                    ticket_id TEXT NOT NULL REFERENCES {}.strategist_tickets(ticket_id) ON DELETE CASCADE,
                    admin_name TEXT,
                    comment TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            z€ALTER TABLE {}.strategist_ticket_comments ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMPz_ALTER TABLE {}.strategist_ticket_comments ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMPa‡  
                CREATE TABLE IF NOT EXISTS {}.strategist_feedback (
                    id SERIAL PRIMARY KEY,
                    thread_id TEXT NOT NULL,
                    user_name TEXT,
                    question TEXT NOT NULL,
                    answer TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            zYALTER TABLE {}.strategist_feedback ADD COLUMN IF NOT EXISTS metadata JSONB DEFAULT '{{}}'a¹  
                CREATE TABLE IF NOT EXISTS {}.strategist_llm_usage (
                    id SERIAL PRIMARY KEY,
                    feature_name TEXT NOT NULL,
                    model_name TEXT NOT NULL,
                    prompt_tokens INTEGER NOT NULL,
                    completion_tokens INTEGER NOT NULL,
                    total_tokens INTEGER NOT NULL,
                    thread_id TEXT,
                    cumulative_prompt_tokens INTEGER DEFAULT 0,
                    cumulative_completion_tokens INTEGER DEFAULT 0,
                    metadata JSONB DEFAULT '{{}}',
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            zKALTER TABLE {}.strategist_llm_usage ADD COLUMN IF NOT EXISTS thread_id TEXTzgALTER TABLE {}.strategist_llm_usage ADD COLUMN IF NOT EXISTS cumulative_prompt_tokens INTEGER DEFAULT 0zkALTER TABLE {}.strategist_llm_usage ADD COLUMN IF NOT EXISTS cumulative_completion_tokens INTEGER DEFAULT 0zZALTER TABLE {}.strategist_llm_usage ADD COLUMN IF NOT EXISTS metadata JSONB DEFAULT '{{}}'zzALTER TABLE {}.strategist_llm_usage ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMPzYALTER TABLE {}.strategist_llm_usage ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMPzZUPDATE {}.strategist_llm_usage SET created_at = CURRENT_TIMESTAMP WHERE created_at IS NULLax  
                CREATE TABLE IF NOT EXISTS {}.strategist_thread_analytics (
                    thread_id TEXT PRIMARY KEY,
                    sentiment_score FLOAT,
                    intent TEXT,
                    is_high_intent BOOLEAN,
                    is_lead_qualified BOOLEAN,
                    is_resolved BOOLEAN,
                    escalation_needed BOOLEAN,
                    positive_points TEXT,
                    key_concerns TEXT,
                    pain_point TEXT,
                    feature_request TEXT,
                    objection TEXT,
                    cta_clicked BOOLEAN,
                    summary TEXT,
                    metadata JSONB,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
                    last_analyzed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            zXALTER TABLE {}.strategist_thread_analytics ADD COLUMN IF NOT EXISTS positive_points TEXTzUALTER TABLE {}.strategist_thread_analytics ADD COLUMN IF NOT EXISTS key_concerns TEXTa8  
                CREATE TABLE IF NOT EXISTS {}.strategist_crawled_urls (
                    id SERIAL PRIMARY KEY,
                    source_id TEXT NOT NULL,
                    url TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            zVCREATE INDEX IF NOT EXISTS idx_usage_created_at ON {}.strategist_llm_usage(created_at)zXCREATE INDEX IF NOT EXISTS idx_feedback_created_at ON {}.strategist_feedback(created_at)zQCREATE INDEX IF NOT EXISTS idx_usage_thread ON {}.strategist_llm_usage(thread_id)z"Successfully bootstrapped tenant: zBrand persona found for z". Storing in strategist_summaries.z¬
                            INSERT INTO {}.strategist_summaries (ingestion_type, summary)
                            VALUES ('brand_persona', %s)
                        Nz0Persona aggregation failed during bootstrap for ú: TzError bootstrapping tenant r*   )ÚreplaceÚisalnumr5   r9   Ú
ValueErrorÚinforA   ÚcursorÚexecuter   ÚSQLr   Ú
IdentifierÚensure_thread_id_columnÚcommitÚfetch_brand_personar:   r6   ÚcloseÚrollback)rF   r?   ÚcurÚpersonaÚpersona_curÚinsert_queryÚper@   s           r   Úbootstrap_tenantr^   [   sm  € ð ×Ñ˜S "Ó%×-Ñ-¨c°2Ó6×>Ñ>Ô@Ü�‰Ð1°)°Ð=Ô>ÜÐaÓbÐbä
‡K�KÐ(¨¨Ð4Ô5ÜÓ€DðqØ�[‰[‹]ð j	˜cà�K‰KÐ?Ô@ð �K‰KœŸ™Ð @ÓA×HÑHÌÏÉÐXaÓIbÓcÔdð �K‰KœŸ™ð !ó ÷ ‘œŸ™ yÓ1Ó2ô4ð �K‰KœŸ™ð  !bó  c÷  jñ  jô  kn÷  kyñ  kyð  zCó  kDó  Eô  FØ�K‰KœŸ™ð  !Aó  B÷  Iñ  Iô  JM÷  JXñ  JXð  Ybó  Jcó  dô  eØ�K‰KœŸ™ð  !Bó  C÷  Jñ  Jô  KN÷  KYñ  KYð  Zcó  Kdó  eô  fð �K‰KœŸ™ð !ó ÷ ‘œŸ™ yÓ1Ó2ô4ð �K‰KœŸ™ð  !]ó  ^÷  eñ  eô  fi÷  ftñ  ftð  u~ó  fó  @ô  AØ�K‰KœŸ™Ð {Ó|÷  Dñ  Dô  EH÷  ESñ  ESð  T]ó  E^ó  _ô  `Ø�K‰KœŸ™Ð |Ó}÷  Eñ  Eô  FI÷  FTñ  FTð  U^ó  F_ó  `ô  að �K‰KœŸ™ð !ó ÷ ‘œŸ™ yÓ1Ó2ô4ð& �K‰KœŸ™ð !ó ÷ ‘œŸ™ yÓ1Ó2ô4ð  �K‰KœŸ™ð:ó÷ ‰f”S—^‘^ IÓ.Ó/ô1ð �K‰KœŸ™ð7ó÷ ‰f”S—^‘^ IÓ.Ó/ô1ð �K‰KœŸ™ð !ó ÷  ‘œŸ™ yÓ1Ó2ô!4ð$ �K‰KœŸ™ð  ![ó  \÷  cñ  cô  dg÷  drñ  drð  s|ó  d}ó  ~ô  Ø�K‰KœŸ™Ð yÓz÷  Bñ  Bô  CF÷  CQñ  CQð  R[ó  C\ó  ]ô  ^Ø�K‰KœŸ™Ð zÓ{÷  Cñ  Cô  DG÷  DRñ  DRð  S\ó  D]ó  ^ô  _ð �K‰KœŸ™Ð kÓl×sÑsÔtw÷  uCñ  uCð  DMó  uNó  Oô  Pð �K‰KœŸ™ð !ó ÷ ‘œŸ™ yÓ1´3·>±>À)Ó3LÓMôOð �K‰KœŸ™ð  !có  d÷  kñ  kô  lo÷  lzñ  lzð  {Dó  lEó  Fô  GØ�K‰KœŸ™ð  !Bó  C÷  Jñ  Jô  KN÷  KYñ  KYð  Zcó  Kdó  eô  fð �K‰KœŸ™ð 	!ó 	÷ ‘œŸ™ yÓ1Ó2ô	4ð �K‰KœŸ™Ð {Ó|÷  Dñ  Dô  EH÷  ESñ  ESð  T]ó  E^ó  _ô  `ð �K‰KœŸ™ð !ó ÷ ‘œŸ™ yÓ1Ó2ô4ð" �K‰KœŸ™Ð mÓn×uÑuÔvy÷  wEñ  wEð  FOó  wPó  Qô  RØ�K‰KœŸ™ð  !Jó  K÷  Rñ  Rô  SV÷  Sañ  Sað  bkó  Sló  mô  nØ�K‰KœŸ™ð  !Nó  O÷  Vñ  Vô  WZ÷  Weñ  Weð  foó  Wpó  qô  rØ�K‰KœŸ™Ð |Ó}÷  Eñ  Eô  FI÷  FTñ  FTð  U^ó  F_ó  `ô  aØ�K‰KœŸ™ð  !]ó  ^÷  eñ  eô  fi÷  ftñ  ftð  u~ó  fó  @ô  AØ�K‰KœŸ™Ð {Ó|÷  Dñ  Dô  EH÷  ESñ  ESð  T]ó  E^ó  _ô  `Ø�K‰KœŸ™Ð |Ó}÷  Eñ  Eô  FI÷  FTñ  FTð  U^ó  F_ó  `ô  að �K‰KœŸ™ð !ó ÷( ‘œŸ™ yÓ1Ó2ô)4ð. �K‰KœŸ™Ð zÓ{÷  Cñ  Cô  DG÷  DRñ  DRð  S\ó  D]ó  ^ô  _Ø�K‰KœŸ™Ð wÓx×Ñô  AD÷  AOñ  AOð  PYó  AZó  [ô  \ð �K‰KœŸ™ð !ó ÷ ‘œŸ™ yÓ1Ó2ô4ð �K‰KœŸ™Ð xÓy÷  Añ  Aô  BE÷  BPñ  BPð  QZó  B[ó  \ô  ]Ø�K‰KœŸ™Ð zÓ{÷  Cñ  Cô  DG÷  DRñ  DRð  S\ó  D]ó  ^ô  _Ø�K‰KœŸ™Ð sÓt×{Ñ{Ô|÷  }Kñ  }Kð  LUó  }Vó  Wô  Xä# I¨sÔ3à�K‰KŒMÜ�K‰KÐ<¸Y¸KÐHÔIðeÜ-¨iÓ8�ÙÜ—K‘KÐ":¸9¸+ÐEgÐ hÔið Ÿ™›ð F¨+Ü'*§w¡wð 0ó (÷ $™V¤C§N¡N°9Ó$=Ó>ð %ð $×+Ñ+¨L¸7¸*ÔE÷Fð —K‘K”Mð ÷Uj	ð` 	�
‰
�÷!Fð Fûô ò eÜ—‘Ð!QÐR[ÐQ\Ð\^Ð_aÐ^bÐc×dÑdûðeú÷Oj	ð j	úð j	øôV ò Ø�‰ŒÜ�‰Ð2°9°+¸RÀ¸sÐCÈdˆÔSØˆûðúðWj	ð` 	�
‰
�øˆ�
‰
�ús†   Á7q Âj>p6í6pí<Ao9ïpïp6ï q ï9p	ï>pð	p3ðp.ð)p6ð.p3ð3p6ð6p?ð;q ñr ñ	r ñ/q;ñ;r ò r òr'c                 óä  — t        «       }	 |j                  «       5 }|j                  d| f«       |j                  «       d   s	 ddd«       |j	                  «        yt        j                  d«      j                  t        j                  | «      «      }|j                  |«       |j                  «       }|r|d   s	 ddd«       |j	                  «        y|d   }|j                  dg «      }|j                  dg «      }|D �cg c]%  }|j                  d«      sŒ|j                  d«      ‘Œ' }	}|D �
cg c]%  }
|
j                  d«      sŒ|
j                  d«      ‘Œ' }}
|	s|s	 ddd«       |j	                  «        yg }|	r#|j                  dd	j                  |	«      › �«       |r#|j                  d
d	j                  |«      › �«       dj                  |«      cddd«       |j	                  «        S c c}w c c}
w # 1 sw Y   nxY wnA# t        $ r5}t        j                  d| › d|› �«       Y d}~|j	                  «        yd}~ww xY w	 |j	                  «        y# |j	                  «        w xY w)zŠ
    Fetches the latest brand analysis result and extracts tone and voice traits.
    Returns a string summarizing the brand persona.
    zâ
                SELECT EXISTS (
                    SELECT FROM information_schema.tables 
                    WHERE table_schema = %s 
                    AND table_name = 'brandforge_analyses'
                )
            r   Nz~
                SELECT full_result FROM {}.brandforge_analyses 
                ORDER BY created_at DESC LIMIT 1
            ÚtoneÚvoiceÚtextzTone: z, zVoice: z | z!Error fetching brand persona for rK   )rA   rP   rQ   ÚfetchonerW   r   rR   r   rS   r   ÚappendÚjoinr:   r5   r9   )rF   r?   rY   ÚqueryÚrowÚfull_resultÚ	tone_listÚ
voice_listÚtÚextracted_toneÚvÚextracted_voiceÚpersona_partsr@   s                 r   rV   rV   Z  s*  € ô
 Ó€Dð,Ø�[‰[‹]ð &	-˜cà�K‰Kð ð �ôð —<‘<“> !Ò$Ø÷&	-ðV 	�
‰
�ô= —G‘Gð ó ÷ ‘œŸ™ yÓ1Ó2ð ð �K‰K˜ÔØ—,‘,“.ˆCÙ˜c !šfØ÷)&	-ðV 	�
‰
�ð+ ˜a™&ˆKØ#Ÿ™¨°Ó3ˆIØ$Ÿ™¨°"Ó5ˆJà5>ÖP°À!Ç%Á%ÈÅ-˜aŸe™e F�mÐPˆNÐPØ6@ÖR°ÀAÇEÁEÈ&ÅM˜qŸu™u V�}ÐRˆOÐRá!©/Ø÷=&	-ðV 	�
‰
�ð ˆMÙØ×$Ñ$ v¨d¯i©i¸Ó.GÐ-HÐ%IÔJÙØ×$Ñ$ w¨t¯y©y¸Ó/IÐ.JÐ%KÔLà—:‘:˜mÓ,÷M&	-ð &	-ðV 	�
‰
�ùò# QùÚR÷7&	-ð &	-úð &	-øôN ò Ü�‰Ð8¸¸À2ÀaÀSÐIÔJÛà�
‰
�ûð	úðO&	-ðV 	�
‰
�øˆ�
‰
�ús–   ŒH
 œ(G=ÁH
 ÁA G=Â=H
 Ã-G=ÄG3ÄG3Ä-G=Ä3G8Å
G8ÅG=Å$H
 Å=AG=Ç	H
 Ç3
G=Ç=HÈH
 È	I È
	IÈIÈ.I ÉIÉI ÉI/c                 ón  — 	 |j                  d| f«       |j                  «       s`t        j                  d| › d�«       |j                  t	        j
                  d«      j                  t	        j                  | «      «      «       yy# t        $ r%}t        j                  d| › d|› �«       Y d}~yd}~ww xY w)z€
    Internal helper to add the thread_id column to the tickets table if missing.
    Used for self-healing legacy tenants.
    zŽSELECT column_name FROM information_schema.columns WHERE table_schema = %s AND table_name = 'strategist_tickets' AND column_name = 'thread_id'z)Self-healing: Adding thread_id column to z.strategist_ticketsz;ALTER TABLE {}.strategist_tickets ADD COLUMN thread_id TEXTz&Failed to ensure thread_id column for rK   N)
rQ   rc   r5   rO   r   rR   r   rS   r:   r6   )rF   rY   r@   s      r   rT   rT   Ž  s³   € ð
RØ�‰ð  eð  irð  htô  	uØ�|‰|Œ~Ü�K‰KÐCÀIÀ;ÐNaÐbÔcØ�K‰KœŸ™Ð ]Ó^×eÑeÔfi×ftÑftÐu~Ófó  Aõ  Bð øô ò RÜ�‰Ð?À	¸{È"ÈQÈCÐP×QÑQûðRús   ‚BB Â	B4ÂB/Â/B4ÚcontentÚ	embeddingÚmetadatac           	      óL  — t         j                  d| › �«       ddlm} t	        |t
        «      r|j                  d«      nd }t        t        j                  |xs i «      j                  d«      «      }t        |xs dj                  d«      «      }t        |xs g «      dz  }t        d||z   |z   «      }	 || d	||	¬
«       t        «       }
	 |
j                  «       5 }t        j                  d«      j!                  t        j"                  | «      «      }|j%                  |||t        j                  |«      f«       |
j'                  «        	 ddlm} t-        | «      } || |«       	 d d d «       |
j3                  «        y# t.        $ r%}t         j1                  d| › d|› �«       Y d }~ŒCd }~ww xY w# 1 sw Y   nxY wn@# t.        $ r4}|
j5                  «        t         j7                  d| › d|› �d¬«       |‚d }~ww xY w	 |
j3                  «        y # |
j3                  «        w xY w)Nz"Inserting vector data for tenant: r   )Úcheck_quotaÚuser_idzutf-8rI   é   r&   Ústorage_vector_capacity)rv   Úrequested_amountz‰
                INSERT INTO {}.strategist_knowledge_base (content, embedding, metadata)
                VALUES (%s, %s, %s)
            ©Úlog_storage_snapshotú#Failed to log storage snapshot for rK   Tz(Failed to insert vector data for tenant r*   )r5   rO   Úapp.services.quotasru   r   r   r   ÚlenÚjsonÚdumpsÚencodeÚmaxrA   rP   r   rR   r   rS   rQ   rU   Úapp.services.firestore_servicer{   Úget_vector_db_sizer:   r6   rW   rX   r9   )rF   rq   rr   rs   ru   rv   Úmetadata_sizeÚcontent_sizeÚembedding_sizeÚprojected_row_sizer?   rY   r\   r{   Únew_sizeÚser@   s                    r   Úinsert_vector_datar‹   ›  sî  € Ü
‡K�KÐ4°Y°KÐ@ÔAõ 0Ü)3°H¼dÔ)Cˆh�l‰l˜9Ô%È€GÜœŸ
™
 8¢>¨rÓ2×9Ñ9¸'ÓBÓC€MÜ˜š 2×-Ñ-¨gÓ6Ó7€LÜ˜š bÓ)¨AÑ-€NÜ˜Q ¨}Ñ <¸~Ñ MÓNÐÙØØ!ØØ+õ	ô Ó€DðØ�[‰[‹]ð 	˜cÜŸ7™7ð $ó ÷ ‘œŸ™ yÓ1Ó2ð ð
 �K‰K˜ w°	¼4¿:¹:ÀhÓ;OÐ&PÔQØ�K‰KŒMðXÝOÜ-¨iÓ8�Ù$ Y°Ô9ð ÷#	ð. 	�
‰
�øô ò XÜ—‘Ð!DÀYÀKÈrÐRTÐQUÐV×WÑWûðXú÷	ð 	úð 	øô$ ò Ø�‰ŒÜ�‰Ð?À	¸{È"ÈQÈCÐPÐ[_ˆÔ`Øˆûðúð%	ð. 	�
‰
�øˆ�
‰
�úsm   ÃF? ÃA0F2ÅFÅ'F2Å(F? Æ	F/Æ
F*Æ%F2Æ*F/Æ/F2Æ2F;Æ7F? Æ>H Æ?	G<Ç/G7Ç7G<Ç<H ÈH#Úingestion_typeÚsummaryc                 óB  — t         j                  d| › d|› �«       t        «       }	 |j                  «       5 }t	        j
                  d«      j                  t	        j                  | «      «      }|j                  |||f«       |j                  «        	 d d d «       |j                  «        y# 1 sw Y   nxY wn@# t        $ r4}|j                  «        t         j                  d| › d|› �d¬«       |‚d }~ww xY w	 |j                  «        y # |j                  «        w xY w)NzInserting summary for tenant: ú, type: z{
                INSERT INTO {}.strategist_summaries (ingestion_type, summary)
                VALUES (%s, %s)
            Tz$Failed to insert summary for tenant rK   r*   ©r5   rO   rA   rP   r   rR   r   rS   rQ   rU   rW   r:   rX   r9   )rF   rŒ   r�   r?   rY   r\   r@   s          r   Úinsert_summaryr‘   Ç  sÿ   € Ü
‡K�KÐ0°°¸8ÀNÐCSÐTÔUÜÓ€DðØ�[‰[‹]ð 	˜cÜŸ7™7ð $ó ÷ ‘œŸ™ yÓ1Ó2ð ð
 �K‰K˜ ~°wÐ&?Ô@Ø�K‰KŒMØ÷	ð 	�
‰
�÷	ð 	úð 	øô ò Ø�‰ŒÜ�‰Ð;¸I¸;ÀbÈÈÐLÐW[ˆÔ\Øˆûðúð	ð 	�
‰
�øˆ�
‰
�úsA   §B: ·AB-ÂB: Â-B6Â2B: Â9D Â:	C7Ã/C2Ã2C7Ã7D ÄDÚ	source_idÚurlc                 óh  — t         j                  d| › d|› �«       t        «       }	 |j                  «       5 }t	        j
                  d«      j                  t	        j                  | «      «      }|j                  |||f«       |j                  «        	 d d d «       |j                  «        y# 1 sw Y   nxY wnS# t        $ rG}|j                  «        t         j                  d| › d|› �d¬«       Y d }~|j                  «        yd }~ww xY w	 |j                  «        y # |j                  «        w xY w)	Nz"Inserting crawled URL for tenant: z, source_id: zu
                INSERT INTO {}.strategist_crawled_urls (source_id, url)
                VALUES (%s, %s)
            Tz(Failed to insert crawled URL for tenant rK   r*   Fr�   )rF   r’   r“   r?   rY   r\   r@   s          r   Úinsert_crawled_urlr•   Û  s  € Ü
‡K�KÐ4°Y°K¸}ÈYÈKÐXÔYÜÓ€DðØ�[‰[‹]ð 	˜cÜŸ7™7ð $ó ÷ ‘œŸ™ yÓ1Ó2ð ð
 �K‰K˜ y°#Ð&6Ô7Ø�K‰KŒMØ÷	ð 	�
‰
�÷	ð 	úð 	øô ò Ø�‰ŒÜ�‰Ð?À	¸{È"ÈQÈCÐPÐ[_ˆÔ`Ûà�
‰
�ûðúð	ð 	�
‰
�øˆ�
‰
�úsG   §B: ·AB-ÂB: Â-B6Â2B: Â9D Â:	D
Ã-DÃ0D ÄD
Ä
D ÄD1Úquery_embeddingÚlimitc                 ó|  — t         j                  d| › �«       t        «       }	 |j                  «       5 }|j	                  d| f«       |j                  «       s4t         j                  d| › d�«       g cddd«       |j                  «        S t        j                  d«      j                  t        j                  | «      «      }ddj                  t        t        |«      «      z   d	z   }|j	                  |||f«       |j                  «       }g }|D ],  }	|j!                  |	d
   |	d   dt#        |	d   «      z
  dœ«       Œ. |cddd«       |j                  «        S # 1 sw Y   nxY wn0# t$        $ r$}
t         j'                  d| › d|
› �d¬«       |
‚d}
~
ww xY w	 |j                  «        y# |j                  «        w xY w)zL
    Performs cosine similarity search on the tenant's vector database.
    zSearching vectors for tenant: úy
                SELECT schema_name FROM information_schema.schemata 
                WHERE schema_name = %s
            úTenant schema z does not exist for search.Nz×
                SELECT content, metadata, (embedding <=> CAST(%s AS vector)) as distance
                FROM {}.strategist_knowledge_base
                ORDER BY distance ASC
                LIMIT %s
            ú[ú,ú]r   r&   é   )rq   rs   Úscorez$Failed to search vectors for tenant rK   Tr*   )r5   rO   rA   rP   rQ   rc   r6   rW   r   rR   r   rS   re   ÚmapÚstrÚfetchallrd   Úfloatr:   r9   )rF   r–   r—   r?   rY   rf   Ú
vector_strÚresultsÚsearch_hitsrg   r@   s              r   Úsearch_vector_datar§   ï  s·  € ô ‡K�KÐ0°°Ð<Ô=ÜÓ€Dð$Ø�[‰[‹]ð 	˜cà�K‰Kð ð �ôð —<‘<”>Ü—‘ °	¨{Ð:UÐVÔWØ÷	ð 	ðF 	�
‰
�ô3 —G‘Gð ó ÷
 ‘œŸ™ yÓ1Ó2ð ð ˜sŸx™x¬¬C°Ó(AÓBÑBÀSÑHˆJØ�K‰K˜ 
¨EÐ2Ô3Ø—l‘l“nˆGð ˆKØò �Ø×"Ñ"Ø" 1™vØ # A¡Ø¤ s¨1¡v£Ñ.ñ$õ ðð ÷=	ð 	ðF 	�
‰
�÷G	ð 	úð 	øô> ò Ü�‰Ð;¸I¸;ÀbÈÈÐLÐW[ˆÔ\Øˆûðúð?	ðF 	�
‰
�øˆ�
‰
�úsM   ¤E' ´>EÁ2	E' ÂB4EÅ 	E' ÅE#ÅE' Å&F) Å'	FÅ0FÆFÆF) Æ)F;c                 ó¢  — t        «       }	 |j                  «       5 }|j                  d| f«       |j                  «       d   cddd«       |j	                  «        S # 1 sw Y   nxY wnA# t
        $ r5}t        j                  d| › d|› �«       Y d}~|j	                  «        yd}~ww xY w	 |j	                  «        y# |j	                  «        w xY w)zKWhether this tenant has been bootstrapped since strategist_settings landed.útSELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = %s AND table_name = 'strategist_settings')r   Nz'Could not check the settings table for rK   F)rA   rP   rQ   rc   rW   r:   r5   r9   ©rF   r?   rY   r@   s       r   Úsettings_table_existsr«     s½   € äÓ€DðØ�[‰[‹]ð 	%˜cØ�K‰KðRà�ôð
 —<‘<“> !Ñ$÷	%ð 	%ð 	�
‰
�÷	%ð 	%úð 	%øô ò Ü�‰Ð>¸y¸kÈÈAÈ3ÐOÔPÛà�
‰
�ûð	úð	%ð 	�
‰
�øˆ�
‰
�úsF   ŒA) œ&AÁ	A) ÁA%Á!A) Á(B< Á)	B'Á2B"ÂB< Â"B'Â'B< Â<CÚkeyc           	      óž  — t        «       }	 |j                  «       5 }|j                  d| f«       |j                  «       d   s	 ddd«       |j	                  «        y|j                  t        j                  d«      j                  t        j                  | «      «      |f«       |j                  «       }|r|d   ndcddd«       |j	                  «        S # 1 sw Y   nxY wnD# t        $ r8}t        j                  d|› d| › d|› �«       Y d}~|j	                  «        yd}~ww xY w	 |j	                  «        y# |j	                  «        w xY w)z>The stored value for a settings key, or None if there is none.r©   r   Nz7SELECT value FROM {}.strategist_settings WHERE key = %szCould not read setting 'ú' for rK   )rA   rP   rQ   rc   rW   r   rR   r   rS   r:   r5   r9   )rF   r¬   r?   rY   rg   r@   s         r   Úget_settingr¯   -  s   € äÓ€DðØ�[‰[‹]ð 	+˜cØ�K‰KðRà�ôð
 —<‘<“> !Ò$Ø÷	+ð" 	�
‰
�ð �K‰KÜ—‘ÐQÓRß‘œŸ™ yÓ1Ó2°S°Fô<ð —,‘,“.ˆCÙ �3�q’6 d÷	+ð 	+ð" 	�
‰
�÷#	+ð 	+úð 	+øô ò Ü�‰Ð/°¨u°F¸9¸+ÀRÈÀsÐKÔLÛà�
‰
�ûð	úð	+ð" 	�
‰
�øˆ�
‰
�úsS   ŒC$ œ(CÁC$ ÁA CÂ=	C$ ÃC ÃC$ Ã#D: Ã$	D%Ã-D ÄD: Ä D%Ä%D: Ä:Ec                 ój  — t        «       }	 |j                  «       5 }|j                  t        j                  d«      j                  t        j                  | «      «      |t        i «      f«       |j                  t        j                  d«      j                  t        j                  | «      «      |f«       |j                  «       }|r
|d   r|d   nd} ||«      }|j                  t        j                  d«      j                  t        j                  | «      «      t        |«      |f«       ddd«       |j                  «        |j                  «        S # 1 sw Y   Œ+xY w# t        $ r/ |j                  «        t        j                  d|› d| › �d¬	«       ‚ w xY w# |j                  «        w xY w)
aç  Read-modify-write a settings key under a row lock.

    `merge` receives the currently stored value (or None) and returns the value
    to store. It runs inside the transaction that holds the lock, so two
    concurrent saves cannot each read the old value and overwrite each other --
    the second waits, then merges on top of the first one's result.

    The empty INSERT first is what makes the lock possible: SELECT FOR UPDATE
    locks nothing when the row does not exist yet.
    z\INSERT INTO {}.strategist_settings (key, value) VALUES (%s, %s) ON CONFLICT (key) DO NOTHINGzBSELECT value FROM {}.strategist_settings WHERE key = %s FOR UPDATEr   Nz[UPDATE {}.strategist_settings SET value = %s, updated_at = CURRENT_TIMESTAMP WHERE key = %szCould not save setting 'r®   Tr*   )rA   rP   rQ   r   rR   r   rS   r   rc   rU   rW   r:   rX   r5   r9   )rF   r¬   Úmerger?   rY   rg   ÚcurrentÚmergeds           r   Úupdate_settingr´   E  si  € ô Ó€DðØ�[‰[‹]ð 	I˜cØ�K‰KÜ—‘ð 7ó 8ç‘œŸ™ yÓ1Ó2°S¼$¸r»(°OôEð �K‰KÜ—‘Ð\Ó]ß‘œŸ™ yÓ1Ó2°S°Fô<ð —,‘,“.ˆCÙ #¨¨Aª�c˜!’f°DˆGá˜7“^ˆFà�K‰KÜ—‘ð Hó Iç‘œŸ™ yÓ1Ó2´T¸&³\À3Ð4GôI÷	Ið" 	�‰ŒØð 	�
‰
�÷1	Ið 	Iûô& ò Ø�‰ŒÜ�‰Ð/°¨u°F¸9¸+ÐFÐQUˆÔVØðûð
 	�
‰
�ús/   ŒE% œDEÄ/E% ÅE"ÅE% Å%8FÆF  Æ F2c                 ó  — t         j                  d| › d|› �«       t        «       }	 |j                  «       5 }|j	                  d| f«       |j                  «       s3t         j                  d| › d�«       	 d d d «       |j                  «        y t        j                  d«      j                  t        j                  | «      «      }|j	                  ||f«       |j                  «       }|r
|d   |d   fnd	cd d d «       |j                  «        S # 1 sw Y   nxY wn@# t        $ r4}t         j                  d
|› �d¬«       Y d }~|j                  «        y	d }~ww xY w	 |j                  «        y # |j                  «        w xY w)Nz$Fetching latest summary for tenant: r�   r™   rš   z does not exist.zÀ
                SELECT summary, created_at FROM {}.strategist_summaries
                WHERE ingestion_type = %s
                ORDER BY created_at DESC
                LIMIT 1
            r   r&   ©NNzError fetching summary: Tr*   )r5   rO   rA   rP   rQ   rc   r6   rW   r   rR   r   rS   r:   r9   )rF   rŒ   r?   rY   rf   Úresultr@   s          r   Úget_latest_summaryr¸   m  sb  € Ü
‡K�KÐ6°y°kÀÈ.ÐIYÐZÔ[ÜÓ€DðØ�[‰[‹]ð 	F˜cà�K‰Kð ð �ôð —<‘<”>Ü—‘ °	¨{Ð:JÐKÔLØ÷	Fð0 	�
‰
�ô —G‘Gð ó ÷
 ‘œŸ™ yÓ1Ó2ð ð �K‰K˜ Ð0Ô1Ø—\‘\“^ˆFÙ-3�F˜1‘I˜v a™yÑ)¸÷'	Fð 	Fð0 	�
‰
�÷1	Fð 	Fúð 	Føô( ò Ü�‰Ð/°¨sÐ3¸dˆÔCÛà�
‰
�ûð	úð)	Fð0 	�
‰
�øˆ�
‰
�úsS   §D ·>DÁ5D ÂA'DÃ5	D ÄDÄD ÄE. Ä	EÄ%EÄ?E. ÅEÅE. Å.F Úticket_dataÚ	thread_idc                 óV  — t         j                  d| › d|› �«       ddlm}m} t        |t        «      r|j                  d«      nd} || d|¬«       t        «       }	 |j                  «       5 }t        j                  d	«      j                  t        j                  | «      «      }	 |j                  ||d
   |d   |d   |d   |d   d|j                  d«      |j                  d«      xs |j                  d«      |j                  d«      xs |j                  d«      |f
«       |j-                  «       d   }|j+                  «        |cddd«       |j/                  «        S # t        $ �r.}	dt!        |	«      j#                  «       v �rdt!        |	«      j#                  «       v rêt         j%                  d| › d�«       |j'                  «        |j                  «       5 }
t)        | |
«       |j+                  «        ddd«       n# 1 sw Y   nxY w|j                  ||d
   |d   |d   |d   |d   d|j                  d«      |j                  d«      xs |j                  d«      |j                  d«      xs |j                  d«      |f
«       n|	‚Y d}	~	�Œqd}	~	ww xY w# 1 sw Y   nxY wnZ# |$ r ‚ t        $ rI}	|r|j'                  «        t         j1                  d| › d|	› �d¬«       Y d}	~	|j/                  «        yd}	~	ww xY w	 |j/                  «        y# |j/                  «        w xY w)zˆ
    Inserts a new support ticket into the tenant-specific table.
    Self-healing: Automatically adds thread_id column if missing.
    zCreating ticket for tenant: ú
, thread: r   )ru   ÚQuotaExceededErrorrv   NÚtotal_tickets)rv   a  
                INSERT INTO {}.strategist_tickets (ticket_id, user_name, heading, content, priority, status, email, contact_no, contact_medium, thread_id)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                RETURNING id
            Ú	ticket_idÚ	user_nameÚheadingrq   ÚpriorityÚOpenÚemailÚ
contact_noÚphone_numberÚcontact_mediumÚany_other_contact_mediumÚcolumnrº   ú"Self-healing: Adding thread_id to z during creation.z!Error creating ticket for tenant rK   Tr*   )r5   rO   r}   ru   r½   r   r   r   rA   rP   r   rR   r   rS   rQ   r:   r¡   Úlowerr6   rX   rT   rU   rc   rW   r9   )rF   r¹   rº   ru   r½   rv   r?   rY   rf   r@   Ú
repair_curÚnew_ids               r   Úcreate_ticketrÎ   ‹  sê  € ô
 ‡K�KÐ.¨y¨k¸ÀIÀ;ÐOÔP÷ DÜ,6°{ÄDÔ,Iˆk�o‰o˜iÔ(Èt€GÙ�	˜?°GÕ<äÓ€Dð8Ø�[‰[‹]ð -	˜cÜ—G‘Gð ó ÷ ‘œŸ™ yÓ1Ó2ð	 ð"Ø—‘˜EØ Ñ,Ø Ñ,Ø 	Ñ*Ø 	Ñ*Ø 
Ñ+ØØ—O‘O GÓ,Ø—O‘O LÓ1ÒT°[·_±_À^Ó5TØ—O‘OÐ$4Ó5Òd¸¿¹ÐIcÓ9dØð$ô ðF —\‘\“^ AÑ&ˆFØ�K‰KŒMØ÷[-	ð -	ðn 	�
‰
�øôG ó Øœs 1›vŸ|™|›~Ò-°+ÄÀQÃÇÁÃÑ2OÜ—N‘NÐ%GÈ	À{ÐRcÐ#dÔeØ—M‘M”OØŸ™›ð &¨*Ü/°	¸:ÔFØŸ™œ÷&÷ &ñ &úð —K‘K Ø# KÑ0Ø# KÑ0Ø# IÑ.Ø# IÑ.Ø# JÑ/ØØ#Ÿ™¨Ó0Ø#Ÿ™¨Ó5ÒX¸¿¹ÈÓ9XØ#Ÿ™Ð(8Ó9Òh¸[¿_¹_ÐMgÓ=hØ!ð(õ ð �Gõûðú÷)-	ð -	úð -	øð\ ò àÜò ÙØ�M‰MŒOÜ�‰Ð8¸¸À2ÀaÀSÐIÐTXˆÔYÛà�
‰
�ûðúðc-	ðn 	�
‰
�øˆ�
‰
�ús�   ÁJ* Á-8JÂ&A>E"Ä$$JÅ	J* Å"
JÅ,A0JÇHÇ9	JÈHÈBJÊJÊJÊJÊJ&Ê"J* Ê)L Ê*LÊ8/K<Ë'L Ë<LÌL ÌL(c                 óP  — t         j                  d|› d| › �«       t        «       }	 |j                  «       5 }t	        j
                  d«      j                  t	        j                  | «      «      }	 |j                  ||f«       |j!                  «       }|r4|d	   |d
   |d   |d   |d   |d   dœcddd«       |j#                  «        S 	 ddd«       |j#                  «        y# t        $ rÂ}dt        |«      j                  «       v ršdt        |«      j                  «       v rt         j                  d| › d�«       |j                  «        |j                  «       5 }t        | |«       |j                  «        ddd«       n# 1 sw Y   nxY w|j                  ||f«       n|‚Y d}~�Œ&d}~ww xY w# 1 sw Y   nxY wnC# t        $ r7}t         j%                  d|› d|› �d¬«       Y d}~|j#                  «        yd}~ww xY w	 |j#                  «        y# |j#                  «        w xY w)z…
    Finds an existing ticket for a given conversation thread.
    Self-healing: Automatically adds thread_id column if missing.
    zGetting ticket for thread ú in tenant zpSELECT id, ticket_id, heading, content, priority, status FROM {}.strategist_tickets WHERE thread_id = %s LIMIT 1rÉ   rº   rÊ   z during fetch-by-thread.Nr   r&   rž   é   é   é   )Úidr¿   rÁ   rq   rÂ   Ústatusz Error fetching ticket by thread rK   Tr*   )r5   rO   rA   rP   r   rR   r   rS   rQ   r:   r¡   rË   r6   rX   rT   rU   rc   rW   r9   )rF   rº   r?   rY   rf   r@   rÌ   rg   s           r   Úget_ticket_by_threadrÖ   Ò  s  € ô
 ‡K�KÐ,¨Y¨K°{À9À+ÐNÔOÜÓ€Dð Ø�[‰[‹]ð 	˜cÜ—G‘Gð  Oó  P÷  Wñ  Wô  X[÷  Xfñ  Xfð  gpó  Xqó  rˆEðØ—‘˜E I <Ô0ð —,‘,“.ˆCÙà˜a™&Ø!$ Q¡Ø" 1™vØ" 1™vØ # A¡Ø! !™fñ÷%	ð 	ð> 	�
‰
�ð ÷5	ð> 	�
‰
�øô7 ò 
Øœs 1›vŸ|™|›~Ñ-°+ÄÀQÃÇÁÃÑ2OÜ—N‘NÐ%GÈ	À{ÐRjÐ#kÔlØ—M‘M”OØŸ™›ð &¨*Ü/°	¸:ÔFØŸ™œ÷&÷ &ñ &úð —K‘K ¨	 |Õ4à�Gõ 5ûð
ú÷		ð 	úð 	øô6 ò Ü�‰Ð7¸	°{À"ÀQÀCÐHÐSWˆÔXÛà�
‰
�ûð	úð7	ð> 	�
‰
�øˆ�
‰
�ús•   §F> ·8F1Á0C#Â,F1Â/	F> Ã	F1Ã
F> Ã#	F.Ã,A/F)ÅFÅ8	F)ÆF
ÆF)Æ#F1Æ)F.Æ.F1Æ1F:Æ6F> Æ=H Æ>	G>ÇG9Ç$H Ç9G>Ç>H ÈH%r¿   Úupdate_datac                 ó~  — t         j                  d|› d| › �«       t        «       }	 |j                  «       5 }t	        j
                  d«      j                  t	        j                  | «      «      }|j                  ||d   |d   |d   |f«       |j                  «        	 ddd«       |j                  «        y# 1 sw Y   nxY wnS# t        $ rG}t         j                  d	|› d
|› �d¬«       |j                  «        Y d}~|j                  «        yd}~ww xY w	 |j                  «        y# |j                  «        w xY w)z6
    Updates an existing ticket with new details.
    zUpdating ticket ú for tenant: zÅ
                UPDATE {}.strategist_tickets
                SET heading = %s,
                    content = %s,
                    priority = %s
                WHERE ticket_id = %s
            rÁ   rq   rÂ   NTzFailed to update ticket rK   r*   F)r5   rO   rA   rP   r   rR   r   rS   rQ   rU   rW   r:   r9   rX   )rF   r¿   r×   r?   rY   rf   r@   s          r   Úupdate_ticketrÚ   û  s#  € ô ‡K�KÐ" 9 +¨]¸9¸+ÐFÔGÜÓ€DðØ�[‰[‹]ð 	˜cÜ—G‘Gð ó ÷ ‘œŸ™ yÓ1Ó2ð ð �K‰K˜Ø˜IÑ&Ø˜IÑ&Ø˜JÑ'Øð	 ô ð �K‰KŒMØ÷!	ð, 	�
‰
�÷-	ð 	úð 	øô" ò Ü�‰Ð/°	¨{¸"¸Q¸CÐ@È4ˆÔPØ�‰ŒÛà�
‰
�ûðúð#	ð, 	�
‰
�øˆ�
‰
�úsG   §C ·A(B8ÂC Â8CÂ=C ÃD* Ã	DÃ-DÃ;D* ÄDÄD* Ä*D<rÕ   c                 óŠ  — t         j                  d|› d|› d| › �«       t        «       }	 |j                  «       5 }t	        j
                  d«      j                  t	        j                  | «      «      }|j                  |||f«       |j                  «        |j                  dkD  cddd«       |j                  «        S # 1 sw Y   nxY wnS# t        $ rG}t         j                  d|› d|› �d	¬
«       |j                  «        Y d}~|j                  «        yd}~ww xY w	 |j                  «        y# |j                  «        w xY w)z3
    Updates the status of an existing ticket.
    zUpdating status of ticket z to 'z' for tenant: z
                UPDATE {}.strategist_tickets
                SET status = %s
                WHERE ticket_id = %s
            r   Nz#Failed to update status for ticket rK   Tr*   F)r5   rO   rA   rP   r   rR   r   rS   rQ   rU   ÚrowcountrW   r:   r9   rX   )rF   r¿   rÕ   r?   rY   rf   r@   s          r   Úupdate_ticket_statusrÝ     s  € ô ‡K�KÐ,¨Y¨K°u¸V¸HÀNÐS\ÐR]Ð^Ô_ÜÓ€DðØ�[‰[‹]ð 		$˜cÜ—G‘Gð ó ÷ ‘œŸ™ yÓ1Ó2ð	 ð �K‰K˜ ¨	Ð2Ô3Ø�K‰KŒMØ—<‘< !Ñ#÷		$ð 		$ð 	�
‰
�÷		$ð 		$úð 		$øô ò Ü�‰Ð:¸9¸+ÀRÈÀsÐKÐVZˆÔ[Ø�‰ŒÛà�
‰
�ûðúð		$ð 	�
‰
�øˆ�
‰
�úsG   ªC ºA*B>Â$	C Â>CÃC Ã
D0 Ã	DÃ-DÄD0 ÄDÄD0 Ä0Ec                 ó”  — t         j                  d|› d| › �«       t        «       }	 |j                  «       5 }|j	                  d| f«       |j                  «       s	 ddd«       |j                  «        yt        j                  d«      j                  t        j                  | «      «      }|j	                  ||f«       |j                  «       }|re|d   |d   |d   |d	   |d
   |d   |d   |d   |d   |d   |d   |d   r|d   j                  «       nd|d   dœcddd«       |j                  «        S 	 ddd«       |j                  «        y# 1 sw Y   nxY wnF# t        $ r:}t         j                  d|› d| › d|› �d¬«       Y d}~|j                  «        yd}~ww xY w	 |j                  «        y# |j                  «        w xY w)z5
    Retrieves a single ticket by its ticket_id.
    zFetching ticket rÙ   úJSELECT schema_name FROM information_schema.schemata WHERE schema_name = %sNz¾SELECT id, ticket_id, user_name, heading, content, priority, status, email, contact_no, contact_medium, contact_details, created_at, thread_id FROM {}.strategist_tickets WHERE ticket_id = %sr   r&   rž   rÑ   rÒ   rÓ   é   é   rw   é	   r   é   é   ©rÔ   r¿   rÀ   rÁ   rq   rÂ   rÕ   rÄ   rÅ   rÇ   Úcontact_detailsÚ
created_atrº   zError fetching ticket ú for tenant rK   Tr*   )r5   rO   rA   rP   rQ   rc   rW   r   rR   r   rS   Ú	isoformatr:   r9   )rF   r¿   r?   rY   rf   rg   r@   s          r   Ú
get_ticketrê   2  sÞ  € ô ‡K�KÐ" 9 +¨]¸9¸+ÐFÔGÜÓ€Dð Ø�[‰[‹]ð 	˜cà�K‰KÐdÐgpÐfrÔsØ—<‘<”>Ø÷		ð> 	�
‰
�ô3 —G‘Gð  ]ó  ^÷  eñ  eô  fi÷  ftñ  ftð  u~ó  fó  @ˆEØ�K‰K˜ 	˜|Ô,Ø—,‘,“.ˆCáà˜a™&Ø!$ Q¡Ø!$ Q¡Ø" 1™vØ" 1™vØ # A¡Ø! !™fØ  ™VØ"% a¡&Ø&)¨!¡fØ'*¨2¡wØ9<¸Rº # b¡'×"3Ñ"3Ô"5ÀdØ!$ R¡ñ÷	ð 	ð> 	�
‰
�ð ÷5	ð> 	�
‰
�÷?	ð 	úð 	øô6 ò Ü�‰Ð-¨i¨[¸ÀYÀKÈrÐRSÐQTÐUÐ`dˆÔeÛà�
‰
�ûð	úð7	ð> 	�
‰
�øˆ�
‰
�ús_   §E ·%EÁE Á5B'EÄ	E Ä6EÄ7E ÅEÅE ÅF5 Å	F Å& FÆF5 ÆF Æ F5 Æ5Gc                 ó   — t         j                  d| › �«       t        «       }	 |j                  «       5 }|j	                  d| f«       |j                  «       sg cddd«       |j                  «        S 	 t        j                  d«      j                  t        j                  | «      «      }|j	                  |«       |j%                  «       }g }|D ]]  }|j'                  |d	   |d
   |d   |d   |d   |d   |d   |d   |d   |d   |d   |d   r|d   j)                  «       nd|d   dœ«       Œ_ |cddd«       |j                  «        S # t        $ rÀ}dt        |«      j                  «       v r˜dt        |«      j                  «       v r}t         j                  d| › d�«       |j                  «        |j                  «       5 }t!        | |«       |j#                  «        ddd«       n# 1 sw Y   nxY w|j	                  «       n|‚Y d}~�ŒSd}~ww xY w# 1 sw Y   nxY wnE# t        $ r9}t         j+                  d| › d|› �d¬«       g cY d}~|j                  «        S d}~ww xY w	 |j                  «        y# |j                  «        w xY w)z—
    Retrieves all tickets for a specific tenant.
    Self-healing: If thread_id is missing (legacy tenant), it applies the migration and retries.
    z!Fetching all tickets for tenant: rß   NzÂSELECT id, ticket_id, user_name, heading, content, priority, status, email, contact_no, contact_medium, contact_details, created_at, thread_id FROM {}.strategist_tickets ORDER BY created_at DESCrÉ   rº   zDetected missing thread_id in z. Applying migration...r   r&   rž   rÑ   rÒ   rÓ   rà   rá   rw   râ   r   rã   rä   rå   z"Error fetching tickets for tenant rK   Tr*   )r5   rO   rA   rP   rQ   rc   rW   r   rR   r   rS   r:   r¡   rË   r6   rX   rT   rU   r¢   rd   ré   r9   )	rF   r?   rY   rf   r@   rÌ   ÚrowsÚticketsrg   s	            r   Úget_ticketsrî   Z  s�  € ô
 ‡K�KÐ3°I°;Ð?Ô@ÜÓ€Dð.Ø�[‰[‹]ð (	˜cà�K‰KÐdÐgpÐfrÔsØ—<‘<”>Ø÷	(	ð (	ðZ 	�
‰
�ðOÜŸ™ð  !eó  f÷  mñ  mô  nq÷  n|ñ  n|ð  }Fó  nGó  H�Ø—‘˜EÔ"ð —<‘<“>ˆDØˆGØò �Ø—‘Ø˜a™&Ø!$ Q¡Ø!$ Q¡Ø" 1™vØ" 1™vØ # A¡Ø! !™fØ  ™VØ"% a¡&Ø&)¨!¡fØ'*¨2¡wØ9<¸Rº # b¡'×"3Ñ"3Ô"5ÀdØ!$ R¡ñ õ ðð  ÷Q(	ð (	ðZ 	�
‰
�øôI ò àœs 1›vŸ|™|›~Ñ-°+ÄÀQÃÇÁÃÑ2OÜ—N‘NÐ%CÀIÀ;ÐNeÐ#fÔgØ—M‘M”OØŸ™›ð &¨*Ü/°	¸:ÔFØŸ™œ÷&÷ &ñ &úð —K‘K Õ&à�Gõ 'ûðú÷(	ð (	úð (	øôR ò Ü�‰Ð9¸)¸ÀBÀqÀcÐJÐUYˆÔZØŒ	à�
‰
�ûð	úðS(	ðZ 	�
‰
�øˆ�
‰
�ús—   ¤H$ ´%HÁ	H$ Á4AEÂ<A5HÄ1	H$ Å	HÅA/HÇG)Ç 	HÇ)G2Ç.HÈ	HÈHÈHÈH ÈH$ È#I; È$	I&È-I!ÉI&ÉI; É!I&É&I; É;JÚ
admin_nameÚcommentc           	      ó®  — t         j                  d|› d| › �«       t        «       }	 |j                  «       5 }t	        j
                  d«      j                  t	        j                  | «      «      }|j                  ||||f«       |j                  «       }|j                  «        |d   ||||d   r|d   j                  «       nddœcddd«       |j                  «        S # 1 sw Y   nxY wnC# t        $ r7}t         j                  d|› d	| › d
|› �d¬«       |j                  «        |‚d}~ww xY w	 |j                  «        y# |j                  «        w xY w)z6
    Inserts a new comment for a specific ticket.
    zAdding comment to ticket rÙ   zµ
                INSERT INTO {}.strategist_ticket_comments (ticket_id, admin_name, comment)
                VALUES (%s, %s, %s)
                RETURNING id, created_at
            r   r&   N©rÔ   r¿   rï   rð   rç   z Failed to add comment to ticket rè   rK   Tr*   )r5   rO   rA   rP   r   rR   r   rS   rQ   rc   rU   ré   rW   r:   r9   rX   )	rF   r¿   rï   rð   r?   rY   rf   r·   r@   s	            r   Úadd_ticket_commentró   ‘  sE  € ô ‡K�KÐ+¨I¨;°mÀIÀ;ÐOÔPÜÓ€DðØ�[‰[‹]ð 	˜cÜ—G‘Gð ó ÷ ‘œŸ™ yÓ1Ó2ð	 ð �K‰K˜ 	¨:°wÐ?Ô@Ø—\‘\“^ˆFØ�K‰KŒMà˜Q‘iØ&Ø(Ø"Ø7=¸a²y˜f Q™i×1Ñ1Ô3Àdñ÷	ð 	ð, 	�
‰
�÷-	ð 	úð 	øô" ò Ü�‰Ð7¸	°{À,ÈyÈkÐY[Ð\]Ð[^Ð_ÐjnˆÔoØ�‰ŒØˆûðúð#	ð, 	�
‰
�øˆ�
‰
�úsA   §C- ·BC Ã	C- Ã C)Ã%C- Ã,E Ã-	D-Ã62D(Ä(D-Ä-E ÅEc           
      óV  — t         j                  d|› d| › �«       t        «       }	 |j                  «       5 }|j	                  d| f«       |j                  «       sg cddd«       |j                  «        S t        j                  d«      j                  t        j                  | «      «      }|j	                  ||f«       |j                  «       }g }|D ]=  }|j                  |d   |d   |d   |d	   |d
   r|d
   j                  «       nddœ«       Œ? |cddd«       |j                  «        S # 1 sw Y   nxY wnH# t        $ r<}t         j                  d|› d| › d|› �d¬«       g cY d}~|j                  «        S d}~ww xY w	 |j                  «        y# |j                  «        w xY w)z7
    Retrieves all comments for a specific ticket.
    zFetching comments for ticket z in tenant: rß   Nz…SELECT id, ticket_id, admin_name, comment, created_at FROM {}.strategist_ticket_comments WHERE ticket_id = %s ORDER BY created_at ASCr   r&   rž   rÑ   rÒ   rò   z#Error fetching comments for ticket rÐ   rK   Tr*   )r5   rO   rA   rP   rQ   rc   rW   r   rR   r   rS   r¢   rd   ré   r:   r9   )	rF   r¿   r?   rY   rf   rì   Úcommentsrg   r@   s	            r   Úget_ticket_commentsrö   °  s½  € ô ‡K�KÐ/°	¨{¸,ÀyÀkÐRÔSÜÓ€DðØ�[‰[‹]ð 	˜cà�K‰KÐdÐgpÐfrÔsØ—<‘<”>Ø÷		ð 	ð0 	�
‰
�ô% —G‘Gð  dó  e÷  lñ  lô  mp÷  m{ñ  m{ð  |Eó  mFó  GˆEØ�K‰K˜ 	˜|Ô,Ø—<‘<“>ˆDàˆHØò �Ø—‘Ø˜a™&Ø!$ Q¡Ø"% a¡&Ø" 1™vØ8;¸Aº # a¡&×"2Ñ"2Ô"4ÀDñ!õ ðð ÷'	ð 	ð0 	�
‰
�÷1	ð 	úð 	øô( ò Ü�‰Ð:¸9¸+À[ÐQZÐP[Ð[]Ð^_Ð]`ÐaÐlpˆÔqØŒ	à�
‰
�ûð	úð)	ð0 	�
‰
�øˆ�
‰
�úsY   §D< ·%D/Á	D< Á6BD/Ä	D< Ä/D8Ä4D< Ä;F Ä<	FÅ!E<Å&FÅ'F Å<FÆF ÆF(c                 óú  — t         j                  d| › �«       t        «       }	 |j                  «       5 }|j	                  d| f«       |j                  «       si cddd«       |j                  «        S t        j                  d«      j                  t        j                  | «      «      }|j	                  |«       |j                  «       }|D �ci c]  }|d   xs d|d   “Œ c}cddd«       |j                  «        S c c}w # 1 sw Y   nxY wnE# t        $ r9}t         j                  d| › d	|› �d
¬«       i cY d}~|j                  «        S d}~ww xY w	 |j                  «        y# |j                  «        w xY w)z=
    Returns a dictionary of status counts for a tenant.
    z*Fetching ticket status counts for tenant: rß   NzBSELECT status, COUNT(*) FROM {}.strategist_tickets GROUP BY statusr   ÚUnknownr&   z/Error fetching ticket status counts for tenant rK   Tr*   )r5   rO   rA   rP   rQ   rc   rW   r   rR   r   rS   r¢   r:   r9   )rF   r?   rY   rf   rì   rg   r@   s          r   Úget_ticket_status_countsrù   Ñ  sd  € ô ‡K�KÐ<¸Y¸KÐHÔIÜÓ€DðØ�[‰[‹]ð 
	A˜cà�K‰KÐdÐgpÐfrÔsØ—<‘<”>Ø÷	
	Að 
	Að 	�
‰
�ô —G‘GÐ`Óa×hÑhÔil×iwÑiwð  yBó  jCó  DˆEØ�K‰K˜ÔØ—<‘<“>ˆDà;?Ö@°C�C˜‘FÒ'˜i¨¨Q©Ñ/Ò@÷
	Að 
	Að 	�
‰
�ùò A÷
	Að 
	Aúð 
	Aøô ò Ü�‰ÐFÀyÀkÐQSÐTUÐSVÐWÐbfˆÔgØŒ	à�
‰
�ûð	úð
	Að 	�
‰
�øˆ�
‰
�úsk   ¤D ´%DÁ	D Á3ADÃC?Ã#DÃ%	D Ã?DÄDÄ	D ÄE( Ä	EÄEÄ8EÄ9E( ÅEÅE( Å(E:Úfeedback_datac                 óà  — t         j                  d| › �«       t        «       }	 |j                  «       5 }t	        j
                  d«      j                  t	        j                  | «      «      }|j                  ||d   |j                  d«      |d   |d   t        j                  |j                  di «      «      f«       |j                  «       d   }|j                  «        |cd	d	d	«       |j                  «        S # 1 sw Y   nxY wn@# t        $ r4}t         j!                  d
| › d|› �d¬«       |j#                  «        |‚d	}~ww xY w	 |j                  «        y	# |j                  «        w xY w)zL
    Inserts user feedback into the tenant's strategist_feedback table.
    zInserting feedback for tenant: z¼
                INSERT INTO {}.strategist_feedback (thread_id, user_name, question, answer, metadata)
                VALUES (%s, %s, %s, %s, %s)
                RETURNING id
            rº   rÀ   ÚquestionÚanswerrs   r   Nz%Failed to insert feedback for tenant rK   Tr*   )r5   rO   rA   rP   r   rR   r   rS   rQ   r   r   r€   rc   rU   rW   r:   r9   rX   )rF   rú   r?   rY   rf   Úfeedback_db_idr@   s          r   Úinsert_feedbackrÿ   é  sP  € ô ‡K�KÐ1°)°Ð=Ô>ÜÓ€DðØ�[‰[‹]ð 	"˜cÜ—G‘Gð ó ÷ ‘œŸ™ yÓ1Ó2ð	 ð �K‰K˜Ø˜kÑ*Ø×!Ñ! +Ó.Ø˜jÑ)Ø˜hÑ'Ü—
‘
˜=×,Ñ,¨Z¸Ó<Ó=ð ô ð !Ÿ\™\›^¨AÑ.ˆNØ�K‰KŒMØ!÷!	"ð 	"ð, 	�
‰
�÷-	"ð 	"úð 	"øô" ò Ü�‰Ð<¸Y¸KÀrÈ!ÈÐMÐX\ˆÔ]Ø�‰ŒØˆûðúð#	"ð, 	�
‰
�øˆ�
‰
�úsA   ¤D	 ´B.C<Ã"	D	 Ã<DÄD	 ÄE Ä		EÄ/EÅEÅE ÅE-Úsourcec           	      óF  — t         j                  d| › d|› �«       t        «       }	 |j                  «       5 }|j	                  d| f«       |j                  «       s	 ddd«       |j                  «        yt        j                  d«      j                  t        j                  | «      «      }|j	                  ||f«       |j                  }t        j                  d«      j                  t        j                  | «      «      }|j	                  ||f«       |j                  «        t         j                  d|› d	| › d|› �«       |dkD  r	 dd
lm} t        | «      } || |«       |cddd«       |j                  «        S # t         $ r%}	t         j#                  d| › d|	› �«       Y d}	~	ŒDd}	~	ww xY w# 1 sw Y   nxY wn@# t         $ r4}
t         j%                  d| › d|
› �d¬«       |j'                  «        |
‚d}
~
ww xY w	 |j                  «        y# |j                  «        w xY w)zC
    Deletes all vector data for a specific tenant and source.
    z!Deleting vector data for tenant: z
, source: rß   Nr   zx
                DELETE FROM {}.strategist_knowledge_base
                WHERE metadata->>'source_id' = %s
            zi
                DELETE FROM {}.strategist_crawled_urls
                WHERE source_id = %s
            úDeleted ú) rows from strategist_knowledge_base for rz   r|   z after deletion: z(Failed to delete vector data for tenant rK   Tr*   )r5   rO   rA   rP   rQ   rc   rW   r   rR   r   rS   rÜ   rU   rƒ   r{   r„   r:   r6   r9   rX   )rF   r   r?   rY   Údelete_queryÚdeleted_countÚdelete_crawled_queryr{   r‰   rŠ   r@   s              r   Údelete_vector_data_by_sourcer    s  € ô ‡K�KÐ3°I°;¸jÈÈÐQÔRÜÓ€Dð)Ø�[‰[‹]ð "	!˜cà�K‰KÐdÐgpÐfrÔsØ—<‘<”>Ø÷	"	!ðP 	�
‰
�ôC Ÿ7™7ð $ó ÷ ‘œŸ™ yÓ1Ó2ð ð
 �K‰K˜ v iÔ0ØŸL™LˆMô $'§7¡7ð ,ó $÷ ‘œŸ™ yÓ1Ó2ð !ð �K‰KÐ,¨v¨iÔ8à�K‰KŒMÜ�K‰K˜( = /Ð1ZÐ[dÐZeÐeoÐpvÐowÐxÔyð ˜qÒ ðkÝSÜ1°)Ó<�HÙ(¨°HÔ=ð !÷E"	!ð "	!ðP 	�
‰
�øô !ò kÜ—N‘NÐ%HÈÈÐSdÐegÐdhÐ#i×jÑjûðkú÷?"	!ð "	!úð "	!øôF ò Ü�‰Ð?À	¸{È"ÈQÈCÐPÐ[_ˆÔ`Ø�‰ŒØˆûðúðG"	!ðP 	�
‰
�øˆ�
‰
�úsw   §F< ·%F/ÁF< Á5CF/Å	E>Å#F/Å$	F< Å>	F,ÆF'Æ"F/Æ'F,Æ,F/Æ/F8Æ4F< Æ;H Æ<	G9Ç/G4Ç4G9Ç9H ÈH Úbase_urlc                 ón  — t         j                  d| › d|› �«       t        «       }	 |j                  «       5 }|j	                  d«      }t        j                  d«      j                  t        j                  | «      «      }|j                  |||dz   f«       |j                  }t        j                  d«      j                  t        j                  | «      «      }|j                  |||dz   f«       |j                  «        t         j                  d|› d|› d	�«       |cd
d
d
«       |r|j                  «        S S # 1 sw Y   nxY wnB# t        $ r6}|r|j                  «        t         j                  d|› d|› �d¬«       |‚d
}~ww xY w	 |r|j                  «        y
y
# |r|j                  «        w w xY w)zš
    Deletes all vector data and crawled URLs for a base URL and its sub-paths.
    This is used to implement 'replacement' logic during re-analysis.
    z2Deleting vector data and crawled URLs for tenant: z, base_url: ú/zÒ
                DELETE FROM {}.strategist_knowledge_base
                WHERE metadata->>'ingestion_type' = 'website'
                AND (metadata->>'source' = %s OR metadata->>'source' LIKE %s)
            z/%zt
                DELETE FROM {}.strategist_crawled_urls
                WHERE (url = %s OR url LIKE %s)
            r  r  z and its sub-paths.Nz Failed to delete by subpath for rK   Tr*   )r5   rO   rA   rP   Úrstripr   rR   r   rS   rQ   rÜ   rU   rW   r:   rX   r9   )	rF   r  r?   rY   Úurl_patternÚdelete_kb_queryÚkb_countÚdelete_urls_queryr@   s	            r   Ú!delete_vector_data_by_url_subpathr  9  sš  € ô
 ‡K�KÐDÀYÀKÈ|Ð\dÐ[eÐfÔgÜÓ€DðØ�[‰[‹]ð 	˜cà"Ÿ/™/¨#Ó.ˆKô "Ÿg™gð 'ó ÷ ‘œŸ™ yÓ1Ó2ð	 ð �K‰K˜¨+°{ÀTÑ7IÐ)JÔKØ—|‘|ˆHô !$§¡ð )ó !÷ ‘œŸ™ yÓ1Ó2ð ð
 �K‰KÐ)¨K¸ÀtÑ9KÐ+LÔMà�K‰KŒMÜ�K‰K˜( 8 *Ð,UÐV^ÐU_Ð_rÐsÔtØ÷1	ð 	ñ< �—‘•ˆ4÷=	ð 	úð 	øô2 ò Ù�—‘”Ü�‰Ð7¸°zÀÀAÀ3ÐGÐRVˆÔWØˆûðúð3	ñ< �—‘•ˆ4ø‰4�—‘•ˆ4úsA   §E ·C'D;Ä	E Ä;EÅ E ÅF Å	FÅ1FÆFÆF ÆF4Úsource_namec                 óÄ  — t        «       }	 |j                  «       5 }|j                  d| f«       |j                  «       sg cddd«       |j	                  «        S t        j                  d«      j                  t        j                  | «      «      }|j                  |||f«       |j                  «       D �cg c]  }|d   sŒ	|d   ‘Œ c}cddd«       |j	                  «        S c c}w # 1 sw Y   nxY wnC# t        $ r7}t        j                  d|› d|› �«       g cY d}~|j	                  «        S d}~ww xY w	 |j	                  «        y# |j	                  «        w xY w)z}
    Returns a list of source_ids matching a specific source name and type.
    Used for implementing replacement logic.
    rß   NzÊ
                SELECT DISTINCT metadata->>'source_id'
                FROM {}.strategist_knowledge_base
                WHERE metadata->>'source' = %s AND metadata->>'ingestion_type' = %s
            r   z&Error fetching source_ids by name for rK   )rA   rP   rQ   rc   rW   r   rR   r   rS   r¢   r:   r5   r9   )rF   r  rŒ   r?   rY   rf   rg   r@   s           r   Úget_source_ids_by_namer  a  s=  € ô
 Ó€DðØ�[‰[‹]ð 	@˜cà�K‰KÐdÐgpÐfrÔsØ—<‘<”>Ø÷		@ð 	@ð" 	�
‰
�ô —G‘Gð ó ÷ ‘œŸ™ yÓ1Ó2ð	 ð
 �K‰K˜ ¨^Ð<Ô=Ø&)§l¡l£nÖ?˜s¸¸A»�C˜“FÒ?÷	@ð 	@ð" 	�
‰
�ùò @÷	@ð 	@úð 	@øô ò Ü�‰Ð=¸k¸]È"ÈQÈCÐPÔQØŒ	à�
‰
�ûð	úð	@ð" 	�
‰
�øˆ�
‰
�úsq   ŒC8 œ%C+Á	C8 ÁAC+Â8
C&ÃC&Ã
C+Ã	C8 Ã&C+Ã+C4Ã0C8 Ã7E Ã8	D8ÄD3ÄD8ÄE Ä3D8Ä8E ÅEÚfeature_nameÚ
model_nameÚprompt_tokensÚcompletion_tokensÚtotal_tokensc                 ó*  — t         j                  d| › d|› d|› �«       |s|r|j                  d«      }t        «       }	 |j	                  «       5 }	|	j                  d| f«       |	j                  «       d   s5t         j                  d| › �«       	 ddd«       |r|j                  «        y	y	d}
d}|rbt        j                  d
«      j                  t        j                  | «      «      }|	j                  |||f«       |	j                  «       }|r|\  }
}||
k  r|}|}n
||
z
  }||z
  }||z   }t        d|«      }t        d|«      }t        d|«      }t        j                  d«      j                  t        j                  | «      «      }|	j                  |||||||||t        j                  |xs i «      f	«       |j!                  «        t         j                  d|› d|› d|› d�«       	 ddd«       |r|j                  «        yy# 1 sw Y   nxY wnV# t"        $ rJ}|r|j%                  «        t         j'                  d| › d|› �«       Y d}~|r|j                  «        y	y	d}~ww xY w	 |r|j                  «        yy# |r|j                  «        w w xY w)z¢
    Persists LLM token usage data into the tenant's strategist_llm_usage table.
    Implements Delta-based tracking to avoid double-counting thread history.
    z#[DB] Logging LLM usage for tenant: z, feature: r¼   rº   zã
                SELECT EXISTS (
                    SELECT FROM information_schema.tables 
                    WHERE table_schema = %s 
                    AND table_name = 'strategist_llm_usage'
                )
            r   zA[DB] Skipping usage log: Table strategist_llm_usage not found in NFa
  
                    SELECT cumulative_prompt_tokens, cumulative_completion_tokens 
                    FROM {}.strategist_llm_usage 
                    WHERE thread_id = %s AND feature_name = %s
                    ORDER BY created_at DESC LIMIT 1
                a—  
                INSERT INTO {}.strategist_llm_usage (
                    feature_name, model_name, 
                    prompt_tokens, completion_tokens, total_tokens,
                    thread_id, cumulative_prompt_tokens, cumulative_completion_tokens,
                    metadata, created_at
                )
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, CURRENT_TIMESTAMP)
            z[DB] Logged Delta: z tokens (Thread: z, Feature: ú)Tz#Failed to log LLM usage for tenant rK   )r5   rO   r   rA   rP   rQ   rc   r6   rW   r   rR   r   rS   r‚   r   r€   rU   r:   rX   r9   )rF   r  r  r  r  r  rs   rº   r?   rY   Úlast_cumulative_promptÚlast_cumulative_completionÚfetch_queryrg   Údelta_promptÚdelta_completionÚdelta_totalr\   r@   s                      r   Úinsert_llm_usager!  {  s·  € ô
 ‡K�KÐ5°i°[ÀÈLÈ>ÐYcÐdmÐcnÐoÔpñ ™Ø—L‘L Ó-ˆ	äÓ€DðOØ�[‰[‹]ð H	˜cà�K‰Kð ð �ôð —<‘<“> !Ò$Ü—‘Ð!bÐclÐbmÐnÔoØ÷H	ñ\ �—‘•ˆ4ðA &'Ð"Ø)*Ð&ñ Ü!Ÿg™gð 'ó ÷
 ‘VœCŸN™N¨9Ó5Ó6ð ð —‘˜K¨)°\Ð)BÔCØ—l‘l“n�ÙØILÑFÐ*Ð,Fð Ð5Ò5Ø,�Ø#4Ñ à,Ð/EÑE�Ø#4Ð7QÑ#QÐ à&Ð)9Ñ9ˆKô ˜q ,Ó/ˆLÜ" 1Ð&6Ó7ÐÜ˜a Ó-ˆKô Ÿ7™7ð $ó ÷ ‘œŸ™ yÓ1Ó2ð ð �K‰K˜ØØØØ ØØØØ!Ü—
‘
˜8š> rÓ*ð
'ô 
ð �K‰KŒMÜ�K‰KÐ-¨k¨]Ð:KÈIÈ;ÐVaÐbnÐaoÐopÐqÔrØ÷QH	ñ\ �—‘•ˆ4÷]H	ð H	úð H	øôR ò Ù�—‘”Ü�‰Ð:¸9¸+ÀRÈÀsÐKÔLÛá�—‘•ˆ4ûðúðSH	ñ\ �—‘•ˆ4ø‰4�—‘•ˆ4úsU   ¿H ÁA HÂH Â+D>HÇ)H ÈHÈ
H ÈI= È	I%È-I ÉI= É I%É%I= É=Jc                 óÄ  — t         j                  d| › �«       t        «       }	 |j                  «       5 }t	        j
                  d«      j                  t	        j                  | «      «      }|j                  ||f«       |j                  «       }|D �cg c]2  }|d   |d   |d   |d   |d   |d   r|d   j                  «       nd d	œ‘Œ4 c}cd d d «       |j                  «        S c c}w # 1 sw Y   nxY wnC# t        $ r7}t         j                  d
| › d|› �«       g cY d }~|j                  «        S d }~ww xY w	 |j                  «        y # |j                  «        w xY w)NzFetching LLM usage for tenant: zšSELECT feature_name, model_name, prompt_tokens, completion_tokens, total_tokens, created_at FROM {}.strategist_llm_usage ORDER BY created_at DESC LIMIT %sr   r&   rž   rÑ   rÒ   rÓ   )ÚfeatureÚmodelr  r  r  Ú	timestampz Error fetching usage for tenant rK   )r5   rO   rA   rP   r   rR   r   rS   rQ   r¢   ré   rW   r:   r9   )rF   r—   r?   rY   rf   rì   Úrr@   s           r   Úget_llm_usager'  Ø  sk  € Ü
‡K�KÐ1°)°Ð=Ô>ÜÓ€DðØ�[‰[‹]ð 	˜cÜ—G‘Gð  yó  z÷  Añ  Aô  BE÷  BPñ  BPð  QZó  B[ó  \ˆEØ�K‰K˜ ˜xÔ(Ø—<‘<“>ˆDð öð ð ˜Q™4Ø˜1™Ø!" 1¡Ø%& q¡TØ ! !¡Ø12°1²˜Q˜q™TŸ^™^Ô-¸4óò ÷		ð 	ð  	�
‰
�ùò÷		ð 	úð 	øô ò Ü�‰Ð7¸	°{À"ÀQÀCÐHÔIØŒ	à�
‰
�ûð	úð	ð  	�
‰
�øˆ�
‰
�ús_   ¤C8 ´AC+Â7C&Ã
C+Ã	C8 Ã&C+Ã+C4Ã0C8 Ã7E Ã8	D8ÄD3ÄD8ÄE Ä3D8Ä8E ÅEÚdatac                 ól  — t         j                  d|j                  d«      › d| › �«       t        «       }	 |j	                  «       5 }t        j                  d«      j                  t        j                  | «      «      }|j                  ||d   |j                  d«      |j                  d«      |j                  d«      |j                  d«      |j                  d	«      |j                  d
«      |j                  d«      |j                  d«      |j                  d«      |j                  d«      |j                  d«      |j                  d«      |j                  d«      t        j                  |j                  di «      «      f«       |j                  «        	 ddd«       |j                  «        y# 1 sw Y   nxY wnP# t        $ rD}|j                  «        t         j!                  d|› �d¬«       Y d}~|j                  «        yd}~ww xY w	 |j                  «        y# |j                  «        w xY w)z=
    Inserts or updates analytics for a specific thread.
    z&Upserting thread analytics for thread rº   rÐ   aŒ  
                INSERT INTO {}.strategist_thread_analytics (
                    thread_id, sentiment_score, intent, is_high_intent, is_lead_qualified,
                    is_resolved, escalation_needed, positive_points, key_concerns,
                    pain_point, feature_request, objection, 
                    cta_clicked, summary, metadata, last_analyzed_at
                )
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, CURRENT_TIMESTAMP)
                ON CONFLICT (thread_id) DO UPDATE SET
                    sentiment_score = EXCLUDED.sentiment_score,
                    intent = EXCLUDED.intent,
                    is_high_intent = EXCLUDED.is_high_intent,
                    is_lead_qualified = EXCLUDED.is_lead_qualified,
                    is_resolved = EXCLUDED.is_resolved,
                    escalation_needed = EXCLUDED.escalation_needed,
                    positive_points = EXCLUDED.positive_points,
                    key_concerns = EXCLUDED.key_concerns,
                    pain_point = EXCLUDED.pain_point,
                    feature_request = EXCLUDED.feature_request,
                    objection = EXCLUDED.objection,
                    cta_clicked = EXCLUDED.cta_clicked,
                    summary = EXCLUDED.summary,
                    metadata = EXCLUDED.metadata,
                    last_analyzed_at = CURRENT_TIMESTAMP
            Úsentiment_scoreÚintentÚis_high_intentÚis_lead_qualifiedÚis_resolvedÚescalation_neededÚpositive_pointsÚkey_concernsÚ
pain_pointÚfeature_requestÚ	objectionÚcta_clickedr�   rs   NTz#Failed to upsert thread analytics: r*   F)r5   rO   r   rA   rP   r   rR   r   rS   rQ   r   r€   rU   rW   r:   rX   r9   )rF   r(  r?   rY   Úupsert_queryr@   s         r   Úupsert_thread_analyticsr7  î  sÕ  € ô ‡K�KÐ8¸¿¹À+Ó9NÐ8OÈ{Ð[dÐZeÐfÔgÜÓ€Dð4Ø�[‰[‹]ð -	˜cÜŸ7™7ð $ó ÷0 ‘œŸ™ yÓ1Ó2ð1 ð4 �K‰K˜Ø�[Ñ!Ø—‘Ð*Ó+Ø—‘˜Ó"Ø—‘Ð)Ó*Ø—‘Ð,Ó-Ø—‘˜Ó'Ø—‘Ð,Ó-Ø—‘Ð*Ó+Ø—‘˜Ó(Ø—‘˜Ó&Ø—‘Ð*Ó+Ø—‘˜Ó%Ø—‘˜Ó'Ø—‘˜Ó#Ü—
‘
˜4Ÿ8™8 J°Ó3Ó4ð'ô ð" �K‰KŒMØ÷[-	ðf 	�
‰
�÷g-	ð -	úð -	øô\ ò Ø�‰ŒÜ�‰Ð:¸1¸#Ð>ÈˆÔNÛà�
‰
�ûðúð]-	ðf 	�
‰
�øˆ�
‰
�úsH   ¶F? ÁEF2ÆF? Æ2F;Æ7F? Æ>H! Æ?	HÇ*HÇ2H! ÈHÈH! È!H3c                 ób  — t        «       }	 |j                  «       5 }t        j                  d«      j	                  t        j
                  | «      t        j
                  | «      «      }|j                  |«       |j                  «       D �cg c]  }|d   ‘Œ	 c}cddd«       |j                  «        S c c}w # 1 sw Y   nxY wnE# t        $ r9}t        j                  d| › d|› �d¬«       g cY d}~|j                  «        S d}~ww xY w	 |j                  «        y# |j                  «        w xY w)zV
    Returns unique thread IDs that are present in feedback but not in analytics.
    zÃ
                SELECT DISTINCT thread_id 
                FROM {}.strategist_feedback 
                WHERE thread_id NOT IN (SELECT thread_id FROM {}.strategist_thread_analytics)
            r   Nz-Error fetching unanalyzed threads for tenant rK   Tr*   )rA   rP   r   rR   r   rS   rQ   r¢   rW   r:   r5   r9   )rF   r?   rY   rf   rg   r@   s         r   Úget_unanalyzed_threadsr9  *  s  € ô Ó€DðØ�[‰[‹]ð 	6˜cÜ—G‘Gð ó ÷ ‘œŸ™ yÓ1´3·>±>À)Ó3LÓMð	 ð
 �K‰K˜ÔØ&)§l¡l£nÖ5˜s�C˜“FÒ5÷	6ð 	6ð 	�
‰
�ùò 6÷	6ð 	6úð 	6øô ò Ü�‰ÐDÀYÀKÈrÐRSÐQTÐUÐ`dˆÔeØŒ	à�
‰
�ûð	úð	6ð 	�
‰
�øˆ�
‰
�ús_   ŒC œA/B8ÂB3ÂB8Â	C Â3B8Â8CÂ=C ÃD Ã	DÃDÃ,DÃ-D ÄDÄD ÄD.c                 óÌ  — t         j                  d| › d|› �«       t        «       }	 |j                  «       5 }|j	                  d| f«       |j                  «       sg cddd«       |j                  «        S t        j                  d«      j                  t        j                  | «      «      }|j	                  ||f«       |j                  «       }g }|D ]{  }|d   }|j                  |d   |d   |r|j                  «       j                  «       nd|r|j                  «       j!                  d	«      nd|r|j                  «       nd|d
   dœ«       Œ} |cddd«       |j                  «        S # 1 sw Y   nxY wnE# t"        $ r9}	t         j%                  d| › d|	› �d¬«       g cY d}	~	|j                  «        S d}	~	ww xY w	 |j                  «        y# |j                  «        w xY w)zX
    Returns a list of unique sources (filenames, URLs) for a given ingestion type.
    z-Listing unique knowledge sources for tenant: r�   rß   Na§  
                SELECT 
                    metadata->>'source_id' as source_id,
                    metadata->>'source' as source_name,
                    MIN(created_at) as first_ingested_at,
                    metadata
                FROM {}.strategist_knowledge_base
                WHERE metadata->>'ingestion_type' = %s
                GROUP BY 1, 2, 4
                ORDER BY first_ingested_at DESC
            rž   r   r&   z%H:%M:%SrÑ   )r’   r   Úingested_dateÚingested_timerç   rs   z-Failed to fetch knowledge sources for tenant rK   Tr*   )r5   rO   rA   rP   rQ   rc   rW   r   rR   r   rS   r¢   rd   Údateré   r7   Ústrftimer:   r9   )
rF   rŒ   r?   rY   rf   rì   Úsourcesrg   Údtr@   s
             r   Úget_knowledge_sourcesrA  =  sÆ  € ô ‡K�KÐ?À	¸{È(ÐSaÐRbÐcÔdÜÓ€Dð&Ø�[‰[‹]ð  	˜cà�K‰KÐdÐgpÐfrÔsØ—<‘<”>Ø÷	 	ð  	ðJ 	�
‰
�ô? —G‘Gð 
ó 
÷ ‘œŸ™ yÓ1Ó2ð ð �K‰K˜ Ð0Ô1Ø—<‘<“>ˆDàˆGØò 	�Ø˜‘V�Ø—‘Ø!$ Q¡Ø! !™fÙ>@ R§W¡W£Y×%8Ñ%8Ô%:ÀdÙGI R§W¡W£Y×%7Ñ%7¸
Ô%CÈtÙ46 "§,¡,¤.¸DØ # A¡ñ õ ð	ð ÷A 	ð  	ðJ 	�
‰
�÷K 	ð  	úð  	øôB ò Ü�‰ÐDÀYÀKÈrÐRSÐQTÐUÐ`dˆÔeØŒ	à�
‰
�ûð	úðC 	ðJ 	�
‰
�øˆ�
‰
�úsY   §E: ·%E-Á	E: Á6CE-Å	E: Å-E6Å2E: Å9G Å:	F<ÆF7Æ!F<Æ"G Æ7F<Æ<G ÇG#c            	      ó  — t         j                  d«       	 t        «       } 	 | j                  «       5 }dt        › d�}|j                  |«       |j                  «       D ��ch c]6  \  }}t        |t        «      r!t        |«      j                  d«      r|nd|› �’Œ8 }}}|rJt        |«      }t         j                  dt        |«      › d|› �«       |cddd«       | j                  «        S t         j                  d«       ddd«       | j                  «        	 t         j                  d«       t#        «       } 	 | j                  «       5 }d}|j                  |«       |j                  «       D �cg c]  }|d   ‘Œ	 }}t         j                  dt        |«      › d|› �«       |cddd«       | j                  «        S c c}}w # 1 sw Y   ŒÃxY w# | j                  «        w xY w# t        $ r$}t         j!                  d	|› �d
¬«       Y d}~Œòd}~ww xY wc c}w # 1 sw Y   nxY wnB# t        $ r6}t         j!                  d|› �d
¬«       g cY d}~| j                  «        S d}~ww xY w	 | j                  «        y# | j                  «        w xY w)zä
    Returns tenant schema names eligible for scheduled analytics refresh.
    Active Aivora subscriptions from master are authoritative. The legacy
    schema scan remains as a fallback if the master lookup is unavailable.
    z,Fetching list of eligible Strategist tenantsa  
                    SELECT o."tenantId"::text, p."productFeatures"
                    FROM "Organisations" o
                    JOIN "Subscriptions" s ON s."organisationId" = o."id"
                    JOIN "Plans" p ON p."id" = s."planId"
                    WHERE zA
                    ORDER BY o."tenantId"::text
                Úorg_zFound z active/trial Aivora tenants: Nz>No active/trial Aivora subscriptions found in master database.z;Error fetching active Aivora tenants from master database: Tr*   zEFalling back to tenant schemas that contain strategist_knowledge_basea  
                SELECT DISTINCT table_schema 
                FROM information_schema.tables 
                WHERE table_name = 'strategist_knowledge_base'
                AND table_schema NOT IN ('public', 'information_schema', 'pg_catalog')
            r   z eligible tenants: z!Error fetching eligible tenants: )r5   rO   rE   rP   ÚSUBSCRIPTION_ELIGIBILITY_SQLrQ   r¢   r   ÚAIVORA_PRODUCT_NAMEr¡   Ú
startswithÚsortedr~   rW   r6   r:   r9   rA   )	r?   rY   rf   rF   r   ÚtenantsÚtenant_listr@   rg   s	            r   Úget_eligible_tenantsrJ  k  sa  € ô ‡K�KÐ>Ô?ðgÜ'Ó)ˆð	Ø—‘“ð a #ðô
 8Ð8ð 9ð�ð —‘˜EÔ"ð 8;·|±|³~÷á3˜	Ð#3Ü(Ð)9Ô;NÔOô "% Y£×!:Ñ!:¸6Ô!B‘IÈ$ÈyÈkÐHZÑZð�ñ ñ
 Ü"(¨£/�KÜ—K‘K &¬¨[Ó)9Ð(:Ð:XÐYdÐXeÐ fÔgØ&÷%að að* �J‰J�Lô —‘Ð_Ô`÷'að* �J‰J�Lô ‡K�KÐWÔXÜÓ€DðØ�[‰[‹]ð 
	˜cðˆEð �K‰K˜ÔØ),¯©«Ö8 #�s˜1“vÐ8ˆGÐ8Ü�K‰K˜&¤ W£ Ð.AÀ'ÀÐKÔLØ÷
	ð 
	ð 	�
‰
�ùóC÷að aûð* �J‰J�LûÜò gÜ�‰ÐRÐSTÐRUÐVÐaeˆ×fÑfûðgüò 9÷
	ð 
	úð 
	øô ò Ü�‰Ð8¸¸Ð<ÀtˆÔLØŒ	à�
‰
�ûð	úð
	ð 	�
‰
�øˆ�
‰
�úsÉ   —
G# ¢G ²/GÁ!;F<Â5GÃ	G ÃG# Ã+GÄ G ÄG# Ä9H% Å	&HÅ/HÅ;'HÆ"	H% Æ<GÇGÇG ÇG Ç G# Ç#	HÇ,HÈHÈHÈH!ÈH% È$I9 È%	I$È.IÉ	I$É
I9 ÉI$É$I9 É9Jc                 ó´  — t        | «      j                  dd«      }	 t        j                  |«       t        «       }	 |j                  «       5 }|j                  d|f«       |j                  «       }|s	 ddd«       |j                  «        y|d   |d   |d	   |d
   |d   dœcddd«       |j                  «        S # t        $ r t
        j                  d| › d�«       Y yw xY w# 1 sw Y   nxY wnA# t        $ r5}t
        j                  d| › d|› �«       Y d}~|j                  «        yd}~ww xY w	 |j                  «        y# |j                  «        w xY w)z™Returns the tenant's Organisation record from the master DB, or None.

    Used for per-tenant email branding (business name, logo, website, email).
    rC  rI   úTenant ID 'z3' is not a valid UUID. Organisation lookup skipped.Nz˜SELECT "name", "businessName", "image", "website", "email" FROM "Organisations" WHERE "tenantId"::text = %s ORDER BY "createdAt" DESC NULLS LAST LIMIT 1r   r&   rž   rÑ   rÒ   )ÚnameÚbusinessNameÚimageÚwebsiterÄ   z)Failed to fetch organisation profile for rK   )r¡   rL   ÚuuidÚUUIDrN   r5   r6   rE   rP   rQ   rc   rW   r:   r9   )rF   Úraw_idr?   rY   rg   r@   s         r   Úfetch_organisation_profilerT  £  sO  € ô
 �‹^×#Ñ# F¨BÓ/€FðÜ�	‰	�&Ôô
 $Ó%€DðØ�[‰[‹]ð 	8˜cð �K‰Kð?ð �	ô	ð —,‘,“.ˆCÙØ÷	8ð& 	�
‰
�ð   ™F°C¸±FÀSÈÁVØ" 1™v°°A±ñ8÷	8ð 	8ð& 	�
‰
�øô3 ò Ü�‰˜ Y KÐ/bÐcÔdÙðú÷	8ð 	8úð 	8øô ò Ü�‰Ð@ÀÀÈ2ÈaÈSÐQÔRÛà�
‰
�ûð	úð	8ð& 	�
‰
�øˆ�
‰
�úsd   �B= ½C2 Á'C%Á4C2 ÂC%Â#	C2 Â="C"Ã!C"Ã%C.Ã*C2 Ã1E Ã2	D0Ã;D+ÄE Ä+D0Ä0E ÅEc                 ó\  — t         j                  d| › �«       t        | «      j                  dd«      }	 t	        j
                  |«       t        «       }	 |j                  «       5 }d}|j                  ||f«       |j                  «       }|s2t         j                  d| › �«       	 d	d	d	«       |j                  «        y|d
   }dt        › d�}|j                  ||f«       |j                  «       }|s2t         j                  d|› �«       	 d	d	d	«       |j                  «        y|D ]5  \  }	}
}t        |
t         «      sŒ|
|fc cd	d	d	«       |j                  «        S  t         j                  d|› �«       	 d	d	d	«       |j                  «        y# t        $ r t         j                  d| › d�«       Y yw xY w# 1 sw Y   nxY wnA# t"        $ r5}t         j%                  d| › d|› �«       Y d	}~|j                  «        yd	}~ww xY w	 |j                  «        y	# |j                  «        w xY w)zr
    Fetches real-time limits from the galaxiq_master database using the Organisation-Subscription-Plan path.
    z9Fetching dynamic limits from master database for tenant: rC  rI   rL  z4' is not a valid UUID. Master limits lookup skipped.r¶   zDSELECT "id" FROM "Organisations" WHERE "tenantId"::text = %s LIMIT 1z#No Organisation found for tenantId Nr   zè
                SELECT s."planId", p."productFeatures", p."updatedAt"
                FROM "Subscriptions" s
                JOIN "Plans" p ON p."id" = s."planId"
                WHERE s."organisationId" = %s
                  AND zc
                ORDER BY s."createdAt" DESC NULLS LAST, p."updatedAt" DESC NULLS LAST
            z6No active/trial Subscription found for organisationId z5No active/trial Aivora plan found for organisationId z*Failed to fetch dynamic limits for tenant rK   )r5   rO   r¡   rL   rQ  rR  rN   r6   rE   rP   rQ   rc   rW   rD  r¢   r   rE  r:   r9   )rF   rS  r?   rY   Ú	org_queryÚorg_rowÚorg_idÚ	sub_queryÚsubscription_rowsÚplan_idr   Ú
updated_atr@   s                r   Úfetch_tenant_limitsr]  Ç  s  € ô ‡K�KÐKÈIÈ;ÐWÔXô �‹^×#Ñ# F¨BÓ/€FðÜ�	‰	�&Ôô
 $Ó%€Dð%Ø�[‰[‹]ð 	˜cà^ˆIØ�K‰K˜	 F 9Ô-Ø—l‘l“nˆGÙÜ—‘Ð!DÀYÀKÐPÔQØ!÷	ðH 	�
‰
�ð9 ˜Q‘ZˆFðô
 4Ð4ð 5ðˆIð �K‰K˜	 F 9Ô-Ø #§¡£ÐÙ$Ü—‘Ð!WÐX^ÐW_Ð`ÔaØ!÷1	ðH 	�
‰
�ð :Kò 8Ñ5�Ð)¨:Ü$Ð%5Ô7JÕKØ+¨ZÐ7Ñ7÷9	ð 	ðH 	�
‰
�ð8ô �N‰NÐRÐSYÐRZÐ[Ô\Ø÷?	ðH 	�
‰
�øôU ò Ü�‰˜ Y KÐ/cÐdÔeÙðú÷	ð 	úð 	øô@ ò Ü�‰ÐAÀ)ÀÈBÈqÈcÐRÔSÛà�
‰
�ûð	úðA	ðH 	�
‰
�øˆ�
‰
�ús…   µF ÁG Á%AF9Â&G Â?AF9ÄG Ä%F9Ä?F9Å	G ÅF9Å8G Æ"F6Æ5F6Æ9GÆ>G ÇH Ç	HÇG?Ç*H Ç?HÈH ÈH+c                 óz  — t        «       }	 |j                  «       5 }|rKt        j                  d«      j	                  t        j
                  | «      «      }|j                  ||f«       nF|j                  t        j                  d«      j	                  t        j
                  | «      «      «       |j                  «       d   cddd«       |j                  «        S # 1 sw Y   nxY wn-# t        $ r!}t        j                  d| › d|› �«       ‚ d}~ww xY w	 |j                  «        y# |j                  «        w xY w)zTReturns the number of tickets created for the tenant, optionally since a start date.zASELECT COUNT(*) FROM {}.strategist_tickets WHERE created_at >= %sz*SELECT COUNT(*) FROM {}.strategist_ticketsr   NzError getting ticket count for rK   ©rA   rP   r   rR   r   rS   rQ   rc   rW   r:   r5   r9   ©rF   Ú
start_dater?   rY   rf   r@   s         r   Úget_ticket_countrb  ý  s  € äÓ€DðØ�[‰[‹]ð 	%˜cÙÜŸ™Ð cÓd×kÑkÔlo×lzÑlzð  |Eó  mFó  G�Ø—‘˜E J =Õ1à—‘œCŸG™GÐ$PÓQ×XÑXÔY\×YgÑYgÐhqÓYrÓsÔtØ—<‘<“> !Ñ$÷	%ð 	%ð 	�
‰
�÷	%ð 	%úð 	%øô ò Ü�‰Ð6°y°kÀÀAÀ3ÐGÔHØûðúð	%ð 	�
‰
�øˆ�
‰
�úóA   ŒC) œB&CÃ	C) ÃC%Ã!C) Ã(D( Ã)	DÃ2DÄDÄD( Ä(D:c                 óà  — t        «       }	 |j                  «       5 }|j                  t        j                  d«      j                  t        j                  | «      «      «       |j                  «       d   cddd«       |j                  «        S # 1 sw Y   nxY wn-# t        $ r!}t        j                  d| › d|› �«       ‚ d}~ww xY w	 |j                  «        y# |j                  «        w xY w)z<Returns the total size of the knowledge base table in bytes.z>SELECT pg_total_relation_size('{0}.strategist_knowledge_base')r   Nz!Error getting vector db size for rK   )rA   rP   rQ   r   rR   r   rS   rc   rW   r:   r5   r9   rª   s       r   r„   r„     s×   € äÓ€Dð	Ø�[‰[‹]ð 	%˜cà�K‰KœŸ™Ð `Óa×hÑhÔil×iwÑiwð  yBó  jCó  Dô  EØ—<‘<“> !Ñ$÷	%ð 	%ð 	�
‰
�÷	%ð 	%úð 	%øô ò Ü�‰Ð8¸¸À2ÀaÀSÐIÔJØûðúð		%ð 	�
‰
�øˆ�
‰
�úsA   ŒB œABÁ5	B ÂBÂB ÂC Â	CÂ%CÃCÃC ÃC-c                 ó�  — t        «       }	 |j                  «       5 }|rKt        j                  d«      j	                  t        j
                  | «      «      }|j                  ||f«       nF|j                  t        j                  d«      j	                  t        j
                  | «      «      «       |j                  «       }|r
|d   r|d   ndcddd«       |j                  «        S # 1 sw Y   nxY wn-# t        $ r!}t        j                  d| › d|› �«       ‚ d}~ww xY w	 |j                  «        y# |j                  «        w xY w)zNReturns the sum of total_tokens for the tenant, optionally since a start date.zLSELECT SUM(total_tokens) FROM {}.strategist_llm_usage WHERE created_at >= %sz5SELECT SUM(total_tokens) FROM {}.strategist_llm_usager   NzError getting token usage for rK   r_  )rF   ra  r?   rY   rf   rg   r@   s          r   Úget_total_token_usagerf    s$  € äÓ€DðØ�[‰[‹]ð 	3˜cÙÜŸ™Ð nÓo×vÑvÔwz÷  xFñ  xFð  GPó  xQó  R�Ø—‘˜E J =Õ1à—‘œCŸG™GÐ$[Ó\×cÑcÔdg×drÑdrÐs|Ód}Ó~ÔØ—,‘,“.ˆCÙ  S¨¢V�3�q’6°÷	3ð 	3ð 	�
‰
�÷	3ð 	3úð 	3øô ò Ü�‰Ð5°i°[ÀÀ1À#ÐFÔGØûðúð	3ð 	�
‰
�øˆ�
‰
�úsA   ŒC4 œB1C'Ã	C4 Ã'C0Ã,C4 Ã3D3 Ã4	DÃ=DÄDÄD3 Ä3Ec                 óz  — t        «       }	 |j                  «       5 }|rKt        j                  d«      j	                  t        j
                  | «      «      }|j                  ||f«       nF|j                  t        j                  d«      j	                  t        j
                  | «      «      «       |j                  «       d   cddd«       |j                  «        S # 1 sw Y   nxY wn-# t        $ r!}t        j                  d| › d|› �«       ‚ d}~ww xY w	 |j                  «        y# |j                  «        w xY w)zRReturns the number of unique threads that had activity [usage] since a start date.zTSELECT COUNT(DISTINCT thread_id) FROM {}.strategist_llm_usage WHERE created_at >= %sz=SELECT COUNT(DISTINCT thread_id) FROM {}.strategist_llm_usager   Nz%Error getting conversation count for rK   r_  r`  s         r   Úget_conversation_countrh  .  s(  € äÓ€DðØ�[‰[‹]ð 	%˜cÙÜŸ™Ð vÓw×~Ñ~ô  @C÷  @Nñ  @Nð  OXó  @Yó  Z�Ø—‘˜E J =Õ1à—‘œCŸG™GÐ$cÓd×kÑkÔlo×lzÑlzð  |Eó  mFó  Gô  HØ—<‘<“> !Ñ$÷	%ð 	%ð 	�
‰
�÷	%ð 	%úð 	%øô ò Ü�‰Ð<¸Y¸KÀrÈ!ÈÐMÔNØûðúð	%ð 	�
‰
�øˆ�
‰
�úrc  )rÑ   rž   )rÓ   )Nr¶   )éd   )?r-   r   Úpsycopg2.extrasr   r   Úapp.core.configr   Úloggingr7   Útypingr   rQ  ÚbasicConfigÚINFOÚ	getLoggerr5   rE  rD  r¡   Úboolr   rA   rE   r^   rV   rT   r   r   r‹   r‘   r•   r£   Úintr§   r«   r¯   r´   r¸   rÎ   rÖ   rÚ   rÝ   rê   rî   ró   rö   rù   rÿ   r  r  r  r!  r'  r7  r9  rA  rJ  rT  r]  rb  r„   rf  rh  © r   r   ú<module>rt     sq  ðÛ Ý Ý  Û Ý $Û Û Ý Û ð €× Ñ ˜'Ÿ,™,Ð/eÕ fØ	ˆ×	Ñ	˜:Ó	&€àÐ ð Ð ð°cð ¸dó óó4ð4} ó }ð~2 3ó 2ðhR só Rð* #ð *°ð *Àð *ÐPTó *ðX˜cð °3ð Àó ð( #ð °#ð ¸Có ñ(* #ð *¸¸U¹ð *ÈCó *ðX Sð ¨Tó ð$˜3ð  Só ð0%˜cð %¨ó %ðP #ð °só ñ<E˜Sð E¨tð EÀó EðN' Cð '°Có 'ðR˜Sð ¨Sð ¸tó ð> Cð °Cð Àó ð0&˜#ð &¨#ó &ðP5˜3ó 5ðn #ð °#ð À3ð ÐQTó ð> 3ð °3ó ðB¨ó ð0˜sð °4ó ð>/¨Cð /¸ó /ðb&°ð &Àó &ðP cð ¸ð ÈSó ñ4[ ð [°3ð [ÀCð [ÐX[ð [Ðpsð [ð  DGð [ð  SWð [ð  knó [ñz˜Sð ¨ó ð,: sð :°$ó :ðx có ð&, Sð ,¸#ó ,ò\6ðp!¨#ó !ðH4 3ó 4ñl ó ð" #ó ñ Só ñ$ cô r   