From 3f78ecc67152e49b6821021f411c12324dea7096 Mon Sep 17 00:00:00 2001 From: Ross Date: Tue, 23 May 2023 14:48:12 +0100 Subject: [PATCH] update a few things --- .gitignore | 5 ++- .vscode/launch.json | 2 +- rota/shifts.py | 45 +++++++++++--------- rota/workers.py | 2 +- scip.set | 8 +--- web/crud.py | 58 +++++++++++++++++++++++++ web/database.py | 15 +++++++ web/main.py | 101 ++++++++++++++++++++++++++++++++++++++------ web/models.py | 77 +++++++++++++++++++++++++++------ web/schemas.py | 32 ++++++++++++++ 10 files changed, 290 insertions(+), 55 deletions(-) create mode 100644 web/crud.py create mode 100644 web/database.py create mode 100644 web/schemas.py diff --git a/.gitignore b/.gitignore index ab16c17..99c1a1b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,7 @@ no nights.log no twighlights.log no weekends.log logs/ -.env/ \ No newline at end of file +.env/ + +*.db +web/rota diff --git a/.vscode/launch.json b/.vscode/launch.json index 4670127..b604ae0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ "name": "Python: Run", "type": "python", "request": "launch", - "program": "${workspaceFolder}/run.py", + "program": "${workspaceFolder}/gen.py", "console": "integratedTerminal" }, ] diff --git a/rota/shifts.py b/rota/shifts.py index 38b4b06..8670d94 100644 --- a/rota/shifts.py +++ b/rota/shifts.py @@ -286,7 +286,7 @@ class RotaBuilder(object): ): console.print("Setting up solver") - solver = "scip" + #solver = "scip" if solver == "scip": self.opt = SolverFactory(solver, executable="scip") @@ -295,23 +295,28 @@ class RotaBuilder(object): - 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" - ) - else: - results = self.opt.solve( - self.model, - tee=True, - options=options, - # options={ - # "threads": 10, - # }, - logfile="test.log", - ) + 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" + ) + else: + results = self.opt.solve( + self.model, + tee=True, + options=options, + # options={ + # "threads": 10, + # }, + logfile="test.log", + keepfiles=True, + ) + except KeyboardInterrupt: + return + pass self.results = results @@ -321,8 +326,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 diff --git a/rota/workers.py b/rota/workers.py index 962004d..2468cf9 100644 --- a/rota/workers.py +++ b/rota/workers.py @@ -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 diff --git a/scip.set b/scip.set index f482c61..9cea0e6 100644 --- a/scip.set +++ b/scip.set @@ -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 diff --git a/web/crud.py b/web/crud.py new file mode 100644 index 0000000..4eb245f --- /dev/null +++ b/web/crud.py @@ -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 diff --git a/web/database.py b/web/database.py new file mode 100644 index 0000000..03021c2 --- /dev/null +++ b/web/database.py @@ -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() \ No newline at end of file diff --git a/web/main.py b/web/main.py index cdc070e..5bb419b 100644 --- a/web/main.py +++ b/web/main.py @@ -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.get("/shifts/") -async def shifts(): - shifts = db.session.query(ModelSingleShift).all() +@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) - 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("/") diff --git a/web/models.py b/web/models.py index 56f25df..d09b7ad 100644 --- a/web/models.py +++ b/web/models.py @@ -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' - id = Column(Integer, primary_key=True, index=True) + __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") diff --git a/web/schemas.py b/web/schemas.py new file mode 100644 index 0000000..4a0ea6e --- /dev/null +++ b/web/schemas.py @@ -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