Spaces:
Running
Running
Commit ·
8102cc6
1
Parent(s): f02c5cf
send commit
Browse files- app.py +397 -41
- documents.csv +4 -5
- evaluations.csv +4 -0
- instructions.md +31 -43
- project_status.md +35 -2
- readme.md +12 -97
- requirements.txt +2 -0
- sample_documents_template.csv +4 -4
- static/style.css +70 -1
- templates/debug.html +167 -0
- templates/evaluate.html +62 -10
- templates/index.html +234 -0
- templates/instructions.html +22 -43
- templates/no_documents.html +101 -7
- templates/results.html +94 -26
app.py
CHANGED
|
@@ -1,17 +1,20 @@
|
|
| 1 |
import flask
|
| 2 |
from flask import Flask, render_template, request, redirect, url_for, session, flash, send_file
|
| 3 |
import pandas as pd
|
|
|
|
| 4 |
import random
|
| 5 |
import csv
|
| 6 |
from datetime import datetime
|
| 7 |
import os
|
| 8 |
import io
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
app = Flask(__name__)
|
| 11 |
app.secret_key = os.environ.get('SECRET_KEY', 'your-secret-key-here') # Gets from env or uses default
|
| 12 |
|
| 13 |
# Constants
|
| 14 |
-
PASSWORD = os.environ.get('APP_PASSWORD', "12345") # Gets from env or uses default
|
| 15 |
CRITERIA = [
|
| 16 |
"Up-to-date",
|
| 17 |
"Accurate",
|
|
@@ -36,31 +39,194 @@ CRITERIA_DESCRIPTIONS = [
|
|
| 36 |
"No part of the note ignores or contradicts any other part."
|
| 37 |
]
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
DATA_DIR = os.environ.get('DATA_DIR', '.') # Gets from env or uses current directory
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
def load_documents():
|
| 42 |
"""Load documents from CSV file, excluding already evaluated ones."""
|
| 43 |
try:
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
# If description is empty, replace with empty string
|
| 48 |
if 'description' in df.columns:
|
| 49 |
df['description'] = df['description'].fillna('')
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
# Load evaluated documents
|
| 52 |
try:
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
pass
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
| 63 |
except FileNotFoundError:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
return []
|
| 65 |
|
| 66 |
def save_evaluation(data):
|
|
@@ -71,7 +237,7 @@ def save_evaluation(data):
|
|
| 71 |
with open(filename, 'a', newline='') as f:
|
| 72 |
writer = csv.writer(f)
|
| 73 |
if not file_exists:
|
| 74 |
-
headers = ['timestamp', 'investigator_name', 'document_title', 'description'] + CRITERIA
|
| 75 |
writer.writerow(headers)
|
| 76 |
writer.writerow(data)
|
| 77 |
|
|
@@ -81,37 +247,133 @@ def get_results():
|
|
| 81 |
# Load evaluations
|
| 82 |
eval_df = pd.read_csv(os.path.join(DATA_DIR, 'evaluations.csv'))
|
| 83 |
|
| 84 |
-
# Load all documents to get descriptions
|
| 85 |
try:
|
| 86 |
docs_df = pd.read_csv(os.path.join(DATA_DIR, 'documents.csv'))
|
| 87 |
-
# Create a mapping of filename to description
|
| 88 |
filename_to_desc = dict(zip(docs_df['filename'], docs_df['description']))
|
|
|
|
| 89 |
except FileNotFoundError:
|
| 90 |
filename_to_desc = {}
|
|
|
|
| 91 |
|
| 92 |
-
return eval_df, filename_to_desc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
except FileNotFoundError:
|
| 94 |
-
return
|
|
|
|
|
|
|
| 95 |
|
| 96 |
@app.route('/', methods=['GET', 'POST'])
|
| 97 |
-
def
|
|
|
|
| 98 |
if request.method == 'POST':
|
| 99 |
-
if request
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
flash(
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
@app.route('/evaluate', methods=['GET', 'POST'])
|
| 107 |
def evaluate():
|
| 108 |
-
|
| 109 |
-
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
if request.method == 'POST':
|
| 112 |
-
investigator_name =
|
| 113 |
document_title = session.get('current_document_title')
|
| 114 |
document_description = session.get('current_document_description', '')
|
|
|
|
|
|
|
| 115 |
|
| 116 |
# Collect scores
|
| 117 |
scores = [request.form.get(f'criteria_{i}') for i in range(len(CRITERIA))]
|
|
@@ -120,47 +382,132 @@ def evaluate():
|
|
| 120 |
data = [datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
| 121 |
investigator_name,
|
| 122 |
document_title,
|
| 123 |
-
document_description
|
|
|
|
|
|
|
| 124 |
save_evaluation(data)
|
| 125 |
|
| 126 |
flash('Evaluation saved successfully!')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
return redirect(url_for('evaluate'))
|
| 128 |
|
| 129 |
# Get random document
|
| 130 |
documents = load_documents()
|
| 131 |
if not documents:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
return render_template('no_documents.html')
|
| 133 |
|
| 134 |
document = random.choice(documents)
|
| 135 |
session['current_document_title'] = document['filename']
|
| 136 |
session['current_document_description'] = document.get('description', '')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
return render_template('evaluate.html',
|
| 139 |
note=document['note'],
|
| 140 |
description=document.get('description', ''),
|
|
|
|
| 141 |
criteria=CRITERIA,
|
| 142 |
descriptions=CRITERIA_DESCRIPTIONS,
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
@app.route('/results')
|
| 146 |
def results():
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
eval_df, filename_to_desc = get_results()
|
| 151 |
if eval_df.empty:
|
| 152 |
flash('No evaluations available yet.')
|
| 153 |
-
return redirect(url_for('
|
| 154 |
|
| 155 |
return render_template('results.html',
|
| 156 |
evaluations=eval_df.to_dict('records'),
|
| 157 |
criteria=CRITERIA,
|
| 158 |
descriptions=CRITERIA_DESCRIPTIONS)
|
| 159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
@app.route('/instructions')
|
| 161 |
def view_instructions():
|
| 162 |
"""Display instructions page."""
|
| 163 |
-
return render_template('instructions.html'
|
|
|
|
|
|
|
| 164 |
|
| 165 |
@app.route('/download/instructions')
|
| 166 |
def download_instructions():
|
|
@@ -173,7 +520,7 @@ def download_instructions():
|
|
| 173 |
as_attachment=True)
|
| 174 |
except FileNotFoundError:
|
| 175 |
flash('Instructions file not found.')
|
| 176 |
-
return redirect(url_for('
|
| 177 |
|
| 178 |
@app.route('/download/template')
|
| 179 |
def download_template():
|
|
@@ -186,16 +533,25 @@ def download_template():
|
|
| 186 |
as_attachment=True)
|
| 187 |
except FileNotFoundError:
|
| 188 |
flash('Template file not found.')
|
| 189 |
-
return redirect(url_for('
|
| 190 |
|
| 191 |
-
@app.route('/
|
| 192 |
-
def
|
| 193 |
-
session
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
if __name__ == '__main__':
|
| 197 |
# Create data directory if it doesn't exist
|
| 198 |
os.makedirs(DATA_DIR, exist_ok=True)
|
| 199 |
|
|
|
|
|
|
|
|
|
|
| 200 |
# Set host to 0.0.0.0 to make it accessible outside container
|
| 201 |
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))
|
|
|
|
| 1 |
import flask
|
| 2 |
from flask import Flask, render_template, request, redirect, url_for, session, flash, send_file
|
| 3 |
import pandas as pd
|
| 4 |
+
import numpy as np
|
| 5 |
import random
|
| 6 |
import csv
|
| 7 |
from datetime import datetime
|
| 8 |
import os
|
| 9 |
import io
|
| 10 |
+
import shutil
|
| 11 |
+
import traceback
|
| 12 |
+
import chardet
|
| 13 |
|
| 14 |
app = Flask(__name__)
|
| 15 |
app.secret_key = os.environ.get('SECRET_KEY', 'your-secret-key-here') # Gets from env or uses default
|
| 16 |
|
| 17 |
# Constants
|
|
|
|
| 18 |
CRITERIA = [
|
| 19 |
"Up-to-date",
|
| 20 |
"Accurate",
|
|
|
|
| 39 |
"No part of the note ignores or contradicts any other part."
|
| 40 |
]
|
| 41 |
|
| 42 |
+
# Note origin options
|
| 43 |
+
NOTE_ORIGINS = [
|
| 44 |
+
"Human written note",
|
| 45 |
+
"Generative AI note",
|
| 46 |
+
"I am not sure"
|
| 47 |
+
]
|
| 48 |
+
|
| 49 |
DATA_DIR = os.environ.get('DATA_DIR', '.') # Gets from env or uses current directory
|
| 50 |
+
ERROR_LOG = [] # Store recent errors for debugging
|
| 51 |
+
|
| 52 |
+
def log_error(error_msg):
|
| 53 |
+
"""Log an error message for debugging."""
|
| 54 |
+
ERROR_LOG.append(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: {error_msg}")
|
| 55 |
+
# Keep only the most recent 10 errors
|
| 56 |
+
while len(ERROR_LOG) > 10:
|
| 57 |
+
ERROR_LOG.pop(0)
|
| 58 |
+
|
| 59 |
+
def detect_encoding(file_path):
|
| 60 |
+
"""Detect the encoding of a file to handle different character encodings."""
|
| 61 |
+
with open(file_path, 'rb') as f:
|
| 62 |
+
result = chardet.detect(f.read())
|
| 63 |
+
return result['encoding']
|
| 64 |
|
| 65 |
def load_documents():
|
| 66 |
"""Load documents from CSV file, excluding already evaluated ones."""
|
| 67 |
try:
|
| 68 |
+
file_path = os.path.join(DATA_DIR, 'documents.csv')
|
| 69 |
+
|
| 70 |
+
if not os.path.exists(file_path):
|
| 71 |
+
log_error(f"File not found: {file_path}")
|
| 72 |
+
return []
|
| 73 |
+
|
| 74 |
+
# Try to detect file encoding
|
| 75 |
+
try:
|
| 76 |
+
encoding = detect_encoding(file_path)
|
| 77 |
+
log_error(f"Detected encoding: {encoding}")
|
| 78 |
+
except Exception as e:
|
| 79 |
+
encoding = 'utf-8' # Default to UTF-8 if detection fails
|
| 80 |
+
log_error(f"Failed to detect encoding, using utf-8: {str(e)}")
|
| 81 |
+
|
| 82 |
+
# Try multiple parsing approaches
|
| 83 |
+
try:
|
| 84 |
+
# First attempt: Use pandas with standard settings
|
| 85 |
+
df = pd.read_csv(file_path, encoding=encoding)
|
| 86 |
+
log_error("Successfully parsed CSV with standard settings")
|
| 87 |
+
except Exception as e1:
|
| 88 |
+
log_error(f"First parsing attempt failed: {str(e1)}")
|
| 89 |
+
try:
|
| 90 |
+
# Second attempt: Use maximum quoting
|
| 91 |
+
df = pd.read_csv(
|
| 92 |
+
file_path,
|
| 93 |
+
encoding=encoding,
|
| 94 |
+
quoting=csv.QUOTE_ALL,
|
| 95 |
+
escapechar='\\'
|
| 96 |
+
)
|
| 97 |
+
log_error("Successfully parsed CSV with QUOTE_ALL")
|
| 98 |
+
except Exception as e2:
|
| 99 |
+
log_error(f"Second parsing attempt failed: {str(e2)}")
|
| 100 |
+
try:
|
| 101 |
+
# Third attempt: Use minimal quoting
|
| 102 |
+
df = pd.read_csv(
|
| 103 |
+
file_path,
|
| 104 |
+
encoding=encoding,
|
| 105 |
+
quoting=csv.QUOTE_MINIMAL,
|
| 106 |
+
escapechar='\\',
|
| 107 |
+
error_bad_lines=False # Skip bad lines
|
| 108 |
+
)
|
| 109 |
+
log_error("Successfully parsed CSV with QUOTE_MINIMAL")
|
| 110 |
+
except Exception as e3:
|
| 111 |
+
log_error(f"Third parsing attempt failed: {str(e3)}")
|
| 112 |
+
# Final attempt: Use Python's CSV module directly
|
| 113 |
+
log_error("Attempting to parse with CSV module directly")
|
| 114 |
+
rows = []
|
| 115 |
+
headers = None
|
| 116 |
+
try:
|
| 117 |
+
with open(file_path, 'r', encoding=encoding, newline='') as f:
|
| 118 |
+
reader = csv.reader(f)
|
| 119 |
+
headers = next(reader)
|
| 120 |
+
for row in reader:
|
| 121 |
+
if len(row) >= 4: # Ensure we have at least 4 columns
|
| 122 |
+
rows.append(row)
|
| 123 |
+
|
| 124 |
+
if not headers or not rows:
|
| 125 |
+
log_error("No valid data or headers found in CSV")
|
| 126 |
+
return []
|
| 127 |
+
|
| 128 |
+
df = pd.DataFrame(rows, columns=headers)
|
| 129 |
+
log_error(f"Successfully parsed with CSV module. Found {len(rows)} rows.")
|
| 130 |
+
except Exception as e4:
|
| 131 |
+
log_error(f"All parsing attempts failed: {str(e4)}")
|
| 132 |
+
return []
|
| 133 |
+
|
| 134 |
+
# Log dataframe information to help debugging
|
| 135 |
+
log_error(f"DataFrame columns: {df.columns.tolist()}")
|
| 136 |
+
log_error(f"DataFrame shape: {df.shape}")
|
| 137 |
+
|
| 138 |
+
# Ensure we have the required columns
|
| 139 |
+
required_columns = ['filename', 'description', 'mrn', 'note']
|
| 140 |
+
missing_columns = [col for col in required_columns if col not in df.columns]
|
| 141 |
+
|
| 142 |
+
if missing_columns:
|
| 143 |
+
log_error(f"Missing required columns: {missing_columns}")
|
| 144 |
+
# Try to handle common column name variations
|
| 145 |
+
column_map = {}
|
| 146 |
+
for req_col in missing_columns:
|
| 147 |
+
# Check for case-insensitive matches
|
| 148 |
+
matches = [col for col in df.columns if col.lower() == req_col.lower()]
|
| 149 |
+
if matches:
|
| 150 |
+
column_map[matches[0]] = req_col
|
| 151 |
+
|
| 152 |
+
# Rename columns if matches found
|
| 153 |
+
if column_map:
|
| 154 |
+
df = df.rename(columns=column_map)
|
| 155 |
+
log_error(f"Renamed columns: {column_map}")
|
| 156 |
+
# Check again for missing columns
|
| 157 |
+
missing_columns = [col for col in required_columns if col not in df.columns]
|
| 158 |
+
|
| 159 |
+
if missing_columns:
|
| 160 |
+
# If still missing columns, try to infer them from position
|
| 161 |
+
if len(df.columns) >= 4 and len(missing_columns) <= 4:
|
| 162 |
+
log_error("Attempting to infer columns by position")
|
| 163 |
+
new_columns = []
|
| 164 |
+
df_columns = df.columns.tolist()
|
| 165 |
+
|
| 166 |
+
for i, req_col in enumerate(required_columns):
|
| 167 |
+
if i < len(df_columns):
|
| 168 |
+
if req_col in df.columns:
|
| 169 |
+
new_columns.append(req_col)
|
| 170 |
+
else:
|
| 171 |
+
new_columns.append(req_col)
|
| 172 |
+
# Rename the column
|
| 173 |
+
old_col = df_columns[i]
|
| 174 |
+
column_map[old_col] = req_col
|
| 175 |
+
|
| 176 |
+
if column_map:
|
| 177 |
+
df = df.rename(columns=column_map)
|
| 178 |
+
log_error(f"Inferred columns by position: {column_map}")
|
| 179 |
+
|
| 180 |
+
# Final check for required columns
|
| 181 |
+
missing_columns = [col for col in required_columns if col not in df.columns]
|
| 182 |
+
if missing_columns:
|
| 183 |
+
log_error(f"Still missing required columns after all attempts: {missing_columns}")
|
| 184 |
+
raise ValueError(f"Missing required columns in documents.csv: {', '.join(missing_columns)}")
|
| 185 |
|
| 186 |
# If description is empty, replace with empty string
|
| 187 |
if 'description' in df.columns:
|
| 188 |
df['description'] = df['description'].fillna('')
|
| 189 |
|
| 190 |
+
# If MRN is empty, replace with empty string
|
| 191 |
+
if 'mrn' in df.columns:
|
| 192 |
+
df['mrn'] = df['mrn'].fillna('')
|
| 193 |
+
|
| 194 |
+
# Convert all columns to string to ensure compatability
|
| 195 |
+
for col in df.columns:
|
| 196 |
+
df[col] = df[col].astype(str)
|
| 197 |
+
|
| 198 |
+
# Log first few rows to help debugging
|
| 199 |
+
log_error(f"First row: {df.iloc[0].to_dict() if len(df) > 0 else 'No rows'}")
|
| 200 |
+
|
| 201 |
# Load evaluated documents
|
| 202 |
try:
|
| 203 |
+
evaluations_path = os.path.join(DATA_DIR, 'evaluations.csv')
|
| 204 |
+
if os.path.exists(evaluations_path):
|
| 205 |
+
evaluations_df = pd.read_csv(evaluations_path)
|
| 206 |
+
# Get unique document titles that have been evaluated
|
| 207 |
+
evaluated_titles = evaluations_df['document_title'].unique()
|
| 208 |
+
# Filter out documents that have already been evaluated
|
| 209 |
+
df = df[~df['filename'].isin(evaluated_titles)]
|
| 210 |
+
log_error(f"Found {len(evaluated_titles)} already evaluated documents")
|
| 211 |
+
else:
|
| 212 |
+
log_error("No evaluations file found, all documents available")
|
| 213 |
+
except Exception as e:
|
| 214 |
+
log_error(f"Error loading evaluations: {str(e)}")
|
| 215 |
+
# If error, assume no evaluations
|
| 216 |
pass
|
| 217 |
+
|
| 218 |
+
documents = df.to_dict('records')
|
| 219 |
+
log_error(f"Returning {len(documents)} documents for evaluation")
|
| 220 |
+
return documents
|
| 221 |
except FileNotFoundError:
|
| 222 |
+
log_error(f"The documents.csv file was not found in {DATA_DIR}")
|
| 223 |
+
flash("The documents.csv file was not found. Please create this file with your documents for evaluation.")
|
| 224 |
+
return []
|
| 225 |
+
except Exception as e:
|
| 226 |
+
error_msg = f"Error loading documents: {str(e)}"
|
| 227 |
+
log_error(error_msg)
|
| 228 |
+
flash(f"{error_msg}. Please check the format of your documents.csv file.")
|
| 229 |
+
traceback.print_exc() # Print the full traceback for debugging
|
| 230 |
return []
|
| 231 |
|
| 232 |
def save_evaluation(data):
|
|
|
|
| 237 |
with open(filename, 'a', newline='') as f:
|
| 238 |
writer = csv.writer(f)
|
| 239 |
if not file_exists:
|
| 240 |
+
headers = ['timestamp', 'investigator_name', 'document_title', 'description', 'mrn', 'note_origin'] + CRITERIA
|
| 241 |
writer.writerow(headers)
|
| 242 |
writer.writerow(data)
|
| 243 |
|
|
|
|
| 247 |
# Load evaluations
|
| 248 |
eval_df = pd.read_csv(os.path.join(DATA_DIR, 'evaluations.csv'))
|
| 249 |
|
| 250 |
+
# Load all documents to get descriptions and MRN
|
| 251 |
try:
|
| 252 |
docs_df = pd.read_csv(os.path.join(DATA_DIR, 'documents.csv'))
|
| 253 |
+
# Create a mapping of filename to description and MRN
|
| 254 |
filename_to_desc = dict(zip(docs_df['filename'], docs_df['description']))
|
| 255 |
+
filename_to_mrn = dict(zip(docs_df['filename'], docs_df['mrn']))
|
| 256 |
except FileNotFoundError:
|
| 257 |
filename_to_desc = {}
|
| 258 |
+
filename_to_mrn = {}
|
| 259 |
|
| 260 |
+
return eval_df, filename_to_desc, filename_to_mrn
|
| 261 |
+
except FileNotFoundError:
|
| 262 |
+
return pd.DataFrame(), {}, {}
|
| 263 |
+
|
| 264 |
+
def get_total_document_count():
|
| 265 |
+
"""Get the total number of documents."""
|
| 266 |
+
try:
|
| 267 |
+
df = pd.read_csv(os.path.join(DATA_DIR, 'documents.csv'))
|
| 268 |
+
return len(df)
|
| 269 |
+
except Exception:
|
| 270 |
+
return 0
|
| 271 |
+
|
| 272 |
+
def get_evaluated_document_count():
|
| 273 |
+
"""Get the count of evaluated documents."""
|
| 274 |
+
try:
|
| 275 |
+
eval_df = pd.read_csv(os.path.join(DATA_DIR, 'evaluations.csv'))
|
| 276 |
+
return len(eval_df['document_title'].unique())
|
| 277 |
except FileNotFoundError:
|
| 278 |
+
return 0
|
| 279 |
+
except Exception:
|
| 280 |
+
return 0
|
| 281 |
|
| 282 |
@app.route('/', methods=['GET', 'POST'])
|
| 283 |
+
def index():
|
| 284 |
+
"""Landing page with file upload and evaluator name."""
|
| 285 |
if request.method == 'POST':
|
| 286 |
+
# Check if the post request has the file part
|
| 287 |
+
if 'file' not in request.files:
|
| 288 |
+
error_msg = 'No file part in the request. Please try again.'
|
| 289 |
+
log_error(error_msg)
|
| 290 |
+
flash(error_msg)
|
| 291 |
+
return redirect(request.url)
|
| 292 |
+
|
| 293 |
+
file = request.files['file']
|
| 294 |
+
evaluator_name = request.form.get('evaluator_name', '').strip()
|
| 295 |
+
|
| 296 |
+
# If user does not select file, browser also
|
| 297 |
+
# submits an empty part without filename
|
| 298 |
+
if file.filename == '':
|
| 299 |
+
error_msg = 'No file selected. Please select a file to upload.'
|
| 300 |
+
log_error(error_msg)
|
| 301 |
+
flash(error_msg)
|
| 302 |
+
return redirect(request.url)
|
| 303 |
+
|
| 304 |
+
if not evaluator_name:
|
| 305 |
+
error_msg = 'Please enter your name as the evaluator.'
|
| 306 |
+
log_error(error_msg)
|
| 307 |
+
flash(error_msg)
|
| 308 |
+
return redirect(request.url)
|
| 309 |
+
|
| 310 |
+
# Store evaluator name in session
|
| 311 |
+
session['evaluator_name'] = evaluator_name
|
| 312 |
+
|
| 313 |
+
if file:
|
| 314 |
+
try:
|
| 315 |
+
# Create a secure file path
|
| 316 |
+
file_path = os.path.join(DATA_DIR, 'documents.csv')
|
| 317 |
+
|
| 318 |
+
# Save the file
|
| 319 |
+
file.save(file_path)
|
| 320 |
+
log_error(f"File saved to {file_path}")
|
| 321 |
+
flash('File successfully uploaded.')
|
| 322 |
+
|
| 323 |
+
# Check file size and contents
|
| 324 |
+
file_size = os.path.getsize(file_path)
|
| 325 |
+
log_error(f"File size: {file_size} bytes")
|
| 326 |
+
|
| 327 |
+
if file_size == 0:
|
| 328 |
+
error_msg = 'The uploaded file is empty. Please check your file and try again.'
|
| 329 |
+
log_error(error_msg)
|
| 330 |
+
flash(error_msg)
|
| 331 |
+
return redirect(url_for('index'))
|
| 332 |
+
|
| 333 |
+
# Preview the file contents
|
| 334 |
+
try:
|
| 335 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
| 336 |
+
first_lines = [next(f) for _ in range(5) if f]
|
| 337 |
+
log_error(f"File preview: {first_lines}")
|
| 338 |
+
except Exception as e:
|
| 339 |
+
log_error(f"Could not preview file: {str(e)}")
|
| 340 |
+
|
| 341 |
+
# Try to load documents to verify the file format
|
| 342 |
+
docs = load_documents()
|
| 343 |
+
|
| 344 |
+
if not docs:
|
| 345 |
+
error_msg = 'No valid documents found in the uploaded file. Please check the file format and try again.'
|
| 346 |
+
log_error(error_msg)
|
| 347 |
+
flash(error_msg)
|
| 348 |
+
return redirect(url_for('index'))
|
| 349 |
+
|
| 350 |
+
# Success - redirect to the evaluation page
|
| 351 |
+
log_error(f"Successfully loaded {len(docs)} documents, redirecting to evaluate page")
|
| 352 |
+
return redirect(url_for('evaluate'))
|
| 353 |
+
|
| 354 |
+
except Exception as e:
|
| 355 |
+
error_msg = f'Error during file upload: {str(e)}'
|
| 356 |
+
log_error(error_msg)
|
| 357 |
+
flash(f'{error_msg}. Please try again.')
|
| 358 |
+
traceback.print_exc() # Print the full traceback for debugging
|
| 359 |
+
return redirect(url_for('index'))
|
| 360 |
+
|
| 361 |
+
return render_template('index.html')
|
| 362 |
|
| 363 |
@app.route('/evaluate', methods=['GET', 'POST'])
|
| 364 |
def evaluate():
|
| 365 |
+
"""Evaluation page for documents."""
|
| 366 |
+
# Check if evaluator name is set
|
| 367 |
+
if 'evaluator_name' not in session:
|
| 368 |
+
flash('Please enter your name and upload a file first')
|
| 369 |
+
return redirect(url_for('index'))
|
| 370 |
|
| 371 |
if request.method == 'POST':
|
| 372 |
+
investigator_name = session.get('evaluator_name')
|
| 373 |
document_title = session.get('current_document_title')
|
| 374 |
document_description = session.get('current_document_description', '')
|
| 375 |
+
document_mrn = session.get('current_document_mrn', '')
|
| 376 |
+
note_origin = request.form.get('note_origin', 'Not sure')
|
| 377 |
|
| 378 |
# Collect scores
|
| 379 |
scores = [request.form.get(f'criteria_{i}') for i in range(len(CRITERIA))]
|
|
|
|
| 382 |
data = [datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
| 383 |
investigator_name,
|
| 384 |
document_title,
|
| 385 |
+
document_description,
|
| 386 |
+
document_mrn,
|
| 387 |
+
note_origin] + scores
|
| 388 |
save_evaluation(data)
|
| 389 |
|
| 390 |
flash('Evaluation saved successfully!')
|
| 391 |
+
|
| 392 |
+
# Check if all documents have been evaluated
|
| 393 |
+
documents = load_documents()
|
| 394 |
+
if not documents:
|
| 395 |
+
# All documents have been evaluated, redirect to results
|
| 396 |
+
flash('All evaluations completed! Here are the results.')
|
| 397 |
+
return redirect(url_for('results'))
|
| 398 |
+
|
| 399 |
return redirect(url_for('evaluate'))
|
| 400 |
|
| 401 |
# Get random document
|
| 402 |
documents = load_documents()
|
| 403 |
if not documents:
|
| 404 |
+
# Check if there are any evaluations
|
| 405 |
+
eval_df, _, _ = get_results()
|
| 406 |
+
if not eval_df.empty:
|
| 407 |
+
# There are evaluations, so all documents have been evaluated
|
| 408 |
+
flash('All evaluations completed! Here are the results.')
|
| 409 |
+
return redirect(url_for('results'))
|
| 410 |
return render_template('no_documents.html')
|
| 411 |
|
| 412 |
document = random.choice(documents)
|
| 413 |
session['current_document_title'] = document['filename']
|
| 414 |
session['current_document_description'] = document.get('description', '')
|
| 415 |
+
session['current_document_mrn'] = document.get('mrn', '')
|
| 416 |
+
|
| 417 |
+
# Get progress information
|
| 418 |
+
total_docs = get_total_document_count()
|
| 419 |
+
evaluated_docs = get_evaluated_document_count()
|
| 420 |
+
progress = 0
|
| 421 |
+
if total_docs > 0:
|
| 422 |
+
progress = int((evaluated_docs / total_docs) * 100)
|
| 423 |
|
| 424 |
return render_template('evaluate.html',
|
| 425 |
note=document['note'],
|
| 426 |
description=document.get('description', ''),
|
| 427 |
+
mrn=document.get('mrn', ''),
|
| 428 |
criteria=CRITERIA,
|
| 429 |
descriptions=CRITERIA_DESCRIPTIONS,
|
| 430 |
+
note_origins=NOTE_ORIGINS,
|
| 431 |
+
score_range=range(1, 6),
|
| 432 |
+
evaluator_name=session.get('evaluator_name', ''),
|
| 433 |
+
progress=progress,
|
| 434 |
+
evaluated_docs=evaluated_docs,
|
| 435 |
+
total_docs=total_docs)
|
| 436 |
|
| 437 |
@app.route('/results')
|
| 438 |
def results():
|
| 439 |
+
"""Results page showing all evaluations."""
|
| 440 |
+
eval_df, filename_to_desc, filename_to_mrn = get_results()
|
|
|
|
|
|
|
| 441 |
if eval_df.empty:
|
| 442 |
flash('No evaluations available yet.')
|
| 443 |
+
return redirect(url_for('index'))
|
| 444 |
|
| 445 |
return render_template('results.html',
|
| 446 |
evaluations=eval_df.to_dict('records'),
|
| 447 |
criteria=CRITERIA,
|
| 448 |
descriptions=CRITERIA_DESCRIPTIONS)
|
| 449 |
|
| 450 |
+
@app.route('/export-csv')
|
| 451 |
+
def export_csv():
|
| 452 |
+
"""Export evaluations to CSV file."""
|
| 453 |
+
try:
|
| 454 |
+
eval_df, _, _ = get_results()
|
| 455 |
+
if eval_df.empty:
|
| 456 |
+
flash('No evaluations available to export.')
|
| 457 |
+
return redirect(url_for('results'))
|
| 458 |
+
|
| 459 |
+
# Create in-memory CSV
|
| 460 |
+
output = io.StringIO()
|
| 461 |
+
eval_df.to_csv(output, index=False, quoting=csv.QUOTE_ALL)
|
| 462 |
+
output.seek(0)
|
| 463 |
+
|
| 464 |
+
# Convert to BytesIO for send_file
|
| 465 |
+
mem = io.BytesIO()
|
| 466 |
+
mem.write(output.getvalue().encode('utf-8'))
|
| 467 |
+
mem.seek(0)
|
| 468 |
+
|
| 469 |
+
return send_file(
|
| 470 |
+
mem,
|
| 471 |
+
mimetype='text/csv',
|
| 472 |
+
as_attachment=True,
|
| 473 |
+
download_name=f'evaluations_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv'
|
| 474 |
+
)
|
| 475 |
+
except Exception as e:
|
| 476 |
+
error_msg = f'Error exporting CSV: {str(e)}'
|
| 477 |
+
log_error(error_msg)
|
| 478 |
+
flash(error_msg)
|
| 479 |
+
return redirect(url_for('results'))
|
| 480 |
+
|
| 481 |
+
@app.route('/debug')
|
| 482 |
+
def debug():
|
| 483 |
+
"""Debug page showing application state."""
|
| 484 |
+
# Get documents
|
| 485 |
+
documents = load_documents()
|
| 486 |
+
|
| 487 |
+
# Get evaluations
|
| 488 |
+
evaluations, _, _ = get_results()
|
| 489 |
+
if not evaluations.empty:
|
| 490 |
+
evaluations = evaluations.to_dict('records')
|
| 491 |
+
else:
|
| 492 |
+
evaluations = []
|
| 493 |
+
|
| 494 |
+
# Check if files exist
|
| 495 |
+
documents_exists = os.path.exists(os.path.join(DATA_DIR, 'documents.csv'))
|
| 496 |
+
evaluations_exists = os.path.exists(os.path.join(DATA_DIR, 'evaluations.csv'))
|
| 497 |
+
|
| 498 |
+
return render_template('debug.html',
|
| 499 |
+
documents=documents,
|
| 500 |
+
evaluations=evaluations,
|
| 501 |
+
documents_exists=documents_exists,
|
| 502 |
+
evaluations_exists=evaluations_exists,
|
| 503 |
+
errors=ERROR_LOG)
|
| 504 |
+
|
| 505 |
@app.route('/instructions')
|
| 506 |
def view_instructions():
|
| 507 |
"""Display instructions page."""
|
| 508 |
+
return render_template('instructions.html',
|
| 509 |
+
criteria=CRITERIA,
|
| 510 |
+
descriptions=CRITERIA_DESCRIPTIONS)
|
| 511 |
|
| 512 |
@app.route('/download/instructions')
|
| 513 |
def download_instructions():
|
|
|
|
| 520 |
as_attachment=True)
|
| 521 |
except FileNotFoundError:
|
| 522 |
flash('Instructions file not found.')
|
| 523 |
+
return redirect(url_for('index'))
|
| 524 |
|
| 525 |
@app.route('/download/template')
|
| 526 |
def download_template():
|
|
|
|
| 533 |
as_attachment=True)
|
| 534 |
except FileNotFoundError:
|
| 535 |
flash('Template file not found.')
|
| 536 |
+
return redirect(url_for('index'))
|
| 537 |
|
| 538 |
+
@app.route('/reset', methods=['POST'])
|
| 539 |
+
def reset():
|
| 540 |
+
"""Reset the session and return to the landing page."""
|
| 541 |
+
session.clear()
|
| 542 |
+
# Remove evaluations.csv if it exists
|
| 543 |
+
evaluations_path = os.path.join(DATA_DIR, 'evaluations.csv')
|
| 544 |
+
if os.path.exists(evaluations_path):
|
| 545 |
+
os.remove(evaluations_path)
|
| 546 |
+
flash('Session reset. You can start a new evaluation.')
|
| 547 |
+
return redirect(url_for('index'))
|
| 548 |
|
| 549 |
if __name__ == '__main__':
|
| 550 |
# Create data directory if it doesn't exist
|
| 551 |
os.makedirs(DATA_DIR, exist_ok=True)
|
| 552 |
|
| 553 |
+
# Set debug mode for troubleshooting
|
| 554 |
+
app.config['DEBUG'] = True
|
| 555 |
+
|
| 556 |
# Set host to 0.0.0.0 to make it accessible outside container
|
| 557 |
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))
|
documents.csv
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
-
filename,description,note
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
doc4.txt,Initial psychiatric evaluation for major depressive disorder,Patient is a 42-year-old divorced female presenting with a 6-month history of persistent low mood, anhedonia, insomnia, poor concentration, and passive suicidal ideation without plan or intent. Symptoms began after job loss and divorce. PHQ-9 score is 21, indicating severe depression. No previous psychiatric history or treatment. Family history is positive for depression in mother. Mental status examination showed psychomotor retardation, depressed affect, and negative thought content. Diagnosis: Major Depressive Disorder, recurrent, severe without psychotic features. Plan: Start sertraline 50mg daily with weekly follow-up initially. Safety plan established. Referral to psychotherapy made.
|
|
|
|
| 1 |
+
filename,description,mrn,note
|
| 2 |
+
"progress_note_1.txt","Primary Care Follow-up Visit","MRN12345678","Patient is a 57-year-old male with history of hypertension, type 2 diabetes, and hyperlipidemia presenting for routine follow-up. BP today is 138/82, weight stable at 87kg. A1c improved from 7.8 to 7.2. Patient reports good medication adherence and states he is walking 30 minutes daily. Physical exam unremarkable. Assessment: 1) Hypertension - well controlled, continue current regimen 2) T2DM - improving, continue metformin 1000mg BID 3) Hyperlipidemia - LDL at goal, continue atorvastatin. Plan: Continue current medications, follow-up in 3 months, routine labs before next visit."
|
| 3 |
+
"ed_visit_note.txt","Emergency Department Visit for Chest Pain","MRN87654321","45-year-old female with no significant past medical history presents with sudden onset substernal chest pain that started 2 hours ago while at rest. Pain is 7/10, described as pressure-like, radiating to left arm, associated with mild SOB and nausea. Vitals: T 37.0, HR 102, BP 142/88, RR 18, SpO2 98% on RA. EKG shows NSR without ST changes. Initial troponin negative. CXR without acute findings. Patient given aspirin 325mg, sublingual nitroglycerin with relief of symptoms. Assessment: Acute chest pain, unclear etiology, low-intermediate risk for ACS. Plan: Admit to observation unit for serial troponins and stress test in AM."
|
| 4 |
+
"discharge_summary.txt","Hospital Discharge Summary - Pneumonia","MRN23456789","72-year-old male with COPD admitted for community-acquired pneumonia. Patient presented with 4 days of productive cough, fever, and worsening shortness of breath. CXR showed RLL infiltrate. Treated with IV ceftriaxone and azithromycin for 3 days with clinical improvement, then transitioned to oral antibiotics. O2 requirements decreased from 4L to baseline 2L. Pulmonary function optimized with scheduled bronchodilators. Discharged home on 7-day course of amoxicillin-clavulanate with pulmonology follow-up in 2 weeks. Patient educated on importance of pneumococcal and influenza vaccinations. Follow-up CXR recommended in 6 weeks to ensure resolution."
|
|
|
evaluations.csv
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
timestamp,investigator_name,document_title,description,mrn,note_origin,Up-to-date,Accurate,Thorough,Useful,Organized,Comprehensible,Succinct,Synthesized,Internally Consistent
|
| 2 |
+
2025-03-07 17:03:41,Iyad Sultan,progress_note_1.txt,Primary Care Follow-up Visit,MRN12345678,Human written note,3,3,3,3,3,3,3,3,3
|
| 3 |
+
2025-03-07 17:03:56,Iyad Sultan,ed_visit_note.txt,Emergency Department Visit for Chest Pain,MRN87654321,Generative AI note,3,3,3,3,3,3,3,3,3
|
| 4 |
+
2025-03-07 17:04:13,Iyad Sultan,discharge_summary.txt,Hospital Discharge Summary - Pneumonia,MRN23456789,Human written note,3,3,3,4,4,3,4,4,3
|
instructions.md
CHANGED
|
@@ -4,12 +4,11 @@ This document provides detailed instructions for using the Human Notes Evaluator
|
|
| 4 |
|
| 5 |
## Contents
|
| 6 |
1. [Overview](#overview)
|
| 7 |
-
2. [
|
| 8 |
-
3. [
|
| 9 |
-
4. [
|
| 10 |
-
5. [
|
| 11 |
-
6. [
|
| 12 |
-
7. [Troubleshooting](#troubleshooting)
|
| 13 |
|
| 14 |
## Overview
|
| 15 |
|
|
@@ -20,12 +19,25 @@ The Human Notes Evaluator is designed to systematically assess documents (such a
|
|
| 20 |
- Tracks which documents have been evaluated
|
| 21 |
- Displays results in a table format
|
| 22 |
|
| 23 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
## Preparing Documents for Evaluation
|
| 31 |
|
|
@@ -36,39 +48,20 @@ To add your own documents for evaluation:
|
|
| 36 |
3. Add your documents using the following format:
|
| 37 |
- **filename**: A unique identifier for each document (not shown to evaluators)
|
| 38 |
- **description**: (Optional) A brief description of the document context
|
|
|
|
| 39 |
- **note**: The full text of the document to be evaluated
|
| 40 |
4. Save the file as CSV format
|
| 41 |
-
5. Upload the completed CSV file
|
| 42 |
|
| 43 |
### Example CSV Format:
|
| 44 |
|
| 45 |
```
|
| 46 |
-
filename,description,note
|
| 47 |
-
doc1.txt,Patient with heart failure,This is the full text of the first document...
|
| 48 |
-
doc2.txt,Emergency room visit,This is the full text of the second document...
|
| 49 |
-
doc3.txt,,This is a document without a description...
|
| 50 |
```
|
| 51 |
|
| 52 |
-
## Evaluating Documents
|
| 53 |
-
|
| 54 |
-
1. After logging in, you'll see a randomly selected document for evaluation
|
| 55 |
-
2. Enter your name in the "Evaluator Name" field
|
| 56 |
-
3. For each criterion, select a rating from 1-5 (Not at all to Extremely)
|
| 57 |
-
4. All criteria must be rated before submission
|
| 58 |
-
5. Click "Submit Evaluation" to record your assessment
|
| 59 |
-
6. The system will then present another random document for evaluation
|
| 60 |
-
7. Continue until all documents have been evaluated
|
| 61 |
-
|
| 62 |
-
## Viewing Results
|
| 63 |
-
|
| 64 |
-
1. Click the "View Results" button in the header
|
| 65 |
-
2. Results are displayed in a table showing:
|
| 66 |
-
- Timestamp of evaluation
|
| 67 |
-
- Document identifier
|
| 68 |
-
- Document description (if provided)
|
| 69 |
-
- Evaluator name
|
| 70 |
-
- Scores for each criterion
|
| 71 |
-
|
| 72 |
## Understanding Evaluation Criteria
|
| 73 |
|
| 74 |
Documents are evaluated on a 1-5 scale (Not at all = 1, Extremely = 5) across these criteria:
|
|
@@ -114,18 +107,13 @@ Documents are evaluated on a 1-5 scale (Not at all = 1, Extremely = 5) across th
|
|
| 114 |
### Common Issues:
|
| 115 |
|
| 116 |
1. **No documents available for evaluation**
|
| 117 |
-
- Ensure the
|
| 118 |
- Check that not all documents have already been evaluated
|
| 119 |
|
| 120 |
2. **Unable to submit evaluation**
|
| 121 |
- Verify that all criteria have been rated
|
| 122 |
-
- Check that you've entered an evaluator name
|
| 123 |
-
|
| 124 |
-
3. **Login issues**
|
| 125 |
-
- Confirm you're using the correct password
|
| 126 |
-
- Contact the administrator if the password doesn't work
|
| 127 |
|
| 128 |
-
|
| 129 |
- Results will only appear after at least one document has been evaluated
|
| 130 |
|
| 131 |
For additional support, please contact your administrator.
|
|
|
|
| 4 |
|
| 5 |
## Contents
|
| 6 |
1. [Overview](#overview)
|
| 7 |
+
2. [Evaluating Documents](#evaluating-documents)
|
| 8 |
+
3. [Viewing Results](#viewing-results)
|
| 9 |
+
4. [Preparing Documents for Evaluation](#preparing-documents-for-evaluation)
|
| 10 |
+
5. [Understanding Evaluation Criteria](#understanding-evaluation-criteria)
|
| 11 |
+
6. [Troubleshooting](#troubleshooting)
|
|
|
|
| 12 |
|
| 13 |
## Overview
|
| 14 |
|
|
|
|
| 19 |
- Tracks which documents have been evaluated
|
| 20 |
- Displays results in a table format
|
| 21 |
|
| 22 |
+
## Evaluating Documents
|
| 23 |
+
|
| 24 |
+
1. Enter your name in the "Evaluator Name" field
|
| 25 |
+
2. For each criterion, select a rating from 1-5 (Not at all to Extremely)
|
| 26 |
+
3. All criteria must be rated before submission
|
| 27 |
+
4. Click "Submit Evaluation" to record your assessment
|
| 28 |
+
5. The system will then present another random document for evaluation
|
| 29 |
+
6. Continue until all documents have been evaluated
|
| 30 |
|
| 31 |
+
## Viewing Results
|
| 32 |
+
|
| 33 |
+
1. Click the "View Results" button in the header
|
| 34 |
+
2. Results are displayed in a table showing:
|
| 35 |
+
- Timestamp of evaluation
|
| 36 |
+
- Document identifier
|
| 37 |
+
- Document description (if provided)
|
| 38 |
+
- Medical Record Number (MRN)
|
| 39 |
+
- Evaluator name
|
| 40 |
+
- Scores for each criterion
|
| 41 |
|
| 42 |
## Preparing Documents for Evaluation
|
| 43 |
|
|
|
|
| 48 |
3. Add your documents using the following format:
|
| 49 |
- **filename**: A unique identifier for each document (not shown to evaluators)
|
| 50 |
- **description**: (Optional) A brief description of the document context
|
| 51 |
+
- **mrn**: Medical Record Number for the patient (to facilitate chart lookups)
|
| 52 |
- **note**: The full text of the document to be evaluated
|
| 53 |
4. Save the file as CSV format
|
| 54 |
+
5. Upload the completed CSV file using the file upload on the home page
|
| 55 |
|
| 56 |
### Example CSV Format:
|
| 57 |
|
| 58 |
```
|
| 59 |
+
filename,description,mrn,note
|
| 60 |
+
doc1.txt,Patient with heart failure,MRN12345,This is the full text of the first document...
|
| 61 |
+
doc2.txt,Emergency room visit,MRN67890,This is the full text of the second document...
|
| 62 |
+
doc3.txt,,MRN24680,This is a document without a description...
|
| 63 |
```
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
## Understanding Evaluation Criteria
|
| 66 |
|
| 67 |
Documents are evaluated on a 1-5 scale (Not at all = 1, Extremely = 5) across these criteria:
|
|
|
|
| 107 |
### Common Issues:
|
| 108 |
|
| 109 |
1. **No documents available for evaluation**
|
| 110 |
+
- Ensure the CSV file you uploaded exists and is properly formatted
|
| 111 |
- Check that not all documents have already been evaluated
|
| 112 |
|
| 113 |
2. **Unable to submit evaluation**
|
| 114 |
- Verify that all criteria have been rated
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
+
3. **Results not showing**
|
| 117 |
- Results will only appear after at least one document has been evaluated
|
| 118 |
|
| 119 |
For additional support, please contact your administrator.
|
project_status.md
CHANGED
|
@@ -15,21 +15,54 @@
|
|
| 15 |
- Added functionality to view instructions in the browser
|
| 16 |
- Implemented file download functionality for instructions and template files
|
| 17 |
- Added links to resources throughout the application
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
## Current Status
|
| 20 |
- Application is ready for deployment on Hugging Face Spaces
|
| 21 |
- All necessary files have been created and configured
|
| 22 |
- Sample data is included for testing
|
| 23 |
- Comprehensive user instructions are available both in-app and as downloadable files
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
## Next Steps
|
| 26 |
- Deploy application to Hugging Face Spaces
|
| 27 |
- Test application functionality in the Hugging Face environment
|
| 28 |
- Consider adding additional features:
|
| 29 |
-
- Export results to CSV/Excel
|
| 30 |
- Data visualization for evaluation results
|
| 31 |
- Custom evaluation criteria configuration
|
| 32 |
-
- User authentication for multiple evaluators
|
| 33 |
|
| 34 |
## Open Questions
|
| 35 |
- None at this time
|
|
|
|
| 15 |
- Added functionality to view instructions in the browser
|
| 16 |
- Implemented file download functionality for instructions and template files
|
| 17 |
- Added links to resources throughout the application
|
| 18 |
+
- Removed login page and authentication requirements for direct access to evaluation
|
| 19 |
+
- Improved error handling for CSV parsing to handle commas in text fields
|
| 20 |
+
- Enhanced the no_documents.html template with detailed troubleshooting guidance
|
| 21 |
+
- Fixed CSV formatting in example files to use proper quoting
|
| 22 |
+
- Added a landing page with file upload and evaluator name entry
|
| 23 |
+
- Moved evaluator name input to the landing page and stored in session
|
| 24 |
+
- Added navigation links to easily move between pages
|
| 25 |
+
- Implemented session reset functionality for starting new evaluations
|
| 26 |
+
- Added progress tracking with progress bar during evaluation
|
| 27 |
+
- Added automatic redirection to results when all documents are evaluated
|
| 28 |
+
- Implemented CSV export functionality for evaluation results
|
| 29 |
+
- Fixed table layout issues with responsive design for the results table
|
| 30 |
+
- Fixed file upload issues with improved error handling
|
| 31 |
+
- Added a debug page showing application state for troubleshooting
|
| 32 |
+
- Implemented error logging for better issue diagnosis
|
| 33 |
+
- Enhanced CSV parsing with multiple fallback methods for greater compatibility
|
| 34 |
+
- Added MRN (Medical Record Number) field to identify patient charts
|
| 35 |
+
- Updated all templates and sample files to include MRN information
|
| 36 |
+
- Implemented MRN display in the evaluation and results pages
|
| 37 |
+
- Significantly enhanced CSV parsing with automatic encoding detection
|
| 38 |
+
- Updated sample data with realistic clinical notes
|
| 39 |
+
- Improved error messages for file upload problems
|
| 40 |
+
- Added handling for common CSV format variations and column name mismatches
|
| 41 |
|
| 42 |
## Current Status
|
| 43 |
- Application is ready for deployment on Hugging Face Spaces
|
| 44 |
- All necessary files have been created and configured
|
| 45 |
- Sample data is included for testing
|
| 46 |
- Comprehensive user instructions are available both in-app and as downloadable files
|
| 47 |
+
- Landing page now provides file upload and evaluator name entry
|
| 48 |
+
- Robust error handling for CSV parsing issues
|
| 49 |
+
- Enhanced navigation between all pages
|
| 50 |
+
- Progress tracking during evaluation process
|
| 51 |
+
- Ability to export evaluation results to CSV
|
| 52 |
+
- Responsive design for better display on various devices
|
| 53 |
+
- Debug mode available for identifying issues
|
| 54 |
+
- Multiple CSV parsing methods for better compatibility with different file formats
|
| 55 |
+
- MRN tracking for patient chart identification
|
| 56 |
+
- Realistic clinical note examples included in sample files
|
| 57 |
+
- Enhanced error reporting and diagnosis capabilities
|
| 58 |
|
| 59 |
## Next Steps
|
| 60 |
- Deploy application to Hugging Face Spaces
|
| 61 |
- Test application functionality in the Hugging Face environment
|
| 62 |
- Consider adding additional features:
|
|
|
|
| 63 |
- Data visualization for evaluation results
|
| 64 |
- Custom evaluation criteria configuration
|
| 65 |
+
- User authentication for multiple evaluators (if needed in the future)
|
| 66 |
|
| 67 |
## Open Questions
|
| 68 |
- None at this time
|
readme.md
CHANGED
|
@@ -1,97 +1,12 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
## Criteria for Evaluation
|
| 14 |
-
|
| 15 |
-
Documents are evaluated on a 1-5 scale (Not at all to Extremely) across these criteria:
|
| 16 |
-
|
| 17 |
-
1. **Up-to-date**: The note contains the most recent test results and recommendations.
|
| 18 |
-
2. **Accurate**: The note is true. It is free of incorrect information.
|
| 19 |
-
3. **Thorough**: The note is complete and documents all of the issues of importance to the patient.
|
| 20 |
-
4. **Useful**: The note is extremely relevant, providing valuable information and/or analysis.
|
| 21 |
-
5. **Organized**: The note is well-formed and structured in a way that helps the reader understand the patient's clinical course.
|
| 22 |
-
6. **Comprehensible**: The note is clear, without ambiguity or sections that are difficult to understand.
|
| 23 |
-
7. **Succinct**: The note is brief, to the point, and without redundancy.
|
| 24 |
-
8. **Synthesized**: The note reflects the author's understanding of the patient's status and ability to develop a plan of care.
|
| 25 |
-
9. **Internally Consistent**: No part of the note ignores or contradicts any other part.
|
| 26 |
-
|
| 27 |
-
## How to Use
|
| 28 |
-
|
| 29 |
-
### Login
|
| 30 |
-
- Default password: `12345` (you can change this in the app.py file or set as an environment variable)
|
| 31 |
-
|
| 32 |
-
### Adding Your Own Documents
|
| 33 |
-
1. Download the sample template (`sample_documents_template.csv`)
|
| 34 |
-
2. Fill in your documents following the template format:
|
| 35 |
-
- **filename**: A unique identifier for the document (not visible to evaluators)
|
| 36 |
-
- **description**: (Optional) A brief description of the document
|
| 37 |
-
- **note**: The full text of the document to be evaluated
|
| 38 |
-
3. Upload your completed CSV file as `documents.csv` to your Hugging Face Space
|
| 39 |
-
|
| 40 |
-
### Evaluating Documents
|
| 41 |
-
1. Enter your name as the evaluator
|
| 42 |
-
2. Rate each document across all criteria
|
| 43 |
-
3. Submit your evaluation
|
| 44 |
-
4. Continue until all documents are evaluated
|
| 45 |
-
|
| 46 |
-
### Viewing Results
|
| 47 |
-
- Click "View Results" to see all evaluations in a table format
|
| 48 |
-
|
| 49 |
-
## For Developers
|
| 50 |
-
|
| 51 |
-
### Running Locally
|
| 52 |
-
|
| 53 |
-
1. Clone the repository
|
| 54 |
-
2. Install dependencies:
|
| 55 |
-
```
|
| 56 |
-
pip install -r requirements.txt
|
| 57 |
-
```
|
| 58 |
-
3. Run the application:
|
| 59 |
-
```
|
| 60 |
-
python app.py
|
| 61 |
-
```
|
| 62 |
-
|
| 63 |
-
### Environment Variables
|
| 64 |
-
|
| 65 |
-
- `SECRET_KEY`: Flask session encryption key (default: 'your-secret-key-here')
|
| 66 |
-
- `APP_PASSWORD`: Login password (default: '12345')
|
| 67 |
-
- `DATA_DIR`: Directory for data files (default: current directory)
|
| 68 |
-
- `PORT`: Port to run the application (default: 7860)
|
| 69 |
-
|
| 70 |
-
### Docker Usage
|
| 71 |
-
|
| 72 |
-
Build and run the Docker container:
|
| 73 |
-
|
| 74 |
-
```bash
|
| 75 |
-
docker build -t human-evaluator .
|
| 76 |
-
docker run -p 7860:7860 human-evaluator
|
| 77 |
-
```
|
| 78 |
-
|
| 79 |
-
## Deploying to Hugging Face Spaces
|
| 80 |
-
|
| 81 |
-
1. Create a new Space on Hugging Face with Docker runtime
|
| 82 |
-
2. Upload all files to the Space repository
|
| 83 |
-
3. Make sure to include your `documents.csv` file with the documents you want to evaluate
|
| 84 |
-
4. The Space will automatically build and deploy the application
|
| 85 |
-
|
| 86 |
-
## File Structure
|
| 87 |
-
|
| 88 |
-
- `app.py`: Main application code
|
| 89 |
-
- `Dockerfile`: Docker configuration
|
| 90 |
-
- `requirements.txt`: Python dependencies
|
| 91 |
-
- `documents.csv`: Your documents for evaluation
|
| 92 |
-
- `templates/`: HTML templates
|
| 93 |
-
- `static/`: CSS and other static assets
|
| 94 |
-
|
| 95 |
-
## Sample Data
|
| 96 |
-
|
| 97 |
-
A sample `documents.csv` file is included with example medical notes for testing purposes.
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Human Evaluator
|
| 3 |
+
emoji: 🏆
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
license: mit
|
| 9 |
+
short_description: Human evaluation of medical notes
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
|
@@ -12,3 +12,5 @@ pytz==2024.2
|
|
| 12 |
six==1.17.0
|
| 13 |
tzdata==2024.2
|
| 14 |
werkzeug==3.1.3
|
|
|
|
|
|
|
|
|
| 12 |
six==1.17.0
|
| 13 |
tzdata==2024.2
|
| 14 |
werkzeug==3.1.3
|
| 15 |
+
chardet==5.2.0
|
| 16 |
+
chardet==5.2.0
|
sample_documents_template.csv
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
filename,description,note
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
|
|
|
| 1 |
+
filename,description,mrn,note
|
| 2 |
+
"progress_note_1.txt","Primary Care Follow-up Visit","MRN12345678","Patient is a 57-year-old male with history of hypertension, type 2 diabetes, and hyperlipidemia presenting for routine follow-up. BP today is 138/82, weight stable at 87kg. A1c improved from 7.8 to 7.2. Patient reports good medication adherence and states he is walking 30 minutes daily. Physical exam unremarkable. Assessment: 1) Hypertension - well controlled, continue current regimen 2) T2DM - improving, continue metformin 1000mg BID 3) Hyperlipidemia - LDL at goal, continue atorvastatin. Plan: Continue current medications, follow-up in 3 months, routine labs before next visit."
|
| 3 |
+
"ed_visit_note.txt","Emergency Department Visit for Chest Pain","MRN87654321","45-year-old female with no significant past medical history presents with sudden onset substernal chest pain that started 2 hours ago while at rest. Pain is 7/10, described as pressure-like, radiating to left arm, associated with mild SOB and nausea. Vitals: T 37.0, HR 102, BP 142/88, RR 18, SpO2 98% on RA. EKG shows NSR without ST changes. Initial troponin negative. CXR without acute findings. Patient given aspirin 325mg, sublingual nitroglycerin with relief of symptoms. Assessment: Acute chest pain, unclear etiology, low-intermediate risk for ACS. Plan: Admit to observation unit for serial troponins and stress test in AM."
|
| 4 |
+
"discharge_summary.txt","Hospital Discharge Summary - Pneumonia","MRN23456789","72-year-old male with COPD admitted for community-acquired pneumonia. Patient presented with 4 days of productive cough, fever, and worsening shortness of breath. CXR showed RLL infiltrate. Treated with IV ceftriaxone and azithromycin for 3 days with clinical improvement, then transitioned to oral antibiotics. O2 requirements decreased from 4L to baseline 2L. Pulmonary function optimized with scheduled bronchodilators. Discharged home on 7-day course of amoxicillin-clavulanate with pulmonology follow-up in 2 weeks. Patient educated on importance of pneumococcal and influenza vaccinations. Follow-up CXR recommended in 6 weeks to ensure resolution."
|
static/style.css
CHANGED
|
@@ -60,12 +60,24 @@ h1, h2, h3 {
|
|
| 60 |
border: 1px solid #eee;
|
| 61 |
}
|
| 62 |
|
| 63 |
-
.
|
| 64 |
margin-bottom: 15px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
padding: 10px;
|
| 66 |
background-color: #e9f7fe;
|
| 67 |
border-radius: 4px;
|
| 68 |
border-left: 3px solid #3498db;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
}
|
| 70 |
|
| 71 |
.note-content {
|
|
@@ -181,6 +193,55 @@ h1, h2, h3 {
|
|
| 181 |
text-decoration: underline;
|
| 182 |
}
|
| 183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
@media (max-width: 768px) {
|
| 185 |
.container {
|
| 186 |
width: 95%;
|
|
@@ -195,4 +256,12 @@ h1, h2, h3 {
|
|
| 195 |
flex-direction: column;
|
| 196 |
gap: 5px;
|
| 197 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
}
|
|
|
|
| 60 |
border: 1px solid #eee;
|
| 61 |
}
|
| 62 |
|
| 63 |
+
.doc-info {
|
| 64 |
margin-bottom: 15px;
|
| 65 |
+
display: flex;
|
| 66 |
+
flex-wrap: wrap;
|
| 67 |
+
gap: 10px;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.info-item {
|
| 71 |
padding: 10px;
|
| 72 |
background-color: #e9f7fe;
|
| 73 |
border-radius: 4px;
|
| 74 |
border-left: 3px solid #3498db;
|
| 75 |
+
flex: 1 1 45%;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.mrn-info {
|
| 79 |
+
background-color: #f0f9eb;
|
| 80 |
+
border-left-color: #67c23a;
|
| 81 |
}
|
| 82 |
|
| 83 |
.note-content {
|
|
|
|
| 193 |
text-decoration: underline;
|
| 194 |
}
|
| 195 |
|
| 196 |
+
.evaluator-info {
|
| 197 |
+
background-color: #f0f7ff;
|
| 198 |
+
padding: 10px 15px;
|
| 199 |
+
border-radius: 4px;
|
| 200 |
+
margin-bottom: 20px;
|
| 201 |
+
border-left: 3px solid #3498db;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
.evaluator-info p {
|
| 205 |
+
margin: 0;
|
| 206 |
+
font-size: 16px;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
/* Progress bar styles */
|
| 210 |
+
.progress-bar-container {
|
| 211 |
+
margin-bottom: 20px;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
.progress-info {
|
| 215 |
+
display: flex;
|
| 216 |
+
justify-content: space-between;
|
| 217 |
+
margin-bottom: 5px;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
.progress-info p {
|
| 221 |
+
margin: 0;
|
| 222 |
+
font-size: 14px;
|
| 223 |
+
color: #666;
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
.progress-bar {
|
| 227 |
+
height: 10px;
|
| 228 |
+
background-color: #f0f0f0;
|
| 229 |
+
border-radius: 5px;
|
| 230 |
+
overflow: hidden;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
.progress-fill {
|
| 234 |
+
height: 100%;
|
| 235 |
+
background-color: #3498db;
|
| 236 |
+
border-radius: 5px;
|
| 237 |
+
transition: width 0.3s ease;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
/* Table responsive styles */
|
| 241 |
+
.table-wrapper {
|
| 242 |
+
overflow-x: auto;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
@media (max-width: 768px) {
|
| 246 |
.container {
|
| 247 |
width: 95%;
|
|
|
|
| 256 |
flex-direction: column;
|
| 257 |
gap: 5px;
|
| 258 |
}
|
| 259 |
+
|
| 260 |
+
.doc-info {
|
| 261 |
+
flex-direction: column;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
.info-item {
|
| 265 |
+
flex: 1 1 100%;
|
| 266 |
+
}
|
| 267 |
}
|
templates/debug.html
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Debug Information</title>
|
| 5 |
+
<style>
|
| 6 |
+
body {
|
| 7 |
+
font-family: monospace;
|
| 8 |
+
padding: 20px;
|
| 9 |
+
background-color: #f5f5f5;
|
| 10 |
+
}
|
| 11 |
+
.debug-container {
|
| 12 |
+
background-color: white;
|
| 13 |
+
padding: 20px;
|
| 14 |
+
border-radius: 5px;
|
| 15 |
+
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
| 16 |
+
max-width: 1000px;
|
| 17 |
+
margin: 0 auto;
|
| 18 |
+
}
|
| 19 |
+
h1 {
|
| 20 |
+
border-bottom: 2px solid #eee;
|
| 21 |
+
padding-bottom: 10px;
|
| 22 |
+
}
|
| 23 |
+
.section {
|
| 24 |
+
margin-bottom: 30px;
|
| 25 |
+
}
|
| 26 |
+
.section h2 {
|
| 27 |
+
background-color: #f0f0f0;
|
| 28 |
+
padding: 10px;
|
| 29 |
+
border-radius: 5px;
|
| 30 |
+
}
|
| 31 |
+
.debug-item {
|
| 32 |
+
border-bottom: 1px solid #eee;
|
| 33 |
+
padding: 10px 0;
|
| 34 |
+
}
|
| 35 |
+
.key {
|
| 36 |
+
font-weight: bold;
|
| 37 |
+
color: #333;
|
| 38 |
+
display: inline-block;
|
| 39 |
+
width: 200px;
|
| 40 |
+
}
|
| 41 |
+
.value {
|
| 42 |
+
font-family: monospace;
|
| 43 |
+
background-color: #f9f9f9;
|
| 44 |
+
padding: 3px 6px;
|
| 45 |
+
border-radius: 3px;
|
| 46 |
+
color: #0066cc;
|
| 47 |
+
display: inline-block;
|
| 48 |
+
white-space: pre-wrap;
|
| 49 |
+
word-break: break-all;
|
| 50 |
+
max-width: 700px;
|
| 51 |
+
}
|
| 52 |
+
.error {
|
| 53 |
+
color: red;
|
| 54 |
+
background-color: #fff0f0;
|
| 55 |
+
padding: 10px;
|
| 56 |
+
border-radius: 5px;
|
| 57 |
+
margin: 10px 0;
|
| 58 |
+
border-left: 4px solid red;
|
| 59 |
+
}
|
| 60 |
+
.success {
|
| 61 |
+
color: green;
|
| 62 |
+
background-color: #f0fff0;
|
| 63 |
+
padding: 10px;
|
| 64 |
+
border-radius: 5px;
|
| 65 |
+
margin: 10px 0;
|
| 66 |
+
border-left: 4px solid green;
|
| 67 |
+
}
|
| 68 |
+
.back-link {
|
| 69 |
+
margin-top: 20px;
|
| 70 |
+
display: inline-block;
|
| 71 |
+
padding: 8px 15px;
|
| 72 |
+
background-color: #0066cc;
|
| 73 |
+
color: white;
|
| 74 |
+
text-decoration: none;
|
| 75 |
+
border-radius: 4px;
|
| 76 |
+
}
|
| 77 |
+
.back-link:hover {
|
| 78 |
+
background-color: #0055aa;
|
| 79 |
+
}
|
| 80 |
+
</style>
|
| 81 |
+
</head>
|
| 82 |
+
<body>
|
| 83 |
+
<div class="debug-container">
|
| 84 |
+
<h1>Debug Information</h1>
|
| 85 |
+
|
| 86 |
+
<div class="section">
|
| 87 |
+
<h2>Session Data</h2>
|
| 88 |
+
{% if session %}
|
| 89 |
+
{% for key, value in session.items() %}
|
| 90 |
+
<div class="debug-item">
|
| 91 |
+
<span class="key">{{ key }}</span>: <span class="value">{{ value }}</span>
|
| 92 |
+
</div>
|
| 93 |
+
{% endfor %}
|
| 94 |
+
{% else %}
|
| 95 |
+
<div class="error">No session data available</div>
|
| 96 |
+
{% endif %}
|
| 97 |
+
</div>
|
| 98 |
+
|
| 99 |
+
<div class="section">
|
| 100 |
+
<h2>Files Available</h2>
|
| 101 |
+
<div class="debug-item">
|
| 102 |
+
<span class="key">documents.csv</span>:
|
| 103 |
+
<span class="value">
|
| 104 |
+
{% if documents_exists %}
|
| 105 |
+
<span class="success">File exists</span>
|
| 106 |
+
{% else %}
|
| 107 |
+
<span class="error">File does not exist</span>
|
| 108 |
+
{% endif %}
|
| 109 |
+
</span>
|
| 110 |
+
</div>
|
| 111 |
+
<div class="debug-item">
|
| 112 |
+
<span class="key">evaluations.csv</span>:
|
| 113 |
+
<span class="value">
|
| 114 |
+
{% if evaluations_exists %}
|
| 115 |
+
<span class="success">File exists</span>
|
| 116 |
+
{% else %}
|
| 117 |
+
<span class="error">File does not exist</span>
|
| 118 |
+
{% endif %}
|
| 119 |
+
</span>
|
| 120 |
+
</div>
|
| 121 |
+
</div>
|
| 122 |
+
|
| 123 |
+
<div class="section">
|
| 124 |
+
<h2>Document Data</h2>
|
| 125 |
+
{% if documents %}
|
| 126 |
+
<div class="success">Found {{ documents|length }} document(s) available for evaluation</div>
|
| 127 |
+
{% for doc in documents %}
|
| 128 |
+
<div class="debug-item">
|
| 129 |
+
<span class="key">Document {{ loop.index }}</span>:
|
| 130 |
+
<span class="value">{{ doc.filename }} ({{ doc.description }})</span>
|
| 131 |
+
</div>
|
| 132 |
+
{% endfor %}
|
| 133 |
+
{% else %}
|
| 134 |
+
<div class="error">No documents found or all documents have been evaluated</div>
|
| 135 |
+
{% endif %}
|
| 136 |
+
</div>
|
| 137 |
+
|
| 138 |
+
<div class="section">
|
| 139 |
+
<h2>Evaluations</h2>
|
| 140 |
+
{% if evaluations %}
|
| 141 |
+
<div class="success">Found {{ evaluations|length }} evaluation(s)</div>
|
| 142 |
+
{% for eval in evaluations %}
|
| 143 |
+
<div class="debug-item">
|
| 144 |
+
<span class="key">Evaluation {{ loop.index }}</span>:
|
| 145 |
+
<span class="value">{{ eval.document_title }} by {{ eval.investigator_name }}</span>
|
| 146 |
+
</div>
|
| 147 |
+
{% endfor %}
|
| 148 |
+
{% else %}
|
| 149 |
+
<div class="error">No evaluations found</div>
|
| 150 |
+
{% endif %}
|
| 151 |
+
</div>
|
| 152 |
+
|
| 153 |
+
<div class="section">
|
| 154 |
+
<h2>Recent Errors</h2>
|
| 155 |
+
{% if errors %}
|
| 156 |
+
{% for error in errors %}
|
| 157 |
+
<div class="error">{{ error }}</div>
|
| 158 |
+
{% endfor %}
|
| 159 |
+
{% else %}
|
| 160 |
+
<div class="success">No errors recorded</div>
|
| 161 |
+
{% endif %}
|
| 162 |
+
</div>
|
| 163 |
+
|
| 164 |
+
<a href="{{ url_for('index') }}" class="back-link">Back to Home</a>
|
| 165 |
+
</div>
|
| 166 |
+
</body>
|
| 167 |
+
</html>
|
templates/evaluate.html
CHANGED
|
@@ -4,6 +4,30 @@
|
|
| 4 |
<head>
|
| 5 |
<title>Evaluate Document</title>
|
| 6 |
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
</head>
|
| 8 |
<body>
|
| 9 |
<div class="container">
|
|
@@ -12,28 +36,43 @@
|
|
| 12 |
<div class="header-links">
|
| 13 |
<a href="{{ url_for('view_instructions') }}" class="instructions-btn">Instructions</a>
|
| 14 |
<a href="{{ url_for('results') }}" class="results-btn">View Results</a>
|
| 15 |
-
<a href="{{ url_for('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
</div>
|
| 17 |
</div>
|
| 18 |
|
| 19 |
<div class="note-container">
|
| 20 |
<h2>Document Content:</h2>
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
</div>
|
| 25 |
-
{% endif %}
|
| 26 |
<div class="note-content">
|
| 27 |
{{ note }}
|
| 28 |
</div>
|
| 29 |
</div>
|
| 30 |
|
| 31 |
<form method="POST">
|
| 32 |
-
<div class="form-group">
|
| 33 |
-
<label for="investigator_name">Evaluator Name:</label>
|
| 34 |
-
<input type="text" id="investigator_name" name="investigator_name" required>
|
| 35 |
-
</div>
|
| 36 |
-
|
| 37 |
<div class="criteria-container">
|
| 38 |
{% for i in range(criteria|length) %}
|
| 39 |
<div class="criteria-group">
|
|
@@ -54,6 +93,19 @@
|
|
| 54 |
{% endfor %}
|
| 55 |
</div>
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
<button type="submit" class="submit-btn">Submit Evaluation</button>
|
| 58 |
</form>
|
| 59 |
|
|
|
|
| 4 |
<head>
|
| 5 |
<title>Evaluate Document</title>
|
| 6 |
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
| 7 |
+
<style>
|
| 8 |
+
.form-section {
|
| 9 |
+
margin: 20px 0;
|
| 10 |
+
padding: 15px;
|
| 11 |
+
background-color: #f5f5f5;
|
| 12 |
+
border-radius: 8px;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.origin-options {
|
| 16 |
+
display: flex;
|
| 17 |
+
flex-direction: column;
|
| 18 |
+
gap: 10px;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
.origin-option {
|
| 22 |
+
display: flex;
|
| 23 |
+
align-items: center;
|
| 24 |
+
gap: 10px;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
.origin-option input[type="radio"] {
|
| 28 |
+
margin: 0;
|
| 29 |
+
}
|
| 30 |
+
</style>
|
| 31 |
</head>
|
| 32 |
<body>
|
| 33 |
<div class="container">
|
|
|
|
| 36 |
<div class="header-links">
|
| 37 |
<a href="{{ url_for('view_instructions') }}" class="instructions-btn">Instructions</a>
|
| 38 |
<a href="{{ url_for('results') }}" class="results-btn">View Results</a>
|
| 39 |
+
<a href="{{ url_for('index') }}" class="evaluate-btn">Back to Home</a>
|
| 40 |
+
</div>
|
| 41 |
+
</div>
|
| 42 |
+
|
| 43 |
+
<div class="evaluator-info">
|
| 44 |
+
<p><strong>Evaluator:</strong> {{ evaluator_name }}</p>
|
| 45 |
+
</div>
|
| 46 |
+
|
| 47 |
+
<div class="progress-bar-container">
|
| 48 |
+
<div class="progress-info">
|
| 49 |
+
<p>Progress: <strong>{{ evaluated_docs }} of {{ total_docs }}</strong> documents evaluated ({{ progress }}%)</p>
|
| 50 |
+
</div>
|
| 51 |
+
<div class="progress-bar">
|
| 52 |
+
<div class="progress-fill" style="width: {{ progress }}%;"></div>
|
| 53 |
</div>
|
| 54 |
</div>
|
| 55 |
|
| 56 |
<div class="note-container">
|
| 57 |
<h2>Document Content:</h2>
|
| 58 |
+
<div class="doc-info">
|
| 59 |
+
{% if description %}
|
| 60 |
+
<div class="info-item">
|
| 61 |
+
<strong>Description:</strong> {{ description }}
|
| 62 |
+
</div>
|
| 63 |
+
{% endif %}
|
| 64 |
+
{% if mrn %}
|
| 65 |
+
<div class="info-item mrn-info">
|
| 66 |
+
<strong>MRN:</strong> {{ mrn }}
|
| 67 |
+
</div>
|
| 68 |
+
{% endif %}
|
| 69 |
</div>
|
|
|
|
| 70 |
<div class="note-content">
|
| 71 |
{{ note }}
|
| 72 |
</div>
|
| 73 |
</div>
|
| 74 |
|
| 75 |
<form method="POST">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
<div class="criteria-container">
|
| 77 |
{% for i in range(criteria|length) %}
|
| 78 |
<div class="criteria-group">
|
|
|
|
| 93 |
{% endfor %}
|
| 94 |
</div>
|
| 95 |
|
| 96 |
+
<div class="form-section">
|
| 97 |
+
<h3>Note Origin Assessment</h3>
|
| 98 |
+
<div class="origin-options">
|
| 99 |
+
{% for origin in note_origins %}
|
| 100 |
+
<div class="origin-option">
|
| 101 |
+
<input type="radio" id="origin_{{ loop.index }}" name="note_origin"
|
| 102 |
+
value="{{ origin }}" {% if loop.first %}checked{% endif %}>
|
| 103 |
+
<label for="origin_{{ loop.index }}">{{ origin }}</label>
|
| 104 |
+
</div>
|
| 105 |
+
{% endfor %}
|
| 106 |
+
</div>
|
| 107 |
+
</div>
|
| 108 |
+
|
| 109 |
<button type="submit" class="submit-btn">Submit Evaluation</button>
|
| 110 |
</form>
|
| 111 |
|
templates/index.html
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- templates/index.html -->
|
| 2 |
+
<!DOCTYPE html>
|
| 3 |
+
<html>
|
| 4 |
+
<head>
|
| 5 |
+
<title>Human Notes Evaluator</title>
|
| 6 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
| 7 |
+
<style>
|
| 8 |
+
.upload-container {
|
| 9 |
+
max-width: 600px;
|
| 10 |
+
margin: 0 auto;
|
| 11 |
+
text-align: center;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
.upload-form {
|
| 15 |
+
background-color: #f9f9f9;
|
| 16 |
+
padding: 25px;
|
| 17 |
+
border-radius: 8px;
|
| 18 |
+
border: 1px solid #eee;
|
| 19 |
+
margin-top: 30px;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.file-input-wrapper {
|
| 23 |
+
margin: 20px 0;
|
| 24 |
+
padding: 20px;
|
| 25 |
+
border: 2px dashed #ccc;
|
| 26 |
+
border-radius: 5px;
|
| 27 |
+
text-align: center;
|
| 28 |
+
transition: all 0.3s;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
.file-input-wrapper:hover {
|
| 32 |
+
border-color: #3498db;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.file-input-wrapper input[type="file"] {
|
| 36 |
+
display: block;
|
| 37 |
+
width: 100%;
|
| 38 |
+
margin-top: 10px;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.steps-container {
|
| 42 |
+
margin-bottom: 30px;
|
| 43 |
+
text-align: left;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.step {
|
| 47 |
+
margin-bottom: 15px;
|
| 48 |
+
display: flex;
|
| 49 |
+
align-items: flex-start;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.step-number {
|
| 53 |
+
display: inline-block;
|
| 54 |
+
width: 25px;
|
| 55 |
+
height: 25px;
|
| 56 |
+
background-color: #3498db;
|
| 57 |
+
color: white;
|
| 58 |
+
border-radius: 50%;
|
| 59 |
+
text-align: center;
|
| 60 |
+
line-height: 25px;
|
| 61 |
+
margin-right: 10px;
|
| 62 |
+
flex-shrink: 0;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.step-content {
|
| 66 |
+
flex-grow: 1;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
.submit-container {
|
| 70 |
+
margin-top: 30px;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.preview-info {
|
| 74 |
+
text-align: left;
|
| 75 |
+
margin-top: 15px;
|
| 76 |
+
font-size: 14px;
|
| 77 |
+
color: #666;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.preview-table {
|
| 81 |
+
width: 100%;
|
| 82 |
+
border-collapse: collapse;
|
| 83 |
+
margin-top: 10px;
|
| 84 |
+
font-size: 14px;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.preview-table th, .preview-table td {
|
| 88 |
+
border: 1px solid #ddd;
|
| 89 |
+
padding: 8px;
|
| 90 |
+
text-align: left;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.preview-table th {
|
| 94 |
+
background-color: #f2f2f2;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.debug-link {
|
| 98 |
+
margin-top: 20px;
|
| 99 |
+
text-align: center;
|
| 100 |
+
font-size: 12px;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.debug-link a {
|
| 104 |
+
color: #999;
|
| 105 |
+
text-decoration: none;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
.debug-link a:hover {
|
| 109 |
+
text-decoration: underline;
|
| 110 |
+
}
|
| 111 |
+
</style>
|
| 112 |
+
</head>
|
| 113 |
+
<body>
|
| 114 |
+
<div class="container">
|
| 115 |
+
<div class="header">
|
| 116 |
+
<h1>Human Notes Evaluator</h1>
|
| 117 |
+
<div class="header-links">
|
| 118 |
+
<a href="{{ url_for('view_instructions') }}" class="instructions-btn">Instructions</a>
|
| 119 |
+
<a href="{{ url_for('results') }}" class="results-btn">View Results</a>
|
| 120 |
+
</div>
|
| 121 |
+
</div>
|
| 122 |
+
|
| 123 |
+
<div class="upload-container">
|
| 124 |
+
<h2>Welcome to the Human Notes Evaluator</h2>
|
| 125 |
+
<p>This application allows you to evaluate clinical notes based on several quality criteria.</p>
|
| 126 |
+
|
| 127 |
+
<div class="steps-container">
|
| 128 |
+
<h3>Getting Started</h3>
|
| 129 |
+
|
| 130 |
+
<div class="step">
|
| 131 |
+
<span class="step-number">1</span>
|
| 132 |
+
<div class="step-content">
|
| 133 |
+
<strong>Prepare your documents file</strong>
|
| 134 |
+
<p>Create a CSV file with columns for filename, description, MRN, and note content.
|
| 135 |
+
You can <a href="{{ url_for('download_template') }}">download a template</a> to get started.</p>
|
| 136 |
+
</div>
|
| 137 |
+
</div>
|
| 138 |
+
|
| 139 |
+
<div class="step">
|
| 140 |
+
<span class="step-number">2</span>
|
| 141 |
+
<div class="step-content">
|
| 142 |
+
<strong>Enter your name</strong>
|
| 143 |
+
<p>Provide your name as the evaluator so we can track who completed each evaluation.</p>
|
| 144 |
+
</div>
|
| 145 |
+
</div>
|
| 146 |
+
|
| 147 |
+
<div class="step">
|
| 148 |
+
<span class="step-number">3</span>
|
| 149 |
+
<div class="step-content">
|
| 150 |
+
<strong>Upload your documents file</strong>
|
| 151 |
+
<p>Select and upload your CSV file containing the clinical notes to evaluate.</p>
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
<div class="step">
|
| 156 |
+
<span class="step-number">4</span>
|
| 157 |
+
<div class="step-content">
|
| 158 |
+
<strong>Start evaluating</strong>
|
| 159 |
+
<p>After uploading, you'll be presented with clinical notes to evaluate one by one.</p>
|
| 160 |
+
</div>
|
| 161 |
+
</div>
|
| 162 |
+
</div>
|
| 163 |
+
|
| 164 |
+
<div class="upload-form">
|
| 165 |
+
<h3>Upload Documents and Begin Evaluation</h3>
|
| 166 |
+
<form method="POST" enctype="multipart/form-data" action="{{ url_for('index') }}">
|
| 167 |
+
<div class="form-group">
|
| 168 |
+
<label for="evaluator_name">Your Name (as Evaluator):</label>
|
| 169 |
+
<input type="text" id="evaluator_name" name="evaluator_name" required>
|
| 170 |
+
</div>
|
| 171 |
+
|
| 172 |
+
<div class="file-input-wrapper">
|
| 173 |
+
<label for="file">Select CSV file with documents to evaluate:</label>
|
| 174 |
+
<input type="file" id="file" name="file" accept=".csv" required>
|
| 175 |
+
<div class="preview-info">
|
| 176 |
+
<p>Your CSV file should have these columns:</p>
|
| 177 |
+
<table class="preview-table">
|
| 178 |
+
<thead>
|
| 179 |
+
<tr>
|
| 180 |
+
<th>filename</th>
|
| 181 |
+
<th>description</th>
|
| 182 |
+
<th>mrn</th>
|
| 183 |
+
<th>note</th>
|
| 184 |
+
</tr>
|
| 185 |
+
</thead>
|
| 186 |
+
<tbody>
|
| 187 |
+
<tr>
|
| 188 |
+
<td>progress_note_1.txt</td>
|
| 189 |
+
<td>Primary Care Follow-up Visit</td>
|
| 190 |
+
<td>MRN12345678</td>
|
| 191 |
+
<td>Patient is a 57-year-old male with history of hypertension, type 2 diabetes...</td>
|
| 192 |
+
</tr>
|
| 193 |
+
</tbody>
|
| 194 |
+
</table>
|
| 195 |
+
</div>
|
| 196 |
+
</div>
|
| 197 |
+
|
| 198 |
+
<div class="submit-container">
|
| 199 |
+
<button type="submit" class="submit-btn">Upload & Begin Evaluation</button>
|
| 200 |
+
</div>
|
| 201 |
+
</form>
|
| 202 |
+
</div>
|
| 203 |
+
|
| 204 |
+
{% if session.get('evaluator_name') %}
|
| 205 |
+
<div style="margin-top: 20px;">
|
| 206 |
+
<form method="POST" action="{{ url_for('reset') }}">
|
| 207 |
+
<button type="submit" class="logout-btn">Reset & Start New Evaluation</button>
|
| 208 |
+
</form>
|
| 209 |
+
</div>
|
| 210 |
+
{% endif %}
|
| 211 |
+
|
| 212 |
+
<div class="debug-link">
|
| 213 |
+
<a href="{{ url_for('debug') }}">Debug Information</a>
|
| 214 |
+
</div>
|
| 215 |
+
</div>
|
| 216 |
+
|
| 217 |
+
{% with messages = get_flashed_messages() %}
|
| 218 |
+
{% if messages %}
|
| 219 |
+
{% for message in messages %}
|
| 220 |
+
<div class="alert">{{ message }}</div>
|
| 221 |
+
{% endfor %}
|
| 222 |
+
{% endif %}
|
| 223 |
+
{% endwith %}
|
| 224 |
+
|
| 225 |
+
<div class="resource-links">
|
| 226 |
+
<p>
|
| 227 |
+
<strong>Resources:</strong>
|
| 228 |
+
<a href="{{ url_for('download_instructions') }}">Download Instructions</a> |
|
| 229 |
+
<a href="{{ url_for('download_template') }}">Download Template CSV</a>
|
| 230 |
+
</p>
|
| 231 |
+
</div>
|
| 232 |
+
</div>
|
| 233 |
+
</body>
|
| 234 |
+
</html>
|
templates/instructions.html
CHANGED
|
@@ -73,7 +73,7 @@
|
|
| 73 |
<h1>Instructions</h1>
|
| 74 |
<div class="header-links">
|
| 75 |
<a href="{{ url_for('evaluate') }}" class="evaluate-btn">Back to Evaluation</a>
|
| 76 |
-
<a href="{{ url_for('
|
| 77 |
</div>
|
| 78 |
</div>
|
| 79 |
|
|
@@ -85,10 +85,9 @@
|
|
| 85 |
<h2>Contents</h2>
|
| 86 |
<ol>
|
| 87 |
<li><a href="#overview">Overview</a></li>
|
| 88 |
-
<li><a href="#login-process">Login Process</a></li>
|
| 89 |
-
<li><a href="#preparing-documents">Preparing Documents for Evaluation</a></li>
|
| 90 |
<li><a href="#evaluating-documents">Evaluating Documents</a></li>
|
| 91 |
<li><a href="#viewing-results">Viewing Results</a></li>
|
|
|
|
| 92 |
<li><a href="#understanding-criteria">Understanding Evaluation Criteria</a></li>
|
| 93 |
<li><a href="#troubleshooting">Troubleshooting</a></li>
|
| 94 |
</ol>
|
|
@@ -102,15 +101,28 @@
|
|
| 102 |
<li>Displays results in a table format</li>
|
| 103 |
</ul>
|
| 104 |
|
| 105 |
-
<h2 id="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
<ol>
|
| 107 |
-
<li>
|
| 108 |
-
<li>
|
| 109 |
<ul>
|
| 110 |
-
<li>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
</ul>
|
| 112 |
</li>
|
| 113 |
-
<li>Upon successful login, you will be directed to the evaluation interface</li>
|
| 114 |
</ol>
|
| 115 |
|
| 116 |
<h2 id="preparing-documents">Preparing Documents for Evaluation</h2>
|
|
@@ -126,7 +138,7 @@
|
|
| 126 |
</ul>
|
| 127 |
</li>
|
| 128 |
<li>Save the file as CSV format</li>
|
| 129 |
-
<li>Upload the completed CSV file
|
| 130 |
</ol>
|
| 131 |
|
| 132 |
<h3>Example CSV Format:</h3>
|
|
@@ -135,31 +147,6 @@ doc1.txt,Patient with heart failure,This is the full text of the first document.
|
|
| 135 |
doc2.txt,Emergency room visit,This is the full text of the second document...
|
| 136 |
doc3.txt,,This is a document without a description...</pre>
|
| 137 |
|
| 138 |
-
<h2 id="evaluating-documents">Evaluating Documents</h2>
|
| 139 |
-
<ol>
|
| 140 |
-
<li>After logging in, you'll see a randomly selected document for evaluation</li>
|
| 141 |
-
<li>Enter your name in the "Evaluator Name" field</li>
|
| 142 |
-
<li>For each criterion, select a rating from 1-5 (Not at all to Extremely)</li>
|
| 143 |
-
<li>All criteria must be rated before submission</li>
|
| 144 |
-
<li>Click "Submit Evaluation" to record your assessment</li>
|
| 145 |
-
<li>The system will then present another random document for evaluation</li>
|
| 146 |
-
<li>Continue until all documents have been evaluated</li>
|
| 147 |
-
</ol>
|
| 148 |
-
|
| 149 |
-
<h2 id="viewing-results">Viewing Results</h2>
|
| 150 |
-
<ol>
|
| 151 |
-
<li>Click the "View Results" button in the header</li>
|
| 152 |
-
<li>Results are displayed in a table showing:
|
| 153 |
-
<ul>
|
| 154 |
-
<li>Timestamp of evaluation</li>
|
| 155 |
-
<li>Document identifier</li>
|
| 156 |
-
<li>Document description (if provided)</li>
|
| 157 |
-
<li>Evaluator name</li>
|
| 158 |
-
<li>Scores for each criterion</li>
|
| 159 |
-
</ul>
|
| 160 |
-
</li>
|
| 161 |
-
</ol>
|
| 162 |
-
|
| 163 |
<h2 id="understanding-criteria">Understanding Evaluation Criteria</h2>
|
| 164 |
<p>Documents are evaluated on a 1-5 scale (Not at all = 1, Extremely = 5) across these criteria:</p>
|
| 165 |
|
|
@@ -178,7 +165,7 @@ doc3.txt,,This is a document without a description...</pre>
|
|
| 178 |
<li>
|
| 179 |
<strong>No documents available for evaluation</strong>
|
| 180 |
<ul>
|
| 181 |
-
<li>Ensure the
|
| 182 |
<li>Check that not all documents have already been evaluated</li>
|
| 183 |
</ul>
|
| 184 |
</li>
|
|
@@ -186,14 +173,6 @@ doc3.txt,,This is a document without a description...</pre>
|
|
| 186 |
<strong>Unable to submit evaluation</strong>
|
| 187 |
<ul>
|
| 188 |
<li>Verify that all criteria have been rated</li>
|
| 189 |
-
<li>Check that you've entered an evaluator name</li>
|
| 190 |
-
</ul>
|
| 191 |
-
</li>
|
| 192 |
-
<li>
|
| 193 |
-
<strong>Login issues</strong>
|
| 194 |
-
<ul>
|
| 195 |
-
<li>Confirm you're using the correct password</li>
|
| 196 |
-
<li>Contact the administrator if the password doesn't work</li>
|
| 197 |
</ul>
|
| 198 |
</li>
|
| 199 |
<li>
|
|
|
|
| 73 |
<h1>Instructions</h1>
|
| 74 |
<div class="header-links">
|
| 75 |
<a href="{{ url_for('evaluate') }}" class="evaluate-btn">Back to Evaluation</a>
|
| 76 |
+
<a href="{{ url_for('index') }}" class="evaluate-btn">Back to Home</a>
|
| 77 |
</div>
|
| 78 |
</div>
|
| 79 |
|
|
|
|
| 85 |
<h2>Contents</h2>
|
| 86 |
<ol>
|
| 87 |
<li><a href="#overview">Overview</a></li>
|
|
|
|
|
|
|
| 88 |
<li><a href="#evaluating-documents">Evaluating Documents</a></li>
|
| 89 |
<li><a href="#viewing-results">Viewing Results</a></li>
|
| 90 |
+
<li><a href="#preparing-documents">Preparing Documents for Evaluation</a></li>
|
| 91 |
<li><a href="#understanding-criteria">Understanding Evaluation Criteria</a></li>
|
| 92 |
<li><a href="#troubleshooting">Troubleshooting</a></li>
|
| 93 |
</ol>
|
|
|
|
| 101 |
<li>Displays results in a table format</li>
|
| 102 |
</ul>
|
| 103 |
|
| 104 |
+
<h2 id="evaluating-documents">Evaluating Documents</h2>
|
| 105 |
+
<ol>
|
| 106 |
+
<li>Enter your name in the "Evaluator Name" field</li>
|
| 107 |
+
<li>For each criterion, select a rating from 1-5 (Not at all to Extremely)</li>
|
| 108 |
+
<li>All criteria must be rated before submission</li>
|
| 109 |
+
<li>Click "Submit Evaluation" to record your assessment</li>
|
| 110 |
+
<li>The system will then present another random document for evaluation</li>
|
| 111 |
+
<li>Continue until all documents have been evaluated</li>
|
| 112 |
+
</ol>
|
| 113 |
+
|
| 114 |
+
<h2 id="viewing-results">Viewing Results</h2>
|
| 115 |
<ol>
|
| 116 |
+
<li>Click the "View Results" button in the header</li>
|
| 117 |
+
<li>Results are displayed in a table showing:
|
| 118 |
<ul>
|
| 119 |
+
<li>Timestamp of evaluation</li>
|
| 120 |
+
<li>Document identifier</li>
|
| 121 |
+
<li>Document description (if provided)</li>
|
| 122 |
+
<li>Evaluator name</li>
|
| 123 |
+
<li>Scores for each criterion</li>
|
| 124 |
</ul>
|
| 125 |
</li>
|
|
|
|
| 126 |
</ol>
|
| 127 |
|
| 128 |
<h2 id="preparing-documents">Preparing Documents for Evaluation</h2>
|
|
|
|
| 138 |
</ul>
|
| 139 |
</li>
|
| 140 |
<li>Save the file as CSV format</li>
|
| 141 |
+
<li>Upload the completed CSV file using the file upload on the home page</li>
|
| 142 |
</ol>
|
| 143 |
|
| 144 |
<h3>Example CSV Format:</h3>
|
|
|
|
| 147 |
doc2.txt,Emergency room visit,This is the full text of the second document...
|
| 148 |
doc3.txt,,This is a document without a description...</pre>
|
| 149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
<h2 id="understanding-criteria">Understanding Evaluation Criteria</h2>
|
| 151 |
<p>Documents are evaluated on a 1-5 scale (Not at all = 1, Extremely = 5) across these criteria:</p>
|
| 152 |
|
|
|
|
| 165 |
<li>
|
| 166 |
<strong>No documents available for evaluation</strong>
|
| 167 |
<ul>
|
| 168 |
+
<li>Ensure the CSV file you uploaded exists and is properly formatted</li>
|
| 169 |
<li>Check that not all documents have already been evaluated</li>
|
| 170 |
</ul>
|
| 171 |
</li>
|
|
|
|
| 173 |
<strong>Unable to submit evaluation</strong>
|
| 174 |
<ul>
|
| 175 |
<li>Verify that all criteria have been rated</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
</ul>
|
| 177 |
</li>
|
| 178 |
<li>
|
templates/no_documents.html
CHANGED
|
@@ -2,20 +2,114 @@
|
|
| 2 |
<!DOCTYPE html>
|
| 3 |
<html>
|
| 4 |
<head>
|
| 5 |
-
<title>No Documents Available</title>
|
| 6 |
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
</head>
|
| 8 |
<body>
|
| 9 |
<div class="container">
|
| 10 |
<div class="header">
|
| 11 |
-
<h1>
|
| 12 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
</div>
|
| 14 |
-
|
| 15 |
-
<div class="
|
| 16 |
-
<p>
|
| 17 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</div>
|
| 20 |
</body>
|
| 21 |
</html>
|
|
|
|
| 2 |
<!DOCTYPE html>
|
| 3 |
<html>
|
| 4 |
<head>
|
| 5 |
+
<title>No Documents Available - Human Notes Evaluator</title>
|
| 6 |
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
| 7 |
+
<style>
|
| 8 |
+
.help-section {
|
| 9 |
+
background-color: #f8f9fa;
|
| 10 |
+
padding: 20px;
|
| 11 |
+
border-radius: 5px;
|
| 12 |
+
margin-top: 20px;
|
| 13 |
+
border-left: 4px solid #3498db;
|
| 14 |
+
}
|
| 15 |
+
.help-section h2 {
|
| 16 |
+
margin-top: 0;
|
| 17 |
+
color: #2c3e50;
|
| 18 |
+
}
|
| 19 |
+
.code-block {
|
| 20 |
+
background-color: #f1f1f1;
|
| 21 |
+
padding: 15px;
|
| 22 |
+
border-radius: 5px;
|
| 23 |
+
font-family: monospace;
|
| 24 |
+
white-space: pre-wrap;
|
| 25 |
+
margin: 15px 0;
|
| 26 |
+
}
|
| 27 |
+
.step {
|
| 28 |
+
margin-bottom: 15px;
|
| 29 |
+
}
|
| 30 |
+
.step-number {
|
| 31 |
+
display: inline-block;
|
| 32 |
+
width: 25px;
|
| 33 |
+
height: 25px;
|
| 34 |
+
background-color: #3498db;
|
| 35 |
+
color: white;
|
| 36 |
+
border-radius: 50%;
|
| 37 |
+
text-align: center;
|
| 38 |
+
line-height: 25px;
|
| 39 |
+
margin-right: 10px;
|
| 40 |
+
}
|
| 41 |
+
</style>
|
| 42 |
</head>
|
| 43 |
<body>
|
| 44 |
<div class="container">
|
| 45 |
<div class="header">
|
| 46 |
+
<h1>No Documents Available</h1>
|
| 47 |
+
<div class="header-links">
|
| 48 |
+
<a href="{{ url_for('view_instructions') }}" class="instructions-btn">Instructions</a>
|
| 49 |
+
<a href="{{ url_for('results') }}" class="results-btn">View Results</a>
|
| 50 |
+
<a href="{{ url_for('index') }}" class="evaluate-btn">Back to Home</a>
|
| 51 |
+
</div>
|
| 52 |
</div>
|
| 53 |
+
|
| 54 |
+
<div class="alert">
|
| 55 |
+
<p><strong>No documents are available for evaluation.</strong> This could be because:</p>
|
| 56 |
+
<ul>
|
| 57 |
+
<li>The CSV file you uploaded is missing or has an incorrect format</li>
|
| 58 |
+
<li>All documents have already been evaluated</li>
|
| 59 |
+
<li>There is an error in the format of your CSV file</li>
|
| 60 |
+
</ul>
|
| 61 |
</div>
|
| 62 |
+
|
| 63 |
+
<div class="help-section">
|
| 64 |
+
<h2>How to Add Documents for Evaluation</h2>
|
| 65 |
+
|
| 66 |
+
<div class="step">
|
| 67 |
+
<span class="step-number">1</span>
|
| 68 |
+
<strong>Download the template file:</strong>
|
| 69 |
+
<a href="{{ url_for('download_template') }}">sample_documents_template.csv</a>
|
| 70 |
+
</div>
|
| 71 |
+
|
| 72 |
+
<div class="step">
|
| 73 |
+
<span class="step-number">2</span>
|
| 74 |
+
<strong>Open the template in a spreadsheet program</strong> (Excel, Google Sheets, etc.)
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
<div class="step">
|
| 78 |
+
<span class="step-number">3</span>
|
| 79 |
+
<strong>Add your documents using the following format:</strong>
|
| 80 |
+
<div class="code-block">filename,description,note
|
| 81 |
+
"doc1.txt","Patient with heart failure","The patient is a 67-year-old male with a history of chronic heart failure who presented with increasing dyspnea on exertion."
|
| 82 |
+
"doc2.txt","Emergency department visit","32-year-old female with history of asthma presented to the ED with acute onset of wheezing and shortness of breath."</div>
|
| 83 |
+
<p><em>Note: Make sure to properly quote text fields that contain commas</em></p>
|
| 84 |
+
</div>
|
| 85 |
+
|
| 86 |
+
<div class="step">
|
| 87 |
+
<span class="step-number">4</span>
|
| 88 |
+
<strong>Save the file as CSV format</strong>
|
| 89 |
+
</div>
|
| 90 |
+
|
| 91 |
+
<div class="step">
|
| 92 |
+
<span class="step-number">5</span>
|
| 93 |
+
<strong>Return to the home page and upload your file</strong>
|
| 94 |
+
<p><a href="{{ url_for('index') }}" class="submit-btn">Go to Home Page</a></p>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
|
| 98 |
+
<div class="resource-links">
|
| 99 |
+
<p>
|
| 100 |
+
<strong>Resources:</strong>
|
| 101 |
+
<a href="{{ url_for('download_instructions') }}">Download Instructions</a> |
|
| 102 |
+
<a href="{{ url_for('download_template') }}">Download Template CSV</a>
|
| 103 |
+
</p>
|
| 104 |
+
</div>
|
| 105 |
+
|
| 106 |
+
{% with messages = get_flashed_messages() %}
|
| 107 |
+
{% if messages %}
|
| 108 |
+
{% for message in messages %}
|
| 109 |
+
<div class="alert">{{ message }}</div>
|
| 110 |
+
{% endfor %}
|
| 111 |
+
{% endif %}
|
| 112 |
+
{% endwith %}
|
| 113 |
</div>
|
| 114 |
</body>
|
| 115 |
</html>
|
templates/results.html
CHANGED
|
@@ -5,22 +5,64 @@
|
|
| 5 |
<title>Evaluation Results</title>
|
| 6 |
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
| 7 |
<style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
table {
|
| 9 |
width: 100%;
|
| 10 |
border-collapse: collapse;
|
| 11 |
margin-top: 20px;
|
|
|
|
| 12 |
}
|
|
|
|
| 13 |
th, td {
|
| 14 |
padding: 10px;
|
| 15 |
border: 1px solid #ddd;
|
| 16 |
text-align: left;
|
|
|
|
| 17 |
}
|
|
|
|
| 18 |
th {
|
| 19 |
background-color: #f2f2f2;
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
|
|
|
| 21 |
tr:nth-child(even) {
|
| 22 |
background-color: #f9f9f9;
|
| 23 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
</style>
|
| 25 |
</head>
|
| 26 |
<body>
|
|
@@ -29,38 +71,64 @@
|
|
| 29 |
<h1>Evaluation Results</h1>
|
| 30 |
<div class="header-links">
|
| 31 |
<a href="{{ url_for('evaluate') }}" class="evaluate-btn">Continue Evaluating</a>
|
| 32 |
-
<a href="{{ url_for('
|
|
|
|
| 33 |
</div>
|
| 34 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
<div class="results-container">
|
| 37 |
<h2>Results Table</h2>
|
| 38 |
-
<table>
|
| 39 |
-
<
|
| 40 |
-
<
|
| 41 |
-
<
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
<
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
<
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
{% endfor %}
|
| 60 |
-
</
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
</div>
|
| 65 |
|
| 66 |
{% with messages = get_flashed_messages() %}
|
|
|
|
| 5 |
<title>Evaluation Results</title>
|
| 6 |
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
| 7 |
<style>
|
| 8 |
+
.results-container {
|
| 9 |
+
overflow-x: auto; /* Add horizontal scrolling for the table */
|
| 10 |
+
margin-top: 20px;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
table {
|
| 14 |
width: 100%;
|
| 15 |
border-collapse: collapse;
|
| 16 |
margin-top: 20px;
|
| 17 |
+
min-width: 800px; /* Set a minimum width so smaller screens will scroll */
|
| 18 |
}
|
| 19 |
+
|
| 20 |
th, td {
|
| 21 |
padding: 10px;
|
| 22 |
border: 1px solid #ddd;
|
| 23 |
text-align: left;
|
| 24 |
+
font-size: 0.9em; /* Slightly smaller font size */
|
| 25 |
}
|
| 26 |
+
|
| 27 |
th {
|
| 28 |
background-color: #f2f2f2;
|
| 29 |
+
position: sticky;
|
| 30 |
+
top: 0;
|
| 31 |
+
z-index: 10;
|
| 32 |
}
|
| 33 |
+
|
| 34 |
tr:nth-child(even) {
|
| 35 |
background-color: #f9f9f9;
|
| 36 |
}
|
| 37 |
+
|
| 38 |
+
.export-btn {
|
| 39 |
+
margin-top: 20px;
|
| 40 |
+
margin-bottom: 20px;
|
| 41 |
+
text-align: right;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
.export-btn a {
|
| 45 |
+
background-color: #28a745;
|
| 46 |
+
padding: 10px 15px;
|
| 47 |
+
color: white;
|
| 48 |
+
border-radius: 4px;
|
| 49 |
+
text-decoration: none;
|
| 50 |
+
display: inline-flex;
|
| 51 |
+
align-items: center;
|
| 52 |
+
gap: 5px;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.export-btn a:hover {
|
| 56 |
+
background-color: #218838;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
/* Icon for export button */
|
| 60 |
+
.export-icon {
|
| 61 |
+
width: 16px;
|
| 62 |
+
height: 16px;
|
| 63 |
+
display: inline-block;
|
| 64 |
+
margin-right: 5px;
|
| 65 |
+
}
|
| 66 |
</style>
|
| 67 |
</head>
|
| 68 |
<body>
|
|
|
|
| 71 |
<h1>Evaluation Results</h1>
|
| 72 |
<div class="header-links">
|
| 73 |
<a href="{{ url_for('evaluate') }}" class="evaluate-btn">Continue Evaluating</a>
|
| 74 |
+
<a href="{{ url_for('view_instructions') }}" class="instructions-btn">Instructions</a>
|
| 75 |
+
<a href="{{ url_for('index') }}" class="evaluate-btn">Back to Home</a>
|
| 76 |
</div>
|
| 77 |
</div>
|
| 78 |
+
|
| 79 |
+
<div class="export-btn">
|
| 80 |
+
<a href="{{ url_for('export_csv') }}">
|
| 81 |
+
<svg class="export-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
| 82 |
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
| 83 |
+
<polyline points="7 10 12 15 17 10"></polyline>
|
| 84 |
+
<line x1="12" y1="15" x2="12" y2="3"></line>
|
| 85 |
+
</svg>
|
| 86 |
+
Export to CSV
|
| 87 |
+
</a>
|
| 88 |
+
</div>
|
| 89 |
|
| 90 |
<div class="results-container">
|
| 91 |
<h2>Results Table</h2>
|
| 92 |
+
<div class="table-wrapper">
|
| 93 |
+
<table>
|
| 94 |
+
<thead>
|
| 95 |
+
<tr>
|
| 96 |
+
<th>Timestamp</th>
|
| 97 |
+
<th>Document</th>
|
| 98 |
+
<th>Description</th>
|
| 99 |
+
<th>MRN</th>
|
| 100 |
+
<th>Evaluator</th>
|
| 101 |
+
{% for criterion in criteria %}
|
| 102 |
+
<th>{{ criterion }}</th>
|
| 103 |
+
{% endfor %}
|
| 104 |
+
<th>Note Origin Assessment</th>
|
| 105 |
+
</tr>
|
| 106 |
+
</thead>
|
| 107 |
+
<tbody>
|
| 108 |
+
{% for eval in evaluations %}
|
| 109 |
+
<tr>
|
| 110 |
+
<td>{{ eval.timestamp }}</td>
|
| 111 |
+
<td>{{ eval.document_title }}</td>
|
| 112 |
+
<td>{{ eval.description }}</td>
|
| 113 |
+
<td>{{ eval.mrn }}</td>
|
| 114 |
+
<td>{{ eval.investigator_name }}</td>
|
| 115 |
+
{% for criterion in criteria %}
|
| 116 |
+
<td>{{ eval[criterion] }}</td>
|
| 117 |
+
{% endfor %}
|
| 118 |
+
<td>{{ eval.note_origin }}</td>
|
| 119 |
+
</tr>
|
| 120 |
{% endfor %}
|
| 121 |
+
</tbody>
|
| 122 |
+
</table>
|
| 123 |
+
</div>
|
| 124 |
+
</div>
|
| 125 |
+
|
| 126 |
+
<div class="resource-links">
|
| 127 |
+
<p>
|
| 128 |
+
<strong>Resources:</strong>
|
| 129 |
+
<a href="{{ url_for('download_instructions') }}">Download Instructions</a> |
|
| 130 |
+
<a href="{{ url_for('download_template') }}">Download Template CSV</a>
|
| 131 |
+
</p>
|
| 132 |
</div>
|
| 133 |
|
| 134 |
{% with messages = get_flashed_messages() %}
|