
    ija                     v    S r SSKJr  SSKrSSKrSSKrSSKrSSKJrJrJ	r	  SSK
Jr  S	S jrS
S jrSSS jjrg)u  Накопление истории просмотров постов личного канала.

TGStat отдаёт только текущие цифры, а архив в волте перезаписывается — «сколько
пост набрал в первые сутки» нигде не хранится и задним числом не восстановимо.
Этот модуль — единственный механизм, который копит такую историю: ежедневный
снапшот просмотров/пересылок по свежим постам в JSONL-лог, который растёт
только вперёд.
    )annotationsN)datedatetime	timedelta)Pathc                    U S   nUR                  S5      (       a  US S S-   n[        R                  " U5      R                  5       $ )N	posted_atZz+00:00)endswithr   fromisoformatr   )postr	   s     %/home/rasp/tgstat-puller/snapshots.py_posted_dater      sG    [!I#crNX-	!!),1133    c                   0 n/ nU  H:  nUS   US   4nXA;  a  / X'   UR                  U5        X   R                  U5        M<     / nU H  nX   n[        US S9n[        U5      n[        U5      S:  a*  [	        S U 5       5      US'   [	        S U 5       5      US	'   [	        [        U5      [	        S
 U 5       5      5      US'   UR                  U5        M     U$ )u  Схлопнуть альбом в одну строку на пост.

Телеграм отдаёт альбом (медиагруппу) несколькими сообщениями с разными
`msg_id` и одной и той же секундой публикации, и TGStat считает просмотры
по каждому. В снапшотах это давало 3–5 почти одинаковых строк на один
пост: 2037–2041 от 2026-07-28 с `posted_at` до секунды одинаковым и
просмотрами 361/373/373/381/381. Пока сравнения по темпу набора нет, это
просто шум; как только оно заработает, один пост весил бы как пять и
перекосил бы и медиану темпа, и любой порог по рубрике.

Группируем по (`snapshot_date`, `posted_at`): двум разным постам совпасть
до секунды практически невозможно, а у альбома совпадение гарантировано.
Представитель группы — минимальный `msg_id`: это голова альбома, на неё
указывает ссылка на пост, по ней он и опознаётся в архиве. Просмотры и
пересылки берём максимальные по группе — ближайшая снизу оценка охвата
поста целиком (человек, посмотревший любой слайд, посмотрел пост).
`album_size` оставляем в строке: иначе схлопывание — молчаливая правка
данных, а его видно в git-диффе и понятно тому, кто читает файл.

Применяется к уже слитому набору строк, а не только к свежим, — поэтому
чинит и то, что записано в файл раньше.
snapshot_dater	   c                    U S   $ )Nmsg_id rs    r   <lambda>"_collapse_albums.<locals>.<lambda>=   s    (r   key   c              3  T   #    U  H  oR                  S 5      =(       d    Sv   M      g7f)viewsr   Nget.0r   s     r   	<genexpr>#_collapse_albums.<locals>.<genexpr>@   s     !GA55>#6Q#6   &(r   c              3  T   #    U  H  oR                  S 5      =(       d    Sv   M      g7f)forwardsr   Nr    r"   s     r   r$   r%   A   s     $Mu!eeJ&7&<1&<ur&   r(   c              3  T   #    U  H  oR                  S 5      =(       d    Sv   M      g7f)
album_sizer   Nr    r"   s     r   r$   r%   F   s     F1UU<05A5r&   r*   )appendmindictlenmax)	rowsgroupsorderrowr   outgroupheadmergeds	            r   _collapse_albumsr8      s    . 13F#%E?#S%56FKLL3  C534du:>!!G!GGF7O!$$Mu$M!MF:  #JFFF 
| 	

6  Jr   c                &   [        U 5      n [        R                  " U5      [        US9-
  nU Vs/ s H  n[	        U5      U:  d  M  UPM     nn0 nU R                  5       (       aj  [        U R                  SS9R                  5       SS9 HA  u  pU	R                  5       (       d  M   [        R                  " U	5      n
U
S   U
S   4nXU'   MC     U H8  nUS   U4nUS   US   UUR#                  S5      UR#                  S5      S.X{'   M:     [%        UR'                  5       S S9n[)        U5      nU R*                  R-                  SSS9  [.        R0                  " [3        U R*                  5      SSS9u  nn [4        R6                  " USSS9 nU H*  n
UR9                  [        R:                  " U
SS9S-   5        M,     SSS5        [4        R<                  " U[3        U 5      5        [C        U Vs1 s H  oUS   iM	     sn5      $ s  snf ! [        R                  [        4 a.  n[        SU S	U  S
U S3[        R                   S9   SnAGM  SnAff = f! , (       d  f       N= f! [>         a    [        U5      RA                  SS9  e f = fs  snf )u  Дописать в `path` снапшот просмотров по постам не старше `window_days`.

Повторный вызов в тот же `today` обновляет строку `msg_id`+`snapshot_date`,
а не дублирует её. Файл — единственная копия истории, поэтому он читается
целиком, дополняется и перезаписывается атомарно (временный файл рядом +
`os.replace`), а не дописывается построчно.

Строки альбома (несколько `msg_id` с одной секундой публикации) схлопываются
в одну — см. `_collapse_albums`.

Возвращает число постов, отобранных в этом прогоне (попавших в окно, после
схлопывания альбомов), а не итоговый размер файла.
)dayszutf-8)encodingr   )startr   r   u#   snapshots: битая строка u    в u    пропущена (u7   ); остальная история сохранена)fileNr	   r   r(   )r   r	   r   r   r(   c                    U S   U S   4$ )Nr   r   r   r   s    r   r   write_rows.<locals>.<lambda>   s    Ao4F(3Tr   r   T)parentsexist_okz.snapshots-z.tmp)dirprefixsuffixwF)ensure_ascii
)
missing_ok)"r   r   r   r   r   exists	enumerate	read_text
splitlinesstripjsonloadsJSONDecodeErrorKeyErrorprintsysstderrr!   sortedvaluesr8   parentmkdirtempfilemkstempstrosfdopenwritedumpsreplace	Exceptionunlinkr.   )pathpoststodaywindow_dayscutoffpfreshexistinglinenoliner3   r   er   r0   fdtmpfhs                     r   
write_rowsrq   L   se    :D&)DDF;1aF!:QE;,.H{{}}%dnngn&F&Q&Q&S[\]LF::<<	jj&8}c/&:;  SM' ^* H~u%8nk*"XXg&,
  (//#)TUDD!DKKdT23t{{#3MRXYGBYYr31RCe<tCD  2 	

3D	" .1+.//i <  (((3 9&dVK`ab`c dK K
 6 21  S	D)
 /sM   HH( H9I' 1I (I' 1JI%"II
I$ I' '$J)r   r-   returnr   )r0   
list[dict]rr   rs   )   )
rc   r   rd   rs   re   r[   rf   intrr   ru   )__doc__
__future__r   rN   r\   rS   rY   r   r   r   pathlibr   r   r8   rq   r   r   r   <module>ry      s5    #  	 
  . . 4/dE0r   