Compare commits

...
2 Commits
Author SHA1 Message Date
Ross b9edf36103 continue fixing tests for scip 2023-05-28 09:01:35 +01:00
Ross 3f78ecc671 update a few things 2023-05-23 14:48:12 +01:00
11 changed files with 386 additions and 90 deletions
+3
View File
@@ -10,3 +10,6 @@ no twighlights.log
no weekends.log no weekends.log
logs/ logs/
.env/ .env/
*.db
web/rota
+1 -1
View File
@@ -17,7 +17,7 @@
"name": "Python: Run", "name": "Python: Run",
"type": "python", "type": "python",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/run.py", "program": "${workspaceFolder}/gen.py",
"console": "integratedTerminal" "console": "integratedTerminal"
}, },
] ]
+87 -23
View File
@@ -2,7 +2,7 @@ from calendar import week
import datetime import datetime
from distutils.log import debug from distutils.log import debug
import itertools 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 import time
from pydantic import BaseModel, Extra, constr from pydantic import BaseModel, Extra, constr
@@ -65,9 +65,21 @@ SHIFT_BOUNDS = {
"weekend_count": (0, 60), "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): class ShiftConstraint(BaseModel):
name: str name: VALID_SHIFT_CONSTRAINTS
options: bool | int | Dict | tuple = False options: bool | int | Dict | tuple = False
@@ -79,7 +91,7 @@ class SingleShift(BaseModel):
balance_across_groups (requires block assignment) balance_across_groups (requires block assignment)
postclear(2) / preclear(2) post / pre clear
require_remote_site_presence_week: require_remote_site_presence_week:
options: (site, required_number) options: (site, required_number)
@@ -180,9 +192,16 @@ class RotaBuilder(object):
SHIFT_BOUNDS=SHIFT_BOUNDS, SHIFT_BOUNDS=SHIFT_BOUNDS,
): ):
console.print(Panel(f"""[white] console.print(
Panel(
f"""[white]
{locals()} {locals()}
""", style="green", title="Generating Rota"), justify="left") """,
style="green",
title="Generating Rota",
),
justify="left",
)
self.SHIFT_BOUNDS = SHIFT_BOUNDS self.SHIFT_BOUNDS = SHIFT_BOUNDS
self.shifts: List[SingleShift] = [] # type List[SingleShift] 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_shift_balance_extra = use_shift_balance_extra
self.use_bank_holiday_extra = use_bank_holiday_extra self.use_bank_holiday_extra = use_bank_holiday_extra
self.workers: list[Worker] = [] self.workers: list[Worker] = []
# self.night_blocks = ["weekday", "weekend", "none"] # self.night_blocks = ["weekday", "weekend", "none"]
@@ -231,14 +249,16 @@ class RotaBuilder(object):
"avoid_shifts_by_grades": [], "avoid_shifts_by_grades": [],
} }
self.results = None self.results = None
self.ignore_valid_shifts = False self.ignore_valid_shifts = False
self.set_rota_dates(start_date, weeks_to_rota) 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 self.weeks_to_rota = weeks_to_rota
@@ -293,14 +313,17 @@ class RotaBuilder(object):
else: else:
self.opt = SolverFactory(solver) self.opt = SolverFactory(solver)
try:
console.print("Solving") console.print("Solving")
if use_neos: if use_neos:
solver_manager = SolverManagerFactory("neos") # Solve using neos server solver_manager = SolverManagerFactory("neos") # Solve using neos server
# results = solver_manager.solve(Rota.model, opt=opt, logfile="test.log") # results = solver_manager.solve(Rota.model, opt=opt, logfile="test.log")
results = solver_manager.solve( 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: else:
results = self.opt.solve( results = self.opt.solve(
@@ -311,7 +334,11 @@ class RotaBuilder(object):
# "threads": 10, # "threads": 10,
# }, # },
logfile="test.log", logfile="test.log",
keepfiles=True,
) )
except KeyboardInterrupt:
return
pass
self.results = results self.results = results
@@ -321,8 +348,8 @@ class RotaBuilder(object):
console.print(f"Attempting each shift individually") console.print(f"Attempting each shift individually")
self.solve_shifts_individually(options) self.solve_shifts_individually(options)
if not results.solver.status: # if not results.solver.status:
sys.exit(0) # sys.exit(0)
def solve_shifts_by_block(self, options, block_length: int, folder="block_shifts"): def solve_shifts_by_block(self, options, block_length: int, folder="block_shifts"):
shifts = self.shifts shifts = self.shifts
@@ -337,18 +364,22 @@ class RotaBuilder(object):
start_time = time.time() start_time = time.time()
start_date = original_start_date + datetime.timedelta(weeks=block) start_date = original_start_date + datetime.timedelta(weeks=block)
self.set_rota_dates(start_date=start_date, weeks_to_rota=block_length) 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.clear_shifts()
self.add_shifts(*shifts) self.add_shifts(*shifts)
self.build_and_solve(options) 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() end_time = time.time()
time_taken = end_time - start_time time_taken = end_time - start_time
outcomes[f"{start_date}_{self.weeks}"] = ( outcomes[f"{start_date}_{self.weeks}"] = (
self.results.solver.status, self.results.solver.status,
self.results.solver.termination_condition, self.results.solver.termination_condition,
time_taken time_taken,
) )
console.print(outcomes) console.print(outcomes)
@@ -380,6 +411,9 @@ class RotaBuilder(object):
solve=True, solve=True,
debug_if_fail: bool = False, debug_if_fail: bool = False,
): ):
self.run_start_time = datetime.datetime.now()
self.build_shifts() self.build_shifts()
self.build_workers() self.build_workers()
self.build_model() self.build_model()
@@ -387,6 +421,8 @@ class RotaBuilder(object):
if solve: if solve:
self.solve_model(options=options, debug_if_fail=debug_if_fail) self.solve_model(options=options, debug_if_fail=debug_if_fail)
self.run_end_time = datetime.datetime.now()
def build_model(self): def build_model(self):
# Initialize model # Initialize model
self.model = ConcreteModel() self.model = ConcreteModel()
@@ -1010,7 +1046,9 @@ class RotaBuilder(object):
# - self.model.night_per_site_t2[week, block, site] # - self.model.night_per_site_t2[week, block, site]
# == self.model.night_per_site[week, block, site] - 1 # == 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"): for shift in self.get_shifts_with_constraint("balance_across_groups"):
if site in shift.sites: if site in shift.sites:
block = shift.name block = shift.name
@@ -1122,7 +1160,9 @@ class RotaBuilder(object):
# Most of our constraints apply per worker # Most of our constraints apply per worker
for worker in self.workers: 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}") logging.debug(f"Generate worker constraints: {worker.name}")
if self.constraint_options["minimise_shift_diffs"]: if self.constraint_options["minimise_shift_diffs"]:
@@ -1148,7 +1188,9 @@ class RotaBuilder(object):
except ValueError: except ValueError:
# Occurs if there are no shifts within a defined block # Occurs if there are no shifts within a defined block
# TODO: test if this breaks (and we should check rathar than except) # 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 pass
for constraint_dict in self.constraint_options["avoid_shifts_by_grades"]: 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) # Balance shifts within required limits (set by balance_offset)
# If balance_offset is too restrictive (for the rota length) # If balance_offset is too restrictive (for the rota length)
# no solution will be possible # 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 ( if (
worker.site in shift.sites worker.site in shift.sites
): # Each site specfies which sites self.workers can fullfill it ): # 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 # 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) # t1+t2 approximates the absolute target (which otherwise requires a quadratic solver)
# TODO: quadratic implementation so perfect solutions will be chosen # 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"]: if self.constraint_options["balance_shifts_over_workers"]:
self.model.constraints.add( self.model.constraints.add(
self.model.worker_shift_count_t1[worker.id] self.model.worker_shift_count_t1[worker.id]
@@ -1548,10 +1595,15 @@ class RotaBuilder(object):
self.constraint_options["max_night_frequency"] self.constraint_options["max_night_frequency"]
): ):
# Ignore excluded weeks # 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 continue
if self.get_shifts_with_constraint("night"): if self.get_shifts_with_constraint("night"):
# Prevent nights more than once every n weeks # Prevent nights more than once every n weeks
self.model.constraints.add( self.model.constraints.add(
@@ -1834,7 +1886,9 @@ class RotaBuilder(object):
# def get_pre_may(week_days, max_pre) # 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] 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( for constraint_shift in self.get_shifts_with_constraints(
"pre", "pre",
): ):
@@ -2968,6 +3025,13 @@ class RotaBuilder(object):
timetable = [] 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( timetable.append(
f"<h2>Rota start date: {self.start_date.isoformat()} ({self.weeks[-1]} weeks)</h2>" f"<h2>Rota start date: {self.start_date.isoformat()} ({self.weeks[-1]} weeks)</h2>"
) )
+1 -1
View File
@@ -77,7 +77,7 @@ class Worker(BaseModel):
not_available_to_work: list[NotAvailableToWork] = [] not_available_to_work: list[NotAvailableToWork] = []
pref_not_to_work: list[PreferenceNotToWork] = [] pref_not_to_work: list[PreferenceNotToWork] = []
work_requests: list[WorkRequests] = [] 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 = {} previous_shifts: dict = {}
shift_balance_extra: dict = {} shift_balance_extra: dict = {}
bank_holiday_extra: int = 0 bank_holiday_extra: int = 0
+2 -6
View File
@@ -1,7 +1,3 @@
parallel/minnthreads = 5 #limits/gap = 0.005
parallel/maxnthreads = 10
limits/solutions = -1 #limits/time = 100
limits/gap = 0
limits/time = 20000
+15 -13
View File
@@ -1,5 +1,6 @@
import datetime import datetime
import pytest import pytest
from pytest import approx
from rota.shifts import NoWorkers, RotaBuilder, SingleShift, days from rota.shifts import NoWorkers, RotaBuilder, SingleShift, days
from rota.workers import Worker from rota.workers import Worker
@@ -61,9 +62,9 @@ class TestBalancing:
for worker_name in shift_summary: for worker_name in shift_summary:
worker_shifts = shift_summary[worker_name] worker_shifts = shift_summary[worker_name]
worker = Rota.get_worker_by_name(worker_name) #worker = Rota.get_worker_by_name(worker_name)
assert worker_shifts["a"] in (22, 23, 24, 25) assert worker_shifts["a"] in (approx(22), approx(23), approx(24), approx(25))
assert worker_shifts["b"] in (22, 23, 24, 25) assert worker_shifts["b"] in (approx(22), approx(23), approx(24), approx(25))
def test_weighted_shift_balancing(self): def test_weighted_shift_balancing(self):
Rota = generate_basic_rota(20) Rota = generate_basic_rota(20)
@@ -74,10 +75,10 @@ class TestBalancing:
name="a", name="a",
length=12.5, length=12.5,
days=days, days=days,
balance_weighting=4, balance_weighting=5,
workers_required=1, workers_required=1,
force_as_block=False, force_as_block=False,
constraint=[{"name": "preclear2"}, {"name": "postclear2"}], constraint=[{"name": "pre","options": "2"}, {"name": "post","options": "2"}],
), ),
SingleShift( SingleShift(
sites=("group1", "group2"), sites=("group1", "group2"),
@@ -105,7 +106,8 @@ class TestBalancing:
worker_shifts = shift_summary[worker_name] worker_shifts = shift_summary[worker_name]
worker = Rota.get_worker_by_name(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) # assert worker_shifts["b"] in (22, 23, 24, 25)
def test_weighted_shift_balancing2(self): def test_weighted_shift_balancing2(self):
@@ -127,7 +129,7 @@ class TestBalancing:
name="b", name="b",
length=12.5, length=12.5,
days=days, days=days,
balance_weighting=4, balance_weighting=8,
workers_required=1, workers_required=1,
force_as_block=False, 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") Rota.export_rota_to_html("basic_balancing_weighted_shifts2")
shift_summary = Rota.get_shift_summary_dict() shift_summary = Rota.get_shift_summary_dict()
@@ -151,7 +153,7 @@ class TestBalancing:
worker = Rota.get_worker_by_name(worker_name) worker = Rota.get_worker_by_name(worker_name)
# assert worker_shifts["a"] in (46,47,48) # assert worker_shifts["a"] in (46,47,48)
# assert worker_shifts["c"] 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): def test_weighted_shift_balancing3(self):
Rota = generate_basic_rota(23) Rota = generate_basic_rota(23)
@@ -172,7 +174,7 @@ class TestBalancing:
name="b", name="b",
length=12.5, length=12.5,
days=days, days=days,
balance_weighting=4, balance_weighting=1,
workers_required=1, workers_required=1,
force_as_block=False, force_as_block=False,
), ),
@@ -181,13 +183,13 @@ class TestBalancing:
name="c", name="c",
length=12.5, length=12.5,
days=days[0], days=days[0],
balance_weighting=4, balance_weighting=8,
workers_required=1, workers_required=1,
force_as_block=False, 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") Rota.export_rota_to_html("basic_balancing_weighted_shifts3")
shift_summary = Rota.get_shift_summary_dict() shift_summary = Rota.get_shift_summary_dict()
@@ -197,7 +199,7 @@ class TestBalancing:
worker = Rota.get_worker_by_name(worker_name) worker = Rota.get_worker_by_name(worker_name)
# assert worker_shifts["a"] in (46,47,48) # assert worker_shifts["a"] in (46,47,48)
assert worker_shifts["c"] in (7, 8) 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): def test_weighted_shift_balancing4(self):
Rota = generate_basic_rota(10) Rota = generate_basic_rota(10)
+58
View File
@@ -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
+15
View File
@@ -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
View File
@@ -1,16 +1,21 @@
import uvicorn import uvicorn
from fastapi import FastAPI, Query
from pydantic import BaseModel from pydantic import BaseModel
from fastapi_sqlalchemy import DBSessionMiddleware, db from fastapi_sqlalchemy import DBSessionMiddleware, db
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
from rota.shifts import SingleShift, ShiftConstraint from rota.shifts import SingleShift, ShiftConstraint
from web.models import SingleShift as ModelSingleShift from models import SingleShift as ModelSingleShift
from web.models import ShiftConstraint as ModelShiftConstraint #from models import ShiftConstraint as ModelShiftConstraint
from database import SessionLocal, engine
import crud, models, schemas
#from rota.wokers #from rota.wokers
@@ -19,21 +24,89 @@ load_dotenv('../.env')
app = FastAPI() 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): #app.add_middleware(DBSessionMiddleware, db_url="sqlite:///test.db")
db_shift = ModelSingleShift(name=shift.name, length=shift.length) models.Base.metadata.create_all(bind=engine)
db.session.add(db_shift)
db.session.commit() @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 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("/") @app.get("/")
+64 -11
View File
@@ -1,31 +1,84 @@
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Float, Boolean from sqlalchemy import (
from sqlalchemy.ext.declarative import declarative_base Column,
DateTime,
ForeignKey,
Integer,
String,
Float,
Boolean,
JSON,
)
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy.sql import func 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): class SingleShift(Base):
__tablename__ = 'single_shift' __tablename__ = "single_shift"
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
name = Column(String) name = Column(String)
length = Column(Float) length = Column(Float)
days = Column(JSON)
sites = Column(JSON)
balance_offset = Column(Float, nullable=True) balance_offset = Column(Float, nullable=True)
balance_weighting = Column(Float) balance_weighting = Column(Float)
workers_required = Column(Float) workers_required = Column(Float)
rota_on_nwds = Column(Boolean)
assign_as_block = Column(Boolean) assign_as_block = Column(Boolean)
force_as_block = Column(Boolean) force_as_block = Column(Boolean)
force_as_block_unless_nwd = Column(Boolean)
hard_constrain_shift = Column(Boolean) hard_constrain_shift = Column(Boolean)
bank_holidays_only = 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): #class SiteShiftLinkTable(Base):
__tablename__ = 'shift_constraint' # __tablename__ = "site_shift_link_table"
id = Column(Integer, primary_key=True) # shift_id = Column(Integer, ForeignKey("single_shift.id"), primary_key=True)
name = Column(String) # site_id = Column(Integer, ForeignKey("sites.id"), primary_key=True)
options = Column(Integer, nullable=True)
#class Site(Base):
# __tablename__ = "sites"
# id = Column(Integer, primary_key=True)
# name = Column(String)
#
# shifts = relationship(SingleShift, secondary="site_shift_link_table")
+32
View File
@@ -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