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.
|
||||
- `middleware/`: Custom middleware for request/response processing.
|
||||
- `tests/`: Unit and integration tests.
|
||||
- `templates/`: HTML templates (if applicable).
|
||||
- `templates/`: Jinja2 HTML templates for server-side rendering.
|
||||
- `docs/`: Documentation files.
|
||||
|
||||
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 fastapi import FastAPI
|
||||
from fastapi.middleware import Middleware
|
||||
@@ -15,3 +17,5 @@ app.middleware("http")(validate_json)
|
||||
# Include API routers
|
||||
app.include_router(scenarios_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}>"
|
||||
@@ -5,3 +5,4 @@ psycopg2-binary
|
||||
python-dotenv
|
||||
pytest
|
||||
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