
    dj!                     J   S r SSKrSSKrSSKJrJrJr  SSKJr  \" \5      R                  5       R                  r\S-  r\S-  r\S-  r\S-  rSSS	S
SSSSSSSSS.rS$S jrS rS$S jrS rS rS rSrS\-   rS$S jrS rS$S jrS%S jrS rS$S  jrS! rS&S" jr   S'S# jr!g)(uR   Общие утилиты feedwatch: .env, конфиг, база, медианы.    N)datetime	timedeltatimezone)Pathdatafeedwatch.dbzconfig.jsonz.env       @               sunz18:00z10:00)	instagramtelegrampulse_multipliermedian_window_daysmedian_min_postsmedian_min_age_dayspulse_max_age_daysfetch_limit_pulsefetch_limit_weekly
weekly_dayweekly_time
pulse_timec                    [        U =(       d    [        5      n 0 nU R                  5       (       d  U$ U R                  SS9R	                  5        H  nUR                  5       nU(       a  UR                  S5      (       d  SU;  a  M8  UR                  S5      u  p4nUR                  5       R                  S5      R                  S5      nU(       d  M  XQUR                  5       '   M     U$ )Nutf-8encoding#="')r   ENV_PATHexists	read_text
splitlinesstrip
startswith	partition)pathenvlinekey_values         /home/rasp/feedwatch/common.pyload_envr2      s     !D
C;;==
0;;=zz|ts++s$s+##C(..s35$		 > J    c                 B   U R                  S5      (       dd  U R                  S5      (       d  U R                  S5      (       a8  U R                  S5      =(       d    SU R                  S5      =(       d    / S.U S'   U R                  SS5        U R                  SS5        U $ )u   Плоский legacy-формат (source/accounts в корне) → секция instagram.

Корневые legacy-ключи не удаляем — их никто больше не читает.r   accountssourceapify)r6   r5   Nr   )get
setdefault)cfgs    r1   normalize_configr;   -   s     77;SWWZ%8%8CGGH<M<M&)ggh&7&B7(+
(;(ArCKNN;%NN:t$Jr3   c                     [        U =(       d    [        5      n [        [        5      nU R	                  5       (       a2  UR                  [        R                  " U R                  SS95      5        [        U5      $ )Nr   r   )
r   CONFIG_PATHdictDEFAULTSr%   updatejsonloadsr&   r;   )r+   r:   s     r1   load_configrC   9   sP    #$D
x.C{{}}

4::dnngn>?@C  r3   c                 Z   / nU R                  S5      =(       d    0 nUR                  S/ 5       H$  nUR                  SUR                  5       45        M&     U R                  S5      =(       d    0 nUR                  S/ 5       H$  nUR                  SUR                  5       45        M&     U$ )uJ   [(platform, account), ...] по конфигу, аккаунты в lower.r   r5   r   channels)r8   appendlower)r:   outigatgcs         r1   active_accountsrM   A   s    
C			#BVVJ#

K+, $			"BVVJ#

J	*+ $Jr3   c                  J    [         R                  " [        R                  5      $ N)r   nowr   utc r3   r1   now_utcrS   M   s    <<%%r3   c                     U R                  SS5      n U R                  S5      (       a  U SS S-   n [        R                  " U 5      $ )uT   ISO-таймстамп → aware datetime. Терпит 'Z' и '+0000' (Python 3.9).Zz+00:00z+0000N)replaceendswithr   fromisoformat)r0   s    r1   parse_tsrZ   Q   sB    MM#x(E~~gcr
X%!!%((r3   z
CREATE TABLE IF NOT EXISTS account_status (
    platform TEXT NOT NULL DEFAULT 'instagram',
    account TEXT NOT NULL,
    last_ok TEXT,
    last_error TEXT,
    subscribers INTEGER,
    PRIMARY KEY (platform, account)
);
a  
CREATE TABLE IF NOT EXISTS posts (
    post_id TEXT PRIMARY KEY,
    account TEXT NOT NULL,
    platform TEXT NOT NULL DEFAULT 'instagram',
    caption TEXT,
    posted_at TEXT NOT NULL,
    permalink TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS snapshots (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    post_id TEXT NOT NULL,
    fetched_at TEXT NOT NULL,
    likes INTEGER,
    comments INTEGER,
    views INTEGER
);
CREATE TABLE IF NOT EXISTS alerted (
    post_id TEXT PRIMARY KEY,
    alerted_at TEXT NOT NULL
);
c                    [        U =(       d    [        5      nUR                  R                  SSS9  UR                  S-  nUR                  S:X  a;  UR                  5       (       d&  UR                  5       (       a  UR                  U5        [        R                  " U5      n[        R                  Ul
        UR                  [        5        [        U5        U$ )NT)parentsexist_okzinstawatch.dbr   )r   DB_PATHparentmkdirnamer%   renamesqlite3connectRowrow_factoryexecutescriptSCHEMA_migrate)db_pathr+   legacycons       r1   rd   rd   |   s    "7#DKKdT2[[?*FyyN"4;;==V]]__d
//$
CkkCOfSMJr3   c                    U R                  S5       Vs1 s H  oS   iM	     nnSU;  a  U R                  S5        U R                  S5       Vs1 s H  oS   iM	     nnSU;  aH  U R                  S5        U R                  [        5        U R                  S5        U R                  S5        U R                  5         g s  snf s  snf )	NzPRAGMA table_info(posts)ra   platformzGALTER TABLE posts ADD COLUMN platform TEXT NOT NULL DEFAULT 'instagram'z!PRAGMA table_info(account_status)z:ALTER TABLE account_status RENAME TO account_status_legacyzINSERT INTO account_status (platform, account, last_ok, last_error) SELECT 'instagram', account, last_ok, last_error FROM account_status_legacyz DROP TABLE account_status_legacy)executerg   ACCOUNT_STATUS_SCHEMAcommit)rl   rcolss      r1   ri   ri      s    "{{+EFGF!fIFDG]^"{{+NOPO!fIODPPQ/0 b 	c67JJL H Qs   B=Cc                 p   [        U[        5      (       d!  U=(       d
    [        5       R                  5       OUnU Hh  nU R	                  SUS   US   UR                  SS5      US   US   US   45        U R	                  S	US   X4S
   US   UR                  S5      45        Mj     U R                  5         g )NzINSERT INTO posts (post_id, account, platform, caption, posted_at, permalink) VALUES (?,?,?,?,?,?) ON CONFLICT(post_id) DO UPDATE SET caption=excluded.captionpost_idaccountrn   r   caption	posted_at	permalinkzVINSERT INTO snapshots (post_id, fetched_at, likes, comments, views) VALUES (?,?,?,?,?)likescommentsviews)
isinstancestrrS   	isoformatro   r8   rq   )rl   records
fetched_atfetchedrr   s        r1   
save_postsr      s    ;EjRU;V;Vz&WY113\fGJ y\1Y<z;)G9{^Q{^-		
 	!y\7gJ*quuW~N	
  JJLr3   c                     U R                  SX45      R                  5       nU Vs/ s H  n[        U5      PM     sn$ s  snf )uu   Последний снапшот каждого поста аккаунта, свежие посты первыми.a|  
        SELECT p.post_id, p.account, p.platform, p.caption, p.posted_at, p.permalink,
               s.likes, s.comments, s.views
        FROM posts p
        JOIN snapshots s ON s.post_id = p.post_id
        WHERE p.account = ?
          AND p.platform = ?
          AND s.id = (SELECT MAX(id) FROM snapshots WHERE post_id = p.post_id)
        ORDER BY p.posted_at DESC
        )ro   fetchallr>   )rl   rv   rn   rowsrr   s        r1   latest_metricsr      sG    ;;		 
 hj 	 ""TDGT"""s   >c                 H    U R                  SU45      R                  5       S L$ )Nz'SELECT 1 FROM alerted WHERE post_id = ?)ro   fetchone)rl   ru   s     r1   was_alertedr      s(    ;;1G:hj r3   c                     U=(       d
    [        5       R                  5       nU R                  SU Vs/ s H  oDU4PM     sn5        U R                  5         g s  snf )Nz@INSERT OR IGNORE INTO alerted (post_id, alerted_at) VALUES (?,?))rS   r   executemanyrq   )rl   post_idsatstamppids        r1   mark_alertedr      sI    _79'')EOOJ!)*#u* JJL 	+s   A
c                     [        U 5      n [        U 5      nUS:X  a  g US-  nUS-  (       a  [        X   5      $ XS-
     X   -   S-  $ )Nr   r      r	   )sortedlenfloat)valuesnmids      r1   _medianr      sS    F^FFAAv
q&C1uV[!!7Ofk)S00r3   c                 p   U=(       d
    [        5       nU[        US   S9-
  nU[        US   S9-
  n[        XUS9 Vs/ s H  n[        US   5      U::  d  M  UPM     nnU Vs/ s H  n[        US   5      U:  d  M  UPM     n	n[	        U	5      US   :  a  USUS    n	U	 Vs/ s H  owS   c  M
  US   PM     n
nU	 Vs/ s H  owS	   c  M
  US	   PM     nnU	 Vs/ s H  owS
   c  M
  US
   PM     nn[        U
5      [        U5      [        U5      [	        U	5      S.$ s  snf s  snf s  snf s  snf s  snf )uw   Медианы лайков/комментов/просмотров аккаунта по окну из конфига.r   )daysr   )rn   rx   r   Nrz   r{   r|   )rz   r{   r|   n_posts)rS   r   r   rZ   r   r   )rl   rv   r:   rP   rn   min_agewindow_startpsettled	in_windowrz   r{   r|   s                r1   account_mediansr      sK   

CI3'<#=>>G,@(ABBL(I 7IQ1[>*g5 IG 7#PGqx+'?<'OGIP
9~.//4S!345	!*EAjZQwZEE'0Ny!jM*yHN!*EAjZQwZEEU^1BU^I@ @7P FNEs;   DD$D$>D$$	D)1	D) 	D.	D.	D3)	D3c                     U=(       d
    [        5       R                  5       nUc  U R                  SXAXe45        OU R                  SXAU45        U R                  5         g )Na  INSERT INTO account_status (platform, account, last_ok, last_error, subscribers) VALUES (?,?,?,NULL,?) ON CONFLICT(platform, account) DO UPDATE SET last_ok=excluded.last_ok, last_error=NULL, subscribers=COALESCE(excluded.subscribers, account_status.subscribers)zINSERT INTO account_status (platform, account, last_error) VALUES (?,?,?) ON CONFLICT(platform, account) DO UPDATE SET last_error=excluded.last_error)rS   r   ro   rq   )rl   rv   errorr   rn   subscribersr   s          r1   set_account_statusr      s^    _79'')E}U
 3	
 	Z&	

 JJLr3   rO   )r   )Nr   )NNr   N)"__doc__rA   rc   r   r   r   pathlibr   __file__resolver_   ROOTDATA_DIRr^   r=   r$   r?   r2   r;   rC   rM   rS   rZ   rp   rh   rd   ri   r   r   r   r   r   r   r   rR   r3   r1   <module>r      s    X   2 2 H~&&&=
^
#]"&=   	!	&)	 
* +
0
$#$1@" DO#'r3   