build(agent): new-agents#a6e6ec iteration
This commit is contained in:
parent
8dd26142d1
commit
31d39c5339
|
|
@ -1,5 +1,13 @@
|
|||
# ELAC-Plan Agents
|
||||
|
||||
Architecture and MVP changes
|
||||
- Added a FastAPI-based MVP API (elac_plan.api) exposing /problems, /status, and /deltas endpoints to drive the LocalProblem -> PlanDelta flow locally.
|
||||
- In-memory MVP storage for LocalProblem and PlanDelta with a toy LocalSolver and two starter adapters (NBBOFeedAdapter, BrokerGatewayAdapter).
|
||||
- Public Python package surface updated: elac_plan.api is importable via elac_plan.api.app (FastAPI app), and re-exported from elac_plan for convenience.
|
||||
- Tests: Added tests/test_api.py to validate problem creation and status endpoints using FastAPI TestClient.
|
||||
- Test runner: test.sh added to execute pytest and python -m build for packaging validation.
|
||||
- Documentation: README updated with MVP overview (next iteration to expand with Phase 1 governance, phase 2 cross-venue demos, etc.).
|
||||
|
||||
Architecture overview:
|
||||
- Primitives: LocalProblem, SharedVariables, DualVariables, PlanDelta, Governance/AuditLog
|
||||
- Adapters: NBBOFeedAdapter (data feed), BrokerGatewayAdapter (execution gateway)
|
||||
|
|
|
|||
21
README.md
21
README.md
|
|
@ -1 +1,22 @@
|
|||
# ELAC-Plan (Edge-Latency Aware Cross-Venue Execution Planner)
|
||||
|
||||
This repository implements a production-oriented MVP of ELAC-Plan: an edge-native federation for cross-venue execution planning with privacy-preserving signals and deterministic delta-sync.
|
||||
|
||||
- Primitives map to canonical primitives used across adapters:
|
||||
- Objects: LocalProblem
|
||||
- Morphisms: SharedVariables
|
||||
- DualVariables: DualVariables
|
||||
- PlanDelta: PlanDelta
|
||||
- Governance/AuditLog: GovernanceAuditLog (stubbed for now)
|
||||
- MVP adapters:
|
||||
- NBBOFeedAdapter: translates NBBO-like feeds into SharedVariables
|
||||
- BrokerGatewayAdapter: simulates broker API publishing of PlanDelta
|
||||
- Solver: LocalSolver (toy solver)
|
||||
- API: FastAPI app exposing /problems and /status to drive LocalProblem -> PlanDelta flow
|
||||
- Packaging: Python packaging metadata in pyproject.toml; tests with pytest; build via python -m build
|
||||
|
||||
How to run (dev):
|
||||
- Run tests: ./test.sh
|
||||
- Run API locally (example): uvicorn elac_plan.api:app --reload
|
||||
|
||||
This is a minimal but production-conscious MVP intended to be extended by adding governance ledger, secure aggregation, and real adapters in subsequent iterations.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from .core import LocalProblem, SharedVariables, PlanDelta, DualVariables
|
|||
from .solver import LocalSolver
|
||||
from .adapters import NBBOFeedAdapter, BrokerGatewayAdapter
|
||||
from .dsl import DSLObject, DSLMorphisms, DSLDualVariables, DSLPlanDelta
|
||||
from .api import app as api_app
|
||||
|
||||
__all__ = [
|
||||
"LocalProblem",
|
||||
|
|
@ -17,4 +18,5 @@ __all__ = [
|
|||
"DSLMorphisms",
|
||||
"DSLDualVariables",
|
||||
"DSLPlanDelta",
|
||||
"api_app",
|
||||
]
|
||||
|
|
|
|||
154
elac_plan/api.py
154
elac_plan/api.py
|
|
@ -1,17 +1,45 @@
|
|||
from __future__ import annotations
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
"""ELAC-Plan API
|
||||
|
||||
Simple FastAPI-based MVP to exercise LocalProblem -> PlanDelta flow with two starter adapters.
|
||||
This is intentionally lightweight and in-process to bootstrap cross-venue planning without
|
||||
external dependencies beyond FastAPI, Pydantic (via existing dataclasses), and httpx for tests.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
import json
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .core import LocalProblem, PlanDelta, SharedVariables, DualVariables
|
||||
from .solver import LocalSolver
|
||||
from .adapters import NBBOFeedAdapter, BrokerGatewayAdapter
|
||||
from .dsl import (
|
||||
DSLObject,
|
||||
DSLMorphisms,
|
||||
DSLDualVariables,
|
||||
DSLPlanDelta,
|
||||
to_dsl_object,
|
||||
to_dsl_morphisms,
|
||||
to_dsl_dual,
|
||||
to_dsl_plan,
|
||||
)
|
||||
|
||||
app = FastAPI(title="ELAC-Plan API")
|
||||
|
||||
app = FastAPI(title="ELAC-Plan MVP API", version="0.1.0")
|
||||
|
||||
# In-memory stores for MVP: problems and deltas
|
||||
_problems: Dict[str, LocalProblem] = {}
|
||||
_deltas: Dict[str, PlanDelta] = {}
|
||||
|
||||
# Starter adapters (could be used by endpoints or in a factory pattern)
|
||||
_nbbo = NBBOFeedAdapter(contract_id="default-contract")
|
||||
_broker = BrokerGatewayAdapter()
|
||||
|
||||
_solver = LocalSolver()
|
||||
_feed = NBBOFeedAdapter()
|
||||
_broker = BrokerGatewayAdapter()
|
||||
|
||||
|
||||
class LocalProblemInput(BaseModel):
|
||||
|
|
@ -19,27 +47,113 @@ class LocalProblemInput(BaseModel):
|
|||
asset: str
|
||||
venue: str
|
||||
objective: str
|
||||
constraints: Dict[str, Any]
|
||||
constraints: Optional[Dict[str, Any]] = None
|
||||
price_target: float
|
||||
tolerance: float
|
||||
|
||||
|
||||
@app.post("/problems")
|
||||
def create_problem(p: LocalProblemInput) -> Dict[str, Any]:
|
||||
problem = LocalProblem(
|
||||
id=p.id,
|
||||
asset=p.asset,
|
||||
venue=p.venue,
|
||||
objective=p.objective,
|
||||
constraints=p.constraints,
|
||||
price_target=p.price_target,
|
||||
tolerance=p.tolerance,
|
||||
@app.post("/problems", response_model=Dict[str, Any])
|
||||
def create_problem(problem: LocalProblemInput):
|
||||
# Build a LocalProblem from input and run the toy solver to produce a delta
|
||||
lp = LocalProblem(
|
||||
id=problem.id,
|
||||
asset=problem.asset,
|
||||
venue=problem.venue,
|
||||
objective=problem.objective,
|
||||
constraints=problem.constraints or {},
|
||||
price_target=problem.price_target,
|
||||
tolerance=problem.tolerance,
|
||||
)
|
||||
delta = _solver.solve(problem)
|
||||
|
||||
_problems[lp.id] = lp
|
||||
delta = _solver.solve(lp)
|
||||
_deltas[delta.contract_id] = delta
|
||||
|
||||
# Simulate publishing via broker gateway (stringized payload) for MVP
|
||||
_broker.publish(delta)
|
||||
return {"problem": problem.to_json(), "delta": delta.to_json()}
|
||||
|
||||
return {
|
||||
"problem_id": lp.id,
|
||||
"delta": json.dumps(delta.delta),
|
||||
"delta_contract": delta.contract_id,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/status")
|
||||
def status() -> Dict[str, Any]:
|
||||
return {"status": "ELAC-Plan API running"}
|
||||
def status():
|
||||
return {
|
||||
"problems_count": len(_problems),
|
||||
"deltas_count": len(_deltas),
|
||||
"latest_delta_contract": max(_deltas.keys()) if _deltas else None,
|
||||
"server_time": datetime.utcnow().isoformat() + "Z",
|
||||
}
|
||||
|
||||
|
||||
@app.get("/problems/{problem_id}")
|
||||
def get_problem(problem_id: str):
|
||||
lp = _problems.get(problem_id)
|
||||
if not lp:
|
||||
raise HTTPException(status_code=404, detail="problem not found")
|
||||
return {
|
||||
"id": lp.id,
|
||||
"asset": lp.asset,
|
||||
"venue": lp.venue,
|
||||
"objective": lp.objective,
|
||||
"price_target": lp.price_target,
|
||||
"tolerance": lp.tolerance,
|
||||
"constraints": lp.constraints,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/deltas/{contract_id}")
|
||||
def get_delta(contract_id: str):
|
||||
delta = _deltas.get(contract_id)
|
||||
if not delta:
|
||||
raise HTTPException(status_code=404, detail="delta not found")
|
||||
return {
|
||||
"contract_id": delta.contract_id,
|
||||
"delta": delta.delta,
|
||||
"timestamp": delta.timestamp,
|
||||
"author": delta.author,
|
||||
"privacy_budget": delta.privacy_budget,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/dsl/{problem_id}")
|
||||
def get_problem_dsl(problem_id: str):
|
||||
# Expose a minimal DSL representation for a given problem, including
|
||||
# LocalProblem -> DSLObject, SharedVariables, DualVariables, and PlanDelta.
|
||||
lp = _problems.get(problem_id)
|
||||
if not lp:
|
||||
raise HTTPException(status_code=404, detail="problem not found")
|
||||
|
||||
# Build a deterministic contract_id as used by the MVP
|
||||
contract_id = f"{lp.id}:{lp.venue}"
|
||||
# Rehydrate a minimal SharedVariables and DualVariables for DSL exposure
|
||||
sw = SharedVariables(variables={}, version=1, contract_id=contract_id)
|
||||
dv = DualVariables(shadow_prices={}, version=1, contract_id=contract_id)
|
||||
|
||||
# Resolve latest delta for the problem if available
|
||||
delta = _deltas.get(contract_id)
|
||||
if delta is None:
|
||||
# create a minimal placeholder delta to showcase DSL shape
|
||||
delta = PlanDelta(
|
||||
delta={},
|
||||
timestamp="",
|
||||
author="unknown",
|
||||
contract_id=contract_id,
|
||||
privacy_budget=0.0,
|
||||
)
|
||||
|
||||
# Transform to DSL structures
|
||||
dsl_lp = to_dsl_object(lp)
|
||||
dsl_sw = to_dsl_morphisms(sw)
|
||||
dsl_dv = to_dsl_dual(dv)
|
||||
dsl_plan = to_dsl_plan(delta)
|
||||
|
||||
return {
|
||||
"lp": dsl_lp.to_dict(),
|
||||
"shared_variables": dsl_sw.to_dict(),
|
||||
"dual_variables": dsl_dv.to_dict(),
|
||||
"plan_delta": dsl_plan.to_dict(),
|
||||
}
|
||||
|
|
|
|||
9
test.sh
9
test.sh
|
|
@ -1,12 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Run unit tests and packaging build to ensure MVP integrity
|
||||
echo "Installing package in editable mode..."
|
||||
pip install -e .
|
||||
export PYTHONPATH=$(pwd)
|
||||
|
||||
echo "Running tests..."
|
||||
pytest -q
|
||||
|
||||
echo "Building package..."
|
||||
python3 -m build
|
||||
echo "OK: tests and build completed."
|
||||
|
||||
echo "All tests passed and package built."
|
||||
exit 0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import json
|
||||
import pytest
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from elac_plan.api import app
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_create_problem_smoke():
|
||||
payload = {
|
||||
"id": "probe-001",
|
||||
"asset": "AAPL",
|
||||
"venue": "XNAS",
|
||||
"objective": "minimize_spread",
|
||||
"constraints": {"max_slippage": 0.5},
|
||||
"price_target": 150.0,
|
||||
"tolerance": 0.2,
|
||||
}
|
||||
resp = client.post("/problems", json=payload)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["problem_id"] == payload["id"]
|
||||
assert "delta" in data
|
||||
# validate delta payload json can be parsed back
|
||||
delta = json.loads(data["delta"])
|
||||
assert delta["action"] == "place_order"
|
||||
|
||||
|
||||
def test_status_endpoint():
|
||||
resp = client.get("/status")
|
||||
assert resp.status_code == 200
|
||||
body = resp.json()
|
||||
assert "problems_count" in body
|
||||
assert "deltas_count" in body
|
||||
Loading…
Reference in New Issue