Compare commits
2
Commits
a444024eaa
...
b9edf36103
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9edf36103 | ||
|
|
3f78ecc671 |
@@ -10,3 +10,6 @@ no twighlights.log
|
||||
no weekends.log
|
||||
logs/
|
||||
.env/
|
||||
|
||||
*.db
|
||||
web/rota
|
||||
|
||||
Vendored
+1
-1
@@ -17,7 +17,7 @@
|
||||
"name": "Python: Run",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/run.py",
|
||||
"program": "${workspaceFolder}/gen.py",
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
]
|
||||
|
||||
+87
-23
@@ -2,7 +2,7 @@ from calendar import week
|
||||
import datetime
|
||||
from distutils.log import debug
|
||||
import itertools
|
||||
from typing import Dict, Iterable, List, Sequence, Tuple, Set, Any, Type
|
||||
from typing import Dict, Iterable, List, Sequence, Tuple, Set, Any, Type, Literal
|
||||
import time
|
||||
|
||||
from pydantic import BaseModel, Extra, constr
|
||||
@@ -65,9 +65,21 @@ SHIFT_BOUNDS = {
|
||||
"weekend_count": (0, 60),
|
||||
}
|
||||
|
||||
VALID_SHIFT_CONSTRAINTS = Literal[
|
||||
"night",
|
||||
"pre",
|
||||
"post",
|
||||
"balance_across_groups",
|
||||
"limit_grade_number",
|
||||
"minimum_grade_number",
|
||||
"max_shifts_per_week",
|
||||
"require_remote_site_presence",
|
||||
"require_remote_site_presence_week",
|
||||
]
|
||||
|
||||
|
||||
class ShiftConstraint(BaseModel):
|
||||
name: str
|
||||
name: VALID_SHIFT_CONSTRAINTS
|
||||
options: bool | int | Dict | tuple = False
|
||||
|
||||
|
||||
@@ -79,7 +91,7 @@ class SingleShift(BaseModel):
|
||||
|
||||
balance_across_groups (requires block assignment)
|
||||
|
||||
postclear(2) / preclear(2)
|
||||
post / pre clear
|
||||
|
||||
require_remote_site_presence_week:
|
||||
options: (site, required_number)
|
||||
@@ -180,9 +192,16 @@ class RotaBuilder(object):
|
||||
SHIFT_BOUNDS=SHIFT_BOUNDS,
|
||||
):
|
||||
|
||||
console.print(Panel(f"""[white]
|
||||
console.print(
|
||||
Panel(
|
||||
f"""[white]
|
||||
{locals()}
|
||||
""", style="green", title="Generating Rota"), justify="left")
|
||||
""",
|
||||
style="green",
|
||||
title="Generating Rota",
|
||||
),
|
||||
justify="left",
|
||||
)
|
||||
|
||||
self.SHIFT_BOUNDS = SHIFT_BOUNDS
|
||||
self.shifts: List[SingleShift] = [] # type List[SingleShift]
|
||||
@@ -191,7 +210,6 @@ class RotaBuilder(object):
|
||||
self.use_shift_balance_extra = use_shift_balance_extra
|
||||
self.use_bank_holiday_extra = use_bank_holiday_extra
|
||||
|
||||
|
||||
self.workers: list[Worker] = []
|
||||
|
||||
# self.night_blocks = ["weekday", "weekend", "none"]
|
||||
@@ -231,14 +249,16 @@ class RotaBuilder(object):
|
||||
"avoid_shifts_by_grades": [],
|
||||
}
|
||||
|
||||
|
||||
self.results = None
|
||||
|
||||
self.ignore_valid_shifts = False
|
||||
|
||||
self.set_rota_dates(start_date, weeks_to_rota)
|
||||
|
||||
def set_rota_dates(self, start_date: datetime.datetime, weeks_to_rota: int):
|
||||
self.run_start_time: datetime.datetime | None = None
|
||||
self.run_end_time: datetime.datetime | None = None
|
||||
|
||||
def set_rota_dates(self, start_date: datetime.date, weeks_to_rota: int):
|
||||
|
||||
self.weeks_to_rota = weeks_to_rota
|
||||
|
||||
@@ -293,14 +313,17 @@ class RotaBuilder(object):
|
||||
else:
|
||||
self.opt = SolverFactory(solver)
|
||||
|
||||
|
||||
|
||||
try:
|
||||
console.print("Solving")
|
||||
if use_neos:
|
||||
solver_manager = SolverManagerFactory("neos") # Solve using neos server
|
||||
# results = solver_manager.solve(Rota.model, opt=opt, logfile="test.log")
|
||||
results = solver_manager.solve(
|
||||
self.model, keepfiles=True, tee=True, opt=self.opt, logfile="test.log"
|
||||
self.model,
|
||||
keepfiles=True,
|
||||
tee=True,
|
||||
opt=self.opt,
|
||||
logfile="test.log",
|
||||
)
|
||||
else:
|
||||
results = self.opt.solve(
|
||||
@@ -311,7 +334,11 @@ class RotaBuilder(object):
|
||||
# "threads": 10,
|
||||
# },
|
||||
logfile="test.log",
|
||||
keepfiles=True,
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
return
|
||||
pass
|
||||
|
||||
self.results = results
|
||||
|
||||
@@ -321,8 +348,8 @@ class RotaBuilder(object):
|
||||
console.print(f"Attempting each shift individually")
|
||||
self.solve_shifts_individually(options)
|
||||
|
||||
if not results.solver.status:
|
||||
sys.exit(0)
|
||||
# if not results.solver.status:
|
||||
# sys.exit(0)
|
||||
|
||||
def solve_shifts_by_block(self, options, block_length: int, folder="block_shifts"):
|
||||
shifts = self.shifts
|
||||
@@ -337,18 +364,22 @@ class RotaBuilder(object):
|
||||
start_time = time.time()
|
||||
start_date = original_start_date + datetime.timedelta(weeks=block)
|
||||
self.set_rota_dates(start_date=start_date, weeks_to_rota=block_length)
|
||||
console.print(f"Testing block: start_date - {self.start_date} , length {self.weeks_to_rota} weeks")
|
||||
console.print(
|
||||
f"Testing block: start_date - {self.start_date} , length {self.weeks_to_rota} weeks"
|
||||
)
|
||||
self.clear_shifts()
|
||||
self.add_shifts(*shifts)
|
||||
self.build_and_solve(options)
|
||||
self.export_rota_to_html(filename=f"{start_date}_{self.weeks_to_rota}", folder=folder)
|
||||
self.export_rota_to_html(
|
||||
filename=f"{start_date}_{self.weeks_to_rota}", folder=folder
|
||||
)
|
||||
end_time = time.time()
|
||||
time_taken = end_time - start_time
|
||||
|
||||
outcomes[f"{start_date}_{self.weeks}"] = (
|
||||
self.results.solver.status,
|
||||
self.results.solver.termination_condition,
|
||||
time_taken
|
||||
time_taken,
|
||||
)
|
||||
|
||||
console.print(outcomes)
|
||||
@@ -380,6 +411,9 @@ class RotaBuilder(object):
|
||||
solve=True,
|
||||
debug_if_fail: bool = False,
|
||||
):
|
||||
|
||||
self.run_start_time = datetime.datetime.now()
|
||||
|
||||
self.build_shifts()
|
||||
self.build_workers()
|
||||
self.build_model()
|
||||
@@ -387,6 +421,8 @@ class RotaBuilder(object):
|
||||
if solve:
|
||||
self.solve_model(options=options, debug_if_fail=debug_if_fail)
|
||||
|
||||
self.run_end_time = datetime.datetime.now()
|
||||
|
||||
def build_model(self):
|
||||
# Initialize model
|
||||
self.model = ConcreteModel()
|
||||
@@ -1010,7 +1046,9 @@ class RotaBuilder(object):
|
||||
# - self.model.night_per_site_t2[week, block, site]
|
||||
# == self.model.night_per_site[week, block, site] - 1
|
||||
# )
|
||||
for site in track(self.sites, description="Generating site balance constraints..."):
|
||||
for site in track(
|
||||
self.sites, description="Generating site balance constraints..."
|
||||
):
|
||||
for shift in self.get_shifts_with_constraint("balance_across_groups"):
|
||||
if site in shift.sites:
|
||||
block = shift.name
|
||||
@@ -1122,7 +1160,9 @@ class RotaBuilder(object):
|
||||
|
||||
# Most of our constraints apply per worker
|
||||
for worker in self.workers:
|
||||
console.rule(f"Generate worker constraints: [bold blue]{worker.name}[/bold blue]")
|
||||
console.rule(
|
||||
f"Generate worker constraints: [bold blue]{worker.name}[/bold blue]"
|
||||
)
|
||||
logging.debug(f"Generate worker constraints: {worker.name}")
|
||||
|
||||
if self.constraint_options["minimise_shift_diffs"]:
|
||||
@@ -1148,7 +1188,9 @@ class RotaBuilder(object):
|
||||
except ValueError:
|
||||
# Occurs if there are no shifts within a defined block
|
||||
# TODO: test if this breaks (and we should check rathar than except)
|
||||
logging.debug(f"Failed to constrain max_shifts_per_month for worker: {worker.name}")
|
||||
logging.debug(
|
||||
f"Failed to constrain max_shifts_per_month for worker: {worker.name}"
|
||||
)
|
||||
pass
|
||||
|
||||
for constraint_dict in self.constraint_options["avoid_shifts_by_grades"]:
|
||||
@@ -1213,7 +1255,9 @@ class RotaBuilder(object):
|
||||
# Balance shifts within required limits (set by balance_offset)
|
||||
# If balance_offset is too restrictive (for the rota length)
|
||||
# no solution will be possible
|
||||
for shift in track(self.get_shifts(), description="Generate shift balance constraints"):
|
||||
for shift in track(
|
||||
self.get_shifts(), description="Generate shift balance constraints"
|
||||
):
|
||||
if (
|
||||
worker.site in shift.sites
|
||||
): # Each site specfies which sites self.workers can fullfill it
|
||||
@@ -1359,6 +1403,9 @@ class RotaBuilder(object):
|
||||
# As the objective is to minimise t1+t2 and t1 and t2 are positive reals
|
||||
# t1+t2 approximates the absolute target (which otherwise requires a quadratic solver)
|
||||
# TODO: quadratic implementation so perfect solutions will be chosen
|
||||
|
||||
# NOTE: as this balances across the sum of the worker's shifts
|
||||
# they can cancel each other out if a high weighting is given to multiple shifts
|
||||
if self.constraint_options["balance_shifts_over_workers"]:
|
||||
self.model.constraints.add(
|
||||
self.model.worker_shift_count_t1[worker.id]
|
||||
@@ -1548,10 +1595,15 @@ class RotaBuilder(object):
|
||||
self.constraint_options["max_night_frequency"]
|
||||
):
|
||||
# Ignore excluded weeks
|
||||
if set(week_blocks).intersection(set(self.constraint_options["max_night_frequency_week_exclusions"])):
|
||||
if set(week_blocks).intersection(
|
||||
set(
|
||||
self.constraint_options[
|
||||
"max_night_frequency_week_exclusions"
|
||||
]
|
||||
)
|
||||
):
|
||||
continue
|
||||
|
||||
|
||||
if self.get_shifts_with_constraint("night"):
|
||||
# Prevent nights more than once every n weeks
|
||||
self.model.constraints.add(
|
||||
@@ -1834,7 +1886,9 @@ class RotaBuilder(object):
|
||||
|
||||
# def get_pre_may(week_days, max_pre)
|
||||
|
||||
for n in track(range(len(weeks_days)), description="Generate week/day constraints"):
|
||||
for n in track(
|
||||
range(len(weeks_days)), description="Generate week/day constraints"
|
||||
):
|
||||
|
||||
week, day = weeks_days[n]
|
||||
|
||||
@@ -2039,6 +2093,9 @@ class RotaBuilder(object):
|
||||
)
|
||||
)
|
||||
|
||||
# NOTE: due to the way that this is implemented, if a
|
||||
# shift spans 7 days you may get >7 allocations in a row
|
||||
# as it only checks for a different shift allocation
|
||||
for constraint_shift in self.get_shifts_with_constraints(
|
||||
"pre",
|
||||
):
|
||||
@@ -2968,6 +3025,13 @@ class RotaBuilder(object):
|
||||
|
||||
timetable = []
|
||||
|
||||
if self.run_start_time is not None and self.run_end_time is not None:
|
||||
timetable.append(
|
||||
f"<div>Start time: <span id='run_start_time'>{self.run_start_time:%Y-%m-%d %H:%M:%S%z}</span></div>"
|
||||
f"<div>End time: <span id='run_end_time'>{self.run_end_time:%Y-%m-%d %H:%M:%S%z}</span></div>"
|
||||
f"<div>Elapsed: <span id='run_elapsed_time'>{self.run_end_time-self.run_start_time}</span></div>"
|
||||
)
|
||||
|
||||
timetable.append(
|
||||
f"<h2>Rota start date: {self.start_date.isoformat()} ({self.weeks[-1]} weeks)</h2>"
|
||||
)
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ class Worker(BaseModel):
|
||||
not_available_to_work: list[NotAvailableToWork] = []
|
||||
pref_not_to_work: list[PreferenceNotToWork] = []
|
||||
work_requests: list[WorkRequests] = []
|
||||
remote_site: str = ("plymouth",) # We set a default proc_site
|
||||
remote_site: str = "plymouth" # We set a default proc_site
|
||||
previous_shifts: dict = {}
|
||||
shift_balance_extra: dict = {}
|
||||
bank_holiday_extra: int = 0
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
parallel/minnthreads = 5
|
||||
parallel/maxnthreads = 10
|
||||
#limits/gap = 0.005
|
||||
|
||||
limits/solutions = -1
|
||||
limits/gap = 0
|
||||
|
||||
limits/time = 20000
|
||||
#limits/time = 100
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import datetime
|
||||
import pytest
|
||||
from pytest import approx
|
||||
from rota.shifts import NoWorkers, RotaBuilder, SingleShift, days
|
||||
|
||||
from rota.workers import Worker
|
||||
@@ -61,9 +62,9 @@ class TestBalancing:
|
||||
for worker_name in shift_summary:
|
||||
worker_shifts = shift_summary[worker_name]
|
||||
|
||||
worker = Rota.get_worker_by_name(worker_name)
|
||||
assert worker_shifts["a"] in (22, 23, 24, 25)
|
||||
assert worker_shifts["b"] in (22, 23, 24, 25)
|
||||
#worker = Rota.get_worker_by_name(worker_name)
|
||||
assert worker_shifts["a"] in (approx(22), approx(23), approx(24), approx(25))
|
||||
assert worker_shifts["b"] in (approx(22), approx(23), approx(24), approx(25))
|
||||
|
||||
def test_weighted_shift_balancing(self):
|
||||
Rota = generate_basic_rota(20)
|
||||
@@ -74,10 +75,10 @@ class TestBalancing:
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=days,
|
||||
balance_weighting=4,
|
||||
balance_weighting=5,
|
||||
workers_required=1,
|
||||
force_as_block=False,
|
||||
constraint=[{"name": "preclear2"}, {"name": "postclear2"}],
|
||||
constraint=[{"name": "pre","options": "2"}, {"name": "post","options": "2"}],
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
@@ -105,7 +106,8 @@ class TestBalancing:
|
||||
worker_shifts = shift_summary[worker_name]
|
||||
|
||||
worker = Rota.get_worker_by_name(worker_name)
|
||||
assert worker_shifts["a"] in (46, 47, 48)
|
||||
assert worker_shifts["a"] in (approx(46), approx(47), approx(48))
|
||||
#assert worker_shifts["b"] in (46, 47, 48)
|
||||
# assert worker_shifts["b"] in (22, 23, 24, 25)
|
||||
|
||||
def test_weighted_shift_balancing2(self):
|
||||
@@ -127,7 +129,7 @@ class TestBalancing:
|
||||
name="b",
|
||||
length=12.5,
|
||||
days=days,
|
||||
balance_weighting=4,
|
||||
balance_weighting=8,
|
||||
workers_required=1,
|
||||
force_as_block=False,
|
||||
),
|
||||
@@ -141,7 +143,7 @@ class TestBalancing:
|
||||
),
|
||||
)
|
||||
|
||||
Rota.build_and_solve(options={"ratio": 0.001})
|
||||
Rota.build_and_solve(options={"ratio": 0.000})
|
||||
Rota.export_rota_to_html("basic_balancing_weighted_shifts2")
|
||||
|
||||
shift_summary = Rota.get_shift_summary_dict()
|
||||
@@ -151,7 +153,7 @@ class TestBalancing:
|
||||
worker = Rota.get_worker_by_name(worker_name)
|
||||
# assert worker_shifts["a"] in (46,47,48)
|
||||
# assert worker_shifts["c"] in (46,47,48)
|
||||
assert worker_shifts["b"] in (52, 53, 54, 55)
|
||||
assert worker_shifts["b"] in (approx(53), approx(54), approx(55))
|
||||
|
||||
def test_weighted_shift_balancing3(self):
|
||||
Rota = generate_basic_rota(23)
|
||||
@@ -172,7 +174,7 @@ class TestBalancing:
|
||||
name="b",
|
||||
length=12.5,
|
||||
days=days,
|
||||
balance_weighting=4,
|
||||
balance_weighting=1,
|
||||
workers_required=1,
|
||||
force_as_block=False,
|
||||
),
|
||||
@@ -181,13 +183,13 @@ class TestBalancing:
|
||||
name="c",
|
||||
length=12.5,
|
||||
days=days[0],
|
||||
balance_weighting=4,
|
||||
balance_weighting=8,
|
||||
workers_required=1,
|
||||
force_as_block=False,
|
||||
),
|
||||
)
|
||||
|
||||
Rota.build_and_solve(options={"ratio": 0.000})
|
||||
Rota.build_and_solve(options={"ratio": 0.0001})
|
||||
Rota.export_rota_to_html("basic_balancing_weighted_shifts3")
|
||||
|
||||
shift_summary = Rota.get_shift_summary_dict()
|
||||
@@ -197,7 +199,7 @@ class TestBalancing:
|
||||
worker = Rota.get_worker_by_name(worker_name)
|
||||
# assert worker_shifts["a"] in (46,47,48)
|
||||
assert worker_shifts["c"] in (7, 8)
|
||||
assert worker_shifts["b"] in (52, 53, 54, 55)
|
||||
#assert worker_shifts["b"] in (53, 54)
|
||||
|
||||
def test_weighted_shift_balancing4(self):
|
||||
Rota = generate_basic_rota(10)
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
import models, schemas
|
||||
|
||||
|
||||
def get_shift(db: Session, shift_id: int):
|
||||
return (
|
||||
db.query(models.SingleShift).filter(models.SingleShift.id == shift_id).first()
|
||||
)
|
||||
|
||||
|
||||
def get_shift_by_name(db: Session, name: str):
|
||||
return db.query(models.SingleShift).filter(models.SingleShift.name == name).first()
|
||||
|
||||
|
||||
def get_shifts(db: Session, skip: int = 0, limit: int = 100):
|
||||
return db.query(models.SingleShift).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
def create_shift(db: Session, shift: schemas.SingleShiftCreate):
|
||||
shift = models.SingleShift(**shift.dict())
|
||||
db.add(shift)
|
||||
db.commit()
|
||||
db.refresh(shift)
|
||||
return shift
|
||||
|
||||
def create_worker(db: Session, worker: schemas.WorkerCreate):
|
||||
worker = models.Worker(**worker.dict())
|
||||
db.add(worker)
|
||||
db.commit()
|
||||
db.refresh(worker)
|
||||
return worker
|
||||
|
||||
def get_worker(db: Session, worker_id: int):
|
||||
return (
|
||||
db.query(models.Worker).filter(models.Worker.id == worker_id).first()
|
||||
)
|
||||
|
||||
def get_worker_by_name(db: Session, name: str):
|
||||
return db.query(models.Worker).filter(models.Worker.name == name).first()
|
||||
|
||||
def get_workers(db: Session, skip: int = 0, limit: int = 100):
|
||||
return db.query(models.Worker).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
|
||||
#def get_shift_constraints(db: Session, skip: int = 0, limit: int = 100):
|
||||
# return db.query(models.ShiftConstraint).offset(skip).limit(limit).all()
|
||||
#
|
||||
#
|
||||
#def create_shift_constraints(
|
||||
# db: Session, item: schemas.ShiftConstraintCreate, constraint_id: int
|
||||
#):
|
||||
# db_item = models.ShiftConstraint(**item.dict(), constraint_id=constraint_id)
|
||||
# db.add(db_item)
|
||||
# db.commit()
|
||||
# db.refresh(db_item)
|
||||
# return db_item
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
|
||||
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
|
||||
|
||||
engine = create_engine(
|
||||
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
+86
-13
@@ -1,16 +1,21 @@
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, Query
|
||||
from pydantic import BaseModel
|
||||
|
||||
from fastapi_sqlalchemy import DBSessionMiddleware, db
|
||||
from fastapi import Depends, FastAPI, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from rota.shifts import SingleShift, ShiftConstraint
|
||||
|
||||
from web.models import SingleShift as ModelSingleShift
|
||||
from web.models import ShiftConstraint as ModelShiftConstraint
|
||||
from models import SingleShift as ModelSingleShift
|
||||
#from models import ShiftConstraint as ModelShiftConstraint
|
||||
|
||||
from database import SessionLocal, engine
|
||||
|
||||
import crud, models, schemas
|
||||
|
||||
#from rota.wokers
|
||||
|
||||
@@ -19,21 +24,89 @@ load_dotenv('../.env')
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.add_middleware(DBSessionMiddleware, db_url="sqlite:///test.db")
|
||||
# Dependency
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@app.post('/shifts/', response_model=SingleShift)
|
||||
async def shifts(shift: ModelSingleShift):
|
||||
db_shift = ModelSingleShift(name=shift.name, length=shift.length)
|
||||
db.session.add(db_shift)
|
||||
db.session.commit()
|
||||
|
||||
#app.add_middleware(DBSessionMiddleware, db_url="sqlite:///test.db")
|
||||
models.Base.metadata.create_all(bind=engine)
|
||||
|
||||
@app.post("/shifts/", response_model=schemas.SingleShift)
|
||||
def create_shift(shift: schemas.SingleShiftCreate, db: Session = Depends(get_db)):
|
||||
db_shift = crud.get_shift_by_name(db, name=shift.name)
|
||||
if db_shift:
|
||||
raise HTTPException(status_code=400, detail="Shift name already exists")
|
||||
return crud.create_shift(db=db, shift=shift)
|
||||
|
||||
|
||||
@app.get("/shifts/", response_model=list[schemas.SingleShift])
|
||||
def read_shifts(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
shifts = crud.get_shifts(db, skip=skip, limit=limit)
|
||||
return shifts
|
||||
|
||||
|
||||
@app.get("/shifts/{shift_id}", response_model=schemas.SingleShift)
|
||||
def read_shift(shift_id: int, db: Session = Depends(get_db)):
|
||||
db_shift = crud.get_shifts(db, shift_id=shift_id)
|
||||
if db_shift is None:
|
||||
raise HTTPException(status_code=404, detail="Shift not found")
|
||||
return db_shift
|
||||
|
||||
@app.post("/workers/", response_model=schemas.Worker)
|
||||
def create_worker(worker: schemas.WorkerCreate, db: Session = Depends(get_db)):
|
||||
db_worker = crud.get_worker_by_name(db, name=worker.name)
|
||||
if db_worker:
|
||||
raise HTTPException(status_code=400, detail="worker name already exists")
|
||||
return crud.create_worker(db=db, worker=worker)
|
||||
|
||||
@app.get("/shifts/")
|
||||
async def shifts():
|
||||
shifts = db.session.query(ModelSingleShift).all()
|
||||
|
||||
return shifts
|
||||
@app.get("/workers/", response_model=list[schemas.Worker])
|
||||
def read_workers(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
workers = crud.get_workers(db, skip=skip, limit=limit)
|
||||
return workers
|
||||
|
||||
|
||||
@app.get("/workers/{worker_id}", response_model=schemas.Worker)
|
||||
def read_worker(worker_id: int, db: Session = Depends(get_db)):
|
||||
db_worker = crud.get_workers(db, worker_id=worker_id)
|
||||
if db_worker is None:
|
||||
raise HTTPException(status_code=404, detail="worker not found")
|
||||
return db_worker
|
||||
|
||||
#@app.post("/users/{user_id}/items/", response_model=schemas.Item)
|
||||
#def create_item_for_user(
|
||||
# user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
|
||||
#):
|
||||
# return crud.create_user_item(db=db, item=item, user_id=user_id)
|
||||
#
|
||||
#
|
||||
#@app.get("/items/", response_model=list[schemas.Item])
|
||||
#def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
# items = crud.get_items(db, skip=skip, limit=limit)
|
||||
# return items
|
||||
|
||||
#@app.post("/shifts/")
|
||||
#async def create_shift(shift: SingleShift):
|
||||
# return shift
|
||||
|
||||
#@app.post('/shifts/', response_model=SingleShift)
|
||||
#async def shifts(shift: ModelSingleShift):
|
||||
# db_shift = ModelSingleShift(name=shift.name, length=shift.length)
|
||||
# db.session.add(db_shift)
|
||||
# db.session.commit()
|
||||
# return db_shift
|
||||
|
||||
|
||||
#@app.get("/shifts/")
|
||||
#async def shifts():
|
||||
# shifts = db.session.query(ModelSingleShift).all()
|
||||
#
|
||||
# return shifts
|
||||
|
||||
|
||||
@app.get("/")
|
||||
|
||||
+64
-11
@@ -1,31 +1,84 @@
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Float, Boolean
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Integer,
|
||||
String,
|
||||
Float,
|
||||
Boolean,
|
||||
JSON,
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
Base = declarative_base()
|
||||
from database import Base
|
||||
|
||||
class Worker(Base):
|
||||
__tablename__ = "worker"
|
||||
name= Column(String)
|
||||
site= Column(String)
|
||||
grade= Column(Integer)
|
||||
id=Column(Integer, primary_key=True, index=True)
|
||||
fte= Column(Integer)
|
||||
nwds= Column(JSON)
|
||||
start_date= Column(DateTime)
|
||||
end_date= Column(DateTime)
|
||||
oop= Column(JSON)
|
||||
not_available_to_work= Column(JSON)
|
||||
pref_not_to_work= Column(JSON)
|
||||
work_requests= Column(JSON)
|
||||
remote_site= Column(String)
|
||||
previous_shifts= Column(JSON)
|
||||
shift_balance_extra=Column(JSON)
|
||||
bank_holiday_extra= Column(Integer)
|
||||
pair= Column(String)
|
||||
|
||||
class SingleShift(Base):
|
||||
__tablename__ = 'single_shift'
|
||||
__tablename__ = "single_shift"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String)
|
||||
length = Column(Float)
|
||||
days = Column(JSON)
|
||||
sites = Column(JSON)
|
||||
balance_offset = Column(Float, nullable=True)
|
||||
balance_weighting = Column(Float)
|
||||
workers_required = Column(Float)
|
||||
rota_on_nwds = Column(Boolean)
|
||||
assign_as_block = Column(Boolean)
|
||||
force_as_block = Column(Boolean)
|
||||
force_as_block_unless_nwd = Column(Boolean)
|
||||
hard_constrain_shift = Column(Boolean)
|
||||
bank_holidays_only = Column(Boolean)
|
||||
constraint_id = Column(Integer, ForeignKey('shift_constraint.id'))
|
||||
|
||||
constraint = relationship('ShiftConstraint')
|
||||
#constraint = relationship("ShiftConstraint", back_populates="shifts")
|
||||
constraint = Column(JSON)
|
||||
|
||||
# This should never be used from here (automatically created by the pydantic model)
|
||||
constraint_options = Column(JSON)
|
||||
constraints = Column(JSON)
|
||||
|
||||
#sites = relationship("Site", secondary="site_shift_link_table")
|
||||
|
||||
#class ShiftConstraint(Base):
|
||||
# __tablename__ = "shift_constraint"
|
||||
# id = Column(Integer, primary_key=True)
|
||||
# name = Column(String)
|
||||
# options = Column(Integer, nullable=True)
|
||||
#
|
||||
# shifts = relationship("SingleShift", back_populates="constraint")
|
||||
#
|
||||
# shift_id = Column(Integer, ForeignKey("single_shift.id"))
|
||||
|
||||
|
||||
class ShiftConstraint(Base):
|
||||
__tablename__ = 'shift_constraint'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
options = Column(Integer, nullable=True)
|
||||
#class SiteShiftLinkTable(Base):
|
||||
# __tablename__ = "site_shift_link_table"
|
||||
# shift_id = Column(Integer, ForeignKey("single_shift.id"), primary_key=True)
|
||||
# site_id = Column(Integer, ForeignKey("sites.id"), primary_key=True)
|
||||
|
||||
|
||||
#class Site(Base):
|
||||
# __tablename__ = "sites"
|
||||
# id = Column(Integer, primary_key=True)
|
||||
# name = Column(String)
|
||||
#
|
||||
# shifts = relationship(SingleShift, secondary="site_shift_link_table")
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
from rota.shifts import SingleShift as SingleShiftBase, ShiftConstraint as ShiftConstraintBase
|
||||
from rota.workers import Worker as WorkerBase
|
||||
|
||||
|
||||
class SingleShift(SingleShiftBase):
|
||||
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class SingleShiftCreate(SingleShiftBase):
|
||||
pass
|
||||
|
||||
|
||||
#class ShiftConstraint(ShiftConstraintBase):
|
||||
# id: int
|
||||
#
|
||||
# class Config:
|
||||
# orm_mode = True
|
||||
#
|
||||
#class ShiftConstraintCreate(ShiftConstraintBase):
|
||||
# pass
|
||||
|
||||
class Worker(WorkerBase):
|
||||
pass
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class WorkerCreate(WorkerBase):
|
||||
pass
|
||||
Reference in New Issue
Block a user