o
    #j8                     @   s   d Z ddlZddlZddlZddlmZmZmZmZm	Z	 ddl
m
Z
 ddlZddlZddlZddlmZ ddlmZ G dd dZdS )	z
Regression/Classification Service
Handles XGBoost model training, persistence, and prediction.
Mirrors ProphetService pattern: 3-layer cache, .joblib disk, ml_regression_models DB table.
    N)DictListOptionalAnyTuple)datetime)
get_logger)
MySQLModelc                   @   s  e Zd ZU dZi Zeeef ed< e	
 ZdZe	
 Zdddddd	Zdddd
ddZdddZdddZdd ZdefddZdDddZdDddZdedefddZdededefd d!Zded"edefd#d$Zded"ed%ed&ed'ee d(eddfd)d*Zdedee fd+d,Zdedefd-d.Z	/	dEded0e j!d1e j"d%ed&ee deeef fd2d3Z#ded0e j!dee$ fd4d5Z%ded0e j!dee$ fd6d7Z&dedee fd8d9Z'dee fd:d;Z(dede)fd<d=Z*e+d>e$defd?d@Z,e+dAe$defdBdCZ-dS )FRegressionServicez<Unified service for XGBoost classifier and regressor models._cacheF      g?logloss*   )n_estimators	max_depthlearning_rateeval_metricrandom_statezreg:squarederror)r   r   r   	objectiver   g333333?gffffff?)HIGHMEDIUMg     R@g     V@)LOWr   c                 C   s&   t d| _t | _|   |   d S )Nregression_service)r   loggerr	   _db_ensure_table_ensure_model_dirself r    =/var/www/html/oms-prod-mcp/app/services/regression_service.py__init__/   s   
zRegressionService.__init__returnc              	   C   s.   ddl m} |jdtj|jdddS )Nr   )current_appREGRESSION_MODEL_DIRPROPHET_MODEL_DIRzstorage/app
regression)flaskr$   configgetospathjoin)r   r$   r    r    r!   _get_model_dir9   s   z RegressionService._get_model_dirNc                 C   s   t j|  dd d S )NT)exist_ok)r+   makedirsr.   r   r    r    r!   r   >   s   z#RegressionService._ensure_model_dirc                 C   sd   t jrd S t j  t jr	 W d    d S d}| j| dt _W d    d S 1 s+w   Y  d S )Na  
                CREATE TABLE IF NOT EXISTS `ml_regression_models` (
                    `id`          INT UNSIGNED NOT NULL AUTO_INCREMENT,
                    `model_id`    VARCHAR(120) NOT NULL,
                    `file_path`   VARCHAR(512) NOT NULL,
                    `model_type`  ENUM('classifier','regressor') NOT NULL,
                    `config`      JSON NOT NULL,
                    `features`    JSON NOT NULL,
                    `data_points` INT UNSIGNED NOT NULL DEFAULT 0,
                    `created_at`  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                    `updated_at`  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                    PRIMARY KEY (`id`),
                    UNIQUE KEY `uq_model_id` (`model_id`),
                    KEY `idx_model_type` (`model_type`),
                    KEY `idx_created_at` (`created_at`)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            T)r
   _table_ensured_table_lockr   execute_query)r   ddlr    r    r!   r   A   s   "zRegressionService._ensure_tablemodel_idc                 C   s,   d dd |D }tj |  | dS )N c                 s   s(    | ]}|  s|d v r|ndV  qdS ))-_r8   N)isalnum).0cr    r    r!   	<genexpr>\   s   & z5RegressionService._model_file_path.<locals>.<genexpr>z.joblib)r-   r+   r,   r.   )r   r5   safe_idr    r    r!   _model_file_path[   s   z"RegressionService._model_file_pathpayloadc                 C   s   |  |}tj||dd |S )N   )compress)r>   joblibdump)r   r5   r?   	file_pathr    r    r!   _save_to_disk_   s   
zRegressionService._save_to_diskrD   c                 C   s,   t j|std| d| dt|S )NzModel file for 'z' not found at '')r+   r,   isfileFileNotFoundErrorrB   load)r   r5   rD   r    r    r!   _load_from_diskd   s   
z!RegressionService._load_from_disk
model_typer)   featuresdata_pointsc              
   C   s.   d}| j ||||t|t||f d S )Na  
            INSERT INTO ml_regression_models
                (model_id, file_path, model_type, config, features, data_points)
            VALUES (%s, %s, %s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                file_path=VALUES(file_path), model_type=VALUES(model_type),
                config=VALUES(config), features=VALUES(features),
                data_points=VALUES(data_points), updated_at=CURRENT_TIMESTAMP
        )r   r3   jsondumps)r   r5   rD   rK   r)   rL   rM   sqlr    r    r!   _upsert_metadatai   s
   	
z"RegressionService._upsert_metadatac                 C   sh   | j d|f}|sd S |d }t|dtr!t|d |d< t|dtr2t|d |d< |S )Nz>SELECT * FROM ml_regression_models WHERE model_id = %s LIMIT 1r   r)   rL   )r   r3   
isinstancer*   strrN   loads)r   r5   rowsrowr    r    r!   _fetch_metadatay   s   z!RegressionService._fetch_metadatac                 C   s   t j |t jv rt j| W  d   S W d   n1 sw   Y  | |}|du r5td| dz| || |}W n tyW   | j	d|f t
d| dw t j |t j|< W d   |S 1 slw   Y  |S )u8   Three-layer fetch: process cache → DB metadata + disk.NModel 'z' not found4DELETE FROM ml_regression_models WHERE model_id = %sz;' file missing. Orphaned record removed. Re-train required.)r
   _cache_lockr   rW   
ValueErrorrJ   r>   rH   r   r3   RuntimeError)r   r5   metar?   r    r    r!   _resolve_model   s2   



z RegressionService._resolve_model
classifierXyc              
   C   s  zDddl m}m} ddlm} ddlm}	 ddlm}
m	}m
}m} t|dk r2tdt| dt|j}|d	krpt| j}||pEi  t| }t|| }|dkra|dkra|| |d
< |d i dd | D }nt| j}||pzi  |d i dd | D }| }||d}i }t|dkr|	||ddd\}}}}||| |d	kr||}tt|||d|d< n||}tt|||d|d< tt|||d|d< ||| n||| |||||t|t d}tj  | !||}| "|||||t| |tj#|< W d   n	1 s#w   Y  | j$%d| d| dt| d |d|t|||dW S  t&ya } z| j$'d| dt(|   d}~ww )!a{  
        Train an XGBoost model.

        Args:
            model_id: Unique identifier for this model
            X: Feature DataFrame
            y: Target Series
            model_type: 'classifier' or 'regressor'
            config: Override default XGBoost hyperparameters

        Returns:
            {model_id, status, data_points, features, model_type, metrics}
        r   )XGBClassifierXGBRegressor)StandardScaler)train_test_split)classification_reportmean_absolute_errorr2_scoreaccuracy_score   zInsufficient data (z rows, minimum 5 required)r_   scale_pos_weightc                 S      i | ]\}}|d kr||qS r   r    r:   kvr    r    r!   
<dictcomp>       z+RegressionService.train.<locals>.<dictcomp>c                 S   rl   rm   r    rn   r    r    r!   rq      rr   
   g?r   )	test_sizer   r   accuracymaer2)modelscalerrL   rK   r)   rM   
created_atNzTrained z model 'z' on z rowstrained)r5   statusrK   rM   rL   metricszError training model 'z': r    ))xgboostrb   rc   sklearn.preprocessingrd   sklearn.model_selectionre   sklearn.metricsrf   rg   rh   ri   lenr[   listcolumnsdictCLASSIFIER_DEFAULTSupdateintsumitemsREGRESSOR_DEFAULTSfit_transformfillnafitpredictroundfloatr   nowr
   rZ   rE   rQ   r   r   info	ExceptionerrorrS   )r   r5   r`   ra   rK   r)   rb   rc   rd   re   rf   rg   rh   ri   rL   defaultsposnegrx   ry   X_scaledr}   X_trX_tey_try_tepredsr?   rD   er    r    r!   train   sz   





$	zRegressionService.trainc                 C   s`   |  |}|d dkrtd| d|d ||d  d}|d |d	d	d
f  S )z4Return class-1 probabilities for a classifier model.rK   r_   rX   z' is not a classifierry   rL   r   rx   N   )r^   r[   	transformr   predict_probatolistr   r5   r`   r?   r   r    r    r!   r     s
   
zRegressionService.predict_probac                 C   sT   |  |}|d dkrtd| d|d ||d  d}|d | S )	zReturn regression predictions.rK   	regressorrX   z' is not a regressorry   rL   r   rx   )r^   r[   r   r   r   r   r   r    r    r!   r     s
   
zRegressionService.predictc              	   C   s   t j. |t jv r*t j| }||d |d |d |d |d  dW  d    S W d    n1 s4w   Y  | |}|d u rDd S |d |d |d |d |d t|d t|d d	S )
NrK   rM   rL   r)   rz   )r5   rK   rM   rL   r)   rz   r5   
updated_at)r5   rK   rM   rL   r)   rz   r   )r
   rZ   r   	isoformatrW   rS   )r   r5   r;   r]   r    r    r!   get_model_info  s0   





z RegressionService.get_model_infoc                 C   sv   | j d}g }|D ].}t|d tr|d nt|d }||d |d |d |t|d t|d d q
|S )	Nz}SELECT model_id, model_type, data_points, features, created_at, updated_at FROM ml_regression_models ORDER BY created_at DESCrL   r5   rK   rM   rz   r   )r5   rK   rM   rL   rz   r   )r   r3   rR   r   rN   rT   appendrS   )r   rU   resultrV   rL   r    r    r!   list_models0  s   $


zRegressionService.list_modelsc              
   C   s   |  |}tj |tjv }W d    n1 sw   Y  |d u r%|s%dS tj tj|d  W d    n1 s:w   Y  |roz| |}tj|rRt	| W n t
yn } z| jd|  W Y d }~nd }~ww | jd|f dS )NFzCould not delete model file: rY   T)rW   r
   rZ   r   popr>   r+   r,   rG   removeOSErrorr   warningr   r3   )r   r5   r]   in_cacherD   r   r    r    r!   delete_modelB  s.   


zRegressionService.delete_modelprobabilityc                 C   s(   | t jd kr	dS | t jd krdS dS )Nr   r   r   )r
   RISK_THRESHOLDS_COLLECTION)r   r    r    r!   collection_risk_level]  
   z'RegressionService.collection_risk_levelattainment_pctc                 C   s(   | t jd kr	dS | t jd krdS dS )Nr   r   r   )r
   RISK_THRESHOLDS_SALES)r   r    r    r!   sales_target_risk_levele  r   z)RegressionService.sales_target_risk_level)r#   N)r_   N).__name__
__module____qualname____doc__r   r   rS   r   __annotations__	threadingLockrZ   r1   r2   r   r   r   r   r"   r.   r   r   r>   rE   rJ   r   r   r   rQ   r   rW   r^   pd	DataFrameSeriesr   r   r   r   r   r   boolr   staticmethodr   r   r    r    r    r!   r
      s|   
 	





#

cr
   )r   r+   rN   r   typingr   r   r   r   r   r   pandasr   numpynprB   app.utils.helpersr   app.models.mysql_modelr	   r
   r    r    r    r!   <module>   s    