
    {mj                         S r SSKrSSKrSSKrSSKrSSKrSSKrSSKrSSK	r	SSK
r
SSK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JrJrJrJrJrJrJrJrJrJrJ r J!r!  \RD                  r#SSK$J%r&  Sr'Sr(Sr)S r*S	 r+S
 r,S,S jr-S r.S r/S r0/ r1S r2\
Rf                  r3S,S jr4S-S jr5S r6S.S jr7Sr8S r9S r:S r;S r<S r=S r>S r?S r@S rAS rBSrCS  rDS! rES"rFS# rGS$ rHS% rIS& rJS' rKS( rLS) rMS* rN\OS+:X  a  \N" 5         gg)/u  Финотчёт Марса для владельца: состояние → когда кончатся деньги → что делать.

Три формата (по важности, чтобы не замыливалось):
  • pulse — короткий пульс на каждый день (остаток, пришло сегодня, ближайшая выплата, вердикт)
  • full  — полный разбор раз в неделю (пн): состояние → runway → что делать
  • flag  — красный флаг в любой день, если runway после обязательных выплат уходит в минус

Данные:
  Доходы: разбивка — из CRM (AlfaCRM, все доходы туда заносит Оля), контрольный итог — Финтабло.
  Расходы: факт — Финтабло; «ещё платить» — плановые операции (isPlan=1) месяца БЕЗ совпавшего факта.
           Матчинг план↔факт — КАСКАД ПО КОНТРАГЕНТАМ (см. match_cascade): A) partnerId, B) фамилия из
           описания, C) точная сумма (статья «Неразнесенное списание» — джокер), C2) единственная планка
           статьи (сумма не критерий), D) агрегаты по сумме фактов статьи. Проматченные
           выпадают; непогашенное с прошедшей датой — «просрочено», недоплата агрегата — «оплачено X из Y».
  Свободный остаток = счета Финтабло МИНУС «Рабочие руки» (транзит под самозанятых, уже выплаченное).

Запуск:
  python3 report.py [pulse|full|auto] [--send] [--date дд.мм.гггг]
    auto (дефолт) = пульс каждый день; по понедельникам — полный; + флаг при риске.
    --send  = отправить в Телеграм (креды из .env: TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID); без него — печать в консоль.
    N)match_cascadeclassify_unpaidsettled	parse_dmy_lead_token_norm_is_aggregate	_is_joker_tokensMATCH_TOL_PCTMATCH_TOL_ABSMATCH_WINDOW_DAYSC2_COVER_LOC2_COVER_HISETTLED_MIN_COVER)	TEACH_ESTiN	    i  c                 <    [        U 5      S R                  SS5      $ )N,    )roundreplacexs    /home/rasp/finsync/report.pymoneyr   0   s    Ahq\""3--    c                 6    U S-  S R                  SS5      S-   $ )Ni@B z.2f.r   u    млн)r   r   s    r   mlnr    4   s#    	k#''S1J>>r   c                 b    U =(       d    SR                  5       nSU;   a  gSU;   a  gSU;   a  gU $ )N u
   наличu   Наличныеu   заначкu   Заначкаu
   точкаu
   Точкаlower)namens     r   	short_accr'   8   s:    	Aq!qKr   c           
      |   0 nU  H'  nUR                  US   / 5      R                  U5        M)     / n[        U[        S9 Hg  nX%   n[	        S U 5       5      n[        U Vs1 s H  n[        US   5      iM     sn5      nUR                  USS USR                  U5      45        Mi     U(       a  USU $ U$ s  snf )us   [(дд.мм, сумма, 'статьи')] по дням из списка плановых операций next2w.datekeyc              3   J   #    U  H  oR                  S US   5      v   M     g7f)duevalueN)get.0os     r   	<genexpr>pay_schedule.<locals>.<genexpr>K   s      6#QeQwZ((#s   !#catN   , )
setdefaultappendsortedr   sumexpense_bucketjoin)	next2wlimitby_dayr2   rowsdtopsslabelss	            r   pay_schedulerF   C   s    F!F)R(//2 DV+j6#663?3a%13?@RVQ		& 123	 ,
 !4<*d* @s   "B9
c                  *    [         R                  " SS9$ )uq   Все приходы года (ВКЛЮЧАЯ плановые isPlan=1 — фильтруем на месте).incomegroup)coreft_transactions r   r   _scan_incomerN   Q   s    h//r   c                 \   SU S3n0 nU  H  nUR                  S5      =(       d    SnUR                  S5      (       d  M5  UR                  U5      (       d  MM  UR                  U[        U5      S S5      [        UR                  S5      =(       d    S	5      -   X5[        U5      S '   M     U$ )
u   {slug: сумма плана} из плановых income-операций месяца mm (externalId plansync:income:MM.YYYY:slug).plansync:income::
externalIdr"   isPlanN        r.   r   )r/   
startswithlenfloat)
all_incomemmprefixrestexts         r   plan_income_mapr^   V   s    t1%F
CeeL!'R55??s~~f55%(WWSV->%DuQUUSZ^M`_`Ga%aCCKL!"  Jr   c                 :   ^ SU S3m[        U4S jU  5       5      $ )NrP   rQ   c              3      >#    U  HE  oR                  S 5      =(       a)    UR                  S5      =(       d    SR                  T5      v   MG     g7f)rS   rR   r"   N)r/   rU   )r1   r\   rZ   s     r   r3   "has_income_plan.<locals>.<genexpr>c   s;     f[eVWuuXQAEE,$7$=2#I#I&#QQ[es   AA)any)rX   rY   rZ   s     @r   has_income_planrc   a   s#    t1%Ff[efffr   c                  r    [         (       d'  [         R                  [        R                  " SS95        [         $ )u   Все расходные операции года (план и факт вперемешку). Кэш: за прогон нужен дважды.outcomerI   )_OUTCOME_CACHEextendrK   rL   rM   r   r   _scan_outcomerh   i   s&    >d22CDr   c                    U =(       d    [         R                  " 5       n [        5        Vs/ s H0  oR                  S5      (       d  M  [        R
                  " X5      PM2     nn [        R                  " [        R                  " 5       5      nU H$  nUR                  S5      U;   d  M  X4S      US'   M&     U$ s  snf ! [         a    Us $ f = f)u  Плановые расходные операции (isPlan=1) года — вход каскада матчинга.

Планкам «События Оли» подставляем человека из колонки «Контрагент» её таблицы: в
«Назначении» он назван как придётся («Вове (бас)», «Саше Б»), и каскад по описанию его
не узнаёт — выплата висела в «просрочено», хотя деньги ушли. Работает и для планок,
занесённых до того, как мы стали писать partnerId. Таблица недоступна — матчим как раньше.
rS   r]   kontr)
rK   ft_categoriesrh   r/   matchingplan_op
olya_sheetkontr_by_ext
fetch_rows	Exception)catsr\   rC   rj   r2   s        r   plan_ops_outcomers   s   s     '4%%'D.;o
Qox$8A$oC
Q''
(=(=(?@ 55<5 xAgJ  J R  
s   B:B:%)B? ?CCc                 
   U=(       d    [         R                  " 5       nU b  U O[         R                  " 5       n [        5        Vs/ s H1  o"R	                  S5      (       a  M  [
        R                  " X!U 5      PM3     sn$ s  snf )u   Фактические (НЕ план) расходные операции года — для матчинга с планом.rS   )rK   rk   ft_partnersrh   r/   rl   fact_op)partnersrr   r\   s      r   fact_ops_outcomerx      s`    '4%%'D#/xT5E5E5GH9F`APUPUV^P_/HQh/```s   B "B c                     U R                  5       nSU;   a  gSU;   d  SU;   d  SU;   a  gSU;   d  SU;   d  S	U;   d  S
U;   a  gSU;   a  gSU;   a  gSU;   d  SU;   d  SU;   d  SU;   a  gU $ )N   педагогu   ЗП педагоговu   зарплатu
   взносu
   админu#   ЗП бэк-офис + админыu   % по займуu   кредитu   процентu   займu&   проценты по кредитамu   бухгалтu   бухгалтерияu   типографu   типографияu
   ивентu   мероприятu   съемu   съёмu   события (Оля)r#   )r5   r&   s     r   r<   r<      s    		A1(1 1\Q5F4a>Q#6:Ja:OS]abSb71'Q%q0A5qJZ[O(Jr   c                    [         R                  " UR                  UR                  5      S   n[        R
                  " UR                  UR                  U5      n[        SX-
  5      nXb-
  R                  S-   n[        X65      U-
  R                  S-   n	US:  a  Xy-  U-  OSn
X6:  a_  US:  aY  U[        R                  " SS9-   nXX6-
  R                  -  [         R                  " UR                  UR                  5      S   -  -  n
U
$ )u@  Сколько прихода ждём внутри окна — равномерно по ОСТАВШИМСЯ дням месяца.

Горизонт доходов обязан совпадать с горизонтом расходов: сравнивать выплаты за
15 дней с месячным планом 6 050 000 нельзя, к 15-му весь месячный приход не случится.
Делим по оставшимся дням, а не по всем: иначе в конце месяца остаток плана терялся бы.
Равномерность — сознательное упрощение (абонементы идут волной в начале месяца),
поэтому в первой половине месяца оценка консервативна.
   rT   r   days)
calendar
monthrangeyearmonthdatetimer)   maxr~   min	timedelta)
plan_month	got_monthtoday
window_end	plan_nextlasteom
left_month	days_leftdays_ingotnxts               r   expected_in_windowr      s     uzz5;;7:D
--

EKK
6CS*01J""Q&I:#e+11A5G.7!m*

*CIMH&&A..J,222X5H5HSVS\S\5]^_5```Jr   )rz   u   подрядчикu   рабочие рукиc                 t   ^  U[         ::  d  [        U 4S j[         5       5      (       d  gS[        U5       S3$ )u  Пометка к планке, деньги под которую уже лежат на площадке.

Выплату могли сделать и не занести в Финтабло (31.07.2026 педагогам заплатили,
операции нет) — тогда планка висит непогашенной и выглядит будущим платежом.
Скрывать её нельзя (мы не знаем наверняка), но и выдавать за бесспорный долг тоже.
c              3   >   >#    U  H  o[        T5      ;   v   M     g 7fNr   )r1   kr5   s     r   r3    platform_hint.<locals>.<genexpr>   s     &O1E#Js   r"   u    (из них u    уже на площадке))RR_NOISErb   _PLATFORM_CATSr   )r5   platforms   ` r   platform_hintr      s5     83&O&O#O#OE(O,,JKKr   c                     [        S U  5       5      n[        S U  5       5      n[        X5      nU[        SX$-
  5      4$ )u  Сколько из предстоящих выплат уже лежит на площадке и сколько нужно живыми.

Остаток площадки гасит только выплаты людям — аренду с него не заплатишь.
c              3   *   #    U  H	  oS    v   M     g7fr-   NrM   r0   s     r   r3   &covered_by_platform.<locals>.<genexpr>   s     -*Q%*   c              3   n   ^#    U  H*  m[        U4S  j[         5       5      (       d  M#  TS   v   M,     g7f)c              3   D   >#    U  H  o[        TS    5      ;   v   M     g7f)r5   Nr   )r1   r   r2   s     r   r3   0covered_by_platform.<locals>.<genexpr>.<genexpr>   s     F~!eAeHo-~s    r-   N)rb   r   r0   s    @r   r3   r      s+      HJqF~FF !E(Js   "55rT   )r;   r   r   )
due_windowr   totalpayablecovereds        r   covered_by_platformr      sK    
 -*--E HJ H HG($GCU_---r   c                     / n[          Hl  nU R                  UR                  S5      n[        R                  " XAU5      nUR                  UR                  XV[        SXV-
  5      UR                  S.5        Mn     U$ )u  Доходы построчно по канону: план (из Финтабло) / факт CRM / сколько ещё ждём.

Факт — через il.crm_fact_for (канон), а не самодельной суммой по l.crm_dirs: у
prochee/podpiska crm_dirs пуст, «Прочее» получает факт только через ветку other
(доход CRM вне направлений — мерч, внешние мероприятия). Самодельная сумма по
crm_dirs всегда возвращала 0 для этих строк (см. находку #3 финального ревью).
Чистая функция — без сети, тестируем напрямую.rT   )r%   planr   waitrisky)	INCOME_LINESr/   slugilcrm_fact_forr9   r%   r   r   )pinccrm	crm_otherr[   lpr   s          r   build_income_expectr      sk     CHHQVVS!ooai0

AFFAQW-A 	B 
 Jr   c                   ^J^K^L^M [         R                  " U 5      mK[         R                  " 5       mJ[        R                  " 5       u  p[        R
                  " 5       n[        5       nU Vs/ s H  oUR                  S5      (       a  M  UPM     nn[        UTK5      mLTL(       + n[        UL4S j[         5       5      n[        UL4S j[         5       5      n	X-   n
[         R                  " U 5      nU R                  S:  =(       a    [        XK5      (       + n[        R                  " 5       oR                  S-   n[        R                   " U5      nU(       a  SOKU[#        U[        R$                     5      :  a*  [        R&                  " U[        R$                     U   5      OSn[(        R*                  " 5       n[(        R,                  " TKU5      u  nn[        UR/                  5       5      U-   nU [0        R2                  " SS9-
  R5                  S5      mM[        UJUK4S	 jU 5       5      n[        UJUM4S
 jU 5       5      n[7        SU
U-
  5      n[9        TLUU5      n[        R:                  " 5       u  nn[        R<                   Vs0 s H&  nUUR                  TK0 5      R                  US5      _M(     nn[?        TJ5      n[A        TJS9nU [0        R2                  " [B        S9-   n[E        UUU TKU5      n U S   n![        S U! 5       5      n"U S   n#[        S U# 5       5      n$U S   n%[        S U% 5       5      n&U S   n'U S   n([        S U( 5       5      n)U S   n*0 n+U( H6  n,U+R                  [G        U,S   5      S5      U,S   -   U+[G        U,S   5      '   M8     [I        U(U5      u  n-n.0 n/U! H6  n,U/R                  [G        U,S   5      S5      U,S   -   U/[G        U,S   5      '   M8     [K        U V0s/ s H<  n0U0R                  S5      TK:X  d  M  S[M        U0R                  S5      5      ;   d  M:  U0PM>     sn0S S9n1[        S U1 5       5      n2U S   n3[O        S U3 5       SS 9n4U3 V,s/ s H  n,U,S!   U4:X  d  M  U,PM     n5n,[        S" U5 5       5      n6S#RQ                  [K        U5 V,s1 s H  n,[G        U,S   5      iM     sn,5      5      n7[        S$ U 5       5      n8[        S% U 5       5      n9[        [        XK5      R/                  5       5      n:[        S& U 5       5      n;[        S' U 5       5      n<[        S( U 5       5      n=[        S) U 5       5      n>[S        U<U;U UU:5      n?[S        U=U>U U5      n@[0        RT                  " U RV                  U R                  [X        RZ                  " U RV                  U R                  5      S   5      nAUUA:  =(       a    U:S*:H  nBUU8-   U9-   U)-
  nCUU8-   U)-
  nDUU-   U"-
  nEUE[\        -
  nFUU"-
  [\        -
  nGUC[^        -
  nHUG[^        -
  nI[a        S\0 S+U _S,TK_S-U_S.U_S/U_S0U_S1U
_S2U_S3U	_S4U_S5U_S6U_S7U_S8U_S9U_S:U"_S;U/_SU#_S<U$_SU%_S=U&_S>U_S?U4_S@U6_SAU7_SBUE_SCUF_SDUG_SEUH_SFUI_SU'_SGU_SHU+_SIU)_SJU_SKU SK   _SLU_SMU-_SNU._SOU?_SPW@_SQUB_SRU8_SSU9_STUC_SUUD_SU*_SVU1_SWU2_SXU_SYU_SZU_S[U_6$ s  snf s  snf s  sn0f s  sn,f s  sn,f )]ua   Один расчёт → dict со всеми числами для любого рендера.rS   c              3      >#    U  H@  oR                   [        R                  :X  d  M"  TR                  UR                  S 5      v   MB     g7frT   N)categoryr   CAT_B2Cr/   r   r1   r   r   s     r   r3   compute.<locals>.<genexpr>   6     [,Q**PRPZPZBZ(488AFFC((,
   !A#Ac              3      >#    U  H@  oR                   [        R                  :X  d  M"  TR                  UR                  S 5      v   MB     g7fr   )r   r   CAT_B2Br/   r   r   s     r   r3   r      r   r      r|   rT   r}   z%d.%m.%Yc              3     >#    U  H{  nUR                  S 5      T:X  d  M  [        R                  " TR                  UR                  S5      S5      S5      S:X  d  MV  [        UR                  S5      =(       d    S5      v   M}     g7f)	factMonth
categoryIdr"   rH      Притокr.   r   Nr/   cf	cash_linerW   )r1   r\   rr   rY   s     r   r3   r     so      A1uu[)R/ /46LL!%%P\J]_aAbdl4mq4 /E!%%.-A..   B8B*Bc              3     >#    U  H{  nUR                  S 5      T:X  d  M  [        R                  " TR                  UR                  S5      S5      S5      S:X  d  MV  [        UR                  S5      =(       d    S5      v   M}     g7f)r)   r   r"   rH   r   r.   r   Nr   )r1   r\   rr   yests     r   r3   r     sm      |fUU6]d* -/1||DHHQUU<EXZ\<]_g/hlz/z -%g+!,,fr   )rr   restc              3   *   #    U  H	  oS    v   M     g7fr   rM   r0   s     r   r3   r     s     *TU8Tr   overduec              3   *   #    U  H	  oS    v   M     g7fr   rM   r0   s     r   r3   r     s     01hr   stalec              3   *   #    U  H	  oS    v   M     g7fr   rM   r0   s     r   r3   r     s     ,eeHer   r>   r   c              3   *   #    U  H	  oS    v   M     g7fr   rM   r0   s     r   r3   r   !  s     /JqE(Jr   	underpaidr5   r-   r   u   неразнесенc                     [        U S   5      $ )Nr)   r   )fs    r   <lambda>compute.<locals>.<lambda>-  s    9QvY#7r   r*   c              3   *   #    U  H	  oS    v   M     g7f)r.   NrM   r1   r   s     r   r3   r   .  s     2'Qj'r   futc              3   *   #    U  H	  oS    v   M     g7f)r)   NrM   r0   s     r   r3   r   1  s     +s!fIsr   N)defaultr)   c              3   *   #    U  H	  oS    v   M     g7fr   rM   r0   s     r   r3   r   3  s     .XU8Xr   r7   c              3   B   #    U  H  oS    (       a  M  US   v   M     g7fr   r   NrM   r1   rs     r   r3   r   7       G}!gJIAfI}   c              3   B   #    U  H  oS    (       d  M  US   v   M     g7fr   rM   r   s     r   r3   r   8  s     D17YQvYr   c              3   *   #    U  H	  oS    v   M     g7fr   NrM   r   s     r   r3   r   ;  s     2MqE(Mr   c              3   *   #    U  H	  oS    v   M     g7fr   NrM   r   s     r   r3   r   <  s     4mV9mr   c              3   B   #    U  H  oS    (       a  M  US   v   M     g7f)r   r   NrM   r   s     r   r3   r   =  r   r   c              3   B   #    U  H  oS    (       a  M  US   v   M     g7f)r   r   NrM   r   s     r   r3   r   >  s     E]G*81U8]r   r   r   rY   freedetailrrplan_opt	plan_realplan_b2cplan_b2br   	crm_total	ft_incomeyest_ininc_leftpaidrest_sumbucketsoverdue_sum	stale_sumfm_badnext_daynext_sum
next_label	end_monthafter_next1worstafter_closeworst_closeincome_expectdue_bucketsdue_sumr   after_windowr   r   	need_livewait_winwait_win_safenext_plan_missing	wait_safe
wait_riskyafter1_fullafter1_baseunallocunalloc_sumfactsplan_missingremind_nextnmrM   )1rK   	month_keyrk   r   balancesplatform_balancerN   r/   r^   r;   r   next_month_keydayrc   	read_planr   check_plan_layoutrV   ROW_REVENUE_numm
alfa_loginincome_with_othervaluesr   r   strftimer   r   fact_by_month	OUT_LINESrs   rx   WINDOW_DAYSr   r<   r   r:   r   r   r=   r   r)   r   r   r   TEACHERS_NEXT1OB1_LUMPdict)Nr   
free_totalr   r   rX   r\   inc_txr  r   r   r   r  r  fmcolr   r   tokenr   r   r   r   r   r   r  _factr   r   rC   r  r   clsr   r   r   r   r   r   r>   r   r
  r   r	  r2   r   r  r   r   r  r  r   r   next_opsr  r  r  r  plan_next_totalgot_allplan_all	safe_plansafe_gotr  r  _eomr  r  r  r  r  r  r  r  rr   rY   r   r   sN                                                                             @@@@r   computer=     s   		BD J				B J#;A55?aF;:r*D8L[,[[H[,[[H#I			U	#B))r/I/**I&IK 
B{{Q!!"%Fs&)C2>>0B,C&C $&772bnn+=c+B#CIL LLNE
 ((U3NCCJJL!I-IH&&A..88DD A A AI  |f | |G3	I-.H'c9=M  GAt57\\B\AtxxB##As++\DB
4
 C$'E ++==J
#ueR
<Cv;D*T**H)nG000KLE,e,,I]F\"J/J//GK IK0;qQVx@XZ]0^abchai0iN1U8,-  -Z<GYG,3KKqx8PRU,VYZ[`Ya,aqx()  uA!%%*<*BaG]afghglglmrgsatGtau79G2'22K
e*C+s+T:H83a!F)x"73H8.X..H6X"NX>!E(#;X"NOPJ G}GGIDDDJ/*9@@BCO2M22G4m44HG}GGIE]EEH!(GUJXH&y(E:NM==U[[(2E2EejjRWR]R]2^_`2abD"T)Bo.By(:5?Ky(72KX%0In,K!N2E(K("K Ke K K KF Kr K!K-6KAIKT\KK#,K8AKKRK]eK K %-K 7>K  	K .9	K AF	K R[	K
 K "K -5K BLK $K 2=K EJK (K 6AK JPK ,K :EK OVK &K 584GK K &-K 9BK "K 2?K #4K $K 1;K (K 6AK  $!K  .5!K  CN!K" #K$ *%K$ 8C%K$ HJ%K KA <J C6 v 9"Ns6   *[[-[
,[	[)["[3[![c                     U S   U S   :  $ )Nr   r  rM   ds    r   is_redrA  a  s     V9q~%%r   c                    U S   (       d  g/ nU S    GH  n[        US   5      nUR                  S5      (       ac  UR                  SS5      S:  aM  UR                  SU S	[        US
   5       S[        US   5       S[        US   5       SUS   SS  S35        M  UR                  SS5      S:  aM  UR                  SU S[        US
   5       S[        US   5       S[        US   5       SUS   SS  S35        M  UR                  SU S[        US
   5       SUS   SS  S35        GM     S[        U S   5       S3SR	                  U5      -   S-   $ )uG  Блок «просрочено, не вижу оплаты» — планки месяца с прошедшей датой без факта (по контрагенту).
Для агрегатов показывает частичное покрытие «оплачено X из Y». Пусто, если таких нет.r   r"   r5   	aggregater   r   gư>   • u    — ещё r-   u    (оплачено     из r.   u   , было на r)   Nr6   )    — u-    (видна частичная оплата u    ₽, было на u    (было на u>   

⚠️ <b>Просрочено, не вижу оплаты (r   ):</b>

u   
<i>Матчим по контрагенту; если платили под другим именем/статьёй — проверь вручную.</i>)r<   r/   r9   r   r=   )r@  rA   r2   bs       r   overdue_blockrK  i  s    Y<Dy\1U8$55!%%"2T"9KK$qceAeHo->>QRWXYZ`XaRbQccijopqrypzj{i|  }M  NO  PV  NW  XZ  YZ  N[  M\  \]  ^  _UU61$KK$qcuQuX&7veAgJ>O=PP}  D  EF  GM  EN  O  ~P  Pd  ef  gm  en  oq  pq  er  ds  st  u  vKK$qcuQuX&77G&	RTSTVWXY  OuUVWdUeOfNggopsws|s|  ~B  tC  C nn or   c                     U S   (       d  gSR                  S U S   SS  5       5      n[        U S   5      S:  a  S[        U S   5      S-
   3OSnS[        U S	   5       S
U U S3$ )u'  Непогашенные планки ПРОШЛЫХ месяцев. Раньше они нигде не показывались: «Просрочено»
фильтруется по текущему месяцу, и старый долг просто выпадал из поля зрения.r   r"   rI  c           
   3   t   #    U  H.  nS US   SS  S[        US   5       S[        US   5       3v   M0     g7f)rD  r)   Nr6   rG  r5   r-   r<   r   r0   s     r   r3   stale_block.<locals>.<genexpr>  sJ      /- AfIbqM?%qx0H/IuUVW\U]N_`-s   68N
   u   
… и ещё u7   

🕰 <b>Висит с прошлых месяцев (r   rH  u   
<i>Либо оплачено под другим контрагентом, либо план так и не сбылся — проверь.</i>)r=   rV   r   )r@  rA   mores      r   stale_blockrR  {  s     W:99 /j"o/ /D8;AgJ"8Lc!G*o234RTDGaP[nH]G^^fgkflmqlr aa br   c                 d    U R                  S5      (       d  gSSR                  U S   5      -   S-   $ )u   Финмодель поехала — оптимистичный план не читается (молчать тут нельзя).r   r"   u6   

⚠️ <b>Финмодель поехала</b> — z; u%    (план из неё не беру))r/   r=   r?  s    r   
fm_warningrT    s=    55??E		RST\R]H^^  bI  I  Ir   c                 f   ^ U S   (       d  gS mSR                  U4S jU S    5       5      nSU 3$ )u   Планки-люди, где факт оказался меньше плана (план — грубая оценка). Не долг, а сверка.r   r"   c           
          [        U S   5      R                  5       nU(       a  SU S3OSnS[        U S   5       U S[        U S   5       S	[        U S
   5       3$ )Ndesc (rF  r"   rD  r5   u    — оплачено r   u    из плановых r.   )r   titler<   r   )r2   whotags      r   _rowunderpaid_block.<locals>._row  s    !F)$**, 3%qkbnQuX./u4J5QRSYQZK[J\\styz{  }D  {E  uF  tG  H  	Hr   rI  c              3   4   >#    U  H  nT" U5      v   M     g 7fr   rM   )r1   r2   r\  s     r   r3   "underpaid_block.<locals>.<genexpr>  s     5nT!WWns   uc   

💸 <b>Оплачено меньше плана (план — оценка, не долг):</b>
)r=   )r@  rA   r\  s     @r   underpaid_blockr`    s<    [>H 995an55Dstxsyzzr   c                 v    U S   (       d  gSR                  S U S    5       5      nS[        U S   5       SU 3$ )u   Факты месяца в статье-помойке «Неразнесенное списание» — списком, чтобы разнести в Финтабло.r  r"   rI  c           	   3      #    U  H?  nS US   SS  S[        US   5       SUS   =(       d    SR                  5       SS  3v   MA     g7f)	rD  r)   Nr6   rG  r.   rW  r"   -   )r   stripr   s     r   r3    unalloc_block.<locals>.<genexpr>  sZ      ,* AfIbqM?%aj0A/B%6VXH_H_HabeceHfGgh*s   AA	u7   

🗂 <b>Не разнесено по статьям (r  u@    ₽) — зайди в Финтабло и разнеси:</b>
)r=   r   )r@  rA   s     r   unalloc_blockrf    s_    Y<99 ,	l, ,DGaP]N^H_G`  ab  cg  bh  i  jr   rP  c                   ^ U[         R                  " [        S9-
  n0 nU  H  n[        US   5      nX%s=::  a  U::  d  O  M!  [	        UR                  S5      5      mS[	        UR                  S5      5      ;   =(       d    [        U4S j[         5       5      nST;   a  ST;   a  SO
U(       a  S	OS
nU(       d  M  UR                  US   0 5        X4S      R                  US5      US   -   X4S      U'   M     [        UR                  5       S S9 VV	s/ s HM  u  pU	R                  S5      (       d  M  U	R                  S	5      (       a  M5  SUS
S  S[        U	S   5       S3PMO     n
nn	U
(       d  gSSR                  U
5      -   $ s  sn	nf )u  Комиссия площадки прошла, а выплаты в тот же день нет — операцию не занесли.

Пока выплата не разнесена, её плановая планка висит непогашенной, и отчёт требует
те же деньги второй раз (31.07.2026: комиссия 11 969 есть, выплаты 700 000 нет).
r}   r)   r5   u
   рабочrW  c              3   ,   >#    U  H	  oT;   v   M     g 7fr   rM   )r1   r   r&   s     r   r3   !unposted_block.<locals>.<genexpr>  s     BbSaa6Sas   u   комиссfeepayNrT   r.   c                     [        U S   5      $ )Nr   r   zs    r   r    unposted_block.<locals>.<lambda>  s    y1r   r*   rD  r6   u#    — прошла комиссия u!   , а выплаты не вижуr"   uV   

💡 <b>Похоже, выплата не занесена в Финтабло:</b>
rI  )r   r   _WATCHDOG_DAYSr   r   r/   rb   r   r8   r:   itemsr   r=   )r  r   lor@   r   r@  via_platformkindr  vrA   r&   s              @r   unposted_blockrv    s{    
##8	8BFai  5 !%%, $uQUU6]';;bsBbSaBb?b'1,1B*U 	4ai,&,vY&7&;&;D#&F7&SFV9d#  !5NO2OuuU| r$%EE%L rd3r7)>uQuX>OOpqO 	 2 fimirirswixxx2s   F5FFc                 ~   U S   (       a  gU S   R                  S5      nU S   [        :  a  S[        U S   5       S3OSnU R                  S	5      (       a  S
OSn[	        U 5      (       d=  S[        U S   5       SU S[        U S   5       U S[        U S   U S   -
  5       SU 3$ U S   U S   -
  nU S   U S   -   U S   -
  nUS:  a  S[        U5       3OS[        U* 5       3nS[        U S   5       SU S[        U S   5       U S[        U5       SU S[        U S   5       S[        U S   5       SU SU 3$ )u  Две ступени: сначала живые деньги, потом чем закрываем.

Ожидаемый приход намеренно не растворяется в главной цифре — иначе в первый день
месяца отчёт писал «спокойно, остаётся 3 165 103» при 1,4 млн на счетах и нулевом
факте, потому что считал весь месячный план уже пришедшим.
r  u   ⚪ <b>Прогноз неполный.</b> Плана доходов на этот месяц ещё нет — не могу оценить кассу. Пришли цифры от Оли и Блиновой, занесу и пересчитаю.r   %d.%mr   u   , из них u    уже на площадкеr"   r  u   
⚠️ Плана на следующий месяц ещё нет — приход за границей месяца не учтён.uD   🟢 <b>Живых денег хватает.</b> На счетах r   u   , выплаты до rG  r
  u   . Остаётся r  r   r  r   u)   закрывается, остаётся u/   не закрывается, не хватит uI   🔴 <b>Живых денег не хватает.</b> На счетах u   . Разрыв u   .
Ждём к u    ≈ r  u   , из них надёжных )r'  r   r   r/   rA  )r@  tillplattailgapafterclosess          r   verdict_liner    s    	q\?##G,DRST]R^aiRi^E!I,/00MNoqDuu()) X/1 	!99VW\]^_e]fWgVh i''+fE%)2E1Ftf M$$)!F)an*D$E#FavO 	P K.1V9
$CfI/**Q{^;ELQUVJ9%,HDUE6]OT WX]^_`f^gXhWi j##'&eAiL.A-B4&X]^aXbWc d&eAjM&:%;;ZQ'()vhav? @r   )u   январяu   февраляu
   мартаu   апреляu   маяu   июняu   июляu   августаu   сентябряu   октябряu   ноябряu   декабряc                    U S   (       a  S[        U S   5       S3$ U S   R                  S:X  a;  S[        U S   R                  S-
      S[        U S	   5       S
[        U S   5       S3$ / nU S    Ht  nUS   (       a  [	        SUS   -  US   -  5      OSnUS   (       a  SOSnUR                  US   S:  a  SUS    SU S[        US   5       U 3OSUS    SU S35        Mv     [        S U S    5       5      n[        S U S    5       5      nU(       a  [	        SU-  U-  5      OSn[        S U S    5       5      nSU S[        U5       S 3S!R                  U5      -   $ )"u	  Доходы месяца. В первый день — одной строкой: построчные нули не несут информации,
деньги приходят каждый день начиная со второго (Наташа, 01.08.2026).r  u   ⚠️ <b>Плана доходов на месяц нет</b> — жду цифры от Оли и Блиновой
(за месяц пришло r   rF  r   r|   u   <b>Доходы u   :</b> пришло r   rE  r   u+    — месяц только началсяr  r   d   r   r   r   u    ⚠️r"   r   rD  r%   rG  u   % · ждём u   % ✅c              3   *   #    U  H	  oS    v   M     g7fr   rM   r   s     r   r3   income_block.<locals>.<genexpr>  s     9&8eH&8r   c              3   *   #    U  H	  oS    v   M     g7fr   rM   r   s     r   r3   r    s     ;(:1vY(:r   c              3   *   #    U  H	  oS    v   M     g7f)r   NrM   r   s     r   r3   r    s     5"4Qy"4r   u   <b>Доходы: u   % плана · ждём </b>
rI  )r   r  
_MONTHS_RUr   r   r9   r;   r=   )	r@  rA   r   pctrisk	got_total
plan_totalpct_allr   s	            r   income_blockr    s    	116q~1F0GqJ 	Kz~~":aj.>.>.B#C"DDWX]^_`k^lXmWn oan-..Y[ 	\D34V9eC!E(NQvY./!gJyBy1} 1V9+U3%~eAfI>N=OPTvV,065U*K	M  
 9a&899I;/(:;;J5?eC)Oj01QG5!O"455Dwi'@tVTW[W`W`aeWfffr   c                 h  ^  T S   nSR                  S [        T S   S S9 5       5      nSR                  U 4S j[        T S   R                  5       S	 S9 5       5      =(       d    S
nT S   (       a  S[        T S   5       S3OSnT S   [        :  a  S[        T S   5       S3OSnT S   (       a  ST S   S S  S3OSnSnT S   (       a1  T S   S   S   S S nSR                  S T S    5       5      n	SU SU	 3nSUR                  S 5       S![        T 5       S"U S#[        T S$   5       S%U U S&[        T 5       S'T S(   R                  S 5       S)[        T S*   5       S+U U [        T 5       [        T 5       [        T 5       [        T 5       [        T S,   T S   5       [        T 5       U 3$ )-Nr   rI  c              3   Z   #    U  H!  u  pS [        U5       S[        U5       3v   M#     g7f)rD  rG  N)r'   r   r1   r&   rJ  s      r   r3   render_pulse.<locals>.<genexpr>  s)     oFndatIaL>uQxj9Fns   )+r   c                     U S   * $ Nr|   rM   rm  s    r   r   render_pulse.<locals>.<lambda>  s    ijklimhmr   r*   c           
   3   h   >#    U  H'  u  pS U S[        U5       [        UTS   5       3v   M)     g7f)rD  rG  r   N)r   r   )r1   r&   ru  r@  s      r   r3   r    s<      W U 1#U58*]1a
m-L,MN Us   /2r	  c                     U S   * $ r  rM   rm  s    r   r   r    s    PQRSPTur   u
   • нетr   u     (вчера пришло +rF  r"   r   u7   
На площадке «Рабочие руки»: <b>u4   </b> — уже отложено на выплатыr  u3   

⚠️ <b>Через несколько дней r     uT    месяц</b> — плана доходов ещё нет, пришли цифрыr  r   r)   r6   r7   c              3   `   #    U  H$  n[        US    5       S[        US   5       3v   M&     g7f)r5    r-   NrN  r0   s     r   r3   r    s0     dRcQN1U845QuQuX6GHRcs   ,.u#   

<b>Следом за окном (z):</b> u   💰 <b>Марс · rx  r  u   

<b>Кассы:</b>
u   
Свободно: <b>r   z</b>z

u   

<b>Выплаты до r   rX  r
  rH  r  )r=   r:   rq  r   r   r'  r  r  rK  rR  r`  rf  rv  rT  )
r@  r\   accsrk  r   rz  remindr   whenrA   s
   `         r   render_pulser    s+   	'
A99ofQx[^mFnooD
)) W &q'7'='='?_ UW W gZf DEiL)%)*=)>a@VXD }x' GuQz]G[F\  ]Q  R-/ 	 =! Fagbqk]  Sg  h')  C #F+BQ/yydRSTbRcdd5dV74&I
qzz'236?
 #f %  %ai 01dVD6?
 ""#L/":":7"C!DBuQy\GZF[[cdgch%
[^,_Q-?,@qAQ@R!G*aj1
2:a=/(		r   c                 B    U S   [         :  a  gS[        U S   5       S3$ )u   Сноска про транзит «Рабочих рук» — только пока на счёте реально что-то висит.r   r"   u    <i>(«Рабочие руки» ui    не в счёте — уже выплачено педагогам, ждёт разнесения)</i>
)r   r   r?  s    r   rr_noter    s3    w-eAdGn-=  >h  i  ir   c                 $    U S   [         :  a  S$ S$ )u   Пункт «разнести Рабочие руки» в списке дел — тоже только пока транзит висит.r   uk   
4. Разнести «Рабочие руки» — факт расходов станет честным.r"   )r   r?  s    r   rr_todor  "  s7    }~  @D  ~E  IQ  ~Qy  Y  WY  Yr   c                    U S   n[        [        S [        U S   5       5       5      5      nSR                  S [	        U S   S S9 5       5      nS	R                  S
 [	        U S   R                  5       S S9 5       5      nS[        R                  " U5       S[        U 5       S[        U S   5       SU S[        U 5       SU S3SR                  S [        U S   5       5       5      -   SR                  / SP[        U S   5       PSPU P[        U 5       P[        U 5       P[        U 5       P[        U 5       P[        U 5       PSP[        [         5       PSP[        U S   5       PSP[        U S   5       PSP[        U S   5       PS P[        U S!   5       PS"P[        U S   5      S# PS$P[        U S   5      S# PS%P[        [         5      S# PS&P[        U S'   5      S# PS(P[        U S   5      S# PS)P[        U S*   5      S# PS+P[        U S,   5       PS-P[        U S.   5       PS/P[#        U 5       P5      -   $ )0Nr   c              3   *   #    U  H	  u  pov   M     g 7fr   rM   )r1   r3  rD   s      r   r3   render_full.<locals>.<genexpr>,  s     C)BgaA)Br   r>   rI  c              3   H   #    U  H  u  pS U S[        U5       3v   M     g7f)   — z: Nr   r  s      r   r3   r  -  s%     _6^daT!BuQxj)6^s    "r   c                     U S   * $ r  rM   rm  s    r   r   render_full.<locals>.<lambda>-  s    YZ[\Y]X]r   r*   u    · c              3   F   #    U  H  u  pU S [        U5       3v   M     g7f)r  Nr  )r1   r&   ru  s      r   r3   r  .  s#     f4eDA1U1XJ'4es   !r   c                     U S   * $ r  rM   rm  s    r   r   r  .  s    `abc`d_dr   u&   💰 <b>Марс · деньги на z</b>

u6   

<b>Сейчас на счетах свободно: r   r  u9   
<b>Календарь выплат на 2 недели (u    ₽):</b>
c              3   P   #    U  H  u  pnS U S[        U5       SU 3v   M     g7f)r  z  <b>z</b>  Nr  )r1   rB   rD   lbls       r   r3   r  5  s-     fLejbSd2$eE!H:VC59Les   $&r"   u6   

<b>До конца месяца заплатить: r   u	   </b> — u$   

<b>1-го — педагогам: u?   </b>

<b>Придёт дохода (реалист. план): r   u   </b>
Пришло r   rE  r   u<   . Оптимистичный план (финмодель): r   u>   .

<b>🛬 Когда кончатся деньги</b>
<pre> z>9u      сейчас
-u+      выплаты до конца мес.
-u&      педагоги 1-го
---------
 r  u%      без нового дохода
+u?      но приходит доход по плану
---------
 r  u     остаётся после 1-го
</pre>
<b>Точка риска — начало месяца.</b> Обязательных выплат больше, чем на счёте; разрыв закрывает входящий доход.

<b>⚠️ Оборотный кредит 610к — гасим досрочно</b>
Кредит дорогой (высокие проценты), поэтому хотим закрыть его как можно скорее — <b>кидаем туда свободную наличку</b> по мере появления. Но закрыть разом сейчас касса не тянет: остаток после всех выплат упал бы до <b>r  u1   </b>, а без нового дохода — <b>r  u  </b> 🔴. Поэтому гасим <b>частями</b>, наличными, а не одним платежом.

<b>Что делать</b>
1. Следить за приходами до конца месяца — это разница между «спокойно» и «впритык».
2. Резерв к 1-му — если приходы отстают, придержать необязательное (события Оли — гибкая статья).
3. Оборотный 610к — кидать свободную наличку на досрочное гашение (дорогие проценты), но не одним платежом.)r   r;   rF   r=   r:   rq  rK   ru_dater  r    r  rK  rR  r`  rf  rT  r+  r  )r@  r\   cal_sumblexps        r   render_fullr  '  s   	'
A CCak)BCCDG	_fQx[o6^_	_B
++fF1Y<;M;M;OUd4ef
fC
0a0A?
 ??B1V9~>NfUWTXXZ1:, CCJ)<		Y
 ))fLYZ[cYdLef
f	g
j j j? j?DQz]?S>TjT]j^a]bj
j(^,j-<Q-?,@jANqAQ@RjS]^_S`Rajb-j .3>-B,CjDDj EH*DVCW	jX	j
 AkN+,j
 -3j
 47q~3F2Gj
 HDj
 EH  IJ  KU  IV  EW  DXj
Xj &	N2jj *r"j#j #j$j '
OBj j *r"j#j - !"%j&Yj( Z]  ^_  `m  ^n  Zo  Yp)j( pa)j( be  fg  hu  fv  bw  ax)j(xJ)j4 1:,5j	!r   c                     SU S   R                  S5       S[        U S   5       S[        [        5       S[        U S   5       S	3	$ )
Nu,   🔴 <b>Марс · КРАСНЫЙ ФЛАГ r   rx  uS   </b>
После обязательных выплат (остаток месяца r   u    + педагоги uW   ) и с учётом входящего дохода остаток уходит в <b>r  u{   </b>.
Нужно: ускорить приходы или придержать гибкие расходы (события).)r'  r   r+  r?  s    r   render_flagr  S  s    
6qz7J7J77S6T UYY^_`ak_lYmXn  oC  DI  JX  DY  CZ Z``efghufv`w_x y@	Ar   c                    [         R                  R                  S5      n[         R                  R                  S5      nU(       a  U(       d  [        S5      e[        R
                  R                  X SSS.5      R                  5       n[        R                  R                  SU S3US	S
9n[        R                  " U5      R                  S5      $ )NTELEGRAM_BOT_TOKENTELEGRAM_CHAT_IDu4   нет TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID в .envHTMLtrue)chat_idtext
parse_modedisable_web_page_previewzhttps://api.telegram.org/botz/sendMessage   )datatimeoutok)osenvironr/   RuntimeErrorurllibparse	urlencodeencoderequesturlopenjsonload)r  tokchatr  r   s        r   send_telegramr  ]  s    
**..-
.Crzz~~FX7YdQRR<<!!dPV?E#G HHN 	!=cU,OVZdfgA99Q<D!!r   c                     [         R                  SS  n SnSU ;   n[        R                  R	                  5       n[        U 5       H9  u  pEUS;   a  UnUS:X  d  M  US-   [        U 5      :  d  M)  [        XS-      5      nM;     US:X  aF  SS KnUR                  USU ;   S	9nU(       a  [        S
[        U5       35        g [        SU S35        g [        U5      n/ n	US:X  ay  U	R                  S[        U5      45        UR                  5       S:X  a  U	R                  S[!        U5      45        [#        U5      (       a  U	R                  S[%        U5      45        O,U	R                  U[        [         [$        S.U   " U5      45        U	 H<  u  pU(       a  [        U5      n[        SU
 SU 35        M*  [        SU
 SU S35        M>     g )Nr|   autoz--send)pulsefullflagr  r   z--dater   r   z--commit)commitu!   [month] отправлено: ok=z
===== month =====
rI  r  r  r  )r  r  r  [u   ] отправлено: ok=z
===== z =====
)sysargvr   r)   r   	enumeraterV   r   report_monthbuildprintr  r=  r9   r  weekdayr  rA  r  )argsmodesendr)   iar  r  r@  msgsr%   r  s               r   mainr  g  s   88AB<DDtD== D$::D=QUSY.Ta%[)D	   w!!$zT/A!B5mD6I5JKL 	 )$r23A Dv~Wl1o./<<>QKKQ01!99KKQ01T\;P[\]abcdefg
t$BAdV6rd;<HTF(4&34 r   __main__r   )NN)rT   )P__doc__r  r  rer   r   urllib.requestr  urllib.parser  rK   marginsr#  cashflowr   rl   rn   income_linesr   r   r   r   r   r   r   r	   r
   r   r   r   r   r   r   r   LINESr   	plan_syncr   r+  r,  r*  r   r   r    r'   rF   rN   r^   rc   rf   rh   ru   rs   rx   r<   r   r   r   r   r   r=  rA  rK  rR  rT  r`  rf  rp  rv  r  r  r  r  r  r  r  r  r  r  __name__rM   r   r   <module>r     sZ  * K J J J      C C C C C xx 1 .?+0
g
  (a", U	L	."}K@&o$	bI	{j y<@8t
g,@iY
)X"&5R zF r   