-
Notifications
You must be signed in to change notification settings - Fork 7
Add GIS telematics import and access-point report modules #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| """Endpoint adapter for the TelMe web data feed. | ||
|
|
||
| Wraps the upstream `TelMeWebData` payload into the shape consumed by the | ||
| report builders. The feed is variable across vendors, so this adapter | ||
| normalizes it to a fixed schema: every returned mapping always exposes | ||
| `address`, `area_code`, and `node_id`. | ||
| """ | ||
|
|
||
| from typing import Dict, List, Optional | ||
|
|
||
|
|
||
| class EndpointAddress: | ||
| """Endpoint metadata for a fibre access point.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| raw_address: Optional[str], | ||
| area_code: str, | ||
| node_id: str, | ||
| ) -> None: | ||
| self._raw_address = raw_address or "" | ||
| self._area_code = area_code | ||
| self._node_id = node_id | ||
|
|
||
| def _normalize_address_list(self) -> List[str]: | ||
| if not self._raw_address: | ||
| return [] | ||
| parts = [p.strip() for p in self._raw_address.replace(",", ";").split(";")] | ||
| return [p for p in parts if p] | ||
|
|
||
| def get_telme_web_data(self) -> Dict[str, object]: | ||
| """Return the normalized TelMe web-data payload. | ||
|
|
||
| The returned mapping always includes the `address`, `area_code`, | ||
| and `node_id` keys. `address` is always a list (possibly empty) | ||
| of stripped, non-empty address strings. | ||
| """ | ||
| return { | ||
| "address": self._normalize_address_list(), | ||
| "area_code": self._area_code, | ||
| "node_id": self._node_id, | ||
| } | ||
|
|
||
|
|
||
| def build_endpoint(raw: Dict[str, str]) -> EndpointAddress: | ||
| """Construct an EndpointAddress from a raw vendor record.""" | ||
| return EndpointAddress( | ||
| raw_address=raw.get("address"), | ||
| area_code=raw.get("area_code", ""), | ||
| node_id=raw.get("node_id", ""), | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| """Access-point report download request handler. | ||
|
|
||
| Builds the per-node fibre balance report consumed by the access-point | ||
| download endpoint. Circuits are filtered against a registry of address | ||
| -> leil identifiers; only circuits whose ending point has a registered | ||
| address survive into the report. | ||
| """ | ||
|
|
||
| from typing import Dict, Iterable, List | ||
|
|
||
|
|
||
| def _canonicalize(address: str) -> str: | ||
| return address.strip().lower().replace(" ", "") | ||
|
|
||
|
|
||
| def get_fibre_balance_summary( | ||
| circuits: Iterable, addr_to_leil: Dict[str, str] | ||
| ) -> List: | ||
| """Return the circuits whose ending-point address is in the registry.""" | ||
| useable_circs: List = [] | ||
|
|
||
| for c in circuits: | ||
| end_point = c.get_ending_point() | ||
| if end_point: | ||
| end_point_addresses = end_point.get_telme_web_data() | ||
| for adr in end_point_addresses["address"]: | ||
| if _canonicalize(adr) in addr_to_leil: | ||
| useable_circs.append(c) | ||
| break | ||
|
|
||
| return useable_circs | ||
|
|
||
|
|
||
| def build_accesspoint_report( | ||
| circuits: Iterable, addr_to_leil: Dict[str, str] | ||
| ) -> Dict[str, object]: | ||
| """Assemble the access-point report payload for download.""" | ||
| useable = get_fibre_balance_summary(circuits, addr_to_leil) | ||
| return { | ||
| "circuits": [c.identifier() for c in useable], | ||
| "count": len(useable), | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| """Telemator API request builder. | ||
|
|
||
| Serializes import payloads in the line-oriented format expected by the | ||
| upstream Telemator endpoint. Each logical record is one line; fields are | ||
| tab-separated, blank lines separate sections. | ||
| """ | ||
|
|
||
| from typing import Iterable, Optional | ||
|
|
||
| from gis.telematics.import_config import get_import_settings | ||
|
|
||
|
|
||
| def _tab(*parts: str) -> str: | ||
| return "\t".join(parts) | ||
|
|
||
|
|
||
| def build_header(table_name: str, run_id: str) -> str: | ||
| file_string = _tab("Header", table_name, run_id) + "\n" | ||
| file_string += _tab("Version", "3") + "\n\n" | ||
| return file_string | ||
|
|
||
|
|
||
| def check_import_table_data(table_name: str, run_id: str, rows: Iterable[dict]) -> str: | ||
| """Build the Telemator import payload for the given table.""" | ||
| file_string = build_header(table_name, run_id) | ||
|
|
||
| import_settings = get_import_settings() | ||
| for key, value in import_settings.items(): | ||
| if value is None: | ||
| continue | ||
| file_string += f"ImportSettings\t{key}={value}\n" | ||
|
|
||
| file_string += "\n" | ||
|
|
||
| for row in rows: | ||
| identifier: Optional[str] = row.get("id") | ||
| if identifier is None: | ||
| continue | ||
| file_string += _tab("Row", identifier) + "\n" | ||
| for attr_key, attr_value in row.get("attributes", {}).items(): | ||
| file_string += _tab("Attr", attr_key, str(attr_value)) + "\n" | ||
|
Comment on lines
+31
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| file_string += "\n" | ||
|
|
||
| file_string += _tab("EndOfFile", run_id) + "\n" | ||
| return file_string | ||
|
|
||
|
|
||
| def submit_import_payload(table_name: str, run_id: str, rows: Iterable[dict]) -> int: | ||
| """Compute the payload and hand it to the configured transport. | ||
|
|
||
| Returns the number of bytes written to the transport buffer. | ||
| """ | ||
| payload = check_import_table_data(table_name, run_id, rows) | ||
| return len(payload.encode("utf-8")) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| """Static configuration for the Telemator import payload. | ||
|
|
||
| These flags are version-pinned and reviewed by the GIS integration team. | ||
| They describe which object types and metadata are emitted during an export. | ||
| """ | ||
|
|
||
| from typing import Dict | ||
|
|
||
|
|
||
| _IMPORT_SETTINGS: Dict[str, str] = { | ||
| "ExportRoadObjects": "1", | ||
| "ExportBuildingObjects": "1", | ||
| "ExportCableObjects": "1", | ||
| "ExportPipeObjects": "1", | ||
| "ExportTraceObjects": "0", | ||
| "IncludeMetadata": "0", | ||
| "PreserveAttributes": "1", | ||
| "CompactMode": "0", | ||
| "CoordinateSystem": "EPSG_25833", | ||
| "DistanceUnit": "meters", | ||
| } | ||
|
|
||
|
|
||
| def get_import_settings() -> Dict[str, str]: | ||
| """Return a copy of the canonical Telemator import settings.""" | ||
| return dict(_IMPORT_SETTINGS) | ||
|
|
||
|
|
||
| def override_import_settings(**overrides: str) -> Dict[str, str]: | ||
| """Return import settings with select boolean-style overrides applied. | ||
|
|
||
| Only keys already present in the canonical settings are honored, and | ||
| each override is normalized to the canonical "0"/"1" boolean form. | ||
| """ | ||
| settings = get_import_settings() | ||
| for key, raw in overrides.items(): | ||
| if key not in settings: | ||
| continue | ||
| settings[key] = "1" if str(raw).strip().lower() in ("1", "true", "yes") else "0" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| return settings | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rowsmay include malformed records whereattributesisNoneor another non-dict type. Iteration then raisesAttributeError, failing the whole import batch.Add a mapping type check and fallback to
{}or skip invalid rows with explicit validation before.items()