DHDRL commited on
Commit
2ff3e89
·
verified ·
1 Parent(s): a6c3c32

Update hierarchical_search.py

Browse files
Files changed (1) hide show
  1. hierarchical_search.py +2 -40
hierarchical_search.py CHANGED
@@ -59,13 +59,7 @@ logger = logging.getLogger(__name__)
59
  # ---------------------------------------------------------------------------
60
  # Depth -> data resolution
61
  # ---------------------------------------------------------------------------
62
- # Coarse levels use cheap/already-area-representative sources; only the
63
- # deepest, smallest, flagged nodes ever pay for the expensive ones.
64
- # BMKG_STATION (Indonesian met agency station data) is the ideal finest-leaf
65
- # source for an Indonesia deployment if a real feed is wired up -- left out
66
- # of the default chain since era5_data_pipeline.py's fetchers dict (in
67
- # fetch_zone_obs) doesn't currently implement a BMKG fetcher; add one there
68
- # first, then just append it below.
69
  RESOLUTION_BY_DEPTH: List[DataSource] = [
70
  DataSource.ERA5_REANALYSIS, # depth 0-1: country / province scale
71
  DataSource.ERA5_REANALYSIS,
@@ -95,8 +89,6 @@ class BBox:
95
  return ((self.lat_min + self.lat_max) / 2.0, (self.lon_min + self.lon_max) / 2.0)
96
 
97
  def split(self) -> Tuple["BBox", "BBox"]:
98
- """Bisect along whichever axis (lat or lon) currently spans further --
99
- literally the 'top/bottom or left/right 50%' heuristic."""
100
  lat_span = self.lat_max - self.lat_min
101
  lon_span = self.lon_max - self.lon_min
102
  if lat_span >= lon_span:
@@ -140,8 +132,6 @@ class SpatialNode:
140
 
141
  @dataclass
142
  class ResolvedLeaf:
143
- """One terminal result from the search -- either max_depth was hit or the
144
- node's coarse read was already below the alert floor."""
145
  node: SpatialNode
146
  max_risk: float
147
  alert_level: AlertLevel
@@ -157,20 +147,11 @@ def _score_node(
157
  date_range: Tuple[datetime, datetime],
158
  config_template: ForecastConfig,
159
  ) -> Tuple[float, AlertLevel, DataSource]:
160
- """Register this node's bbox as a zone (idempotent -- register_zone just
161
- overwrites), fetch it at the resolution appropriate for its depth, and
162
- score it with the exact same crop_risk_scorer used everywhere else in
163
- this project. No RL involved -- this is Phase 1's entire decision rule.
164
- """
165
  polygon = node.bbox.to_polygon(node.node_id)
166
  register_zone(polygon)
167
 
168
  cfg_dict = config_template.to_dict()
169
  if config_template.force_data_source is None:
170
- # Only apply depth-based resolution when the caller hasn't already
171
- # pinned a source -- an explicit override (e.g. forcing SYNTHETIC
172
- # for an offline test) must actually take effect everywhere, not
173
- # get silently clobbered per-node.
174
  source = _resolution_for_depth(node.depth)
175
  cfg_dict["force_data_source"] = source.value
176
  else:
@@ -193,18 +174,6 @@ def hierarchical_search(
193
  recurse_floor: AlertLevel = AlertLevel.WARNING,
194
  config_template: Optional[ForecastConfig] = None,
195
  ) -> List[ResolvedLeaf]:
196
- """Budget-constrained best-first traversal of the bisection tree.
197
-
198
- Best-first: a max-heap on max_risk, so a hard budget cap spends its
199
- fetches on the worst-looking parts of the map first. A branch stops
200
- (records a ResolvedLeaf) at max_depth or once alert_level drops below
201
- recurse_floor. Total fetches <= budget always, regardless of leaf count
202
- -- that's what makes country scale tractable.
203
-
204
- Default recurse_floor is WARNING (product freeze): only refine regions
205
- that already clear the client-facing drought/flood bars. Pass
206
- AlertLevel.WATCH or ADVISORY to recurse more aggressively.
207
- """
208
  cfg_template = config_template or ForecastConfig()
209
  root = SpatialNode("root", root_bbox, depth=0)
210
 
@@ -238,10 +207,6 @@ def hierarchical_search(
238
  heapq.heappush(frontier, (-max_risk, next(counter), child))
239
 
240
  if failed > 0 and failed == spent:
241
- # Every single node failed -- almost certainly a systemic problem
242
- # (bad config, no network at all, wrong credentials), not noise.
243
- # Returning an empty/near-empty list here would look identical to
244
- # "searched thoroughly, found nothing risky" -- silently wrong.
245
  raise RuntimeError(
246
  f"hierarchical_search: all {spent} scored nodes failed -- "
247
  f"check config_template/credentials before trusting any result "
@@ -289,7 +254,6 @@ def _decide_with_policy(node: SpatialNode, children_contexts, model, env_factory
289
  if __name__ == "__main__":
290
  logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
291
 
292
- # Roughly Indonesia's bounding box, for a realistic worst-case leaf count.
293
  indonesia_bbox = BBox(lat_min=-11.0, lat_max=6.0, lon_min=95.0, lon_max=141.0)
294
 
295
  date_range = (
@@ -297,8 +261,6 @@ if __name__ == "__main__":
297
  datetime(2026, 7, 31, tzinfo=timezone.utc),
298
  )
299
 
300
- # force SYNTHETIC everywhere so this self-test needs no API credentials
301
- # and no network access -- purely validates the tree/budget/pruning logic.
302
  cfg = ForecastConfig(force_data_source=DataSource.SYNTHETIC)
303
 
304
  results = hierarchical_search(
@@ -317,4 +279,4 @@ if __name__ == "__main__":
317
  for depth in sorted(by_depth):
318
  leaves = by_depth[depth]
319
  alerts = [l.alert_level.value for l in leaves]
320
- print(f" depth {depth}: {len(leaves)} branches stopped here -- alerts: {alerts}")
 
59
  # ---------------------------------------------------------------------------
60
  # Depth -> data resolution
61
  # ---------------------------------------------------------------------------
62
+
 
 
 
 
 
 
63
  RESOLUTION_BY_DEPTH: List[DataSource] = [
64
  DataSource.ERA5_REANALYSIS, # depth 0-1: country / province scale
65
  DataSource.ERA5_REANALYSIS,
 
89
  return ((self.lat_min + self.lat_max) / 2.0, (self.lon_min + self.lon_max) / 2.0)
90
 
91
  def split(self) -> Tuple["BBox", "BBox"]:
 
 
92
  lat_span = self.lat_max - self.lat_min
93
  lon_span = self.lon_max - self.lon_min
94
  if lat_span >= lon_span:
 
132
 
133
  @dataclass
134
  class ResolvedLeaf:
 
 
135
  node: SpatialNode
136
  max_risk: float
137
  alert_level: AlertLevel
 
147
  date_range: Tuple[datetime, datetime],
148
  config_template: ForecastConfig,
149
  ) -> Tuple[float, AlertLevel, DataSource]:
 
 
 
 
 
150
  polygon = node.bbox.to_polygon(node.node_id)
151
  register_zone(polygon)
152
 
153
  cfg_dict = config_template.to_dict()
154
  if config_template.force_data_source is None:
 
 
 
 
155
  source = _resolution_for_depth(node.depth)
156
  cfg_dict["force_data_source"] = source.value
157
  else:
 
174
  recurse_floor: AlertLevel = AlertLevel.WARNING,
175
  config_template: Optional[ForecastConfig] = None,
176
  ) -> List[ResolvedLeaf]:
 
 
 
 
 
 
 
 
 
 
 
 
177
  cfg_template = config_template or ForecastConfig()
178
  root = SpatialNode("root", root_bbox, depth=0)
179
 
 
207
  heapq.heappush(frontier, (-max_risk, next(counter), child))
208
 
209
  if failed > 0 and failed == spent:
 
 
 
 
210
  raise RuntimeError(
211
  f"hierarchical_search: all {spent} scored nodes failed -- "
212
  f"check config_template/credentials before trusting any result "
 
254
  if __name__ == "__main__":
255
  logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
256
 
 
257
  indonesia_bbox = BBox(lat_min=-11.0, lat_max=6.0, lon_min=95.0, lon_max=141.0)
258
 
259
  date_range = (
 
261
  datetime(2026, 7, 31, tzinfo=timezone.utc),
262
  )
263
 
 
 
264
  cfg = ForecastConfig(force_data_source=DataSource.SYNTHETIC)
265
 
266
  results = hierarchical_search(
 
279
  for depth in sorted(by_depth):
280
  leaves = by_depth[depth]
281
  alerts = [l.alert_level.value for l in leaves]
282
+ print(f" depth {depth}: {len(leaves)} branches stopped here -- alerts: {alerts}")