coordinator-node 0.1.2__tar.gz → 0.1.4__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.
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/PKG-INFO +1 -1
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/Makefile +7 -1
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/Dockerfile +1 -1
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/Makefile +7 -1
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/docker-compose.yml +37 -15
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/db/init_db.py +41 -9
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/db/repositories.py +1 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/db/session.py +7 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/services/score.py +8 -1
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/pyproject.toml +1 -1
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/tests/test_node_template_predict_service.py +66 -2
- coordinator_node-0.1.4/tests/test_node_template_repositories.py +86 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/uv.lock +1 -1
- coordinator_node-0.1.2/tests/test_node_template_repositories.py +0 -39
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/.dev.env +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/.dockerignore +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/.gitignore +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/.local.env +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/.production.env +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/.python-version +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/Dockerfile +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/Makefile +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/README.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/SKILL.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/README.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/SKILL.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/README.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/SKILL.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/pyproject.toml +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/examples/README.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/examples/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/examples/mean_reversion_tracker.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/examples/trend_following_tracker.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/examples/volatility_regime_tracker.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/schemas/README.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/scoring.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/tracker.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/.local.env +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/.local.env.example +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/.production.env.example +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/README.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/RUNBOOK.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/SKILL.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/config/README.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/config/callables.env +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/config/scheduled_prediction_configs.json +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/deployment/README.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/deployment/model-orchestrator-local/config/docker-entrypoint.sh +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/deployment/model-orchestrator-local/config/models.dev.yml +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/deployment/model-orchestrator-local/config/orchestrator.dev.yml +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/deployment/model-orchestrator-local/config/starter-submission/main.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/deployment/model-orchestrator-local/config/starter-submission/requirements.txt +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/deployment/model-orchestrator-local/config/starter-submission/tracker.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/deployment/report-ui/config/global-settings.json +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/deployment/report-ui/config/leaderboard-columns.json +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/deployment/report-ui/config/metrics-widgets.json +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/extensions/README.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/plugins/README.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/pyproject.toml +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/runtime_definitions/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/runtime_definitions/contracts.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/scripts/backfill.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/scripts/capture_runtime_logs.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/scripts/check_models.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/scripts/verify_e2e.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/clean-data.sh +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/config/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/config/extensions.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/config/runtime.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/contracts.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/db/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/db/feed_records.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/db/pg_notify.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/db/tables/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/db/tables/feed.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/db/tables/models.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/db/tables/pipeline.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/entities/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/entities/feed_record.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/entities/model.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/entities/prediction.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/extensions/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/extensions/callable_resolver.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/extensions/default_callables.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/feeds/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/feeds/base.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/feeds/contracts.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/feeds/providers/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/feeds/providers/binance.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/feeds/providers/pyth.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/feeds/registry.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/schemas/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/schemas/payload_contracts.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/services/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/services/backfill.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/services/feed_data.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/services/feed_reader.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/services/predict.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/services/realtime_predict.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/workers/__init__.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/workers/checkpoint_worker.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/workers/feed_data_worker.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/workers/predict_worker.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/workers/report_worker.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/workers/score_worker.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/docker-compose.yml +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/docs/plans/2026-02-10-coordinator-restructure-design.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/docs/plans/2026-02-11-contract-based-architecture.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/docs/plans/2026-02-11-scoring-snapshots-checkpoints.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/docs/plans/2026-02-11-thin-node-cli-implementation-plan.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/docs/plans/2026-02-11-thin-node-cli-onboarding-design.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/mkdocs.yml +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/packs/README.md +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/scripts/verify_e2e.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/tests/test_backfill.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/tests/test_callable_resolver.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/tests/test_checkpoint_worker.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/tests/test_coordinator_core_schema.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/tests/test_coordinator_runtime_data_feeds.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/tests/test_core_entities.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/tests/test_node_template_report_worker.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/tests/test_node_template_score_service.py +0 -0
- {coordinator_node-0.1.2 → coordinator_node-0.1.4}/tests/test_prediction_lifecycle.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coordinator-node
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Runtime engine for Crunch coordinator nodes
|
|
5
5
|
Project-URL: Homepage, https://github.com/crunchdao/coordinator-node-starter
|
|
6
6
|
Project-URL: Repository, https://github.com/crunchdao/coordinator-node-starter
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Workspace-level convenience — proxies to node/
|
|
2
|
-
.PHONY: deploy down logs verify-e2e check-models
|
|
2
|
+
.PHONY: deploy down logs verify-e2e check-models init-db reset-db
|
|
3
3
|
|
|
4
4
|
deploy:
|
|
5
5
|
cd node && make deploy
|
|
@@ -15,3 +15,9 @@ verify-e2e:
|
|
|
15
15
|
|
|
16
16
|
check-models:
|
|
17
17
|
cd node && make check-models
|
|
18
|
+
|
|
19
|
+
init-db:
|
|
20
|
+
cd node && make init-db
|
|
21
|
+
|
|
22
|
+
reset-db:
|
|
23
|
+
cd node && make reset-db
|
|
@@ -8,7 +8,7 @@ WORKDIR /app
|
|
|
8
8
|
|
|
9
9
|
# Coordinator engine (from PyPI)
|
|
10
10
|
# Bump this to force Docker to re-pull the latest package version
|
|
11
|
-
ARG COORDINATOR_NODE_VERSION=0.1.
|
|
11
|
+
ARG COORDINATOR_NODE_VERSION=0.1.4
|
|
12
12
|
RUN pip install --no-cache-dir "coordinator-node>=${COORDINATOR_NODE_VERSION}"
|
|
13
13
|
|
|
14
14
|
# Challenge package
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
COMPOSE := docker compose -f docker-compose.yml --env-file .local.env
|
|
2
2
|
|
|
3
|
-
.PHONY: deploy down logs logs-capture verify-e2e check-models starter platform backfill
|
|
3
|
+
.PHONY: deploy down logs logs-capture verify-e2e check-models starter platform backfill init-db reset-db
|
|
4
4
|
|
|
5
5
|
deploy:
|
|
6
6
|
$(COMPOSE) up -d --build
|
|
7
7
|
|
|
8
|
+
init-db:
|
|
9
|
+
$(COMPOSE) run --rm init-db
|
|
10
|
+
|
|
11
|
+
reset-db:
|
|
12
|
+
$(COMPOSE) run --rm reset-db
|
|
13
|
+
|
|
8
14
|
down:
|
|
9
15
|
$(COMPOSE) down
|
|
10
16
|
|
|
@@ -27,6 +27,28 @@ services:
|
|
|
27
27
|
dockerfile: node/Dockerfile
|
|
28
28
|
container_name: starter-challenge-init-db
|
|
29
29
|
command: ["python", "-m", "coordinator_node.db.init_db"]
|
|
30
|
+
profiles: [init]
|
|
31
|
+
environment:
|
|
32
|
+
POSTGRES_HOST: ${POSTGRES_HOST:-postgres}
|
|
33
|
+
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
|
|
34
|
+
POSTGRES_USER: ${POSTGRES_USER:-starter}
|
|
35
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-starter}
|
|
36
|
+
POSTGRES_DB: ${POSTGRES_DB:-starter}
|
|
37
|
+
SCHEDULED_PREDICTION_CONFIGS_PATH: /app/config/scheduled_prediction_configs.json
|
|
38
|
+
volumes:
|
|
39
|
+
- ./config/scheduled_prediction_configs.json:/app/config/scheduled_prediction_configs.json:ro
|
|
40
|
+
depends_on:
|
|
41
|
+
postgres:
|
|
42
|
+
condition: service_healthy
|
|
43
|
+
networks: [coordinator-net]
|
|
44
|
+
|
|
45
|
+
reset-db:
|
|
46
|
+
build:
|
|
47
|
+
context: ..
|
|
48
|
+
dockerfile: node/Dockerfile
|
|
49
|
+
container_name: starter-challenge-reset-db
|
|
50
|
+
command: ["python", "-m", "coordinator_node.db.init_db", "--reset"]
|
|
51
|
+
profiles: [reset]
|
|
30
52
|
environment:
|
|
31
53
|
POSTGRES_HOST: ${POSTGRES_HOST:-postgres}
|
|
32
54
|
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
|
|
@@ -62,12 +84,12 @@ services:
|
|
|
62
84
|
FEED_BACKFILL_MINUTES: ${FEED_BACKFILL_MINUTES:-180}
|
|
63
85
|
FEED_RECORD_TTL_DAYS: ${FEED_RECORD_TTL_DAYS:-90}
|
|
64
86
|
FEED_RETENTION_CHECK_SECONDS: ${FEED_RETENTION_CHECK_SECONDS:-3600}
|
|
65
|
-
PYTHONPATH: /app/challenge
|
|
87
|
+
PYTHONPATH: /app:/app/challenge
|
|
66
88
|
volumes:
|
|
67
89
|
- ../challenge:/app/challenge
|
|
68
90
|
depends_on:
|
|
69
|
-
|
|
70
|
-
condition:
|
|
91
|
+
postgres:
|
|
92
|
+
condition: service_healthy
|
|
71
93
|
networks: [coordinator-net]
|
|
72
94
|
|
|
73
95
|
predict-worker:
|
|
@@ -93,12 +115,12 @@ services:
|
|
|
93
115
|
FEED_SUBJECTS: ${FEED_SUBJECTS:-BTC}
|
|
94
116
|
FEED_KIND: ${FEED_KIND:-tick}
|
|
95
117
|
FEED_GRANULARITY: ${FEED_GRANULARITY:-1s}
|
|
96
|
-
PYTHONPATH: /app/challenge
|
|
118
|
+
PYTHONPATH: /app:/app/challenge
|
|
97
119
|
volumes:
|
|
98
120
|
- ../challenge:/app/challenge
|
|
99
121
|
depends_on:
|
|
100
|
-
|
|
101
|
-
condition:
|
|
122
|
+
postgres:
|
|
123
|
+
condition: service_healthy
|
|
102
124
|
model-orchestrator:
|
|
103
125
|
condition: service_started
|
|
104
126
|
feed-data-worker:
|
|
@@ -124,12 +146,12 @@ services:
|
|
|
124
146
|
FEED_KIND: ${FEED_KIND:-tick}
|
|
125
147
|
FEED_GRANULARITY: ${FEED_GRANULARITY:-1s}
|
|
126
148
|
SCORING_FUNCTION: ${SCORING_FUNCTION}
|
|
127
|
-
PYTHONPATH: /app/challenge
|
|
149
|
+
PYTHONPATH: /app:/app/challenge
|
|
128
150
|
volumes:
|
|
129
151
|
- ../challenge:/app/challenge
|
|
130
152
|
depends_on:
|
|
131
|
-
|
|
132
|
-
condition:
|
|
153
|
+
postgres:
|
|
154
|
+
condition: service_healthy
|
|
133
155
|
feed-data-worker:
|
|
134
156
|
condition: service_started
|
|
135
157
|
networks: [coordinator-net]
|
|
@@ -148,12 +170,12 @@ services:
|
|
|
148
170
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-starter}
|
|
149
171
|
POSTGRES_DB: ${POSTGRES_DB:-starter}
|
|
150
172
|
CHECKPOINT_INTERVAL_SECONDS: ${CHECKPOINT_INTERVAL_SECONDS:-604800}
|
|
151
|
-
PYTHONPATH: /app/challenge
|
|
173
|
+
PYTHONPATH: /app:/app/challenge
|
|
152
174
|
volumes:
|
|
153
175
|
- ../challenge:/app/challenge
|
|
154
176
|
depends_on:
|
|
155
|
-
|
|
156
|
-
condition:
|
|
177
|
+
postgres:
|
|
178
|
+
condition: service_healthy
|
|
157
179
|
networks: [coordinator-net]
|
|
158
180
|
|
|
159
181
|
report-worker:
|
|
@@ -171,12 +193,12 @@ services:
|
|
|
171
193
|
POSTGRES_USER: ${POSTGRES_USER:-starter}
|
|
172
194
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-starter}
|
|
173
195
|
POSTGRES_DB: ${POSTGRES_DB:-starter}
|
|
174
|
-
PYTHONPATH: /app/challenge
|
|
196
|
+
PYTHONPATH: /app:/app/challenge
|
|
175
197
|
volumes:
|
|
176
198
|
- ../challenge:/app/challenge
|
|
177
199
|
depends_on:
|
|
178
|
-
|
|
179
|
-
condition:
|
|
200
|
+
postgres:
|
|
201
|
+
condition: service_healthy
|
|
180
202
|
networks: [coordinator-net]
|
|
181
203
|
|
|
182
204
|
model-orchestrator:
|
|
@@ -57,15 +57,13 @@ def load_scheduled_prediction_configs() -> list[dict[str, Any]]:
|
|
|
57
57
|
return payload
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
def
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
conn.execute(text(f"DROP TABLE IF EXISTS {table} CASCADE"))
|
|
65
|
-
|
|
66
|
-
print("➡️ Creating coordinator core tables...")
|
|
60
|
+
def migrate() -> None:
|
|
61
|
+
"""Create tables if they don't exist and upsert prediction configs.
|
|
62
|
+
Safe to run on every boot — never drops data."""
|
|
63
|
+
print("➡️ Creating tables (if not exist)...")
|
|
67
64
|
SQLModel.metadata.create_all(engine)
|
|
68
65
|
|
|
66
|
+
print("➡️ Upserting scheduled prediction configs...")
|
|
69
67
|
with create_session() as session:
|
|
70
68
|
session.exec(delete(PredictionConfigRow))
|
|
71
69
|
for idx, config in enumerate(load_scheduled_prediction_configs(), start=1):
|
|
@@ -83,8 +81,42 @@ def init_db() -> None:
|
|
|
83
81
|
)
|
|
84
82
|
session.commit()
|
|
85
83
|
|
|
86
|
-
print("✅
|
|
84
|
+
print("✅ Database migration complete.")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def reset_db() -> None:
|
|
88
|
+
"""Drop all tables and recreate from scratch. Destroys all data."""
|
|
89
|
+
print("⚠️ Dropping all tables...")
|
|
90
|
+
with engine.begin() as conn:
|
|
91
|
+
for table in tables_to_reset():
|
|
92
|
+
conn.execute(text(f"DROP TABLE IF EXISTS {table} CASCADE"))
|
|
93
|
+
|
|
94
|
+
migrate()
|
|
95
|
+
print("✅ Database reset complete.")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Keep backward compat
|
|
99
|
+
init_db = reset_db
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def auto_migrate() -> None:
|
|
103
|
+
"""Run migrate if tables don't exist yet. Called by workers on boot."""
|
|
104
|
+
try:
|
|
105
|
+
from sqlalchemy import inspect as sa_inspect
|
|
106
|
+
inspector = sa_inspect(engine)
|
|
107
|
+
if not inspector.has_table("models"):
|
|
108
|
+
migrate()
|
|
109
|
+
except Exception:
|
|
110
|
+
# First boot or connection issue — try migrate anyway
|
|
111
|
+
try:
|
|
112
|
+
migrate()
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
87
115
|
|
|
88
116
|
|
|
89
117
|
if __name__ == "__main__":
|
|
90
|
-
|
|
118
|
+
import sys
|
|
119
|
+
if "--reset" in sys.argv:
|
|
120
|
+
reset_db()
|
|
121
|
+
else:
|
|
122
|
+
migrate()
|
|
@@ -168,6 +168,7 @@ class DBPredictionRepository:
|
|
|
168
168
|
existing.meta_jsonb = row.meta_jsonb
|
|
169
169
|
existing.scope_key = row.scope_key
|
|
170
170
|
existing.scope_jsonb = row.scope_jsonb
|
|
171
|
+
existing.resolvable_at = row.resolvable_at
|
|
171
172
|
self._session.commit()
|
|
172
173
|
|
|
173
174
|
def save_all(self, predictions: Iterable[PredictionRecord]) -> None:
|
|
@@ -16,6 +16,13 @@ def database_url() -> str:
|
|
|
16
16
|
|
|
17
17
|
engine = create_engine(database_url())
|
|
18
18
|
|
|
19
|
+
_migrated = False
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
def create_session() -> Session:
|
|
23
|
+
global _migrated
|
|
24
|
+
if not _migrated:
|
|
25
|
+
from coordinator_node.db.init_db import auto_migrate
|
|
26
|
+
auto_migrate()
|
|
27
|
+
_migrated = True
|
|
21
28
|
return Session(engine)
|
|
@@ -224,7 +224,7 @@ class ScoreService:
|
|
|
224
224
|
metrics: dict[str, float] = {}
|
|
225
225
|
for window_name, window in aggregation.windows.items():
|
|
226
226
|
cutoff = now - timedelta(hours=window.hours)
|
|
227
|
-
window_snaps = [s for s in model_snapshots if s.period_end >= cutoff]
|
|
227
|
+
window_snaps = [s for s in model_snapshots if self._ensure_utc(s.period_end) >= cutoff]
|
|
228
228
|
if window_snaps:
|
|
229
229
|
vals = [float(s.result_summary.get(aggregation.ranking_key, 0)) for s in window_snaps]
|
|
230
230
|
metrics[window_name] = sum(vals) / len(vals)
|
|
@@ -270,6 +270,13 @@ class ScoreService:
|
|
|
270
270
|
|
|
271
271
|
_rank_leaderboard = _rank
|
|
272
272
|
|
|
273
|
+
@staticmethod
|
|
274
|
+
def _ensure_utc(dt: datetime) -> datetime:
|
|
275
|
+
"""Ensure a datetime is timezone-aware (assume UTC if naive)."""
|
|
276
|
+
if dt.tzinfo is None:
|
|
277
|
+
return dt.replace(tzinfo=timezone.utc)
|
|
278
|
+
return dt
|
|
279
|
+
|
|
273
280
|
def _rollback_repositories(self) -> None:
|
|
274
281
|
for name, repo in [("input", self.input_repository),
|
|
275
282
|
("prediction", self.prediction_repository),
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/tests/test_node_template_predict_service.py
RENAMED
|
@@ -2,7 +2,7 @@ import unittest
|
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
3
|
|
|
4
4
|
from coordinator_node.entities.model import Model
|
|
5
|
-
from coordinator_node.entities.prediction import PredictionRecord
|
|
5
|
+
from coordinator_node.entities.prediction import InputRecord, PredictionRecord
|
|
6
6
|
from coordinator_node.contracts import CrunchContract
|
|
7
7
|
from coordinator_node.services.realtime_predict import RealtimePredictService
|
|
8
8
|
|
|
@@ -111,16 +111,33 @@ class InMemoryPredictionRepository:
|
|
|
111
111
|
]
|
|
112
112
|
|
|
113
113
|
|
|
114
|
+
class InMemoryInputRepository:
|
|
115
|
+
def __init__(self):
|
|
116
|
+
self.records: list[InputRecord] = []
|
|
117
|
+
|
|
118
|
+
def save(self, record: InputRecord):
|
|
119
|
+
for i, r in enumerate(self.records):
|
|
120
|
+
if r.id == record.id:
|
|
121
|
+
self.records[i] = record
|
|
122
|
+
return
|
|
123
|
+
self.records.append(record)
|
|
124
|
+
|
|
125
|
+
def find(self, **kwargs):
|
|
126
|
+
return list(self.records)
|
|
127
|
+
|
|
128
|
+
|
|
114
129
|
class NoConfigPredictionRepository(InMemoryPredictionRepository):
|
|
115
130
|
def fetch_active_configs(self):
|
|
116
131
|
return []
|
|
117
132
|
|
|
118
133
|
|
|
119
|
-
def _make_service(feed_reader=None, prediction_repo=None,
|
|
134
|
+
def _make_service(feed_reader=None, prediction_repo=None, input_repo=None,
|
|
135
|
+
runner=None, contract=None):
|
|
120
136
|
return RealtimePredictService(
|
|
121
137
|
checkpoint_interval_seconds=60,
|
|
122
138
|
feed_reader=feed_reader or FakeFeedReader(),
|
|
123
139
|
contract=contract or CrunchContract(),
|
|
140
|
+
input_repository=input_repo,
|
|
124
141
|
model_repository=InMemoryModelRepository(),
|
|
125
142
|
prediction_repository=prediction_repo or InMemoryPredictionRepository(),
|
|
126
143
|
runner=runner or FakeRunner(),
|
|
@@ -191,5 +208,52 @@ class TestRealtimePredictService(unittest.IsolatedAsyncioTestCase):
|
|
|
191
208
|
self.assertTrue(any("INFERENCE_OUTPUT_VALIDATION_ERROR" in line for line in logs.output))
|
|
192
209
|
|
|
193
210
|
|
|
211
|
+
async def test_run_once_sets_input_scope_with_feed_dimensions(self):
|
|
212
|
+
"""Regression: input scope must include source/subject/kind/granularity
|
|
213
|
+
so the score worker can query matching feed records for ground truth."""
|
|
214
|
+
input_repo = InMemoryInputRepository()
|
|
215
|
+
pred_repo = InMemoryPredictionRepository()
|
|
216
|
+
feed_reader = FakeFeedReader({"symbol": "BTC"})
|
|
217
|
+
feed_reader.source = "binance"
|
|
218
|
+
feed_reader.subject = "BTC"
|
|
219
|
+
feed_reader.kind = "candle"
|
|
220
|
+
feed_reader.granularity = "1m"
|
|
221
|
+
|
|
222
|
+
service = _make_service(
|
|
223
|
+
feed_reader=feed_reader,
|
|
224
|
+
prediction_repo=pred_repo,
|
|
225
|
+
input_repo=input_repo,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
await service.run_once(raw_input={"symbol": "BTC"}, now=datetime.now(timezone.utc))
|
|
229
|
+
|
|
230
|
+
self.assertEqual(len(input_repo.records), 1)
|
|
231
|
+
inp = input_repo.records[0]
|
|
232
|
+
# Feed dimensions must be in scope for score worker to query feed records
|
|
233
|
+
self.assertEqual(inp.scope.get("source"), "binance")
|
|
234
|
+
self.assertEqual(inp.scope.get("kind"), "candle")
|
|
235
|
+
self.assertEqual(inp.scope.get("granularity"), "1m")
|
|
236
|
+
# subject comes from config scope_template, which may override feed_reader
|
|
237
|
+
self.assertIn("subject", inp.scope)
|
|
238
|
+
|
|
239
|
+
async def test_run_once_sets_input_resolvable_at(self):
|
|
240
|
+
"""Regression: input resolvable_at must be set so the score worker
|
|
241
|
+
can find inputs that are ready for ground truth resolution."""
|
|
242
|
+
input_repo = InMemoryInputRepository()
|
|
243
|
+
pred_repo = InMemoryPredictionRepository()
|
|
244
|
+
service = _make_service(
|
|
245
|
+
prediction_repo=pred_repo,
|
|
246
|
+
input_repo=input_repo,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
now = datetime.now(timezone.utc)
|
|
250
|
+
await service.run_once(raw_input={"symbol": "BTC"}, now=now)
|
|
251
|
+
|
|
252
|
+
self.assertEqual(len(input_repo.records), 1)
|
|
253
|
+
inp = input_repo.records[0]
|
|
254
|
+
self.assertIsNotNone(inp.resolvable_at)
|
|
255
|
+
self.assertGreater(inp.resolvable_at, now)
|
|
256
|
+
|
|
257
|
+
|
|
194
258
|
if __name__ == "__main__":
|
|
195
259
|
unittest.main()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from coordinator_node.db.repositories import (
|
|
5
|
+
DBInputRepository,
|
|
6
|
+
DBLeaderboardRepository,
|
|
7
|
+
DBModelRepository,
|
|
8
|
+
DBPredictionRepository,
|
|
9
|
+
DBScoreRepository,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestRepositoryAPIs(unittest.TestCase):
|
|
14
|
+
def test_model_repository_has_required_methods(self):
|
|
15
|
+
self.assertTrue(callable(getattr(DBModelRepository, "fetch_all", None)))
|
|
16
|
+
self.assertTrue(callable(getattr(DBModelRepository, "save", None)))
|
|
17
|
+
|
|
18
|
+
def test_input_repository_has_required_methods(self):
|
|
19
|
+
self.assertTrue(callable(getattr(DBInputRepository, "save", None)))
|
|
20
|
+
self.assertTrue(callable(getattr(DBInputRepository, "find", None)))
|
|
21
|
+
|
|
22
|
+
def test_prediction_repository_has_required_methods(self):
|
|
23
|
+
self.assertTrue(callable(getattr(DBPredictionRepository, "save", None)))
|
|
24
|
+
self.assertTrue(callable(getattr(DBPredictionRepository, "save_all", None)))
|
|
25
|
+
self.assertTrue(callable(getattr(DBPredictionRepository, "find", None)))
|
|
26
|
+
|
|
27
|
+
def test_score_repository_has_required_methods(self):
|
|
28
|
+
self.assertTrue(callable(getattr(DBScoreRepository, "save", None)))
|
|
29
|
+
self.assertTrue(callable(getattr(DBScoreRepository, "find", None)))
|
|
30
|
+
|
|
31
|
+
def test_prediction_repository_has_query_scores_method(self):
|
|
32
|
+
self.assertTrue(callable(getattr(DBPredictionRepository, "query_scores", None)))
|
|
33
|
+
|
|
34
|
+
def test_leaderboard_repository_has_required_methods(self):
|
|
35
|
+
self.assertTrue(callable(getattr(DBLeaderboardRepository, "save", None)))
|
|
36
|
+
self.assertTrue(callable(getattr(DBLeaderboardRepository, "get_latest", None)))
|
|
37
|
+
|
|
38
|
+
def test_input_repository_save_updates_scope_and_resolvable_at(self):
|
|
39
|
+
"""Regression: DBInputRepository.save() must update scope_jsonb and
|
|
40
|
+
resolvable_at on existing records, not just status/actuals/meta."""
|
|
41
|
+
source = inspect.getsource(DBInputRepository.save)
|
|
42
|
+
self.assertIn("scope_jsonb", source,
|
|
43
|
+
"save() must update scope_jsonb on existing records")
|
|
44
|
+
self.assertIn("resolvable_at", source,
|
|
45
|
+
"save() must update resolvable_at on existing records")
|
|
46
|
+
|
|
47
|
+
def test_prediction_repository_save_updates_resolvable_at(self):
|
|
48
|
+
"""Regression: DBPredictionRepository.save() must update resolvable_at
|
|
49
|
+
on existing records."""
|
|
50
|
+
source = inspect.getsource(DBPredictionRepository.save)
|
|
51
|
+
self.assertIn("existing.resolvable_at", source,
|
|
52
|
+
"save() must update resolvable_at on existing records")
|
|
53
|
+
|
|
54
|
+
def test_all_repository_save_methods_update_all_constructor_fields(self):
|
|
55
|
+
"""Regression: every save() method must update all fields it constructs,
|
|
56
|
+
except the primary key (id). Catches field omission bugs like the
|
|
57
|
+
scope_jsonb/resolvable_at issue."""
|
|
58
|
+
import re
|
|
59
|
+
from coordinator_node.db.repositories import (
|
|
60
|
+
DBCheckpointRepository, DBSnapshotRepository,
|
|
61
|
+
)
|
|
62
|
+
repos = [
|
|
63
|
+
("DBModelRepository", DBModelRepository),
|
|
64
|
+
("DBInputRepository", DBInputRepository),
|
|
65
|
+
("DBPredictionRepository", DBPredictionRepository),
|
|
66
|
+
("DBScoreRepository", DBScoreRepository),
|
|
67
|
+
("DBSnapshotRepository", DBSnapshotRepository),
|
|
68
|
+
("DBCheckpointRepository", DBCheckpointRepository),
|
|
69
|
+
]
|
|
70
|
+
for name, cls in repos:
|
|
71
|
+
source = inspect.getsource(cls.save)
|
|
72
|
+
# Fields assigned via row.X in constructor
|
|
73
|
+
row_fields = set(re.findall(r'(\w+)=row\.(\w+)', source))
|
|
74
|
+
constructor_fields = {f[0] for f in row_fields if f[0] != 'id'}
|
|
75
|
+
# Fields updated via existing.X = row.X
|
|
76
|
+
existing_fields = set(re.findall(r'existing\.(\w+)\s*=\s*row\.(\w+)', source))
|
|
77
|
+
update_fields = {f[0] for f in existing_fields}
|
|
78
|
+
|
|
79
|
+
missing = constructor_fields - update_fields
|
|
80
|
+
self.assertEqual(missing, set(),
|
|
81
|
+
f"{name}.save() creates fields {sorted(missing)} "
|
|
82
|
+
f"but doesn't update them on existing records")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
unittest.main()
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
|
|
3
|
-
from coordinator_node.db.repositories import (
|
|
4
|
-
DBInputRepository,
|
|
5
|
-
DBLeaderboardRepository,
|
|
6
|
-
DBModelRepository,
|
|
7
|
-
DBPredictionRepository,
|
|
8
|
-
DBScoreRepository,
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestRepositoryAPIs(unittest.TestCase):
|
|
13
|
-
def test_model_repository_has_required_methods(self):
|
|
14
|
-
self.assertTrue(callable(getattr(DBModelRepository, "fetch_all", None)))
|
|
15
|
-
self.assertTrue(callable(getattr(DBModelRepository, "save", None)))
|
|
16
|
-
|
|
17
|
-
def test_input_repository_has_required_methods(self):
|
|
18
|
-
self.assertTrue(callable(getattr(DBInputRepository, "save", None)))
|
|
19
|
-
self.assertTrue(callable(getattr(DBInputRepository, "find", None)))
|
|
20
|
-
|
|
21
|
-
def test_prediction_repository_has_required_methods(self):
|
|
22
|
-
self.assertTrue(callable(getattr(DBPredictionRepository, "save", None)))
|
|
23
|
-
self.assertTrue(callable(getattr(DBPredictionRepository, "save_all", None)))
|
|
24
|
-
self.assertTrue(callable(getattr(DBPredictionRepository, "find", None)))
|
|
25
|
-
|
|
26
|
-
def test_score_repository_has_required_methods(self):
|
|
27
|
-
self.assertTrue(callable(getattr(DBScoreRepository, "save", None)))
|
|
28
|
-
self.assertTrue(callable(getattr(DBScoreRepository, "find", None)))
|
|
29
|
-
|
|
30
|
-
def test_prediction_repository_has_query_scores_method(self):
|
|
31
|
-
self.assertTrue(callable(getattr(DBPredictionRepository, "query_scores", None)))
|
|
32
|
-
|
|
33
|
-
def test_leaderboard_repository_has_required_methods(self):
|
|
34
|
-
self.assertTrue(callable(getattr(DBLeaderboardRepository, "save", None)))
|
|
35
|
-
self.assertTrue(callable(getattr(DBLeaderboardRepository, "get_latest", None)))
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if __name__ == "__main__":
|
|
39
|
-
unittest.main()
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/schemas/README.md
RENAMED
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/scoring.py
RENAMED
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/challenge/starter_challenge/tracker.py
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
|
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/config/scheduled_prediction_configs.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/base/node/runtime_definitions/contracts.py
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/extensions/callable_resolver.py
RENAMED
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/extensions/default_callables.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/feeds/providers/__init__.py
RENAMED
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/feeds/providers/binance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/schemas/payload_contracts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/services/realtime_predict.py
RENAMED
|
File without changes
|
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/workers/checkpoint_worker.py
RENAMED
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/workers/feed_data_worker.py
RENAMED
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/coordinator_node/workers/predict_worker.py
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coordinator_node-0.1.2 → coordinator_node-0.1.4}/tests/test_coordinator_runtime_data_feeds.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|