Commit 0966d9d7 authored by Lennart Holzenkamp's avatar Lennart Holzenkamp
Browse files

adding a json_api

parent 4fe17995
......@@ -13,4 +13,5 @@ data/*
legacy.md
**/__pycache__
data/clustered_ocel_files/*
venv
\ No newline at end of file
venv
uploads
\ No newline at end of file
......@@ -15,6 +15,88 @@ For examples how to use the CLI check out the file code/sample-cli-execution.sh,
python ./code/cli.py --help
```
# REST-API
For using the REST-API please execute the file json_api.py.
Then a local server starts and you can use the following API-Endpoints. You may use tools like Postman to test the API. Many functions are also easily accessible via your browser.
## ocel-upload
### GET
```http
GET http://127.0.0.1:5000/ocel-upload
```
Returns a json with a list of already uploaded files.
### POST
```http
POST http://127.0.0.1:5000/ocel-upload
```
Using the key-field "file" (which is per default used in a file-upload formular in HTML) a new file is uploaded to the system. If the file already exists an error occurs.
## ocel-upload/\<filename\>
### DELETE
```http
POST http://127.0.0.1:5000/ocel-upload/<filename>
```
Deletes the file with the name 'filename'.
## ocel-cluster/\<filename\>
### POST
```http
POST http://127.0.0.1:5000/ocel-cluster/<filename>
```
Applies the core clustering algorithm for the file described via the filename.
Parameters of the algorithm can be described in the body of the request as json. The following is an example.
```json
{
"data": {
"object-type": "customers",
"mode": "all",
"max-cluster-count": 4,
"attr-weights": {
"cflow": 2
}
}
}
```
A list of all possible parameters:
- _object-type_: type of object to apply the clustering algorithm on.
- _mode_: either "all" or "existence" which defines how the assignment of events to object clusters is done.
- _attr-weights_: A JSON-object to define the weights of attributes (including the pseudo-attribute "cflow" which described the control-flow of events corresponding to a object in the ocel-file).
- _cluster-algorithm_: Defines the clustering-technique to be used. Default is kmeans. possible is also agglomerative or spectral
- _max-cluster-count_: Defines the maximum cluster count. This value can be -1 for auto-maximum (which is the default value) or anything greater or equal to two. ATTENTION: This parameter highly affects the runtime! Be careful/patient with using high values.
- _cluster-count_: If its set to 2 or greater (default is -1 which means automatic) the cluster-count is fixed and max-cluster-count is ignored.
## ocel-cluster/\<filename\>/clusters
### GET
```http
GET http://127.0.0.1:5000/ocel-cluster/<filename>/clusters
```
Returns a list of the found cluster-ids.
## ocel-cluster/\<filename\>/clusters/\<number\>
### GET
```http
GET http://127.0.0.1:5000/ocel-cluster/<filename>/clusters/<number>
```
Returns the ocel-file corresponding to the cluster with the index "number".
## ocel-cluster/\<filename\>/clusters/\<number\>/graph
### GET
```http
GET http://127.0.0.1:5000/ocel-cluster/<filename>/clusters/<number>/graph?activity_threshold=0&edge_threshold=0
```
Creates a graph based on the ocel-file and returns it as download. The parameter define minimum threshold for activities or edges.
If the parameters are omitted theire values are interpreted as 0.
# Docker
## Install the Docker-App
......
......@@ -6,6 +6,11 @@ import ocel_clustering.main as ocel_clustering
import json
import argparse
##########################################################################################################
# Module for executing ocel_clustering algorithm as a cli version.
# parameters are documentet. execute with --help for retrieving detailed information for these parameters
##########################################################################################################
###############
## CONSTANTS ##
###############
......
from flask import Flask, render_template, request
import os
from sympy import arg
import model
import main
app = Flask(__name__)
@app.route("/")
def hello_world():
form = model.MyForm()
return render_template('start_page.html', form=form)
@app.route("/run_code", methods=["GET"])
def run_code():
main.main()
return "<p>Back to Main Page</p>"
@app.route("/set_params", methods=["POST"])
def set_params():
ocel_file = request.form.get("ocel_file")
form = model.MyForm()
main.p_ocel_file = form.ocel_file.data
main.p_mode = form.mode.data
main.p_attr_weights = form.attr_weights.data
main.p_object_type = form.object_type.data
main.p_clusteval_mode = form.clusteval_mode.data
main.p_clustering_mode = form.clustering_mode.data
main.p_max_cluster_count = form.max_cluster_count.data
main.p_ocel_file_type = form.ocel_file_type.data
main.p_graph_file_type = form.graph_file_type.data
if form.validate_on_submit():
return "<p>Parameters where set successfully.</p>"
return render_template('start_page.html', form=form)
@app.route("/oc-dfg/<int:dfg_num>")
def get_oc_dfg():
return
if __name__ == "__main__":
SECRET_KEY = os.urandom(32)
app.config['SECRET_KEY'] = SECRET_KEY
app.run(debug=True)
\ No newline at end of file
import tempfile
from flask import Flask, request, jsonify, send_file
import os
import ocel_clustering.main as ocel_clustering
from werkzeug.utils import secure_filename
from os.path import exists
import pathlib
import pm4py
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'jsonocel', 'xmlocel'}
RESULT_FOLDER_SUFFIX = '_results'
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# ensure existence of upload folder
if not os.path.exists(app.config['UPLOAD_FOLDER']):
os.makedirs(app.config['UPLOAD_FOLDER'])
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
# returns ordered list of result-files without directory or anything.
# valid results cant be empty. if empty list is returned, there exeists no results.
def get_result_files_for(original_ocel_file_name: str) -> list[str]:
s_filename = secure_filename(original_ocel_file_name)
if not exists(app.config['UPLOAD_FOLDER'] + '/' + s_filename):
raise Exception('file does not exists')
original_ext = pathlib.Path(s_filename).suffix[1:]
folder = app.config['UPLOAD_FOLDER'] + '/' + s_filename + RESULT_FOLDER_SUFFIX
res = []
if exists(folder):
for file in sorted(os.listdir(folder)):
ext = pathlib.Path(file).suffix[1:]
if ext == original_ext: res.append(file)
return res
def get_suffix(filename: str) -> str:
return pathlib.Path(filename).suffix[1:]
############
## ROUTES ##
############
@app.route("/")
def hello_world():
return 'Hello World'
# UPLOAD ROUTES
@app.route('/ocel-upload', methods=['GET'])
def ocel_upload_get():
res = []
for file in os.listdir(app.config['UPLOAD_FOLDER']):
print(file)
ext = pathlib.Path(file).suffix[1:]
if ext in ALLOWED_EXTENSIONS:
res.append({
'name': file,
'type': ext
})
return jsonify({
'data': res
})
@app.route('/ocel-upload/<filename>', methods=['DELETE'])
def ocel_upload_delete(filename):
filename = secure_filename(filename)
full_filename = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if exists(full_filename):
os.remove(full_filename)
return jsonify({
'data': [
{
'filename': filename,
'type': pathlib.Path(filename).suffix[1:]
}
]
})
else:
return jsonify({
'errors': [
{
'name': 'file-not-found',
'description': 'The given file does not exist.'
}
]
}), 404
@app.route('/ocel-upload', methods=['POST'])
def ocel_upload_post():
# check if the post request has the file part
if 'file' not in request.files:
return jsonify({
'errors': [
{
'name': 'no-file-attribute',
'description': 'your request does not provide a file-attribute'
}
]
}), 404
file = request.files['file']
# If the user does not select a file, the browser submits an
# empty file without a filename.
if file.filename == '':
return jsonify({
'errors': [
{
'name': 'empty-file',
'description': 'your file is empty'
}
]
}), 404
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
full_filename = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if(exists(full_filename)):
return jsonify({
'errors': [
{
'name': 'file-already-exists',
'description': 'The given file already exists. Use the same API-Endpoint with DELETE to delete the current one.'
}
]
}), 404
file.save(full_filename)
return jsonify({
'data': {
'filename': filename,
'type': pathlib.Path(filename).suffix[1:]
}
})
else:
return jsonify({
'errors': [
{
'name': 'file-type-error',
'description': 'The given filetype is not allowed.'
}
]
}), 404
# CLUSTERING-ROUTES
# todo: delete all files that are inside the folder before
@app.route("/ocel-cluster/<filename>", methods=['POST'])
def ocel_cluster_post(filename):
s_filename = secure_filename(filename)
full_filename = os.path.join(app.config['UPLOAD_FOLDER'], s_filename)
if not exists(full_filename) or not pathlib.Path(s_filename).suffix[1:] in ALLOWED_EXTENSIONS:
return jsonify({
'errors': [
{
'name': 'file-not-found',
'description': 'The file does not exist.'
}
]
}), 404
try:
JSON_sent = request.get_json().get('data', {})
object_type = JSON_sent.get('object-type', None)
if object_type == None:
raise Exception('"object-type" attribute missing')
mode = JSON_sent.get('mode', None)
if mode == None:
raise Exception('"mode" attribute missing.')
# reading OCEL-File
ocel = pm4py.read_ocel(full_filename)
# getting attribute weights and setting accordingly
attr_weights = JSON_sent.get('attr-weights', {})
attr_def = ocel_clustering.ocel_get_attr_def(ocel)
def set_weights_function(attr_def: dict) -> dict:
res = attr_def
if res['name'] in attr_weights.keys():
res['weight'] = attr_weights[res['name']]
return res
attr_def = list(map(set_weights_function, attr_def))
# setting control_flow_weight
control_flow_weight = attr_weights.get(ocel_clustering.OCEL_COL_NAME_CONTROL_FLOW, 1.0)
# executing core clustering
res = ocel_clustering.ocel_cluster_by_objects(
ocel=ocel,
object_type = object_type,
event_assignment_mode = mode,
attr_def = attr_def,
clustering_algorithm = JSON_sent.get('clustering-algorithm', 'kmeans'),
max_cluster_count = JSON_sent.get('max-cluster-count', -1),
cluster_count = JSON_sent.get('cluster-count', -1),
control_flow_active = control_flow_weight != 0.0,
control_flow_weight = control_flow_weight
)
# storing result files
target_dir = UPLOAD_FOLDER + '/' + s_filename + RESULT_FOLDER_SUFFIX # storing in a folder named according to file
if not os.path.exists(target_dir):
os.makedirs(target_dir)
appendix_len = len(str(len(res)))
result_files = []
for ii in range(0, len(res)):
res_filename = target_dir + '/' + str(ii+1).rjust(appendix_len, '0') + '.' + pathlib.Path(s_filename).suffix[1:]
pm4py.write_ocel(res[ii], res_filename)
result_files.append({
'id': s_filename + '-cluster-' + str(ii),
'type': 'cluster-' + pathlib.Path(res_filename).suffix[1:]
})
# returning
return jsonify({
'data': result_files
})
except Exception as e:
return jsonify({
'errors': [{
'name': 'general-exception',
'description': str(e),
}]})
@app.route("/ocel-cluster/<filename>/clusters", methods=['GET'])
def ocel_cluster_get(filename):
s_filename = secure_filename(filename)
if not exists(app.config['UPLOAD_FOLDER'] + '/' + s_filename):
return jsonify({
'errors': [{
'name': 'not-found',
'description': 'not found. upload file first.'
}]
})
suffix = get_suffix(s_filename)
file_list = sorted(get_result_files_for(s_filename))
res = []
for ii in range(len(file_list)):
res.append({
'id': s_filename + '-cluster-' + str(ii),
'type': 'clustered-' + suffix
})
if len(res) == 0:
return jsonify({
'errors': [{
'name': 'not-found',
'description': 'not found. execute POST first.'
}]
}), 404
else:
return jsonify({
'data': res
})
# missing: type of file has to be set accordingly! json/xml
@app.route("/ocel-cluster/<filename>/clusters/<number>", methods=['GET'])
def ocel_cluster_get_result_file(filename, number):
number = int(number)
res_files = get_result_files_for(filename)
if number >= len(res_files) or number < 0:
return jsonify({
'errors': [{
'name': 'not-found',
'description': 'index out of bounds. cant be less than zero and cant be higher than result count.'
}]
}), 404
full_filename = app.config['UPLOAD_FOLDER'] + '/' + filename + RESULT_FOLDER_SUFFIX + '/' + res_files[number]
text_file = open(full_filename, 'r')
data = text_file.read()
text_file.close()
return data
# implement different graph formats
@app.route("/ocel-cluster/<filename>/clusters/<number>/graph", methods=['GET'])
def ocel_cluster_get_visualization(filename, number):
suffix = '.svg'
number = int(number)
s_filename = secure_filename(filename)
res_files = get_result_files_for(s_filename)
if number >= len(res_files) or number < 0:
return jsonify({
'errors': [{
'name': 'not-found',
'description': 'index out of bounds. cant be less than zero and cant be higher than result count.'
}]
}), 404
full_filename = app.config['UPLOAD_FOLDER'] + '/' + s_filename + RESULT_FOLDER_SUFFIX + '/' + res_files[number]
ocel = pm4py.read_ocel(full_filename)
ocdfg = pm4py.discover_ocdfg(ocel)
tmp_file = tempfile.TemporaryFile(suffix=suffix, delete=False)
print(tmp_file.name)
pm4py.save_vis_ocdfg(ocdfg, tmp_file.name, act_threshold=int(request.args.get('activity_threshold', 0)), edge_threshold=int(request.args.get('edge_threshold', 0)))
tmp_file.delete = True
return send_file(tmp_file, attachment_filename=s_filename + '-cluster-' + str(number) + suffix)
if __name__ == "__main__":
SECRET_KEY = os.urandom(32)
app.config['SECRET_KEY'] = SECRET_KEY
app.run(debug=True)
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment