Implement distribution management API and UI forms; add distribution model and tests

This commit is contained in:
2025-10-20 18:51:23 +02:00
parent 4533a4c166
commit 0b19a93e0d
10 changed files with 229 additions and 2 deletions

View File

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

View File

@@ -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
View 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}>"

View File

@@ -5,3 +5,4 @@ psycopg2-binary
python-dotenv python-dotenv
pytest pytest
pytest-cov pytest-cov
jinja2

44
routes/distributions.py Normal file
View 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
View 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
View 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>

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

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

View 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)