o
    #j.I                     @   s   U d Z ddl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
mZ ddlmZmZ ddlZddl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 ddlmZ dd	d
d
dZeeef e d< G dd dZ!dS )zT
Prophet Service Module
Handles Facebook Prophet time series forecasting operations
    N)DictListOptionalAnyTupleUnion)datetime	timedelta)Prophet)
get_logger)
MySQLModelMEQEYE)MQYA_FREQ_ALIASESc                   @   s  e Zd ZU dZi Zeeef ed< e	
 ZdZe	
 Zdd ZdefddZd=d
dZd=d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d Zdedee fddZdedefddZd>dee d ed!edejfd"d#Z				d?dee dee d$ee deeef fd%d&Z	'	(	)d@ded*ed+ed,edeeef f
d-d.Z 	'	(	/	0dAded*ed+ed1ed2edefd3d4Z!dedeeeef  fd5d6Z"deeeef  fd7d8Z#dedefd9d:Z$dee de%eef fd;d<Z&d	S )BProphetServicez<Service for handling Facebook Prophet forecasting operations_cacheFc                 C   s&   t d| _t | _|   |   d S )Nprophet_service)r   loggerr   _db_ensure_table_ensure_model_dirself r   :/var/www/html/oms-prod-mcp/app/services/prophet_service.py__init__$   s   
zProphetService.__init__returnc                 C   s   ddl m} |jd S )Nr   )current_appPROPHET_MODEL_DIR)flaskr"   config)r   r"   r   r   r   _get_model_dir.   s   
zProphetService._get_model_dirNc                 C   s   t j|  dd d S )NT)exist_ok)osmakedirsr&   r   r   r   r   r   2   s   z ProphetService._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_prophet_models` (
                    `id`          INT UNSIGNED NOT NULL AUTO_INCREMENT,
                    `model_id`    VARCHAR(120) NOT NULL,
                    `file_path`   VARCHAR(512) NOT NULL,
                    `config`      JSON         NOT NULL,
                    `data_points` INT UNSIGNED NOT NULL DEFAULT 0,
                    `train_start` DATE         NOT NULL,
                    `train_end`   DATE         NOT NULL,
                    `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_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   5   s   "zProphetService._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 ))-_r1   N)isalnum).0cr   r   r   	<genexpr>O   s   & z2ProphetService._model_file_path.<locals>.<genexpr>z.joblib)joinr(   pathr&   )r   r.   safe_idr   r   r   _model_file_pathN   s   zProphetService._model_file_pathpayloadc                 C   s   |  |}tj||dd |S )N   )compress)r9   joblibdump)r   r.   r:   	file_pathr   r   r   _save_to_diskR   s   
zProphetService._save_to_diskr?   c                 C   s,   t j|std| d| dt|S )NzModel file for 'z' not found at '')r(   r7   isfileFileNotFoundErrorr=   load)r   r.   r?   r   r   r   _load_from_diskW   s   
zProphetService._load_from_diskc              	   C   s(   d}| j |||t||||f d S )Na  
            INSERT INTO ml_prophet_models (model_id, file_path, config, data_points, train_start, train_end)
            VALUES (%s, %s, %s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                file_path=VALUES(file_path), config=VALUES(config),
                data_points=VALUES(data_points), train_start=VALUES(train_start),
                train_end=VALUES(train_end), updated_at=CURRENT_TIMESTAMP
        )r   r,   jsondumps)r   r.   r?   r%   data_pointstrain_start	train_endsqlr   r   r   _upsert_metadata\   s   $zProphetService._upsert_metadatac                 C   sF   | j d|f}|sd S |d }t|dtr!t|d |d< |S )Nz;SELECT * FROM ml_prophet_models WHERE model_id = %s LIMIT 1r   r%   )r   r,   
isinstancegetstrrF   loads)r   r.   rowsrowr   r   r   _fetch_metadatag   s   zProphetService._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.NzModel 'z' not found1DELETE FROM ml_prophet_models WHERE model_id = %sz<' file is missing. Orphaned record removed. Please re-train.)r   _cache_lockr   rS   
ValueErrorrE   r9   rC   r   r,   RuntimeError)r   r.   metar:   r   r   r   _resolve_modelr   s.   



zProphetService._resolve_modeldsydatadate_columnvalue_columnc              
   C   s   z;t |}||jv rt || ||< ||}||jv r)t j|| dd||< | }| jdt	| d |W S  t
y[ } z| jdt|  tdt| d}~ww )a+  
        Prepare data for Prophet model

        Args:
            data: List of dictionaries with date and value columns
            date_column: Name of the date column
            value_column: Name of the value column

        Returns:
            Pandas DataFrame formatted for Prophet
        coerce)errorszPrepared DataFrame with z rows for ProphetzError preparing DataFrame: zInvalid data format: N)pd	DataFramecolumnsto_datetimesort_values
to_numericdropnar   infolen	ExceptionerrorrO   rV   )r   r\   r]   r^   dfer   r   r   _prepare_dataframe   s   



z!ProphetService._prepare_dataframer%   c              
   C   s  z|  |}t|dk rtddddddddddd		}|r#|| tdi |}|| |s;d
t d }|d 	 d}|d 
 d}|||t t|||d}	tj | ||	}
| ||
|t||| |	tj|< W d   n1 sw   Y  | jd| dt| d |dt|||d 	  |d 
  ddW S  ty } z| jdt|   d}~ww )a	  
        Train a Prophet model

        Args:
            data: Time series data
            model_id: Optional model identifier for storage
            config: Prophet configuration parameters

        Returns:
            Training results with model info
           z?Insufficient data for training (minimum 2 data points required)additiveTFg?g      $@g?)	seasonality_modeyearly_seasonalityweekly_seasonalitydaily_seasonalitychangepoint_prior_scaleseasonality_prior_scaleholidays_prior_scalechangepoint_rangeinterval_widthprophet_z%Y%m%d_%H%M%SrZ   %Y-%m-%d)modelr%   training_data
created_atrH   rI   rJ   NzTrained Prophet model z with z data pointstrained)startend)r.   statusrH   r%   training_date_rangezError training Prophet model: r   )rn   ri   rV   updater
   fitr   nowstrftimeminmaxr   rU   r@   rL   r   r   rh   	isoformatrj   rk   rO   )r   r\   r.   r%   rl   prophet_configr|   rI   rJ   r:   r?   rm   r   r   r   train_model   sZ   


zProphetService.train_model   DTperiodsfreqinclude_historyc                 C   sv  z|  |}|d }t||}|j||d}||}|s$t|d nd}	g }
t|	t|D ];}|
|d j| 	dt
dtt|d j| d	t
dtt|d
 j| d	t
dtt|d j| d	d q/||||
t  d}|r|d }|d j	d |d d	 d|d< | jd| d| d |W S  ty } z| jdt|   d}~ww )ap  
        Generate forecast using trained model

        Args:
            model_id: ID of the trained model
            periods: Number of periods to forecast
            freq: Frequency of forecast ('D' for daily, 'H' for hourly, etc.)
            include_history: Whether to include historical data in response

        Returns:
            Forecast results
        r|   r   r   r}   r   rZ   r{   g        yhat   
yhat_lower
yhat_upper)rZ   r   r   r   )r.   forecast_periods	frequencyforecastgenerated_atr[   rZ   r[   historical_datazGenerated forecast for model z: z periodszError generating forecast: N)rY   r   rN   make_future_dataframepredictri   rangeappendilocr   r   roundfloatr   r   r   dttolistr   rh   rj   rk   rO   )r   r.   r   r   r   
model_infor|   futurer   	start_idxforecast_rowsiresultr}   rm   r   r   r   generate_forecast  sB   




z ProphetService.generate_forecast   X  widthheightc              
   C   s   z\|  |}|d }t||}|j||d}||}	|j|	|d |d fd}
t }|
j|dddd |	d t
| d	}|  t|
 | jd
|  d| W S  tyt } z| jdt|   d}~ww )aa  
        Generate forecast plot as base64 encoded image

        Args:
            model_id: ID of the trained model
            periods: Number of periods to forecast
            freq: Frequency of forecast
            width: Plot width in pixels
            height: Plot height in pixels

        Returns:
            Base64 encoded PNG image
        r|   r   d   )figsizepngtight)formatdpibbox_inchesr   zutf-8zGenerated plot for model zdata:image/png;base64,zError generating plot: N)rY   r   rN   r   r   plotioBytesIOsavefigseekbase64	b64encodegetvaluedecodeclosepltr   rh   rj   rk   rO   )r   r.   r   r   r   r   r   r|   r   r   figbufferimage_base64rm   r   r   r   generate_plotJ  s(   



zProphetService.generate_plotc                 C   s   t j/ |t jv r+t j| }|d |d |d  |d|ddW  d   S W d   n1 s5w   Y  | |}|du rEdS |d |d t|d t|d t|d dS )z
        Get information about a trained model

        Args:
            model_id: ID of the model

        Returns:
            Model information or None if not found
        rH   r%   r~   rI   rJ   )rH   r%   r~   rI   rJ   N)r   rU   r   r   rN   rS   rO   )r   r.   r4   rX   r   r   r   get_model_info}  s"   





zProphetService.get_model_infoc                 C   sz   | j d}g }|D ]0}t|d tr|d nt|d }||d |d |t|d t|d t|d d q
|S )	z_
        List all trained models

        Returns:
            List of model summaries
        zxSELECT model_id, data_points, config, train_start, train_end, created_at FROM ml_prophet_models ORDER BY created_at DESCr%   r.   rH   rI   rJ   r~   )r.   rH   r%   rI   rJ   r~   )r   r,   rM   dictrF   rP   r   rO   )r   rQ   r   rR   r%   r   r   r   list_models  s   $
zProphetService.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 )z
        Delete a trained model

        Args:
            model_id: ID of the model to delete

        Returns:
            True if deleted, False if not found
        NFzCould not delete model file: rT   T)rS   r   rU   r   popr9   r(   r7   rB   removeOSErrorr   warningr   r,   )r   r.   rX   in_cacher?   rm   r   r   r   delete_model  s*   



zProphetService.delete_modelc                 C   s   t |trt|dk rdS ddh}t|D ]R\}}t |ts(dd| df  S || s9dd| df  S z	t|d  W n   dd	| f Y   S zt	|d  W q   dd
| f Y   S dS )z
        Validate input data format for Prophet

        Args:
            data: Input data to validate

        Returns:
            Tuple of (is_valid, error_message)
        ro   )Fz/Data must be a list with at least 2 data pointsrZ   r[   FzData point z must be a dictionaryz must contain 'ds' and 'y' keysz"Invalid date format in data point z$Invalid numeric value in data point )Tr/   )
rM   listri   	enumerater   issubsetkeysra   rd   r   )r   r\   required_keysr   itemr   r   r   validate_data_format  s"   

z#ProphetService.validate_data_format)r!   Nr   )NN)r   r   T)r   r   r   r   )'__name__
__module____qualname____doc__r   r   rO   r   __annotations__	threadingLockrU   r*   r+   r    r&   r   r   r9   r@   rE   rL   r   rS   rY   r   ra   rb   rn   r   intboolr   r   r   r   r   r   r   r   r   r   r   r      s~   
 


" )

Q

G
3"r   )"r   r(   rF   loggingr   typingr   r   r   r   r   r   r   r	   pandasra   numpynpprophetr
   r   r   r=   matplotlib.pyplotpyplotr   app.utils.helpersr   app.models.mysql_modelr   r   rO   r   r   r   r   r   r   <module>   s$     