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.
@@ -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,5 @@
1
+ """EPIC Participant SDK."""
2
+
3
+ from epic_client.client import EPICClient
4
+
5
+ __all__ = ["EPICClient"]
@@ -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,4 @@
1
+ websockets
2
+
3
+ [notebook]
4
+ pandas
@@ -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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+