
    Yi%                        d Z ddlZddlmZ ddlmZ ddlmZmZm	Z	m
Z
 ddlm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mZmZmZmZmZmZm Z m!Z!m"Z"m#Z# ddl$m%Z% ddl&m'Z'  ee(          Z) G d d          Z*dS )zv
Storage service for health data.

Handles JSON file storage and database indexing for health metrics and activities.
    N)date)Path)OptionalUnionDictAny)	BaseModel)config)HealthRepository)StorageErrorDataValidationError)setup_logger)	StepsDataHeartRateData	SleepData
StressDataBodyBatteryDataSpO2DataRespirationDataHydrationData
FloorsDataIntensityMinutesDataHRVDataRHRDataLifestyleLoggingData)Activity)
WeightDatac                   4   e Zd ZdZddee         ddfdZ	 ddeded	e	defd
Z
	 ddeded	e	defdZdeeeeeeeeeeeeeeef         dedefdZdededeeeef                  fdZdedefdZ dedeeeef                  fdZ!dedede	fdZ"dede	fdZ#dS )HealthStoragezCService for storing health data to JSON files and database indexes.Ndata_dirreturnc                 R    |pt           j        | _        t                      | _        dS )zInitialize storage service.

        Args:
            data_dir: Optional data directory path (defaults to config.DATA_DIR)
        N)r
   DATA_DIRr    r   repo)selfr    s     0/root/projects/butler/health/services/storage.py__init__zHealthStorage.__init__*   s#     !3FO$&&			    Tstorage_pathtarget_datecreate_dirsc                     t          |j                  }|j        d}|                                 d}| j        |z  |z  |z  |z  }|r|j                            dd           |S )a.  Get file path for a data item.

        Args:
            storage_path: Relative storage path (e.g., "daily_metrics/steps")
            target_date: Date of the data
            create_dirs: Whether to create directories if they don't exist

        Returns:
            Full path to JSON file
        02d.jsonTparentsexist_ok)stryearmonth	isoformatr    parentmkdir)r%   r)   r*   r+   r3   r4   filename	file_paths           r&   _get_file_pathzHealthStorage._get_file_path3   s     ;#$$$**!++--444ML047%?(J	 	@""4$"???r(   activity_idactivity_datec                     t          |j                  }|j        d}| d}| j        dz  |z  |z  |z  }|r|j                            dd           |S )a   Get file path for an activity.

        Args:
            activity_id: Activity ID
            activity_date: Date of the activity
            create_dirs: Whether to create directories

        Returns:
            Full path to activity JSON file
        r-   r.   
activitiesTr/   )r2   r3   r4   r    r6   r7   )r%   r;   r<   r+   r3   r4   r8   r9   s           r&   _get_activity_file_pathz%HealthStorage._get_activity_file_pathL   sv     =%&& &,,!(((ML047%?(J	 	@""4$"???r(   datametric_typec                    	 t          |t                    st          dt          |                     |t          j        vrt          d|           t          j        |         d         }|                     ||j                  }|	                    dd          }t          |dd	          5 }t          j        ||d
d           ddd           n# 1 swxY w Y   t                              d| d|j         d|            | j                            ||j        |d           |S # t          $ r  t"          $ r;}t                              d| d|            t          d| d|           |d}~ww xY w)ab  Save a daily metric to JSON file and update database index.

        Args:
            data: Pydantic model instance
            metric_type: Type of metric (e.g., "steps", "sleep")

        Returns:
            Path to saved file

        Raises:
            StorageError: If save fails
            DataValidationError: If data validation fails
        z#Data must be a Pydantic model, got zUnknown metric type: r)   jsonTmodeexclude_nonewutf-8encoding   Findentensure_asciiNzSaved 
 data for z to )rA   metric_dater9   has_datazFailed to save z data: )
isinstancer	   r   typer
   DATA_TYPE_CONFIGr   r:   r   
model_dumpopenrC   dumploggerdebugr$   index_daily_metric	Exceptionerror)r%   r@   rA   r)   r9   	data_dictfes           r&   save_daily_metriczHealthStorage.save_daily_metricd   s   B$	QdI.. ^)*\PTUYPZPZ*\*\]]] &"999"#H;#H#HIII!2;?OL ++L$)DDI V$GGIiw777 F1	)QquEEEEF F F F F F F F F F F F F F F LLS+SSSS	SSTTT I((' I#	 )    " 	 	 	 	Q 	Q 	QLLB;BBqBBCCCHHHQHHIIqP	Qs=   B0D/ 2CD/ CD/ CAD/ /E>6E99E>c           	          	 | j                             ||          }|rt          |          }n0t          j        |         d         }|                     ||d          }|                                sdS t          |dd          5 }t          j	        |          }ddd           n# 1 swxY w Y   t                              d| d	|            |S # t          $ r.}t                              d
| d	| d|            Y d}~dS d}~ww xY w)zLoad a daily metric from JSON file.

        Args:
            metric_type: Type of metric
            target_date: Date of the data

        Returns:
            Data dictionary or None if not found
        r)   F)r+   NrrH   rI   zLoaded rO   zFailed to load : )r$   get_daily_metric_pathr   r
   rT   r:   existsrV   rC   loadrX   rY   r[   r\   )	r%   rA   r*   indexed_pathr9   r)   r^   r@   r_   s	            r&   load_daily_metriczHealthStorage.load_daily_metric   s{   	9::;TTL  ..		  &6{CNS // +5 0  	 ##%% tiw777 $1y||$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ LLG;GG+GGHHHK 	 	 	LLT;TT+TTQRTTUUU44444	sB   A1C 5C B(C (B,,C /B,0$C 
D#DDactivityc                 Z   	 |                      |j        |j                  }|                    dd          }t	          |dd          5 }t          j        ||dd	           d
d
d
           n# 1 swxY w Y   t                              d|j         d|j	         d|            | j
                            |j        |j	        |j        ||j        |j                   |S # t          $ r=}t                              d|j         d|            t!          d|           |d
}~ww xY w)zSave an activity to JSON file and update database index.

        Args:
            activity: Activity model instance

        Returns:
            Path to saved file

        Raises:
            StorageError: If save fails
        rC   TrD   rG   rH   rI   rK   FrL   NzSaved activity z (z) to )r;   activity_typer<   r9   duration_secondsdistance_meterszFailed to save activity rc   zFailed to save activity: )r?   r;   r   rU   rV   rC   rW   rX   rY   rk   r$   index_activityrl   rm   r[   r\   r   )r%   ri   r9   r]   r^   r_   s         r&   save_activityzHealthStorage.save_activity   s   	G44$hm I
 !++d+KKIiw777 F1	)QquEEEEF F F F F F F F F F F F F F F LLb("6bb(:PbbW`bb  
 I$$$0&4&m#!)!: ( 8 %     	G 	G 	GLLOH4HOOAOOPPP>1>>??QF	Gs=   A	C# A0$C# 0A44C# 7A48A*C# #
D*-8D%%D*c                    	 | j                                         5 }|                                }|                    d|f           |                                }ddd           n# 1 swxY w Y   |sdS t          |d                   }|                                s"t                              d| d|            dS t          |dd          5 }t          j        |          }ddd           n# 1 swxY w Y   t                              d	|            |S # t          $ r+}t                              d
| d|            Y d}~dS d}~ww xY w)zLoad an activity from JSON file.

        Args:
            activity_id: Activity ID

        Returns:
            Activity data dictionary or None if not found
        z:SELECT file_path FROM activity_index WHERE activity_id = ?Nr9   z	Activity z indexed but file not found: rb   rH   rI   zLoaded activity zFailed to load activity rc   )r$   	_get_conncursorexecutefetchoner   re   rX   warningrV   rC   rf   rY   r[   r\   )	r%   r;   connrr   rowr9   r^   r@   r_   s	            r&   load_activityzHealthStorage.load_activity   s   	$$&& ($P N   oo''( ( ( ( ( ( ( ( ( ( ( ( ( ( (  tS-..I##%% UUU)UU   tiw777 $1y||$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ LL9K99:::K 	 	 	LLFKFF1FFGGG44444	sl   D A A'D 'A++D .A+/D 6A	D D C4(D 4C88D ;C8<!D 
E( EEc                     | j                             ||          }|duo t          |                                          S )zCheck if a metric already exists for a date.

        Args:
            metric_type: Type of metric
            target_date: Date to check

        Returns:
            True if metric exists
        N)r$   rd   r   re   )r%   rA   r*   rg   s       r&   metric_existszHealthStorage.metric_exists%  s?     y66{KPP4'GD,>,>,E,E,G,GGr(   c                 6    | j                             |          S )zCheck if an activity already exists.

        Args:
            activity_id: Activity ID

        Returns:
            True if activity exists
        )r$   activity_exists)r%   r;   s     r&   r|   zHealthStorage.activity_exists2  s     y((555r(   )N)T)$__name__
__module____qualname____doc__r   r   r'   r2   r   boolr:   r?   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r`   r   r   rh   r   ro   rx   rz   r|    r(   r&   r   r   '   s       MM' '$ '4 ' ' ' ' IM .2AE	   4 JN /3BF	   0EQ  "
EQ$ %EQ& 
'EQ EQ EQ EQN$$-1$	$sCx.	!$ $ $ $L*Gh *G4 *G *G *G *GX& &$sCx.1I & & & &PH H4 HD H H H H	63 	64 	6 	6 	6 	6 	6 	6r(   r   )+r   rC   datetimer   pathlibr   typingr   r   r   r   pydanticr	   healthr
   health.db.repositoryr   health.utils.exceptionsr   r   health.utils.logging_configr   health.models.daily_metricsr   r   r   r   r   r   r   r   r   r   r   r   r   health.models.activityr   health.models.body_metricsr   r}   rX   r   r   r(   r&   <module>r      s                 - - - - - - - - - - - -             1 1 1 1 1 1 E E E E E E E E 4 4 4 4 4 4                              , + + + + + 1 1 1 1 1 1	h		T6 T6 T6 T6 T6 T6 T6 T6 T6 T6r(   