
    ̩lj$9                     2   S 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JrJr  / SQr0 SS_SS_SS_S	S
_SS
_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SSSSSSSSSSSS.ErS rSS1r\" S5      S
\" S 5      S\" S!5      S\" S"5      S#0r\" S$5      rS% rSS&KJr  SSSSSSS#S#S#S'.	rS( r S) r!S* r"S+ r#S;S, jr$S;S- jr%0 r&S. r'S<S/ jr(\" S05      \" S15      1r)S<S2 jr*S<S3 jr+S<S4 jr,S5 r-S6 r.S7 r/S8 r0\1S9:X  a	  \2" S:5        gg)=uU  Маржинальность направлений Марса. См. docs/margins-design-2026-07-13.md

Клиенты API, .env и календарь года живут в core.py — здесь только бизнес-логика.
Заливка в Google Sheet — push_to_gsheet.py (считает из .sdd/full_year_matrix.json).
    N)HEREBRANCHESFIN_YEARftgetftmutft_allft_transactionsft_categoriesalfa
alfa_loginalfa_allmonths_to_now	month_keymonth_range_alfayear_range_alfamoneynorm)1-1   Групповые   ХорB2B+   Внутренние мероприятия   Съёмки   Аренда   Групповойr   u%   Групповой абонементu!   Групповое пробноеu   Хор (курс)r   u   Хор (разовый)u   Квартирникr   u   Концертu.   Прочие внутренние ивентыzCover sessionr   u   Сover sessionr   u!   Абонемент оффлайнr   u   Абонемент онлайнu   Разовоеu   Разовое онлайнu   Пробное оффлайнu   Пробное онлайн)u.   Первый абонемент оффлайнu,   Первый абонемент онлайнu   Первое разовоеu   Предоплатаu   оплата с сайтаu   Парный абонементu   Парное пробноеu   Парное разовоеu   Фитмост разовоеu)   Подарочный сертификатu   Абонемент ЧП 2024c                     [         R                  U =(       d    SR                  5       5      nU(       d*  U (       a#  SU R                  5       R                  5       ;   a  gU$ )u   Статья CRM → направление. B2B-статьи не перечислены поимённо (их много) — фолбэк ниже. b2br   )
INCOME_MAPgetstriplower)pay_item_nameds     /home/rasp/finsync/margins.pyincome_directionr&   &   sD    +2245A5M,?,?,A,G,G,I#IH    u   мерчuA   мероприятия внешние на привлечениеu-   Расходы на хоровые курсыu    Расходы на съемкиu5   Расходы на внутренние ивентыu$   Расходы на b2b-ивентыr   uK   Зарплаты педагогов: Расходы на занятия 1-1c                 Z    [        U 5      nU[        :X  a  g[        R                  US5      $ )N__lump__overhead)_norm	LUMP_NAMEEXPENSE_DIRECTr    )category_namens     r%   expense_directionr0   =   s)    mAI~a,,r'   )LESSON_RATES)	u   Индивидуальныйu   Пробныйu   Вписанныйu   ФИТМОСТr   u!   Групповой пробныйu    B2B Индивидуальныйu   B2B Пробныйu   B2B Групповойc                 p   SSSS.nUR                  5        HG  u  p4[        R                  U5      nU(       d  M#  X%==   [        R                  " US5      U-  -  ss'   MI     [	        UR                  5       5      nUS:X  a  SSSS.$ UR                  5        VVs0 s H  u  pxXpU-  U-  _M     snn$ s  snnf )u"  ЗП-лампу делим между 1-1 / Групповые / B2B пропорционально «уроки × ставка».

Если уроков нет вообще (CRM не ответил, месяц ещё пустой) — возвращаем НУЛИ, а не
сваливаем всю сумму на «1-1»: раньше сбой CRM молча топил маржу одного направления
на миллионы. Неразбитый остаток вызывающий кладёт в overhead (см. month_pnl).
        )r   r   r   r   )itemsLESSON_TYPE_BUCKETr    LESSON_TYPE_RATEsumvalues)	lump_amountlesson_countsweightsltypecntbuckettotalkws	            r%   
split_lumprB   O   s     SAG#))+
#''.+//q9C??	 ,
  !Ez#cBB3:==?C?41AQ&&?CCCs   B2c                 x    [        SSS0U 5      R                  S/ 5       Vs0 s H  oS   US   _M     sn$ s  snf )Nz/v2api/3/pay-item/indexpager   r4   idname)r   r    )tokenxs     r%   _alfa_pay_itemsrI   d   sF    (,-FQRUZ([(_(_`gik(lm(l1dGQvY(lmmms   7c           
      &   U  VVs0 s H  oU[          Vs0 s H  ofS_M     sn_M     nnnU  Vs0 s H  oU0 _M     nnU  VVs0 s H  oU[          Vs0 s H  of0 _M     sn_M     n	nn[        5       n
[         GHa  n[        SU S3SX4S.U5       GHD  nUR	                  S5      nX;   a  M  U
R                  U5        UR	                  S5      =(       d    Sn[        U5      S	:  a  MX  US
S nX;  a  Md  UR	                  UR	                  S5      5      n[        UR	                  S5      =(       d    S5      n[        U5      nU(       aM  X   U==   U-  ss'   U=(       d    SR                  5       nX   U   R	                  US5      U-   X   U   U'   GM  U=(       d    SR                  5       =(       d    SnX   R	                  US5      U-   X   U'   GMG     GMd     U  H  nX   X   S'   X   X   S'   M     U$ s  snf s  snnf s  snf s  snf s  snnf )u  Платежи CRM за период → выручка по направлениям для каждого месяца из months.

Единственное место, где живёт маппинг платёж → направление (раньше эта логика была
скопирована в income_by_direction и расходилась с income_all_months).
r3   /v2api/z
/pay/index   )pay_type_id	date_fromdate_torE   document_dater   
      Npay_item_idincomer   u   Прочий доход CRM__crm_other____income_detail__)

DIRECTIONSsetr   r   r    addlenfloatr&   r!   )monthsrG   	pay_itemsrN   rO   mr$   res	crm_other
inc_detailseenbrrH   pidddmorF   valkeys                      r%   _collect_incomeri   h   s    5;
;Fqz*z!#vz**FC
; &'1BI':@A&QZ0ZeZ00&JA5DhGB4z2*+)X!A %%+C{HHSM%%(.BB2w|ABB}==}!56Dh,1-C &A
c!
zr((*)3):)>)>sC)H3)N
q!#&zr((*K.K%.]%6%6sC%@3%F	c"-! 0 #,= '1~#$  J? +
;'0As2   G=G8G=HHHH8G=Hc                 ,    [        XU/[        5       Q76 $ )uO   Выручка по направлениям за все месяцы года.)ri   r   )r\   rG   r]   s      r%   income_all_monthsrk      s    6)Ho6GHHr'   c                     U=(       d
    [        5       nUb  UO
[        U5      n[        U /X/[        U 5      Q76 U    n[         Vs0 s H  oDX4   _M	     sn$ s  snf )u   Выручка по направлениям за ОДИН месяц (MM.YYYY) — тот же маппинг, что и за год.)r   rI   ri   r   rW   )monthrG   r]   r_   r$   s        r%   income_by_directionrn      sY    !Z\E&2	8NI
5'5
N6Fu6M
Nu
UC)*z!svIz***s   Ac                 *   U=(       d
    [        5       nUb  UO
[        U5      n[        U /X/[        U 5      Q76 U    n[         Vs0 s H  oDUR                  US5      _M     nn[        UR                  S0 5      R                  5       5      nXV4$ s  snf )uI  (направления, прочий_доход) за месяц MM.YYYY.

income_by_direction отдаёт только DIRECTIONS и молча теряет «прочее» (мерч,
внешние мероприятия). Канону доходных строк оно нужно — это строка «Прочее».
r3   rU   )r   rI   ri   r   rW   r    r7   r8   )rm   rG   r]   r_   r$   dirsothers          r%   income_with_otherrr      s     !Z\E&2	8NI
5'5
N6Fu6M
Nu
UC(23
1swwq#
D3,3356E; 4s   Bc                     U [         ;  a  [        U S9[         U '   [         U     Vs/ s H3  o"R                  S5      (       a  M  UR                  S5      U:X  d  M1  UPM5     sn$ s  snf )uV   факт-операции нужной группы за factMonth == month (MM.YYYY).)groupisPlan	factMonth)_FT_TX_CACHEr	   r    )rt   rm   ts      r%   _ft_txry      sS    L -E:U#E*b*!%%/AaeeKFX\aFaA*bbbs   A#A#A#c                    U=(       d
    [        5       n[         Vs0 s H  o"S_M     nnSUS'   SUS'   0 n[        SU 5       H  nUR                  UR                  S5      S5      =(       d    Sn[	        U5      n[        UR                  S5      =(       d    S	5      nUR                  US5      U-   X2'   US:X  d  Mz  UR                  US5      U-   XF'   M     XCS
'   U$ s  snf )Nr3   r*   r)   outcome
categoryIdr      (без статьи)valuer   __overhead_detail__)r
   rW   ry   r    r0   r[   )rm   catsr$   r_   detailrx   rF   rg   s           r%   expenses_by_directionr      s    "=?D%
&:ac6:C
&C
OS3z?FIu%xxl+R0K4Kd#AEE'N'a(C3&
?!::dC036FL & "(J 's   Cu   Выручка b2cu   Выручка b2bc                 D   U=(       d
    [        5       n0 n[        SU 5       Hz  nUR                  UR                  S5      S5      =(       d    Sn[        U5      [        ;   a  MB  UR                  US5      [        UR                  S5      =(       d    S5      -   X$'   M|     U$ )NrT   r|   r   r}   r3   r~   r   )r
   ry   r    r+   INCOME_OTHER_SKIPr[   )rm   r   r   rx   rF   s        r%   ft_income_otherr      s    "=?DFHe$xxl+R0K4K;++zz$,uQUU7^5Hq/II	 %
 Mr'   c                 (   U=(       d
    [        5       n[        U 5      u  p#0 n[         He  n[        SU S3X#S.U5       HK  nUR	                  S5      S:w  a  M  UR	                  S5      =(       d    SnUR	                  US5      S	-   XG'   MM     Mg     U$ )
uR   month = MM.YYYY. Считает проведённые уроки по типам.rK   z/lesson/index)rN   rO   statusrR   lesson_type_namer   r   rL   )r   r   r   r   r    )rm   rG   dfdtcountsrc   rH   rF   s           r%   r:   r:      s    !Z\Ee$FBFGB4}5R7WY^_AuuX!#55+,2D!::dA.2FL	 `  Mr'   c                 ,    [        U[        X5      5      $ N)rB   r:   )rm   r9   rG   s      r%   salary_by_directionr      s    k=#>??r'   c                 
   [          Vs0 s H  o"U R                  US5      _M     nnUS==   UR                  SS5      -  ss'   US==   UR                  SS5      -  ss'   US==   UR                  SS5      -  ss'   U$ s  snf )Nr3   r   r   r   )rW   r    )expensessalaryr$   directs       r%   merge_directr      s}    /9:z!a%%zF:
5MVZZs++M
 FJJ/CS$II 
5MVZZs++MM	 ;s   B c           
      (   [          Vs0 s H  oDX   _M	     nn[        X5      n[        XS   U5      n[        Xg5      n[          Vs0 s H  oDXT   X   -
  _M     n	nUS   n
US   [	        UR                  5       5      -
  nUS:  a  [        SU  S[        U5       S35        X-  n
[        X5      nUR                  S0 5      R                  5        H  u  pUR                  US5      U-   X'   M     XXU	XS	   UUR                  S
0 5      S.$ s  snf s  snf )Nr)   r*   rL   u   ⚠ u   : ЗП педагогов u    не разнесена по направлениям (нет проведённых уроков в CRM) — учтена в overheadrU   r3   r   rV   )revenuer   marginr*   overhead_detailincome_otherincome_detail)rW   r   r   r   r7   r8   printr   r   r    r4   )rm   income_mr   rG   r$   r   expr   r   r   r*   unsplitr   rF   rg   s                  r%   	month_pnlr      s1   '12z!(+~zG2

,C J?F#&F1;<Afi''F<:H*oFMMO 44G{UG5eGn5E Fa b 	c"5/L\\/26<<>	)--dC83> ?F 9N5O(%\\*=rBD D# 3
 =s   D
Dc           
          [        5       n[        U5      n[        5       n[        XU5      nU  Vs0 s H  oU[	        XTU   X15      _M     sn$ s  snf r   )r   rI   r
   rk   r   )r\   rG   r]   r   rT   r^   s         r%   assembler     sL    LE&I?Dvi8F=CDVy1It33VDDDs   Ac                      [        5       $ )u   Месяцы года до текущего включительно. Оставлено как псевдоним core.months_to_now.)r    r'   r%   months_2026_to_nowr     s
    ?r'   __main__ux   margins — библиотека. Пересчёт: python3 rebuild_matrix.py, заливка: python3 push_to_gsheet.py)NNr   )3__doc__calendarcorer   r   r   r   r   r   r	   r
   r   r   r   r   r   r   r   r   r   r+   rW   r   r&   INCOME_EXCLUDE_NAMESr-   r,   r0   
pay_configr1   r6   r5   rB   rI   ri   rk   rn   rr   rw   ry   r   r   r   r:   r   r   r   r   r   __name__r   r   r'   r%   <module>r      sL  
  K K K K K]
.0WYm')=  $=h I	 L\  ^K	
 56c ^ &6~ N ( 1RSX e ;E $U -J5 7<mr#(%gl',Usx%*Y^$)
( #$gh 
 

9:H	
,-~	
ABDq	
015	 _`	- 8$)UZ_sx.Ui(-Ufk D*n%PI
+  c& /0%8L2MN @D0E
 z	  E  F r'   