Files changed (1) hide show
  1. app.py +103 -6
app.py CHANGED
@@ -1589,22 +1589,27 @@ def _select_best_polygon_row(
1589
  return rank.iloc[0], float("nan"), alerts
1590
 
1591
 
1592
- def _infer_polygon_context_by_numbloco(
1593
  polygons: PolygonContext,
1594
  numbloco: Any,
1595
  x: float | None = None,
1596
  y: float | None = None,
1597
- ) -> tuple[dict[str, Any], dict[str, str], list[str]]:
1598
  if not polygons.has_data or not polygons.numbloco_key_index:
1599
- return {}, {}, []
1600
 
1601
  key = _normalize_numbloco_key(numbloco)
1602
  if not key:
1603
- return {}, {}, []
1604
 
1605
  candidate_idx = polygons.numbloco_key_index.get(key)
1606
  if not candidate_idx:
1607
- return {}, {}, [f"NUMBLOCO `{_safe_text(numbloco, '')}` nao encontrado no geoparquet enriquecido; usada busca espacial."]
 
 
 
 
 
1608
 
1609
  point = None
1610
  x_number = _to_float(x)
@@ -1620,11 +1625,29 @@ def _infer_polygon_context_by_numbloco(
1620
  matches = polygons.gdf.loc[candidate_idx]
1621
  row, polygon_distance, alerts = _select_best_polygon_row(polygons, matches, point)
1622
  if row is None:
1623
- return {}, {}, [f"NUMBLOCO `{_safe_text(numbloco, '')}` sem linha utilizavel no geoparquet; usada busca espacial."]
 
 
 
 
 
1624
 
1625
  if len(candidate_idx) > 1:
1626
  alerts.append(f"NUMBLOCO encontrado em {len(candidate_idx)} poligonos; aplicado desempate deterministico.")
1627
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1628
  values, sources, _ = _polygon_row_to_context(polygons, row, "poligono_numbloco", polygon_distance)
1629
  return values, sources, alerts
1630
 
@@ -2492,6 +2515,75 @@ def initialize_app_state(base_dir: Path = BASE_DIR) -> AppState:
2492
  APP_STATE = initialize_app_state(BASE_DIR)
2493
 
2494
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2495
  def predict(
2496
  area: float,
2497
  numbloco: str,
@@ -2845,6 +2937,11 @@ with gr.Blocks(title=APP_TITLE) as demo:
2845
  ],
2846
  outputs=[result_markdown, details_json, report_file],
2847
  )
 
 
 
 
 
2848
  autofill_button.click(
2849
  fn=preencher_por_coordenadas,
2850
  inputs=[numbloco_input, lat_input, lon_input],
 
1589
  return rank.iloc[0], float("nan"), alerts
1590
 
1591
 
1592
+ def _select_polygon_row_by_numbloco(
1593
  polygons: PolygonContext,
1594
  numbloco: Any,
1595
  x: float | None = None,
1596
  y: float | None = None,
1597
+ ) -> tuple[pd.Series | None, float, list[str], int]:
1598
  if not polygons.has_data or not polygons.numbloco_key_index:
1599
+ return None, float("nan"), [], 0
1600
 
1601
  key = _normalize_numbloco_key(numbloco)
1602
  if not key:
1603
+ return None, float("nan"), [], 0
1604
 
1605
  candidate_idx = polygons.numbloco_key_index.get(key)
1606
  if not candidate_idx:
1607
+ return (
1608
+ None,
1609
+ float("nan"),
1610
+ [f"NUMBLOCO `{_safe_text(numbloco, '')}` nao encontrado no geoparquet enriquecido; usada busca espacial."],
1611
+ 0,
1612
+ )
1613
 
1614
  point = None
1615
  x_number = _to_float(x)
 
1625
  matches = polygons.gdf.loc[candidate_idx]
1626
  row, polygon_distance, alerts = _select_best_polygon_row(polygons, matches, point)
1627
  if row is None:
1628
+ return (
1629
+ None,
1630
+ float("nan"),
1631
+ [f"NUMBLOCO `{_safe_text(numbloco, '')}` sem linha utilizavel no geoparquet; usada busca espacial."],
1632
+ len(candidate_idx),
1633
+ )
1634
 
1635
  if len(candidate_idx) > 1:
1636
  alerts.append(f"NUMBLOCO encontrado em {len(candidate_idx)} poligonos; aplicado desempate deterministico.")
1637
 
1638
+ return row, polygon_distance, alerts, len(candidate_idx)
1639
+
1640
+
1641
+ def _infer_polygon_context_by_numbloco(
1642
+ polygons: PolygonContext,
1643
+ numbloco: Any,
1644
+ x: float | None = None,
1645
+ y: float | None = None,
1646
+ ) -> tuple[dict[str, Any], dict[str, str], list[str]]:
1647
+ row, polygon_distance, alerts, _ = _select_polygon_row_by_numbloco(polygons, numbloco, x, y)
1648
+ if row is None:
1649
+ return {}, {}, alerts
1650
+
1651
  values, sources, _ = _polygon_row_to_context(polygons, row, "poligono_numbloco", polygon_distance)
1652
  return values, sources, alerts
1653
 
 
2515
  APP_STATE = initialize_app_state(BASE_DIR)
2516
 
2517
 
2518
+ def obter_lat_lon_centroide_por_numbloco(
2519
+ numbloco: Any,
2520
+ lat_atual: Any = None,
2521
+ lon_atual: Any = None,
2522
+ ) -> tuple[float, float] | None:
2523
+ polygon_x = None
2524
+ polygon_y = None
2525
+ lon_number = _to_float(lon_atual)
2526
+ lat_number = _to_float(lat_atual)
2527
+ if (
2528
+ APP_STATE.polygon_context.to_base_crs is not None
2529
+ and lon_number is not None
2530
+ and lat_number is not None
2531
+ and -180.0 <= lon_number <= 180.0
2532
+ and -90.0 <= lat_number <= 90.0
2533
+ ):
2534
+ try:
2535
+ polygon_x, polygon_y = APP_STATE.polygon_context.to_base_crs.transform(lon_number, lat_number)
2536
+ except Exception as exc:
2537
+ LOGGER.warning("Falha ao transformar coordenada atual para desempate por NUMBLOCO: %s", exc)
2538
+
2539
+ row, _, _, _ = _select_polygon_row_by_numbloco(APP_STATE.polygon_context, numbloco, polygon_x, polygon_y)
2540
+ if row is None:
2541
+ return None
2542
+
2543
+ geometry = row.get("geometry")
2544
+ if geometry is None or getattr(geometry, "is_empty", True):
2545
+ return None
2546
+ if hasattr(geometry, "is_valid") and not geometry.is_valid:
2547
+ return None
2548
+
2549
+ source_crs = getattr(APP_STATE.polygon_context.gdf, "crs", None)
2550
+ if source_crs is None:
2551
+ return None
2552
+
2553
+ try:
2554
+ centroid = geometry.centroid
2555
+ if centroid is None or getattr(centroid, "is_empty", True):
2556
+ return None
2557
+ lon_centroid, lat_centroid = Transformer.from_crs(source_crs, "EPSG:4326", always_xy=True).transform(
2558
+ float(centroid.x),
2559
+ float(centroid.y),
2560
+ )
2561
+ except Exception as exc:
2562
+ LOGGER.warning("Falha ao calcular centroide por NUMBLOCO: %s", exc)
2563
+ return None
2564
+
2565
+ lat_result = _to_float(lat_centroid)
2566
+ lon_result = _to_float(lon_centroid)
2567
+ if (
2568
+ lat_result is None
2569
+ or lon_result is None
2570
+ or not np.isfinite(lat_result)
2571
+ or not np.isfinite(lon_result)
2572
+ or not (-90.0 <= lat_result <= 90.0 and -180.0 <= lon_result <= 180.0)
2573
+ ):
2574
+ return None
2575
+ return float(lat_result), float(lon_result)
2576
+
2577
+
2578
+ def atualizar_lat_lon_por_numbloco(numbloco: str, lat_atual: Any, lon_atual: Any) -> tuple[Any, Any]:
2579
+ centroide = obter_lat_lon_centroide_por_numbloco(numbloco, lat_atual, lon_atual)
2580
+ if centroide is None:
2581
+ return gr.update(), gr.update()
2582
+
2583
+ lat, lon = centroide
2584
+ return gr.update(value=lat), gr.update(value=lon)
2585
+
2586
+
2587
  def predict(
2588
  area: float,
2589
  numbloco: str,
 
2937
  ],
2938
  outputs=[result_markdown, details_json, report_file],
2939
  )
2940
+ numbloco_input.change(
2941
+ fn=atualizar_lat_lon_por_numbloco,
2942
+ inputs=[numbloco_input, lat_input, lon_input],
2943
+ outputs=[lat_input, lon_input],
2944
+ )
2945
  autofill_button.click(
2946
  fn=preencher_por_coordenadas,
2947
  inputs=[numbloco_input, lat_input, lon_input],