
    ]iX                        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mZmZ ddlmZmZ ddlmZmZ dd	lmZ dd
lmZmZmZ 	 	 	 	 	 d#de	dededeeef   dedz  dedz  dedz  dedz  dedz  ddfdZde	deddfdZde	dedededededededdfdZde	dededededdfd Zde	deddfd!Z de	dedededededededdfd"Z!y)$u   
Anomali detection utilities.

Called after laporan shift / stock adjustment is submitted or approved.
Creates AnomaliRecord entries for flagging — does not block transactions.
    )Decimal)Any)selectfunc)AsyncSession)AnomaliRecordStatusAnomaliTipeAnomali)LaporanShiftPenjualanNozzle)StockAdjustmentStockAdjustmentItem)Produk)NozzleSpbuTangkiNdbspbu_idtipedetail	deskripsi	produk_id	nozzle_idlaporan_shift_id
plat_nomorreturnc	                 r   K   t        ||||||||t        j                  	      }	| j                  |	       yw)zCInsert an anomaly record (idempotent-friendly: always creates new).)	r   r   r   r   r   r   r   r   statusN)r   r	   NEWadd)
r   r   r   r   r   r   r   r   r   records
             3/var/www/html/spbu.com/backend/app/utils/anomali.py_create_anomalir#      s@      )  
F FF6Ns   57laporanc                   K   | j                  t        t              j                  t        j                  |j
                  k(               d{   }|j                         }|syt        t        |j                  xs d            }|j                  xs g D ]  }|j                  xs t        d      |j                  xs t        d      z
  }|j                  xs t        d      |j                  xs t        d      z
  }|j                  rw|dk  r|dk  rt!        ||      }|dk  rt#        ||z
        }	|	dk  r|	|z  dz  }
|
|kD  s|j$                  r|j$                  j&                  nd|j(                   }t+        | |j
                  t,        j.                  |j                  |j(                  |j$                  r|j$                  j0                  ndd| dt3        |
d	       d
|	dd| d	|j                  |j(                  |t        |      t        |      t        |	      t        t3        |
d	            t        |      t        |j4                        |j6                  d
       d{     y7 )7 w)z
    METER_DISCREPANCY: fired when |manual_volume - digital_volume| > threshold %.
    Threshold from SPBU config: teller_discrepancy_threshold_pct (default 0.3%).
    Minimum absolute difference: 1 liter.
    Nz0.300r      d   zNozzle #z!Selisih teller manual vs digital z:    z% (z.3fz L) melebihi threshold %)
r   r   nozzle_namamanual_volumedigital_volumeselisihselisih_pctthreshold_pcttanggalshift_id)r   r   r   r   r   r   r   )executer   r   whereidr   scalar_one_or_noner   str teller_discrepancy_threshold_pctpenjualan_nozzleteller_akhir_manualteller_awal_manualteller_akhir_digitalteller_awal_digitalflag_reset_tellermaxabsnozzlenamar   r#   r
   METER_DISCREPANCYr   roundr0   r1   )r   r$   spbu_resultspbur/   pn
manual_voldigital_vol	referencediffpctr*   s               r"   check_meter_discrepancyrL   /   s-     

6$<#5#5dgg6P#QRRK))+DC E E PQRM''-2-,,:
r?T?T?bX_`aXbc
..<'!*AWAWAe[bcd[ef?{a/
K0	>:+,!8i3&,.II"))..Xbll^;TK! 22!(,,13"))--7}BS!}oSc
2I-XY[ )0

!##.%(_&)+&6"4y#&uS!}#5%(%7"7??3 ' 0 0  + . S8s,   AI<I7C>I<D I<.I:/	I<:I<	tangki_idstok_teoritisstok_aktualtotal_penjualantotal_penerimaanr0   c                   K   ||z
  }|dk\  ryt        |      }	| j                  t        t              j	                  t        j
                  |k(               d{   }
|
j                         }|r|j                  sy| j                  t        t              j	                  t        j
                  |j                  k(               d{   }|j                         }|syd}|dkD  r1t        t        |j                  xs d            }|	|z  dz  }||kD  rd}|dkD  r1t        t        |j                  xs d            }|	|z  dz  }||kD  rd}|r|t        | |t        j                  |j                  ||j                   |j                   t        |      t        |      t        |      t        |      t        |      |d		       d{    yy7 t7 7 w)
z
    LOSSES_EXCEEDED: fired when losses per tank exceed threshold.
    Thresholds from master_produk: losses_threshold_penjualan_pct, losses_threshold_penerimaan_pct.
    r   NFz0.500r'   Tz0.150)	rM   tangki_namaproduk_namarN   rO   lossesrP   rQ   r0   r   r   r   r   )r?   r2   r   r   r3   r4   r5   r   r   r   r6   losses_threshold_penjualan_pctlosses_threshold_penerimaan_pctr#   r
   LOSSES_EXCEEDEDrA   )r   r   rM   rN   rO   rP   rQ   r0   rU   
abs_lossestangki_resulttangkiproduk_resultproduk	triggeredpenjualan_thresholdpenjualan_pctpenerimaan_thresholdpenerimaan_pcts                      r"   check_losses_exceededrd   n   s     =(F{VJ **vVYY)34 M --/F))**vVYY&*:*::; M --/FI %c&*O*O*ZSZ&[\#o5<..I !&s6+Q+Q+\U\']^$'773>00I,,&&&%{{%{{!$]!3";/f+#&#7$'(8$9"

 	
 	
 =0	
s8   AGGA.G	G
C=GGGGGc                 l  K   |dk\  ry| j                  t        t              j                  t        j                  |k(               d{   }|j                         }t        | |t        j                  |r|j                  nd||r|j                  ndt        |      |d       d{    y7 e7 w)z=NEGATIVE_STOCK: fired when theoretical stock goes below zero.r   N)rM   rS   rN   r0   rV   )r2   r   r   r3   r4   r5   r#   r
   NEGATIVE_STOCKr   rA   r6   )r   r   rM   rN   r0   r[   r\   s          r"   check_negative_stockrg      s      **vVYY)34 M --/F

''&,&""$"*06;;d /	
  
s%   AB4
B0AB4*B2+B42B4c                 8   K   t        | |       d{    y7 w)zNRun all applicable anomali checks after a laporan shift is submitted/approved.N)rL   )r   r$   s     r"   run_penjualan_checksri      s     
!"g
...s   c           
      v   K   t        | ||||       d{    t        | |||||||       d{    y7 7 w)z2Run anomali checks after rekonsiliasi calculation.N)rg   rd   )r   r   rM   rN   rO   rP   rQ   r0   s           r"   run_rekonsiliasi_checksrk      sO      r7I}g
NNN

GY{)7   Os   959799)NNNNN)"__doc__decimalr   typingr   
sqlalchemyr   r   sqlalchemy.ext.asyncior   app.models.anomalir   r	   r
   app.models.operationalr   r   r   r   app.models.productr   app.models.spbur   r   r   intr6   dictr#   rL   rd   rg   ri   rk        r"   <module>ry      s     # / H H @ G % 0 0 !  #'!  cN	
 Tz Tz Tz Dj d
 
4<<< 
<~D
D
D
 D
 	D

 D
 D
 D
 D
 
D
N  	
  
</< /, /4 /
  	
     
rx   