
    ijF                     "   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rSr	Sr
S\	 S\
 3r\R                  rSS	S
SSS.rSrSrS&S jrS rS'S jrS r1 Skr1 SkrSrS rS rS rSr1 SkrS rS rS rS&S jrS r S r!S  r"S! r#S" r$S# r%S$ r&S% r'g)(u  Таблица расходов Оли: чтение, разбор сумм, статьи. Общий модуль для двух скриптов.

Раньше `enter_cash_expenses` (оплаченное → факт) и `enter_olya_plan` (неоплаченное → план)
читали одну и ту же таблицу двумя разными парсерами сумм и держали свои копии CAT_MAP.
    Nzplansync:olya:z,1MaEMGjyFfMeHw-xhaW38MjT954Bg9a3rFEZSAjLe1t00z'https://docs.google.com/spreadsheets/d/z/export?format=csv&gid=u5   расходы на внутренние ивентыu    расходы на съемки   сервисыu$   расходы на b2b-ивенты)u)   концерты / квартирникиu   съемкиr   u   лекцииu   b2b ивенты)u   хор\w*\s+в\s+пг\bu   хор\w*\s+вк\bc                    ^ X4 H7  n[        U5      mT(       d  M  [        U4S j[         5       5      (       d  M7    g   g)u   Строка относится к выездному b2b-мероприятию? Смотрим «Проект», при пустом —
назначение (страховка для строк без проекта). Чистая функция.c              3   R   >#    U  H  n[         R                  " UT5      v   M     g 7f)N)research).0markns      /home/rasp/finsync/olya_sheet.py	<genexpr>is_b2b_event.<locals>.<genexpr>.   s     @KDRYYtQ''Ks   $'TF)normanyB2B_MARKERS)projectnaznachtextr   s      @r   is_b2b_eventr   )   s8     "J1@K@@@ #     c                 >   [        U =(       d    S5      R                  SS5      R                  SS5      R                  SS5      R                  SS5      R                  SS5      n [        R                  " SSU 5      n U (       d  g[        R                  " S	U 5      nU(       a>  [        R                  " S
SU SUR                  5        5       SUR                  S5       3n O[        R                  " S
SU 5      n  [        [        U 5      S5      $ ! [         a     gf = f)u  Сумма из таблицы: 'р.5 000' → 5000.0, '1 234,56' → 1234.56, '1.500' → 1500.0.

Сначала убираем «р.»/«₽» и пробелы (иначе точка от «р.» делает число дробным: .5000 = 0.5).
Точка как разделитель ТЫСЯЧ ('1.500') раньше давала 1.5 — дробной частью считаем
только хвост из 1–2 цифр.
 u   р.u   ₽   руб     z	[^\d,.\-]Nz[.,](\d{1,2})$z[.,].      )
strreplacer   subr   startgrouproundfloat
ValueError)sms     r   	parse_sumr)   3   s     	AGUB'//r:BB8RPWVR(b)9 
|R#A
		#Q'Avvgr1Zaggi=12!AGGAJ<@FF7B"U1Xq!! s   :D 
DDc                 j    [        X!5      (       a  [        $ [        R                  [	        U 5      5      $ )u   Статья таблицы (+ проект/назначение) → имя статьи Финтабло или None. Чистая функция.)r   B2B_CATEGORYCAT_MAPgetr   )artr   r   s      r   ft_category_namer/   J   s&    G%%;;tCy!!r   c                 v    [        U R                  S5      U R                  S5      U R                  S5      5      $ )u   Строка таблицы → имя статьи Финтабло (учитывает колонку «Проект»). Чистая функция.u   Статья расхода   Назначение   Проект)r/   r-   )rows    r   row_categoryr4   Q   s2    CGG$ABCGGLbDcehelelm{e|}}r   >   sova   зао   нко   оао   ооо   пао   реп   база
   такси   наличка   менеджер   наличкой   наличные   площадке   менеджеру   наличными>      ип   сз   самозанятый   c                 B    [         R                  " S[        U 5      5      $ )Nu   [а-яa-z]+)r   findallr   )r'   s    r   _wordsrK   b   s    ::nd1g..r   c                     [        U 5       Vs1 s H%  o[        ;  d  M  [        U5      [        :  d  M#  UiM'     sn$ s  snf )u   Имя → значимые токены (без приставок «СЗ»/«ИП» и коротких частиц). Чистая функция.rK   NAME_PREFIXlenNAME_MIN)r'   ts     r   _name_tokensrR   f   s0    ayPy![$8ASVx=OAyPPPs   ===c                 j    [        [        U 5      5      n[        U5      =(       a    U[        -  (       + $ )uT  В «Контрагенте» человек, а не компания или способ оплаты? Чистая функция.

В колонке живут и «Яндекс Такси», и «ООО ВАЗЗАП», и «Наличными, на площадке» —
их в индекс исполнителей не берём.
)setrK   boolNOT_A_PERSON)kontrws     r   
_is_personrY   k   s)     	F5MA7-A,--r   r   >      без   для   над   ндс   приr      что   это
   сумма   оплата   работа   работы   услуга   услуги   оплатитьc                    [        5       n[        R                  " S[        U =(       d    S5      5       HR  u  p#[	        U5      [	        U5      p2SUs=::  a  S::  d  M)  O  M-  SUs=::  a  S::  d  M<  O  M@  UR                  X#45        MT     U$ )u0  Даты события из текста: «ДЕПО 11.07», «концерт 22/08» → {(11, 7), (22, 8)}. Чистая функция.

В «Назначении» и «Проекте» у Оли дата события есть почти всегда, а дата акта — это
дата работы. По ним и разводятся двойники: у Кокорева 15 000 за ДЕПО (11.07) и
15 000 за cover session (25.07) — суммой и фамилией их не отличить, датой отличить можно.
z!\b(\d{1,2})[./](\d{1,2})(?![\d.])r   r         )rT   r   rJ   r   intadd)r   outdmos       r   	_dates_inrp   z   si     %C@#djb/RAB2<R<<AMrMMGGQG S Jr   c                     [        U 5       Vs1 s H&  n[        U5      [        :  d  M  U[        ;  d  M$  UiM(     sn$ s  snf )u   Значимые слова описания — для сверки акта со строкой таблицы. Чистая функция.)rK   rO   rP   
STOP_WORDS)r   rQ   s     r   _meaningrs      s2    d|R|!s1v'9Aaz>QA|RRRs   >>>c                    / nU  GH6  nUR                  S5      n[        U5      n[        UR                  S5      5      nUR                  S5      =(       d    SR                  5       n[        R
                  " SU5      nU(       a%  U(       a  U(       a  U(       a  [        U5      (       d  M  [        U5      nU(       d  M  UR                  S5      =(       d    S SUR                  S5      =(       d    S 3n	UR                  U[        US	5      UUR                  S
5      XI[        U	5      [        U	5      S.5        GM9     U$ )u  Строки-выплаты людям → список записей для сопоставления с актом. Чистая функция.

Месяц в ключ НЕ входит: работа и платёж бывают в разных месяцах (cover session 25.07
оплачивается 03.08), а акт датирован работой. Привязка к месяцу такие строки теряла,
и статья падала обратно в «Зарплаты педагогов».
   Контрагент
   Сумма   Дата платежаr   \d{2}\.(\d{2}\.\d{4})$r1   r   r2   r   r   )tokensamountpay_datemonthcatr   dateswords)r-   r4   r)   stripr   matchrY   rR   appendr$   r#   rp   rs   )
rowsrm   rrW   r}   amtdater(   toksr   s
             r   payment_rowsr      s    C,-1ol+,/06B==?HH.5e
50A0AE"%%./526an8M8SQS7TU

deCmWWQZ&t_x~G 	H  Jr   c           	        ^ [        U5      nU(       d  S/ 4$ U  Vs/ s H/  oeUS   ;   d  M  [        US   [        U5      -
  5      S:  d  M-  UPM1     nn[        U5      S::  a  U(       a  US   U4$ SU4$ U Vs/ s H&  ofS   U=(       d    SR	                  5       :X  d  M$  UPM(     nn[        U5      S:X  a  US   U4$ [
        R                  " S	U=(       d    SR	                  5       5      n	U	(       ae  U Vs/ s HB  n[        U	R                  S5      5      [        U	R                  S
5      5      4US   ;   d  M@  UPMD     n
n[        U
5      S:X  a  U
S   U4$ [        U5      mT(       aP  [        U4S j[        U5       5       5      SSS2   nUS   S   [        :  a  US   S   US   S   :  a
  US   S
   U4$ SU4$ s  snf s  snf s  snf )u7  (строка таблицы | None, кандидаты) для акта (ФИО, сумма, дата акта, описание услуги).

Сначала фамилия + сумма. Если подходит ровно одна строка — она и есть. Если несколько,
разводим по акту: сперва по ДАТЕ ПЛАТЕЖА (акт Рабочих рук выписан днём выплаты, так что
совпадение с «Датой платежа» — самая сильная улика; заодно отсекает ещё не оплаченные
будущие строки), затем по ДАТЕ СОБЫТИЯ из «Назначения», затем по пересечению слов
описания услуги с текстом строки.

Ничем не различить — возвращаем (None, кандидаты): пусть занос пометит акт «проверь,
за что», а не проставит статью наугад. Чистая функция.
Nry   rz   g{Gz?r   r   r{   r   z(\d{1,2})\.(\d{1,2})\.r   r~   c              3   N   >#    U  H  u  p[        TUS    -  5      X4v   M     g7f)r   N)rO   )r	   ir   wants      r   r    match_payment.<locals>.<genexpr>   s&     S?41TAgJ./6?s   "%)_surnameabsr%   rO   r   r   r   rk   r#   rs   sorted	enumerateSERVICE_MIN_HITS)prowsfiorz   akt_dateservicesnr   candby_payr(   by_datescoredr   s               @r   match_paymentr      s    
#BRx^u!ak 1Ac!H+f:U6VY]6]AuD^
4yA~QD00TD00KA:8>r2H2H2J!JaFK
6{aay$
*X^,B,B,DEA"Wds1771:AGGAJ&HAgJ&V1dWw<11:t##GDS9T?SSTXVXTXY !9Q<++q	!vay|0K!9Q<%%:% _ L
 Xs(   F>F>F>9#G G7?G:Gc                     [         R                  " U  SU SU 3R                  5       5      R                  5       SS n[         U 3$ )ut  Детерминированный externalId плановой операции. Чистая функция.

Живёт здесь, а не в enter_olya_plan: по этому же ключу отчёт находит человека
из колонки «Контрагент» для планок, которые уже лежат в Финтабло (kontr_by_ext).
|Nrj   )hashlibmd5encode	hexdigest
EXT_PREFIX)r   rz   r   hs       r   ext_idr      sK     	tfAfXQwi0779:DDFsKA\!r   c           
         0 nU  H  nUR                  S5      =(       d    SR                  5       n[        UR                  S5      5      nU(       a  [        U5      (       a  Uc  Ma  UU[	        UR                  S5      =(       d    SR                  5       UUR                  S5      =(       d    SR                  5       5      '   M     U$ )u   {externalId планки: ФИО из колонки «Контрагент»}. Чистая функция.

Колонка «Контрагент» — самый надёжный ключ к человеку: в «Назначении» он назван как
придётся («Вове (бас)», «Саше Б»), а здесь стоит полное ФИО. Так матчинг узнаёт своего
человека даже у планок, занесённых до того, как мы стали писать partnerId.
ru   r   rv   rw   r1   )r-   r   r)   rY   r   )r   rm   r   rW   rz   s        r   kontr_by_extr      s     C-.4";;=155./*U++0BEJ 	FAEE34:AACVEE017R>>@B 	C  Jr   c                     0 nU  H  nUR                  S5      n[        U5      n[        UR                  S5      5      n[        R                  " SUR                  S5      =(       d    SR                  5       5      nU(       a%  U(       a  U(       a  U(       a  [        U5      (       d  M  [        U5      nU(       d  M  UR                  UR                  S5      [        US5      4/ 5      R                  Xt45        M     U$ )u  Строки таблицы → {(MM.YYYY, сумма): [(токены имени, статья Финтабло)]}. Чистая функция.

Ключ — месяц И сумма: один человек в одном месяце получает и за b2b-хор, и за внутренний
концерт, и за съёмку (у Кокорева в июле 10 000 за хор в ПГ и 15 000 за ДЕПО), так что
статью различает только сумма. Статус не смотрим — акт приходит после оплаты, к этому
моменту строка уже «Оплачено».
ru   rv   rx   rw   r   r   r   )r-   r4   r)   r   r   r   rY   rR   
setdefaultr#   r$   r   )r   idxr   rW   r}   r   r(   r   s           r   expense_indexr      s     C,-1ol+,HH.7P1Q1WUW0^0^0`ae
50A0AE"4NNAGGAJc16;BBD;O  Jr   c                     [        U 5       Vs/ s H%  o[        ;  d  M  [        U5      [        :  d  M#  UPM'     nnU(       a  US   $ S$ s  snf )uS  Фамилия из ФИО акта — первое значимое слово. Чистая функция.

Финтабло и акты пишут человека как «Фамилия Имя Отчество» (на этом же держится
two2pid в enter_akty), поэтому первое слово — фамилия.
r   r   rM   )r   rQ   rX   s      r   r   r      sE     3KOKqK#7CFh<NKAO1Q4" 	Ps   A
A
A
c                 \    [        XX#5      n[        U5      S:X  a  UR                  5       $ S$ )u  Статья для выплаты (ФИО из акта, сумма, месяц) по таблице Оли — или None. Чистая функция.

Ищем ФАМИЛИЮ из акта среди слов «Контрагента», не заботясь о порядке: в таблице
встречается и «СЗ Кокорев Юрий Сергеевич», и «СЗ Ксения Хохлова» (имя первым),
и «Харитонов Саша» против «ХАРИТОНОВ АЛЕКСАНДР ИГОРЕВИЧ» в акте.

Именно фамилию, а не любое общее слово: по отчеству «Сергеевич» акт Барышникова
цеплялся за строку Кокорева той же суммы, а по имени «Александр» Булгаков — за
строку Русманова. И то и другое давало ложную неоднозначность или чужую статью.

None = «в таблице такого нет либо неоднозначно»; статью тогда решает прежняя логика
enter_akty (история выплат человека). Молча не угадываем: если под один ключ подходят
строки с РАЗНЫМИ статьями, отдаём None и просим проверить глазами.
r   N)category_candidatesrO   pop)indexr   rz   r|   hitss        r   lookup_categoryr     s+     u69DTa488:1T1r   c                     [        U5      nU(       d
  [        5       $ U R                  U[        [	        U5      S5      4/ 5       VVs1 s H  u  pVXE;   d  M  UiM     snn$ s  snnf )u  Все статьи таблицы Оли, подходящие под (ФИО, сумма, месяц). Чистая функция.

Больше одной — человек в этом месяце получает одинаковую сумму за разные вещи
(у Ционова в августе 10 000 за съёмку и 10 000 за b2b-хор). Различить нечем,
поэтому lookup_category молчит, а enter_akty про такие говорит вслух: иначе они
тихо падают на историю выплат, то есть обратно в «Зарплаты педагогов».
r   )r   rT   r-   r$   r%   )r   r   rz   r|   r   r   r}   s          r   r   r     sT     
#Bu!&E5v3J+KR!P_!PIDTVT^C!P___s   A A c                      [         R                  R                  [        SS9R	                  5       R                  S5      n [        [        R                  " [        R                  " U 5      5      5      $ )N   )timeoutzutf-8)urllibrequesturlopenCSV_URLreaddecodelistcsv
DictReaderioStringIO)raws    r   
fetch_rowsr   )  sL    
..
 
 "
 
5
:
:
<
C
CG
LCr{{3/011r   c                      [         R                  " S5      R                  S/ 5       V s0 s H,  n U R                  S5      S:X  d  M  [        U S   5      U S   _M.     sn $ s  sn f )uL   {norm(имя): id} только outcome-категории Финтабло.z/v1/categoryitemsr#   outcomenameid)coreftgetr-   r   )cs    r   outcome_cats_by_namer   .  sb     ZZ/33GR@,@uuW~* %D6OQtW$@, , ,s   AA)r   )r   r   )(__doc__r   r   r   r   urllib.requestr   r   r   SHEET_IDGIDr   r   r,   r   r+   r   r)   r/   r4   rV   rN   rP   rK   rR   rY   r   rr   rp   rs   r   r   r   r   r   r   r   r   r   r    r   r   <module>r      s   
 , + + 
9	3H:=TUXTY
Zyy 2i$F%5$J$J 6."~F8/Q
.  ^
S
4!H$,2&`2
,r   