
    4j                        d Z ddlmZ ddlmZ ddlmZmZ ddlm	Z	 ddl
mZ ddlmZ ddlmZ g d	Z e	d
      Ze G d d             ZddZddZy)u   
Subscription detector — Story 10.4.

Scans transactions for recurring charges: same merchant, similar amounts (±$1),
on a recurring interval (weekly ~7d, monthly ~30d, annual ~365d).
    )annotations)	dataclass)date	timedelta)Decimal)func)db)Transaction))weekly   	   )monthly   #   )annuali^  i|  z1.00c                  J    e Zd ZU ded<   ded<   ded<   ded<   ded<   ded	<   y
)DetectedSubscriptionstrmerchantr   estimated_amount
recurrencelast_charge_dateintoccurrence_counttotal_last_12moN)__name__
__module____qualname____annotations__     G/var/www/html/financials/app/services/insights/subscription_detector.pyr   r      s$    MOr!   r   c                 r  	 t        j                         } | t        d      z
  j                         	t        j
                  j                  t        j                  t        j                  t        j                        j                  d      t        j                  t        j                        j                  d      t        j                  t        j                         j                  d            j!                  t        j"                  dk(  t        j                   | t        d      z
  j                         k\        j%                  t        j                        j'                  t        j                  t        j                        dk\        j)                         }g }|D ]  }|j                  xs d	}t        j                  j!                  t        j                  |k(  t        j"                  dk(  t        j                   | t        d      z
  j                         k\        j+                  t        j                         j)                         }t-        |      dk  rt/        |      }|t        	fd
|D              }t1        t3        |j4                  xs d            t-        |      z  }|j7                  t9        ||j;                  t1        d            ||j<                  t-        |      t1        t3        |xs d            j;                  t1        d                          t?        |d       S )z
    FR-3.3: Detect subscription-like transactions.

    Groups by merchant_normalized, then checks if charges appear at regular
    intervals with similar amounts.
    im  )dayscounttotal	last_dateFi     Unknownc              3  V   K   | ]   }|j                   k\  s|j                   " y wN)r   amount).0tone_year_agos     r"   	<genexpr>z'detect_subscriptions.<locals>.<genexpr>X   s%      
aff&<AHH
s   ))r   z0.01)r   r   r   r   r   r   c                    | j                   S r+   )r   )ss    r"   <lambda>z&detect_subscriptions.<locals>.<lambda>f   s
    qzz r!   )key) r   todayr   	isoformatr	   sessionqueryr
   merchant_normalizedr   r%   idlabelsumr,   maxfilter	is_creditgroup_byhavingallorder_bylen_detect_recurrencer   r   r&   appendr   quantizer'   sorted)
r5   merchant_rowssubscriptionsrowr   txnsr   
total_12mo
avg_amountr/   s
            @r"   detect_subscriptionsrO   &   sp    JJLEI3//::<L 	

++JJ{~~&,,W5HH[''(..w7HH[%%&,,[9		
 
!!U*)<!< G G II

 
+11	2	

;>>*a/	0	   M "**7i V//8;%%.  UYC-@%@$K$K$MM
 Xk&&'SU 	 t9q='-
  
"
 

 Sa01CI=
1'00A! ]] Y#C
a$89BB76?S
 	7"H -%9::r!   c           	     n   t        |       dk  ryg }t        dt        |             D ]  }| |dz
     | |   }}t        t        t	        |j
                              t        t	        |j
                              z
        }|t        kD  r`	 t        j                  |j                        }t        j                  |j                        }|j                  ||z
  j                          |syt        |      t        |      z  }t        D ]  \  }	}
}|
|cxk  r	|k  s|	c S   y# t        t        f$ r Y w xY w)zGCheck if transactions follow a regular interval. Returns label or None.r(   N   )rD   rangeabsr   r   r,   _AMOUNT_TOLERANCEr   fromisoformatrF   r$   
ValueErrorAttributeErrorr<   
_INTERVALS)rL   gapsiabamount_diffd_ad_bavg_gapr;   min_daysmax_dayss               r"   rE   rE   i   s#   
4y1} D1c$i  
AE{DG1'#ahh-073qxx=3IIJ**	$$QVV,C$$QVV,CKKs(()
 $i#d)#G%/ !xw*(*L +  N+ 		s   AD!!D43D4N)returnzlist[DetectedSubscription])rL   listrc   z
str | None)__doc__
__future__r   dataclassesr   datetimer   r   decimalr   
sqlalchemyr   app.extensionsr	   app.models.transactionr
   rX   rT   r   rO   rE   r    r!   r"   <module>rm      sY    # ! $    .

 FO    @;Fr!   