2626import copy
2727import datetime
2828import logging
29- import warnings
29+ from typing import cast
3030
31+ import numpy as np
3132import pandas as pd
3233
3334from climada .entity .exposures import Exposures
@@ -52,6 +53,10 @@ class Snapshot:
5253 date : datetime.date | str | pd.Timestamp
5354 The date of the Snapshot, it can be an string representing a year,
5455 a datetime object or a string representation of a datetime object.
56+ measure : Measure | None, default None.
57+ Measure associated with the Snapshot. The measure object is *not* applied
58+ to the other parameters of the object (Exposure, Hazard, Impfset).
59+ Users should leave it to None, and use `apply_measure()` instead (see notes).
5560 ref_only : bool, default False
5661 Should the `Snapshot` contain deep copies of the Exposures, Hazard and Impfset (False)
5762 or references only (True).
@@ -61,19 +66,27 @@ class Snapshot:
6166 date : datetime
6267 Date of the snapshot.
6368 measure: Measure | None
64- The possible measure applied to the snapshot.
69+ A possible measure associated with the snapshot.
6570
6671 Notes
6772 -----
6873
69- The object creates deep copies of the exposure hazard and impact function set.
74+ Providing a measure to the init assumes that the (Exposure, Hazard, Impfset) triplet
75+ already corresponds to the triplet once the measure is applied. As Measure objects
76+ contain "the changes to apply", the creating a consistent Snapshot with a measure should
77+ be done by first creating a Snapshot with the "baseline" (Exposure, Hazard, Impfset) triplet
78+ and calling `<Snapshot>.apply_measure(<measure>)`, which returns a new Snapshot object
79+ with the measure applied.
80+
81+ Instantiating a Snapshot with a measure directly does not garantee the
82+ consistency between the triplet and the measure, and should be avoided.
83+
84+ If `ref_only` is True (default) the object creates deep copies of the
85+ exposure, hazard, and impact function set.
7086
7187 Also note that exposure, hazard and impfset are read-only properties.
72- Consider snapshot as immutable objects.
88+ Consider snapshots as immutable objects.
7389
74- To create a snapshot with a measure, create a snapshot `snap` without
75- the measure and call `snap.apply_measure(measure)`, which returns a new Snapshot object
76- with the measure applied to its risk dimensions.
7790 """
7891
7992 def __init__ (
@@ -82,74 +95,16 @@ def __init__(
8295 exposure : Exposures ,
8396 hazard : Hazard ,
8497 impfset : ImpactFuncSet ,
85- measure : Measure | None ,
8698 date : datetime .date | str | pd .Timestamp ,
99+ measure : Measure | None = None ,
87100 ref_only : bool = False ,
88- _from_factory : bool = False ,
89101 ) -> None :
90- if not _from_factory :
91- warnings .warn (
92- "Direct instantiation of 'Snapshot' is discouraged. "
93- "Use 'Snapshot.from_triplet()' instead." ,
94- UserWarning ,
95- stacklevel = 2 ,
96- )
97102 self ._exposure = exposure if ref_only else copy .deepcopy (exposure )
98103 self ._hazard = hazard if ref_only else copy .deepcopy (hazard )
99104 self ._impfset = impfset if ref_only else copy .deepcopy (impfset )
100105 self ._measure = measure if ref_only else copy .deepcopy (measure )
101106 self ._date = self ._convert_to_timestamp (date )
102107
103- @classmethod
104- def from_triplet (
105- cls ,
106- * ,
107- exposure : Exposures ,
108- hazard : Hazard ,
109- impfset : ImpactFuncSet ,
110- date : datetime .date | str | pd .Timestamp ,
111- ref_only : bool = False ,
112- ) -> "Snapshot" :
113- """Create a Snapshot from exposure, hazard and impact functions set
114-
115- This method is the main point of entry for the creation of Snapshot. It
116- creates a new Snapshot object for the given date with copies of the
117- hazard, exposure and impact function set given in argument (or
118- references if ref_only is True)
119-
120- Parameters
121- ----------
122- exposure : Exposures
123- hazard : Hazard
124- impfset : ImpactFuncSet
125- date : datetime.date | str | pd.Timestamp
126- ref_only : bool
127- If true, uses references to the exposure, hazard and impact
128- function objects. Note that modifying the original objects after
129- computations using the Snapshot might lead to inconsistencies in
130- results.
131-
132- Returns
133- -------
134- Snapshot
135-
136- Notes
137- -----
138-
139- To create a Snapshot with a measure, first create the Snapshot without
140- the measure using this method, and use `apply_measure(measure)` afterward.
141-
142- """
143- return cls (
144- exposure = exposure ,
145- hazard = hazard ,
146- impfset = impfset ,
147- measure = None ,
148- date = date ,
149- ref_only = ref_only ,
150- _from_factory = True ,
151- )
152-
153108 @property
154109 def exposure (self ) -> Exposures :
155110 """Exposure data for the snapshot."""
@@ -176,7 +131,7 @@ def date(self) -> pd.Timestamp:
176131 return self ._date
177132
178133 @property
179- def impact_calc_data (self ) -> dict :
134+ def impact_calc_kwargs (self ) -> dict :
180135 """Convenience function for ImpactCalc class."""
181136 return {
182137 "exposures" : self .exposure ,
@@ -185,20 +140,36 @@ def impact_calc_data(self) -> dict:
185140 }
186141
187142 @staticmethod
188- def _convert_to_timestamp (date_arg ) -> pd .Timestamp :
189- """Convert date argument of type str or datetime.date to pandas Timestamp object."""
143+ def _convert_to_timestamp (
144+ date_arg : str | datetime .date | pd .Timestamp | np .datetime64 ,
145+ ) -> pd .Timestamp :
146+ """
147+ Convert date argument of type str, datetime.date,
148+ np.datetime64, or pandas Timestamp to a pandas Timestamp object.
149+ """
190150 if isinstance (date_arg , str ):
191- # Try to parse the string as a date
192151 try :
193- return pd .Timestamp (date_arg )
194- except ValueError as exc :
195- raise ValueError ("String must be in the format 'YYYY-MM-DD'" ) from exc
196- if isinstance (date_arg , datetime .date ):
197- return pd .Timestamp (date_arg )
198- if isinstance (date_arg , pd .Timestamp ):
199- return date_arg
152+ date = pd .Timestamp (date_arg )
153+ except (ValueError , TypeError ) as exc :
154+ raise ValueError (
155+ "String must be in a valid date format (e.g., 'YYYY-MM-DD')"
156+ ) from exc
157+
158+ elif isinstance (date_arg , (datetime .date , pd .Timestamp , np .datetime64 )):
159+ date = pd .Timestamp (date_arg )
160+
161+ else :
162+ raise TypeError (
163+ f"Unsupported type: { type (date_arg )} . Must be str, date, Timestamp, or datetime64."
164+ )
165+
166+ # Final check for NaT (Not-a-Time)
167+ if date is pd .NaT :
168+ raise ValueError (
169+ f"Could not resolve '{ date_arg } ' to a valid Pandas Timestamp."
170+ )
200171
201- raise TypeError ( "date_arg must be an str, datetime. date or pandas.Timestamp" )
172+ return cast ( pd . Timestamp , date )
202173
203174 def apply_measure (self , measure : Measure ) -> "Snapshot" :
204175 """Create a new snapshot by applying a Measure object.
@@ -226,6 +197,5 @@ def apply_measure(self, measure: Measure) -> "Snapshot":
226197 date = self .date ,
227198 measure = measure ,
228199 ref_only = True , # Avoid unecessary copies of new objects
229- _from_factory = True ,
230200 )
231201 return snap
0 commit comments