
    YmjS                        S r SSKrSSKrSSKrSSKrSSKr\R                   V s1 s H  o R                  iM     sn r	S r
SS jrS rS rS rS rS	rS
rSS jrS rSS jrSrSrS rSS jrS rS rS rS rS rS rSS jrgs  sn f )u}  Месячный итог Марса: что случилось в закрывшемся месяце и с чем входим в новый.

Шлётся 1-го числа в 08:30 Екб, перед пульсом, и САМ снимает доходные планки закрывшегося
месяца — раньше это делал отдельный таймер в 09:30. Из-за разнесённых по времени шагов
отчёт и снятие могли разъехаться, а ручной прогон снятия накануне вечером (31.07.2026,
18:13) оставлял отчёт без плана: планки уже удалены, сравнивать не с чем.

Порядок внутри: считаем план↔факт (планки ещё на месте) → журнал → удаление → отправка.
    Nc                 <    [        U 5      S R                  SS5      $ )N, )roundreplace)xs    "/home/rasp/finsync/report_month.pymoneyr
      s    Ahq\""3,,    c                 |    U(       d  SSS.$ U(       a  U" U 5        U  H  nU" US   5        M     [        U 5      SS.$ )u\  Снять доходные планки закрывшегося месяца.

Журнал пишется строго ДО мутаций: если упадём посреди удаления, из него видно, что
восстанавливать. Без --commit не трогаем ничего — это живые деньги.
r   T)retireddry_runidF)len)opscommitdeletejournalos        r	   retirer      s@     ..qw 3xE22r   c                 B    [        S U R                  5        5       5      $ )ug   Есть ли в месяце план на слагах, которых уже нет в каноне.c              3   h   #    U  H(  u  pU[         ;  =(       a    UR                  S 5      v   M*     g7f)planN)_KNOWN_SLUGSget).0svs      r	   	<genexpr>has_legacy.<locals>.<genexpr>+   s'     S?41q$6v6?s   02)anyitems)summarys    r	   
has_legacyr$   )   s    S7==?SSSr   c                    [         R                   Vs0 s H  oR                  U_M     nnSSS.SSS.S.nU R                  5        H  u  pEUR	                  U5      nU(       a  UR
                  [         R                  :X  d  US:X  a  SOSnX7   S==   UR	                  SS5      -  ss'   X7   S==   UR	                  SS5      -  ss'   M     U$ s  snf )	u   План и факт по B2C/B2B — единственный срез, который сходится при легаси-слагах:
abonementy, uroki и gruppy все лежат в B2C, поэтому итог категории не врёт.        )r   fact)B2CB2Bb2br)   r(   r   r'   )ilLINESslugr"   r   categoryCAT_B2B)r#   lknownoutr-   r   linecats           r	   by_categoryr5   .   s     !#)1VVQYE),c35O
PC==?yy"**!<eTYAEE&#..AEE&#..	 #
 J *s   Cc                    [        U 5      (       a  [        U 5      R                  5        VVs/ s HS  u  pUS   (       d  US   (       d  M  SU S[        US   5       S[        US   5       S[        US   US   -
  5       3PMU     nnnSSR	                  U5      -   S	-   $ / n[
        R                   H  nU R                  UR                  5      nU(       a,  UR                  S5      (       d  UR                  S5      (       d  MS  UR                  SUR                   S[        US   5       S[        US   5       S[        US   US   -
  5       35        M     SSR	                  U5      -   $ s  snnf )
uQ  Доходы: прогноз ↔ факт по строкам канона.

При легаси-слагах построчная Δ врёт (план на abonementy, факт на uroki — одни и те же
деньги выглядят как −2 400 000 и +2 907 064), поэтому сводим по категориям.
r   r'     u   : план u    / факт u    / Δ u1   <b>Доходы: прогноз ↔ факт</b>

un   
  ⚠️ план заводился на старых слагах, построчно не сравнить)r$   r5   r"   r
   joinr+   r,   r   r-   appendname)r#   kr   rowsr0   s        r	   income_vs_planr>   ;   sy    ' (0668T8DAAfI65"QC{56#3"4LqyAQ@R SAfI&	12358 	 T EtyyQUV BB 	CDXXKKvquuV}}bE!F),<+=\%PQRXPYJZI[ \&	AfI 5679 	:	  @$))D/QQTs   E/<E/c           
          [        U R                  5       S S9 VVs/ s H  u  pSU S[        U5       3PM     nnnSSR                  U5      -   $ s  snnf )ub   Расходы месяца по строкам ДДС, от большего к меньшему.c                     U S   * $ )N    zs    r	   <lambda>!outcome_vs_plan.<locals>.<lambda>S   s    Z[\]Z^Y^r   keyr7       — u*   <b>Расходы по строкам</b>
r8   )sortedr"   r
   r9   )
fact_linesr<   r   r=   s       r	   outcome_vs_planrL   Q   sT    06z7G7G7I0_`0_b5q
#0_D`8499T?JJ as   Ag?g?c                     [        U S S 5      [        U SS  5      p2[        U5       H  nUS-  nUS:X  d  M  SUS-
  p2M     US SU 3$ )N      rA   r      02d.)intrange)mmbackmoy_s        r	   _prev_monthrZ   ]   sZ    2AKRV4[
a7A  XQqc?r   c                    [        U 5      n[        R                  R                  U5      (       d  U$  [	        USS9 n[
        R                  " U5      =(       d    0 R                  S5      =(       d    0 nSSS5        UR                  5        VVs0 s H  u  pVU[        U5      _M     nnnWR                  5        Hn  u  pUR                  USSSS.5      n	U	R                  S5      (       a  M3  UR                  S5      (       d  MK  US   U	S'   U	R                  SS5      U	S   -
  U	S	'   Mp     U$ ! , (       d  f       N= f! [        [        4 a    Us $ f = fs  snnf )
u  Подставить план из журнала снятия, если живых планок уже нет.

Обычно месячный отчёт читает план до снятия, но планки могли снять раньше срока
вручную (31.07.2026, 18:13) — тогда план нулевой, и весь факт выглядит как рост.
Журнал пишется перед удалением, поэтому в нём план сохранился.
utf-8encodingr#   Nr&   )r   r'   deltar   r'   r_   )journal_pathospathexistsopenjsonloadr   OSError
ValueErrorr"   dict
setdefault)
rU   r#   rb   fsavedr<   r   r2   r-   rows
             r	   with_journal_planrn   f   s    D77>>$$)QYYq\'R,,Y7=2E * #*--/
2/$!1d1g:/C
2;;=nnTCs#KLwwv155==F)CK7763/#f+=CL	 !
 J *)Z  
2s4   
E  8D/5E  E/
D=9E  =E   EEc                 D  ^ / n[        TS5      [        TS5      T4 Vs/ s H  o`R                  U5      PM     nn[        U5      (       a  SR                  S U 5       5      n[	        U5      [        U5      -
  [	        U5      -  [        ::  a  UR                  SU 35        OUS   US   s=:  a	  US   :  a  O  OUR                  SU 35        OUUS   US   s=:  a	  US   :  a  O  OUR                  SU 35        O'US   US   :  a  S	OS
n	UR                  SU	 SU 35        TSS  S[        TSS 5      S-
   3n
U R                  U
5      nU(       aG  U R                  T5      (       a1  UR                  SSU T   -  U-  S-
  S SU
 S[        U5       S35        [        U4S jU R                  5        5       5      n[        U4S jU R                  5        5       5      nU(       a0  UR                  S[        U5       S[        U5       SX-  S S35        U H2  u  pnUR                  SU S[        U5       S[        U5       35        M4     [        UR                  5       5      nU R                  T5      =(       d    SnU(       a;  U(       a4  UR                  S SU-  U-  S! S"[        U5       S[        U5       S35        [        S# UR                  5        5       5      nU(       a4  UU-  [        :  a'  UR                  S$SU-  U-  S! S%[        U5       35        US:  a`  UU-
  nUS:  a,  UR                  S&[        U5       S'[        U* 5       S(35        O)UR                  S)[        U5       S*[        U5       35        S+S,R                  U5      -   $ s  snf )-u  Выводы по механическим правилам — отчёт считает, а не сочиняет.

Сравнения идут по выручке b2c+b2b: по «Притоку» июль 2025 равен 37,7 млн из-за
возврата депозита, и любой вывод на этой базе был бы мусором (см. cashflow.cash_line).
rN   rA   u    → c              3   8   #    U  H  n[        U5      v   M     g 7fNr
   )r   r   s     r	   r   #how_are_we_doing.<locals>.<genexpr>   s     3s!U1XXss   u-   • Третий месяц на плато: r   u3   • Рост третий месяц подряд: u3   • Спад третий месяц подряд: u   вышеu   нижеuE   • Выручка колеблется, месяц закрылся u*    трёхмесячного начала: NrR   rO   u   • Год к году d   +.0fz% (rI   )c              3   d   >#    U  H%  u  pUS S TS S :X  d  M  USS TSS ::  d  M!  Uv   M'     g7f)rO   NrN   rB   r   r<   r   rU   s      r	   r   rs      s<     SODAqu12a1Ra5BrPQF?aaOs   0
0	0c           	   3      >#    U  H:  u  pUS S [        [        TS S 5      S-
  5      :X  d  M(  USS TSS ::  d  M6  Uv   M<     g7f)rO   NrA   rN   )strrS   rx   s      r	   r   rs      sT      Ies3r!"v;?33 89"1BQ 1s   'A
A	Au   • С начала года u    против u$    за те же месяцы — в z.1fu	    разаu   • u.    второй месяц ниже плана: u    из u   • Расходы съели .0fu   % выручки (c              3      #    U  HG  u  pS UR                  5       ;   d*  SUR                  5       ;   d  SUR                  5       ;   d  MC  Uv   MI     g7f)u   кредитu   займu   процентN)lower)r   r<   r   s      r	   r   rs      sI      k2TQ AGGI-qwwy1HL\`a`g`g`iLi q2s   AA	Au6   • На обслуживание долга ушло u   % выручки — u;   • Месяц закрыт заёмными: пришло uL    кредитом или займом, а операционно минус u>    — без займа платить было бы нечемu!   • Из денег месяца uy    — заёмные (кредит или заём), это не выручка; операционно месяц дал u   <b>Как идём</b>
r8   )rZ   r   allr9   maxmin_FLATr:   rS   r
   sumr"   values_DEBT_ALARM)revenuerU   plan_missesoutcome_linesfin_inr2   r   trichain	direction	prev_yearsameytdytd_prevr;   r'   r   totalrevdebtgaps    `                   r	   how_are_we_doingr   ~   s    C$/A$6B8JB#O
P#Oa;;q>#OC
P
3xx3s33 Hs3x3s8+u4JJFugNOVc!f%s1v%JJLUGTUVc!f%s1v%JJLUGTU&)!fs1vo
:IJJ^_h^i  jT  UZ  T[  \  ]bq6(!C12K!O,-I;;y!DB

,S72;->-E-KD,Q R kuT{m16 	7
SGMMOS
SC I I IH

25:,nUS[_L] ^99<8LIW 	X'D

T$MeTXk]Z`afgkal`mno ( $$&'E
++b/
QC

3C%K#4Ec3J KU|nF5:,a9 	: k]002 k kD
tcz[(

KCRVJY\L\]`Kaavw|  ~B  xC  wD  E  	F zEk7JJTUZ[aUbTc d??DcT{m  LJK L JJ:5=/ J__deh_i^jl m%		#66c Qs   N)u   январяu   февраляu
   мартаu   апреляu   маяu   июняu   июляu   августаu   сентябряu   октябряu   ноябряu   декабря)u   январьu   февральu   мартu   апрельu   майu   июньu   июльu   августu   сентябрьu   октябрьu   ноябрьu   декабрьc                   ^
^^ 0 0 smmU H  nU R                  U5      =(       d    0 nUR                  S0 5      R                  5        H  u  pETR                  US5      U-   TU'   M     UR                  S0 5      R                  5        H  u  pETR                  US5      U-   TU'   M     M     T(       d  gUU4S jm
/ n[        TU
4S jS9 H@  nT
" U5      nUb	  S	U-  S
 S3OSnUR                  SU SU S[	        TU   5       S35        MB     [
        [        US   SS 5      S-
      S[
        [        US   SS 5      S-
      3n	SU	 S3SR                  U5      -   $ )u  Маржинальность направлений за три месяца — в процентах от выручки направления.

Проценты, а не рубли: «1-1 — 5 030 610» не говорит, эффективно ли направление,
а «68%» говорит. Сумма остаётся рядом, чтобы был виден масштаб.

Окно трёхмесячное намеренно: за один месяц события и B2B дают случайный разброс,
и направление, которое просто не проводило мероприятий, выглядело бы убыточным.
marginr&   r    c                 J   > TR                  U S5      nU(       a  TU    U-  $ S $ )Nr&   )r   )draggr   s     r	   _pctmargin_block.<locals>._pct   s'    GGAsOs1vz(D(r   c                 <   > T" U 5      S L T" U 5      =(       d    S* 4$ )Nr   rB   )rD   r   s    r	   rE   margin_block.<locals>.<lambda>   s    Q447<a'Ir   rG   Nrt   r{   %u   нет выручкиr7   rI   z (rv   r   rN   rA   u   –u-   <b>Маржа по направлениям (z)</b>
r8   )r   r"   rJ   r:   r
   _MONTHS_RU_NOMrS   r9   )pnl_by_monthmonthsmpnlr   r   r=   psharespanr   r   r   s             @@@r	   margin_blockr      sx    2HCq!'RGGHb)//1DAWWQ_q(CF 2GGIr*002DAWWQ_q(CF 3	  ) DCIJG%&]373-q!8Ob5r%A-:; K S2A/!345SFSUJWYXYNH[^_H_9`8abD:4&H499UY?ZZr   c                     / n[        U S S9 HM  nUR                  SUS   5      nUS   SS U:X  d  M&  XR:  d  M-  UR                  US   SS US	   XTS	   45        MO     U$ )
u  Крупные выплаты нового месяца, которые РЕАЛЬНО предстоят.

Берём остаток долга (`due`), а не исходную сумму планки: планка — оценка, и если она
уже погашена фактом (ЗП педагогов выплачена 31.07 и разнесена), в список ей не место.
Раньше блок строился по сырым плановым операциям и показывал оплаченное.
c                 "    U S   SS  U S   S S 4$ )NdaterO   rN   rB   rC   s    r	   rE   big_upcoming.<locals>.<lambda>   s    &	!"qy!}'Er   rG   duevaluer   rO   N   r4   )rJ   r   r:   )r   nm	thresholdr=   r   r   s         r	   big_upcomingr      ss     DCEFeeE1W:&V9QR=B3#3KK62A%#x@A G Kr   c                     SSK n/ UpCU  H^  u  pVpxSn	XBR                  :  a4  UR                  X5      (       a  [        XG5      n
S[	        U
5       S3n	XJ-  nUR                  XVXy45        M`     U$ )u  Разметить, какие предстоящие выплаты уже обеспечены остатком площадки.

Гасим по порядку дат и уменьшаем остаток: иначе одна и та же сумма помечала бы
каждую планку ЗП, и две пометки по 598 887 читались бы как 1,2 млн покрытия.
r   Nr   u    (из них u    уже на площадке))reportRR_NOISEplatform_hintr   r
   r:   )r=   platformrpr2   leftwhenwhatr   r4   hinttakes              r	   mark_platformr      sx     H"&E++""2"23"="=t#D#E$K=0NODLD

D,- #' Jr   c                 8   U S   nUSS  S[        USS 5      S-
   3nU S   R                  U5      nU(       a  SS	U S
   -  U-  S	-
  S S3OSnSR                  S U S    5       5      nSR                  [        SS[        [        USS 5      S-
      SUSS  S[        U S
   5       S[        U S   5       S[        U S
   U S   -
  5       U 3[        U S   5      [        U S   5      [        U S   XS   U S   U R                  SS5      5      [        U S   U S   5      U(       a2  S[        U S    5       S![        U S"   5       S#[        U S$   5       S%U 3O.S[        U S    5       S![        U S"   5       S#[        U S$   5       3S&U S'U S(    3/5      5      $ ))u  Месячный итог одним сообщением. Порядок блоков: итог → доходы → расходы →
как идём → маржа → с чем входим → техническая строка про снятые планки.rU   NrN   rR   rO   rA   revenue_by_monthu    · год к году rt   r   ru   r   r   r8   c           	   3   T   #    U  H  u  pp4S U SU S[        U5       U 3v   M      g7f)r7   rI   r   Nrr   )r   r   r   r   r   s        r	   r   render.<locals>.<genexpr>  s8      A1?-$a 4&dV1U1XJtf=1?s   &(	big_aheadz

u   📊 <b>Марс · итоги r   u   </b>
Выручка u    · расходы outcomeu    · разница r#   r   r   r   r&   r   r   uD   <b>С чем входим в новый месяц</b>
  Касса freeu   , на площадке r   u   
  План доходов 	plan_nextu"   
  Крупное впереди:
u.   Снято доходных планок за z: r   )
rS   r   r9   filter_MONTHS_RU_GENr
   r>   rL   r   r   )r   rU   r   r   yoyaheads         r	   renderr      s$    
4Bbq6(!C12K!O,-I $$Y/DOS#C!I,$6$=$CD#I
KY[CII A12;A AE;;vd
).RVq*I)J!BqrF8 T)-..A%)BUAV W)q| ;<=cU	D 	q|$/*+-.m4DaFXx-	/QuXq{+ 9>ai())A%*BVAW X%%*1[>%:$; <--2G	5ai())A%*BVAW X%%*1[>%:$;	= 9Bq|nM!%   r   c           	          [         R                  R                  [         R                  R                  [         R                  R	                  [
        5      5      SSU R                  SS5      -  5      $ )Nexportszincome_retire_%s.jsonrR   -)ra   rb   r9   dirnameabspath__file__r   )rU   s    r	   r`   r`     sJ    77<<(ABI/"**S#2FFH Hr   c                     [        U 5      n[        R                  " [        R                  R	                  U5      SS9  [        USSS9 n[        R                  " XUS.USSS	9  S S S 5        U$ ! , (       d  f       U$ = f)
NT)exist_okwr\   r]   )monthr#   deletedFrA   )ensure_asciiindent)r`   ra   makedirsrb   r   rd   re   dump)rU   r#   r   rb   rk   s        r	   write_journalr   !  sk    DKK%5	dC'	*a		BsC%	3 
+ K 
+	* Ks   	A..
A=c           	         SSK n0 nU H  n[        R                  " USS9 H  nUR                  S5      (       a  M  UR	                  U R                  UR                  S5      S5      S5      S:w  a  MR  UR                  S	5      nU(       d  Ml  UR                  US
5      [        UR                  S5      =(       d    S5      -   X6'   M     M     U$ )u  Выручка по месяцам за несколько лет — база всех сравнений.

Считается по «Притоку» уже ПОСЛЕ фикса cash_line: возврат депозита и переводы между
счетами туда не попадают, иначе июль 2025 равнялся бы 37,7 млн вместо 1,6 млн.
r   NincomeyeargroupisPlan
categoryIdr   u   Приток	factMonthr&   r   )cashflowcoreft_transactionsr   	cash_linefloat)catsyearscfresrX   trU   s          r	   _revenue_by_monthr   *  s     
C%%1H=AuuX||DHHQUU<%8"=xHNZ{#Br''"c*U155>3FQ-GG >  Jr   c                    / n[         R                   H  nUR                  UR                  5      U =(       d    0 R                  UR                  5      pTU(       a  U(       d  MQ  UR                  S5      (       d  Mi  US   US   :  d  Mw  UR                  S5      (       d  M  US   US   :  d  M  UR	                  UR
                  US   US   45        M     U$ )u   Строки дохода, недобравшие план два месяца подряд — тянущийся недобор, не разовый.r   r'   )r+   r,   r   r-   r:   r;   )summary_prevr#   r2   r0   curprevs         r	   _plan_missesr   >  s    
CXXKK',*<")A)A!&&)IT$776??s6{S[8TXXf=M=MRVW]R^aeflamRmJJFS[9:  Jr   c                 R  ^ ^! SSK nSSKnSSKnSSKn[	        [
        R                  " U 5      5      m [        T SS 5      n[
        R                  " 5       n[
        R                  " USS9n[        T UR                  T U5      5      m!UR                  UT 5      n	UR                  [	        T 5      [
        R                  " [        [	        T 5      SS 5      SS95      n
[        U[        XfS-
  15      5      n0 n[
        R                  " USS9 H  nUR                  S5      (       d  UR                  S	5      T :w  a  M0  UR!                  UR                  UR                  S
5      S5      S5      nUR                  US5      [#        UR                  S5      =(       d    S5      -   X'   M     Sn[
        R                  " USS9 H  nUR                  S5      (       d  UR                  S	5      T :w  a  M0  UR!                  UR                  UR                  S
5      S5      S5      S:X  d  Mg  U[#        UR                  S5      =(       d    S5      -  nM     [	        T S5      [	        T S5      T /n UR%                  U5      nUR+                  5       u  nn[
        R                  " U 5      n[-        UR/                  [
        R                  " SS9U5      R1                  5       5      nUR3                  5       nUR5                  U5      nUR7                  UUR9                  US9U [
        R                  " U 5      U [:        R<                  " SS9-   5      n[?        US   U5       VVVVs/ s H  u  nnnnUURA                  U5      UU4PM     nnnnn[C        XS U U!4S jS9n[E        [G        T UR                  T S5      [-        UR1                  5       5      T!XUUUUUU[I        USS U5      US   [K        U
T!5      S95      $ ! [&         a  n[)        SU 35        0 n SnAGNSnAff = fs  snnnnf )u  Собрать месячный итог за закрывшийся месяц и (при commit) снять его планки.

Данные читаются ДО снятия — иначе план исчезнет и сравнивать будет не с чем.
r   NrO   r   r   rA   r   r   r   r   r   r&   r   
__fin_in__rN   u,   ⚠ маржа не посчиталась: )r   )r   -   )daysunpaidc                 6    [         R                  " SSU  35      $ )NDELETEz/v1/transaction/)r   ftmut)is    r	   rE   build.<locals>.<lambda>  s    tzz(FVWXVYDZ/[r   c                    > [        TTU 5      $ rq   )r   )r   rU   r#   s    r	   rE   r     s    =Wa#@r   )r   r   r   r   )rU   r   r   r#   r   r   r   r   r   r   r   r   r   r   r   )&r   margins	plan_syncr   rZ   r   	month_keyrS   ft_categoriesr   rn   income_summaryincome_plansr   rJ   r   r   r   assemble	Exceptionprintbalancesr   plan_income_mapr   platform_balanceplan_ops_outcomeclassify_unpaidfact_ops_outcomedatetime	timedeltar   expense_bucketr   r   ri   r   r   )"todayr   r   mgpsr   r   r   
all_incomer   r   r   r   r   r3   r   r   r   er   rY   r   r   r   out_opsclsr   r4   r   rawr   r   rU   r#   s"                                   @@r	   buildr  J  s   
 	T^^E*	+Br!"v;DD%%4x@JB$5$5b*$EFG
//*b
)C$$[_d6J6JR$%X77 8L )vtAX6F/GHM!!t9=55??aeeK0B6||DHHQUU<%8"=yI+//c:U155>CVUV=WW	 > F!!t8<55??aeeK0B6<<|!4b98DTeAEE'N/a00F	 = "a +b!"4b9Fkk&!
 kkmGD!		BB&&t';';('KRPWWYZI""$H !!$'G


Wb&9&9t&9&De!^^E2EH<N<NTV<W4WYC -9X,KM,K(T3S ))#.S9,K  M %[@BC$'++B4c-BVBVBX>Y}TH}Yr]H'MI w79 : :%  <QC@AMs   *O9 $P!
9
PPPrq   )rA   )r&   )i )F) __doc__r  re   ra   r   income_linesr+   r,   r-   r   r
   r   r$   r5   r>   rL   r   r   rZ   rn   r   r   r   r   r   r   r   r`   r   r   r   r  )r0   s   0r	   <module>r     s      	   !#)1)-3T

R,K 	087vxv[>$:H
(	@:o	 *s   B