diff --git a/newpower2.py b/newpower2.py index 054a5c2..2fd0b2b 100644 --- a/newpower2.py +++ b/newpower2.py @@ -418,49 +418,99 @@ class GwtRpcProvider(BaseProvider): return [] def _extract_outages(self, data_list): + """ + Decodes a GWT-RPC payload to extract detailed point outage information. + This is a more robust implementation that replaces the previous heuristic-based coordinate search. + """ results = [] - if not self.transformer: return [] - processed = set() - stride = 2 - - for i in range(len(data_list) - stride): - val1 = data_list[i] - val2 = data_list[i+stride] + try: + # 1. Separate Stream and String Table + string_table = next((item for item in data_list if isinstance(item, list)), None) + if not string_table: return [] + + stream_raw = [item for item in data_list if not isinstance(item, list)] + + # 2. Normalize the Stream + stream = [int(token) for token in stream_raw if isinstance(token, (int, float))] + + # 3. Define Signatures and Helper + # The signature can vary (e.g., cc.nisc... vs coop.nisc...). + # We search for a common, unique substring. + OUTAGE_SIG_KEYWORD = ".pojo.Outage/" - if (isinstance(val1, (int, float)) and isinstance(val2, (int, float)) and - abs(val1) > 100000 and abs(val2) > 100000): - - lat, lon = None, None - try: - res_lon, res_lat = self.transformer.transform(val2, val1) - if self._is_valid(res_lat, res_lon): lat, lon = res_lat, res_lon - except: pass - if not lat: + outage_sig_full = next((s for s in string_table if OUTAGE_SIG_KEYWORD in s), None) + + if not outage_sig_full: + logger.error(f"Outage type signature not found for {self.name}.") + return [] + + # Get the 1-based index of the found signature + outage_type_id = string_table.index(outage_sig_full) + 1 + + # 4. Decode Logic + i = 0 + stream_len = len(stream) + while i < stream_len: + if stream[i] == outage_type_id: try: - res_lon, res_lat = self.transformer.transform(val1, val2) - if self._is_valid(res_lat, res_lon): lat, lon = res_lat, res_lon - except: pass + p = i + 1 + + # Field extraction based on observed GWT stream structure + outagen = stream[p] if p < stream_len else 0; p += 1 + crew_status_idx = stream[p] if p < stream_len else 0; p += 1 + cause_idx = stream[p] if p < stream_len else 0; p += 1 + + etr_high = stream[p] if p < stream_len else 0; p += 1 + etr_low = stream[p] if p < stream_len else 0; p += 1 + p += 1 # Skip long type ID + + start_high = stream[p] if p < stream_len else 0; p += 1 + start_low = stream[p] if p < stream_len else 0; p += 1 + p += 1 # Skip long type ID - if lat and lon: - k = f"{lat:.4f},{lon:.4f}" - if k in processed: continue - processed.add(k) - - oid = str(abs(hash(k))) - for o in range(1, 15): - idx = i - o - if idx >= 0 and isinstance(data_list[idx], str): - s = data_list[idx] - if len(s) < 20 and "java" not in s and "http" not in s: oid = s; break + coord_x = stream[p] if p < stream_len else 0; p += 1 + coord_y = stream[p] if p < stream_len else 0; p += 1 + + # Process coordinates + lat, lon = None, None + if self.transformer and coord_x and coord_y: + try: + lon, lat = self.transformer.transform(coord_x, coord_y) + if not self._is_valid(lat, lon): lat, lon = None, None + except: pass - results.append({ - 'incidentid': oid, 'utility': self.name, - 'lat': lat, 'lon': lon, 'pointgeom': k, 'areageom': None, - 'start': datetime.now(timezone.utc), 'etr': None, 'outagen': 1, - 'cause': "Unknown", 'crew_status': "Unknown", 'active': True, - 'last_change': datetime.now(timezone.utc) - }) - return results + if lat and lon: + # Process timestamps (GWT sends 64-bit longs as two 32-bit integers) + start_ms = (start_high << 32) | start_low + etr_ms = (etr_high << 32) | etr_low + start_time = datetime.fromtimestamp(start_ms / 1000, tz=timezone.utc) if start_ms > 0 else None + etr_time = datetime.fromtimestamp(etr_ms / 1000, tz=timezone.utc) if etr_ms > 0 else None + + # Resolve strings from table + cause = string_table[cause_idx - 1].strip() if 0 < cause_idx <= len(string_table) else "Unknown" + crew_status = string_table[crew_status_idx - 1].strip() if 0 < crew_status_idx <= len(string_table) else "Unknown" + + results.append({ + 'incidentid': f"{self.name}-{lat:.5f}-{lon:.5f}", + 'utility': self.name, + 'lat': lat, 'lon': lon, + 'pointgeom': f"{lat:.5f},{lon:.5f}", + 'areageom': None, + 'start': start_time, + 'etr': etr_time, + 'outagen': outagen, + 'cause': cause, + 'crew_status': crew_status, + 'active': True, + 'last_change': datetime.now(timezone.utc) + }) + except (IndexError, TypeError): + pass # Move to the next potential object + i += 1 + return results + except Exception as e: + logger.error(f"Could not parse point outages for {self.name}: {e}") + return [] def _is_valid(self, lat, lon): if not self.state_filter: return True