pingintel-api 1.4.6__tar.gz → 1.4.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pingintel_api-1.4.8/.claude/settings.json +7 -0
- pingintel_api-1.4.8/.claude/settings.local.json +16 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/PKG-INFO +1 -1
- pingintel_api-1.4.8/examples/close_submissions.py +129 -0
- pingintel_api-1.4.8/src/pingintel_api/__about__.py +1 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/pingdata/pingdata_api_client.py +19 -1
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/pingdata/types.py +11 -7
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/pingvisionapi_cmd.py +46 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/sov_fixer/sov_fixer_api_client.py +151 -30
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/sov_fixer/types.py +49 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/sovfixerapi_cmd.py +19 -0
- pingintel_api-1.4.6/src/pingintel_api/__about__.py +0 -1
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/.gitignore +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/.pre-commit-config.yaml +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/LICENSE +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/README.md +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/SOV10 2026-02_Acc-STG.txt +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/Untitled-9.json5 +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/assignment_queue.xlsx +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/completed_submissions.xlsx +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/data/demo_email.eml +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/data/parse_sov_testfile.xlsx +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/find-amwins-missing-account-numbers.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-data/enhance_multiple_locations.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-data/enhance_one_location.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-data/pingdata_enhance.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-extraction/parse_sov.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-extraction/sovfixer_fix_sov.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-extraction/sovfixer_list_activity.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-extraction/sovfixer_reoutput_sov.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-vision/clear_and_triage_api.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-vision/pingvision_activity.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-vision/pingvision_clear_and_triage.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-vision/send_and_track_submission.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/pingvision_assignment.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/pingvision_declination_flow.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/sovfixer_list_history.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/sovfixer_list_history_ping_None_1_run2.csv +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/sovfixerapi +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/test_reoutput_locations.csv +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/test_sov.xlsx +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/generate_docs.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/justfile +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/output-data-f432c75e-b40e-11f0-9e68-3c58c2f91432-1.json +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/pyproject.toml +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/requirements.txt +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/__init__.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/api_client_base.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/common_types.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/constants.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/pingdataapi_cmd.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/pingmaps/pingmaps_api_client.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/pingmaps/types.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/pingmapsapi_cmd.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/pingvision/pingvision_api_client.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/pingvision/types.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/url_signature_factory.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/utils.py +0 -0
- {pingintel_api-1.4.6 → pingintel_api-1.4.8}/tests/__init__.py +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"WebFetch(domain:data-api.sovfixer.com)",
|
|
5
|
+
"WebFetch(domain:docs.pingintel.com)",
|
|
6
|
+
"WebFetch(domain:api.sovfixer.com)",
|
|
7
|
+
"Bash(curl -s \"http://localhost/olddocs\")",
|
|
8
|
+
"Bash(curl -sv \"http://localhost/api/schema/\")",
|
|
9
|
+
"Bash(curl -s \"http://localhost/api/schema/\")",
|
|
10
|
+
"Bash(python3 -c ' *)",
|
|
11
|
+
"Bash(mkdir -p \"C:/temp\")",
|
|
12
|
+
"Bash(curl -s \"http://localhost/api/schema/\" -o \"C:/temp/schema.yaml\")",
|
|
13
|
+
"Read(//c/temp/**)"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pingintel-api
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.8
|
|
4
4
|
Summary: Python-based client to Ping Data Intelligence APIs
|
|
5
5
|
Project-URL: Homepage, https://github.com/pingintel/pingintel-api
|
|
6
6
|
Project-URL: Issues, https://github.com/pingintel/pingintel-api/issues
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Set one or more PingVision submissions to a given workflow status.
|
|
3
|
+
|
|
4
|
+
Accepts pingids (e.g. p-ob-amw-agtdgc) or client_refs. Identifiers starting
|
|
5
|
+
with "p-" are treated as pingids; all others are looked up as client_refs.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python close_submissions.py <id1> [id2 ...] --status <name> [options]
|
|
9
|
+
|
|
10
|
+
python close_submissions.py p-ob-amw-abc123 my-client-ref-456 --status Closed
|
|
11
|
+
python close_submissions.py p-ob-amw-abc123 --status Declined --env staging --dry-run
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
from pingintel_api import PingVisionAPIClient
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def resolve_pingids(client: PingVisionAPIClient, identifiers: list[str]) -> dict[str, str | None]:
|
|
21
|
+
"""Return {identifier: pingid} for all identifiers. pingid is None if not found."""
|
|
22
|
+
result = {}
|
|
23
|
+
for ident in identifiers:
|
|
24
|
+
if ident.startswith("p-"):
|
|
25
|
+
result[ident] = ident
|
|
26
|
+
else:
|
|
27
|
+
activity = client.list_submission_activity(client_ref=ident, page_size=10)
|
|
28
|
+
submissions = activity.get("results", [])
|
|
29
|
+
if not submissions:
|
|
30
|
+
print(f" WARNING: No submission found for client_ref={ident!r}", file=sys.stderr)
|
|
31
|
+
result[ident] = None
|
|
32
|
+
elif len(submissions) > 1:
|
|
33
|
+
pingids = [s["id"] for s in submissions]
|
|
34
|
+
print(
|
|
35
|
+
f" WARNING: Multiple submissions found for client_ref={ident!r}: {pingids}. Skipping.",
|
|
36
|
+
file=sys.stderr,
|
|
37
|
+
)
|
|
38
|
+
result[ident] = None
|
|
39
|
+
else:
|
|
40
|
+
result[ident] = submissions[0]["id"]
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def find_status_uuid(client: PingVisionAPIClient, division_uuid: str, status_name: str) -> str:
|
|
45
|
+
statuses = client.list_submission_statuses(division=division_uuid)
|
|
46
|
+
matched = [s for s in statuses if s["name"].lower() == status_name.lower()]
|
|
47
|
+
if not matched:
|
|
48
|
+
available = [s["name"] for s in statuses]
|
|
49
|
+
raise RuntimeError(f"Status {status_name!r} not found. Available: {available}")
|
|
50
|
+
return matched[0]["uuid"]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def find_division_uuid(client: PingVisionAPIClient, team_name: str | None, division: str | None) -> str:
|
|
54
|
+
teams = client.list_teams()
|
|
55
|
+
if not teams:
|
|
56
|
+
raise RuntimeError("No teams found for this API token.")
|
|
57
|
+
if division:
|
|
58
|
+
matched = [
|
|
59
|
+
t for t in teams
|
|
60
|
+
if t["division_uuid"] == division
|
|
61
|
+
or t.get("division_name", "").lower() == division.lower()
|
|
62
|
+
or t.get("division_short_name", "").lower() == division.lower()
|
|
63
|
+
]
|
|
64
|
+
if not matched:
|
|
65
|
+
available = [(t.get("division_short_name") or t["division_name"], t["division_uuid"]) for t in teams]
|
|
66
|
+
raise RuntimeError(f"Division {division!r} not found. Available: {available}")
|
|
67
|
+
return matched[0]["division_uuid"]
|
|
68
|
+
if team_name:
|
|
69
|
+
matched = [t for t in teams if t["team_name"] == team_name]
|
|
70
|
+
if not matched:
|
|
71
|
+
available = [t["team_name"] for t in teams]
|
|
72
|
+
raise RuntimeError(f"Team {team_name!r} not found. Available: {available}")
|
|
73
|
+
return matched[0]["division_uuid"]
|
|
74
|
+
if len(teams) == 1:
|
|
75
|
+
return teams[0]["division_uuid"]
|
|
76
|
+
names = [t["team_name"] for t in teams]
|
|
77
|
+
raise RuntimeError(f"Multiple teams found: {names}. Specify one with --team or --division.")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def main():
|
|
81
|
+
parser = argparse.ArgumentParser(description="Set PingVision submission workflow status.")
|
|
82
|
+
parser.add_argument("identifiers", nargs="+", help="pingids or client_refs to update")
|
|
83
|
+
parser.add_argument("--status", required=True, help="Target workflow status name (e.g. Closed, Declined)")
|
|
84
|
+
parser.add_argument("--env", default="prod", help="API environment (prod, staging, local). Default: prod")
|
|
85
|
+
parser.add_argument("--api-url", help="Override API URL (e.g. http://localhost:8002)")
|
|
86
|
+
parser.add_argument("--team", help="Team name for status lookup (required if multiple teams)")
|
|
87
|
+
parser.add_argument("--division", help="Division UUID (alternative to --team)")
|
|
88
|
+
parser.add_argument("--dry-run", action="store_true", help="Print what would happen without making changes")
|
|
89
|
+
args = parser.parse_args()
|
|
90
|
+
|
|
91
|
+
kwargs = {}
|
|
92
|
+
if args.api_url:
|
|
93
|
+
kwargs["api_url"] = args.api_url
|
|
94
|
+
else:
|
|
95
|
+
kwargs["environment"] = args.env
|
|
96
|
+
|
|
97
|
+
client = PingVisionAPIClient(**kwargs)
|
|
98
|
+
|
|
99
|
+
division_uuid = find_division_uuid(client, args.team, args.division)
|
|
100
|
+
status_uuid = find_status_uuid(client, division_uuid, args.status)
|
|
101
|
+
print(f"Status {args.status!r} UUID: {status_uuid}")
|
|
102
|
+
|
|
103
|
+
id_map = resolve_pingids(client, args.identifiers)
|
|
104
|
+
|
|
105
|
+
success, skipped, failed = 0, 0, 0
|
|
106
|
+
for ident, pingid in id_map.items():
|
|
107
|
+
if pingid is None:
|
|
108
|
+
skipped += 1
|
|
109
|
+
continue
|
|
110
|
+
label = pingid if ident == pingid else f"{pingid} (client_ref={ident!r})"
|
|
111
|
+
if args.dry_run:
|
|
112
|
+
print(f" DRY RUN: would set {label} -> {args.status!r}")
|
|
113
|
+
success += 1
|
|
114
|
+
continue
|
|
115
|
+
try:
|
|
116
|
+
client.change_status(pingid=pingid, workflow_status_id=status_uuid)
|
|
117
|
+
print(f" Updated {label} -> {args.status!r}")
|
|
118
|
+
success += 1
|
|
119
|
+
except Exception as e:
|
|
120
|
+
print(f" ERROR updating {label}: {e}", file=sys.stderr)
|
|
121
|
+
failed += 1
|
|
122
|
+
|
|
123
|
+
print(f"\nDone. {success} updated, {skipped} skipped, {failed} failed.")
|
|
124
|
+
if failed:
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
if __name__ == "__main__":
|
|
129
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.4.8"
|
{pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/pingdata/pingdata_api_client.py
RENAMED
|
@@ -162,7 +162,7 @@ class PingDataAPIClient(APIClientBase):
|
|
|
162
162
|
delegate_to=delegate_to,
|
|
163
163
|
)
|
|
164
164
|
request_id = response_data["id"]
|
|
165
|
-
message = response_data
|
|
165
|
+
message = response_data.get("message", "")
|
|
166
166
|
|
|
167
167
|
self.logger.info(
|
|
168
168
|
f"+ Dispatched {request_id}: {message}. Now, polling for results at {self.bulk_enhance_async_get_status_url(request_id=request_id)}."
|
|
@@ -299,3 +299,21 @@ class PingDataAPIClient(APIClientBase):
|
|
|
299
299
|
raise_for_status(response)
|
|
300
300
|
response_data = response.json()
|
|
301
301
|
return response_data
|
|
302
|
+
|
|
303
|
+
def fetch_bulk_enhance_output(self, request_id: str, filename: str, output_path: str | None = None) -> bytes:
|
|
304
|
+
"""
|
|
305
|
+
Download a result file from a completed bulk enhance job.
|
|
306
|
+
|
|
307
|
+
:param request_id: The bulk enhance job ID.
|
|
308
|
+
:param filename: The output filename (from result.outputs[].filename).
|
|
309
|
+
:param output_path: If provided, write the result to this local file path.
|
|
310
|
+
:return: Raw response bytes.
|
|
311
|
+
"""
|
|
312
|
+
url = self.api_url + f"/api/v1/bulk_enhance/{request_id}/output/{filename}"
|
|
313
|
+
response = self.get(url)
|
|
314
|
+
raise_for_status(response)
|
|
315
|
+
if output_path:
|
|
316
|
+
with open(output_path, "wb") as fd:
|
|
317
|
+
for chunk in response.iter_content(chunk_size=128):
|
|
318
|
+
fd.write(chunk)
|
|
319
|
+
return response.content
|
|
@@ -124,9 +124,6 @@ class SingleLocation(TypedDict):
|
|
|
124
124
|
address: NotRequired[str | None]
|
|
125
125
|
latitude: NotRequired[float | None]
|
|
126
126
|
longitude: NotRequired[float | None]
|
|
127
|
-
limits__building_limit: NotRequired[float | None]
|
|
128
|
-
limits__bpp_limit: NotRequired[float | None]
|
|
129
|
-
limits__bi_limit: NotRequired[float | None]
|
|
130
127
|
address_line_1: NotRequired[str | None]
|
|
131
128
|
address_line_2: NotRequired[str | None]
|
|
132
129
|
city: NotRequired[str | None]
|
|
@@ -135,9 +132,10 @@ class SingleLocation(TypedDict):
|
|
|
135
132
|
country: NotRequired[str | None]
|
|
136
133
|
county: NotRequired[str | None]
|
|
137
134
|
bldg_name: NotRequired[str | None]
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
lightbox_assessment_id: NotRequired[str | None]
|
|
136
|
+
lightbox_building_id: NotRequired[str | None]
|
|
137
|
+
lightbox_parcel_id: NotRequired[str | None]
|
|
138
|
+
lightbox_parcel_wkt: NotRequired[str | None]
|
|
141
139
|
occupancy__type_desc: NotRequired[str | None]
|
|
142
140
|
occupancy__desc_ping: NotRequired[str | None]
|
|
143
141
|
occupancy__code_air: NotRequired[str | None]
|
|
@@ -149,10 +147,14 @@ class SingleLocation(TypedDict):
|
|
|
149
147
|
const__wall_type: NotRequired[str | None]
|
|
150
148
|
const__bldg_year_built: NotRequired[int | None]
|
|
151
149
|
const__num_stories: NotRequired[int | None]
|
|
150
|
+
const__num_stories_below_gnd: NotRequired[int | None]
|
|
152
151
|
const__bldg_area: NotRequired[int | None]
|
|
152
|
+
limits__building_limit: NotRequired[float | None]
|
|
153
|
+
limits__bpp_limit: NotRequired[float | None]
|
|
154
|
+
limits__bi_limit: NotRequired[float | None]
|
|
155
|
+
limits__signs_and_other_limit: NotRequired[float | None]
|
|
153
156
|
dtc_include_coastline_within_miles: NotRequired[float | None]
|
|
154
157
|
dtc_return_connected_coastlines: NotRequired[bool | None]
|
|
155
|
-
insured_name: NotRequired[str | None]
|
|
156
158
|
|
|
157
159
|
|
|
158
160
|
Location = SingleLocation
|
|
@@ -160,6 +162,8 @@ Location = SingleLocation
|
|
|
160
162
|
|
|
161
163
|
class BatchLocation(SingleLocation):
|
|
162
164
|
id: str
|
|
165
|
+
sources: NotRequired[list[str] | None]
|
|
166
|
+
datasource_config: NotRequired[dict | None]
|
|
163
167
|
|
|
164
168
|
|
|
165
169
|
class EnhanceResponse(TypedDict):
|
|
@@ -316,6 +316,52 @@ def list_submission_statuses(ctx, division):
|
|
|
316
316
|
pprint.pprint(results)
|
|
317
317
|
|
|
318
318
|
|
|
319
|
+
@cli.command()
|
|
320
|
+
@click.pass_context
|
|
321
|
+
@click.option("--pretty", is_flag=True, default=False)
|
|
322
|
+
@click.option("--pingid", help="Filter by Ping ID")
|
|
323
|
+
@click.option("--division", help="Filter by division UUID or short name")
|
|
324
|
+
@click.option("--team", help="Filter by team UUID or short name")
|
|
325
|
+
@click.option("--start", help="Start datetime filter (YYYYMMDDHHmmss UTC)")
|
|
326
|
+
@click.option("--cursor-id", help="Cursor ID for pagination")
|
|
327
|
+
@click.option("-l", "--page-size", "--limit", default=50)
|
|
328
|
+
def events(ctx, pretty, pingid, division, team, start, cursor_id, page_size):
|
|
329
|
+
"""List submission events."""
|
|
330
|
+
import datetime
|
|
331
|
+
|
|
332
|
+
client = get_client(ctx)
|
|
333
|
+
|
|
334
|
+
if not any([pingid, division, team]):
|
|
335
|
+
raise click.UsageError("One or more of --pingid, --division, or --team must be provided.")
|
|
336
|
+
|
|
337
|
+
start_dt = None
|
|
338
|
+
if start:
|
|
339
|
+
start_dt = datetime.datetime.strptime(start, "%Y%m%d%H%M%S")
|
|
340
|
+
|
|
341
|
+
results = client.list_submission_events(
|
|
342
|
+
pingid=pingid,
|
|
343
|
+
division=division,
|
|
344
|
+
team=team,
|
|
345
|
+
start=start_dt,
|
|
346
|
+
cursor_id=cursor_id,
|
|
347
|
+
page_size=page_size,
|
|
348
|
+
)
|
|
349
|
+
if pretty:
|
|
350
|
+
print(f"{'Ping ID':<20}{'Event Type':<6}{'Created':<20}{'Message'}")
|
|
351
|
+
for event in results["results"]:
|
|
352
|
+
created = event["created_time"]
|
|
353
|
+
try:
|
|
354
|
+
created = time.strftime("%Y-%m-%d %H:%M", time.strptime(created, "%Y-%m-%dT%H:%M:%S.%fZ"))
|
|
355
|
+
except ValueError:
|
|
356
|
+
pass
|
|
357
|
+
print(f"{event['pingid']:<20}{event['event_type']:<6}{created:<20}{event.get('message', '')}")
|
|
358
|
+
cursor = results.get("cursor_id")
|
|
359
|
+
if cursor:
|
|
360
|
+
print(f"\nNext cursor: {cursor}")
|
|
361
|
+
else:
|
|
362
|
+
pprint.pprint(results)
|
|
363
|
+
|
|
364
|
+
|
|
319
365
|
def main():
|
|
320
366
|
cli()
|
|
321
367
|
|
{pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/sov_fixer/sov_fixer_api_client.py
RENAMED
|
@@ -42,6 +42,8 @@ class SOVFixerAPIClient(APIClientBase):
|
|
|
42
42
|
allow_ping_data_api=None,
|
|
43
43
|
workflow=None,
|
|
44
44
|
skip_prior_update_reuse: bool = False,
|
|
45
|
+
company: str | None = None,
|
|
46
|
+
team: str | None = None,
|
|
45
47
|
):
|
|
46
48
|
"""
|
|
47
49
|
Start a SOV Fixer request from one or more files asynchronously.
|
|
@@ -77,6 +79,10 @@ class SOVFixerAPIClient(APIClientBase):
|
|
|
77
79
|
data["allow_ping_data_api"] = allow_ping_data_api
|
|
78
80
|
if workflow is not None:
|
|
79
81
|
data["workflow"] = workflow
|
|
82
|
+
if company is not None:
|
|
83
|
+
data["company"] = company
|
|
84
|
+
if team is not None:
|
|
85
|
+
data["team"] = team
|
|
80
86
|
|
|
81
87
|
data["skip_prior_update_reuse"] = skip_prior_update_reuse
|
|
82
88
|
|
|
@@ -250,7 +256,7 @@ class SOVFixerAPIClient(APIClientBase):
|
|
|
250
256
|
if request_status == "PENDING":
|
|
251
257
|
self.logger.info(" - Has not yet been queued for processing.")
|
|
252
258
|
time.sleep(POLL_SECS)
|
|
253
|
-
elif request_status
|
|
259
|
+
elif request_status in t.INCOMPLETE_STATUSES:
|
|
254
260
|
self.logger.info(f" - Still in progress ({pct_complete}% complete): {last_status}")
|
|
255
261
|
time.sleep(POLL_SECS)
|
|
256
262
|
else:
|
|
@@ -340,27 +346,28 @@ class SOVFixerAPIClient(APIClientBase):
|
|
|
340
346
|
search=None,
|
|
341
347
|
origin: Literal["api", "email"] | None = None,
|
|
342
348
|
status: Literal["P", "I", "E", "R", "C", "F"] | None = None,
|
|
343
|
-
|
|
349
|
+
company__short_name: str | list[str] | None = None,
|
|
350
|
+
division__short_name: str | list[str] | None = None,
|
|
351
|
+
pingid: str | None = None,
|
|
352
|
+
completed_time__gt: str | None = None,
|
|
353
|
+
completed_time__gte: str | None = None,
|
|
354
|
+
completed_time__lt: str | None = None,
|
|
355
|
+
completed_time__lte: str | None = None,
|
|
344
356
|
) -> t.ActivityResponse:
|
|
345
357
|
"""List activity in the SOV Fixer system.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
cursor_id:
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
Filter by the origin of the activity. Can be "api" or "email".
|
|
360
|
-
status: str
|
|
361
|
-
Filter by the status of the activity. Can be "P" (pending), "I" (in progress), "E" (enriching), "R" (re-enriching), "C" (complete), or "F" (failed).
|
|
362
|
-
organization__short_name: str
|
|
363
|
-
Filter by the short name of the organization that created the activity
|
|
358
|
+
|
|
359
|
+
id: filter by sovid.
|
|
360
|
+
cursor_id: pagination cursor from a previous response.
|
|
361
|
+
prev_cursor_id: reverse pagination cursor.
|
|
362
|
+
page_size: max results per page (default 50, max 250).
|
|
363
|
+
fields: list of field names to include on each result.
|
|
364
|
+
search: global case-insensitive search substring.
|
|
365
|
+
origin: filter by "api" or "email".
|
|
366
|
+
status: filter by status
|
|
367
|
+
company__short_name: filter by company short name(s).
|
|
368
|
+
division__short_name: filter by division short name(s).
|
|
369
|
+
pingid: filter by Ping ID.
|
|
370
|
+
completed_time__gt/gte/lt/lte: filter by completed_time (format: YYYYMMDDHHmmss, UTC).
|
|
364
371
|
"""
|
|
365
372
|
parameters = {}
|
|
366
373
|
if id:
|
|
@@ -379,8 +386,20 @@ class SOVFixerAPIClient(APIClientBase):
|
|
|
379
386
|
parameters["origin"] = origin
|
|
380
387
|
if status:
|
|
381
388
|
parameters["status"] = status
|
|
382
|
-
if
|
|
383
|
-
parameters["
|
|
389
|
+
if company__short_name:
|
|
390
|
+
parameters["company__short_name"] = company__short_name
|
|
391
|
+
if division__short_name:
|
|
392
|
+
parameters["division__short_name"] = division__short_name
|
|
393
|
+
if pingid:
|
|
394
|
+
parameters["pingid"] = pingid
|
|
395
|
+
if completed_time__gt:
|
|
396
|
+
parameters["completed_time__gt"] = completed_time__gt
|
|
397
|
+
if completed_time__gte:
|
|
398
|
+
parameters["completed_time__gte"] = completed_time__gte
|
|
399
|
+
if completed_time__lt:
|
|
400
|
+
parameters["completed_time__lt"] = completed_time__lt
|
|
401
|
+
if completed_time__lte:
|
|
402
|
+
parameters["completed_time__lte"] = completed_time__lte
|
|
384
403
|
|
|
385
404
|
url = self.api_url + "/api/v1/sov/activity"
|
|
386
405
|
response = self.get(url, params=parameters)
|
|
@@ -464,20 +483,19 @@ class SOVFixerAPIClient(APIClientBase):
|
|
|
464
483
|
sudid: str,
|
|
465
484
|
extra_data=None,
|
|
466
485
|
policy_terms=None,
|
|
467
|
-
|
|
486
|
+
outputter_name: str | None = None,
|
|
468
487
|
output_formats=None,
|
|
469
488
|
metadata=None,
|
|
470
489
|
integrations=None,
|
|
471
490
|
delegate_to_team: UUID | str | int | None = None,
|
|
472
491
|
) -> t.SOVUpdateAsyncAPIResponse:
|
|
473
492
|
url = self.api_url + f"/api/v1/sov/update/{sudid}/start"
|
|
474
|
-
data = {}
|
|
475
|
-
|
|
476
|
-
data["extra_data"] = extra_data
|
|
493
|
+
data: dict = {}
|
|
494
|
+
data["extra_data"] = extra_data or {}
|
|
477
495
|
if policy_terms:
|
|
478
496
|
data["policy_terms"] = policy_terms
|
|
479
|
-
if
|
|
480
|
-
data["
|
|
497
|
+
if outputter_name:
|
|
498
|
+
data["outputter_name"] = outputter_name
|
|
481
499
|
if output_formats:
|
|
482
500
|
data["output_formats"] = output_formats
|
|
483
501
|
if metadata:
|
|
@@ -515,7 +533,7 @@ class SOVFixerAPIClient(APIClientBase):
|
|
|
515
533
|
location_filenames,
|
|
516
534
|
extra_data=None,
|
|
517
535
|
policy_terms=None,
|
|
518
|
-
|
|
536
|
+
outputter_name: str | None = None,
|
|
519
537
|
output_formats=None,
|
|
520
538
|
actually_write=False,
|
|
521
539
|
update_type=None,
|
|
@@ -545,7 +563,7 @@ class SOVFixerAPIClient(APIClientBase):
|
|
|
545
563
|
sudid,
|
|
546
564
|
extra_data=extra_data,
|
|
547
565
|
policy_terms=policy_terms,
|
|
548
|
-
|
|
566
|
+
outputter_name=outputter_name,
|
|
549
567
|
output_formats=output_formats,
|
|
550
568
|
metadata=metadata,
|
|
551
569
|
integrations=integrations,
|
|
@@ -625,6 +643,7 @@ class SOVFixerAPIClient(APIClientBase):
|
|
|
625
643
|
def get_or_create_output_async_check_progress(self, output_request_id: str):
|
|
626
644
|
url = self.api_url + f"/api/v1/sov/get_or_create_output/{output_request_id}"
|
|
627
645
|
response = self.get(url)
|
|
646
|
+
raise_for_status(response)
|
|
628
647
|
return response.json()
|
|
629
648
|
|
|
630
649
|
def get_or_create_output(
|
|
@@ -697,3 +716,105 @@ class SOVFixerAPIClient(APIClientBase):
|
|
|
697
716
|
|
|
698
717
|
response_data = response.json()
|
|
699
718
|
return response_data
|
|
719
|
+
|
|
720
|
+
def fetch_sov_output(self, sov_id: str, filename: str, output_path: str | None = None) -> bytes:
|
|
721
|
+
"""
|
|
722
|
+
Download an output file from a completed SOV parsing job.
|
|
723
|
+
|
|
724
|
+
:param sov_id: The SOV job ID.
|
|
725
|
+
:param filename: The output filename (from result.outputs[].filename).
|
|
726
|
+
:param output_path: If provided, write the result to this local file path.
|
|
727
|
+
:return: Raw response bytes.
|
|
728
|
+
"""
|
|
729
|
+
url = self.api_url + f"/api/v1/sov/{sov_id}/output/{filename}"
|
|
730
|
+
response = self.get(url)
|
|
731
|
+
raise_for_status(response)
|
|
732
|
+
if output_path:
|
|
733
|
+
with open(output_path, "wb") as fd:
|
|
734
|
+
for chunk in response.iter_content(chunk_size=1024 * 1024):
|
|
735
|
+
fd.write(chunk)
|
|
736
|
+
return response.content
|
|
737
|
+
|
|
738
|
+
def get_history_item(self, id: str) -> t.SOVHistoryResponse:
|
|
739
|
+
"""
|
|
740
|
+
Get a specific historical SOV or SOV Update by its ID.
|
|
741
|
+
|
|
742
|
+
:param id: SOV ID (sovid) or SOV Update ID (sudid).
|
|
743
|
+
:return: Historical SOV data including outputs.
|
|
744
|
+
"""
|
|
745
|
+
url = self.api_url + f"/api/v1/sov/history/{id}"
|
|
746
|
+
response = self.get(url)
|
|
747
|
+
raise_for_status(response)
|
|
748
|
+
return response.json()
|
|
749
|
+
|
|
750
|
+
def get_building(self, item_key: str) -> dict:
|
|
751
|
+
"""
|
|
752
|
+
Retrieve a specific building by its item key.
|
|
753
|
+
|
|
754
|
+
:param item_key: Unique building identifier, e.g. "i-s-e-xxxxxxx!SOV!1".
|
|
755
|
+
:return: Building data dict.
|
|
756
|
+
"""
|
|
757
|
+
url = self.api_url + f"/api/v1/building/{item_key}"
|
|
758
|
+
response = self.get(url)
|
|
759
|
+
raise_for_status(response)
|
|
760
|
+
return response.json()
|
|
761
|
+
|
|
762
|
+
def list_output_formats(
|
|
763
|
+
self,
|
|
764
|
+
sovid: str | None = None,
|
|
765
|
+
division_uuid: str | None = None,
|
|
766
|
+
team_uuid: str | None = None,
|
|
767
|
+
) -> t.OutputFormatsResponse:
|
|
768
|
+
"""List available output formats for the given context."""
|
|
769
|
+
url = self.api_url + "/api/v1/output_formats"
|
|
770
|
+
params = {}
|
|
771
|
+
if sov_id:
|
|
772
|
+
params["sovid"] = sovid
|
|
773
|
+
if division_uuid:
|
|
774
|
+
params["division_uuid"] = division_uuid
|
|
775
|
+
if team_uuid:
|
|
776
|
+
params["team_uuid"] = team_uuid
|
|
777
|
+
response = self.get(url, params=params)
|
|
778
|
+
raise_for_status(response)
|
|
779
|
+
return response.json()
|
|
780
|
+
|
|
781
|
+
def get_public_shareable_url(self, sovid: str) -> t.GetPublicShareableUrlResponse:
|
|
782
|
+
"""
|
|
783
|
+
Get a publicly shareable Ping.Maps URL for the given SOV.
|
|
784
|
+
|
|
785
|
+
:param sovid: The SOV ID.
|
|
786
|
+
:return: Dict with a "url" key containing the shareable URL.
|
|
787
|
+
"""
|
|
788
|
+
url = self.api_url + f"/api/v1/pli/policy/{sovid}/get_public_shareable_url"
|
|
789
|
+
response = self.get(url)
|
|
790
|
+
raise_for_status(response)
|
|
791
|
+
return response.json()
|
|
792
|
+
|
|
793
|
+
def create_submission(
|
|
794
|
+
self,
|
|
795
|
+
document_type: str = "SOV",
|
|
796
|
+
client_ref: str | None = None,
|
|
797
|
+
extra_data: dict | None = None,
|
|
798
|
+
filename: str | None = None,
|
|
799
|
+
) -> t.CreateSubmissionResponse:
|
|
800
|
+
"""
|
|
801
|
+
Create a new empty submission. Follow with update_sov_async_add_locations then update_sov_async_start.
|
|
802
|
+
|
|
803
|
+
:param document_type: "SOV", "PREM_BDX", "CLAIM_BDX", or "SOV_BDX". Default "SOV".
|
|
804
|
+
:param client_ref: Optional user reference identifier.
|
|
805
|
+
:param extra_data: Optional dict of extra_data_* fields (e.g. insured_name, inception_date).
|
|
806
|
+
:param filename: Optional filename for the submission.
|
|
807
|
+
:return: Dict with "id" (int) and "message".
|
|
808
|
+
"""
|
|
809
|
+
url = self.api_url + "/api/v1/submission"
|
|
810
|
+
data: dict = {"document_type": document_type}
|
|
811
|
+
if client_ref:
|
|
812
|
+
data["client_ref"] = client_ref
|
|
813
|
+
if extra_data:
|
|
814
|
+
for k, v in extra_data.items():
|
|
815
|
+
data["extra_data_" + k] = v
|
|
816
|
+
if filename:
|
|
817
|
+
data["filename"] = filename
|
|
818
|
+
response = self.post(url, data=data)
|
|
819
|
+
raise_for_status(response)
|
|
820
|
+
return response.json()
|
|
@@ -83,6 +83,7 @@ class SOVUpdateInitiateRequest(TypedDict):
|
|
|
83
83
|
update_type: NotRequired[str | None]
|
|
84
84
|
callback_url: NotRequired[str | None]
|
|
85
85
|
username: NotRequired[str | None]
|
|
86
|
+
delegate_to_team: NotRequired[str | int | None]
|
|
86
87
|
|
|
87
88
|
|
|
88
89
|
class SOVUpdateResponseResultOutputs(TypedDict):
|
|
@@ -165,6 +166,7 @@ class SOVData(TypedDict):
|
|
|
165
166
|
document_type: str | None
|
|
166
167
|
pct_complete: float | None
|
|
167
168
|
filename: str | None
|
|
169
|
+
additional_filenames: list[str] | None
|
|
168
170
|
status: str | None
|
|
169
171
|
status_display: str | None
|
|
170
172
|
last_health_status: str | None
|
|
@@ -173,12 +175,17 @@ class SOVData(TypedDict):
|
|
|
173
175
|
output_data: list[OutputData] | None
|
|
174
176
|
origin: str | None
|
|
175
177
|
organization__short_name: str | None
|
|
178
|
+
company__short_name: str | None
|
|
179
|
+
team__name: str | None
|
|
180
|
+
division__short_name: str | None
|
|
176
181
|
global_request_id: str | None
|
|
177
182
|
ping_maps_url: str | None
|
|
183
|
+
ping_vision_url: str | None
|
|
178
184
|
input_file_url: str | None
|
|
179
185
|
extra_data: dict[str, Any] | None
|
|
180
186
|
updates: dict[str, UpdateData] | None
|
|
181
187
|
all_updates: list[UpdateData] | None
|
|
188
|
+
latest_revision: int | None
|
|
182
189
|
completed_time: str | None
|
|
183
190
|
created_time: str | None
|
|
184
191
|
subject: str | None
|
|
@@ -198,6 +205,9 @@ class ActivityResponse(TypedDict):
|
|
|
198
205
|
cursor_id: str | None
|
|
199
206
|
prev_cursor_id: str | None
|
|
200
207
|
remaining_count: int
|
|
208
|
+
has_remaining: bool
|
|
209
|
+
total_count: int
|
|
210
|
+
total_size_without_cursors: str
|
|
201
211
|
|
|
202
212
|
|
|
203
213
|
class HistoryItem(TypedDict):
|
|
@@ -210,8 +220,47 @@ class HistoryItem(TypedDict):
|
|
|
210
220
|
completed_time: datetime
|
|
211
221
|
record_type: str
|
|
212
222
|
incremental: bool
|
|
223
|
+
is_data_ready: NotRequired[bool | None]
|
|
213
224
|
|
|
214
225
|
|
|
215
226
|
class HistoryResponse(TypedDict):
|
|
216
227
|
cursor_id: str | None
|
|
217
228
|
results: list[HistoryItem]
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class SOVHistoryOutputItem(TypedDict):
|
|
232
|
+
url: str
|
|
233
|
+
output_format: str
|
|
234
|
+
completed_time: str
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class SOVHistoryResult(TypedDict):
|
|
238
|
+
sovid: str
|
|
239
|
+
previous_sovid: str
|
|
240
|
+
original_sovid: str
|
|
241
|
+
sheet_name: str
|
|
242
|
+
outputs: list[SOVHistoryOutputItem]
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class SOVHistoryResponse(TypedDict):
|
|
246
|
+
result: SOVHistoryResult
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class CreateSubmissionResponse(TypedDict):
|
|
250
|
+
message: str
|
|
251
|
+
id: int
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class GetPublicShareableUrlResponse(TypedDict):
|
|
255
|
+
url: str
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class OutputFormatItem(TypedDict):
|
|
259
|
+
output_format: str
|
|
260
|
+
label: str
|
|
261
|
+
is_public: bool
|
|
262
|
+
output_extension: str | None
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class OutputFormatsResponse(TypedDict):
|
|
266
|
+
output_formats: list[OutputFormatItem]
|
|
@@ -502,6 +502,25 @@ def update(
|
|
|
502
502
|
click.echo(f"Executed SOV Update, SUDID: {sudid}")
|
|
503
503
|
|
|
504
504
|
|
|
505
|
+
@cli.command()
|
|
506
|
+
@click.pass_context
|
|
507
|
+
@click.option("--sovid", help="Scope to a specific SOV ID")
|
|
508
|
+
@click.option("--division-uuid", help="Scope to a division UUID")
|
|
509
|
+
@click.option("--team-uuid", help="Scope to a team UUID")
|
|
510
|
+
@click.option("--pretty", is_flag=True, default=False)
|
|
511
|
+
def list_output_formats(ctx, sovid, division_uuid, team_uuid, pretty):
|
|
512
|
+
"""List available output formats."""
|
|
513
|
+
client = get_client(ctx)
|
|
514
|
+
results = client.list_output_formats(sovid=sovid, division_uuid=division_uuid, team_uuid=team_uuid)
|
|
515
|
+
if pretty:
|
|
516
|
+
print(f"{'Format':<30}{'Label':<40}{'Extension':<12}{'Public'}")
|
|
517
|
+
for fmt in results["output_formats"]:
|
|
518
|
+
ext = fmt.get("output_extension") or ""
|
|
519
|
+
print(f"{fmt['output_format']:<30}{fmt['label']:<40}{ext:<12}{fmt['is_public']}")
|
|
520
|
+
else:
|
|
521
|
+
pprint.pprint(results)
|
|
522
|
+
|
|
523
|
+
|
|
505
524
|
def main():
|
|
506
525
|
cli()
|
|
507
526
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.4.6"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-data/enhance_multiple_locations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-extraction/sovfixer_list_activity.py
RENAMED
|
File without changes
|
{pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-extraction/sovfixer_reoutput_sov.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-vision/pingvision_clear_and_triage.py
RENAMED
|
File without changes
|
{pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/ping-vision/send_and_track_submission.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pingintel_api-1.4.6 → pingintel_api-1.4.8}/examples/sovfixer_list_history_ping_None_1_run2.csv
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pingintel_api-1.4.6 → pingintel_api-1.4.8}/output-data-f432c75e-b40e-11f0-9e68-3c58c2f91432-1.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/pingmaps/pingmaps_api_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pingintel_api-1.4.6 → pingintel_api-1.4.8}/src/pingintel_api/pingvision/pingvision_api_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|