SFM2001 commited on
Commit
4f591e5
·
1 Parent(s): cc73377

upload files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Dockerfile +12 -0
  2. Longformer_checkpoint/__init__.py +0 -0
  3. Longformer_checkpoint/config.json +56 -0
  4. __init__.py +0 -0
  5. app.py +6 -0
  6. configs.py +3 -0
  7. data/__init__.py +0 -0
  8. data/dataset.py +26 -0
  9. inference/__init__.py +0 -0
  10. inference/__pycache__/__init__.cpython-312.pyc +0 -0
  11. inference/__pycache__/infer_single.cpython-312.pyc +0 -0
  12. inference/infer_single.py +85 -0
  13. init.sh +1 -0
  14. instance/users.db +0 -0
  15. models/__init__.py +0 -0
  16. models/__pycache__/__init__.cpython-312.pyc +0 -0
  17. models/__pycache__/model.cpython-312.pyc +0 -0
  18. models/model.py +144 -0
  19. my_app/__init__.py +63 -0
  20. my_app/__pycache__/__init__.cpython-312.pyc +0 -0
  21. my_app/__pycache__/database.cpython-312.pyc +0 -0
  22. my_app/database.py +28 -0
  23. my_app/static/js/dashboard_charts.js +191 -0
  24. my_app/static/js/home_page.js +65 -0
  25. my_app/static/js/particles_init.js +55 -0
  26. my_app/static/styles.css +347 -0
  27. my_app/templates/403.html +14 -0
  28. my_app/templates/about_us.html +65 -0
  29. my_app/templates/base.html +75 -0
  30. my_app/templates/coherence_cohesion.html +106 -0
  31. my_app/templates/dashboard.html +111 -0
  32. my_app/templates/error.html +13 -0
  33. my_app/templates/grammatical_range_accuracy.html +104 -0
  34. my_app/templates/index.html +67 -0
  35. my_app/templates/infer.html +181 -0
  36. my_app/templates/lexical_resource.html +104 -0
  37. my_app/templates/login.html +49 -0
  38. my_app/templates/register.html +77 -0
  39. my_app/templates/rubric_explanation.html +28 -0
  40. my_app/templates/task_response.html +104 -0
  41. my_app/views/__init__.py +9 -0
  42. my_app/views/__pycache__/__init__.cpython-310.pyc +0 -0
  43. my_app/views/__pycache__/__init__.cpython-311.pyc +0 -0
  44. my_app/views/__pycache__/__init__.cpython-312.pyc +0 -0
  45. my_app/views/__pycache__/about.cpython-311.pyc +0 -0
  46. my_app/views/__pycache__/about.cpython-312.pyc +0 -0
  47. my_app/views/__pycache__/auth.cpython-310.pyc +0 -0
  48. my_app/views/__pycache__/auth.cpython-311.pyc +0 -0
  49. my_app/views/__pycache__/auth.cpython-312.pyc +0 -0
  50. my_app/views/__pycache__/dashboard.cpython-310.pyc +0 -0
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ EXPOSE 3478
11
+
12
+ CMD ["python", "app.py"]
Longformer_checkpoint/__init__.py ADDED
File without changes
Longformer_checkpoint/config.json ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "CustomLongformerForSequenceClassification"
4
+ ],
5
+ "attention_mode": "longformer",
6
+ "attention_probs_dropout_prob": 0.1,
7
+ "attention_window": [
8
+ 512,
9
+ 512,
10
+ 512,
11
+ 512,
12
+ 512,
13
+ 512,
14
+ 512,
15
+ 512,
16
+ 512,
17
+ 512,
18
+ 512,
19
+ 512
20
+ ],
21
+ "bos_token_id": 0,
22
+ "eos_token_id": 2,
23
+ "gradient_checkpointing": false,
24
+ "hidden_act": "gelu",
25
+ "hidden_dropout_prob": 0.1,
26
+ "hidden_size": 768,
27
+ "id2label": {
28
+ "0": "LABEL_0",
29
+ "1": "LABEL_1",
30
+ "2": "LABEL_2",
31
+ "3": "LABEL_3"
32
+ },
33
+ "ignore_attention_mask": false,
34
+ "initializer_range": 0.02,
35
+ "intermediate_size": 3072,
36
+ "label2id": {
37
+ "LABEL_0": 0,
38
+ "LABEL_1": 1,
39
+ "LABEL_2": 2,
40
+ "LABEL_3": 3
41
+ },
42
+ "layer_norm_eps": 1e-05,
43
+ "max_position_embeddings": 4098,
44
+ "model_type": "longformer",
45
+ "num_attention_heads": 12,
46
+ "num_hidden_layers": 12,
47
+ "onnx_export": false,
48
+ "output_hidden_states": true,
49
+ "pad_token_id": 1,
50
+ "problem_type": "regression",
51
+ "sep_token_id": 2,
52
+ "torch_dtype": "float32",
53
+ "transformers_version": "4.51.3",
54
+ "type_vocab_size": 1,
55
+ "vocab_size": 50265
56
+ }
__init__.py ADDED
File without changes
app.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from my_app import create_app
2
+
3
+ app = create_app()
4
+
5
+ if __name__ == '__main__':
6
+ app.run(debug=True,host='0.0.0.0')
configs.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ SECRET_KEY = 'secret_key_here'
2
+ SQLALCHEMY_DATABASE_URI = 'sqlite:///users.db'
3
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
data/__init__.py ADDED
File without changes
data/dataset.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ class EssayDataset(torch.utils.data.Dataset):
3
+ def __init__(self, dataframe, tokenizer, max_length):
4
+ self.data = dataframe
5
+ self.tokenizer = tokenizer
6
+ self.max_length = max_length
7
+
8
+ def __len__(self):
9
+ return len(self.data)
10
+
11
+ def __getitem__(self, idx):
12
+ text = self.data.iloc[idx]['train_input']
13
+ labels = self.data.iloc[idx]['labels']
14
+ encoding = self.tokenizer(
15
+ text,
16
+ max_length=self.max_length,
17
+ padding='max_length',
18
+ truncation=True,
19
+ return_tensors='pt'
20
+ )
21
+ return {
22
+ 'input_ids': encoding['input_ids'].flatten(),
23
+ 'attention_mask': encoding['attention_mask'].flatten(),
24
+ 'labels': torch.tensor(labels, dtype=torch.float)
25
+ }
26
+
inference/__init__.py ADDED
File without changes
inference/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (155 Bytes). View file
 
inference/__pycache__/infer_single.cpython-312.pyc ADDED
Binary file (4.98 kB). View file
 
inference/infer_single.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from utils.data_utils import *
2
+ from utils.prompts import *
3
+ import torch
4
+ from my_app import *
5
+
6
+ def replace_single_newlines(text):
7
+ return re.sub(r'(?<!\n)\n(?!\n)', '\\\\n\\\\n', text)
8
+
9
+ def generate_full_prompt(topic, essay, cefr_stat):
10
+ essay = replace_single_newlines(essay)
11
+ paragraph_cnt = len(essay.replace('\\n\\n', '\\n').split('\\n'))
12
+ word_cnt = len(essay.split())
13
+ stat = f"The essay has {word_cnt} words and {paragraph_cnt} paragraphs.\n"
14
+ full_prompt = feedback_prompt_1 + "\n ##{{PROMPT}}\n```\n" + topic + "\n```\n ##{{ESSAY}}\n```\n" + essay + "\n```\n##CEFR Analysis\n" + str(cefr_stat) + "\n\n##{{STATS}}\n" + stat + '\n' + feedback_prompt_2
15
+ return full_prompt
16
+
17
+
18
+ def generate_and_score_essay(topic, essay):
19
+ global MODELS_LOADED, LONGFORMER_TOKENIZER, LONGFORMER_MODEL, QWEN_TOKENIZER, QWEN_MODEL
20
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
21
+ LONGFORMER_MODEL = LONGFORMER_MODEL.to(device)
22
+ QWEN_MODEL = QWEN_MODEL.to(device)
23
+
24
+ cefr_results = get_cefr_stats(essay)
25
+ full_prompt = generate_full_prompt(topic=topic, essay=essay, cefr_stat=cefr_results)
26
+ essay = replace_single_newlines(essay)
27
+ paragraph_cnt = len(essay.replace('\\n\\n', '\\n').split('\\n'))
28
+ text = QWEN_TOKENIZER.apply_chat_template(
29
+ [{"role": "user", "content": full_prompt}],
30
+ tokenize=False,
31
+ add_generation_prompt=True,
32
+ enable_thinking=False
33
+ )
34
+ inputs = QWEN_TOKENIZER(
35
+ text,
36
+ return_tensors="pt",
37
+ padding=True,
38
+ truncation=True,
39
+ padding_side='left'
40
+ ).to(device)
41
+ with torch.inference_mode():
42
+ outputs = QWEN_MODEL.generate(
43
+ **inputs,
44
+ max_new_tokens=1500,
45
+ use_cache=True,
46
+ pad_token_id=QWEN_TOKENIZER.eos_token_id
47
+ )
48
+ generated_ids = outputs[0][inputs.input_ids.shape[1]:]
49
+ full_feedback = QWEN_TOKENIZER.decode(
50
+ generated_ids,
51
+ skip_special_tokens=True,
52
+ clean_up_tokenization_spaces=True # Fix spaces/newlines
53
+ )
54
+ output_match = re.search(r"{(.*?)}", full_feedback, re.DOTALL)
55
+ response = output_match.group(1).strip() if output_match else full_feedback
56
+ feedback_components = extract_feedback_keys_values(response)
57
+ feedback_components = dict(feedback_components)
58
+ feedback_components['word_count'] = len(essay.split())
59
+ feedback_components['paragraph_count'] = paragraph_cnt
60
+ feedback_components['cefr_stat'] = cefr_results
61
+ score_input = create_train_input({
62
+ 'topic': topic,
63
+ 'essay': essay,
64
+ 'Corrected_essay': feedback_components.get('Corrected_essay', ''),
65
+ 'TR_feedback': feedback_components.get('TR_feedback', ''),
66
+ 'CC_feedback': feedback_components.get('CC_feedback', ''),
67
+ 'LR_feedback': feedback_components.get('LR_feedback', ''),
68
+ 'GRA_feedback': feedback_components.get('GRA_feedback', ''),
69
+ 'word_count':feedback_components.get('word_count', ''),
70
+ 'paragraph_count': feedback_components.get('paragraph_count', ''),
71
+ 'cefr_stat': feedback_components.get('cefr_stat', '')
72
+ })
73
+ score_inputs = LONGFORMER_TOKENIZER(
74
+ score_input,
75
+ return_tensors="pt",
76
+ max_length=2048,
77
+ truncation=True,
78
+ padding=True
79
+ ).to(device)
80
+ LONGFORMER_MODEL.eval()
81
+ with torch.no_grad():
82
+ outputs = LONGFORMER_MODEL(**score_inputs) # Get full outputs dictionary
83
+ scores = outputs['logits'].cpu().numpy()
84
+ scores = [round(x) for x in scores[0]]
85
+ return scores, feedback_components
init.sh ADDED
@@ -0,0 +1 @@
 
 
1
+ python -m spacy download en_core_web_sm
instance/users.db ADDED
Binary file (20.5 kB). View file
 
models/__init__.py ADDED
File without changes
models/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (152 Bytes). View file
 
models/__pycache__/model.cpython-312.pyc ADDED
Binary file (6.69 kB). View file
 
models/model.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from transformers import LongformerPreTrainedModel, LongformerModel
4
+ import torch.nn.functional as F
5
+
6
+ class AttentionPooling(nn.Module):
7
+ def __init__(self, hidden_dim):
8
+ super().__init__()
9
+ self.hidden_dim = hidden_dim
10
+ self.query = nn.Linear(hidden_dim, hidden_dim)
11
+ self.energy = nn.Linear(hidden_dim, 1)
12
+
13
+ # Initialize weights
14
+ nn.init.xavier_uniform_(self.query.weight)
15
+ nn.init.xavier_uniform_(self.energy.weight)
16
+ self.query.bias.data.zero_()
17
+ self.energy.bias.data.zero_()
18
+
19
+ def forward(self, hidden_states, attention_mask=None):
20
+ # Compute attention scores
21
+ transformed = torch.tanh(self.query(hidden_states)) # (batch_size, seq_len, hidden_dim)
22
+ scores = self.energy(transformed).squeeze(-1) # (batch_size, seq_len)
23
+
24
+ # Apply attention mask if provided
25
+ if attention_mask is not None:
26
+ scores = scores.masked_fill(attention_mask == 0, float('-inf'))
27
+
28
+ # Compute attention weights
29
+ weights = F.softmax(scores, dim=-1) # (batch_size, seq_len)
30
+
31
+ # Apply attention pooling
32
+ pooled = torch.sum(hidden_states * weights.unsqueeze(-1), dim=1) # (batch_size, hidden_dim)
33
+ return pooled
34
+
35
+
36
+
37
+ class CustomLongformerForSequenceClassification(LongformerPreTrainedModel):
38
+ """Longformer model with attention pooling for sequence classification.
39
+
40
+ Uses attention pooling over the last four hidden layers instead of CLS token pooling.
41
+ """
42
+ def __init__(self, config):
43
+ super().__init__(config)
44
+ self.num_labels = config.num_labels
45
+ self.config = config
46
+
47
+ # Longformer backbone
48
+ self.longformer = LongformerModel(config)
49
+ self.dropout = nn.Dropout(config.hidden_dropout_prob)
50
+
51
+ # Attention pooling for each layer
52
+ self.attention_poolers = nn.ModuleList([
53
+ AttentionPooling(config.hidden_size) for _ in range(4)
54
+ ])
55
+
56
+ # Final classifier
57
+ self.classifier = nn.Linear(config.hidden_size * 4, config.num_labels)
58
+
59
+ # Initialize weights
60
+ self.post_init()
61
+
62
+ def forward(self, input_ids=None, attention_mask=None, labels=None, **kwargs):
63
+ outputs = self.longformer(
64
+ input_ids=input_ids,
65
+ attention_mask=attention_mask,
66
+ output_hidden_states=True,
67
+ **kwargs
68
+ )
69
+
70
+ # Get last four hidden layers
71
+ last_four_layers = outputs.hidden_states[-4:]
72
+
73
+ # Apply attention pooling to each layer
74
+ pooled = []
75
+ for layer, pooler in zip(last_four_layers, self.attention_poolers):
76
+ pooled.append(pooler(layer, attention_mask=attention_mask))
77
+
78
+ # Concatenate pooled representations
79
+ concatenated = torch.cat(pooled, dim=1)
80
+ concatenated = self.dropout(concatenated)
81
+ logits = self.classifier(concatenated)
82
+
83
+ # Compute loss if labels provided
84
+ loss = None
85
+ if labels is not None:
86
+ if hasattr(self, 'loss_fct'):
87
+ loss = self.loss_fct(logits, labels)
88
+ else:
89
+ loss = F.mse_loss(logits, labels.float())
90
+
91
+ return {'loss': loss, 'logits': logits}
92
+
93
+ class CustomLongformerForSequenceClassification(LongformerPreTrainedModel):
94
+ """Longformer model with attention pooling for sequence classification."""
95
+ def __init__(self, config):
96
+ super().__init__(config)
97
+ self.num_labels = config.num_labels
98
+ self.config = config
99
+
100
+ # Longformer backbone
101
+ self.longformer = LongformerModel(config)
102
+ self.dropout = nn.Dropout(config.hidden_dropout_prob)
103
+
104
+ # Attention pooling for each layer
105
+ self.attention_poolers = nn.ModuleList([
106
+ AttentionPooling(config.hidden_size) for _ in range(4)
107
+ ])
108
+
109
+ # Final classifier
110
+ self.classifier = nn.Linear(config.hidden_size * 4, config.num_labels)
111
+
112
+ # Initialize weights
113
+ self.post_init()
114
+
115
+ def forward(self, input_ids=None, attention_mask=None, labels=None, **kwargs):
116
+ outputs = self.longformer(
117
+ input_ids=input_ids,
118
+ attention_mask=attention_mask,
119
+ output_hidden_states=True,
120
+ **kwargs
121
+ )
122
+
123
+ # Get last four hidden layers
124
+ last_four_layers = outputs.hidden_states[-4:]
125
+
126
+ # Apply attention pooling to each layer
127
+ pooled = []
128
+ for layer, pooler in zip(last_four_layers, self.attention_poolers):
129
+ pooled.append(pooler(layer, attention_mask=attention_mask))
130
+
131
+ # Concatenate pooled representations
132
+ concatenated = torch.cat(pooled, dim=1)
133
+ concatenated = self.dropout(concatenated)
134
+ logits = self.classifier(concatenated)
135
+
136
+ # Compute loss if labels provided
137
+ loss = None
138
+ if labels is not None:
139
+ if hasattr(self, 'loss_fct'):
140
+ loss = self.loss_fct(logits, labels)
141
+ else:
142
+ loss = F.mse_loss(logits.view(-1), labels.float().view(-1))
143
+
144
+ return {'loss': loss, 'logits': logits}
my_app/__init__.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask
2
+ from flask_sqlalchemy import SQLAlchemy
3
+ from flask_login import LoginManager
4
+ from flask import render_template
5
+ from transformers import LongformerTokenizer, AutoTokenizer, AutoModelForCausalLM, LongformerConfig
6
+ from models.model import *
7
+ from utils.util_func import *
8
+ from safetensors.torch import load_file
9
+
10
+ db = SQLAlchemy()
11
+ login_manager = LoginManager()
12
+
13
+ MODELS_LOADED = False
14
+ LONGFORMER_TOKENIZER = None
15
+ LONGFORMER_MODEL = None
16
+ QWEN_TOKENIZER = None
17
+ QWEN_MODEL = None
18
+ MODEL_SESSION = None
19
+
20
+ def load_models():
21
+ global MODELS_LOADED, LONGFORMER_TOKENIZER, LONGFORMER_MODEL, QWEN_TOKENIZER, QWEN_MODEL
22
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
23
+ if not MODELS_LOADED:
24
+ LONGFORMER_TOKENIZER = LongformerTokenizer.from_pretrained('allenai/longformer-base-4096', device='auto')
25
+ config = LongformerConfig.from_json_file("checkpoints/Longformer_checkpoint/config.json")
26
+ LONGFORMER_MODEL = CustomLongformerForSequenceClassification(config).from_pretrained('SFM2001/LongFormerScorer')
27
+ LONGFORMER_MODEL = LONGFORMER_MODEL.to(device)
28
+ LONGFORMER_MODEL.eval()
29
+
30
+ model_name = 'Qwen/Qwen3-1.7B'
31
+ QWEN_TOKENIZER = AutoTokenizer.from_pretrained(model_name, device='auto')
32
+ QWEN_TOKENIZER.pad_token_id = QWEN_TOKENIZER.eos_token_id
33
+ QWEN_MODEL = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16).half()
34
+ MODELS_LOADED = True
35
+
36
+ def create_app():
37
+ set_seed(42)
38
+ load_models()
39
+ app = Flask(__name__)
40
+ app.config.from_pyfile('../configs.py')
41
+ db.init_app(app)
42
+ login_manager.init_app(app)
43
+ login_manager.login_view = 'auth.login'
44
+ @login_manager.user_loader
45
+ def load_user(user_id):
46
+ return User.query.get(int(user_id))
47
+
48
+ with app.app_context():
49
+ from .views import auth_bp, dashboard_bp, infer_bp, about_bp, error_bp
50
+ app.register_blueprint(auth_bp)
51
+ app.register_blueprint(dashboard_bp)
52
+ app.register_blueprint(infer_bp)
53
+ app.register_blueprint(about_bp)
54
+ app.register_blueprint(error_bp)
55
+ @app.errorhandler(Exception)
56
+ def handle_all_exceptions(e):
57
+ code = getattr(e, 'code', 500)
58
+ error_message = str(e) if hasattr(e, 'description') else "Something went wrong."
59
+ return render_template('error.html', code=code, error_message=error_message), code
60
+ from .database import User, History
61
+ db.create_all()
62
+
63
+ return app
my_app/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (4.12 kB). View file
 
my_app/__pycache__/database.cpython-312.pyc ADDED
Binary file (2.8 kB). View file
 
my_app/database.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask_login import UserMixin
2
+ from werkzeug.security import generate_password_hash, check_password_hash
3
+ from datetime import datetime
4
+ from . import db
5
+
6
+ class User(UserMixin, db.Model):
7
+ id = db.Column(db.Integer, primary_key=True)
8
+ email = db.Column(db.String(100), unique=True, nullable=False)
9
+ nickname = db.Column(db.String(100), unique=True, nullable=False)
10
+ password_hash = db.Column(db.String(200))
11
+
12
+ def set_password(self, password):
13
+ self.password_hash = generate_password_hash(password)
14
+
15
+ def check_password(self, password):
16
+ return check_password_hash(self.password_hash, password)
17
+
18
+ class History(db.Model):
19
+ id = db.Column(db.Integer, primary_key=True)
20
+ user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
21
+ topic = db.Column(db.String(255), nullable=False)
22
+ essay = db.Column(db.Text, nullable=False)
23
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
24
+ score_tr = db.Column(db.Integer, nullable=False)
25
+ score_cc = db.Column(db.Integer, nullable=False)
26
+ score_lr = db.Column(db.Integer, nullable=False)
27
+ score_gra = db.Column(db.Integer, nullable=False)
28
+ user = db.relationship('User', backref=db.backref('histories', lazy=True))
my_app/static/js/dashboard_charts.js ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function showModal(content) {
2
+ document.getElementById('content-text').innerText = content;
3
+ $('#contentModal').modal('show');
4
+ }
5
+
6
+ function selectAll(source) {
7
+ const checkboxes = document.querySelectorAll('input[type="checkbox"]');
8
+ for (let i = 0; i < checkboxes.length; i++) {
9
+ if (checkboxes[i] !== source)
10
+ checkboxes[i].checked = source.checked;
11
+ }
12
+ }
13
+
14
+ function initializeCharts(scoresTR, scoresCC, scoresLR, scoresGRA, creationTimes) {
15
+ const essayNumbers = [...Array(scoresTR.length).keys()].map(x => x + 1); // Array from 1 to the length of scores
16
+
17
+ const rubricChartData = {
18
+ labels: essayNumbers,
19
+ datasets: [
20
+ {
21
+ label: 'Task Response',
22
+ data: scoresTR,
23
+ borderColor: 'rgba(54, 162, 235, 1.0)',
24
+ backgroundColor: 'rgba(54, 162, 235, 0.6)',
25
+ fill: false,
26
+ pointRadius: 5
27
+ },
28
+ {
29
+ label: 'Coherence and Cohesion',
30
+ data: scoresCC,
31
+ borderColor: 'rgba(255, 99, 132, 1.0)',
32
+ backgroundColor: 'rgba(255, 99, 132, 0.6)',
33
+ fill: false,
34
+ pointRadius: 5
35
+ },
36
+ {
37
+ label: 'Lexical Resource',
38
+ data: scoresLR,
39
+ borderColor: 'rgba(75, 192, 192, 1.0)',
40
+ backgroundColor: 'rgba(75, 192, 192, 0.6)',
41
+ fill: false,
42
+ pointRadius: 5
43
+ },
44
+ {
45
+ label: 'Grammatical Range and Accuracy',
46
+ data: scoresGRA,
47
+ borderColor: 'rgba(153, 102, 255, 1.0)',
48
+ backgroundColor: 'rgba(153, 102, 255, 0.6)',
49
+ fill: false,
50
+ pointRadius: 5
51
+ }
52
+ ]
53
+ };
54
+
55
+ const rubricChartOptions = {
56
+ responsive: true,
57
+ plugins: {
58
+ tooltip: {
59
+ callbacks: {
60
+ label: function (tooltipItem) {
61
+ const index = tooltipItem.dataIndex;
62
+ switch (tooltipItem.dataset.label) {
63
+ case 'Task Response':
64
+ return `Task Response Score: ${tooltipItem.raw}, Time: ${creationTimes[index]}`;
65
+ case 'Coherence and Cohesion':
66
+ return `Coherence and Cohesion Score: ${tooltipItem.raw}, Time: ${creationTimes[index]}`;
67
+ case 'Lexical Resource':
68
+ return `Lexical Resource Score: ${tooltipItem.raw}, Time: ${creationTimes[index]}`;
69
+ case 'Grammatical Range and Accuracy':
70
+ return `Grammatical Range and Accuracy Score: ${tooltipItem.raw}, Time: ${creationTimes[index]}`;
71
+ default:
72
+ return `Score: ${tooltipItem.raw}, Time: ${creationTimes[index]}`;
73
+ }
74
+ }
75
+ }
76
+ },
77
+ legend: {
78
+ display: true,
79
+ labels: {
80
+ color: 'white',
81
+ padding: 20
82
+ },
83
+ position: 'top',
84
+ align: 'center'
85
+ }
86
+ },
87
+ scales: {
88
+ x: {
89
+ title: {
90
+ display: true,
91
+ text: 'Essay Number',
92
+ color: 'white'
93
+ },
94
+ ticks: {
95
+ color: 'white'
96
+ },
97
+ grid: {
98
+ display: true
99
+ }
100
+ },
101
+ y: {
102
+ title: {
103
+ display: true,
104
+ text: 'Score',
105
+ color: 'white'
106
+ },
107
+ ticks: {
108
+ color: 'white'
109
+ },
110
+ min: 0,
111
+ max: 10,
112
+ grid: {
113
+ display: true,
114
+ color: 'rgba(255, 255, 255, 0.2)'
115
+ }
116
+ }
117
+ }
118
+ };
119
+
120
+ new Chart(document.getElementById('RubricChart'), {
121
+ type: 'line',
122
+ data: rubricChartData,
123
+ options: rubricChartOptions
124
+ });
125
+
126
+ const essaysPerDay = {};
127
+ creationTimes.forEach(time => {
128
+ const date = time.split(' ')[0];
129
+ essaysPerDay[date] = (essaysPerDay[date] || 0) + 1;
130
+ });
131
+ const sortedEssaysArray = Object.entries(essaysPerDay).sort((a, b) => new Date(a[0]) - new Date(b[0]));
132
+ const sortedEssaysPerDay = Object.fromEntries(sortedEssaysArray);
133
+
134
+ const dates = Object.keys(sortedEssaysPerDay);
135
+ const essayCounts = Object.values(sortedEssaysPerDay);
136
+
137
+ const essayCountChartData = {
138
+ labels: dates,
139
+ datasets: [{
140
+ label: 'Essays Per Day',
141
+ data: essayCounts,
142
+ borderColor: 'rgba(255, 165, 0, 1.0)',
143
+ backgroundColor: 'rgba(145, 128, 128, 0.5)',
144
+ fill: true,
145
+ pointRadius: 5
146
+ }]
147
+ };
148
+
149
+ const essayCountChartOptions = {
150
+ responsive: true,
151
+ plugins: {
152
+ legend: {
153
+ display: false
154
+ }
155
+ },
156
+ scales: {
157
+ x: {
158
+ title: {
159
+ display: true,
160
+ text: 'Date',
161
+ color: 'white'
162
+ },
163
+ ticks: {
164
+ color: 'white'
165
+ },
166
+ grid: {
167
+ display: true
168
+ }
169
+ },
170
+ y: {
171
+ title: {
172
+ display: true,
173
+ text: 'Number of Essays',
174
+ color: 'white'
175
+ },
176
+ ticks: {
177
+ color: 'white'
178
+ },
179
+ grid: {
180
+ display: true,
181
+ color: 'rgba(255, 255, 255, 0.2)' // Light grid lines
182
+ }
183
+ }
184
+ }
185
+ };
186
+ new Chart(document.getElementById('EssayCountChart'), {
187
+ type: 'bar',
188
+ data: essayCountChartData,
189
+ options: essayCountChartOptions
190
+ });
191
+ }
my_app/static/js/home_page.js ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $(document).ready(function() {
2
+ $('#toggle-sidebar').on('click', function() {
3
+ const sidebar = $('#sidebar');
4
+ sidebar.toggleClass('active');
5
+
6
+ if (sidebar.hasClass('active')) {
7
+ $(this).text("✖");
8
+ } else {
9
+ $(this).text("☰");
10
+ }
11
+ });
12
+
13
+ $('#close-sidebar').on('click', function() {
14
+ const sidebar = $('#sidebar');
15
+ sidebar.removeClass('active');
16
+ $('#toggle-sidebar').text("☰");
17
+ });
18
+
19
+ var isEmpty = $('#isempty').val() === 'True';
20
+ if (isEmpty) {
21
+ $('#alertModal').modal('show');
22
+ }
23
+
24
+ $('#essay').on('keydown', function(e) {
25
+ if (e.key === 'Enter') {
26
+ e.preventDefault();
27
+ var start = this.selectionStart;
28
+ var end = this.selectionEnd;
29
+ var text = this.value;
30
+ this.value = text.substring(0, start) + "\n" + text.substring(end);
31
+ this.selectionStart = this.selectionEnd = start + 1;
32
+ }
33
+ });
34
+
35
+ let countdownSeconds;
36
+ const countdownDisplay = document.getElementById("countdown-timer");
37
+ let countdownInterval;
38
+
39
+ function updateCountdown() {
40
+ const minutes = Math.floor(countdownSeconds / 60);
41
+ const seconds = countdownSeconds % 60;
42
+ countdownDisplay.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
43
+ countdownSeconds--;
44
+ if (countdownSeconds >= 0) {
45
+ countdownInterval = setTimeout(updateCountdown, 1000);
46
+ } else {
47
+ countdownDisplay.textContent = "Time's up!";
48
+ }
49
+ }
50
+
51
+ $('#start-countdown').on('click', function() {
52
+ const time = countdownDisplay.textContent.trim();
53
+ const timeParts = time.split(':');
54
+ const minutesInput = parseInt(timeParts[0], 10) || 0;
55
+ const secondsInput = parseInt(timeParts[1], 10) || 0;
56
+ countdownSeconds = minutesInput * 60 + secondsInput;
57
+
58
+ clearTimeout(countdownInterval);
59
+ updateCountdown();
60
+ });
61
+
62
+ $('#stop-countdown').on('click', function() {
63
+ clearTimeout(countdownInterval);
64
+ });
65
+ });
my_app/static/js/particles_init.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener("DOMContentLoaded", function () {
2
+ particlesJS("particles-js", {
3
+ "particles": {
4
+ "number": {
5
+ "value": 380,
6
+ "density": {
7
+ "enable": true,
8
+ "value_area": 800
9
+ }
10
+ },
11
+ "color": {
12
+ "value": "#b3b3cc"
13
+ },
14
+ "shape": {
15
+ "type": "circle",
16
+ "stroke": {
17
+ "width": 0,
18
+ "color": "#000000"
19
+ }
20
+ },
21
+ "opacity": {
22
+ "value": 0.5,
23
+ "random": false
24
+ },
25
+ "size": {
26
+ "value": 3,
27
+ "random": true
28
+ },
29
+ "line_linked": {
30
+ "enable": true,
31
+ "distance": 150,
32
+ "color": "#b3b3cc",
33
+ "opacity": 0.4,
34
+ "width": 1
35
+ },
36
+ "move": {
37
+ "enable": true,
38
+ "speed": 1
39
+ }
40
+ },
41
+ "interactivity": {
42
+ "events": {
43
+ "onhover": {
44
+ "enable": true,
45
+ "mode": "grab"
46
+ },
47
+ "onclick": {
48
+ "enable": true,
49
+ "mode": "push"
50
+ }
51
+ }
52
+ },
53
+ "retina_detect": true
54
+ });
55
+ });
my_app/static/styles.css ADDED
@@ -0,0 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --background-color: rgb(46, 44, 46);
3
+ --navbar-color: rgb(52, 45, 53, 0.0);
4
+ --footer-background-color: var(--navbar-color);
5
+ --footer-text-color: rgb(145, 128, 128);
6
+ --title-color: #d6cdcd;
7
+ --button-bg-color: #505050;
8
+ --button-hover-bg-color: #404040;
9
+ --button-border-color: #404040;
10
+ --button-hover-border-color: #303030;
11
+ --input-bg-color: rgba(255, 255, 255, 0.5);
12
+ --input-text-color: #d6cdcd;
13
+ --alert-color: var(--input-text-color);
14
+ --toggle-color: #45586d;
15
+ --link-color: var(--title-color);
16
+ --link-hover-color: var(--title-color);
17
+ --modal-background-color: var(--background-color);
18
+ --modal-text-color: var(--title-color);
19
+ }
20
+
21
+ #particles-js {
22
+ position: fixed;
23
+ top: 0;
24
+ left: 0;
25
+ width: 100%;
26
+ height: 100%;
27
+ background-color: var(--background-color);
28
+ background-image: url("");
29
+ background-repeat: no-repeat;
30
+ background-size: cover;
31
+ background-position: 50% 50%;
32
+ z-index: -1;
33
+ }
34
+
35
+ body {
36
+ margin: 0;
37
+ padding: 0;
38
+ height: 100vh;
39
+ display: flex;
40
+ flex-direction: column;
41
+ min-height: 100vh;
42
+ color: var(--title-color);
43
+ font-family: 'Georgia', serif;
44
+ -webkit-background-size: cover;
45
+ -moz-background-size: cover;
46
+ -o-background-size: cover;
47
+ background-size: cover;
48
+ }
49
+
50
+ input[type="text"], input[type="email"], input[type="password"] {
51
+ background-color: rgba(10, 9, 9, 0.5);
52
+ border: 1px solid #100f0f;
53
+ color: white;
54
+ border-radius: 4px;
55
+ padding: 10px;
56
+ }
57
+
58
+ textarea {
59
+ background-color:transparent;
60
+ background: transparent;
61
+ resize: vertical;
62
+ overflow-y: auto;
63
+ }
64
+
65
+ .modal-content {
66
+ background-color: var(--modal-background-color);
67
+ color: var(--modal-background-color);
68
+ z-index: 1050;
69
+ }
70
+
71
+ .modal-header .close {
72
+ color: var(--modal-text-color);
73
+ }
74
+
75
+ body, h1, h2, h3, p, a {
76
+ font-family: 'Georgia', serif;
77
+ text-align: center;
78
+ }
79
+
80
+ table {
81
+ margin-top: 20px;
82
+ color: var(--title-color);
83
+ }
84
+
85
+ thead {
86
+ color: var(--title-color);
87
+ }
88
+
89
+ tbody {
90
+ color: var(--title-color);
91
+ }
92
+
93
+ .footer {
94
+ background-color: var(--footer-background-color);
95
+ color: var(--footer-text-color);
96
+ padding: 10px;
97
+ text-align: center;
98
+ margin-top: 20px;
99
+ position: relative;
100
+ bottom: 0;
101
+ width: 100%;
102
+ }
103
+
104
+ .navbar {
105
+ margin-bottom: 20px;
106
+ background-color: var(--navbar-color);
107
+ }
108
+
109
+ a.nav-link {
110
+ color: var(--link-color);
111
+ text-decoration: none;
112
+ }
113
+
114
+ a.nav-link:hover {
115
+ color: var(--link-hover-color);
116
+ }
117
+
118
+ .center-text {
119
+ text-align: center;
120
+ }
121
+
122
+ .btn-center {
123
+ display: flex;
124
+ justify-content: center;
125
+ }
126
+
127
+ .footer a {
128
+ color: var(--link-color);
129
+ text-decoration: none;
130
+ }
131
+
132
+ .footer a:hover {
133
+ color: var(--link-hover-color);
134
+ }
135
+
136
+ textarea {
137
+ background-color: #1e0606 ;
138
+ border: 1px solid #ccc;
139
+ resize: vertical;
140
+ overflow-y: auto;
141
+ }
142
+
143
+ .btn-primary {
144
+ background-color: var(--button-bg-color);
145
+ border-color: var(--button-border-color);
146
+ margin-bottom: 20px;
147
+ }
148
+
149
+ .btn-primary:hover {
150
+ background-color: var(--button-hover-bg-color);
151
+ border-color: var(--button-hover-border-color);
152
+ }
153
+
154
+ h1, h2 {
155
+ color: var(--title-color);
156
+ padding-top: 20px;
157
+ }
158
+
159
+ .table td, .table th {
160
+ text-align: left;
161
+ vertical-align: middle;
162
+ }
163
+
164
+ .text-center {
165
+ margin-top: 20px;
166
+ }
167
+
168
+ .btn-danger {
169
+ width: 100px;
170
+ }
171
+
172
+ .btn-info {
173
+ width: 100px;
174
+ height: 50;
175
+ }
176
+
177
+ .form-text.text-muted {
178
+ color: var(--alert-color) !important;
179
+ }
180
+
181
+ .btn-toggle {
182
+ border: none;
183
+ background: none;
184
+ color: var(--toggle-color);
185
+ cursor: pointer;
186
+ }
187
+
188
+ .content-hidden {
189
+ display: none;
190
+ }
191
+
192
+ .container {
193
+ flex: 1;
194
+ }
195
+
196
+ .modal-content {
197
+ background-color: var(--modal-background-color);
198
+ color: var(--modal-text-color);
199
+ }
200
+
201
+ .modal-header .close {
202
+ color: var(--modal-text-color);
203
+ }
204
+
205
+ .flip-card {
206
+ background-color: transparent;
207
+ width: 100%;
208
+ height: 200px;
209
+ perspective: 1000px;
210
+ }
211
+
212
+ .flip-card-inner {
213
+ position: relative;
214
+ width: 100%;
215
+ height: 100%;
216
+ text-align: center;
217
+ transition: transform 0.8s;
218
+ transform-style: preserve-3d;
219
+ box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
220
+ }
221
+
222
+ .flip-card:hover .flip-card-inner {
223
+ transform: rotateY(180deg);
224
+ }
225
+
226
+ .flip-card-front,
227
+ .flip-card-back {
228
+ position: absolute;
229
+ width: 100%;
230
+ height: 100%;
231
+ -webkit-backface-visibility: hidden;
232
+ backface-visibility: hidden;
233
+ display: flex;
234
+ flex-direction: column;
235
+ justify-content: center;
236
+ align-items: center;
237
+ padding: 20px;
238
+ }
239
+
240
+ .flip-card-front {
241
+ background-color: #313840;
242
+ color: white;
243
+ }
244
+
245
+ .flip-card-back {
246
+ background-color: #8fa2b5;
247
+ color: black;
248
+ transform: rotateY(180deg);
249
+ }
250
+
251
+
252
+ .sidebar {
253
+ position: fixed;
254
+ top: 0;
255
+ left: -300px;
256
+ width: 300px;
257
+ height: 100%;
258
+ background-color: #343a40;
259
+ color: white;
260
+ transition: 0.3s;
261
+ z-index: 1000;
262
+ }
263
+
264
+ .sidebar.active {
265
+ left: 0;
266
+ }
267
+
268
+ .sidebar-content {
269
+ padding: 20px;
270
+ }
271
+
272
+ .flashcards {
273
+ display: flex;
274
+ flex-wrap: wrap;
275
+ justify-content: space-around;
276
+ }
277
+
278
+ .flashcard {
279
+ background-color: white;
280
+ width: 900px;
281
+ height: 900px;
282
+ margin: 20px;
283
+ perspective: 1000px;
284
+ }
285
+
286
+ .flashcard-inner {
287
+ position: relative;
288
+ width: 100%;
289
+ height: 100%;
290
+ text-align: center;
291
+ transition: transform 0.8s;
292
+ transform-style: preserve-3d;
293
+ box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
294
+ border-radius: 10px;
295
+ }
296
+
297
+ .flashcard:hover .flashcard-inner {
298
+ transform: rotateY(180deg);
299
+ }
300
+
301
+ .flashcard-front, .flashcard-back {
302
+ position: absolute;
303
+ width: 100%;
304
+ height: 100%;
305
+ -webkit-backface-visibility: hidden;
306
+ backface-visibility: hidden;
307
+ border-radius: 10px;
308
+ }
309
+
310
+ .flashcard-front {
311
+ background-color: #091d2b;
312
+ color: white;
313
+ display: flex;
314
+ align-items: center;
315
+ justify-content: center;
316
+ font-size: 24px;
317
+ }
318
+
319
+ .flashcard-back {
320
+ background-color: rgb(207, 214, 234);
321
+ color: black;
322
+ transform: rotateY(180deg);
323
+ display: flex;
324
+ align-items: center;
325
+ justify-content: center;
326
+ padding: 20px;
327
+ box-sizing: border-box;
328
+ text-align: justify;
329
+ }
330
+
331
+ .col-lg-7 {
332
+ background-color: rgba(68, 68, 68, 0.85);
333
+ padding: 20px;
334
+ border-radius: 10px;
335
+ color: white;
336
+ }
337
+
338
+ .col-lg-7 h5 {
339
+ font-size: 1.2rem;
340
+ margin-top: 15px;
341
+ color: #f8f9fa;
342
+ }
343
+
344
+ .col-lg-7 p {
345
+ font-size: 1rem;
346
+ line-height: 1.6;
347
+ }
my_app/templates/403.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Access Denied</title>
5
+ <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
6
+ </head>
7
+ <body>
8
+ <div class="container text-center">
9
+ <h1>Access Denied</h1>
10
+ <p>You do not have permission to access this page.</p>
11
+ <a href="{{ url_for('index') }}" class="btn btn-primary">Go Home</a>
12
+ </div>
13
+ </body>
14
+ </html>
my_app/templates/about_us.html ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block content %}
4
+ <div class="container mt-4" style="background-color: rgba(0, 0, 0, 0.3); color: white; padding: 20px; border-radius: 10px;">
5
+ <h2>About Us</h2>
6
+ <p style="text-indent: 40px; text-align:left;">
7
+ Welcome to the project page for SimpleAES, a comprehensive system designed to evaluate English
8
+ essays based on the IELTS rubrics. The primary goal of this project is to develop an automated tool that
9
+ can accurately score essays on four critical criteria: Task Response, Coherence and Cohesion,
10
+ Lexical Resource, and Grammatical Range and Accuracy. This project aims to provide a reliable
11
+ and objective means of essay assessment, helping both learning and evaluation processes in educational
12
+ settings.
13
+ </p>
14
+ <br>
15
+ <p style="text-indent: 40px; text-align:left;">
16
+ The IELTS (International English Language Testing System) is a widely recognized standard for
17
+ assessing English language proficiency. It is used by educational institutions, employers,
18
+ and immigration authorities worldwide. To meet the specified standards set by IELTS, this project
19
+ leverages advanced natural language processing (NLP) techniques and neural network to evaluate essays
20
+ with a high degree of precision and consistency.
21
+ </p>
22
+ <br>
23
+ <p style="text-indent: 40px; text-align:left;">
24
+ The project focuses on the following four criteria:
25
+ </p>
26
+ <ul>
27
+ <li style="text-indent: 40px; text-align:left;">
28
+ <strong>Task Response:</strong> This criterion evaluates how well the essay addresses the prompt.
29
+ It checks if the writer has answered all parts of the question, developed a clear position,
30
+ and supported their arguments with relevant examples. An effective task response ensures that the
31
+ essay stays on topic and thoroughly covers all aspects of the task.
32
+ </li>
33
+ <br>
34
+ <li style="text-indent: 40px; text-align:left;">
35
+ <strong>Coherence and Cohesion:</strong> This criterion assesses the logical flow and organization
36
+ of the essay. It examines how ideas are connected and how effectively the essay uses cohesive
37
+ devices (such as conjunctions and transitions) to link sentences and paragraphs. Good coherence
38
+ and cohesion make an essay easy to follow and understand.
39
+ </li>
40
+ <br>
41
+ <li style="text-indent: 40px; text-align:left;">
42
+ <strong>Lexical Resource:</strong> This criterion evaluates the range and accuracy of vocabulary
43
+ used in the essay. It looks at the writer’s ability to use a wide variety of words and phrases
44
+ accurately and appropriately. This includes the use of idiomatic expressions, collocations, and less
45
+ common lexical items that enhance the essay's overall quality.
46
+ </li>
47
+ <br>
48
+ <li style="text-indent: 40px; text-align:left;">
49
+ <strong>Grammatical Range and Accuracy:</strong> This criterion assesses the writer's command of
50
+ grammatical structures. It evaluates the variety and correctness of sentence structures, verb tenses,
51
+ punctuation, and syntax. An essay that demonstrates a high level of grammatical proficiency is easier to
52
+ read and more effective in communicating its message.
53
+ </li>
54
+ </ul>
55
+ <br>
56
+ <p style="text-indent: 40px; text-align:left;">
57
+ By implementing this automated essay scoring system, the project aims to provide students
58
+ and educators with a powerful tool for improving writing skills and ensuring fair and consistent
59
+ evaluation. The system can be used for practice, feedback, and formal assessment, helping learners to
60
+ identify areas for improvement and track their progress over time. Ultimately, the project seeks to
61
+ enhance the teaching and learning of English writing by leveraging technology to provide high-quality,
62
+ accessible, and efficient essay evaluations.
63
+ </p>
64
+ </div>
65
+ {% endblock %}
my_app/templates/base.html ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>{% block title %}{% endblock %}</title>
5
+ <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
6
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
7
+ <script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
8
+ <script src="{{ url_for('static', filename='js/particles_init.js') }}"></script>
9
+ <!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> -->
10
+ <script>
11
+ window.addEventListener('beforeunload', function() {
12
+ if (typeof sessionStorage !== 'undefined') {
13
+ sessionStorage.clear();
14
+ }
15
+ });
16
+ </script>
17
+ </head>
18
+ <body>
19
+ <div id="particles-js"></div>
20
+ <div class="overlay-content">
21
+ <nav class="navbar navbar-expand-lg">
22
+ <div class="container-fluid">
23
+ <a class="nav-link" href="{{ url_for('about.about_us') }}">About Us</a>
24
+ <a class="nav-link" href="{{ url_for('infer.rubric_explanation') }}">Rubric Explanation</a>
25
+ <a class="nav-link" href="{{ url_for('infer.index') }}">Home</a>
26
+ {% if current_user.is_authenticated %}
27
+ <a class="nav-link" href="{{ url_for('dashboard.dashboard') }}">Dashboard [ {{ current_user.nickname }} ]</a>
28
+ <a class="nav-link" href="{{ url_for('auth.logout') }}">Logout</a>
29
+ {% else %}
30
+ <span class="navbar-text">
31
+ No user is logged in.
32
+ </span>
33
+ <a class="nav-link" href="{{ url_for('auth.login') }}">Login</a>
34
+ <a class="nav-link" href="{{ url_for('auth.register') }}">Register</a>
35
+ {% endif %}
36
+ </div>
37
+ </nav>
38
+
39
+ <div class="container">
40
+ {% block content %}
41
+ {% endblock %}
42
+ </div>
43
+
44
+ <div class="footer">
45
+ <p>Visit our <a href="https://github.com/mengsifei/SimpleAES">GitHub</a></p>
46
+ </div>
47
+
48
+ <!-- Modals -->
49
+ <div class="modal fade" id="alertModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
50
+ <div class="modal-dialog" role="document">
51
+ <div class="modal-content">
52
+ <div class="modal-header">
53
+ <h5 class="modal-title" id="exampleModalLabel">Alert</h5>
54
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
55
+ <span aria-hidden="true">&times;</span>
56
+ </button>
57
+ </div>
58
+ <div class="modal-body">
59
+ Please provide both a topic and an essay text.
60
+ </div>
61
+ <div class="modal-footer">
62
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">OK</button>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+
68
+ <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
69
+ <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.0/dist/umd/popper.min.js"></script>
70
+ <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
71
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
72
+ {% block extra_scripts %}{% endblock %}
73
+ </div>
74
+ </body>
75
+ </html>
my_app/templates/coherence_cohesion.html ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block content %}
4
+ <div class="container mt-4">
5
+ <h2>Coherence & Cohesion</h2>
6
+ <div class="flashcards">
7
+ <div class="flashcard">
8
+ <div class="flashcard-inner">
9
+ <div class="flashcard-front">
10
+ Band 9
11
+ </div>
12
+ <div class="flashcard-back">
13
+ The message can be followed effortlessly. Cohesion is used in such a way that it very rarely attracts attention. Any lapses in coherence or cohesion are minimal. Paragraphing is skilfully managed.
14
+ </div>
15
+ </div>
16
+ </div>
17
+ <div class="flashcard">
18
+ <div class="flashcard-inner">
19
+ <div class="flashcard-front">
20
+ Band 8
21
+ </div>
22
+ <div class="flashcard-back">
23
+ The message can be followed with ease. Information and ideas are logically sequenced, and cohesion is well managed. Occasional lapses in coherence and cohesion may occur. Paragraphing is used sufficiently and appropriately.
24
+ </div>
25
+ </div>
26
+ </div>
27
+ <div class="flashcard">
28
+ <div class="flashcard-inner">
29
+ <div class="flashcard-front">
30
+ Band 7
31
+ </div>
32
+ <div class="flashcard-back">
33
+ Information and ideas are logically organised, and there is a clear progression throughout the response.
34
+ A range of cohesive devices including reference and substitution is used flexibly but with some inaccuracies or some over/under use.
35
+ Paragraphing is generally used effectively to support overall coherence, and the sequencing of ideas within a paragraph is generally logical.
36
+ </div>
37
+ </div>
38
+ </div>
39
+ <div class="flashcard">
40
+ <div class="flashcard-inner">
41
+ <div class="flashcard-front">
42
+ Band 6
43
+ </div>
44
+ <div class="flashcard-back">
45
+ Information and ideas are generally arranged coherently and there is a clear overall progression. Cohesive devices are used to some good effect but cohesion within and/or between sentences may be faulty or mechanical due to misuse, overuse or omission. The use of reference and substitution may lack flexibility or clarity and result in some repetition or error. Paragraphing may not always be logical and/or the central topic may not always be clear.
46
+ </div>
47
+ </div>
48
+ </div>
49
+ <div class="flashcard">
50
+ <div class="flashcard-inner">
51
+ <div class="flashcard-front">
52
+ Band 5
53
+ </div>
54
+ <div class="flashcard-back">
55
+ Organisation is evident but is not wholly logical and there may be a lack of overall progression. Nevertheless, there is a sense of underlying coherence to the response. The relationship of ideas can be followed but the sentences are not fluently linked to each other. There may be limited/overuse of cohesive devices with some inaccuracy. The writing may be repetitive due to inadequate and/or inaccurate use of reference and substitution. Paragraphing may be inadequate or missing.
56
+ </div>
57
+ </div>
58
+ </div>
59
+ <div class="flashcard">
60
+ <div class="flashcard-inner">
61
+ <div class="flashcard-front">
62
+ Band 4
63
+ </div>
64
+ <div class="flashcard-back">
65
+ Information and ideas are evident but not arranged coherently and there is no clear progression within the response. Relationships between ideas can be unclear and/or inadequately marked. There is some use of basic cohesive devices, which may be inaccurate or repetitive. There is inaccurate use or a lack of substitution or referencing. There may be no paragraphing and/or no clear main topic within paragraphs.
66
+ </div>
67
+ </div>
68
+ </div>
69
+ <div class="flashcard">
70
+ <div class="flashcard-inner">
71
+ <div class="flashcard-front">
72
+ Band 3
73
+ </div>
74
+ <div class="flashcard-back">
75
+ There is no apparent logical organisation. Ideas are discernible but difficult to relate to each other. There is minimal use of sequencers or cohesive devices. Those used do not necessarily indicate a logical relationship between ideas. There is difficulty in identifying referencing. Any attempts at paragraphing are unhelpful.
76
+ </div>
77
+ </div>
78
+ </div>
79
+ <div class="flashcard">
80
+ <div class="flashcard-inner">
81
+ <div class="flashcard-front">
82
+ Band 2
83
+ </div>
84
+ <div class="flashcard-back">
85
+ There is little evidence of control of organisational features.
86
+ </div>
87
+ </div>
88
+ </div>
89
+ <div class="flashcard">
90
+ <div class="flashcard-inner">
91
+ <div class="flashcard-front">
92
+ Band 1
93
+ </div>
94
+ <div class="flashcard-back">
95
+ Responses of 20 words or fewer are rated at Band 1. There is no apparent control of organisational features.
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ <a href="{{ url_for('infer.rubric_explanation') }}" class="btn btn-primary">Go Back to Rubric Explanation Page</a>
101
+ </div>
102
+ {% endblock %}
103
+
104
+ {% block extra_css %}
105
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/flashcards.css') }}">
106
+ {% endblock %}
my_app/templates/dashboard.html ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block content %}
4
+ <div class="container mt-4">
5
+ <h2 class="center-text">Dashboard</h2>
6
+ <form method="POST" action="{{ url_for('dashboard.delete_histories') }}">
7
+ <div class="row mt-4">
8
+ <div class="col-lg-6 mb-4">
9
+ <h4>Rubric Score Dynamics</h4>
10
+ <canvas id="RubricChart"></canvas>
11
+ </div>
12
+ <div class="col-lg-6 mb-4">
13
+ <h4>Number of Essays Checked per Day</h4>
14
+ <canvas id="EssayCountChart"></canvas>
15
+ </div>
16
+ </div>
17
+ <table class="table">
18
+ <thead>
19
+ <tr>
20
+ <th scope="col">#</th>
21
+ <th scope="col"><input id="select-all" type="checkbox" onclick="selectAll(this)"></th>
22
+ <th scope="col">Time</th>
23
+ <th scope="col">Topic</th>
24
+ <th scope="col">Essay</th>
25
+ <th scope="col">Task Response</th>
26
+ <th scope="col">Coherence and Cohesion</th>
27
+ <th scope="col">Lexical Resource</th>
28
+ <th scope="col">Grammatical Range and Accuracy</th>
29
+ </tr>
30
+ </thead>
31
+ <tbody>
32
+ {% for history in histories %}
33
+ <tr>
34
+ <td>{{ loop.index }}</td>
35
+ <td><input type="checkbox" name="history_ids" value="{{ history.id }}"></td>
36
+ <td>{{ history.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
37
+ <td onclick="showModal('{{ history.topic }}')">{{ history.topic | truncate(10) }}</td>
38
+ <td onclick="showModal('{{ history.essay }}')">{{ history.essay | truncate(100) }}</td>
39
+ <td>{{ history.score_tr | int }}</td>
40
+ <td>{{ history.score_cc | int }}</td>
41
+ <td>{{ history.score_lr | int }}</td>
42
+ <td>{{ history.score_gra | int }}</td>
43
+ </tr>
44
+ {% endfor %}
45
+ </tbody>
46
+ </table>
47
+ <div class="text-center">
48
+ <button type="submit" class="btn btn-danger">Delete Selected</button>
49
+ </div>
50
+ </form>
51
+ <nav aria-label="Page navigation example" style="margin-top: 50px;">
52
+ <ul class="pagination justify-content-center">
53
+ {% if pagination.has_prev %}
54
+ <li class="page-item">
55
+ <a class="page-link" href="{{ url_for('dashboard.dashboard', page=pagination.prev_num) }}">Previous</a>
56
+ </li>
57
+ {% else %}
58
+ <li class="page-item disabled"><span class="page-link">Previous</span></li>
59
+ {% endif %}
60
+
61
+ {% for page in pagination.iter_pages(left_edge=2, left_current=1, right_current=2, right_edge=2) %}
62
+ <li class="page-item {{ 'active' if page == pagination.page else '' }}">
63
+ {% if page %}
64
+ <a class="page-link" href="{{ url_for('dashboard.dashboard', page=page) }}">{{ page }}</a>
65
+ {% else %}
66
+ <span class="page-link">...</span>
67
+ {% endif %}
68
+ </li>
69
+ {% endfor %}
70
+
71
+ {% if pagination.has_next %}
72
+ <li class="page-item">
73
+ <a class="page-link" href="{{ url_for('dashboard.dashboard', page=pagination.next_num) }}">Next</a>
74
+ </li>
75
+ {% else %}
76
+ <li class="page-item disabled"><span class="page-link">Next</span></li>
77
+ {% endif %}
78
+ </ul>
79
+ </nav>
80
+ </div>
81
+ <div class="modal fade" id="contentModal" tabindex="-1" role="dialog">
82
+ <div class="modal-dialog modal-lg" role="document">
83
+ <div class="modal-content">
84
+ <div class="modal-header">
85
+ <h5 class="modal-title">Full Content</h5>
86
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
87
+ <span aria-hidden="true">&times;</span>
88
+ </button>
89
+ </div>
90
+ <div class="modal-body" style="max-height:400px; overflow-y:auto;">
91
+ <p id="content-text"></p>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
99
+ <script>
100
+ document.addEventListener('DOMContentLoaded', function() {
101
+ const scoresTR = JSON.parse('{{ scores_tr|tojson|safe }}');
102
+ const scoresCC = JSON.parse('{{ scores_cc|tojson|safe }}');
103
+ const scoresLR = JSON.parse('{{ scores_lr|tojson|safe }}');
104
+ const scoresGRA = JSON.parse('{{ scores_gra|tojson|safe }}');
105
+ const creationTimes = JSON.parse('{{ creation_times|tojson|safe }}');
106
+ initializeCharts(scoresTR, scoresCC, scoresLR, scoresGRA, creationTimes);
107
+ });
108
+ </script>
109
+ <script src="{{ url_for('static', filename='js/dashboard_charts.js') }}"></script>
110
+
111
+ {% endblock %}
my_app/templates/error.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block title %}Error{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-4">
7
+ <h2>Error</h2>
8
+ <div class="alert alert-danger" role="alert">
9
+ {{ error_message }}
10
+ </div>
11
+ <a href="{{ url_for('infer.index') }}" class="btn btn-primary">Go Back to Home</a>
12
+ </div>
13
+ {% endblock %}
my_app/templates/grammatical_range_accuracy.html ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block content %}
4
+ <div class="container mt-4">
5
+ <h2>Grammatical Range & Accuracy</h2>
6
+ <div class="flashcards">
7
+ <div class="flashcard">
8
+ <div class="flashcard-inner">
9
+ <div class="flashcard-front">
10
+ Band 9
11
+ </div>
12
+ <div class="flashcard-back">
13
+ A wide range of structures is used with full flexibility and control. Punctuation and grammar are used appropriately throughout. Minor errors are extremely rare and have minimal impact on communication.
14
+ </div>
15
+ </div>
16
+ </div>
17
+ <div class="flashcard">
18
+ <div class="flashcard-inner">
19
+ <div class="flashcard-front">
20
+ Band 8
21
+ </div>
22
+ <div class="flashcard-back">
23
+ A wide range of structures is flexibly and accurately used. The majority of sentences are error-free, and punctuation is well managed. Occasional, non-systematic errors and inappropriacies occur, but have minimal impact on communication.
24
+ </div>
25
+ </div>
26
+ </div>
27
+ <div class="flashcard">
28
+ <div class="flashcard-inner">
29
+ <div class="flashcard-front">
30
+ Band 7
31
+ </div>
32
+ <div class="flashcard-back">
33
+ A variety of complex structures is used with some flexibility and accuracy. Grammar and punctuation are generally well controlled, and error-free sentences are frequent. A few errors in grammar may persist, but these do not impede communication.
34
+ </div>
35
+ </div>
36
+ </div>
37
+ <div class="flashcard">
38
+ <div class="flashcard-inner">
39
+ <div class="flashcard-front">
40
+ Band 6
41
+ </div>
42
+ <div class="flashcard-back">
43
+ A mix of simple and complex sentence forms is used but flexibility is limited. Examples of more complex structures are not marked by the same level of accuracy as in simple structures. Errors in grammar and punctuation occur, but rarely impede communication.
44
+ </div>
45
+ </div>
46
+ </div>
47
+ <div class="flashcard">
48
+ <div class="flashcard-inner">
49
+ <div class="flashcard-front">
50
+ Band 5
51
+ </div>
52
+ <div class="flashcard-back">
53
+ The range of structures is limited and rather repetitive. Although complex sentences are attempted, they tend to be faulty, and the greatest accuracy is achieved on simple sentences. Grammatical errors may be frequent and cause some difficulty for the reader. Punctuation may be faulty.
54
+ </div>
55
+ </div>
56
+ </div>
57
+ <div class="flashcard">
58
+ <div class="flashcard-inner">
59
+ <div class="flashcard-front">
60
+ Band 4
61
+ </div>
62
+ <div class="flashcard-back">
63
+ A very limited range of structures is used. Subordinate clauses are rare and simple sentences predominate. Some structures are produced accurately but grammatical errors are frequent and may impede meaning. Punctuation is often faulty or inadequate.
64
+ </div>
65
+ </div>
66
+ </div>
67
+ <div class="flashcard">
68
+ <div class="flashcard-inner">
69
+ <div class="flashcard-front">
70
+ Band 3
71
+ </div>
72
+ <div class="flashcard-back">
73
+ Sentence forms are attempted, but errors in grammar and punctuation predominate (except in memorised phrases or those taken from the input material). This prevents most meaning from coming through.
74
+ </div>
75
+ </div>
76
+ </div>
77
+ <div class="flashcard">
78
+ <div class="flashcard-inner">
79
+ <div class="flashcard-front">
80
+ Band 2
81
+ </div>
82
+ <div class="flashcard-back">
83
+ There is little or no evidence of sentence forms (except in memorised phrases).
84
+ </div>
85
+ </div>
86
+ </div>
87
+ <div class="flashcard">
88
+ <div class="flashcard-inner">
89
+ <div class="flashcard-front">
90
+ Band 1
91
+ </div>
92
+ <div class="flashcard-back">
93
+ Responses of 20 words or fewer are rated at Band 1. No rateable language is evident.
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ <a href="{{ url_for('infer.rubric_explanation') }}" class="btn btn-primary">Go Back to Rubric Explanation Page</a>
99
+ </div>
100
+ {% endblock %}
101
+
102
+ {% block extra_css %}
103
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/flashcards.css') }}">
104
+ {% endblock %}
my_app/templates/index.html ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block title %}Home Page{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-4">
7
+ <!-- Sidebar with Toggle Button Inside -->
8
+ <div id="sidebar" class="sidebar">
9
+ <!-- Sidebar Toggle Button -->
10
+ <button class="btn btn-primary" id="toggle-sidebar" style="position: absolute; top: 350px; right: -45px; z-index: 999;">☰</button>
11
+
12
+ <!-- Sidebar Content -->
13
+ <div class="sidebar-content">
14
+ <h3 id="countdown-timer" contenteditable="true">5:00</h3> <!-- Starting value -->
15
+ <button class="btn btn-info mt-2 mr-2" id="start-countdown">Start</button>
16
+ <button class="btn btn-danger mt-2" id="stop-countdown">Stop</button>
17
+ <div class="mt-4">
18
+ <label for="draft-text">Draft Text:</label>
19
+ <textarea id="draft-text" class="form-control" rows="8" placeholder="Type your draft here..."></textarea>
20
+ </div>
21
+ </div>
22
+ </div>
23
+
24
+ <!-- Main Content: Essay Topic and Essay Content -->
25
+ <div style="margin-left: 30px;"> <!-- Adjust based on sidebar width -->
26
+ <h1 class="center-text">Enter Topic and Essay for Prediction</h1>
27
+ <form method="post" class="mt-4" id="essayForm">
28
+ <div class="mb-3">
29
+ <label for="topic" class="form-label">Essay Topic:</label>
30
+ <input type="text" id="topic" name="topic" class="form-control" autocomplete="off">
31
+ </div>
32
+ <div class="mb-3">
33
+ <label for="essay" class="form-label">Essay Content:</label>
34
+ <textarea style="background-color:rgba(10, 9, 9, 0.5); color:rgb(255, 255, 255); border: 1px solid #100f0f;" id="essay" name="essay" class="form-control" rows="8" autocomplete="off"></textarea>
35
+ </div>
36
+ <div class="btn-center">
37
+ <button type="submit" class="btn btn-primary">Submit</button>
38
+ </div>
39
+ <input type="hidden" id="isempty" value="{{ isempty }}">
40
+ </form>
41
+ </div>
42
+
43
+ <!-- Alert Modal -->
44
+ <div class="modal fade" id="alertModal" tabindex="-1" role="dialog" aria-labelledby="alertModalLabel" aria-hidden="true">
45
+ <div class="modal-dialog" role="document">
46
+ <div class="modal-content">
47
+ <div class="modal-header">
48
+ <h5 class="modal-title" id="alertModalLabel">Alert</h5>
49
+ <button type="button" class="close" data-dismiss="modal">
50
+ <span aria-hidden="true">&times;</span>
51
+ </button>
52
+ </div>
53
+ <div class="modal-body">
54
+ Please provide both a topic and an essay text.
55
+ </div>
56
+ <div class="modal-footer">
57
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">OK</button>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ {% endblock %}
64
+
65
+ {% block extra_scripts %}
66
+ <script src="{{ url_for('static', filename='js/home_page.js') }}"></script>
67
+ {% endblock %}
my_app/templates/infer.html ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block title %}Prediction Results{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-4">
7
+ <!-- Topic Card (same width as comparison) -->
8
+ <div class="row">
9
+ <div class="col-12 mb-4">
10
+ <div class="card border-primary">
11
+ <div class="card-header bg-secondary text-white">
12
+ <h4 class="mb-0">Topic</h4>
13
+ </div>
14
+ <div class="card-body">
15
+ <p class="card-text" style="color: black;">{{ topic }}</p>
16
+ </div>
17
+ </div>
18
+ </div>
19
+ </div>
20
+
21
+ <!-- Essay Comparison -->
22
+ <div class="row">
23
+ <div class="col-12 mb-4">
24
+ <div class="card border-primary">
25
+ <div class="card-header bg-secondary text-white">
26
+ <h4 class="mb-0">Essay Comparison</h4>
27
+ </div>
28
+ <div class="card-body p-0">
29
+ <div class="row g-0">
30
+ <!-- Original Essay -->
31
+ <div class="col-md-6 border-end">
32
+ <div class="p-3">
33
+ <h5 class="text-center text-primary">Original Essay</h5>
34
+ <div class="original-essay" id="originalEssay" style="color: black; height: 400px; overflow-y: auto;">
35
+ {{ essay|safe }}
36
+ </div>
37
+ </div>
38
+ </div>
39
+
40
+ <!-- Corrected Essay -->
41
+ <div class="col-md-6">
42
+ <div class="p-3">
43
+ <h5 class="text-center text-success">Corrected Essay</h5>
44
+ <div class="corrected-essay" id="correctedEssay" style="color: black; height: 400px; overflow-y: auto;">
45
+ {{ corrected_essay|safe }}
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+
55
+ <!-- Rubric Cards (now below comparison) -->
56
+ <div class="row">
57
+ {% set rubrics = [
58
+ ('Task Response', result[0], tr , 'task_response'),
59
+ ('Coherence and Cohesion', result[1], cc, 'coherence_cohesion'),
60
+ ('Lexical Resource', result[2], lr, 'lexical_resource'),
61
+ ('Grammatical Range and Accuracy', result[3], gra, 'grammatical_accuracy')
62
+ ] %}
63
+
64
+ {% for rubric, score, explanation, link in rubrics %}
65
+ <div class="col-md-3 mb-4"> <!-- Changed to col-md-3 for 4 cards in a row -->
66
+ <div class="card h-100 bg-light border-info"> <!-- Changed to light bg with info border -->
67
+ <div class="card-body">
68
+ <h5 class="card-title" style="color: black;">{{ rubric }}</h5>
69
+ <div class="score-display mb-3 text-center"> <!-- Centered score -->
70
+ <span class="score">{{ score|round|int }}</span>/9
71
+ </div>
72
+ <p class="card-text text-truncate-multiline text-dark"> <!-- Dark text for better readability -->
73
+ {{ explanation }}
74
+ </p>
75
+ <div class="d-flex justify-content-between mt-3">
76
+ <button type="button" class="btn btn-sm btn-outline-info" data-bs-toggle="modal" data-bs-target="#feedbackModal{{ loop.index }}">
77
+ View Feedback
78
+ </button>
79
+ <a href="{{ url_for('infer.rubric_explanation', rubric=link) }}" class="btn btn-sm btn-outline-info">
80
+ Details
81
+ </a>
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ <!-- Modal for each feedback item -->
87
+ <div class="modal fade" id="feedbackModal{{ loop.index }}" tabindex="-1" aria-labelledby="feedbackModalLabel{{ loop.index }}" aria-hidden="true">
88
+ <div class="modal-dialog modal-dialog-centered">
89
+ <div class="modal-content">
90
+ <div class="modal-header bg-info text-white">
91
+ <h5 class="modal-title" id="feedbackModalLabel{{ loop.index }}">{{ rubric }} Feedback</h5>
92
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
93
+ </div>
94
+ <div class="modal-body" style="max-height: 60vh; overflow-y: auto;">
95
+ {{ explanation }}
96
+ </div>
97
+ <div class="modal-footer">
98
+ <button type="button" class="btn btn-info" data-bs-dismiss="modal">Close</button>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ </div>
104
+ {% endfor %}
105
+ </div>
106
+
107
+ <div class="text-center mt-4">
108
+ <a href="{{ url_for('infer.index') }}" class="btn btn-primary">Submit Another Essay</a>
109
+ </div>
110
+ </div>
111
+ {% endblock %}
112
+
113
+ {% block extra_scripts %}
114
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jsdiff/5.0.1/diff.min.js"></script>
115
+ <script>
116
+ document.addEventListener('DOMContentLoaded', function() {
117
+ try {
118
+ const origEl = document.getElementById('originalEssay');
119
+ const corrEl = document.getElementById('correctedEssay');
120
+ if (!origEl || !corrEl) return;
121
+
122
+ const originalText = origEl.textContent;
123
+ const correctedText = corrEl.textContent;
124
+
125
+ // 2) do the diff
126
+ const diff = JsDiff.diffWordsWithSpace(originalText, correctedText);
127
+
128
+ // if nothing really changed, bail out
129
+ if (diff.length <= 1) return;
130
+
131
+ // 3) clear out the old
132
+ corrEl.innerHTML = '';
133
+
134
+ // 4) re‐render with spans
135
+ diff.forEach(part => {
136
+ const span = document.createElement('span');
137
+ span.textContent = part.value;
138
+ if (part.added) span.classList.add('diff-ins');
139
+ if (part.removed) span.classList.add('diff-del');
140
+ corrEl.appendChild(span);
141
+ });
142
+ }
143
+ catch (e) {
144
+ console.error('Diff rendering error:', e);
145
+ }
146
+ });
147
+ </script>
148
+ <style>
149
+ .diff-ins {border-bottom:2px solid #28a745; }
150
+ .diff-del {text-decoration: line-through; color: #dc3545; }
151
+
152
+ /* Custom styles */
153
+ .score-display {
154
+ font-size: 1.2rem;
155
+ font-weight: bold;
156
+ }
157
+
158
+ .score {
159
+ color: #0d6efd; /* Changed to Bootstrap primary blue */
160
+ font-size: 1.5rem;
161
+ }
162
+
163
+ .text-truncate-multiline {
164
+ display: -webkit-box;
165
+ -webkit-line-clamp: 3;
166
+ -webkit-box-orient: vertical;
167
+ overflow: hidden;
168
+ text-overflow: ellipsis;
169
+ min-height: 4.5em;
170
+ }
171
+
172
+ .card {
173
+ transition: transform 0.2s;
174
+ }
175
+
176
+ .card:hover {
177
+ transform: translateY(-5px);
178
+ box-shadow: 0 4px 15px rgba(13, 110, 253, 0.25); /* Primary blue shadow */
179
+ }
180
+ </style>
181
+ {% endblock %}
my_app/templates/lexical_resource.html ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block content %}
4
+ <div class="container mt-4">
5
+ <h2>Lexical Resource</h2>
6
+ <div class="flashcards">
7
+ <div class="flashcard">
8
+ <div class="flashcard-inner">
9
+ <div class="flashcard-front">
10
+ Band 9
11
+ </div>
12
+ <div class="flashcard-back">
13
+ Full flexibility and precise use are widely evident. A wide range of vocabulary is used accurately and appropriately with very natural and sophisticated control of lexical features. Minor errors in spelling and word formation are extremely rare and have minimal impact on communication.
14
+ </div>
15
+ </div>
16
+ </div>
17
+ <div class="flashcard">
18
+ <div class="flashcard-inner">
19
+ <div class="flashcard-front">
20
+ Band 8
21
+ </div>
22
+ <div class="flashcard-back">
23
+ A wide resource is fluently and flexibly used to convey precise meanings. There is skilful use of uncommon and/or idiomatic items when appropriate, despite occasional inaccuracies in word choice and collocation. Occasional errors in spelling and/or word formation may occur, but have minimal impact on communication.
24
+ </div>
25
+ </div>
26
+ </div>
27
+ <div class="flashcard">
28
+ <div class="flashcard-inner">
29
+ <div class="flashcard-front">
30
+ Band 7
31
+ </div>
32
+ <div class="flashcard-back">
33
+ The resource is sufficient to allow some flexibility and precision. There is some ability to use less common and/or idiomatic items. An awareness of style and collocation is evident, though inappropriacies occur. There are only a few errors in spelling and/or word formation and they do not detract from overall clarity.
34
+ </div>
35
+ </div>
36
+ </div>
37
+ <div class="flashcard">
38
+ <div class="flashcard-inner">
39
+ <div class="flashcard-front">
40
+ Band 6
41
+ </div>
42
+ <div class="flashcard-back">
43
+ The resource is generally adequate and appropriate for the task. The meaning is generally clear in spite of a rather restricted range or a lack of precision in word choice. If the writer is a risk-taker, there will be a wider range of vocabulary used but higher degrees of inaccuracy or inappropriacy. There are some errors in spelling and/or word formation, but these do not impede communication.
44
+ </div>
45
+ </div>
46
+ </div>
47
+ <div class="flashcard">
48
+ <div class="flashcard-inner">
49
+ <div class="flashcard-front">
50
+ Band 5
51
+ </div>
52
+ <div class="flashcard-back">
53
+ The resource is limited but minimally adequate for the task. Simple vocabulary may be used accurately but the range does not permit much variation in expression. There may be frequent lapses in the appropriacy of word choice and a lack of flexibility is apparent in frequent simplifications and/or repetitions. Errors in spelling and/or word formation may be noticeable and may cause some difficulty for the reader.
54
+ </div>
55
+ </div>
56
+ </div>
57
+ <div class="flashcard">
58
+ <div class="flashcard-inner">
59
+ <div class="flashcard-front">
60
+ Band 4
61
+ </div>
62
+ <div class="flashcard-back">
63
+ The resource is limited and inadequate for or unrelated to the task. Vocabulary is basic and may be used repetitively. There may be inappropriate use of lexical chunks (e.g. memorised phrases, formulaic language and/or language from the input material). Inappropriate word choice and/or errors in word formation and/or in spelling may impede meaning.
64
+ </div>
65
+ </div>
66
+ </div>
67
+ <div class="flashcard">
68
+ <div class="flashcard-inner">
69
+ <div class="flashcard-front">
70
+ Band 3
71
+ </div>
72
+ <div class="flashcard-back">
73
+ The resource is inadequate (which may be due to the response being significantly underlength). Possible over-dependence on input material or memorised language. Control of word choice and/or spelling is very limited, and errors predominate. These errors may severely impede meaning.
74
+ </div>
75
+ </div>
76
+ </div>
77
+ <div class="flashcard">
78
+ <div class="flashcard-inner">
79
+ <div class="flashcard-front">
80
+ Band 2
81
+ </div>
82
+ <div class="flashcard-back">
83
+ The resource is extremely limited with few recognisable strings, apart from memorised phrases. There is no apparent control of word formation and/or spelling.
84
+ </div>
85
+ </div>
86
+ </div>
87
+ <div class="flashcard">
88
+ <div class="flashcard-inner">
89
+ <div class="flashcard-front">
90
+ Band 1
91
+ </div>
92
+ <div class="flashcard-back">
93
+ Responses of 20 words or fewer are rated at Band 1. No resource is apparent, except for a few isolated words.
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ <a href="{{ url_for('infer.rubric_explanation') }}" class="btn btn-primary">Go Back to Rubric Explanation Page</a>
99
+ </div>
100
+ {% endblock %}
101
+
102
+ {% block extra_css %}
103
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/flashcards.css') }}">
104
+ {% endblock %}
my_app/templates/login.html ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block content %}
4
+ <div class="container">
5
+ <h2 class="center-text">Login</h2>
6
+ <form method="POST">
7
+ <div class="mb-3">
8
+ <label for="email" class="form-label">Email:</label>
9
+ <input type="email" class="form-control" id="email" autocomplete="off" name="email" required>
10
+ </div>
11
+ <div class="mb-3">
12
+ <label for="password" class="form-label">Password:</label>
13
+ <input type="password" class="form-control" id="password" autocomplete="off" name="password" required>
14
+ </div>
15
+ <input type="hidden" id="message" value="{{ message }}">
16
+ <div class="btn-center">
17
+ <button type="submit" class="btn btn-primary">Login</button>
18
+ </div>
19
+ </form>
20
+ <!-- Modal HTML -->
21
+ <div class="modal fade" id="loginAlertModal" tabindex="-1" role="dialog" aria-labelledby="loginModalLabel" aria-hidden="true">
22
+ <div class="modal-dialog" role="document">
23
+ <div class="modal-content">
24
+ <div class="modal-header">
25
+ <h5 class="modal-title" id="loginModalLabel">Login Alert</h5>
26
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
27
+ <span aria-hidden="true">&times;</span>
28
+ </button>
29
+ </div>
30
+ <div class="modal-body">
31
+ <!-- Message will be inserted here via JavaScript -->
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ {% endblock %}
38
+
39
+ {% block extra_scripts %}
40
+ <script>
41
+ $(document).ready(function() {
42
+ var message = $('#message').val();
43
+ if (message) {
44
+ $('#loginAlertModal .modal-body').text(message);
45
+ $('#loginAlertModal').modal('show');
46
+ }
47
+ });
48
+ </script>
49
+ {% endblock %}
my_app/templates/register.html ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block title %}Register{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container">
7
+ <h2 class="center-text">Register</h2>
8
+ <form method="POST" id="registrationForm">
9
+ <div class="mb-3">
10
+ <label for="email" class="form-label">Email:</label>
11
+ <input type="email" class="form-control" id="email" autocomplete="off" name="email" required>
12
+ <small id="emailHelp" class="form-text text-muted">Your email here. Example: example@domain.com</small>
13
+ </div>
14
+ <div class="mb-3">
15
+ <label for="nickname" class="form-label">Nickname:</label>
16
+ <input type="text" class="form-control" id="nickname" autocomplete="off" name="nickname" required>
17
+ <small id="nicknameHelp" class="form-text text-muted">Your nickname here. Example: Nickname1</small>
18
+ </div>
19
+ <div class="mb-3">
20
+ <label for="password" class="form-label">Password:</label>
21
+ <input type="password" class="form-control" id="password" autocomplete="off" name="password" required>
22
+ <small id="passwordHelp" class="form-text text-muted">Your password should be more than 8 symbols.</small>
23
+ </div>
24
+ <div class="mb-3">
25
+ <label for="repeatPassword" class="form-label">Repeat Password:</label>
26
+ <input type="password" class="form-control" id="repeatPassword" autocomplete="off" name="repeatPassword" required>
27
+ </div>
28
+ <input type="hidden" id="message" value="{{ message }}">
29
+ <div class="btn-center">
30
+ <button type="submit" class="btn btn-primary">Register</button>
31
+ </div>
32
+ </form>
33
+
34
+ <!-- Registration Alert Modal -->
35
+ <div class="modal fade" id="registrationAlertModal" tabindex="-1" role="dialog" aria-labelledby="registrationModalLabel" aria-hidden="true">
36
+ <div class="modal-dialog" role="document">
37
+ <div class="modal-content">
38
+ <div class="modal-header">
39
+ <h5 class="modal-title" id="registrationModalLabel">Registration Alert</h5>
40
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
41
+ <span aria-hidden="true">&times;</span>
42
+ </button>
43
+ </div>
44
+ <div class="modal-body">
45
+ <!-- Message will be inserted here via JavaScript -->
46
+ </div>
47
+ <div class="modal-footer">
48
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">OK</button>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ {% endblock %}
55
+ {% block extra_scripts %}
56
+ <script>
57
+ $(document).ready(function() {
58
+ // Show message if provided
59
+ var message = $('#message').val();
60
+ if (message) {
61
+ $('#registrationAlertModal .modal-body').text(message);
62
+ $('#registrationAlertModal').modal('show');
63
+ }
64
+
65
+ // Validate passwords before submission
66
+ $('#registrationForm').on('submit', function(e) {
67
+ var password = $('#password').val();
68
+ var repeatPassword = $('#repeatPassword').val();
69
+ if (password !== repeatPassword) {
70
+ e.preventDefault(); // Prevent form submission
71
+ $('#registrationAlertModal .modal-body').text('Passwords do not match!');
72
+ $('#registrationAlertModal').modal('show');
73
+ }
74
+ });
75
+ });
76
+ </script>
77
+ {% endblock %}
my_app/templates/rubric_explanation.html ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block title %}Rubric Explanation{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-4">
7
+ <h2 class="center-text">Explanation</h2>
8
+ <div class="row text-center mb-2">
9
+ <div class="col-md-7">
10
+ <a href="{{ url_for('infer.task_response') }}" class="btn btn-primary mt-3">Task Response Explanation</a>
11
+ </div>
12
+ <div class="col-md-5">
13
+ <a href="{{ url_for('infer.coherence_cohesion') }}" class="btn btn-primary mt-3">Coherence and Cohesion Explanation</a>
14
+ </div>
15
+ </div>
16
+ <div class="row text-center mb-2">
17
+ <div class="col-md-7">
18
+ <a href="{{ url_for('infer.lexical_resource') }}" class="btn btn-primary mt-3">Lexical Resource Explanation</a>
19
+ </div>
20
+ <div class="col-md-5">
21
+ <a href="{{ url_for('infer.grammatical_range_accuracy') }}" class="btn btn-primary mt-3">Grammatical Range and Accuracy Explanation</a>
22
+ </div>
23
+ </div>
24
+ <div class="btn-center">
25
+ <a href="{{ url_for('infer.index') }}" class="btn btn-primary mt-3">Back</a>
26
+ </div>
27
+ </div>
28
+ {% endblock %}
my_app/templates/task_response.html ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+
3
+ {% block content %}
4
+ <div class="container mt-4">
5
+ <h2>Task Response</h2>
6
+ <div class="flashcards">
7
+ <div class="flashcard">
8
+ <div class="flashcard-inner">
9
+ <div class="flashcard-front">
10
+ Band 9
11
+ </div>
12
+ <div class="flashcard-back">
13
+ The prompt is appropriately addressed and explored in depth. A clear and fully developed position is presented which directly answers the question/s. Ideas are relevant, fully extended and well supported. Any lapses in content or support are extremely rare.
14
+ </div>
15
+ </div>
16
+ </div>
17
+ <div class="flashcard">
18
+ <div class="flashcard-inner">
19
+ <div class="flashcard-front">
20
+ Band 8
21
+ </div>
22
+ <div class="flashcard-back">
23
+ The prompt is appropriately and sufficiently addressed. A clear and well-developed position is presented in response to the question/s. Ideas are relevant, well extended and supported. There may be occasional omissions or lapses in content.
24
+ </div>
25
+ </div>
26
+ </div>
27
+ <div class="flashcard">
28
+ <div class="flashcard-inner">
29
+ <div class="flashcard-front">
30
+ Band 7
31
+ </div>
32
+ <div class="flashcard-back">
33
+ The main parts of the prompt are appropriately addressed. A clear and developed position is presented. Main ideas are extended and supported but there may be a tendency to over-generalise or there may be a lack of focus and precision in supporting ideas/material.
34
+ </div>
35
+ </div>
36
+ </div>
37
+ <div class="flashcard">
38
+ <div class="flashcard-inner">
39
+ <div class="flashcard-front">
40
+ Band 6
41
+ </div>
42
+ <div class="flashcard-back">
43
+ The main parts of the prompt are addressed (though some may be more fully covered than others). An appropriate format is used. A position is presented that is directly relevant to the prompt, although the conclusions drawn may be unclear, unjustified or repetitive.
44
+ </div>
45
+ </div>
46
+ </div>
47
+ <div class="flashcard">
48
+ <div class="flashcard-inner">
49
+ <div class="flashcard-front">
50
+ Band 5
51
+ </div>
52
+ <div class="flashcard-back">
53
+ The main parts of the prompt are incompletely addressed. The format may be inappropriate in places. The writer expresses a position, but the development is not always clear. Some main ideas are put forward, but they are limited and are not sufficiently developed and/or there may be irrelevant detail.
54
+ </div>
55
+ </div>
56
+ </div>
57
+ <div class="flashcard">
58
+ <div class="flashcard-inner">
59
+ <div class="flashcard-front">
60
+ Band 4
61
+ </div>
62
+ <div class="flashcard-back">
63
+ The prompt is tackled in a minimal way, or the answer is tangential, possibly due to some misunderstanding of the prompt. The format may be inappropriate. A position is discernible, but the reader has to read carefully to find it. Main ideas are difficult to identify and such ideas that are identifiable may lack relevance, clarity and/or support.
64
+ </div>
65
+ </div>
66
+ </div>
67
+ <div class="flashcard">
68
+ <div class="flashcard-inner">
69
+ <div class="flashcard-front">
70
+ Band 3
71
+ </div>
72
+ <div class="flashcard-back">
73
+ No part of the prompt is adequately addressed, or the prompt has been misunderstood. No relevant position can be identified, and/or there is little direct response to the question/s. There are few ideas, and these may be irrelevant or insufficiently developed.
74
+ </div>
75
+ </div>
76
+ </div>
77
+ <div class="flashcard">
78
+ <div class="flashcard-inner">
79
+ <div class="flashcard-front">
80
+ Band 2
81
+ </div>
82
+ <div class="flashcard-back">
83
+ The content is barely related to the prompt. No position can be identified. There may be glimpses of one or two ideas without development. There is little relevant message, or the entire response may be off-topic.
84
+ </div>
85
+ </div>
86
+ </div>
87
+ <div class="flashcard">
88
+ <div class="flashcard-inner">
89
+ <div class="flashcard-front">
90
+ Band 1
91
+ </div>
92
+ <div class="flashcard-back">
93
+ Responses of 20 words or fewer are rated at Band 1. The content is wholly unrelated to the prompt. Any copied rubric must be discounted.
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ <a href="{{ url_for('infer.rubric_explanation') }}" class="btn btn-primary">Go Back to Rubric Explanation Page</a>
99
+ </div>
100
+ {% endblock %}
101
+
102
+ {% block extra_css %}
103
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/flashcards.css') }}">
104
+ {% endblock %}
my_app/views/__init__.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint
2
+
3
+ auth_bp = Blueprint('auth', __name__, template_folder='templates')
4
+ dashboard_bp = Blueprint('dashboard', __name__, template_folder='templates')
5
+ infer_bp = Blueprint('infer', __name__, template_folder='templates')
6
+ about_bp = Blueprint('about', __name__, template_folder='templates')
7
+ error_bp = Blueprint('error', __name__, template_folder='templates')
8
+
9
+ from . import auth, dashboard, infer, about, error
my_app/views/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (508 Bytes). View file
 
my_app/views/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (744 Bytes). View file
 
my_app/views/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (604 Bytes). View file
 
my_app/views/__pycache__/about.cpython-311.pyc ADDED
Binary file (544 Bytes). View file
 
my_app/views/__pycache__/about.cpython-312.pyc ADDED
Binary file (472 Bytes). View file
 
my_app/views/__pycache__/auth.cpython-310.pyc ADDED
Binary file (1.81 kB). View file
 
my_app/views/__pycache__/auth.cpython-311.pyc ADDED
Binary file (3.6 kB). View file
 
my_app/views/__pycache__/auth.cpython-312.pyc ADDED
Binary file (3.18 kB). View file
 
my_app/views/__pycache__/dashboard.cpython-310.pyc ADDED
Binary file (2.07 kB). View file