diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..b4a361e Binary files /dev/null and b/.DS_Store differ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/devtest.iml b/.idea/devtest.iml new file mode 100644 index 0000000..6ecad54 --- /dev/null +++ b/.idea/devtest.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..146ab09 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..784420d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..901b03a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/chatgpt/.DS_Store b/chatgpt/.DS_Store new file mode 100644 index 0000000..931ec65 Binary files /dev/null and b/chatgpt/.DS_Store differ diff --git a/chatgpt/__pycache__/app_tests.cpython-312-pytest-6.2.5.pyc b/chatgpt/__pycache__/app_tests.cpython-312-pytest-6.2.5.pyc new file mode 100644 index 0000000..f91fa54 Binary files /dev/null and b/chatgpt/__pycache__/app_tests.cpython-312-pytest-6.2.5.pyc differ diff --git a/chatgpt/__pycache__/main.cpython-312.pyc b/chatgpt/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000..26390a7 Binary files /dev/null and b/chatgpt/__pycache__/main.cpython-312.pyc differ diff --git a/chatgpt/app_tests.py b/chatgpt/app_tests.py index 258a8a6..76bed03 100644 --- a/chatgpt/app_tests.py +++ b/chatgpt/app_tests.py @@ -1,10 +1,181 @@ +import pytest +from main import app, db + + +# Set up test client with in-memory SQLite database +@pytest.fixture +def client(): + app.config['TESTING'] = True + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' + with app.test_client() as client: + with app.app_context(): + db.create_all() + yield client + with app.app_context(): + db.drop_all() + + +# Tests elevator_demands def test_create_demand(client): + # Test creating a demand with a valid floor and dynamic timestamp response = client.post('/demand', json={'floor': 3}) + response_data = response.get_json() + demand_data = response_data['demand'] + actual_id = demand_data['id'] assert response.status_code == 201 - assert response.get_json() == {'message': 'Demand created'} + assert response.get_json() == {'message': 'Demand created', 'demand': { + 'id': actual_id, + 'timestamp': response.get_json()['demand']['timestamp'], # Handle dynamic timestamp + 'floor': 3, + 'day_of_week': response.get_json()['demand']['day_of_week'], + 'hour_of_day': response.get_json()['demand']['hour_of_day'] + }} + + +def test_create_demand_invalid_floor(client): + # Check that we get a 400 error if floor isn't an integer + response = client.post('/demand', json={'floor': 'hola'}) + assert response.status_code == 400 + assert response.get_json() == {'error': 'Floor must be an integer'} + + +def test_get_demand(client): + client.post('/demand', json={'floor': 1}) + response = client.get('/demand/1') + assert response.status_code == 200 + assert response.get_json()['floor'] == 1 + + +def test_get_demand_not_found(client): + # Test that requesting a non-existent demand returns 404 + response = client.get('/demand/hola') + assert response.status_code == 404 + +def test_get_all_demands(client): + client.post('/demand', json={'floor': 3}) + client.post('/demand', json={'floor': -1}) + response = client.get('/demands') + assert response.status_code == 200 + assert len(response.get_json()) == 2 + +def test_update_demand(client): + client.post('/demand', json={'floor': 1}) + response = client.put('/demand/1', json={'floor': 50}) + assert response.status_code == 200 + assert response.get_json()['demand']['floor'] == 50 + + +def test_update_demand_invalid_floor(client): + # Test that updating with an invalid floor gives a 400 error + client.post('/demand', json={'floor': 1}) + response = client.put('/demand/1', json={'floor': 'invalid'}) + assert response.status_code == 400 + assert response.get_json() == {'error': 'Floor must be an integer'} + + +def test_delete_demand(client): + client.post('/demand', json={'floor': -5}) + response = client.delete('/demand/1') + assert response.status_code == 200 + assert response.get_json() == {'message': 'Demand deleted'} + response = client.get('/demand/1') + assert response.status_code == 404 + + +# Test elevator_states def test_create_state(client): + client.post('/demand', json={'floor': -3}) # Set up a demand for other test response = client.post('/state', json={'floor': 5, 'vacant': True}) assert response.status_code == 201 - assert response.get_json() == {'message': 'State created'} + assert response.get_json()['state']['floor'] == 5 + assert response.get_json()['state']['vacant'] is True + + +def test_create_state_non_vacant_valid_demand(client): + # Test creating a non-vacant state with a valid demand_id + client.post('/demand', json={'floor': 3}) + response = client.post('/state', json={'floor': 3, 'vacant': False, 'demand_id': 1}) + assert response.status_code == 201 + assert response.get_json()['state']['demand_id'] == 1 + + +def test_create_state_non_vacant_invalid_demand(client): + # Check that a non-vacant state with an invalid demand_id fails + response = client.post('/state', json={'floor': 3, 'vacant': False, 'demand_id': 999}) + assert response.status_code == 400 + assert response.get_json() == {'error': 'Invalid demand_id'} + + +def test_create_state_non_vacant_no_demand_id(client): + # Test that a non-vacant state without demand_id returns an error + response = client.post('/state', json={'floor': 3, 'vacant': False}) + assert response.status_code == 400 + assert response.get_json() == {'error': 'Non-vacant state requires a valid demand_id'} + + +def test_create_state_invalid_floor(client): + # Test that an invalid floor in state creation gives a 400 error + response = client.post('/state', json={'floor': 'invalid', 'vacant': True}) + assert response.status_code == 400 + assert response.get_json() == {'error': 'Floor must be an integer'} + + +def test_create_state_invalid_vacant(client): + # Check that an invalid vacant value fails with a 400 + response = client.post('/state', json={'floor': 5, 'vacant': 'invalid'}) + assert response.status_code == 400 + assert response.get_json() == {'error': 'Vacant must be a boolean'} + + +def test_get_state(client): + # Test read to obtain a 200 + client.post('/demand', json={'floor': 3}) + client.post('/state', json={'floor': 5, 'vacant': True}) + response = client.get('/state/1') + assert response.status_code == 200 + assert response.get_json()['floor'] == 5 + + +def test_get_state_not_found(client): + # Test that requesting a non-existent state returns 404 + response = client.get('/state/999') + assert response.status_code == 404 + + +def test_get_all_states(client): + # Test to obtain all the states and get a 200 + client.post('/demand', json={'floor': 3}) + client.post('/state', json={'floor': 5, 'vacant': True}) + client.post('/state', json={'floor': -1, 'vacant': False, 'demand_id': 1}) + response = client.get('/states') + assert response.status_code == 200 + assert len(response.get_json()) == 2 + + +def test_update_state(client): + # Test updtae to obtain a 200 + client.post('/demand', json={'floor': 3}) + client.post('/state', json={'floor': 5, 'vacant': True}) + response = client.put('/state/1', json={'floor': -1, 'vacant': False, 'demand_id': 1}) + assert response.status_code == 200 + assert response.get_json()['state']['floor'] == -1 + assert response.get_json()['state']['vacant'] is False + + +def test_update_state_invalid_demand_id(client): + # Test updating a state with an invalid demand_id and obtain a 400 + client.post('/state', json={'floor': 5, 'vacant': True}) + response = client.put('/state/1', json={'floor': 5, 'vacant': False, 'demand_id': 999}) + assert response.status_code == 400 + assert response.get_json() == {'error': 'Invalid demand_id'} + + +def test_delete_state(client): + client.post('/state', json={'floor': 5, 'vacant': True}) + response = client.delete('/state/1') + assert response.status_code == 200 + assert response.get_json() == {'message': 'State deleted'} + response = client.get('/state/1') + assert response.status_code == 404 \ No newline at end of file diff --git a/chatgpt/db.sql b/chatgpt/db.sql index 1555ffe..70386f3 100644 --- a/chatgpt/db.sql +++ b/chatgpt/db.sql @@ -1,12 +1,29 @@ +-- Create table to storage all the demands of the elevator -- CREATE TABLE elevator_demands ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - floor INTEGER + floor INTEGER NOT NULL, + day_of_week INTEGER NOT NULL, + hour_of_day INTEGER NOT NULL, ); +-- Create table to storage the states of the elevator -- CREATE TABLE elevator_states ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - floor INTEGER, - vacant BOOLEAN + floor INTEGER NOT NULL, + vacant BOOLEAN NOT NULL, + day_of_week INTEGER NOT NULL, + hour_of_day INTEGER NOT NULL, + demand_id INTEGER, + FOREIGN KEY (demand_id) REFERENCES elevator_demands(id) ); + +-- Indexes to optimize frequent queries -- +-- Note: These indexes are not actively used in the current API endpoints, +-- but are included to support future machine learning tasks that may require +-- efficient filtering or grouping by timestamp or floor -- +CREATE INDEX idx_demands_timestamp ON elevator_demands(timestamp); +CREATE INDEX idx_states_timestamp ON elevator_states(timestamp); +CREATE INDEX idx_demands_floor ON elevator_demands(floor); +CREATE INDEX idx_states_floor ON elevator_states(floor); \ No newline at end of file diff --git a/chatgpt/instance/.DS_Store b/chatgpt/instance/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/chatgpt/instance/.DS_Store differ diff --git a/chatgpt/instance/elevator.db b/chatgpt/instance/elevator.db new file mode 100644 index 0000000..7377da5 Binary files /dev/null and b/chatgpt/instance/elevator.db differ diff --git a/chatgpt/main.py b/chatgpt/main.py index 7f97d98..1c7679b 100644 --- a/chatgpt/main.py +++ b/chatgpt/main.py @@ -1,43 +1,194 @@ +# Import necessary libraries from flask import Flask, request, jsonify from flask_sqlalchemy import SQLAlchemy from datetime import datetime +from sqlalchemy.sql import text +# Set up Flask app and connect to SQLite database app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///elevator.db' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # Turn off warnings for SQLAlchemy db = SQLAlchemy(app) +# Class model for elevator demands class ElevatorDemand(db.Model): + __tablename__ = 'elevator_demands' # Set table name and ForeignKey reference id = db.Column(db.Integer, primary_key=True) - timestamp = db.Column(db.DateTime, default=datetime.utcnow) - floor = db.Column(db.Integer, nullable=False) + timestamp = db.Column(db.DateTime, default=datetime.now()) # Store when the demand was made + floor = db.Column(db.Integer, nullable=False) # Floor number, can be positive or negative + day_of_week = db.Column(db.Integer, nullable=False) # Day of the week (0-6, Mon-Sun) + hour_of_day = db.Column(db.Integer, nullable=False) # Hour of the day (0-23) + def __init__(self, floor): + self.floor = floor + self.day_of_week = datetime.now().weekday() # Save the current day of the week + self.hour_of_day = datetime.now().hour # Save the current hour + # Convert demand to a dictionary for JSON response + def to_dict(self): + return { + 'id': self.id, + 'timestamp': self.timestamp.isoformat(), + 'floor': self.floor, + 'day_of_week': self.day_of_week, + 'hour_of_day': self.hour_of_day + } + + +# Class model for elevator states class ElevatorState(db.Model): + __tablename__ = 'elevator_states' # Set table name 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) + timestamp = db.Column(db.DateTime, default=datetime.now()) # Store when the state was recorded + floor = db.Column(db.Integer, nullable=False) # Current floor of the elevator + vacant = db.Column(db.Boolean, nullable=False) # True if elevator is empty, False if not + day_of_week = db.Column(db.Integer, nullable=False) # Day of the week (0-6) + hour_of_day = db.Column(db.Integer, nullable=False) # Hour of the day (0-23) + # Link to a demand if the elevator is not vacant + demand_id = db.Column(db.Integer, db.ForeignKey('elevator_demands.id'), nullable=True) + # Set up relationship to access demand data easily + demand = db.relationship('ElevatorDemand', backref=db.backref('states', lazy=True)) + + def __init__(self, floor, vacant, demand_id=None): + self.floor = floor + self.vacant = vacant + self.demand_id = demand_id + self.day_of_week = datetime.now().weekday() # Save current day + self.hour_of_day = datetime.now().hour # Save current hour + # Convert state to a dictionary for JSON response + def to_dict(self): + return { + 'id': self.id, + 'timestamp': self.timestamp.isoformat(), + 'floor': self.floor, + 'vacant': self.vacant, + 'day_of_week': self.day_of_week, + 'hour_of_day': self.hour_of_day, + 'demand_id': self.demand_id + } + +# Create a demand when someone calls the elevator @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 + if not isinstance(data.get('floor'), int): + return jsonify({'error': 'Floor must be an integer'}), 400 # Check if floor is valid + new_demand = ElevatorDemand(floor=data['floor']) # Create new demand + db.session.add(new_demand) # Add to database + db.session.commit() # Save changes + # Return the created demand with its details + return jsonify({'message': 'Demand created', 'demand': new_demand.to_dict()}), 201 + + +# Get a specific demand by ID +@app.route('/demand/', methods=['GET']) +def get_demand(id): + demand = ElevatorDemand.query.get_or_404(id) # Find demand or return 404 + return jsonify(demand.to_dict()), 200 # Return demand details + + +# Get all demands +@app.route('/demands', methods=['GET']) +def get_all_demands(): + demands = ElevatorDemand.query.all() # Grab all demands from the database + return jsonify([demand.to_dict() for demand in demands]), 200 # Return list of demands + + +# Update an existing demand +@app.route('/demand/', methods=['PUT']) +def update_demand(id): + demand = ElevatorDemand.query.get_or_404(id) # Find demand or 404 + data = request.get_json() + if not isinstance(data.get('floor'), int): + return jsonify({'error': 'Floor must be an integer'}), 400 # Validate floor + demand.floor = data['floor'] # Update floor + db.session.commit() # Save changes + return jsonify({'message': 'Demand updated', 'demand': demand.to_dict()}), 200 # Return updated demand +# Delete a demand +@app.route('/demand/', methods=['DELETE']) +def delete_demand(id): + demand = ElevatorDemand.query.get_or_404(id) # Find demand or 404 + db.session.delete(demand) # Remove it + db.session.commit() # Save changes + return jsonify({'message': 'Demand deleted'}), 200 # Confirm deletion + + +# Create a new elevator state @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 not isinstance(data.get('floor'), int): + return jsonify({'error': 'Floor must be an integer'}), 400 # Check floor is an integer + if not isinstance(data.get('vacant'), bool): + return jsonify({'error': 'Vacant must be a boolean'}), 400 # Check vacant is boolean + demand_id = data.get('demand_id') + if not data['vacant'] and demand_id is None: + return jsonify({'error': 'Non-vacant state requires a valid demand_id'}), 400 # Check non-vacant states + if demand_id is not None and ElevatorDemand.query.get(demand_id) is None: + return jsonify({'error': 'Invalid demand_id'}), 400 # Validate demand_id + new_state = ElevatorState(floor=data['floor'], vacant=data['vacant'], demand_id=demand_id) # Create new state + db.session.add(new_state) # Add to database + db.session.commit() # Save changes + return jsonify({'message': 'State created', 'state': new_state.to_dict()}), 201 # Return created state + + +# Get a specific state by its ID +@app.route('/state/', methods=['GET']) +def get_state(id): + state = ElevatorState.query.get_or_404(id) # Find state or 404 + return jsonify(state.to_dict()), 200 # Return state details + + +# Get all states +@app.route('/states', methods=['GET']) +def get_all_states(): + states = ElevatorState.query.all() # Grab all states + return jsonify([state.to_dict() for state in states]), 200 # Return list of states + + +# Update an existing state +@app.route('/state/', methods=['PUT']) +def update_state(id): + state = ElevatorState.query.get_or_404(id) # Find state or 404 + data = request.get_json() + if not isinstance(data.get('floor'), int): + return jsonify({'error': 'Floor must be an integer'}), 400 # Validate floor + if not isinstance(data.get('vacant'), bool): + return jsonify({'error': 'Vacant must be a boolean'}), 400 # Validate vacant + demand_id = data.get('demand_id') + if not data['vacant'] and demand_id is None: + return jsonify({'error': 'Non-vacant state requires a valid demand_id'}), 400 # Check non-vacant states + if demand_id is not None and ElevatorDemand.query.get(demand_id) is None: + return jsonify({'error': 'Invalid demand_id'}), 400 # Validate demand_id + state.floor = data['floor'] # Update floor + state.vacant = data['vacant'] # Update vacant status + state.demand_id = demand_id # Update demand_id + db.session.commit() # Save changes + return jsonify({'message': 'State updated', 'state': state.to_dict()}), 200 # Return updated state + + +# Delete a state +@app.route('/state/', methods=['DELETE']) +def delete_state(id): + state = ElevatorState.query.get_or_404(id) # Find state or 404 + db.session.delete(state) # Remove it + db.session.commit() # Save changes + return jsonify({'message': 'State deleted'}), 200 # Confirm deletion +# Start the app and create database tables if __name__ == '__main__': - db.create_all() - app.run(debug=True) + with app.app_context(): + db.create_all() # Set up the database tables + with db.engine.connect() as connection: + connection.execute(text('CREATE INDEX IF NOT EXISTS idx_demands_timestamp ON elevator_demands(timestamp)')) + connection.execute(text('CREATE INDEX IF NOT EXISTS idx_states_timestamp ON elevator_states(timestamp)')) + connection.execute(text('CREATE INDEX IF NOT EXISTS idx_demands_floor ON elevator_demands(floor)')) + connection.execute(text('CREATE INDEX IF NOT EXISTS idx_states_floor ON elevator_states(floor)')) + print("Database tables created!") + app.run(debug=True) # Run the app in debug mode diff --git a/chatgpt/requirements.txt b/chatgpt/requirements.txt index 14d1bb0..f6f7710 100644 --- a/chatgpt/requirements.txt +++ b/chatgpt/requirements.txt @@ -1,4 +1,4 @@ -Flask==2.0.2 -Flask-SQLAlchemy==2.5.1 -pytest==6.2.5 -pytest-flask==1.2.0 +Flask +Flask-SQLAlchemy +pytest +pytest-flask