
    {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	J
r
  SSKrSSKrSSKr\" S0 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%_6rS&u  rrSS'\S   S(S)4S*S+\S   S,S-4S.S/\S   S0S-4S1S2\S   S3S)4S4S5\S   S6S74S8S9\S   S:S-4S;S<\S   S=S>4S?S@\S
   SAS-4SBSC\S   SDS-4SESF\S   SGSH4SSI\S   SJS>4SSK\S   SJS-4S SL\S    SMS>4S"SN\S"   SOS>4S$SP\S$   SQS>4SRSS\S   STS>4/rSSU\S   SVS-SSW4SXSY\S   SZS[SS\4S]S^\S   S_S)S`Sa4SbSc\S   SdS[SSe4SfSg\S   ShSiSjSk4/rSlSm\S   SnS>So4/rSpSq\S   SrS)Ss/St4SuSv\S   SwS-/ SxQSy4/rSzS{S|S}S~SSS.rSrS rS r\R6                  rS rS r\S   \S   \S   \S   \S   \S   1rS rS r S r!S r"S r#S r$SS jr%SSKJ&r&  S r'S r(S r)S r*SS jr+S r,SS jr-SS jr.S r/SS jr0\Rb                  r2S r3S r4S r5S r6S r7SS jr8S r9\:S:X  a  \9" 5         gg)u<
  Автосинк ПЛАНОВЫХ РАСХОДОВ и снятие ДОХОДНЫХ ПЛАНОК Марса в Финтабло.

Два действия по расходам (по решению Наташи 20.07):
  RETIRE — убрать «отработавшие» плановые: для каждой нашей plansync-плановой ищу совпавший
           факт (та же статья + сумма ±5% + тот же factMonth, жадное 1:1) → предлагаю удалить.
  BUILD  — создать план расходов на следующий месяц из канона (постоянка по контрагентам +
           ЗП из pay_config + кредиты + налоги). Идемпотентно: пересобирает namespace plansync:sync:{MM}.

Отдельный режим по доходам:
  --retire-income — снять доходные плановые операции закрывшегося месяца (namespace
           plansync:income:{MM}:{slug}). В отличие от расходов, доходные планки не матчатся
           по совпадению факта (доход приходит россыпью мелких платежей CRM) — снимаются
           целиком по месяцу, с итогом план↔факт по канону income_lines для контроля.

Запуск (по умолчанию DRY-RUN — только показывает, ничего не пишет):
  python3 plan_sync.py                       # авто: retire + build следующего месяца, dry-run
  python3 plan_sync.py --month 08.2026       # build конкретного месяца
  python3 plan_sync.py --commit              # ВЫПОЛНИТЬ (удалить/создать в Финтабло)
  python3 plan_sync.py --send                # послать предложение в чат «Марс х Оля»
  python3 plan_sync.py --retire-only / --build-only
  python3 plan_sync.py --retire-income                  # снять доходные планки ПРОШЛОГО месяца
  python3 plan_sync.py --retire-income --month 07.2026   # снять доходные планки указанного месяца
                                                          # (нужно, пока закрывшийся месяц ещё текущий)

События Оли (внутренние ивенты / b2b / съёмки) НЕ генерятся тут — заносятся отдельно из её таблицы.
    N)
monthrangearendaiA9 servisyi buhi;9 tipoi nalogi : 	proc_zaymi" proc_krediti! zp_backi, zp_admini- vznosi+ ndfli* zp_pedi$ hoziMU autsorsi/ kom_rriY banki9 	ekvayringi9 )iP i[ u   Аренда помещенияi|    pozharkau$   Пожарка (ТО АПС и СО)iX     vodau*   Вода (аренда автоматов)i)  vodootvu   Водоотведениеi  internetu'   Интернет (телематика)i     botu&   Бот (обслуживание ПО)i	  buildinu"   Buildin (автосписание)i     tipografiyau   Типографияi  buhgalteriyau   Бухгалтерия ФСКi  	nalog_enpu   Налоги ЕНПi;     u+   Хозяйственные (среднее)i@  u   Аутсорсu1   Комиссия Рабочих рук (авто)iЄ  u-   Банковское обслуж. (авто)i  u.   Комиссия эквайринг (авто)i'  podpiskiuD   Сервисы: подписки прочие (SaaS, среднее)i  u0   % по займу (Назипов, тело 5М)iL )     
credit_ksyuB   Кредит Харитоновой Ксении (ИП Ксюши)i8    )r$      loan15mu,   Новый кредит 1,5М (платёж)ip )     )i     	oborotnyju.   Оборотный (610к, погашение)i`  )r$      tbank1muD   Кредит ТБанк 1М (дог. 7145134903, закрыт 15.07)i' 
   )r*   r(   )r*   r,   ndsu1   НДС 5% (1/3 за квартал, оценка)iG )r*   r0   1c_freshu/   1С:Фреш (СЕРВИСКЛАУД, 6 мес)i%     r$   enp_kvartalu,   ЕНП квартальный (оценка)i! )r%   r.   	      r*   ieK iC iF i2G iK iC iC )r	   r/   r   r   r   r   r    i`
 c                 @    U(       a  X4U:  a  gU(       a  X4U:  a  gg)ue   Платёж по кредиту попадает в месяц (y, mo)? Чистая функция.FT )ymosinceuntils       /home/rasp/finsync/plan_sync.pycredit_activer>      s!    !5!5    c                  p    [          V VVVVVVs/ s H  u  pp#pEofb  M  UPM     snnnnnnn $ s  snnnnnnn f )u   Кредиты без даты окончания — их платёж планируется бессрочно. Для предупреждения.)CREDITS)_sdesc_c_a_d_sincer<   s          r=   credits_without_endrH      s)    <CUUG8Rrr5DGUUUUs   --c                     [        X5      S   n[        R                  " X[        X#5      5      nUR	                  5       nUS:X  a  U[        R
                  " SS9-  nU$ US:X  a  U[        R
                  " SS9-  nU$ )Nr3   r"   r(   daysr.   r   datetimedateminweekday	timedeltar9   r:   daylastdwds         r=   paydayrW      sv    aQDaS^,A	
B	Qw	XQ'' H 
q	XQ''Hr?   c                     [        X5      S   n[        R                  " X[        X#5      5      nUR	                  5       nUS:X  a  U[        R
                  " SS9-  nU$ US:X  a  U[        R
                  " SS9-  nU$ )u  Дата обязательного платежа: с выходного сдвигаем НАЗАД, на пятницу.

Зарплату можно отдать в понедельник (payday), а аренду, коммуналку, кредит и налог —
нельзя: по ним опоздание стоит денег. 15.08.2026 суббота → платим 14.08 (Наташа).
r3   r"   rJ   r.   r(   rL   rR   s         r=   duedaterY      sx     aQDaS^,A	
B	Qw	XQ'' H 
q	XQ''Hr?   c                     U [         ;   $ N)MANDATORY_CATScat_ids    r=   is_mandatoryr_      s    ^##r?   c                 :    [        U 5      (       a  [        $ [        $ )u   Функция расчёта даты под статью: обязательные — назад, остальные — вперёд.)r_   rY   rW   r]   s    r=   _duedate_forra      s    "6**766r?   c                 f    U R                  SS5      R                  SS5      R                  SS5      $ )N _( ))replacenames    r=   
_slug_namerk      s-    <<S!))#r2::3CCr?   c                     [        U =(       d    S5      R                  5       R                  5       R                  SS5      $ )Nrf   u   ёu   е)strstriplowerrh   )ss    r=   
_norm_wordrq      s/    qwB<%%'//d;;r?   c                     0 nU  Hh  n[         R                  " SUR                  S5      =(       d    S5      nU(       d  M;  UR                  [	        US   5      UR                  S5      5        Mj     U$ )u   [{id,name,…}] контрагентов → {фамилия(норм): id}. Первый по фамилии выигрывает (детерминированно).   [А-Яа-яЁё]+rj   rf   r   id)refindallget
setdefaultrq   )partnersidxptokss       r=   surname_indexr}      sZ    
Czz.f0CD4NN:d1g.d<  Jr?   c                     [         R                  " SU =(       d    S5       H*  n[        U5      n[        U5      S:  d  M  X1;   d  M&  X   s  $    g)u   partnerId по фамилии из person_name (перебирая слова), либо None если не нашли. Чистая функция.rs   rf   r%   N)ru   rv   rq   len)person_nameindextokns       r=   resolve_pidr      sB    zz.0ArBsOq6Q;1:8O C r?   c                    U=(       d    S n[        X5      S   n0 n[        R                  R                  5        H(  u  pVSU;   a  M  [	        UR                  5       5      XE'   M*     [        R                  R                  5        H  u  pWUS   S:X  a  US   U-  OUS   XE'   M     [        R                  S   S   n[        R                  S   S	   n	/ n
UR                  5        H  u  p[S
UR                  5       ;   a	  [        S   O[        S   n[        U5      nSUR                  5       ;   a  SOU" U5      nXY;   ab  U
R                  SU S3SU S3XS-  [        XS5      [        U45        U
R                  SU S3SU S3XS-  [        XS5      [        U45        M  U
R                  SU S3SU 3X[        XS5      [        U45        M     [        R                  S   nU" S5      nUS    HM  nUS   S:X  a  [        O[        nU
R                  SUS    3S[        S   US   [        XUS   5      UU45        MO     US   nU
R                  S S![        S   US   [        XUS   5      [        U45        U
R                  S"S#[        S$   [        [        XS5      [        S45        U
R                  S%S&[        S$   [        [        XS'5      [        S45        U
$ )(u  ЗП бэк-офиса + Харитонов + педагоги из pay_config → [(slug, desc, cat, amount, date, mb, pid)].

pid_for(name)->partnerId ставит контрагента ЗП-планкам ПО ЛЮДЯМ (чтобы report матчил по partnerId,
а не по грубой сумме). Агрегатам (администраторы, педагоги) partnerId НЕ ставим — они на несколько людей.
Без pid_for (dry-run/тесты без сети) partnerId = None.c                     g r[   r8   ri   s    r=   <lambda>zp_ops.<locals>.<lambda>   s    tr?   r3      Харитоновtypeper_dayamountsplit	full_on_5half_5_half_20u
   админr   r      администраторNzzp:z:5u   ЗП z (50%)r(   r"   z:20r'   
kharitonov%   Харитонов Александрsalarymoneybagu   наличкаzzp:haritonov:rS   u"   ЗП Харитонов (Саша)	tax_vznoszzp:haritonov:vznosu1   ЗП Харитонов доп. (Саша, 20-е)zteach:01u,   ЗП педагогов (1-я, оценка)r   zteach:16u,   ЗП педагогов (2-я, оценка)   )r   pcBACKOFFICE_FIXEDitemssumvaluesBACKOFFICE_VARIABLEBACKOFFICE_PAYro   CATrk   appendrW   	MB_TOCHKAMB_NAL	TEACH_EST)r9   r:   pid_forrK   amtrj   acctscfgfull5halfresacatsnpidkkh_pidrp   mbtvs                       r=   zp_opsr      sK    ,+GaQD
C**0024''	 3 ++113	-0[I-EC	NT)3x=	 4g&{3EW%&67D
C99;!-!=c*o3y>2djjlBdPT<JJ#bTtfF';Sa%PQWXIY[dfijkJJ#bTtfF';Sa%PQWYIZ\egjklJJ#bTdVncfQA>NPY[^_`  	,'A<=Fx[}(88Vi

mAeH:.0TVYZcVdfghpfqsyz{  BC  DI  BJ  tK  MO  QW  X  	Y  
;BJJ$&Y[^_h[ikmnvkwy  AB  HJ  KP  HQ  zR  T]  _e  f  gJJ
JCPXM[dflmntufv  yB  DH  I  JJJ
JCPXM[dflmntvfw  zC  EI  J  KJr?   )RU_MONTH_GENc                 j    [        U SS 5      [        U SS 5      p!US:X  a  SUS-
   3$ US-
  S SU 3$ )u9   «08.2026» → «07.2026». Чистая функция.Nr(   r%   r3   z12.02d.)int)mmr:   r9   s      r=   
prev_monthr     sI    2AKRV!GSQ=<BF3<q)<<r?   c           
         U  Vs/ s Hv  o"R                  S5      (       a  M  UR                  S5      U:X  d  M1  UR                  S5      UR                  S5      [        UR                  S5      =(       d    S5      S.PMx     sn$ s  snf )u   Факты (не план) месяца prior_mm → [{partnerId, categoryId, value}] — база для оценки сумм.isPlan	factMonth	partnerId
categoryIdvaluer   )r   r   r   )rw   float)all_outprior_mmts      r=   prior_facts_fromr     s{     XEE(O289k8Jh8V2!%%,AEE,<OAEE'N/a02X X Xs   BBA	Bc                     U(       a  U  Vs/ s H  oDS   U:X  d  M  UPM     nnSnO&US:X  a  U  Vs/ s H  oDS   U:X  d  M  UPM     nnSnOgU(       d  SU4$ [        S U 5       5      U4$ s  snf s  snf )	u  Сумма факта прошлого месяца для постоянной планки → (amount|None, basis).
По partnerId (если запинен/разрешён), иначе по СТАТЬЕ — но только если планка в статье ОДНА
(несколько планок одной статьи → неоднозначно, None). Нет факта → (None, basis). Чистая функция.
Несколько платежей контрагента за месяц суммируются.r   u   контрагентr3   r   u   статьяNNNc              3   *   #    U  H	  oS    v   M     g7f)r   Nr8   ).0fs     r=   	<genexpr>fact_amount.<locals>.<genexpr>2  s     +7az7   )r   )prior_factsr   r   cat_plan_countr   matchedbasiss          r=   fact_amountr   #  s    
 )Ik{^y-H1kI&	1	)Kk|_
-J1kKU{+7++U22 J Ls   A,A,A1A1c                 |    [         R                  U 5      n[        U[        5      (       a  U$ U(       a  U" U5      $ S$ )u   partnerId постоянного платежа: int в FIXED_PARTNERS — пин, str — lookup по фамилии, иначе None.N)FIXED_PARTNERSrw   
isinstancer   )slugr   vals      r=   
_fixed_pidr   5  s5    


T
"C#s
"73<,,r?   c                    [        U SS 5      [        U SS 5      p2/ n[         H:  u  pVpxn	UR                  XVXx[        U5      " X#U	5      [        [        XQ5      45        M<     [         HN  u  pVpxpn[        X#X5      (       d  M  UR                  XVXx[        U5      " X#U	5      [        [        XQ5      45        MP     [         HC  u  pVpxpX#4U
:  d  M  UR                  XVXx[        U5      " X#U	5      [        [        XQ5      45        ME     [         HI  u  pVpxpnX<;   d  M  X-:  d  M  UR                  XVXx[        U5      " X#U	5      [        [        XQ5      45        MK     U$ )u   Постоянные (НЕ ЗП) планки месяца с КОНФИГ-суммами → [(slug, desc, cat, amount, date, mb, pid)].r%   Nr(   )
r   FIXEDr   ra   r   r   rA   r>   MONTHLY_SINCEPERIODIC)r   r   r9   r:   linesr   rC   r   r   rS   r;   r<   months
since_years                 r=   fixed_linesr   =  sB   12KRVrE%*!Ccd#L,=aS,I9V`aeVopq &+3:/Cc%QE11d#L,=aS,I9V`aeVopq 4; -:(Cc7eLL$cS0A!0MyZdeiZstu -: :B5Cc:<AOLL$cS0A!0MyZdeiZstu :B Lr?   c                 T    0 nU  H  u  p#pEpgnUR                  US5      S-   X'   M!     U$ )Nr   r3   rw   )	fixedcntrB   rF   r   rE   _dt_mb_ps	            r=   _cat_countsr   P  s5    
C).%2773?Q& */Jr?   c           
         [        U SS 5      [        U SS 5      pC[        X5      n[        U5      n/ nU H9  u  pppnUnUb  [        X.XU
   5      u  nnUb  UnUR	                  XXXU45        M;     U[        X4U5      -  nU$ )u  Полный список плановых для месяца mm → [(slug, desc, cat, amount, date, mb, pid)].
Если задан prior_facts (факты прошлого месяца), суммы ПОСТОЯННЫХ планок берутся из факта
контрагента/статьи (см. fact_amount) — «займ Алмаза 85 000 → факт 82 192». ЗП-планки из
pay_config фактом НЕ трогаем (ставки — источник правды).r%   Nr(   )r   r   r   r   r   r   )r   r   r   r9   r:   fxcountsopsr   rC   r   r   dtr   r   r   fsrd   s                     r=   
month_planr   W  s    
 12KRVr	R	!B_F
C-/)Cbc"#c{CEB~

D"#67 .0 6!!!CJr?   c                 8   Uc  / $ [        X5      n[        U5      n[        USS 5      n/ nU Hh  u  pppn[        X>XU
   5      u  nnUc  M  [	        X-
  5      S:  d  M2  UR                  U	 S[        U5       S[        U    S[        U5       S35        Mj     U$ )u   Строки предложения по постоянным планкам, где сумма ВЗЯТА ИЗ ФАКТА и отличается от конфига:
«% по займу (Назипов) — 82 192 (факт июля; конфиг 85 000)». Чистая функция.Nr(   gư>u    — u    (факт u   ; конфиг rg   )r   r   r   r   absr   moneyr   )r   r   r   r   r   r   pmorowsr   rC   r   r   r   r   r   r   _basiss                    r=   fixed_proposal_rowsr   k  s     		R	!B_F
hrl
CD-/)Cbc 3sD
F>c"(md2KK4&eBi[L<M;No^cdg^h]iijkl .0 Kr?   c                    [        X5      S   n0 n[        R                  R                  5        H(  u  pESU;   a  M  [	        UR                  5       5      X4'   M*     [        R                  R                  5        H  u  pFUS   S:X  a  US   U-  OUS   X4'   M     [	        S [        R                  S   S    5       5      US	'   U$ )
uH  {имя: полная месячная ставка} бэк-офиса из pay_config (fixed+variable + зарплата Харитонова).
Без взноса Харитонова и без агрегата «администраторы» отдельно не выделяем — фильтруем при сверке.r3   r   r   r   r   c              3   *   #    U  H	  oS    v   M     g7f)r   Nr8   )r   rp   s     r=   r   %backoffice_amounts.<locals>.<genexpr>  s     6vLuq{Lur   r   r   r   )r   r   r   r   r   r   r   r   )r9   r:   rK   r   rj   r   r   s          r=   backoffice_amountsr   {  s     aQD
C**0024''	 3 ++113	-0[I-EC	NT)3x=	 4366vBL]L]^jLkltLu6v3vC/0Jr?   c                   ^ [        X5      n[        USS 5      n/ n[        U5       H  n	Xi   n
SU	R                  5       ;   d  U
S::  a  M#  U(       a  U" U	5      OSmT(       d  M=  [	        U4S jU 5       5      nUS::  a  MZ  [        X-
  5      XZ-  :  d  Mo  UR                  SU	 S[        U
5       S[        U    S	[        U5       S
3	5        M     U$ )u  Предупреждения, где ФАКТ прошлого месяца по человеку отличается от ставки pay_config > threshold:
«⚠️ Наташа Мухина: ставка 30 000, факт июля 12 000 — проверь pay_config». Чистая функция.
Ставки не меняем (источник правды), только сигналим владелице. Агрегат «администраторы» пропускаем.Nr(   r   r   c              3   B   >#    U  H  oS    T:X  d  M  US   v   M     g7f)r   r   Nr8   )r   r   r   s     r=   r   #zp_rate_warnings.<locals>.<genexpr>  s      L{!n6K:1W:{s   u   ⚠️ u   : ставка u   , факт rc   u    — проверь pay_config)	r   r   sortedro   r   r   r   r   r   )r9   r:   r   r   r   	thresholdr   r   warnsrj   ratefactr   s               @r=   zp_rate_warningsr    s     Q
#C
hrl
CEsy'4::<7419&gdmDL{LL19t{i..LL74&d}KP\]`PaObbcdijndocp  qO  P  Q  Lr?   c                 p    / n[        [        U 5      5       H  nU[        R                  " USS9-  nM     U$ )u  Расходные операции за указанные годы (план + факт). На стыке года нужны оба:
в декабре мы строим план на январь, и без январского окна build не увидел бы уже
созданные планки и создал бы их поверх — дубль плана на месяц.outcomeyeargroup)r   setcoreft_transactions)yearsoutr9   s      r=   all_outcomer    s8     CCJt##)<<  Jr?   c                 |   U  Vs/ s H<  oDR                  S5      (       d  M  SUR                  S5      =(       d    S;   d  M:  UPM>     nnU  Vs/ s H  oDR                  S5      (       a  M  UPM     nnU Vs/ s H  n[        R                  " XA5      PM     nnU Vs/ s H  n[        R                  " XAU5      PM     nn[        R                  " XxU5        [        XW5       VV	s/ s H4  u  pI[        R                  " U	[        R                  5      (       d  M1  XI4PM6     sn	n$ s  snf s  snf s  snf s  snf s  sn	nf )u  Плановые plansync, которые уже ОТРАБОТАЛИ (факт пришёл) → [(операция, разбор)].

Матчинг — общий каскад из matching.py (по контрагенту, фамилии, сумме…), тот же, что
в отчёте. Раньше здесь было своё правило «та же статья + сумма ±5%», и планку мог
погасить случайный платёж близкой суммы — обязательство исчезало из прогноза.
Удаляем строго при покрытии ≥95% (RETIRE_MIN_COVER): недоплату не хороним.r   plansync
externalIdrf   )rw   matchingplan_opfact_opmatch_cascadezipsettledRETIRE_MIN_COVER)
r   catsry   todayr   plansfactsplan_opsfact_opsos
             r=   find_retirer    s      a155?QzaeeLFYF_]_7`QEa71uuXQE73895a  )5H9=BCU  (3UHC8u5"53 ?3tq8#<#<= QF3 ? ? b79C?s4   D$D$D$D)+D)7 D.!D3%0D8D8c                 ,   / nU  H  nUR                  S5      =(       d    SnUR                  S5      (       a  UR                  S5      (       d  MK  UR                  S5      (       a  Mc  UR                  S5      U:w  a  Mz  UR                  U5        M     U$ )u"  Всё plansync за месяц mm, КРОМЕ событий Оли (plansync:olya:*) — их ведём отдельно.
Build забирает месяц под себя: чистит легаси (teach0801, fix0726, credit80, new-loan…) и пересоздаёт из канона.r  rf   r   z	plansync:zplansync:olya:r   rw   
startswithr   )r   r   r   r   exts        r=   existing_syncr$    s}     CeeL!'RhCNN;$?$?>>*++55#

1  Jr?   c                     / nU  Ha  nUR                  S5      =(       d    SnUR                  S5      (       d  M5  UR                  SU-  5      (       d  MP  UR                  U5        Mc     U$ )u  Доходные плановые операции месяца mm (MM.YYYY) — список операций Финтабло.

В отличие от расходов, матчить по контрагенту и сумме нечего: факт доходов
приходит россыпью мелких платежей (в июле 2026 — 267 штук на одну плановую
строку), поэтому снимаем по месяцу, а не по совпадению.
r  rf   r   zplansync:income:%s:r!  )
all_incomer   r   r   r#  s        r=   income_plansr'    sY     CeeL!'R55??s~~.Cb.HIIJJqM  Jr?   c                 P    SU ;   a  U$ [        [        R                  " U5      5      $ )u  Месяц, чьи доходные планки снимаем — единственное решение в `--retire-income`,
определяющее, чей план денег будет удалён, поэтому вынесено отдельно и покрыто
тестами без сети (см. test_income_retire.py).

`--month` — снимаем указанный месяц как есть (`target` уже провалидирован форматом
MM.YYYY в main()). Без флага — прошлый месяц от сегодня: таймер 1-го числа снимает
то, что только что закрылось.--month)r   r	  	month_key)argstargetr  s      r=   retire_target_monthr-    s$     $&6MJt~~e7L,MMr?   c                     SSK nUR                  X5      nUR                  X5      n0 n[        [	        U5      [	        U5      -   5       H0  nUR                  US5      nUR                  US5      n	XX-
  S.Xg'   M2     U$ )ui   {slug: {plan, fact, delta}} — итог месяца по канону: прогноз ↔ факт CRM.r   Ng        )planr  delta)income_linesplan_from_fintablocrm_factr  listrw   )
r   r&  tokenilr/  r  r   r   r{   r   s
             r=   income_summaryr7    sy      0D;;r!D
CDJd+,HHT3HHT3AE:	 - Jr?   c                    ^=^>^? [         R                  SS  n SU ;   nSU ;   nSU ;   nSU ;   n[        R                  R	                  5       nSU ;   a  X R                  S5      S-      O[        R                  " U5      n[        R                  " SU=(       d    S5      (       d  [        S	U S
35        g SU ;   nU(       Ga  [        XU5      n[        SU 35        [        R                  " [        USS  5      SS9n	[        X5      n
[        X5      m?SU-  /nSS KnUR"                   Vs1 s H  oR$                  iM     snm=UR"                   H  nT?R'                  UR$                  5      nU(       a  US   (       d  US   (       d  M;  UR)                  SUR*                  < S[-        US   5      < S[-        US   5      < S[-        US   5      < 35        M     [/        U=4S jT? 5       5      nU(       aG  [1        U?4S jU 5       5      nUR)                  S[-        U5      < SSR3                  U5      < S35        UR)                  S[5        U
5      -  5        S R3                  U5      nU(       Ga  [6        R8                  R3                  [6        R8                  R;                  [6        R8                  R=                  [>        5      5      S!S"URA                  S#S$5      -  5      n[6        RB                  " [6        R8                  R;                  U5      S%S&9  [E        US'S(S)9 n[F        RH                  " UT?U
S*.US+SS,9  S S S 5        U
 H  n[K        S-S.US/   -  5        M     US0[6        R8                  RM                  U5      -  -  nOUS1-  nU(       a  [6        RN                  R'                  S25      n[6        RN                  R'                  S35      n[P        RR                  RU                  UUS4S5.5      RW                  5       n[P        RX                  R[                  S6U-  US7S89  [        URA                  S9S5      RA                  S:S5      5        g [        R\                  " 5       n[        R^                  " 5       n[a        URb                  [        USS  5      [        [e        U5      SS  5      15      nS;URg                  S<5       3/nU(       a  / O[i        UUUU5      nU(       d  [1        S= U 5       5      nUR)                  S>[5        U5       S?[-        U5       S:35        [/        US@ SA9S SB  H  u  nnUR)                  SCUR'                  SD5       SE[-        [k        UR'                  SF5      =(       d    S5      5       SEUR'                  UR'                  SG5      S5      S SH  SI[-        USJ   5       35        M     S n[e        U5      n [m        UU 5      n!U(       d5  [o        [        Rp                  " SK5      R'                  SL/ 5      5      m>U>4SM jnU(       a  / O[s        UUU!5      n"[u        UU5      n#U(       Gd  [1        SN U" 5       5      n$[1        SO U" 5       5      n%UR)                  SPU SQ[5        U"5       S?[-        U$5       S:3U#(       a  SR[5        U#5       S3OS-   SSU% ST3-   5        SSUK;J<n&  U&" [j        5      n'U" H(  u  n(n)n*n+n,n-n.U'UR'                  U*S5      ==   U+-  ss'   M*     [/        U'R{                  5       SV SA9 H)  u  n/n0UR)                  SCU/S SW  SQ[-        U05       35        M+     [}        UU UU!5      n1U1(       aE  UR)                  SX[~        [        U S SY 5          SZ35        U1 H  n2UR)                  SCU2 35        M     [        [        USS  5      [        US SY 5      U U!U5      n3U3(       a.  UR)                  S[5        U3 H  n4UR)                  SCU4 35        M     [        5       n5U5(       a*  U(       d#  UR)                  S\S]R3                  U55      -   5        UR)                  S^5        U(       d  UR)                  S_5        S R3                  U5      nU(       a  [6        RN                  R'                  S25      n[6        RN                  R'                  S35      n[P        RR                  RU                  UUS4S5.5      RW                  5       n[P        RX                  R[                  S`U Sa3US7S89  [        URA                  S9S5      RA                  S:S5      RA                  SbS5      RA                  STS5      5        U(       Ga  [6        RB                  " [6        R8                  R3                  [6        R8                  R;                  [6        R8                  R=                  [>        5      5      S!5      S%S&9  [6        R8                  R3                  [6        R8                  R;                  [6        R8                  R=                  [>        5      5      S!ScURg                  Sd5       SeURA                  S#S$5       Sf35      n[E        US'S(S)9 n[F        RH                  " U(       d  U VV6s/ s H  u  nn6UPM
     sn6nO/ U(       d  U#O/ U(       dC  U" VV7V/V8V,V-V.s/ s H)  u  nn7n/n8n,n-n.[        UU7U/U8U,Rg                  Sg5      U-U.Sh9PM+     sn.n-n,n8n/n7nO/ Si.US+SS,9  S S S 5        S=n9n:U(       d#  U H  u  nn6[K        S-SjUS/    35        U9S-  n9M     U(       d  U VV6s1 s H  u  nn6US/   iM     n;nn6U# H   nUS/   U;;   a  M  [K        S-SjUS/    35        M"     U" HO  u  n(n)n*n+n,n-n.[        U+SkU-U,Rg                  Sg5      UU*SU)SlU SmU( 3Sn9	n<U.(       a  U.U<So'   [K        SpSqU<5        U:S-  n:MQ     [        SrU9 SsU SQU: 35        [        StU 35        g g s  snf ! , (       d  f       GN= fs  sn6nf s  sn.n-n,n8n/n7nf ! , (       d  f       GN%= fs  sn6nf )uNr3   z--commitz--sendz--retire-onlyz--build-onlyr)  z\d{2}\.\d{4}$rf   uD   ⚠️ месяц должен быть MM.YYYY, получено «   »z--retire-incomeu5   📅 снимаю доходные планки за r%   incomer  u8   📊 <b>Доходы %s: прогноз ↔ факт</b>r   r/  r  z  u   : план u    / факт u    / Δ r0  c              3   6   >#    U  H  oT;  d  M
  Uv   M     g 7fr[   r8   )r   r   known_slugss     r=   r   main.<locals>.<genexpr>  s     OGD;7N44Gs   		c              3   4   >#    U  H  nTU   S    v   M     g7f)r/  Nr8   )r   r   summarys     r=   r   r=    s     L)$f 5)s   u!     не сопоставлено: u'    ₽ (устаревшие слаги: z, rg   u-   
Снимаю плановых строк: %d
exportszincome_retire_%s.jsonr   -T)exist_okwzutf-8)encoding)monthr?  deletedF)ensure_asciiindentDELETEz/v1/transaction/%srt   u!   
✅ снято, журнал: %su&   
⚠️ dry-run, добавь --commitTELEGRAM_BOT_TOKENTELEGRAM_CHAT_IDHTML)chat_idtext
parse_modez*https://api.telegram.org/bot%s/sendMessager'   )datatimeoutz<b>z</b>u<   🗓 <b>Автосинк плана расходов</b> · z%d.%mc              3   l   #    U  H*  u  p[        UR                  S 5      =(       d    S5      v   M,     g7f)r   r   N)r   rw   )r   r{   rd   s      r=   r   r=  C  s'     A&$!5w,1--&s   24uC   
<b>Убрать отработавшие (план сбылся): u	    шт · c                 <    U S   R                  S5      =(       d    S$ )Nr   rN   rf   r   )pfs    r=   r   main.<locals>.<lambda>E  s    "Q%))F2C2Ir2Ir?   )key(   u   — rN   rc   r   r      u    ✓оплачено paidz/v1/partnerr   c                    > [        U T5      $ r[   )r   )rj   sidxs    r=   r   rV  O  s    {46r?   c              3   *   #    U  H	  oS    v   M     g7f)r%   Nr8   r   r  s     r=   r   r=  T  s     &AQ4r   c              3   <   #    U  H  oS    (       d  M  Sv   M     g7f)r.   r3   Nr8   r^  s     r=   r   r=  U  s     ,t!tAAts   	u!   
<b>Создать план на z: u$     (пересоздать, было u3   
<i>ЗП-планок с контрагентом: z</i>)defaultdictc                     U S   * $ )Nr3   r8   )zs    r=   r   rV  ]  s
    adUr?   "   u   
<b>Суммы по факту r(   u"    (было в конфиге):</b>u`   
<b>Проверить ставки pay_config (факт разошёлся более 10%):</b>u   
⚠️ <b>Кредиты без даты окончания</b> (планируются бессрочно, уточни срок): z; um   
<i>Событий Оли тут нет — заносятся из её таблицы отдельно.</i>ui   
⚠️ Это предложение (dry-run). Скажи «давай» — выполню (--commit).zhttps://api.telegram.org/botz/sendMessagez<i>plansync_journal_z%Y%m%drd   z.jsonz%d.%m.%Y)r   rC   r   r   rN   
moneybagIdr   )retire_deletedrebuild_deleted	to_createz/v1/transaction/r  zplansync:sync::)	r   r  re  rN   r   r   r   descriptionr  r   POSTz/v1/transactionuA   
✅ ВЫПОЛНЕНО: удалено отработавших u"   , пересоздано план u4   журнал (для восстановления): )CsysargvrM   rN   r  r   r	  next_month_keyru   matchprintr-  r
  r   r'  r7  r1  LINESr   rw   r   rj   r   r   r   joinr   ospathdirnameabspath__file__rh   makedirsopenjsondumpftreqbasenameenvironurllibparse	urlencodeencoderequesturlopenft_categoriesft_partnersr  r  r   strftimer  r   r   r}   ftgetr   r$  collectionsr`  r   r   r   r  rH   dict)@r+  commitsendretire_only
build_onlyr  r,  retire_incomeprevr&  r   r   r6  lrp   	unmatchedunmatched_sumrO  jpathjfr   r   chatrQ  r  ry   r   retirersumr{   r  r   r   r   r/  expsumn_pidr`  byr   rC   r   r   r   r   r   cv	fact_rowsrr   rD  no_endrd   d2r   n_deln_newretired_idsbodyr<  r\  r?  s@                                                                @@@r=   mainr    s
   88AB<D4FtD!T)K4'JMM!E09T0AT**Y'!+,tGZGZ[`GaF88$fl33TU[T\\^_`bh%-M"47EdVLM
 ))s48}HM
:, 2KdRS!')xx0x!vvx0AAFF#AQvYqyLLFFE!F)$4eAfI6FaPWjHY[ \	  OGOO	L)LLMLL!-0$))I2FH IECPQyy GGLL1J!KY!84<<S;Q!QSEKK.>eS73r		DWM5< 4 h 4qw >? 8277;K;KE;RRRD==D**..!56Crzz~~N`?a<<))dD9?+A BBH&( NN""#ORU#U(,b # :dll5"%--fb9:D!H5::s6!":Jv<Nqr<R8STUGKENN[bLcKdefE R;wh#NFA&AA[\_`f\g[hhqrwx|r}q~  C  D  	E6'IJ3BODAqLL4faeAEE'N<Oa6P0Q/RRS HHQUU<%8<SbABBWX]^_`f^gXhWik l P
 G&!H"7H5KTZZ6::7BGH62*VWk"JD	w	'B&&&,t,,9&CI;iX]^bXcWddhiMO>s2wiqIUWYMeWTXYZ 	[ 	,15-D$S"b#txxR !S(! 26288:?;DAqLL4#2xr%(45 < (';O	LL;LXVXWX\IZ<[;\\~  AtA3Z(  !VABZ#fRaj/8[ZabLL|~tA3Z(  !"Fk  ^yy() 	*	LL  B  C  B  	C99UDjjnn122::>>J\;]D||%%$TZ&[\cce!=cU,OVZdfg	$,,ub
!
)
)&"
5
=
=eR
H
P
PQWY[
\] 	BGGLL1J!KYWbfgRWW__RWW__X-FG01I0J!FNN[^_bLcKddijl%w/2II@J"8A1"8PR-82bKVdD D>B":!RAr2s #qPRP[P[\fPgtv  CF  G>BD D\^	
 a1 0 1h"21T7) <=
  /56vtq!1T7vK6T7k)h"21T7) <=  6:1dCb"cY2BKKPZD[$QD!/xq?A (+D%f/6
 6: 	RSXRYY{  }C  |D  DF  GL  FM  N  	ODUGLME _ 16 43x #9D D	 0/ 7sB   &o o
o6'o%6 o60o+o6p
o"%o66
p__main__r8   r[   r   )g?);__doc__rl  rs  ru   rz  rM   urllib.requestr  urllib.parsecalendarr   r	  r  
pay_configr   r  r   r   r   r   rA   r   r   r   r   r>   rH   r   rW   rY   r\   r_   ra   rk   rq   r}   r   r   r   r   r   r   r   r   r   r   r   r   r  ftmutr|  r  r  r$  r'  r-  r7  r  __name__r8   r?   r=   <module>r     sR  4 A @ @ @      Z' Z7 Z Zg ZW ZZ*1Z;BZMTZZ&Z/6Z Z !(Z 18Z ?FZ RYZ # 	6 5c(mYXZ[:s8}eVXY@3x=RXZ\]0S]ETVW=#i.RWY[\
<YQVXZ[8C	NESUV*#f+urR3c%j&RTU)WvqQ
As5zSY[]^$#i.&RTUGXX^`bcCs6{TZ\^_DKHXZ`bdeZ\_`i\jlrtvw%	: Es;GWY_ac	9 WY\]jYkmsuw	9
 ACDVX^`b	
 CSEWY_ac	9 Y"i<7F ?WwXZ\fg	 BC	NTY[]`a_bdhiBCLRXZ\^kmqr	  !6 	V
 	

$ h-Ws6{CLk"C$68$7
D<%P =X3$-&( . 	

?  N
iNX zF r?   