build(agent): new-agents#a6e6ec iteration

This commit is contained in:
agent-a6e6ec231c5f7801 2026-04-20 13:53:57 +02:00
parent 8dd26142d1
commit 31d39c5339
6 changed files with 207 additions and 24 deletions

View File

@ -1,5 +1,13 @@
# ELAC-Plan Agents # 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: Architecture overview:
- Primitives: LocalProblem, SharedVariables, DualVariables, PlanDelta, Governance/AuditLog - Primitives: LocalProblem, SharedVariables, DualVariables, PlanDelta, Governance/AuditLog
- Adapters: NBBOFeedAdapter (data feed), BrokerGatewayAdapter (execution gateway) - Adapters: NBBOFeedAdapter (data feed), BrokerGatewayAdapter (execution gateway)

View File

@ -1 +1,22 @@
# ELAC-Plan (Edge-Latency Aware Cross-Venue Execution Planner) # 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.

View File

@ -4,6 +4,7 @@ from .core import LocalProblem, SharedVariables, PlanDelta, DualVariables
from .solver import LocalSolver from .solver import LocalSolver
from .adapters import NBBOFeedAdapter, BrokerGatewayAdapter from .adapters import NBBOFeedAdapter, BrokerGatewayAdapter
from .dsl import DSLObject, DSLMorphisms, DSLDualVariables, DSLPlanDelta from .dsl import DSLObject, DSLMorphisms, DSLDualVariables, DSLPlanDelta
from .api import app as api_app
__all__ = [ __all__ = [
"LocalProblem", "LocalProblem",
@ -17,4 +18,5 @@ __all__ = [
"DSLMorphisms", "DSLMorphisms",
"DSLDualVariables", "DSLDualVariables",
"DSLPlanDelta", "DSLPlanDelta",
"api_app",
] ]

View File

@ -1,17 +1,45 @@
from __future__ import annotations 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 typing import Dict, Any, Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from .core import LocalProblem, PlanDelta, SharedVariables, DualVariables from .core import LocalProblem, PlanDelta, SharedVariables, DualVariables
from .solver import LocalSolver from .solver import LocalSolver
from .adapters import NBBOFeedAdapter, BrokerGatewayAdapter 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() _solver = LocalSolver()
_feed = NBBOFeedAdapter()
_broker = BrokerGatewayAdapter()
class LocalProblemInput(BaseModel): class LocalProblemInput(BaseModel):
@ -19,27 +47,113 @@ class LocalProblemInput(BaseModel):
asset: str asset: str
venue: str venue: str
objective: str objective: str
constraints: Dict[str, Any] constraints: Optional[Dict[str, Any]] = None
price_target: float price_target: float
tolerance: float tolerance: float
@app.post("/problems") @app.post("/problems", response_model=Dict[str, Any])
def create_problem(p: LocalProblemInput) -> Dict[str, Any]: def create_problem(problem: LocalProblemInput):
problem = LocalProblem( # Build a LocalProblem from input and run the toy solver to produce a delta
id=p.id, lp = LocalProblem(
asset=p.asset, id=problem.id,
venue=p.venue, asset=problem.asset,
objective=p.objective, venue=problem.venue,
constraints=p.constraints, objective=problem.objective,
price_target=p.price_target, constraints=problem.constraints or {},
tolerance=p.tolerance, 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) _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") @app.get("/status")
def status() -> Dict[str, Any]: def status():
return {"status": "ELAC-Plan API running"} 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(),
}

View File

@ -1,12 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail 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) export PYTHONPATH=$(pwd)
echo "Running tests..." echo "Running tests..."
pytest -q pytest -q
echo "Building package..." echo "Building package..."
python3 -m build python3 -m build
echo "OK: tests and build completed."
echo "All tests passed and package built."
exit 0

37
tests/test_api.py Normal file
View File

@ -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