epic-elios-client 1.0.0__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.
- epic_elios_client-1.0.0/PKG-INFO +71 -0
- epic_elios_client-1.0.0/README.md +53 -0
- epic_elios_client-1.0.0/epic_client/__init__.py +5 -0
- epic_elios_client-1.0.0/epic_client/client.py +199 -0
- epic_elios_client-1.0.0/epic_elios_client.egg-info/PKG-INFO +71 -0
- epic_elios_client-1.0.0/epic_elios_client.egg-info/SOURCES.txt +9 -0
- epic_elios_client-1.0.0/epic_elios_client.egg-info/dependency_links.txt +1 -0
- epic_elios_client-1.0.0/epic_elios_client.egg-info/requires.txt +4 -0
- epic_elios_client-1.0.0/epic_elios_client.egg-info/top_level.txt +1 -0
- epic_elios_client-1.0.0/pyproject.toml +33 -0
- epic_elios_client-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: epic-elios-client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Participant SDK for the EPIC — ELIOS Predictive Intelligence Challenge platform.
|
|
5
|
+
Author: ELIOS Lab
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Intended Audience :: Education
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: websockets
|
|
16
|
+
Provides-Extra: notebook
|
|
17
|
+
Requires-Dist: pandas; extra == "notebook"
|
|
18
|
+
|
|
19
|
+
# EPIC Participant SDK
|
|
20
|
+
|
|
21
|
+
`epic-elios-client` is the participant SDK for the EPIC — ELIOS Predictive Intelligence Challenge platform. It helps students and competitors authenticate, browse contests, collect live observations, submit predictions, and inspect results from the default EPIC server at <https://epic.elioslab.net>.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Install the SDK:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install epic-elios-client
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Install the SDK with notebook extras for the Jupyter quickstart:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install "epic-elios-client[notebook]"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Minimal Usage
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import asyncio
|
|
41
|
+
|
|
42
|
+
from epic_client import EPICClient
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def main():
|
|
46
|
+
client = EPICClient("https://epic.elioslab.net")
|
|
47
|
+
client.login("student1", "correct-password")
|
|
48
|
+
|
|
49
|
+
contests = client.list_contests(status="ACTIVE")
|
|
50
|
+
contest_id = contests[0]["contest_id"]
|
|
51
|
+
|
|
52
|
+
observations = await client.collect(contest_id, duration_seconds=10)
|
|
53
|
+
latest = observations[-1]
|
|
54
|
+
|
|
55
|
+
submission = client.submit(
|
|
56
|
+
contest_id=contest_id,
|
|
57
|
+
task_id="forecasting",
|
|
58
|
+
prediction_from_sequence=latest["sequence_id"],
|
|
59
|
+
payload={
|
|
60
|
+
"forecast": {
|
|
61
|
+
"horizon_1": latest["sensors"],
|
|
62
|
+
"horizon_5": latest["sensors"],
|
|
63
|
+
"horizon_10": latest["sensors"],
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
print(submission)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
asyncio.run(main())
|
|
71
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# EPIC Participant SDK
|
|
2
|
+
|
|
3
|
+
`epic-elios-client` is the participant SDK for the EPIC — ELIOS Predictive Intelligence Challenge platform. It helps students and competitors authenticate, browse contests, collect live observations, submit predictions, and inspect results from the default EPIC server at <https://epic.elioslab.net>.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the SDK:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install epic-elios-client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Install the SDK with notebook extras for the Jupyter quickstart:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install "epic-elios-client[notebook]"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Minimal Usage
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import asyncio
|
|
23
|
+
|
|
24
|
+
from epic_client import EPICClient
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def main():
|
|
28
|
+
client = EPICClient("https://epic.elioslab.net")
|
|
29
|
+
client.login("student1", "correct-password")
|
|
30
|
+
|
|
31
|
+
contests = client.list_contests(status="ACTIVE")
|
|
32
|
+
contest_id = contests[0]["contest_id"]
|
|
33
|
+
|
|
34
|
+
observations = await client.collect(contest_id, duration_seconds=10)
|
|
35
|
+
latest = observations[-1]
|
|
36
|
+
|
|
37
|
+
submission = client.submit(
|
|
38
|
+
contest_id=contest_id,
|
|
39
|
+
task_id="forecasting",
|
|
40
|
+
prediction_from_sequence=latest["sequence_id"],
|
|
41
|
+
payload={
|
|
42
|
+
"forecast": {
|
|
43
|
+
"horizon_1": latest["sensors"],
|
|
44
|
+
"horizon_5": latest["sensors"],
|
|
45
|
+
"horizon_10": latest["sensors"],
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
print(submission)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
asyncio.run(main())
|
|
53
|
+
```
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Participant client for EPIC contests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import csv
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import AsyncIterator
|
|
11
|
+
from urllib.error import HTTPError, URLError
|
|
12
|
+
from urllib.parse import urlencode, urljoin, urlparse, urlunparse
|
|
13
|
+
from urllib.request import Request, urlopen
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EPICClient:
|
|
17
|
+
def __init__(self, server_url: str = "https://epic.elioslab.net") -> None:
|
|
18
|
+
self.server_url = server_url.rstrip("/") + "/"
|
|
19
|
+
self._token: str | None = None
|
|
20
|
+
|
|
21
|
+
def login(self, username: str, password: str) -> dict:
|
|
22
|
+
response = self._request(
|
|
23
|
+
"POST",
|
|
24
|
+
"/api/v1/auth/login",
|
|
25
|
+
{"username": username, "password": password},
|
|
26
|
+
authenticated=False,
|
|
27
|
+
)
|
|
28
|
+
self._token = response["access_token"]
|
|
29
|
+
return response
|
|
30
|
+
|
|
31
|
+
def register(self, contest_id: str) -> dict:
|
|
32
|
+
try:
|
|
33
|
+
return self._request(
|
|
34
|
+
"POST",
|
|
35
|
+
"/api/v1/contest-registrations",
|
|
36
|
+
{"contest_id": contest_id},
|
|
37
|
+
)
|
|
38
|
+
except RuntimeError as exc:
|
|
39
|
+
if "Already registered for this contest" not in str(exc):
|
|
40
|
+
raise
|
|
41
|
+
return {
|
|
42
|
+
"contest_id": contest_id,
|
|
43
|
+
"status": "REGISTERED",
|
|
44
|
+
"message": "Already registered for this contest",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def list_contests(self, status: str | None = None) -> list[dict]:
|
|
48
|
+
query = f"?{urlencode({'status': status})}" if status is not None else ""
|
|
49
|
+
response = self._request("GET", f"/api/v1/contests{query}")
|
|
50
|
+
contests = response["contests"]
|
|
51
|
+
for contest in contests:
|
|
52
|
+
if "contest_id" not in contest and "id" in contest:
|
|
53
|
+
contest["contest_id"] = contest["id"]
|
|
54
|
+
return contests
|
|
55
|
+
|
|
56
|
+
async def stream(self, contest_id: str) -> AsyncIterator[dict]:
|
|
57
|
+
self._require_token()
|
|
58
|
+
websocket_url = self._websocket_url(f"/api/v1/ws/contests/{contest_id}")
|
|
59
|
+
reconnect_delay = 1.0
|
|
60
|
+
|
|
61
|
+
while True:
|
|
62
|
+
try:
|
|
63
|
+
import websockets
|
|
64
|
+
|
|
65
|
+
async with websockets.connect(websocket_url) as websocket:
|
|
66
|
+
async for message in websocket:
|
|
67
|
+
payload = json.loads(message)
|
|
68
|
+
yield {
|
|
69
|
+
"sequence_id": payload["sequence_id"],
|
|
70
|
+
"timestamp": payload["timestamp"],
|
|
71
|
+
"sensors": payload["sensors"],
|
|
72
|
+
}
|
|
73
|
+
except asyncio.CancelledError:
|
|
74
|
+
raise
|
|
75
|
+
except Exception:
|
|
76
|
+
await asyncio.sleep(reconnect_delay)
|
|
77
|
+
|
|
78
|
+
async def collect(
|
|
79
|
+
self,
|
|
80
|
+
contest_id: str,
|
|
81
|
+
duration_seconds: float,
|
|
82
|
+
csv_path: str | Path | None = None,
|
|
83
|
+
) -> list[dict]:
|
|
84
|
+
observations: list[dict] = []
|
|
85
|
+
deadline = time.monotonic() + duration_seconds
|
|
86
|
+
writer = None
|
|
87
|
+
csv_file = None
|
|
88
|
+
|
|
89
|
+
if csv_path is not None:
|
|
90
|
+
csv_file = Path(csv_path).open("w", newline="")
|
|
91
|
+
writer = csv.DictWriter(
|
|
92
|
+
csv_file,
|
|
93
|
+
fieldnames=["sequence_id", "timestamp", "sensors"],
|
|
94
|
+
)
|
|
95
|
+
writer.writeheader()
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
stream = self.stream(contest_id).__aiter__()
|
|
99
|
+
while True:
|
|
100
|
+
remaining = deadline - time.monotonic()
|
|
101
|
+
if remaining <= 0:
|
|
102
|
+
break
|
|
103
|
+
try:
|
|
104
|
+
observation = await asyncio.wait_for(stream.__anext__(), remaining)
|
|
105
|
+
except asyncio.TimeoutError:
|
|
106
|
+
break
|
|
107
|
+
observations.append(observation)
|
|
108
|
+
if writer is not None:
|
|
109
|
+
writer.writerow(
|
|
110
|
+
{
|
|
111
|
+
"sequence_id": observation["sequence_id"],
|
|
112
|
+
"timestamp": observation["timestamp"],
|
|
113
|
+
"sensors": json.dumps(observation["sensors"]),
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
finally:
|
|
117
|
+
if csv_file is not None:
|
|
118
|
+
csv_file.close()
|
|
119
|
+
|
|
120
|
+
return observations
|
|
121
|
+
|
|
122
|
+
def submit(
|
|
123
|
+
self,
|
|
124
|
+
contest_id: str,
|
|
125
|
+
task_id: str,
|
|
126
|
+
prediction_from_sequence: int,
|
|
127
|
+
payload: dict,
|
|
128
|
+
) -> dict:
|
|
129
|
+
return self._request(
|
|
130
|
+
"POST",
|
|
131
|
+
f"/api/v1/contests/{contest_id}/submissions",
|
|
132
|
+
{
|
|
133
|
+
"task_id": task_id,
|
|
134
|
+
"prediction_from_sequence": prediction_from_sequence,
|
|
135
|
+
"payload": payload,
|
|
136
|
+
},
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def get_scores(self, contest_id: str) -> dict:
|
|
140
|
+
submissions = self._request(
|
|
141
|
+
"GET", f"/api/v1/contests/{contest_id}/submissions"
|
|
142
|
+
)["submissions"]
|
|
143
|
+
scored_submissions = []
|
|
144
|
+
for submission in submissions:
|
|
145
|
+
scores = self._request(
|
|
146
|
+
"GET", f"/api/v1/submissions/{submission['submission_id']}/scores"
|
|
147
|
+
)
|
|
148
|
+
scored_submissions.append({**submission, "scores": scores["scores"]})
|
|
149
|
+
return {"contest_id": contest_id, "submissions": scored_submissions}
|
|
150
|
+
|
|
151
|
+
def get_leaderboard(self, contest_id: str) -> dict:
|
|
152
|
+
return self._request("GET", f"/api/v1/contests/{contest_id}/leaderboard")
|
|
153
|
+
|
|
154
|
+
def _request(
|
|
155
|
+
self,
|
|
156
|
+
method: str,
|
|
157
|
+
path: str,
|
|
158
|
+
body: dict | None = None,
|
|
159
|
+
authenticated: bool = True,
|
|
160
|
+
) -> dict:
|
|
161
|
+
if authenticated:
|
|
162
|
+
self._require_token()
|
|
163
|
+
|
|
164
|
+
data = None
|
|
165
|
+
headers = {"Content-Type": "application/json"}
|
|
166
|
+
if authenticated and self._token is not None:
|
|
167
|
+
headers["Authorization"] = f"Bearer {self._token}"
|
|
168
|
+
if body is not None:
|
|
169
|
+
data = json.dumps(body).encode("utf-8")
|
|
170
|
+
|
|
171
|
+
request = Request(
|
|
172
|
+
urljoin(self.server_url, path.lstrip("/")),
|
|
173
|
+
data=data,
|
|
174
|
+
headers=headers,
|
|
175
|
+
method=method,
|
|
176
|
+
)
|
|
177
|
+
try:
|
|
178
|
+
with urlopen(request) as response:
|
|
179
|
+
response_body = response.read()
|
|
180
|
+
except HTTPError as exc:
|
|
181
|
+
message = exc.read().decode("utf-8")
|
|
182
|
+
raise RuntimeError(f"EPIC API request failed: {exc.code} {message}") from exc
|
|
183
|
+
except URLError as exc:
|
|
184
|
+
raise RuntimeError(f"EPIC API request failed: {exc.reason}") from exc
|
|
185
|
+
|
|
186
|
+
if not response_body:
|
|
187
|
+
return {}
|
|
188
|
+
return json.loads(response_body.decode("utf-8"))
|
|
189
|
+
|
|
190
|
+
def _require_token(self) -> None:
|
|
191
|
+
if self._token is None:
|
|
192
|
+
raise RuntimeError("Not authenticated. Call login() first.")
|
|
193
|
+
|
|
194
|
+
def _websocket_url(self, path: str) -> str:
|
|
195
|
+
self._require_token()
|
|
196
|
+
parsed = urlparse(urljoin(self.server_url, path.lstrip("/")))
|
|
197
|
+
scheme = "wss" if parsed.scheme == "https" else "ws"
|
|
198
|
+
query = urlencode({"token": self._token})
|
|
199
|
+
return urlunparse((scheme, parsed.netloc, parsed.path, "", query, ""))
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: epic-elios-client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Participant SDK for the EPIC — ELIOS Predictive Intelligence Challenge platform.
|
|
5
|
+
Author: ELIOS Lab
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Intended Audience :: Education
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: websockets
|
|
16
|
+
Provides-Extra: notebook
|
|
17
|
+
Requires-Dist: pandas; extra == "notebook"
|
|
18
|
+
|
|
19
|
+
# EPIC Participant SDK
|
|
20
|
+
|
|
21
|
+
`epic-elios-client` is the participant SDK for the EPIC — ELIOS Predictive Intelligence Challenge platform. It helps students and competitors authenticate, browse contests, collect live observations, submit predictions, and inspect results from the default EPIC server at <https://epic.elioslab.net>.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Install the SDK:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install epic-elios-client
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Install the SDK with notebook extras for the Jupyter quickstart:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install "epic-elios-client[notebook]"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Minimal Usage
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import asyncio
|
|
41
|
+
|
|
42
|
+
from epic_client import EPICClient
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def main():
|
|
46
|
+
client = EPICClient("https://epic.elioslab.net")
|
|
47
|
+
client.login("student1", "correct-password")
|
|
48
|
+
|
|
49
|
+
contests = client.list_contests(status="ACTIVE")
|
|
50
|
+
contest_id = contests[0]["contest_id"]
|
|
51
|
+
|
|
52
|
+
observations = await client.collect(contest_id, duration_seconds=10)
|
|
53
|
+
latest = observations[-1]
|
|
54
|
+
|
|
55
|
+
submission = client.submit(
|
|
56
|
+
contest_id=contest_id,
|
|
57
|
+
task_id="forecasting",
|
|
58
|
+
prediction_from_sequence=latest["sequence_id"],
|
|
59
|
+
payload={
|
|
60
|
+
"forecast": {
|
|
61
|
+
"horizon_1": latest["sensors"],
|
|
62
|
+
"horizon_5": latest["sensors"],
|
|
63
|
+
"horizon_10": latest["sensors"],
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
print(submission)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
asyncio.run(main())
|
|
71
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
epic_client/__init__.py
|
|
4
|
+
epic_client/client.py
|
|
5
|
+
epic_elios_client.egg-info/PKG-INFO
|
|
6
|
+
epic_elios_client.egg-info/SOURCES.txt
|
|
7
|
+
epic_elios_client.egg-info/dependency_links.txt
|
|
8
|
+
epic_elios_client.egg-info/requires.txt
|
|
9
|
+
epic_elios_client.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
epic_client
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "epic-elios-client"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Participant SDK for the EPIC — ELIOS Predictive Intelligence Challenge platform."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "ELIOS Lab" }
|
|
13
|
+
]
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Intended Audience :: Education",
|
|
21
|
+
"Intended Audience :: Science/Research",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"websockets",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
notebook = [
|
|
29
|
+
"pandas",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[tool.setuptools]
|
|
33
|
+
packages = ["epic_client"]
|