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__/
27 changes: 27 additions & 0 deletions Controllers/sequence_measuremnet_conversion_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import cherrypy
from Services.sequence_processor import sequence_compressor
from Models.sequence_history import SequenceHistory

class SequenceConverterAPI:
def __init__(self, db_file):
# Initialize SequenceHistory with the provided database file
self.sequence_history = SequenceHistory(db_file)

@cherrypy.expose
@cherrypy.tools.json_out()
def convert_measurements(self, input):
# Call sequence_compressor to process the input and get compressed values
compressed_values = sequence_compressor(input)

# Save the sequence and its compressed values to the database
self.sequence_history.save_sequence({'input': input, 'compressed_values': compressed_values})

# Return the compressed values in JSON format
return compressed_values

@cherrypy.expose
@cherrypy.tools.json_out()
def get_history(self):
# Get the history of sequences from the database
return self.sequence_history.get_history()

13 changes: 13 additions & 0 deletions Logs/logs.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
2024-05-01 11:03:09,106:INFO:[01/May/2024:11:03:09] ENGINE Listening for SIGTERM.
2024-05-01 11:03:09,107:INFO:[01/May/2024:11:03:09] ENGINE Bus STARTING
2024-05-01 11:03:09,108:INFO:[01/May/2024:11:03:09] ENGINE Started monitor thread 'Autoreloader'.
2024-05-01 11:03:09,324:INFO:[01/May/2024:11:03:09] ENGINE Serving on http://0.0.0.0:8080
2024-05-01 11:03:09,324:INFO:[01/May/2024:11:03:09] ENGINE Bus STARTED
2024-05-01 11:03:12,099:INFO:[01/May/2024:11:03:12] ENGINE Keyboard Interrupt: shutting down bus
2024-05-01 11:03:12,099:INFO:[01/May/2024:11:03:12] ENGINE Bus STOPPING
2024-05-01 11:03:12,274:INFO:[01/May/2024:11:03:12] ENGINE HTTP Server cherrypy._cpwsgi_server.CPWSGIServer(('0.0.0.0', 8080)) shut down
2024-05-01 11:03:12,274:INFO:[01/May/2024:11:03:12] ENGINE Stopped thread 'Autoreloader'.
2024-05-01 11:03:12,287:INFO:[01/May/2024:11:03:12] ENGINE Bus STOPPED
2024-05-01 11:03:12,288:INFO:[01/May/2024:11:03:12] ENGINE Bus EXITING
2024-05-01 11:03:12,288:INFO:[01/May/2024:11:03:12] ENGINE Bus EXITED
2024-05-01 11:03:12,289:INFO:[01/May/2024:11:03:12] ENGINE Waiting for child threads to terminate...
36 changes: 36 additions & 0 deletions Models/sequence_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# sequence_history.py
import sqlite3

class SequenceHistory:
def __init__(self, db_file):
# Store the path to the SQLite database file
self.db_file = db_file

def save_sequence(self, sequence_data):
# Connect to the SQLite database and save the sequence data
with sqlite3.connect(self.db_file) as conn:
cursor = conn.cursor()
sequence = sequence_data['input']
compressed_values = ','.join(map(str, sequence_data['compressed_values']))
# Insert the sequence and compressed values into the 'sequences' table
cursor.execute("INSERT INTO sequences (sequence, compressed_values) VALUES (?, ?)",
(sequence, compressed_values))
conn.commit()

def get_history(self):
# Connect to the SQLite database and retrieve sequence history
with sqlite3.connect(self.db_file) as conn:
cursor = conn.cursor()
# Execute SQL query to select sequences and order by timestamp in descending order
cursor.execute("SELECT sequence, compressed_values, timestamp FROM sequences ORDER BY timestamp DESC")
rows = cursor.fetchall()
history = []
# Iterate over rows and format data into a list of dictionaries
for row in rows:
history.append({
'sequence': row[0],
'compressed_values': list(map(int, row[1].split(','))),
'timestamp': row[2]
})
return history

90 changes: 90 additions & 0 deletions Services/sequence_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# sequence_processor.py
from abc import ABC, abstractmethod

class SequenceProcessorBase(ABC):
@abstractmethod
def process_sequence(self):
# Abstract method to process a sequence of numbers
pass

class SequenceConverter:
def __init__(self, sequence):
# Initialize with the input sequence and an empty list for numerical values
self.sequence = sequence
self.numerical_values = []

def convert_to_numeric_list(self):
# Convert the input sequence into a list of numerical values
in_z_sequence = False
z_accumulated_sum = 0

for char in self.sequence:
# Determine the offset value for ASCII mapping
offset_val = 97 if char != "_" else 96
# Convert character to numerical value based on ASCII and 'z' sequence logic
char_value = ord(char) - offset_val + 1

if char == "z":
# Track if in 'z' sequence and accumulate sum
in_z_sequence = True
z_accumulated_sum += char_value
else:
in_z_sequence = False
if not in_z_sequence:
if z_accumulated_sum > 0:
# Add accumulated sum and reset if not in 'z' sequence
z_accumulated_sum += char_value
self.numerical_values.append(z_accumulated_sum)
z_accumulated_sum = 0
else:
# Add character value if not in 'z' sequence
self.numerical_values.append(char_value)
elif char == "_":
# Append accumulated sum if encounter '_' and reset
self.numerical_values.append(z_accumulated_sum)
z_accumulated_sum = 0
in_z_sequence = False

return self.numerical_values

class CompressedSequenceProcessor(SequenceProcessorBase):
def __init__(self, numerical_values):
# Initialize with the numerical values
self.numerical_values = numerical_values

def process_sequence(self):
# Process numerical values to generate compressed sequence
compressed_values = []
is_step = True
step_remaining = 0
current_sum = 0

for num in self.numerical_values:
if is_step:
# Set current number as a step if True
step_remaining = num
is_step = False
elif step_remaining > 0:
# Accumulate numbers until step_remaining reaches 0
current_sum += num
step_remaining -= 1

if step_remaining == 0:
# Append the result and reset cycle when step_remaining is 0
compressed_values.append(current_sum)
current_sum = 0
is_step = True

return compressed_values

def sequence_compressor(sequence):
# Main function to convert a sequence into compressed values
converter = SequenceConverter(sequence)
numerical_values = converter.convert_to_numeric_list()

if not numerical_values:
return ["Invalid Input"]
print("Numerical Values:", numerical_values)
processor = CompressedSequenceProcessor(numerical_values)
print("Measured Inflows:")
return processor.process_sequence()
141 changes: 141 additions & 0 deletions Utillities/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Package Measurement Conversion API

## Introduction

This API converts measurement input strings into a list of total values of measured inflows for each package. It follows a specific encoding format where a number represents the count of values measured in each cycle, followed by measured values encoded using alphabetical characters ('a' for 1, 'b' for 2, and so on).

### Algorithm Summary

- Each character in the input sequence corresponds to a specific value.
- The algorithm processes the input sequence character by character.
- Special cases, such as 'z', are handled where a single character may represent multiple characters.
- The algorithm calculates the sum of values based on specific rules for each character.
- The sequence restarts after each calculation until the end of the input sequence is reached.

## Operation

1. Start with the first character in the input sequence.
2. Determine the value of the character based on predefined rules.
3. If a special case like 'z' is encountered, treat it and the following characters as a single continuous character.
4. Calculate the sum of values for a specified number of characters after the current character.
5. Repeat the process until the end of the input sequence is reached.

## Examples

1. **Input:** abbcc
- **Response:** [2, 6]
- **Explanation:**
- 'a' -> 1
- 'b' -> 2 (consider 2 characters after 'b': 'c', 'c')
- 'c' -> 3 (consider 2 characters after 'c': 'c')
- Sum: 2 + 6 = 8

2. **Input:** abcdabcdab
- **Response:** [2, 7, 7]
- **Explanation:**
- 'a' -> 1
- 'b' -> 2 (consider 2 characters after 'b': 'c', 'd')
- 'c' -> 3 (consider 3 characters after 'c': 'd', 'a', 'b')
- 'd' -> 4
- Sum: 2 + 7 + 7 = 16

3. **Input:** dz_a_aazzaaa
- **Response:** [28, 53, 1]
- **Explanation:** (Include the detailed explanation of the 'z' case here)

4. **Input:** zd_aaaaaaaaaabaaaaaaaabaaaaaaaabbaa
- **Response:** [34]
- **Explanation:** (Include the detailed explanation of the 'z' case here)

This algorithm effectively encodes or decodes input sequences by assigning values to characters and summing them according to specified rules, providing a concise representation of the sequence.


## Prerequisites

- Python 3.x
- CherryPy

## Installation

1. Clone the repository to your local machine:

git clone https://github.com/jnulia/PackageMeasurementConversionAPI.git


2. Install dependencies:

pip install -r requirements.txt


## Running the Application

1. Navigate to the project directory.
2. Run the main application file with Python:

python main_app.py


You can specify a custom port using the --port argument:

python main_app.py --port <port-number>


## API Usage

### Conversion Endpoint

- **Endpoint:** /convert-measurements
- **Method:** GET
- **Query Parameter:**
- input: Measurement input string
- **Response:** JSON array containing the total values of measured inflows for each package

#### Example Usage:

Assuming the API is running locally on http://localhost:8080:

1. Convert measurements:

GET http://localhost:8080/convert-measurements?input=dz_a_aazzaaa


**Response:**

[28, 53, 1]


### History Endpoint

- **Endpoint:** /get-history
- **Method:** GET
- **Response:** JSON array containing the history of all data from requests

#### Example Usage:

Assuming the API is running locally on http://localhost:8080:

2. Retrieve history:

GET http://localhost:8080/get-history


**Response:**
[
{
"sequence": "dz_a_aazzaaa",
"compressed_values": [28, 53, 1],
"timestamp": "2024-04-29 12:00:00"
},
...
]


## Testing

1. Execute the test cases in the following:

``` python -m unittest .\tests\test_sequence_processor.py


License
This project is licensed under the MIT License - see the LICENSE file for details.
3 changes: 3 additions & 0 deletions Utillities/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CherryPy==18.9.0
requests==2.31.0
setuptools==69.2.0
Binary file added Utillities/sequence_history.db
Binary file not shown.
24 changes: 24 additions & 0 deletions main_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# main_app.py
import cherrypy
from Controllers.sequence_measuremnet_conversion_api import SequenceConverterAPI
import argparse
import logging

if __name__ == '__main__':
# Configure LOGGING to file
logging.basicConfig(filename='./Logs/logs.log', level=logging.CRITICAL,
format='%(asctime)s:%(levelname)s:%(message)s')

# Parse command-line arguments
parser = argparse.ArgumentParser(description='Package Measurement Conversion API')
parser.add_argument('--port', type=int, default=8080, help='Port number to run the server on')
args = parser.parse_args()

# Path to SQLite database file
db_file = 'Utillities/sequence_history.db'

# Update CherryPy configuration with host and port
cherrypy.config.update({'server.socket_host': '0.0.0.0', 'server.socket_port': args.port})

# Start the CherryPy server with the SequenceConverterAPI instance
cherrypy.quickstart(SequenceConverterAPI(db_file))
28 changes: 28 additions & 0 deletions tests/test_sequence_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import unittest
from Services.sequence_processor import sequence_compressor

class TestSequenceProcessor(unittest.TestCase):
def test_sequence_compressor(self):
# Define test cases with input sequences and expected outputs
test_cases = [
("dz_a_aazzaaa", [28, 53, 1]),
("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa", [40, 1]),
("aa", [1]),
("abbcc", [2, 6]),
("a_", [0]),
("abcdabcdab", [2, 7, 7]),
("zdaaaaaaaabaaaaaaaabaaaaaaaabbaa", [34]),
("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa", [40, 1]),
("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])
]

# Iterate through test cases and run sub-tests
for sequence, expected_output in test_cases:
with self.subTest(sequence=sequence):
# Call sequence_compressor function with each sequence
result = sequence_compressor(sequence)
# Assert the result matches the expected output
self.assertEqual(result, expected_output)

if __name__ == '__main__':
unittest.main()