From 5ed478e298303e64a433de1ee5bf4a0347d5d053 Mon Sep 17 00:00:00 2001
From: jaylin <chieh.lin@rwth-aachen.de>
Date: Wed, 3 Apr 2024 17:33:35 +0200
Subject: [PATCH] add Index_constraint, remove none feature values, add
 recommand index column, fix time frame filter, improve outlook

---
 __pycache__/main.cpython-311.pyc          | Bin 87647 -> 91460 bytes
 __pycache__/main.cpython-39.pyc           | Bin 41145 -> 43116 bytes
 main.py                                   | 252 +++++++-----
 models/__pycache__/models.cpython-311.pyc | Bin 15303 -> 15303 bytes
 models/__pycache__/models.cpython-39.pyc  | Bin 9304 -> 9304 bytes
 models/models.py                          |  18 +-
 templates/app.html                        | 445 ++++++++++++++--------
 7 files changed, 451 insertions(+), 264 deletions(-)

diff --git a/__pycache__/main.cpython-311.pyc b/__pycache__/main.cpython-311.pyc
index 3d15975a5b1a12e3a15f7be1710a1cb8b3dfbcf9..483948315bf54879fb3460e718d1ddb0d09fb88d 100644
GIT binary patch
delta 14426
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

delta 12299
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

diff --git a/__pycache__/main.cpython-39.pyc b/__pycache__/main.cpython-39.pyc
index d378229d7ae9fbc0df103aa88b1b69fcb108054c..7b94461d4db815a99be3ffbd99e161d707761ef0 100644
GIT binary patch
delta 18496
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
YfS`Z`mokR}hbxBxhcE{R2R|1h0D`a<NB{r;

delta 16319
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
a6p{r5xg@xhITSctISe?2IXF1@xflT&-xa6;

diff --git a/main.py b/main.py
index 7b8a849..25942d3 100644
--- a/main.py
+++ b/main.py
@@ -1,4 +1,4 @@
-from datetime import datetime
+import datetime
 import pandas as pd
 from . import app
 from .models.models import CustomTable, CustomColumn, Theme, CompressedDataType, Observation_Spec, RegType, RegRole
@@ -12,6 +12,7 @@ from sqlalchemy.dialects.postgresql import JSONB, TSTZRANGE, INTERVAL, BYTEA, JS
 from sqlalchemy.dialects.sqlite import JSON, FLOAT, INTEGER, TIMESTAMP, TEXT, BOOLEAN, VARCHAR, NUMERIC, REAL
 from bs4 import BeautifulSoup
 from sqlalchemy.exc import SQLAlchemyError
+from typing import List, Tuple
 
 # Set up database (call db.engine)
 app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
@@ -411,21 +412,18 @@ def get_MD_info():
         object = getObjectColumns(res['tablename'])       # list
         if res['label'][0] == 'col' and res['label'][1] in object:
             object.remove(res['label'][1])
-
         return jsonify({'time': time, 'object': object})
     elif type == 'S':
         time = getTimeColumns(res['tablename'])          # list
         object = getObjectColumns(res['tablename'])      # list
-        index = getIndexColumns(res['tablename'])        # list
-        return jsonify({'time': time, 'object': object, 'index': index})
+        (index, pk_index) = getIndexColumns(res['tablename'], True)        # list
+        return jsonify({'time': time, 'object': object, 'index': index, 'pk_index': pk_index})
     elif type == 'SD':
         data_header = session.get('data_header', {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]})
         object = getObjectColumns(res['tablename'])      # list
-        index = getIndexColumns(res['tablename'])        # list
+        (index, fk_index) = getIndexColumns(res['tablename'], False)        # list
         segment = [segment['label'][2] for segment in data_header['segment']]
-        print("Segment options")
-        print(segment)
-        return jsonify({'object': object, 'index': index, 'segment': segment})
+        return jsonify({'object': object, 'index': index, 'segment': segment, 'fk_index': fk_index})
 
 
 @app.route('/get-ME-table', methods=['POST'])
@@ -447,8 +445,8 @@ def get_ME_table():
             features.append(feature)
         else:
             features.append(feature)
-    start_time = datetime.strptime(data['minDatetime'], '%Y-%m-%d %H:%M:%S') if 'minDatetime' in data else None
-    end_time = datetime.strptime(data['maxDatetime'], '%Y-%m-%d %H:%M:%S') if 'maxDatetime' in data else None
+    start_time = datetime.datetime.strptime(data['minDatetime'], '%Y-%m-%d %H:%M:%S') if 'minDatetime' in data else None
+    end_time = datetime.datetime.strptime(data['maxDatetime'], '%Y-%m-%d %H:%M:%S') if 'maxDatetime' in data else None
 
     print(table_name)
     print(type)
@@ -457,14 +455,15 @@ def get_ME_table():
     print(label_list)
     print(features)
 
-    query_result = extract_ME_table(engine, table_name, type, time_column, object_list, label_list, features, start_time, end_time)
-    table_HTML = get_ME_table_HTML(query_result)
-
     if start_time == None and end_time == None:
-        min_datetime, max_datetime = get_min_max_datetime(engine, table_name, time_column)
-        return jsonify({'table_HTML': table_HTML, 'min_datetime': min_datetime, 'max_datetime': max_datetime})
+        query_result, row_count, min_datetime, max_datetime = extract_ME_table(engine, table_name, type, time_column, object_list, label_list, features, start_time, end_time)
+        table_HTML = get_ME_table_HTML(query_result)
+        return jsonify({'table_HTML': table_HTML, 'min_datetime': min_datetime, 'max_datetime': max_datetime, 'row_count': str(row_count)})
 
-    return jsonify({'table_HTML': table_HTML})
+    query_result, row_count = extract_ME_table(engine, table_name, type, time_column, object_list, label_list, features, start_time, end_time)
+    table_HTML = get_ME_table_HTML(query_result)
+
+    return jsonify({'table_HTML': table_HTML, 'row_count': str(row_count)})
 
 
 @app.route('/get-S-table', methods=['POST'])
@@ -482,8 +481,8 @@ def get_S_table():
     endtime_column = data['endtime_column']
     label_list = current_data_header['label']
     
-    start_time = datetime.strptime(data['minDatetime'], '%Y-%m-%d %H:%M:%S') if 'minDatetime' in data else None
-    end_time = datetime.strptime(data['maxDatetime'], '%Y-%m-%d %H:%M:%S') if 'maxDatetime' in data else None
+    start_time = datetime.datetime.strptime(data['minDatetime'], '%Y-%m-%d %H:%M:%S') if 'minDatetime' in data else None
+    end_time = datetime.datetime.strptime(data['maxDatetime'], '%Y-%m-%d %H:%M:%S') if 'maxDatetime' in data else None
 
     print(table_name)
     print(type)
@@ -492,14 +491,15 @@ def get_S_table():
     print(object_list)
     print(label_list)
 
-    query_result = extract_S_table(engine, table_name, starttime_column, endtime_column, index_column, object_list, label_list, start_time, end_time)
-    table_HTML = get_ME_table_HTML(query_result)
-
     if start_time == None and end_time == None:
-        min_datetime, max_datetime = get_min_max_datetime2(engine, table_name, starttime_column, endtime_column)
-        return jsonify({'table_HTML': table_HTML, 'min_datetime': min_datetime, 'max_datetime': max_datetime})
+        query_result, row_count, min_datetime, max_datetime = extract_S_table(engine, table_name, starttime_column, endtime_column, index_column, object_list, label_list, start_time, end_time)
+        table_HTML = get_ME_table_HTML(query_result)
+        return jsonify({'table_HTML': table_HTML, 'min_datetime': min_datetime, 'max_datetime': max_datetime, 'row_count': str(row_count)})
 
-    return jsonify({'table_HTML': table_HTML})
+    query_result, row_count = extract_S_table(engine, table_name, starttime_column, endtime_column, index_column, object_list, label_list, start_time, end_time)
+    table_HTML = get_ME_table_HTML(query_result)
+
+    return jsonify({'table_HTML': table_HTML, 'row_count': str(row_count)})
 
 
 @app.route('/get-SD-table', methods=['POST'])
@@ -535,14 +535,15 @@ def get_SD_table():
     index_from = int(data['index_from']) if 'index_from' in data else None
     index_to = int(data['index_to']) if 'index_to' in data else None
 
-    query_result = extract_SD_table(engine, table_name, object_list, label_list, segment_column, index_column, features, index_from, index_to)
-    table_HTML = get_ME_table_HTML(query_result)
-
     if index_from == None and index_to == None:
-        min_index, max_index = get_min_max_index(engine, table_name, index_column)
-        return jsonify({'table_HTML': table_HTML, 'min_index': min_index, 'max_index': max_index})
+        query_result, row_count, min_index, max_index = extract_SD_table(engine, table_name, object_list, label_list, segment_column, index_column, features, index_from, index_to)
+        table_HTML = get_ME_table_HTML(query_result)
+        return jsonify({'table_HTML': table_HTML, 'row_count': str(row_count), 'min_index': min_index, 'max_index': max_index})
+    
+    query_result, row_count = extract_SD_table(engine, table_name, object_list, label_list, segment_column, index_column, features, index_from, index_to)
+    table_HTML = get_ME_table_HTML(query_result)
 
-    return jsonify({'table_HTML': table_HTML})
+    return jsonify({'table_HTML': table_HTML, 'row_count': str(row_count)})
 
 
 @app.route('/add-data-table', methods=['POST'])
@@ -557,10 +558,10 @@ def add_data_table():
 
     if current_row_type in ['E', 'M']:
         data_tables['O'].extend(selected_rows)
-        data_tables['O'].sort(key=lambda x: datetime.strptime(x.get('column1', '9999-12-31 23:59:59.00000'), '%Y-%m-%d %H:%M:%S.%f'))  # Adjust 'columnTime' to your time column key
+        data_tables['O'].sort(key=lambda x: datetime.datetime.strptime(x.get('column1', '9999-12-31 23:59:59.00000'), '%Y-%m-%d %H:%M:%S.%f'))  # Adjust 'columnTime' to your time column key
     elif current_row_type == 'S':
         data_tables[current_row_type].extend(selected_rows)
-        data_tables['S'].sort(key=lambda x:  datetime.strptime(x.get('column4', '9999-12-31 23:59:59.00000'), '%Y-%m-%d %H:%M:%S.%f'))  # Adjust 'columnTime' to your time column key
+        data_tables['S'].sort(key=lambda x:  datetime.datetime.strptime(x.get('column4', '9999-12-31 23:59:59.00000'), '%Y-%m-%d %H:%M:%S.%f'))  # Adjust 'columnTime' to your time column key
     else :
         data_tables['SD'].extend(selected_rows)
         data_tables['SD'].sort(key=lambda x: x.get('column2', ''))  # Group data by 'label'
@@ -870,11 +871,9 @@ def generate_html_header_table():
             table_html += "<td><button type='button' class='btn-close'  aria-label='Close' onclick='deleteRow1(event, this)'></button></td>"
             table_html += f"<td>{dict_item.get('tablename', '')}</td>"
             table_html += f"<td data-id>{dict_item.get('type', '')}</td>"
-            print("723723")
             print(dict_item.get('label', ''))
             label_value = json.dumps(dict_item.get('label', ''))
             table_html += f"<td data-value='{label_value}'>{dict_item.get('label', '')[2]}</td>"
-            print("823823")
             for value in dict_item.get('features_name', []):
                 if '(' in value and ')' in value:
                     feature_cloumn = value.split('(')[0]
@@ -882,7 +881,6 @@ def generate_html_header_table():
                     multiple_columns = tuple(value.split('(')[1].split(')')[0].replace("'","").split(', '))
                     print(multiple_columns)
                     for column in multiple_columns:
-                        print("624624")
                         tmp = [feature_cloumn]
                         for col in multiple_columns:
                             tmp.append(col)
@@ -906,7 +904,7 @@ def generate_html_header_table():
         table_html += "</tr>"
 
     table_html += "</tbody></table>"
-    print(table_html)
+    # print(table_html)
     return table_html
 
 
@@ -947,13 +945,13 @@ def generate_html_data_table(data_tables:list, table_type:str):
             table_html += "<td><button type='button' class='btn-close'  aria-label='Close' onclick='deleteRow2(event, this)'></button></td>"
             for i in range(len(row) - 1):
                 if i == 1:
-                    table_html += f"<td>{row.get('column3', '')}</td>"
+                    table_html += f"<td>{row.get('column3', '')}</td>"  # object
                 elif i == 2:
-                    table_html += f"<td>{row.get('column4', '')}</td>"
+                    table_html += f"<td>{row.get('column4', '')}</td>"  # segment
                 elif i == 3:
-                    table_html += f"<td>{row.get('column2', '')}</td>"
+                    table_html += f"<td>{row.get('column2', '')}</td>"  # segment_index
                 else:
-                    table_html += f"<td>{row.get('column' + str(i+1), '')}</td>"
+                    table_html += f"<td>{row.get('column' + str(i+1), '')}</td>"  # label or feature
             table_html += "</tr>"
     else:    
         for row in data_tables:
@@ -1013,16 +1011,28 @@ def getObjectColumns(table_name:str) -> list:
     return object_columns
 
 
-def getIndexColumns(table_name:str) -> list:
+def getIndexColumns(table_name:str, isSegmentTable:bool) -> Tuple[List[str], List[str]]:
     engine = create_engine(session.get('db_uri', ''))
     insp = inspect(engine)
     schema = getTableSchema(table_name) if insp.dialect.name == 'postgresql' else insp.default_schema_name
     columns = insp.get_columns(table_name, schema)
+    table = getTableInstance(engine, table_name)
+
+    fk_or_pk_list = []
+    if isSegmentTable:
+        for col in table.columns:
+            if col.ispk == True:
+                fk_or_pk_list.append(col.name)
+    else:
+        for col in table.columns:
+            if col.fkof != None:
+                fk_or_pk_list.append(col.name)
+
     index_columns = [column['name'] for column in columns if str(column['type']) == 'INTEGER' or  str(column['type']) == 'NUMERIC' or  str(column['type']) == 'BIGINT' or  str(column['type']) == 'SMALLINT']
     print("index columns")
     print(index_columns)
 
-    return index_columns
+    return (index_columns, fk_or_pk_list)
 
 
 def query_database_for_table_content(engine, table_name, number=200):
@@ -1076,7 +1086,7 @@ def getSchema(insp):
     return schemas
 
 
-def getTableInstance(engine, table_name):
+def getTableInstance(engine, table_name) -> CustomTable:
     insp = inspect(engine)
     table = importMetadata(engine, None, [table_name], True)[table_name]
     return table
@@ -1099,19 +1109,22 @@ def showDistinctValues(engine, table_name, column_name):
     return names
 
 
-def get_min_max_datetime(engine, table_name, time_column, start_time=None, end_time=None):
+def get_min_max_datetime(engine, table_name, time_column, label_value, sql_where=None):
     schema = getTableSchema(table_name) if engine.dialect.name == 'postgresql' else engine.dialect.default_schema_name
     # Formulate the SQL query using the text function
-    query = text(f"SELECT MIN({time_column}) AS start_datetime, MAX({time_column}) AS end_datetime FROM {schema}.{table_name};")
+    sql_join = ''
+    query = text(f"SELECT MIN({time_column}) AS start_datetime, MAX({time_column}) AS end_datetime FROM {schema}.{table_name} {sql_join} {sql_where};")
+
+    params = {'label_value': label_value}
 
     # Execute the query
     with engine.connect() as connection:
-        row = connection.execute(query).mappings().fetchone()
-
+        row = connection.execute(query, params).mappings().fetchone()
 
     # Extract the min and max datetime values
     if row:
-        min_datetime, max_datetime = row['start_datetime'], row['end_datetime']
+        min_datetime = row['start_datetime'].replace(tzinfo=datetime.timezone.utc).isoformat()
+        max_datetime = row['end_datetime'].replace(tzinfo=datetime.timezone.utc).isoformat()
         print("Minimum datetime:", min_datetime)
         print("Maximum datetime:", max_datetime)
         return min_datetime, max_datetime
@@ -1120,18 +1133,22 @@ def get_min_max_datetime(engine, table_name, time_column, start_time=None, end_t
         return None, None
 
 
-def get_min_max_datetime2(engine, table_name, starttime_column, endtime_column, start_time=None, end_time=None):
+def get_min_max_datetime2(engine, table_name, starttime_column, endtime_column, label_value, sql_where=None):
     schema = getTableSchema(table_name) if engine.dialect.name == 'postgresql' else engine.dialect.default_schema_name
     # Formulate the SQL query using the text function
-    query = text(f"SELECT MIN({starttime_column}) AS start_datetime, MAX({endtime_column}) AS end_datetime FROM {schema}.{table_name};")
+    sql_join = ''
+    query = text(f"SELECT MIN({starttime_column}) AS start_datetime, MAX({endtime_column}) AS end_datetime FROM {schema}.{table_name} {sql_join} {sql_where};")
+
+    params = {'label_value': label_value}
 
     # Execute the query
     with engine.connect() as connection:
-        row = connection.execute(query).mappings().fetchone()
+        row = connection.execute(query, params).mappings().fetchone()
 
     # Extract the min and max datetime values
     if row:
-        min_datetime, max_datetime = row['start_datetime'], row['end_datetime']
+        min_datetime = row['start_datetime'].replace(tzinfo=datetime.timezone.utc).isoformat()
+        max_datetime = row['end_datetime'].replace(tzinfo=datetime.timezone.utc).isoformat()
         print("Minimum datetime:", min_datetime)
         print("Maximum datetime:", max_datetime)
         return min_datetime, max_datetime
@@ -1140,14 +1157,17 @@ def get_min_max_datetime2(engine, table_name, starttime_column, endtime_column,
         return None, None
 
 
-def get_min_max_index(engine, table_name, index_column):
+def get_min_max_index(engine, table_name, index_column, label_value, sql_where):
     schema = getTableSchema(table_name) if engine.dialect.name == 'postgresql' else engine.dialect.default_schema_name
     # Formulate the SQL query using the text function
-    query = text(f"SELECT MIN({index_column}) AS start_index, MAX({index_column}) AS end_index FROM {schema}.{table_name};")
+    sql_join = ''
+    query = text(f"SELECT MIN({index_column}) AS start_index, MAX({index_column}) AS end_index FROM {schema}.{table_name} {sql_join} {sql_where};")
+
+    params = {'label_value': label_value}
 
     # Execute the query
     with engine.connect() as connection:
-        row = connection.execute(query).mappings().fetchone()
+        row = connection.execute(query, params).mappings().fetchone()
 
     # Extract the min and max datetime values
     if row:
@@ -1160,7 +1180,7 @@ def get_min_max_index(engine, table_name, index_column):
         return None, None
 
 
-def extract_ME_table(engine, table_name: str, type: str, time_column: str, object: list, label: list, features_name: list, start_time: datetime = None, end_time: datetime = None) -> list:
+def extract_ME_table(engine, table_name: str, type: str, time_column: str, object: list, label: list, features_name: list, start_time: datetime = None, end_time: datetime = None):
     conn = engine.connect()
     insp = inspect(engine)
     database_name = insp.dialect.name      # 'postgresql' or 'sqlite'
@@ -1223,7 +1243,7 @@ def extract_ME_table(engine, table_name: str, type: str, time_column: str, objec
         if end_time:
             sql_where += f" AND {full_table_name}.{time_column} <= :end_time"
 
-    sql_query = f"SELECT {sql_select} FROM {full_table_name} {sql_joins} {sql_where} ORDER BY {time_column} ASC LIMIT 500"
+    sql_query = f"SELECT {sql_select} FROM {full_table_name} {sql_joins} {sql_where} ORDER BY {time_column} ASC"
     print("12345")
     # Executing the query
     params = {'label_value': label_value}
@@ -1244,7 +1264,7 @@ def extract_ME_table(engine, table_name: str, type: str, time_column: str, objec
         return []
 
     # Append object and label values if necessary
-    final_res = []
+    final_res = [] # List of rows
     for row in res:
         modified_row = list(row)
         if object[0].strip()  == 'self':
@@ -1255,10 +1275,15 @@ def extract_ME_table(engine, table_name: str, type: str, time_column: str, objec
         modified_row.insert(2, type)
         final_res.append(modified_row)
 
-    for row in final_res:
-        print(row)
+    # for row in final_res:
+    #     print(row)
+    print("Row count", len(final_res))
 
-    return final_res
+    if start_time == None and end_time == None:
+        start_time, end_time = get_min_max_datetime(engine, table_name, time_column, label_value, sql_where)
+        return final_res, len(final_res), start_time, end_time
+
+    return final_res, len(final_res)
 
 
 def extract_S_table(engine, table_name: str, starttime_column: str, endtime_column: str, index_column: str, object: list, label: list, start_time: datetime = None, end_time: datetime = None) -> list:
@@ -1315,7 +1340,7 @@ def extract_S_table(engine, table_name: str, starttime_column: str, endtime_colu
         if end_time:
             sql_where += f" AND {full_table_name}.{starttime_column} <= :end_time AND {full_table_name}.{endtime_column} <= :end_time"
 
-    sql_query = f"SELECT {sql_select} FROM {full_table_name} {sql_joins} {sql_where} ORDER BY {starttime_column} ASC LIMIT 500"
+    sql_query = f"SELECT {sql_select} FROM {full_table_name} {sql_joins} {sql_where} ORDER BY {starttime_column} ASC"
     print("12345")
     # Executing the query
     params = {'label_value': label_value}
@@ -1347,10 +1372,15 @@ def extract_S_table(engine, table_name: str, starttime_column: str, endtime_colu
 
         final_res.append(modified_row)
 
-    for row in final_res:
-        print(row)
+    # for row in final_res:
+    #     print(row)
+    print("Row count", len(final_res))
+
+    if start_time == None and end_time == None:
+        start_time, end_time = get_min_max_datetime2(engine, table_name, starttime_column, endtime_column, label_value, sql_where)
+        return final_res, len(final_res), start_time, end_time
 
-    return final_res
+    return final_res, len(final_res)
 
 
 def extract_SD_table(engine, table_name: str, object: list, label: list, segment_column: str, index_column: str, features_name: list, index_from: int = None, index_to: int = None) -> list:
@@ -1385,8 +1415,10 @@ def extract_SD_table(engine, table_name: str, object: list, label: list, segment
     print("123")
     # Adding index columns to the select clause 
     sql_columns.append(f"{full_table_name}.{index_column}")
+
     # Handling JSON extractions
     json_extractions = []
+    json_non_none_checks = []
     for feature in features_name:
         if '(' in feature and ')' in feature:
             column_name, keys = feature[:-1].split('(')
@@ -1394,31 +1426,48 @@ def extract_SD_table(engine, table_name: str, object: list, label: list, segment
             for key in keys:
                 if database_name == 'postgresql':
                     json_extraction = f"{full_table_name}.{column_name}->>'{key}' AS {key}"
+                    # For checking non-None JSON keys
+                    json_non_none_checks.append(f"{full_table_name}.{column_name}->>'{key}' IS NOT NULL AND {full_table_name}.{column_name}->>'{key}' != ''")
                 elif database_name == 'sqlite':
                     json_extraction = f"json_extract({full_table_name}.{column_name}, '$.{key}') AS {key}"
+                    # Adjust the check for SQLite if necessary
+                    json_non_none_checks.append(f"json_extract({full_table_name}.{column_name}, '$.{key}') IS NOT NULL")
                 json_extractions.append(json_extraction)
         else:
             sql_columns.append(f"{full_table_name}.{feature}")
-    print("1234")
+            # Non-JSON field non-None check
+            json_non_none_checks.append(f"{full_table_name}.{feature} IS NOT NULL")
+
     # Adding JSON extractions to the select clause
     sql_select = ', '.join(sql_columns + json_extractions)
-    
+
     # Constructing SQL query
     sql_joins = join_clause
+    
+    # Building the WHERE clause with segment index filtering
+    sql_where_list = []
     if label[0].strip() == 'col':
-        sql_where = f"WHERE {full_table_name}.{label_column} = :label_value"
-        if index_from:
-            sql_where += f" AND {full_table_name}.{index_column} >= :index_from"
-        if index_to:
-            sql_where += f" AND {full_table_name}.{index_column} <= :index_to"
-    else:
-        sql_where = ''
-        if index_from:
-            sql_where += f" WHERE {full_table_name}.{index_column} >= :index_from"
-        if index_to:
-            sql_where += f" AND {full_table_name}.{index_column} <= :index_to"
+        sql_where_list.append(f"{full_table_name}.{label_column} = :label_value")
+    if index_from:
+        sql_where_list.append(f"{full_table_name}.{index_column} >= :index_from")
+    if index_to:
+        sql_where_list.append(f"{full_table_name}.{index_column} <= :index_to")
+
+    # Add non-None checks for JSON and non-JSON fields
+    if json_non_none_checks:
+        sql_where_list.append(f"({' OR '.join(json_non_none_checks)})")
+        
+    # Add segment index filtering
+    valid_segment_indices = get_distinct_segment_indices(segment_column)  # Set of segment indices with segment label = segment_column in the data table with type S
+    indices = ', '.join(map(str, valid_segment_indices)) if valid_segment_indices else ''
+    if indices == '':
+        print("No valid segment indices found.")
+        return [], 0, 0, 0
+    sql_where_list.append(f"{full_table_name}.{index_column} IN ({indices})")
+
+    sql_where = f"WHERE {' AND '.join(sql_where_list)}"
 
-    sql_query = f"SELECT {sql_select} FROM {full_table_name} {sql_joins} {sql_where} ORDER BY {index_column} ASC LIMIT 500"
+    sql_query = f"SELECT {sql_select} FROM {full_table_name} {sql_joins} {sql_where} ORDER BY {index_column} ASC"
     print("12345")
     # Executing the query
     params = {'label_value': label_value, 'index_from': index_from, 'index_to': index_to}
@@ -1441,15 +1490,42 @@ def extract_SD_table(engine, table_name: str, object: list, label: list, segment
         if object[0].strip()  == 'self':
             modified_row.insert(0, object_column_value)
         if label[0].strip()  == 'self':
-            label_index = 1 if object[0].strip()  != 'self' else 0
+            label_index = 1 if object[0].strip()  != 'self' else 1
             modified_row.insert(label_index, label[2])
         modified_row.insert(2, segment_column)
         final_res.append(modified_row)
 
-    for row in final_res:
-        print(row)
+    # for row in final_res:
+    #     print(row)
+    print("Row count", len(final_res))
+
+    if index_from == None and index_to == None:
+        index_from, index_to = get_min_max_index(engine, table_name, index_column, label_value, sql_where)
+        return final_res, len(final_res), index_from, index_to
+
+    return final_res, len(final_res)
 
-    return final_res
+
+def get_distinct_segment_indices(segment_label:str) -> set:
+    data_tables = session.get('data_tables', {'O':[], 'S':[], 'SD':[]})
+    data_table = data_tables.get('S', [])
+
+    distinct_values = set()
+    print("segment label " + segment_label)
+    for row in data_table:
+        print(row)
+        # Ensure both strings are stripped of leading/trailing whitespace before comparison
+        if row.get('column2', '').strip() == segment_label.strip():
+            segment_index = row.get('column3', '').strip()  # Also strip any whitespace from the index
+            print("I AM HERE" + segment_index)
+            distinct_values.add(segment_index)
+    
+    # Print the distinct values
+    print("Distinct segment indices from S data table:")
+    for value in distinct_values:
+        print(value)
+    
+    return distinct_values
 
 
 def get_ME_table_HTML(data: list) -> str:
@@ -1460,7 +1536,7 @@ def get_ME_table_HTML(data: list) -> str:
     for row in data:
         html_content += "<tr><td><input class='uk-checkbox' type='checkbox' aria-label='Checkbox'></td>"
         for cell in row:
-            if isinstance(cell, datetime):
+            if isinstance(cell, datetime.datetime):
                 # cell = cell.isoformat()
                 cell = cell.strftime('%Y-%m-%d %H:%M:%S.%f') 
             html_content += f"<td>{cell}</td>"
@@ -1469,7 +1545,7 @@ def get_ME_table_HTML(data: list) -> str:
     return html_content
 
 
-def importMetadata(engine, schema=None, tables_selected=None, show_all=False):
+def importMetadata(engine, schema=None, tables_selected=None, show_all=False) -> dict:  # -> Dict[str, CustomTable]
     tables = {}
     if engine == None:
         return tables
@@ -1670,9 +1746,9 @@ def fetch_constraints_for_tables(engine, tables):
 
                     if fkColumn and pkColumn:
                         fkColumn.fkof = pkColumn
-                        if fk['name'] not in table.fks:
-                            table.fks[fk['name']] = []
-                        table.fks[fk['name']].append(fkColumn)
+                        if referred_table not in table.fks: 
+                            table.fks[referred_table] = []
+                        table.fks[referred_table].append(fkColumn)
 
     return tables
 
diff --git a/models/__pycache__/models.cpython-311.pyc b/models/__pycache__/models.cpython-311.pyc
index 780e177e25900a2bae7265a22e612d24724b20c3..5392b414bd04ce2f81d54089b3d43f92814b8e68 100644
GIT binary patch
delta 18
ZcmX?Je!QG>IWI340|Ntt$wtn7)&Mzz1vmfz

delta 18
ZcmX?Je!QG>IWI340|Ntt(nijG)&Myu1ttIh

diff --git a/models/__pycache__/models.cpython-39.pyc b/models/__pycache__/models.cpython-39.pyc
index 4dcb5a8fa921e8dc45deed97019797f6eac214e2..1c63b8dea427ada5d48f89e1fd38f8e40f3f28fa 100644
GIT binary patch
delta 223
zcmccNal?Zzk(ZZ?fq{YHL@96DdzFoRBAkqdla)DR7!5WzaE3E7I!wO7^_9_k@;UCQ
zjDeF=c;+#NY-Z*)XJL%mY%Tbnk+E{Jw(u9muE`1_XBm4ZzZ1!1?42ATTFuzH`HH9k
zBjc3GHWFHs^~KjSPM>^VJce=lWE+WE#tD;mNz7oh-yAHN!^k*i@&Tz<##Ni$q_;CM
zuG{=iR+EwO%w`RFdq&2alQR_R7-vs@u5g9%<>Wnz(Tp!9ODN4?d^>rgl0D;_$sd(w
fFnwg0+^uZK^nqdWY2_wv0X}}d7(QPC76BFjCrnGz

delta 223
zcmccNal?Zzk(ZZ?fq{X+q@FR2QGFwy2q&ZCWM$46Mu*J}oZ*a&0h4cVePxWEe2#l6
zW8&l#o_UNZo0)mdSs1f6TMNEtWbB-*E&PRX)?@{dvy5{mzZ1!1oI5!}w3>11<}0EG
zjEqYr+em0l))!yTxP0<`@fgPClWinw85d06B{751e{-;84kP25$p@rb8Fy`Vlitq6
zxNq}6SxrX9JDWA+?HL(gPR>xMV_ZG?xxy93pOg0}Ml=4HETJ@m@$ck~O7@I@CVy0#
f!NkZoxm($eiGgwQY2_wv1wMYh7(QPC76BFjiCaoE

diff --git a/models/models.py b/models/models.py
index 9c517d5..2017989 100644
--- a/models/models.py
+++ b/models/models.py
@@ -6,7 +6,7 @@ class Observation_Spec:
     def __init__(self, tablename:str, type:str, label:list[str], features_name:list = None):
         self.tablename = tablename
         self.type = type    # 'event' or 'measurement'
-        self.label = label  # (self-define or from column, column name, column value or self-define label)
+        self.label = label  # [self-define or from column, column name, column value or self-define label]
         self.features_name = features_name if features_name else []
 
     def add_feature(self, name):
@@ -44,22 +44,6 @@ class Obesrvation(Observation_Spec):
     def add_feature(self, feature_name, feature_value):
         self.features[feature_name] = feature_value
 
-# class Segment_Spec:
-#     def __init__(self, type, label, features=None):
-#         self.type = type    # 'event' or 'measurement'
-#         self.label = label
-#         self.features = features if features else {}
-
-#     def add_feature(self, feature_name):
-#         self.features[feature_name] = feature_value
-
-# class Segment(Segment_Spec):
-#     def __init__(self, time, type, object, label, features=None):
-#         super().__init__(type, label, features=None)
-#         self.time = time
-#         self.object = object
-
-
 
 class Theme:
     def __init__(self, color, fillcolor, fillcolorC,
diff --git a/templates/app.html b/templates/app.html
index 44e755e..36462f1 100644
--- a/templates/app.html
+++ b/templates/app.html
@@ -160,12 +160,12 @@
         }
         .resize-handle-left {
             position: sticky;
-            margin-top: -100px;
+            margin-top: -550px;
             margin-left: -13px; /* Adjust this value to position the handle to the left */
             width: 8px; /* Width of the handle */
-            height: 100vh;
+            height: 100%;
             cursor: ew-resize;
-            z-index: 10;
+            z-index: 100;
         }
         .custom-buttom-bar {
             display: flex;
@@ -293,6 +293,49 @@
             height: 83vh; /* or adjust as necessary */
             overflow: hidden; /* This prevents the overall container from scrolling */
         }
+
+        .key {
+            display: inline-block;
+            width: 15px;
+            height: 15px;
+            margin-right: 5px;
+            line-height: 200%;
+        }
+
+        .primary-key {
+            /* padding-bottom: -3px; */
+            border-bottom: 1px solid black; /* Represent underline */
+            width: 15px;
+            height: 15px;
+            margin-right: 5px;
+            line-height: 120%;
+        }
+
+        .foreign-key {
+            font-style: italic; /* Represent foreign key */
+        }
+
+        .nullable {
+            content: "*"; /* Represent nullable */
+            border-top: 2px;
+        }
+
+        .identity {
+            content: "I"; /* Represent identity column */
+        }
+
+        .unique {
+            content: "U"; /* Represent unique constraint */
+        }
+
+        .normal-fk {
+            border-bottom: 3px solid black; /* Represent nullable FK relationship */
+        }
+
+        .optional-fk {
+            border-bottom: 3px dashed black; /* Represent nullable FK relationship */
+        }
+
     </style>
 </head>
 <body>
@@ -303,9 +346,9 @@
     <div id="sidebar1">   <!--class="uk-panel uk-panel-scrollable" -->
         <legend uk-tooltip="title: Show the database name.; pos: right">Database:</legend>
         <div class="mb-3">
-            <label for="disabledTextInput" class="form-label" style="margin-top: 10px;">{{database}}.db</label>
+            <label class="form-label" style="margin-top: 10px;">{{database}}.db</label>
         </div>
-        
+        <hr>
         <ul uk-accordion>
             <li>
                 <a class="uk-accordion-title" href="#" style="text-decoration: none;" uk-tooltip="title: Provide a valid databsae connection here.; pos: right">Database URL</a>
@@ -394,10 +437,9 @@
             </li>
 
 
-            <li class="uk-class uk-open"></li>
+            <li class="uk-open">
                 <a class="uk-accordion-title" href="#" style="text-decoration: none;" uk-tooltip="title: Reorder the tables by dragging the rows to the designated area below.; pos: right">Tables</a>
                 <div class="uk-accordion-content">
-
                     <div class="border border-secondary rounded" id="table_list" style="margin-bottom: 5px; margin-top: -10px;">
                         <div id="table_list_source">
                             <div id="show_tables1" uk-sortable="group: sortable-group" class="uk-list  uk-list-collapse">
@@ -409,17 +451,15 @@
                             </div>
                         </div>
                     </div>
-    
                 </div>
             </li>
         </ul>
-
-
-        <legend style="margin-top: 3px;" uk-tooltip="title: Activate the selected table to populate the data in the scrollable terminal section and for Step 2 procedures.; pos: right">Target Tables:</legend>
-        <div class="border border-secondary rounded" id="table_list" >
+        <hr>
+        <legend style="margin-top: 2px;" uk-tooltip="title: Activate the selected table to populate the data in the scrollable terminal section on the bottom and for Step 2 procedures.; pos: right">Target Tables:</legend>
+        <div class="border border-secondary rounded" id="table_list" style="margin-top: 3px;">
             <div id="dropped_items" uk-sortable="group: sortable-group" class="uk-list uk-list-collapse ">
                 {% for item in dropped_items %}
-                    <div class="uk-margin" style="height: 15px; margin-bottom: -4px; width: 230px;">
+                    <div class="uk-margin" style="height: 23px; margin-bottom: -4px; width: 230px;">
                         <div class="list-group-item-action uk-card uk-card-default uk-card-body uk-card-small"  style="height: 15px; padding-top: 5px;">{{ item }}</div>
                     </div>
                 {% endfor %}
@@ -431,10 +471,10 @@
     <div id="content">
         <button class="uk-button uk-button-default uk-button-small custom-push-button" type="button" uk-toggle="target: #sidebar1" onclick="toggleSidebar()"></button>      
         <ul uk-tab class="uk-flex-center" data-uk-tab="{connect:'#my-id'}" style="margin-top: 10px; z-index: 0;">
-            <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Display a comprehensive summary of the database contents.;pos: bottom">Step1: Overview</a></li>
-            <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Construct the data header based on the selection from the prior step, to be utilized in Step 3.;pos: bottom">Step2: Create Data Header</a></li>
-            <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Generate a detailed data table corresponding to each row of the data header, with options for additional refinement.;pos: bottom">Step3: Create Data instance</a></li>
-            <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Display the generated data tables, with options for exporting to csv files.;pos: bottom">Step4: Established Data Table</a></li>
+            <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Display a comprehensive structure of the database contents.;pos: bottom">Step1: Overview</a></li>
+            <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Construct data row for header based on the selection from the target table section, an overview of header table is shown in Step 3.;pos: bottom">Step2: Create Header</a></li>
+            <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Generate a detailed data table corresponding to each row of the data header, with options for additional refinement on the right sidebar.;pos: bottom">Step3: Select Data Instance</a></li>
+            <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Display the generated data tables, with options for exporting to csv files.;pos: bottom">Step4: Data Table Overvieew</a></li>
         </ul>
 
         <!-- <div> -->
@@ -443,7 +483,7 @@
                 <div style="display: flex; flex-direction: row; height: 85vh; border: 0px 1px;">
 
                     <div style="flex: 0,3; display: flex; flex-direction: column;">
-                        <h4 style="margin: .0em .0em .6em .2em;">Filtered ERD</h4>
+                        <h4 style="margin: .0em .0em .6em .2em;" uk-tooltip= "title: Show overall Entity-Relationship-Diagram (ERD), optional filtering tool can be applied for constraining this ERD with the table instances showing on the Tables section. ; pos: bottom">Filtered Tables - ERD</h4>
                         <div class="uk-panel uk-panel-scrollable" id="canvasContainer" style="margin-right: -1px; min-width: 30%;">
                             <canvas id="erdCanvas1" style="min-width: 30%;"></canvas>
                             <div id="zoomButtons">
@@ -454,7 +494,7 @@
                     </div>
 
                     <div style="flex: 0,7; display: flex; flex-direction: column;">
-                        <h4 style="margin: .0em .0em .6em .2em;">Target ERD</h4>
+                        <h4 style="margin: .0em .0em .6em .2em;" uk-tooltip="title: Build a customized Entity-Relationship-Diagram by dragging the table instances in Tables section on the left sidebar into the Target Tables section below.; pos: bottom">Target Tables - ERD</h4>
                         <div class="uk-panel" id="canvasContainer" style="overflow-y: scroll; min-width: 20%; width: 100%;">
                             <canvas id="erdCanvas2" style="min-width: 30%;"></canvas>
                             <div id="zoomButtons">
@@ -471,7 +511,7 @@
                 <div style="display: flex; flex-direction: row; height: 87vh;">
 
                     <div id="scrollable-panel" style="display: flex; flex-direction: column;">
-                        <h4 style="margin: .0em .0em .6em .2em;">Target ERD</h4>
+                        <h4 style="margin: .0em .0em .6em .2em;" uk-tooltip="Build a customized Entity-Relationship-Diagram by dragging the table instances in Tables section on the left sidebar into the Target Tables section below.";>Target Tables - ERD</h4>
                         <div class="uk-panel" id="canvasContainer" style="overflow-y: auto;">
                             <canvas id="erdCanvas3" style="min-width: 30%;"></canvas>
                             <div id="zoomButtons">
@@ -484,10 +524,10 @@
                     <div style="flex-grow: 1; padding-left: 10px; margin-top: 3px; display: flex; flex-direction: column;">
                     
                     <fieldset>
-                        <legend style="margin: .2em .2em .4em -.4em; color:#5ea9e2; border-bottom: 1px solid gray; padding-bottom: 5px; padding-right: 3px;" uk-tooltip="title: Define the structure of the header table to be used in Step 3; pos: right">Data Header</legend>
+                        <legend style="margin: .2em .2em .4em -.4em; color:#5ea9e2; border-bottom: 1px solid gray; padding-bottom: 5px; padding-right: 3px;" uk-tooltip="title: Define the structure of the header table to be used in Step 3. Active one table instance by clicking table in Target Tables section on the left sidebar.; pos: right">Data Header</legend>
 
                         <div class="mb-3" style="padding-bottom: 5px; border-bottom: 1px dashed black; margin-bottom: 3px;">
-                            <label class="form-label" style="margin-top: 8px;" uk-tooltip="title: Select the type of data header; each type requires different information to be specified; pos: left">Type</label>
+                            <label class="form-label" style="margin-top: 8px;" uk-tooltip="title: Select the data type for the activated table; each type requires different information to be specified; pos: left">Type</label>
                             <div class="uk-grid-small uk-child-width-auto uk-grid">
                                 <label><input class="uk-radio type-radio" type="radio" name="radio1" value="measurement" checked> Measurement </label><br><br>
                                 <label><input class="uk-radio type-radio" type="radio" name="radio1" value="event"> Event </label><br><br>
@@ -569,11 +609,11 @@
 
             <li>
                 <div style="display: flex; flex-direction: column; height: 87vh;">
-                    <div class="mb-3">
-                        <h4 style="display: inline; margin-left: .2em;">Data Header Table</h4>
-                        <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="resetDataHeaderTable()">Reset</button>
+                    <div class="mb-3" style="display: flex; justify-content: left; align-items: center; padding-left: .2em; background-color:#add8e6">
+                        <h5 style="display: inline; padding-top: 10px;" uk-tooltip="title: Provide an overview of the header table. Select one data row inside the header data and fulfill the column forms in  Data Table section to extract corresponding data.">Header Table</h5>
+                        <button class="btn btn-primary uk-button-small headerButton" style="margin-left: 3px;" onclick="resetDataHeaderTable()">Reset</button>
                     </div>
-                    <div class="uk-overflow-auto" id="data-header-table" style="flex-grow: 1; overflow-y: auto; max-height: max-content; margin-top: -20px;">
+                    <div class="uk-overflow-auto" id="data-header-table" style="flex-grow: 1; max-height: 27vh; margin-top: -20px; margin-bottom: 0px;">
                         <table id="H-table" class='uk-table uk-table-small uk-table-hover uk-table-divider'>
                             <thead>
                                 <tr>
@@ -585,14 +625,15 @@
                             </thead>
                         </table>
                     </div>
-                    <div class="mb-3">
-                        <h4 style="display: inline; margin-left: .2em;">Machine Data Table (LIMIT 500)</h4>
-                        <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="resetMachineDataTable()">Reset</button>
-                        <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="addDataTable()">Add</button>
+                    <div class="mb-3" style="display: flex; justify-content: left; align-items: center; padding-left: .2em; border-top: gray solid 0px; background-color:#add8e6;">
+                        <h5 style="display: inline; padding-top: 10px;" uk-tooltip="title: Show all the dataset under the selected header instance and specific columns. First, select one header row by clicking. Second, fulfill the following column options. Then the data table will automatically be generated, and you can start selecting and press add button to send them into the final data table in step 4.; pos: bottom">Data Table</h5>
+                        <button class="btn btn-primary uk-button-small headerButton" style="margin-left: 3px;" onclick="resetMachineDataTable()" >Reset</button>
+                        <button class="btn btn-primary uk-button-small headerButton" style="margin-left: 3px;" onclick="addDataTable()">Add</button>
+                        <h5 style="margin-left: 10px; display: inline; padding-top: 10px;">Row Count:</h5> <label id="row-count" class="form-label" style="margin-left: 3px; padding-top: 10px;">0</label>
                         <span id="addDataTable" style="color: red; font-size: 80%; margin-left: 2px; margin-bottom: 1px;"></span>
                     </div>
-                    <div class="uk-overflow-auto" id="machine-data-table" style="flex-grow: 1; overflow-y: auto; max-height: max-content; margin-top: -20px;" >
-                        <table  id="MD-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;">
+                    <div class="uk-overflow-auto" id="machine-data-table" style="flex-grow: 1; margin-top: -20px;" >
+                        <table  id="MD-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer; height: max-content;">
                             <thead>
                                 <tr class="uk-table-middle">
                                     <th class="uk-table-shrink">
@@ -670,29 +711,34 @@
                 </div>
             </li>
             <li>
-                <div class="accordion-container">
-                    <button type="submit" class="btn btn-primary uk-button-small headerButton" onclick="resetMachineDataTable2()" style="position: absolute; right: 13vh; top:32px;">Reset</button>
-                    <button type="submit" class="btn btn-primary uk-button-small headerButton" onclick="exportSelectedRowsToCSV()" style="position: absolute; right: 1vh; top:32px;">Export</button>
+                <div style="display: flex; justify-content: space-between; align-items:end; margin-top: -15px; z-index: 10; padding-left: .2em; padding-right: .2em;">
+                    <h5 style="margin-bottom: .2em;">Data Tables</h5>
+                    <div>
+                        <button class="btn btn-primary uk-button-small headerButton" onclick="resetMachineDataTable2()">Reset</button>
+                        <button class="btn btn-primary uk-button-small headerButton" onclick="exportSelectedRowsToCSV()">Export</button>
+                    </div>
+                </div>
+                <div class="accordion-container" style="margin-top: -2px;">
                     <ul uk-accordion>
                         <li class="uk-open">
-                            <a class="uk-accordion-title" style="text-decoration: none; background-color: #ccc;" href>Observation Table</a>
-                            <div class="uk-accordion-content" style="max-height: 60vh; flex: 1; overflow-y: auto;">
+                            <a class="uk-accordion-title" style="padding-left: .2em; text-decoration: none; background-color: #ccc;" href> Observation</a>
+                            <div class="uk-accordion-content uk-overflow-auto" style="max-height: 60vh; flex: 1;">
                                 <!-- <h4 style="display: inline; margin-left: .2em;">Observation Table</h4> -->
                                 <table  id="ME-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;">
                                 </table>
                             </div>
                         </li>
                         <li>
-                            <a class="uk-accordion-title" style="text-decoration: none; background-color: #ccc;" href>Segment Table</a>
-                            <div class="uk-accordion-content" style="max-height: 60vh; flex: 1; overflow-y: auto;">
+                            <a class="uk-accordion-title" style="padding-left: .2em; text-decoration: none; background-color: #ccc;" href> Segment</a>
+                            <div class="uk-accordion-content uk-overflow-auto" style="max-height: 60vh; flex: 1;">
                                 <table  id="S-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;">
                                 </table>
                             </div>
                         </li>
                         <li>
-                            <a class="uk-accordion-title" style="text-decoration: none; background-color: #ccc;" href>Segment Data Table</a>
-                            <div class="uk-accordion-content" style="max-height: 60vh; flex: 1; overflow-y: auto;">
-                                <table  id="SD-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;">
+                            <a class="uk-accordion-title" style="padding-left: .2em; text-decoration: none; background-color: #ccc;" href> Segment Data</a>
+                            <div class="uk-accordion-content uk-overflow-auto" style="max-height: 60vh; flex: 1;">
+                                <table  id="SD-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer; overflow: scroll;">
                                 </table>
                             </div>
                         </li>
@@ -706,7 +752,7 @@
         <div class="custom-buttom-bar">
             <div id="terminal" class="terminal">
                 <div id="terminal-header" class="terminal-header">
-                    <span style="position: absolute; left: 5px; margin-top: 7px">Table</span><span aria-hidden="true" uk-icon="menu" style="margin-top: 4px;"></span>
+                    <span style="position: absolute; left: 5px; margin-top: 7px" uk-tooltip="Show sample data rows for the activated table instance from Target Tables section on the left sidebar.";>Table</span><span aria-hidden="true" uk-icon="menu" style="margin-top: 4px;"></span>
                     <button id="myModal" type="button" class="btn-close"  aria-label="Close" data-bs-dismiss="modal" style="position: absolute; right: 1vh; margin-top: 1px; z-index: 1000;" onclick="closeTerminal()"></button>
                 </div>
                 <div id=terminal-body class="terminal-body">
@@ -718,70 +764,76 @@
     </div>
 
     <div id="sidebar2">
-        <ul uk-accordion>
-            <li>
-                <a class="uk-accordion-title" style="text-decoration: none;" href="#">Filter</a>
-                <div class="uk-accordion-content">
-                    <div class="accordion">
-                        <div class="accordion-item">
-                          <h2 class="accordion-header">
-                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1" aria-expanded="true" aria-controls="collapse1">
-                              Time
-                            </button>
-                          </h2>
-                          <div id="collapse1" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
-                            <div class="accordion-body">
-                                <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
-                                <script src="//code.jquery.com/jquery-3.6.0.min.js"></script>
-                                <script src="//code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
-
-                                <p style="margin: -4px -21px 5px -17px; padding: 0px;">
-                                    <label for="amount1">From: </label><br>
-                                    <input type="text" id="amount1" class="form-control" style="display: flex; border: 0; color: #5ea9e2; font-weight:bold;"><br>
-                                    <label for="amount2">Until: </label><br>
-                                    <input type="text" id="amount2" class="form-control" style="border:0; color: #5ea9e2; font-weight:bold;">
-                                </p>
-                                <div id="slider-range"></div>
-                                <button type="submit" class="btn btn-primary headerButton" style="margin: 10px 0px -5px -10px;" onclick="filter_ME_data_table()">Submit</button>
-                            </div>
-                          </div>
-                        </div>
-                        <div class="accordion-item">
-                            <h2 class="accordion-header">
-                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse2" aria-expanded="true" aria-controls="collapse2">
-                                    Segment Index
-                                </button>
-                            </h2>
-                            <div id="collapse2" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
-                                <div class="accordion-body">
-                                    <p style="margin: -4px -21px 5px -17px; padding: 0px;">
-                                        <label for="fromInput">From: </label>
-                                        <input type="number" id="fromInput" min="0" step="1" style="width: 8vh;"/><br><br>
-                                        <label for="toInput">To: </label>
-                                        <input type="number" id="toInput" min="0" step="1" style="width: 8vh; margin-left: 20px;"/>
-                                    </p>
-                                    <button type="submit" class="btn btn-primary headerButton" style="margin: 10px 0px -5px -10px;" onclick="filter_SD_data_table()">Submit</button>
-                                    <script>
-                                        var fromInput = document.getElementById('fromInput');
-                                        var toInput = document.getElementById('toInput');
-
-                                        fromInput.addEventListener('input', function() {
-                                            toInput.min = this.value;
-                                            if (parseInt(toInput.value) < parseInt(this.value)) {
-                                                toInput.value = this.value;
-                                            }
-                                        });
-                                    </script>
-                                </div>
-                            </div>
-                          </div>
+        <div class="uk-accordion-content">
+            <legend>Filter</legend>
+            <hr>
+            <div class="accordion">
+                <div class="accordion-item">
+                    <h2 class="accordion-header">
+                    <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1" aria-expanded="true" aria-controls="collapse1">
+                    Time
+                    </button>
+                    </h2>
+                    <div id="collapse1" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
+                    <div class="accordion-body">
+                        <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
+                        <script src="//code.jquery.com/jquery-3.6.0.min.js"></script>
+                        <script src="//code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
+
+                        <p style="margin: -4px -21px 5px -17px; padding: 0px;">
+                            <label for="amount1">From: </label><br>
+                            <input type="text" id="amount1" class="form-control" style="display: flex; border: 0; color: #5ea9e2; font-weight:bold;"><br>
+                            <label for="amount2">Until: </label><br>
+                            <input type="text" id="amount2" class="form-control" style="border:0; color: #5ea9e2; font-weight:bold;">
+                        </p>
+                        <div id="slider-range"></div>
+                        <button type="submit" class="btn btn-primary headerButton" style="margin: 10px 0px -5px -10px;" onclick="filter_ME_data_table()">Submit</button>
+                    </div>
                     </div>
-        
                 </div>
-            </li>
-        </ul>
-
-
+                <div class="accordion-item">
+                    <h2 class="accordion-header">
+                        <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse2" aria-expanded="true" aria-controls="collapse2">
+                        Segment Index
+                        </button>
+                    </h2>
+                    <div id="collapse2" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
+                        <div class="accordion-body">
+                            <p style="margin: -4px -21px 5px -17px; padding: 0px;">
+                                <label for="fromInput">From: </label>
+                                <input type="number" id="fromInput" min="0" step="1" style="width: 10vh;"/><br><br>
+                                <label for="toInput">To: </label>
+                                <input type="number" id="toInput" min="0" step="1" style="width: 10vh; margin-left: 20px;"/>
+                            </p>
+                            <button type="submit" class="btn btn-primary headerButton" style="margin: 10px 0px -5px -10px;" onclick="filter_SD_data_table()">Submit</button>
+                            <script>
+                                var fromInput = document.getElementById('fromInput');
+                                var toInput = document.getElementById('toInput');
+
+                                fromInput.addEventListener('input', function() {
+                                    toInput.min = this.value;
+                                    if (parseInt(toInput.value) < parseInt(this.value)) {
+                                        toInput.value = this.value;
+                                    }
+                                });
+                            </script>
+                        </div>
+                    </div>
+                    </div>
+            </div>
+        </div>
+        </br></br>
+        <div>
+            <legend style="line-height: 120%; margin-bottom: -3px;">ERD Feature</legend>
+            <hr>
+            <p><span class="primary-key">pk</span> Primary Key (Underlined)</p>
+            <p><span class="key foreign-key">fk</span> Foreign Key (Italic)</p>
+            <p><span class="key nullable">*</span> Nullable (*)</p>
+            <p><span class="key identity">I</span> Identity Column (I)</p>
+            <p><span class="key unique">U</span> Unique Constraint (U)</p>
+            <p><span class="key normal-fk"></span> FK Relationship (Solid Line)</p>
+            <p><span class="key optional-fk"></span> Nullable FK Relationship (Dashed Line)</p>
+        </div>
         
         <div id="resize-handle-left" class="resize-handle-left"></div>
     </div> 
@@ -790,36 +842,38 @@
         var currentRowType = null; // Defined at a higher scope, accessible globally
 
         var jq = jQuery.noConflict();
+        // Function to parse date string with milliseconds
+        function parseDateTime(str) {
+            if (str === '0000-00-00 00:00:00.000000+00:00') {
+                return new Date(0);
+            }
+            var date = new Date(str);
+            if (isNaN(date.getTime())) {
+                console.error('The datetime format is incorrect:', str);
+                return new Date(0);  // Use Unix epoch as fallback
+            }
+            return date;
+        }
+
+        function toTimestamp(strDate) {
+            var date = parseDateTime(strDate);
+            return date ? date.getTime() : new Date(0);
+        }
         function initializeSlider(minDatetime, maxDatetime) {
             // Default values if min or max datetime is not provided
-            var defaultMinDatetime = '2022-11-01 16:00:00.000000+00:00';
-            var defaultMaxDatetime = '2022-11-02 16:00:00.000000+00:00';
+            var defaultMinDatetime = '0000-00-00 00:00:00.000000+00:00';
+            var defaultMaxDatetime = '0000-00-00 00:00:00.000000+00:00';
 
             // Use default values if min or max datetime is empty
             minDatetime = minDatetime || defaultMinDatetime;
             maxDatetime = maxDatetime || defaultMaxDatetime;
-            
-            // Function to parse date string with milliseconds
-            function parseDateTime(str) {
-                var date = new Date(str);
-                if (isNaN(date.getTime())) {
-                    console.error('The datetime format is incorrect:', str);
-                    return null;
-                }
-                return date;
-            }
-
-            function toTimestamp(strDate) {
-                var date = parseDateTime(strDate);
-                return date ? date.getTime() : null;
-            }
 
 
             // Function to format date to string with milliseconds
             function formatDateTime(date) {
-                var hours = ('0' + date.getHours()).slice(-2);
-                var minutes = ('0' + date.getMinutes()).slice(-2);
-                var seconds = ('0' + date.getSeconds()).slice(-2);
+                var hours = ('0' + date.getUTCHours()).slice(-2); // Use getUTCHours for UTC time
+                var minutes = ('0' + date.getUTCMinutes()).slice(-2);
+                var seconds = ('0' + date.getUTCSeconds()).slice(-2);
 
                 return jq.datepicker.formatDate('yy-mm-dd', date) +
                     ' ' + hours + ':' +
@@ -832,25 +886,28 @@
                 range: true,
                 min: toTimestamp(minDatetime), // Use minDatetime from the server
                 max: toTimestamp(maxDatetime), // Use maxDatetime from the server
-                step: 1, // Step is now 1 millisecond
+                step: 1000, // Step is now 1 second
                 values: [
                     toTimestamp(minDatetime), // Set the lower handle to minDatetime
                     toTimestamp(maxDatetime) // Set the upper handle to maxDatetime
                 ],
+                // Inside your slider initialization or update functions
                 slide: function(event, ui) {
                     var startDateTime = new Date(ui.values[0]);
-                    var endDateTime = new Date(ui.values[1]);
+                    var endDateTime = new Date(ui.values[1] + 1000); // Add 1 second to include the last millisecond
+
                     jq("#amount1").val(formatDateTime(startDateTime));
                     jq("#amount2").val(formatDateTime(endDateTime));
                 },
                 create: function(event, ui) {
-                    // Set the initial datetime values when the slider is created
                     var startDateTime = new Date(jq("#slider-range").slider("values", 0));
-                    var endDateTime = new Date(jq("#slider-range").slider("values", 1));
+                    var endDateTime = new Date(jq("#slider-range").slider("values", 1) + 1000);
+
                     jq("#amount1").val(formatDateTime(startDateTime));
                     jq("#amount2").val(formatDateTime(endDateTime));
-                }
+                },
             });
+
         }
 
 
@@ -888,7 +945,7 @@
         function addDataTable() {
             // Collect all selected rows from the Machine Data Table
             var selectedRowsData = [];
-            document.querySelectorAll('#MD-table input[type="checkbox"]:checked').forEach(function(checkbox) {
+            document.querySelectorAll('#MD-table input[type="checkbox"]:checked:not(#click_all)').forEach(function(checkbox) {
                 var row = checkbox.closest('tr');
                 var rowData = {};
                 row.querySelectorAll('td').forEach(function(td, index) {
@@ -909,6 +966,13 @@
                     document.getElementById("addDataTable").textContent = "";
                 }, 2000);
 
+                // Remove the selected rows from the Machine Data Table
+                document.querySelectorAll('#MD-table input[type="checkbox"]:checked:not(#click_all)').forEach(function(checkbox) {
+                    var row = checkbox.closest('tr');
+                    row.remove();
+                });
+
+
                 // Send the selected rows data to the Flask backend
                 fetch('/add-data-table', {
                     method: 'POST',
@@ -1180,6 +1244,15 @@
             })
             .then(response => response.json())
             .then(data => {
+                // Set row coount zero
+                document.getElementById("row-count").textContent = "0";
+                // Set the time frame slider with the empty min and max datetime values
+                document.getElementById('amount1').value = '';
+                document.getElementById('amount2').value = '';
+                // Set index inputs to the default value
+                document.getElementById('fromInput').value = 0;
+                document.getElementById('toInput').value = 0;
+
                 // Set the select element to the default value
                 var selectObject = document.getElementById('table_object');
                 selectObject.value = "no";
@@ -1270,6 +1343,12 @@
                         const optionElement = document.createElement('option');
                         optionElement.value = label_value;
                         optionElement.textContent = label_value;
+                        // Check if the current label_value is in data['fk_index'] and apply a style
+                        if (data['pk_index'] && data['pk_index'].includes(label_value)) {
+                            optionElement.textContent = `[PK] ${label_value}`;
+                        } else {
+                            optionElement.textContent = label_value;
+                        }
                         selectIndex.appendChild(optionElement);  
                     });
                 } else if (type == "SD") {
@@ -1285,7 +1364,13 @@
                         const optionElement = document.createElement('option');
                         optionElement.value = label_value;
                         optionElement.textContent = label_value;
-                        selectIndex.appendChild(optionElement);  
+                        // Check if the current label_value is in data['fk_index'] and apply a style
+                        if (data['fk_index'] && data['fk_index'].includes(label_value)) {
+                            optionElement.textContent = `[FK] ${label_value}`;
+                        } else {
+                            optionElement.textContent = label_value;
+                        }
+                        selectIndex.appendChild(optionElement); 
                     });
                     // Update the object select with the new object columns
                     const selectSegment = document.getElementById('segment-label-select');
@@ -1360,16 +1445,24 @@
                     })
                     .then(response => response.json())
                     .then(data => {
-                        console.log(data['table_HTML']);
-                        const table = document.getElementById('MD-table');
-                        table.querySelector('tbody').innerHTML = ""
-                        table.querySelector('tbody').innerHTML = data['table_HTML'];
+                        document.getElementById('row-count').textContent = data['row_count'];
 
                         if ('min_datetime' in data && 'max_datetime' in data) {
                             // Initialize the slider with the min and max datetime from the server
                             console.log("ME: initialize slider with min and max datetime from the server");
+                            console.log(data['min_datetime'], data['max_datetime']);
                             initializeSlider(data['min_datetime'], data['max_datetime']);
                         }
+                        
+                        const rowCount = parseInt(data['row_count'], 10);
+                        if (rowCount > 10000) { // adjust the threshold as needed
+                            alert("The current column settings return a large amount of data (" + rowCount + " rows). Please use a time-based filter on the right sidebar to reduce the data size for better performance.");
+                        } else {
+                            const table = document.getElementById('MD-table');
+                            table.querySelector('tbody').innerHTML = ""
+                            table.querySelector('tbody').innerHTML = data['table_HTML'];
+                        }
+
                     })
                     .catch(error => {
                         // Handle any error that occurred during the fetch
@@ -1414,16 +1507,22 @@
                     })
                     .then(response => response.json())
                     .then(data => {
-                        console.log(data['table_HTML']);
-                        const table = document.getElementById('MD-table');
-                        table.querySelector('tbody').innerHTML = ""
-                        table.querySelector('tbody').innerHTML = data['table_HTML'];
+                        document.getElementById('row-count').textContent = data['row_count'];
 
                         if ('min_datetime' in data && 'max_datetime' in data) {
                             // Initialize the slider with the min and max datetime from the server
-                            console.log("S: initialize slider with min and max datetime from the server");
+                            console.log("S: initialize slider with min and max datetime from the server", data['min_datetime'], data['max_datetime']);
                             initializeSlider(data['min_datetime'], data['max_datetime']);
                         }
+
+                        const rowCount = parseInt(data['row_count'], 10);
+                        if (rowCount > 10000) { // adjust the threshold as needed
+                            alert("The current column settings return a large amount of data (" + rowCount + " rows). Please use a time-based filter on the right sidebar to reduce the data size for better performance.");
+                        } else {
+                            const table = document.getElementById('MD-table');
+                            table.querySelector('tbody').innerHTML = ""
+                            table.querySelector('tbody').innerHTML = data['table_HTML'];
+                        }
                     })
                     .catch(error => {
                         // Handle any error that occurred during the fetch
@@ -1466,16 +1565,25 @@
                     })
                     .then(response => response.json())
                     .then(data => {
-                        console.log(data['table_HTML']);
-                        const table = document.getElementById('MD-table');
-                        table.querySelector('tbody').innerHTML = ""
-                        table.querySelector('tbody').innerHTML = data['table_HTML'];
+                        document.getElementById('row-count').textContent = data['row_count'];
+                        if (data['row_count'] == '0') {
+                            alert("The current column settings return 0 rows. No valid segment indices found. Please add data for header row with type S first.");
+                        }
 
                         if ('min_index' in data && 'max_index' in data) {
                             // Initialize the slider with the min and max datetime from the server
                             console.log("SD: initialize slider with min and max datetime from the server");
-                            document.getElementById('fromInput').value = min_index;
-                            document.getElementById('toInput').value = max_index;
+                            document.getElementById('fromInput').value = data['min_index'];
+                            document.getElementById('toInput').value = data['max_index'];
+                        }
+
+                        const rowCount = parseInt(data['row_count'], 10);
+                        if (rowCount > 10000) { // adjust the threshold as needed
+                            alert("The current column settings return a large amount of data (" + rowCount + " rows). Please use a time-based filter on the right sidebar to reduce the data size for better performance.");
+                        } else {
+                            const table = document.getElementById('MD-table');
+                            table.querySelector('tbody').innerHTML = ""
+                            table.querySelector('tbody').innerHTML = data['table_HTML'];
                         }
                     })
                     .catch(error => {
@@ -1521,10 +1629,16 @@
                     })
                     .then(response => response.json())
                     .then(data => {
-                        console.log(data['table_HTML']);
-                        const table = document.getElementById('MD-table');
-                        table.querySelector('tbody').innerHTML = ""
-                        table.querySelector('tbody').innerHTML = data['table_HTML'];
+                        document.getElementById('row-count').textContent = data['row_count'];
+
+                        const rowCount = parseInt(data['row_count'], 10);
+                        if (rowCount > 10000) { // adjust the threshold as needed
+                            alert("The current filter settings return a large amount of data (" + rowCount + " rows). Please set a more limited time frame on time-based filter to reduce the data size for better performance.");
+                        } else {
+                            const table = document.getElementById('MD-table');
+                            table.querySelector('tbody').innerHTML = ""
+                            table.querySelector('tbody').innerHTML = data['table_HTML'];
+                        }
                     })
                     .catch(error => {
                         // Handle any error that occurred during the fetch
@@ -1544,10 +1658,17 @@
                     })
                     .then(response => response.json())
                     .then(data => {
-                        console.log(data['table_HTML']);
-                        const table = document.getElementById('MD-table');
-                        table.querySelector('tbody').innerHTML = ""
-                        table.querySelector('tbody').innerHTML = data['table_HTML'];
+                        document.getElementById('row-count').textContent = data['row_count'];
+
+                        const rowCount = parseInt(data['row_count'], 10);
+                        if (rowCount > 10000) { // adjust the threshold as needed
+                            alert("The current filter settings return a large amount of data (" + rowCount + " rows). Please set a more limited time frame on time-based filter to reduce the data size for better performance.");
+                        } else {
+                            const table = document.getElementById('MD-table');
+                            table.querySelector('tbody').innerHTML = ""
+                            table.querySelector('tbody').innerHTML = data['table_HTML'];
+                        }
+
                     })
                     .catch(error => {
                         // Handle any error that occurred during the fetch
@@ -1598,10 +1719,16 @@
                 })
                 .then(response => response.json())
                 .then(data => {
-                    console.log(data['table_HTML']);
-                    const table = document.getElementById('MD-table');
-                    table.querySelector('tbody').innerHTML = ""
-                    table.querySelector('tbody').innerHTML = data['table_HTML'];
+                    document.getElementById('row-count').textContent = data['row_count'];
+
+                    const rowCount = parseInt(data['row_count'], 10);
+                    if (rowCount > 10000) { // adjust the threshold as needed
+                        alert("The current filter settings return a large amount of data (" + rowCount + " rows). Please set a more limited time frame on time-based filter to reduce the data size for better performance.");
+                    } else {
+                        const table = document.getElementById('MD-table');
+                        table.querySelector('tbody').innerHTML = ""
+                        table.querySelector('tbody').innerHTML = data['table_HTML'];
+                    }
                 })
                 .catch(error => {
                     // Handle any error that occurred during the fetch
-- 
GitLab