Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea/
__pycache__/
252 changes: 252 additions & 0 deletions Package_Measurement_Conversion_API/Logging/error_log.log

Large diffs are not rendered by default.

Empty file.
39 changes: 39 additions & 0 deletions Package_Measurement_Conversion_API/Utilities/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import sqlite3


class MeasurementsDB(object):
def __init__(self, name_db='conversion_history.db'):
self.name_db = name_db
self.conn = sqlite3.connect(self.name_db, check_same_thread=False)
self.cursor = self.conn.cursor()
self.create_table()

def create_table(self):

# Create the table that will store the conversion history
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS conversion_history (
id INTEGER PRIMARY KEY,
input TEXT,
output TEXT,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
self.conn.commit()

def save_table(self, input_str, output_str):
# Save the conversion history in the table
output_str = str(output_str)
self.cursor.execute('INSERT INTO conversion_history (input, output) VALUES (?, ?)', (input_str, output_str))
self.conn.commit()

def display_table(self):
# Display the conversion history from the table
conversion_history = []
self.cursor.execute("SELECT input, output FROM conversion_history ORDER BY timestamp DESC")
rows = self.cursor.fetchall()
for row in rows:
data = {'input': row[0], 'output': row[1]}
conversion_history.append(data)
# conversion_history = [{'input': row[0], 'output': row[1]} for row in rows]
return conversion_history
Empty file.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import cherrypy
import logging

from Package_Measurement_Conversion_API.services.converter import Measurements
from Package_Measurement_Conversion_API.Utilities.db import MeasurementsDB

# Initialize database manager and measurement conversion service
db_manager = MeasurementsDB("conversion_history.db")
Measure = Measurements()

# Configure logging
logging.basicConfig(filename='Package_Measurement_Conversion_API\logging\error_log.log',
level=logging.CRITICAL, format='%(asctime)s:%(levelname)s:%(message)s')


class MeasurementService(object):

@cherrypy.expose
@cherrypy.tools.json_out()
def get_data_from_db(self):
"""Fetch conversion history from the database."""
try:
table = db_manager.display_table()
return {"status": "success", "err_msg": "", "result": table}
except Exception as e:
logging.error(f"Failed to retrieve data: {str(e)}")
return {"status": "fail", "err_msg": str(e), "result": []}

@cherrypy.expose
@cherrypy.tools.json_out()
def convert_measurements(self, input_string):
"""Convert input string to numbers based on specific logic."""
try:
if not isinstance(input_string, str):
raise ValueError("Input must be a string.")

converted_data = Measure.convert_measurements(input_string)
if not converted_data:
raise ValueError("Conversion resulted in empty data.")
return {"status": "success", "err_msg": "", "result": converted_data}
except Exception as e:
logging.exception("Error in convert_measurements:")
return {"status": "fail", "err_msg": str(e), "result": []}

@cherrypy.expose
@cherrypy.tools.json_out()
def conversion_results(self, input_string):
"""Process converted data to compute final results and save them to the database."""
try:
results = Measure.measurement_results(input_string)
db_manager.save_table(input_string, results) # Assuming the function name is save_conversion
return {"status": "success", "err_msg": "", "result": results}
except Exception as e:
logging.error(f"Failed to process conversion results: {str(e)}")
return {"status": "fail", "err_msg": str(e), "result": []}
76 changes: 76 additions & 0 deletions Package_Measurement_Conversion_API/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Package Measurement Conversion API

This API is designed to convert measurement input strings into a list of total values of measured inflows for each package. The input strings follow a specific encoding format, where each package consists of a number indicating the count of values measured in each measurement cycle, followed by the measured values encoded using alphabetical characters.

## Pre-requisites

- Python 3.x
- CherryPy
- SQLite

## Installation

### 1. Clone the repository:

https://github.com/Sughiya-AlSaid/PackageMeasurementConversionAPI


### 2. Navigate to the project directory:

cd Package_Measurement_Conversion_API

### 3. Install the required dependencies:

pip install -r requirements.txt

## Running the Application

### 1. Start the server application:

python main_app.py [port_number]

Replace `[port_number]` with the desired port number. If no port number is provided, the application will run on the default port.

### 2. The server will start, and you should see log messages indicating the application's status.

## API Usage
### 1. To view the converted alphabet input string into the corresponding list of numbers in JSON format.

The API provides a GET endpoint `/convert_measurements` that accepts a query parameter `input` containing the measurement input string.

### Example requests:

GET /convert_measurements?input="aa"

GET /convert_measurements?input="abbcc"

GET /convert_measurements?input="dz_a_aazzaaa"

### 2. To view the list of total values of the parsed string and return the result in JSON format.

The API provides a GET endpoint `/conversion_results` that accepts a query parameter `input` containing the measurement input string.

### Example requests:

GET /conversion_results?input="aa"

GET /conversion_results?input="abbcc"

GET /conversion_results?input="dz_a_aazzaaa"
### To retrieve the stored request history, use the following endpoint:

GET /get_data_from_db

This endpoint will return the persisted history of all requests made to the conversion endpoint.

## Testing

The application follows the principles of Test-Driven Development (TDD). Unit tests are provided in the `test/` directory. To run the tests, execute the following command:

python -m unittest Package_Measurement_Conversion_API/services/test/test_converter.py

## Contributing
Contributing to this project is **prohibited**.

## Acknowledgements
This project was done by **Sughiya Al Said**
3 changes: 3 additions & 0 deletions Package_Measurement_Conversion_API/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CherryPy==18.6.0
SQLAlchemy==1.4.31
pytest==7.1.2
Empty file.
68 changes: 68 additions & 0 deletions Package_Measurement_Conversion_API/services/converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
class Measurements(object):
def __init__(self):
pass

def convert_measurements(self, input_string):
# Initialize the results list and the current sum.
results = []
current_sum = 0

# Keep track of the count for each input
input_count = 0

# Loop through each character in the input string.
for char in input_string:
if char == '_':
# When facing an underscore, append the current sum to results then reset it to 0.
results.append(current_sum)
current_sum = 0
input_count = 0
elif 'a' <= char <= 'z':
# Convert the alphabet to their number value (a=1, b=2, etc..).
input_count += 1
current_sum += ord(char) - ord('a') + 1

if input_count > 0 and current_sum > 0:
# append the sum to the result list if input_count and current sum have a value
results.append(current_sum)
current_sum = 0
input_count = 0

return results

def measurement_results(self, input_str):
# Convert the input str from the url into a list of numbers then start the count

list_of_num = self.convert_measurements(input_str)
result_input_list = []
is_a_step = True # First character/number = step one.
step_count_remaining = 0 # Set the count as soon as the number is encountered.
curr_input_sum = 0 # continue adding the values until remaining step count != 0
# i = 0
for curr_num in list_of_num:
# i += 1
if is_a_step:
if curr_num == 26: # Check if current character is Z count
step_count_remaining += curr_num
elif curr_num == 0: # Check if current Character is underscore
result_input_list.append(0)
break
else:
step_count_remaining += curr_num
is_a_step = False
elif step_count_remaining > 0:
if curr_num == 26:
curr_input_sum += curr_num
else:
curr_input_sum += curr_num
step_count_remaining -= 1

if step_count_remaining == 0: # append the measurement result then reset the count before exiting
result_input_list.append(curr_input_sum)
curr_input_sum = 0
step_count_remaining = 0
is_a_step = True
# elif step_count_remaining > (len(list_of_num) - 1) - i:
# result_input_list = []

return result_input_list
Empty file.
25 changes: 25 additions & 0 deletions Package_Measurement_Conversion_API/services/test/test_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import unittest
from Package_Measurement_Conversion_API.services.converter import Measurements


class TestPackageConverter(unittest.TestCase):
# Testing both Valid and Invalid inputs

def setUp(self):
self.converter = Measurements()

def test_conversion(self):
self.assertEqual(self.converter.measurement_results("aa"), [1])
self.assertEqual(self.converter.measurement_results("__"), [0])
self.assertEqual(self.converter.measurement_results("a_"), [0])
self.assertEqual(self.converter.measurement_results('abbcc'), [2, 6])
self.assertEqual(self.converter.measurement_results('abcdabcdab'), [2, 7, 7])
self.assertEqual(self.converter.measurement_results('abcdabcdab_'), [2, 7, 7, 0])
self.assertEqual(self.converter.measurement_results('dz_a_aazzaaa'), [28, 53, 1])
self.assertEqual(self.converter.measurement_results('zdaaaaaaaabaaaaaaaabaaaaaaaabbaa'), [34])
self.assertEqual(self.converter.measurement_results('za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa'), [40, 1])
self.assertEqual(self.converter.measurement_results('zza_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_'), [26])


if __name__ == '__main__':
unittest.main()
Binary file added conversion_history.db
Binary file not shown.
16 changes: 16 additions & 0 deletions main_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import cherrypy

from Package_Measurement_Conversion_API.controller.measurement_controller import MeasurementService


if __name__ == '__main__':
config = {
'/': {
'tools.sessions.on': True,
'tools.response_headers.on': True,
'tools.response_headers.headers': [('Content-Type', 'application/json')],
}
}
cherrypy.config.update({'server.socket_host': "0.0.0.0"})
cherrypy.config.update({'server.socket_port': 8080})
cherrypy.quickstart(MeasurementService(), '/', config)