
    ٩i;                     N   U d Z ddlZddlmZmZ ddlmZ ddlmZm	Z	m
Z
mZ ddlZ ee      j                  dz  Z ee      j                  dz  dz  Ze G d	 d
             Ze G d d             Ze G d d             Ze G d d             Ze G d d             Z G d d      ZefdefdZeefdedefdZddefdededededef
dZefdeded e	e   fd!Zefdeded efd"Zdae
e   e d#<   d efd$Z!ded e
e   fd%Z"d eeef   fd&Z#d eeef   fd'Z$e%d(k(  r e         e         e&d)        e#       jO                         D ]  \  Z(Z)e)jT                  rd*nd+Z+ e,e)jZ                  j\                        Z/ e,e)jZ                  j`                        Z1 e,e)jd                  jf                        Z4 e,e)jZ                  j\                  D  cg c]  } | jj                  d,k(  s|  c}       Z6 e&d-e( d.e)jn                   d/e+ d0        e&d1e/ d/e6 d2e1 d3e4         yyc c} w )4z
Channel Configuration Module

Handles loading, validating, and accessing channel configurations.
Single source of truth for all channel-related operations.
    N)	dataclassfield)Path)DictListOptionalAnyzchannels.yamloutputztrends_history.dbc                   8    e Zd ZU dZeed<   dZeed<   dZeed<   y)RedditSourcezA Reddit subreddit source.name	secondarypriorityr   subscribersN)	__name__
__module____qualname____doc__str__annotations__r   r   int     8/home/sietch6/trending-topics-pipeline/channel_config.pyr   r      s    $
IHcKr   r   c                   &    e Zd ZU dZeed<   eed<   y)	RSSSourcezAn RSS feed source.r   urlN)r   r   r   r   r   r   r   r   r   r   r      s    
I	Hr   r   c                       e Zd ZU dZ ee      Zee   e	d<    ee      Z
ee   e	d<    ee      Zee   e	d<   ddedee   fd	Zdee   fd
Zy)ChannelSourcesz$Sources configuration for a channel.default_factoryredditrssyoutube_categoriesNr   returnc                     |r4| j                   D cg c]  }|j                  |k(  s|j                    c}S | j                   D cg c]  }|j                   c}S c c}w c c}w )z5Get subreddit names, optionally filtered by priority.)r"   r   r   )selfr   rs      r   get_reddit_nameszChannelSources.get_reddit_names*   sK    $(KKJq1::3IAFFJJ $,1,, K,s   AAA c                 T    | j                   D cg c]  }|j                   c}S c c}w )zGet all RSS feed URLs.)r#   r   r'   r(   s     r   get_rss_urlszChannelSources.get_rss_urls0   s    #xx(!(((s   %N)r   r   r   r   r   listr"   r   r   r   r#   r   r$   r   r   r)   r,   r   r   r   r   r   #   sg    .!&t!<FD< 6Ci6$)$$?S	?- -S	 -)d3i )r   r   c                   V    e Zd ZU dZ ee      Zee   e	d<    ee      Z
ee   e	d<   y)ChannelKeywordszKeyword filters for a channel.r    includeexcludeN)r   r   r   r   r   r.   r1   r   r   r   r2   r   r   r   r0   r0   5   s+    (t4GT#Y4t4GT#Y4r   r0   c                   ,   e Zd ZU dZeed<   eed<   eed<   eed<   eed<   eed<   eed<   eed	<   e	e   ed
<   e
eef   ed<   dZeed<   dZeed<   dZeed<   dZeed<   dZeed<   dZeed<   edede
dd fd       Zde
fdZde	e
eef      fdZdefdZy)Channelz*Represents a single channel configuration.idr   
short_namedescriptioncolorenabledsourceskeywordscuriosity_queriesscoring_weights competitive_gapmoderation_risk
risk_notesitch_descriptionformat_descriptionhook_criteria
channel_iddatar%   c                 X   |j                  di       }g }|j                  dg       D ]  }t        |t              r|j                  t	        |             /t        |t
              s@|j                  t	        |j                  dd      |j                  dd      |j                  dd	      
              g }|j                  dg       D ]|  }t        |t              r|j                  t        ||             0t        |t
              sA|j                  t        |j                  dd      |j                  dd                   ~ t        |||j                  dg             }|j                  di       }t        |j                  dg       |j                  dg             }	 | d%i d|d|j                  d|      d|j                  d|      d|j                  dd      d|j                  dd      d|j                  dd      d|d|	d|j                  dg       d|j                  di       d|j                  dd      d|j                  dd      d|j                  dd      d |j                  d!d      d"|j                  d#d      d$|j                  d$d      S )&z#Create a Channel from a dictionary.r:   r"   )r   r   r>   r   r   r   r   r   r   r   r#   r   r   r   r$   r"   r#   r$   r;   r1   r2   r1   r2   r5   r6   r7   r8   z#666666r9   Tr<   r=   r?   r@   rA   rB   itchrC   formatrD   r   )	get
isinstancer   appendr   dictr   r   r0   )
clsrE   rF   sources_datareddit_sourcesr(   rss_sourcesr:   keywords_datar;   s
             r   	from_dictzChannel.from_dictP   s    xx	2. !!(B/ 	A!S!%%l&:;At$%%lvr*UU:{; !mQ 7' 		 !!%, 	A!S!""9!#;<At$""9vr*eR($ 		 !!+//0DbI
 R0"!%%i4!%%i4

  

&*-
 xxj9
 3	

 ((7I.
 HHY-
 
 
 #hh':B?
 !HH%6;
 !HH%6;
 !HH%6;
 xxb1
 "XXfb1
  $xx"5
  ((?B7!
 	
r   c           	         i d| j                   d| j                  d| j                  d| j                  d| j                  d| j
                  d| j                  j                  D cg c]&  }|j                  |j                  |j                  d( c}| j                  j                  D cg c]  }|j                  |j                  d	 c}| j                  j                  d
d| j                  j                  | j                  j                  dd| j                   d| j"                  d| j$                  d| j&                  d| j(                  d| j*                  d| j,                  d| j.                  S c c}w c c}w )z-Convert to dictionary for JSON serialization.r5   r   r6   r7   r8   r9   r:   rH   rI   rJ   r;   rK   r<   r=   r?   r@   rA   rB   rC   rD   )r5   r   r6   r7   r8   r9   r:   r"   r   r   r#   r   r$   r;   r1   r2   r<   r=   r?   r@   rA   rB   rC   rD   r+   s     r   to_dictzChannel.to_dict   s   
$''
DII
 $//
 4++	

 TZZ
 t||
 #'<<#6#68 %&FF

STS`S`a 8@D@P@PQ16Q&*ll&E&E	
 ==00==00
"  !7!7#
$ t33%
& t33'
( t33)
* $//+
,  5 5-
. !$"9"9/
0 T//1
 	
8Qs   )+E?. F
c                    g }| j                   j                  D ]9  }|j                  dd|j                   |j                  |j
                  d       ; | j                   j                  D ]+  }|j                  d|j                  |j                  d       - | j                   j                  D ]  }|j                  dd| d        |S )	z0Get all sources as a flat list with type labels.r"   zr/)typer   r   r   r#   )r[   r   r   youtubez	Category )r[   r   )	r:   r"   rP   r   r   r   r#   r   r$   )r'   r:   r(   cat_ids       r   get_all_sourceszChannel.get_all_sources   s    $$ 	ANN QVVHJJ }}	 	 !! 	ANNuu 	 ll55 	NFNNI6(7KLM	Nr   c                     t        | j                  j                  xs. | j                  j                  xs | j                  j                        S )z,Check if channel has any sources configured.)boolr:   r"   r#   r$   r'   s    r   has_sourceszChannel.has_sources   s@    LL ,LL,LL++
 	
r   N)r   r   r   r   r   r   r`   r   r0   r   r   floatr?   r@   rA   rB   rC   rD   classmethodrW   rY   r^   rb   r   r   r   r4   r4   <   s    4G
IOJMCy #u*%%OSOSJc  M38
3 8
d 8
y 8
 8
t
 
:d38n!5 (
T 
r   r4   c                       e Zd ZdZefdefdZdefdZdee	e
f   fdZdee	e
f   fdZdee	e
f   fdZd	e	dee
   fd
Zdee	ef   fdZdee	ef   fdZd Zy)ChannelConfigLoaderz)Loads and manages channel configurations.config_pathc                 .    || _         d | _        d | _        y r-   )rg   _config	_channels)r'   rg   s     r   __init__zChannelConfigLoader.__init__   s    &'+7;r   r%   c                     | j                   Et        | j                  d      5 }t        j                  |      | _         ddd       | j                   S | j                   S # 1 sw Y   | j                   S xY w)zLoad raw config from YAML file.Nr(   )ri   openrg   yaml	safe_load)r'   fs     r   _load_configz ChannelConfigLoader._load_config   s[    <<d&&, 1#~~a01||t||1||s   AA2c                     | j                   b| j                         }i | _         |j                  di       j                         D ](  \  }}t        j                  ||      | j                   |<   * | j                   S )zParse channel configurations.channels)rj   rq   rN   itemsr4   rW   )r'   configrE   channel_datas       r   _parse_channelsz#ChannelConfigLoader._parse_channels   sp    >>!&&(FDN,2JJz2,F,L,L,N Y(
L-4->->z<-Xz*Y~~r   c                 "    | j                         S )zGet all channel configurations.)rw   ra   s    r   get_all_channelsz$ChannelConfigLoader.get_all_channels   s    ##%%r   c                     | j                         j                         D ci c]  \  }}|j                  r|| c}}S c c}}w )zGet only enabled channels.)rw   rt   r9   )r'   cidchs      r   get_enabled_channelsz(ChannelConfigLoader.get_enabled_channels   sE     $(#7#7#9#?#?#A
Rzz G
 	
 
s   ?rE   c                 @    | j                         j                  |      S )zGet a specific channel by ID.)rw   rN   )r'   rE   s     r   get_channelzChannelConfigLoader.get_channel   s    ##%))*55r   c                 B    | j                         j                  di       S )zGet global settings.settings)rq   rN   ra   s    r   get_settingsz ChannelConfigLoader.get_settings   s      "&&z266r   c           	      R    | j                         }|j                  ddddddd      S )zGet default scoring weights.default_scoring_weights      ?)demand_signalcontent_gapexplainabilityevergreen_potentialaudience_breadth)r   rN   )r'   r   s     r   get_default_scoring_weightsz/ChannelConfigLoader.get_default_scoring_weights   s8    $$&||5 !#& #8
  	r   c                      d| _         d| _        y)zForce reload of configuration.N)ri   rj   ra   s    r   reloadzChannelConfigLoader.reload   s    r   N)r   r   r   r   CONFIG_PATHr   rk   r   rq   r   r4   rw   ry   r}   r   r   r	   r   rc   r   r   r   r   r   rf   rf      s    3+6 <D <
d c7l!3 &$sG|"4 &
d3<&8 
6c 6hw.? 67d38n 7	T#u*-= 	r   rf   db_pathc                 N   t        j                  |       }|j                         }|j                  d       |j                  d       |j                  d       |j                  d       |j                  d       |j	                          |j                          t        d       y)z+Initialize channel-related database tables.a  
        CREATE TABLE IF NOT EXISTS channels (
            id TEXT PRIMARY KEY,
            name TEXT NOT NULL,
            short_name TEXT,
            description TEXT,
            color TEXT,
            enabled INTEGER DEFAULT 1,
            config_json TEXT,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            updated_at TEXT DEFAULT CURRENT_TIMESTAMP
        )
    a  
        CREATE TABLE IF NOT EXISTS item_channels (
            item_id INTEGER NOT NULL,
            channel_id TEXT NOT NULL,
            confidence REAL DEFAULT 1.0,
            matched_by TEXT,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (item_id, channel_id),
            FOREIGN KEY (item_id) REFERENCES trend_items(id),
            FOREIGN KEY (channel_id) REFERENCES channels(id)
        )
    zg
        CREATE INDEX IF NOT EXISTS idx_item_channels_channel
        ON item_channels(channel_id)
    za
        CREATE INDEX IF NOT EXISTS idx_item_channels_item
        ON item_channels(item_id)
    ai  
        CREATE TABLE IF NOT EXISTS channel_analysis_runs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            channel_id TEXT NOT NULL,
            run_date TEXT NOT NULL,
            items_analyzed INTEGER,
            opportunities_found INTEGER,
            status TEXT,
            FOREIGN KEY (channel_id) REFERENCES channels(id)
        )
    z(Channel tables initialized successfully.N)sqlite3connectcursorexecutecommitcloseprint)r   connr   s      r   init_channel_tablesr     s    ??7#D[[]F NN  	 NN  	 NN  	
 NN  	 NN 
 
	 	KKMJJL	
45r   rg   c                    ddl }t        |      }|j                         }t        j                  |       }|j                         }|j                         D ]q  \  }}|j                  d||j                  |j                  |j                  |j                  |j                  rdnd|j                  |j                               f       s |j                          |j!                          t#        dt%        |       d       y)z2Sync channel configurations from YAML to database.r   Na   
            INSERT INTO channels (id, name, short_name, description, color, enabled, config_json, updated_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
            ON CONFLICT(id) DO UPDATE SET
                name = excluded.name,
                short_name = excluded.short_name,
                description = excluded.description,
                color = excluded.color,
                enabled = excluded.enabled,
                config_json = excluded.config_json,
                updated_at = CURRENT_TIMESTAMP
           zSynced z channels to database.)jsonrf   ry   r   r   r   rt   r   r   r6   r7   r8   r9   dumpsrY   r   r   r   len)	r   rg   r   loaderrs   r   r   rE   channels	            r   sync_channels_to_dbr   A  s     -F&&(H??7#D[[]F'~~/ 
G  LLMMAaJJw()
	, 	KKMJJL	GCM?"8
9:r   r   manualitem_idrE   
confidence
matched_byc                     t        j                  |      }|j                         }|j                  d| |||f       |j	                          |j                          y)zTag a trend item to a channel.z|
        INSERT OR REPLACE INTO item_channels (item_id, channel_id, confidence, matched_by)
        VALUES (?, ?, ?, ?)
    N)r   r   r   r   r   r   )r   rE   r   r   r   r   r   s          r   tag_item_to_channelr   f  sP     ??7#D[[]F
NN  :z:	68
 	KKMJJLr   r%   c                    t        j                  |      }t         j                  |_        |j	                         }|j                  d| f       |j                         D cg c]  }t        |       }}|j                          |S c c}w )z1Get all trend items tagged to a specific channel.z
        SELECT ti.*, ic.confidence, ic.matched_by
        FROM trend_items ti
        JOIN item_channels ic ON ti.id = ic.item_id
        WHERE ic.channel_id = ?
        ORDER BY ic.confidence DESC, ti.final_score DESC
    )	r   r   Rowrow_factoryr   r   fetchallrQ   r   )rE   r   r   r   rowrt   s         r   get_items_for_channelr   z  sr    ??7#D{{D[[]F
NN   #)//"343T#Y4E4JJLL 5s    Bc                 ,   t        j                  |      }|j                         }|j                  d| f       |j	                         d   }|j                  d| f       |j	                         }|j                          | ||r|d   nd|r|d   dS ddS )zGet statistics for a channel.zc
        SELECT COUNT(*) as item_count
        FROM item_channels
        WHERE channel_id = ?
    r   z
        SELECT MAX(run_date) as last_analysis,
               SUM(opportunities_found) as total_opportunities
        FROM channel_analysis_runs
        WHERE channel_id = ?
    Nr   )rE   
item_countlast_analysistotal_opportunities)r   r   r   r   fetchoner   )rE   r   r   r   r   r   s         r   get_channel_statsr     s    ??7#D[[]F
NN  	
 "1%J
NN 
  //
CJJL ! #&QD),s1v	  34	 r   _loaderc                  .    t         
t               a t         S )z(Get the singleton channel config loader.)r   rf   r   r   r   
get_loaderr     s     %'Nr   c                 4    t               j                  |       S )z&Convenience function to get a channel.)r   r   )rE   s    r   r   r     s    <##J//r   c                  2    t               j                         S )z)Convenience function to get all channels.)r   ry   r   r   r   ry   ry     s    <((**r   c                  2    t               j                         S )z-Convenience function to get enabled channels.)r   r}   r   r   r   r}   r}     s    <,,..r   __main__z
Loaded channels:r9   disabledprimaryz  z: z ()z    Reddit: z primary) | RSS: z | Keywords: )8r   r   dataclassesr   r   pathlibr   typingr   r   r   r	   rn   __file__parentr   DB_PATHr   r   r   r0   r4   rf   r   r   r   r   rc   r   r   r   r   r   r   r   ry   r}   r   r   rt   rE   r   r9   statusr   r:   r"   reddit_countr#   	rss_countr;   r1   keywords_countr   primary_redditr   )r(   s   0r   <module>r      s    (  , ,  8n##o5
x.

(
*-@
@       ) ) )" 5 5 5 E
 E
 E
P9 9z )0 <6 <6~ )0[ "; ";d ";P   	
 ( <C c D tDz & 8? #  4 > *.%	& -' 0C 0HW$5 0
+$sG|, +
/d3<0 /
 z  

/1779 x
G%oo:7??112++,	W--556)?)?[A1::QZCZa[\:,bb:;\N"^,<<Mi[Xefteuvwx  \s   H"
#H"
