Implement distribution management API and UI forms; add distribution model and tests
This commit is contained in:
@@ -35,7 +35,7 @@ The project is organized into several key directories:
|
|||||||
- `config/`: Configuration files and settings.
|
- `config/`: Configuration files and settings.
|
||||||
- `middleware/`: Custom middleware for request/response processing.
|
- `middleware/`: Custom middleware for request/response processing.
|
||||||
- `tests/`: Unit and integration tests.
|
- `tests/`: Unit and integration tests.
|
||||||
- `templates/`: HTML templates (if applicable).
|
- `templates/`: Jinja2 HTML templates for server-side rendering.
|
||||||
- `docs/`: Documentation files.
|
- `docs/`: Documentation files.
|
||||||
|
|
||||||
Key files include:
|
Key files include:
|
||||||
|
|||||||
4
main.py
4
main.py
@@ -1,3 +1,5 @@
|
|||||||
|
from routes.distributions import router as distributions_router
|
||||||
|
from routes.ui import router as ui_router
|
||||||
from routes.parameters import router as parameters_router
|
from routes.parameters import router as parameters_router
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware import Middleware
|
from fastapi.middleware import Middleware
|
||||||
@@ -15,3 +17,5 @@ app.middleware("http")(validate_json)
|
|||||||
# Include API routers
|
# Include API routers
|
||||||
app.include_router(scenarios_router)
|
app.include_router(scenarios_router)
|
||||||
app.include_router(parameters_router)
|
app.include_router(parameters_router)
|
||||||
|
app.include_router(distributions_router)
|
||||||
|
app.include_router(ui_router)
|
||||||
|
|||||||
14
models/distribution.py
Normal file
14
models/distribution.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from sqlalchemy import Column, Integer, String, JSON
|
||||||
|
from config.database import Base
|
||||||
|
|
||||||
|
|
||||||
|
class Distribution(Base):
|
||||||
|
__tablename__ = "distribution"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
distribution_type = Column(String, nullable=False)
|
||||||
|
parameters = Column(JSON, nullable=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Distribution id={self.id} name={self.name} type={self.distribution_type}>"
|
||||||
@@ -4,4 +4,5 @@ sqlalchemy
|
|||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
python-dotenv
|
python-dotenv
|
||||||
pytest
|
pytest
|
||||||
pytest-cov
|
pytest-cov
|
||||||
|
jinja2
|
||||||
44
routes/distributions.py
Normal file
44
routes/distributions.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from typing import List
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from config.database import SessionLocal
|
||||||
|
from models.distribution import Distribution
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api/distributions", tags=["Distributions"])
|
||||||
|
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
class DistributionCreate(BaseModel):
|
||||||
|
name: str
|
||||||
|
distribution_type: str
|
||||||
|
parameters: dict
|
||||||
|
|
||||||
|
|
||||||
|
class DistributionRead(DistributionCreate):
|
||||||
|
id: int
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/", response_model=DistributionRead)
|
||||||
|
async def create_distribution(dist: DistributionCreate, db: Session = Depends(get_db)):
|
||||||
|
db_dist = Distribution(**dist.dict())
|
||||||
|
db.add(db_dist)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_dist)
|
||||||
|
return db_dist
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=List[DistributionRead])
|
||||||
|
async def list_distributions(db: Session = Depends(get_db)):
|
||||||
|
dists = db.query(Distribution).all()
|
||||||
|
return dists
|
||||||
20
routes/ui.py
Normal file
20
routes/ui.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
# Set up Jinja2 templates directory
|
||||||
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/ui/scenarios", response_class=HTMLResponse)
|
||||||
|
async def scenario_form(request: Request):
|
||||||
|
"""Render the scenario creation form."""
|
||||||
|
return templates.TemplateResponse("ScenarioForm.html", {"request": request})
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/ui/parameters", response_class=HTMLResponse)
|
||||||
|
async def parameter_form(request: Request):
|
||||||
|
"""Render the parameter input form."""
|
||||||
|
return templates.TemplateResponse("ParameterInput.html", {"request": request})
|
||||||
25
templates/Dashboard.html
Normal file
25
templates/Dashboard.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>CalMiner Dashboard</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Simulation Results Dashboard</h1>
|
||||||
|
<div id="report-summary"></div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script>
|
||||||
|
// TODO: fetch summary report and render charts
|
||||||
|
async function loadReport() {
|
||||||
|
const response = await fetch('/api/reporting/summary', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify([])
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
document.getElementById('report-summary').innerText = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
loadReport();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
50
templates/ParameterInput.html
Normal file
50
templates/ParameterInput.html
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Process Parameters Input</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Enter Parameters for a Scenario</h1>
|
||||||
|
<form id="parameter-form">
|
||||||
|
<label
|
||||||
|
>Scenario ID:
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="scenario_id"
|
||||||
|
id="scenario_id"
|
||||||
|
required /></label
|
||||||
|
><br />
|
||||||
|
<label>Name: <input type="text" name="name" id="name" required /></label
|
||||||
|
><br />
|
||||||
|
<label
|
||||||
|
>Value:
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="value"
|
||||||
|
id="value"
|
||||||
|
step="any"
|
||||||
|
required /></label
|
||||||
|
><br />
|
||||||
|
<button type="submit">Add Parameter</button>
|
||||||
|
</form>
|
||||||
|
<div id="result"></div>
|
||||||
|
<script>
|
||||||
|
document
|
||||||
|
.getElementById("parameter-form")
|
||||||
|
.addEventListener("submit", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const scenario_id = document.getElementById("scenario_id").value;
|
||||||
|
const name = document.getElementById("name").value;
|
||||||
|
const value = parseFloat(document.getElementById("value").value);
|
||||||
|
const resp = await fetch("/api/parameters/", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ scenario_id, name, value }),
|
||||||
|
});
|
||||||
|
const data = await resp.json();
|
||||||
|
document.getElementById("result").innerText = JSON.stringify(data);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
templates/ScenarioForm.html
Normal file
36
templates/ScenarioForm.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Scenario Management</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Create a New Scenario</h1>
|
||||||
|
<form id="scenario-form">
|
||||||
|
<label>Name: <input type="text" name="name" id="name" required /></label
|
||||||
|
><br />
|
||||||
|
<label
|
||||||
|
>Description:
|
||||||
|
<input type="text" name="description" id="description" /></label
|
||||||
|
><br />
|
||||||
|
<button type="submit">Create Scenario</button>
|
||||||
|
</form>
|
||||||
|
<div id="result"></div>
|
||||||
|
<script>
|
||||||
|
document
|
||||||
|
.getElementById("scenario-form")
|
||||||
|
.addEventListener("submit", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const name = document.getElementById("name").value;
|
||||||
|
const description = document.getElementById("description").value;
|
||||||
|
const resp = await fetch("/api/scenarios/", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ name, description }),
|
||||||
|
});
|
||||||
|
const data = await resp.json();
|
||||||
|
document.getElementById("result").innerText = JSON.stringify(data);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
33
tests/unit/test_distribution.py
Normal file
33
tests/unit/test_distribution.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from main import app
|
||||||
|
from config.database import Base, engine
|
||||||
|
from models.distribution import Distribution
|
||||||
|
|
||||||
|
# Setup and teardown
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
Base.metadata.drop_all(bind=engine)
|
||||||
|
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_and_list_distribution():
|
||||||
|
# Create distribution
|
||||||
|
payload = {"name": "NormalDist", "distribution_type": "normal",
|
||||||
|
"parameters": {"mu": 0, "sigma": 1}}
|
||||||
|
resp = client.post("/api/distributions/", json=payload)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.json()
|
||||||
|
assert data["name"] == "NormalDist"
|
||||||
|
|
||||||
|
# List distributions
|
||||||
|
resp2 = client.get("/api/distributions/")
|
||||||
|
assert resp2.status_code == 200
|
||||||
|
data2 = resp2.json()
|
||||||
|
assert any(d["name"] == "NormalDist" for d in data2)
|
||||||
Reference in New Issue
Block a user