diff --git a/chatgpt/app_tests.py b/chatgpt/app_tests.py deleted file mode 100644 index 258a8a6..0000000 --- a/chatgpt/app_tests.py +++ /dev/null @@ -1,10 +0,0 @@ -def test_create_demand(client): - response = client.post('/demand', json={'floor': 3}) - assert response.status_code == 201 - assert response.get_json() == {'message': 'Demand created'} - - -def test_create_state(client): - response = client.post('/state', json={'floor': 5, 'vacant': True}) - assert response.status_code == 201 - assert response.get_json() == {'message': 'State created'} diff --git a/chatgpt/db.sql b/chatgpt/db.sql deleted file mode 100644 index 1555ffe..0000000 --- a/chatgpt/db.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE elevator_demands ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - floor INTEGER -); - -CREATE TABLE elevator_states ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - floor INTEGER, - vacant BOOLEAN -); diff --git a/chatgpt/main.py b/chatgpt/main.py deleted file mode 100644 index 7f97d98..0000000 --- a/chatgpt/main.py +++ /dev/null @@ -1,43 +0,0 @@ -from flask import Flask, request, jsonify -from flask_sqlalchemy import SQLAlchemy -from datetime import datetime - -app = Flask(__name__) -app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///elevator.db' -db = SQLAlchemy(app) - - -class ElevatorDemand(db.Model): - id = db.Column(db.Integer, primary_key=True) - timestamp = db.Column(db.DateTime, default=datetime.utcnow) - floor = db.Column(db.Integer, nullable=False) - - -class ElevatorState(db.Model): - id = db.Column(db.Integer, primary_key=True) - timestamp = db.Column(db.DateTime, default=datetime.utcnow) - floor = db.Column(db.Integer, nullable=False) - vacant = db.Column(db.Boolean, nullable=False) - - -@app.route('/demand', methods=['POST']) -def create_demand(): - data = request.get_json() - new_demand = ElevatorDemand(floor=data['floor']) - db.session.add(new_demand) - db.session.commit() - return jsonify({'message': 'Demand created'}), 201 - - -@app.route('/state', methods=['POST']) -def create_state(): - data = request.get_json() - new_state = ElevatorState(floor=data['floor'], vacant=data['vacant']) - db.session.add(new_state) - db.session.commit() - return jsonify({'message': 'State created'}), 201 - - -if __name__ == '__main__': - db.create_all() - app.run(debug=True) diff --git a/chatgpt/requirements.txt b/chatgpt/requirements.txt deleted file mode 100644 index 14d1bb0..0000000 --- a/chatgpt/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -Flask==2.0.2 -Flask-SQLAlchemy==2.5.1 -pytest==6.2.5 -pytest-flask==1.2.0 diff --git a/solution/__pycache__/app_test.cpython-311-pytest-8.3.3.pyc b/solution/__pycache__/app_test.cpython-311-pytest-8.3.3.pyc new file mode 100644 index 0000000..2f69ae9 Binary files /dev/null and b/solution/__pycache__/app_test.cpython-311-pytest-8.3.3.pyc differ diff --git a/solution/__pycache__/main.cpython-311.pyc b/solution/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000..085408e Binary files /dev/null and b/solution/__pycache__/main.cpython-311.pyc differ diff --git a/solution/__pycache__/models.cpython-311.pyc b/solution/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..0141d2f Binary files /dev/null and b/solution/__pycache__/models.cpython-311.pyc differ diff --git a/solution/__pycache__/views.cpython-311.pyc b/solution/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000..5794ff6 Binary files /dev/null and b/solution/__pycache__/views.cpython-311.pyc differ diff --git a/solution/app/__init__.py b/solution/app/__init__.py new file mode 100644 index 0000000..083ffbc --- /dev/null +++ b/solution/app/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from flask import Flask +from flask_sqlalchemy import SQLAlchemy + + +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///elevator.db' +db = SQLAlchemy(app) + + +from app import models + +with app.app_context(): + db.create_all() + +from app import views + diff --git a/solution/app/__pycache__/__init__.cpython-311.pyc b/solution/app/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..a0fceaa Binary files /dev/null and b/solution/app/__pycache__/__init__.cpython-311.pyc differ diff --git a/solution/app/__pycache__/crud.cpython-311.pyc b/solution/app/__pycache__/crud.cpython-311.pyc new file mode 100644 index 0000000..5108d38 Binary files /dev/null and b/solution/app/__pycache__/crud.cpython-311.pyc differ diff --git a/solution/app/__pycache__/models.cpython-311.pyc b/solution/app/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..81ed853 Binary files /dev/null and b/solution/app/__pycache__/models.cpython-311.pyc differ diff --git a/solution/app/__pycache__/validations.cpython-311.pyc b/solution/app/__pycache__/validations.cpython-311.pyc new file mode 100644 index 0000000..97991c8 Binary files /dev/null and b/solution/app/__pycache__/validations.cpython-311.pyc differ diff --git a/solution/app/__pycache__/views.cpython-311.pyc b/solution/app/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000..c1d585e Binary files /dev/null and b/solution/app/__pycache__/views.cpython-311.pyc differ diff --git a/solution/app/crud.py b/solution/app/crud.py new file mode 100644 index 0000000..b3c621f --- /dev/null +++ b/solution/app/crud.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +from typing import Dict,Any,Optional +from datetime import datetime +from app import db +from app.models import ElevatorDemand,ElevatorState +from app.validations import validate_floor, validate_state + +def create_demand(data:Dict[str,int]) -> None: + ''' + Create a new instance of ElevatorDemand. Raise ValueError if the validation fails + + Parameters + ---------- + data : Dict[str,int] + contains the actual and target floor. + + Raises + ------ + ValueError + If the floor validation fails. + + Returns + ------- + None + + ''' + actual_floor = data.get("actual_floor") + target_floor = data.get("target_floor") + if not validate_floor(actual_floor) or not validate_floor(target_floor): + raise ValueError("Floor is not an integer") + new_demand = ElevatorDemand(actual_floor=actual_floor,target_floor=target_floor) + db.session.add(new_demand) + db.session.commit() + return + + +def create_state(data:Dict[str,Any]) -> None: + ''' + Create a new instance of ElevatorState. Raise ValueError if the vacant or floor + validation fails + + Parameters + ---------- + data : Dict[str,Any] + Contains the actual floor of the elevator and if it is vacant. + + Raises + ------ + ValueError + If the floor or state validation fails + + Returns + ------- + None + + ''' + floor = data.get("floor") + vacant = data.get("vacant") + if not validate_floor(floor): + raise ValueError("Floor is not an integer") + if not validate_state(vacant): + raise ValueError("Vacant invalid") + new_state = ElevatorState(floor=floor, vacant=vacant) + db.session.add(new_state) + db.session.commit() + return + +def change_state(state_id:int, data:Dict[str,str]) -> Optional[ElevatorState]: + ''' + Triggers the change of state adding end_time and duration to a ElevatorState instance + + Parameters + ---------- + state_id : int + ID of the state. + data : Dict[str,str] + Contains the end_time in isoformat. + + Returns + ------- + Optional[ElevatorState] + Returns an instance of ElevatorState if exist for state_id else None. + + ''' + end_time = data.get("end_time") + end_time = datetime.fromisoformat(end_time) + state = ElevatorState.query.filter_by(id=state_id).first() + if not state: + return state + state.end_time = end_time + state.duration_sec = int((end_time - state.start_time).total_seconds()) + + #Business Rule: Idle Relocation + state.relocation_flag = state.duration_sec > 300 + db.session.commit() + return state + + + + + + + + \ No newline at end of file diff --git a/solution/app/models.py b/solution/app/models.py new file mode 100644 index 0000000..60e9322 --- /dev/null +++ b/solution/app/models.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from app import db +from datetime import datetime + +class ElevatorDemand(db.Model): + id = db.Column(db.Integer, primary_key=True) + timestamp = db.Column(db.DateTime, default=datetime.utcnow) + target_floor = db.Column(db.Integer, nullable=False) + actual_floor = db.Column(db.Integer, nullable=False) + + +class ElevatorState(db.Model): + id = db.Column(db.Integer, primary_key=True) + timestamp = db.Column(db.DateTime, default=datetime.utcnow) + floor = db.Column(db.Integer) + vacant = db.Column(db.Boolean) + start_time = db.Column(db.DateTime, default=datetime.utcnow) + end_time = db.Column(db.DateTime,nullable=True) + duration_sec = db.Column(db.Integer,nullable=True) + relocation_flag = db.Column(db.Boolean,nullable=True) + + diff --git a/solution/app/validations.py b/solution/app/validations.py new file mode 100644 index 0000000..891e4c2 --- /dev/null +++ b/solution/app/validations.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + + +def validate_floor(floor:int) -> bool: + ''' + Validate if the floor is an integer. More restrictions could be implemented + in particular cases (e.g. top and bottom floor) + + Parameters + ---------- + floor : int + The floor number. + + Returns + ------- + bool + True if the floor is an integer. + + ''' + if isinstance(floor, int): + return True + return False + +def validate_state(vacant:bool) -> bool: + ''' + Validate if the elevator is vacant + + Parameters + ---------- + vacant : bool + True if the elevator is vacant. + + Returns + ------- + bool + True if vacant is a bool, else False. + + ''' + if isinstance(vacant, bool): + return True + return False + diff --git a/solution/app/views.py b/solution/app/views.py new file mode 100644 index 0000000..92ab75b --- /dev/null +++ b/solution/app/views.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from flask import request +from app import app +from app.crud import create_demand,create_state,change_state + +@app.route('/demand', methods=['POST']) +def create_demand_view(): + try: + data = request.json + create_demand(data) + return {'message': 'Demand created'}, 201 + except Exception as e: + return {'message':f'{e.str()}'},404 + +@app.route('/state', methods=['POST']) +def create_state_view(): + try: + data = request.json + create_state(data) + return {'message': 'State created'}, 201 + except Exception as e: + return {'message':f'{e.str()}'},404 + +@app.route('/state//end',methods=["PATCH"]) +def change_state_view(state_id): + data = request.json + result = change_state(state_id,data) + if not result: + return {"message":"No state found"},404 + return {"message":"State changed"},201 + + \ No newline at end of file diff --git a/solution/app_test.py b/solution/app_test.py new file mode 100644 index 0000000..86752cf --- /dev/null +++ b/solution/app_test.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +import pytest +from datetime import datetime,timedelta +from app import app,db +from app.models import ElevatorState + +@pytest.fixture +def client(): + app.config['TESTING'] = True + with app.app_context(): + db.drop_all() + db.create_all() + with app.test_client() as client: + yield client + + +def test_create_demand(client): + response = client.post('/demand',json={"actual_floor":3,"target_floor":6}) + assert response.status_code == 201 + assert response.json == {"message":"Demand created"} + +def test_create_state(client): + response = client.post('/state',json={"floor":2,"vacant":True}) + assert response.status_code == 201 + assert response.json == {"message":"State created"} + +def test_relocation(client): + response = client.post('/state',json={"floor":2,"vacant":True}) + state_id = 1 + end_time = datetime.utcnow() + timedelta(seconds=100) + response = client.patch(f'/state/{state_id}/end',json={"end_time":f"{end_time.isoformat()}"}) + assert response.status_code == 201 + assert response.json == {"message":"State changed"} + state = ElevatorState.query.filter_by(id=state_id).first() + assert not state.relocation_flag + +def test_idle_relocation(client): + response = client.post('/state',json={"floor":2,"vacant":True}) + state_id = 1 + end_time = datetime.utcnow() + timedelta(seconds=600) + response = client.patch(f'/state/{state_id}/end',json={"end_time":f"{end_time.isoformat()}"}) + assert response.status_code == 201 + assert response.json == {"message":"State changed"} + state = ElevatorState.query.filter_by(id=state_id).first() + assert state.relocation_flag + + diff --git a/solution/main.py b/solution/main.py new file mode 100644 index 0000000..1994bb7 --- /dev/null +++ b/solution/main.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from app import app + + +if __name__ == '__main__': + app.run(debug=True)