Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -9,112 +9,176 @@ import os
|
|
| 9 |
from pathlib import Path
|
| 10 |
import time
|
| 11 |
|
|
|
|
| 12 |
GRADING_RUBRIC = """
|
| 13 |
-
GRADING RUBRIC
|
| 14 |
-
|
| 15 |
-
I. APA Formatting (60
|
| 16 |
-
1. APA Title Page (5 pts):
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
REQUIRED OUTPUT FORMAT
|
| 52 |
You must present your final evaluation in the following structured format. Do not deviate from this format.
|
| 53 |
-
|
| 54 |
Grading Evaluation: NURS 305 Essay - Skin Lesions
|
| 55 |
Student Submission Analysis
|
| 56 |
Final Score: [Total Points] / 100
|
| 57 |
-
|
| 58 |
Part I: APA Formatting (Score: [Points] / 60)
|
| 59 |
-
1. APA Title Page: [Score]/5. Justification: [
|
| 60 |
-
2. APA General Guidelines: [Score]/5. Justification: [
|
| 61 |
-
3. APA Text - Misc. Errors: [Score]/5. Justification: [
|
| 62 |
-
4. APA In-text Citations - Presence: [Score]/5. Justification: [
|
| 63 |
-
5. APA In-text Citation - Formatting: [Score]/5. Justification: [
|
| 64 |
-
6. APA Reference Page - General Formatting: [Score]/5. Justification: [
|
| 65 |
-
7. APA Reference Page - Line Spacing: [Score]/5. Justification: [
|
| 66 |
-
8. APA References - Scholarly & Timely: [Score]/10. Justification: [
|
| 67 |
-
9. APA References - Author Names: [Score]/5. Justification: [
|
| 68 |
-
10. APA References - Dates: [Score]/5. Justification: [
|
| 69 |
-
11. APA References - Capitalization: [Score]/5. Justification: [
|
| 70 |
-
12. APA References - Italics: [Score]/5. Justification: [
|
| 71 |
-
13. APA References - Hyperlinks: [Score]/5. Justification: [
|
| 72 |
-
|
| 73 |
Part II: Content (Score: [Points] / 40)
|
| 74 |
-
14. Introduction: [Score]/10. Justification: [
|
| 75 |
-
15. Common Causes: [Score]/10. Justification: [
|
| 76 |
-
16. Nursing Considerations: [Score]/10. Justification: [
|
| 77 |
-
|
| 78 |
Professor's Summary:
|
| 79 |
-
[Provide a 2-3 sentence summary in the persona of Dr. Vance,
|
| 80 |
-
|
| 81 |
"""
|
| 82 |
|
|
|
|
| 83 |
BASE_PROMPT_TEMPLATE = """
|
| 84 |
-
You are
|
| 85 |
|
| 86 |
-
|
| 87 |
-
You are Dr. Stone, a meticulous and experienced Associate Professor of Nursing at a major university. You have been teaching for over 20 years, with a specialization in medical-surgical nursing. You are known for your high standards, particularly regarding academic integrity and the strict application of the American Psychological Association (APA) 7th Edition formatting guidelines. Your feedback is always direct, precise, and aimed at preparing students for the rigorous documentation standards required in the healthcare profession. Your tone is professional, authoritative, and educational. You do not offer praise for meeting baseline expectations; you simply state that the requirements have been met. Your criticism is specific, referencing the exact rule or rubric criterion that was violated.
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
-
|
| 93 |
-
Your task is to analyze the attached student paper file and grade it with absolute precision according to the provided rubric. You must function as a perfect and unflinchingly accurate grader. There is no room for subjective interpretation or leniency. You will identify every error in content, formatting, spelling, grammar, and citation, and assign points strictly based on the rubric's quantitative thresholds.
|
| 94 |
-
|
| 95 |
-
Your output should be a single JSON format structure response as indicated by the example below.
|
| 96 |
-
|
| 97 |
-
**DO NOT** provide any introductory text, conversational pleasantries, or explanations outside of the requested JSON structure. Your entire response must be a single, valid JSON object.
|
| 98 |
-
|
| 99 |
-
**Use the following rubric to grade the attached student paper:**
|
| 100 |
{GRADING_RUBRIC}
|
| 101 |
|
| 102 |
-
**
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
{{
|
| 104 |
-
"
|
| 105 |
-
"
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
}}
|
| 119 |
"""
|
| 120 |
|
|
@@ -123,154 +187,96 @@ class GradingResult:
|
|
| 123 |
"""Holds the structured result of a single graded essay."""
|
| 124 |
file_name: str
|
| 125 |
success: bool
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
feedback: Dict[str, str] = field(default_factory=dict)
|
| 129 |
-
summary: Optional[str] = None
|
| 130 |
error_message: Optional[str] = None
|
| 131 |
|
| 132 |
class GeminiGrader:
|
| 133 |
"""Manages interaction with the Google Gemini API for grading."""
|
| 134 |
def __init__(self, api_key: str):
|
| 135 |
-
print("[DEBUG] Initializing GeminiGrader...")
|
| 136 |
genai.configure(api_key=api_key)
|
| 137 |
self.model = genai.GenerativeModel(model_name="gemini-1.5-pro-latest")
|
| 138 |
-
print("[DEBUG] GeminiGrader initialized successfully.")
|
| 139 |
|
| 140 |
def grade_essay(self, student_paper_file: object, example_paper_file: Optional[object]) -> GradingResult:
|
| 141 |
"""Sends files to Gemini for grading and parses the JSON response."""
|
| 142 |
-
print(f"[DEBUG] Inside grade_essay for student paper: {student_paper_file.display_name}")
|
| 143 |
prompt_text = BASE_PROMPT_TEMPLATE.format(GRADING_RUBRIC=GRADING_RUBRIC)
|
| 144 |
-
|
| 145 |
-
content_list = [prompt_text, student_paper_file]
|
| 146 |
if example_paper_file:
|
| 147 |
-
|
| 148 |
-
content_list.
|
| 149 |
|
| 150 |
try:
|
| 151 |
-
|
| 152 |
-
response = self.model.generate_content(content_list, request_options={'timeout': 600}) # 10 minute timeout
|
| 153 |
-
print(f"[DEBUG] <<< Model response received for {student_paper_file.display_name}. Parsing JSON.")
|
| 154 |
cleaned_response = response.text.strip().replace("```json", "").replace("```", "")
|
| 155 |
data = json.loads(cleaned_response)
|
| 156 |
|
| 157 |
-
|
| 158 |
-
if not all(key in data for key in
|
| 159 |
-
raise KeyError("Model response missing required keys.")
|
| 160 |
-
|
| 161 |
-
print(f"[DEBUG] Successfully parsed response for {student_paper_file.display_name}.")
|
| 162 |
return GradingResult(
|
| 163 |
file_name=student_paper_file.display_name,
|
| 164 |
success=True,
|
| 165 |
-
|
| 166 |
-
deductions=data["pointDeductions"],
|
| 167 |
-
feedback=data["feedback"],
|
| 168 |
-
summary=data["summary"]
|
| 169 |
)
|
| 170 |
except json.JSONDecodeError as e:
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
print("[DEBUG] --- FAULTY RESPONSE FROM MODEL ---")
|
| 174 |
-
print(cleaned_response)
|
| 175 |
-
print("[DEBUG] --- END OF FAULTY RESPONSE ---")
|
| 176 |
-
print("="*58 + "\n")
|
| 177 |
-
return GradingResult(
|
| 178 |
-
file_name=student_paper_file.display_name,
|
| 179 |
-
success=False,
|
| 180 |
-
error_message=f"Model returned malformed JSON. Details: {e}"
|
| 181 |
-
)
|
| 182 |
-
|
| 183 |
except Exception as e:
|
| 184 |
-
|
| 185 |
-
return GradingResult(
|
| 186 |
-
file_name=student_paper_file.display_name,
|
| 187 |
-
success=False,
|
| 188 |
-
error_message=f"An API or model error occurred: {str(e)}"
|
| 189 |
-
)
|
| 190 |
|
| 191 |
def process_single_file(file_path: str, grader: GeminiGrader, example_paper_file_obj: Optional[object]) -> GradingResult:
|
| 192 |
"""Uploads a single student paper and calls the grader."""
|
| 193 |
-
file_name = os.path.basename(file_path)
|
| 194 |
-
print(f"[DEBUG] Starting process_single_file for: {file_name}")
|
| 195 |
student_paper_file_obj = None
|
| 196 |
try:
|
| 197 |
-
|
| 198 |
-
student_paper_file_obj = genai.upload_file(path=file_path, display_name=file_name)
|
| 199 |
-
print(f"[DEBUG] -> Upload successful for '{file_name}'. Handing off to grade_essay.")
|
| 200 |
return grader.grade_essay(student_paper_file_obj, example_paper_file_obj)
|
| 201 |
except Exception as e:
|
| 202 |
-
|
| 203 |
-
return GradingResult(file_name=file_name, success=False, error_message=str(e))
|
| 204 |
finally:
|
| 205 |
if student_paper_file_obj:
|
| 206 |
-
print(f"[DEBUG] -> Cleaning up uploaded file: {student_paper_file_obj.name}")
|
| 207 |
genai.delete_file(student_paper_file_obj.name)
|
| 208 |
-
print(f"[DEBUG] -> Cleanup complete for {file_name}.")
|
| 209 |
|
| 210 |
async def grade_papers_concurrently(
|
| 211 |
files: List[gr.File], example_paper_file: gr.File, api_key: str, progress=gr.Progress(track_tqdm=True)
|
| 212 |
) -> (str, str):
|
| 213 |
"""The main asynchronous function that orchestrates the grading process."""
|
| 214 |
-
print("\n" + "="*50)
|
| 215 |
-
print("[DEBUG] grade_papers_concurrently initiated.")
|
| 216 |
start_time = time.time()
|
| 217 |
-
if not api_key:
|
| 218 |
-
|
| 219 |
-
if not files:
|
| 220 |
-
raise gr.Error("Please upload at least one paper to grade.")
|
| 221 |
|
| 222 |
example_paper_file_obj = None
|
| 223 |
try:
|
| 224 |
grader = GeminiGrader(api_key)
|
| 225 |
-
|
| 226 |
if example_paper_file:
|
| 227 |
-
print("[DEBUG] Example paper provided. Uploading it now...")
|
| 228 |
progress(0, desc="Uploading example paper...")
|
| 229 |
example_paper_file_obj = genai.upload_file(path=example_paper_file.name, display_name=os.path.basename(example_paper_file.name))
|
| 230 |
-
print(f"[DEBUG] Example paper uploaded successfully: {example_paper_file_obj.name}")
|
| 231 |
|
| 232 |
file_paths = [file.name for file in files]
|
| 233 |
total_files = len(file_paths)
|
| 234 |
-
print(f"[DEBUG] Preparing to grade {total_files} paper(s).")
|
| 235 |
|
| 236 |
with ThreadPoolExecutor(max_workers=1) as executor:
|
| 237 |
-
print("[DEBUG] ThreadPoolExecutor started (max_workers=1).")
|
| 238 |
loop = asyncio.get_event_loop()
|
| 239 |
-
tasks = [
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
process_single_file,
|
| 243 |
-
file_path,
|
| 244 |
-
grader,
|
| 245 |
-
example_paper_file_obj
|
| 246 |
-
)
|
| 247 |
-
for file_path in file_paths
|
| 248 |
-
]
|
| 249 |
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
for i, future in enumerate(asyncio.as_completed(tasks)):
|
| 253 |
-
result = await future
|
| 254 |
-
print(f"[DEBUG] Task {i+1}/{total_files} completed for file: {result.file_name}")
|
| 255 |
-
results.append(result)
|
| 256 |
-
|
| 257 |
-
print("[DEBUG] All grading tasks finished. Formatting final output.")
|
| 258 |
-
# --- Format the final output ---
|
| 259 |
successful_grades = [res for res in results if res.success]
|
| 260 |
failed_grades = [res for res in results if not res.success]
|
| 261 |
-
|
| 262 |
for result in successful_grades:
|
|
|
|
| 263 |
output_markdown += f"### ✅ Grade for: **{result.file_name}**\n"
|
| 264 |
-
output_markdown += f"**Final
|
| 265 |
-
|
| 266 |
-
for
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
deductions_str = "Excellent work! No points were deducted.\n"
|
| 271 |
-
output_markdown += "**Point Deductions Breakdown:**\n" + deductions_str + "\n"
|
| 272 |
-
output_markdown += f"**Summary:** {result.summary}\n"
|
| 273 |
output_markdown += "---\n"
|
|
|
|
| 274 |
if failed_grades:
|
| 275 |
output_markdown += "### ❌ Failed Papers\n"
|
| 276 |
for result in failed_grades:
|
|
@@ -281,15 +287,11 @@ async def grade_papers_concurrently(
|
|
| 281 |
end_time = time.time()
|
| 282 |
runtime = f"Total runtime: {end_time - start_time:.2f} seconds."
|
| 283 |
status = f"Grading complete. {len(successful_grades)} papers graded successfully, {len(failed_grades)} failed."
|
| 284 |
-
print(f"[DEBUG] Process finished. {runtime}")
|
| 285 |
-
print("="*50 + "\n")
|
| 286 |
return output_markdown, f"{status}\n{runtime}"
|
| 287 |
|
| 288 |
finally:
|
| 289 |
if example_paper_file_obj:
|
| 290 |
-
print("[DEBUG] Final cleanup: Deleting example paper from API.")
|
| 291 |
genai.delete_file(example_paper_file_obj.name)
|
| 292 |
-
print("[DEBUG] Final cleanup complete.")
|
| 293 |
|
| 294 |
# --- Build the Gradio Interface ---
|
| 295 |
with gr.Blocks(theme=gr.themes.Soft(), title="Nursing Essay Grader") as demo:
|
|
@@ -304,34 +306,16 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Nursing Essay Grader") as demo:
|
|
| 304 |
"""
|
| 305 |
)
|
| 306 |
api_key_input = gr.Textbox(label="Google API Key", placeholder="Enter your Google API Key here", type="password")
|
| 307 |
-
|
| 308 |
with gr.Row():
|
| 309 |
-
file_uploads = gr.File(
|
| 310 |
-
|
| 311 |
-
file_count="multiple",
|
| 312 |
-
file_types=['.pdf', '.docx'],
|
| 313 |
-
type="filepath",
|
| 314 |
-
scale=2
|
| 315 |
-
)
|
| 316 |
-
example_paper_upload = gr.File(
|
| 317 |
-
label="Upload Example Paper (Optional)",
|
| 318 |
-
file_count="single",
|
| 319 |
-
file_types=['.pdf', '.docx'],
|
| 320 |
-
type="filepath",
|
| 321 |
-
scale=1
|
| 322 |
-
)
|
| 323 |
-
|
| 324 |
grade_button = gr.Button("🚀 Grade All Papers", variant="primary")
|
| 325 |
gr.Markdown("---")
|
| 326 |
gr.Markdown("## 📊 Grading Results")
|
| 327 |
results_output = gr.Markdown(label="Formatted Grades")
|
| 328 |
status_output = gr.Textbox(label="Runtime Status", lines=2, interactive=False)
|
| 329 |
|
| 330 |
-
grade_button.click(
|
| 331 |
-
fn=grade_papers_concurrently,
|
| 332 |
-
inputs=[file_uploads, example_paper_upload, api_key_input],
|
| 333 |
-
outputs=[results_output, status_output]
|
| 334 |
-
)
|
| 335 |
|
| 336 |
if __name__ == "__main__":
|
| 337 |
demo.launch(debug=True)
|
|
|
|
| 9 |
from pathlib import Path
|
| 10 |
import time
|
| 11 |
|
| 12 |
+
# The original, verbose rubric has been restored to provide maximum detail to the model.
|
| 13 |
GRADING_RUBRIC = """
|
| 14 |
+
DETAILED GRADING RUBRIC & INSTRUCTIONS
|
| 15 |
+
You will assess the paper against the following 16 criteria. For each criterion, you will determine the student's score based on a forensic analysis of their work.
|
| 16 |
+
I. APA Formatting (Total 60 points)
|
| 17 |
+
1. APA Title Page (5 pts):
|
| 18 |
+
Check for the 9 required components of a student title page. For this task, the 9 components are defined as: (1) Page number in the header (Page 1), (2) Paper title (bolded), (3) Author's name, (4) Department name, (5) University name, (6) Course number and name, (7) Instructor's name, (8) Assignment due date, (9) All elements are centered and correctly spaced in the upper half of the page.
|
| 19 |
+
Scoring:
|
| 20 |
+
5 pts: All 9 components are present and correctly formatted.
|
| 21 |
+
0 pts: 1 or more components are missing or incorrectly formatted.
|
| 22 |
+
2. APA General Guidelines - Main Body (5 pts):
|
| 23 |
+
Check for adherence to all of the following:
|
| 24 |
+
(1) Paper is typed.
|
| 25 |
+
(2) 1-inch margins on all sides.
|
| 26 |
+
(3) Font is either 11 pt Calibri or 12 pt Times New Roman (consistently).
|
| 27 |
+
The 3 additional main body text requirements are: (4) All text is double-spaced, (5) Text is aligned to the left margin (not justified), (6) The first line of every paragraph is indented 0.5 inches.
|
| 28 |
+
Scoring:
|
| 29 |
+
5 pts: All 6 guidelines are met perfectly.
|
| 30 |
+
2 pts: 1 guideline is not met.
|
| 31 |
+
0 pts: 2 or more guidelines are not met.
|
| 32 |
+
3. APA Text - Main Body Misc. Errors (5 pts):
|
| 33 |
+
Scan the entire document for the following: (1) Extra spacing between paragraphs, (2) Misspellings, (3) Typographical errors.
|
| 34 |
+
Scoring:
|
| 35 |
+
5 pts: 0 errors found.
|
| 36 |
+
4 pts: Exactly 1 error found.
|
| 37 |
+
0 pts: 2 or more errors found.
|
| 38 |
+
4. APA In-text Citations - Presence (5 pts):
|
| 39 |
+
Analyze the text: Identify every statement of fact, statistic, or opinion that is not common knowledge and requires a citation. Count how many of these required citations are missing.
|
| 40 |
+
Scoring:
|
| 41 |
+
5 pts: 0 missing in-text citations.
|
| 42 |
+
3.5 pts: 1 to 5 missing in-text citations.
|
| 43 |
+
0 pts: 6 or more missing in-text citations.
|
| 44 |
+
5. APA In-text Citation - Formatting (5 pts):
|
| 45 |
+
Analyze every provided in-text citation. Check for correct APA 7th Ed. format (e.g., (Author, Year) for parenthetical; Author (Year) for narrative; et al. usage; multiple authors). Count every single formatting error.
|
| 46 |
+
Scoring:
|
| 47 |
+
5 pts: 0 formatting errors.
|
| 48 |
+
2 pts: 1 to 2 formatting errors.
|
| 49 |
+
0 pts: 3 or more formatting errors.
|
| 50 |
+
6. APA Reference Page - General Formatting (5 pts):
|
| 51 |
+
Check for the following: (1) The title "References" is on a new page, centered, and bolded. (2) All entries are alphabetized by the first author's last name. (3) A 0.5-inch hanging indent is applied to all entries.
|
| 52 |
+
Scoring:
|
| 53 |
+
5 pts: The entire page meets all 3 general formatting expectations.
|
| 54 |
+
0 pts: 1 or more errors are present.
|
| 55 |
+
7. APA Reference Page - Line Spacing (5 pts):
|
| 56 |
+
Check for the following: The entire reference page, including between entries, is uniformly double-spaced.
|
| 57 |
+
Scoring:
|
| 58 |
+
5 pts: The entire page adheres to APA double-spacing rules.
|
| 59 |
+
0 pts: 1 or more line spacing errors are present (e.g., single spacing, extra space between entries).
|
| 60 |
+
8. APA References - Scholarly & Timely (10 pts):
|
| 61 |
+
Analyze the reference list. A "scholarly" reference is a peer-reviewed journal article, an academic book/chapter, or a publication from a professional organization (e.g., CDC, WHO). A non-scholarly source would be a general website, blog, or news article. A timely reference is one published within the last 7 years (i.e., between July 20, 2018, and July 19, 2025).
|
| 62 |
+
Check for two conditions: (1) Are there at least 3 scholarly references? AND (2) Are all of those references dated within the last 7 years?
|
| 63 |
+
Scoring:
|
| 64 |
+
10 pts: At least 3 scholarly references are cited, AND all references are dated within the last 7 years.
|
| 65 |
+
0 pts: Fewer than 3 scholarly references are cited, OR one or more references are older than 7 years.
|
| 66 |
+
9. APA References - Author Names (5 pts):
|
| 67 |
+
Analyze all author names on the reference page. Check for correct format: Lastname, F. M.. Also check for correct handling of sources with no author (the title or organization name moves to the author position).
|
| 68 |
+
Scoring:
|
| 69 |
+
5 pts: All author names are formatted perfectly.
|
| 70 |
+
2 pts: There is 1 error where an author's name should have been a title/publisher, or vice versa.
|
| 71 |
+
0 pts: There are general, repeated formatting errors in author names.
|
| 72 |
+
10. APA References - Dates (5 pts):
|
| 73 |
+
Analyze the date for each reference. Check for correct APA 7th Ed. format (e.g., (Year). for journals/books; (Year, Month Day). for web sources). Count every error.
|
| 74 |
+
Scoring:
|
| 75 |
+
5 pts: 0 errors.
|
| 76 |
+
3 pts: Exactly 1 error.
|
| 77 |
+
0 pts: 2 or more errors.
|
| 78 |
+
11. APA References - Capitalization (5 pts):
|
| 79 |
+
Analyze the titles in each reference. Check for correct APA 7th Ed. capitalization rules: Sentence case for article and book titles. Title case for periodical (journal) titles. Count every error.
|
| 80 |
+
Scoring:
|
| 81 |
+
5 pts: 0 errors.
|
| 82 |
+
3 pts: Exactly 1 error.
|
| 83 |
+
0 pts: 2 or more errors.
|
| 84 |
+
12. APA References - Italics (5 pts):
|
| 85 |
+
Analyze each reference for correct italicization. Check for APA 7th Ed. rules: Italicize journal titles and volume numbers. Italicize book titles. Count every error.
|
| 86 |
+
Scoring:
|
| 87 |
+
5 pts: 0 or 1 error in italicization.
|
| 88 |
+
0 pts: 2 or more errors in italicization.
|
| 89 |
+
13. APA References - Hyperlinks (5 pts):
|
| 90 |
+
Analyze all DOIs and URLs. The 4 guidelines are: (1) All DOIs are presented as a full, active hyperlink (e.g., https://doi.org/...). (2) The phrase "Retrieved from" is NOT used before a URL or DOI. (3) There is no period after the DOI or URL. (4) URLs that are not DOIs are included.
|
| 91 |
+
Scoring:
|
| 92 |
+
5 pts: All 4 guidelines are followed for all relevant references.
|
| 93 |
+
2 pts: 1 of the 4 guidelines is not followed.
|
| 94 |
+
0 pts: 2 or more of the 4 guidelines are not followed.
|
| 95 |
+
II. Content (Total 40 points)
|
| 96 |
+
14. Skin Lesion: Introduction (10 pts):
|
| 97 |
+
Analyze the introduction. Check that it: (1) Is one or two paragraphs long. (2) Clearly introduces the topic of skin lesions and states the issue to be examined. (3) Is not more than one page long. (4) Is free of spelling/typographical errors.
|
| 98 |
+
Scoring:
|
| 99 |
+
10 pts: Meets all requirements with 0 spelling/typo errors.
|
| 100 |
+
5 pts: Meets length/content requirements but has 1-2 spelling/typo errors.
|
| 101 |
+
0 pts: Does not include an introduction, OR the introduction is more than 1 page long, OR it has 3 or more spelling/typographical errors.
|
| 102 |
+
15. Skin Lesion: Common Causes (10 pts):
|
| 103 |
+
Analyze the body of the essay. Check if the paper discusses at least one common cause of skin lesions (e.g., infection, trauma, allergic reactions, systemic disease).
|
| 104 |
+
Scoring:
|
| 105 |
+
10 pts: Discusses at least 1 common cause.
|
| 106 |
+
0 pts: Does not discuss any common causes.
|
| 107 |
+
16. Skin Lesion: Nursing Considerations (10 pts):
|
| 108 |
+
Analyze the body of the essay. Check if the paper discusses at least one specific nursing consideration or intervention for patients with skin lesions (e.g., wound care, patient education, assessment techniques like ABCDE for melanoma, comfort measures).
|
| 109 |
+
Scoring:
|
| 110 |
+
10 pts: Discusses at least 1 nursing intervention/consideration.
|
| 111 |
+
0 pts: Does not discuss any nursing interventions/considerations.
|
| 112 |
REQUIRED OUTPUT FORMAT
|
| 113 |
You must present your final evaluation in the following structured format. Do not deviate from this format.
|
|
|
|
| 114 |
Grading Evaluation: NURS 305 Essay - Skin Lesions
|
| 115 |
Student Submission Analysis
|
| 116 |
Final Score: [Total Points] / 100
|
|
|
|
| 117 |
Part I: APA Formatting (Score: [Points] / 60)
|
| 118 |
+
1. APA Title Page: [Score]/5. Justification: [State precisely why the score was given. E.g., "Met all 9 requirements." or "0 points awarded. The title page was missing the course number and instructor's name."]
|
| 119 |
+
2. APA General Guidelines: [Score]/5. Justification: [E.g., "5 points awarded. The document used 12 pt Times New Roman, 1-inch margins, double-spacing, left alignment, and paragraph indents." or "2 points awarded. The left and right margins were set to 1.25 inches instead of the required 1 inch."]
|
| 120 |
+
3. APA Text - Misc. Errors: [Score]/5. Justification: [E.g., "4 points awarded. One typographical error ('hte' instead of 'the') was noted in paragraph 3."]
|
| 121 |
+
4. APA In-text Citations - Presence: [Score]/5. Justification: [E.g., "3.5 points awarded. Analysis identified 4 statements requiring a citation that were not cited."]
|
| 122 |
+
5. APA In-text Citation - Formatting: [Score]/5. Justification: [E.g., "2 points awarded. Two citations used an ampersand in the narrative format (e.g., 'Smith & Jones (2022) found...') which is incorrect."]
|
| 123 |
+
6. APA Reference Page - General Formatting: [Score]/5. Justification: [E.g., "0 points awarded. The title 'References' was not bolded, and a hanging indent was not used."]
|
| 124 |
+
7. APA Reference Page - Line Spacing: [Score]/5. Justification: [E.g., "0 points awarded. An extra space was added between each reference entry."]
|
| 125 |
+
8. APA References - Scholarly & Timely: [Score]/10. Justification: [E.g., "10 points awarded. The paper cited 4 peer-reviewed journal articles, all published between 2020 and 2024." or "0 points awarded. Only two scholarly sources were used, and one reference was from 2016."]
|
| 126 |
+
9. APA References - Author Names: [Score]/5. Justification: [E.g., "5 points awarded. All author names were formatted correctly."]
|
| 127 |
+
10. APA References - Dates: [Score]/5. Justification: [E.g., "3 points awarded. One reference was missing the period after the year: (2021) instead of (2021)."]
|
| 128 |
+
11. APA References - Capitalization: [Score]/5. Justification: [E.g., "0 points awarded. The titles of two journal articles were written in title case instead of sentence case."]
|
| 129 |
+
12. APA References - Italics: [Score]/5. Justification: [E.g., "5 points awarded. One error noted where a journal volume number was not italicized. This falls within the 0-1 error threshold for full points."]
|
| 130 |
+
13. APA References - Hyperlinks: [Score]/5. Justification: [E.g., "2 points awarded. The phrase 'Retrieved from' was incorrectly used before a URL."]
|
|
|
|
| 131 |
Part II: Content (Score: [Points] / 40)
|
| 132 |
+
14. Introduction: [Score]/10. Justification: [E.g., "10 points awarded. The introduction was a single, concise paragraph that clearly stated the paper's focus. No errors noted."]
|
| 133 |
+
15. Common Causes: [Score]/10. Justification: [E.g., "10 points awarded. The paper successfully discussed infectious agents as a common cause of skin lesions."]
|
| 134 |
+
16. Nursing Considerations: [Score]/10. Justification: [E.g., "0 points awarded. The paper failed to discuss any nursing interventions or specific considerations for patients with skin lesions."]
|
|
|
|
| 135 |
Professor's Summary:
|
| 136 |
+
[Provide a 2-3 sentence summary in the persona of Dr. Vance. E.g., "While the content discussing the cause of lesions was adequate, the submission demonstrated significant and widespread deficiencies in adhering to APA 7th Edition standards. These formatting and citation skills are non-negotiable in academic and professional nursing. Careful review of the APA manual is required before the next submission."]
|
|
|
|
| 137 |
"""
|
| 138 |
|
| 139 |
+
# The prompt has been updated to request the new, more verbose JSON structure.
|
| 140 |
BASE_PROMPT_TEMPLATE = """
|
| 141 |
+
You are Dr. Stone, a meticulous Associate Professor of Nursing. Your task is to analyze the attached student paper file and grade it with absolute precision against the provided rubric. You may also be provided with an example of a perfectly formatted paper for your reference.
|
| 142 |
|
| 143 |
+
Your entire response must be a single, valid JSON object and nothing else.
|
|
|
|
| 144 |
|
| 145 |
+
**GRADING INSTRUCTIONS:**
|
| 146 |
+
1. Carefully analyze the student's paper file, paying close attention to all formatting details (margins, fonts, spacing, etc.) and content.
|
| 147 |
+
2. For each of the 16 criteria in the rubric, provide a score and a brief, specific justification for that score.
|
| 148 |
+
3. Calculate the final score by summing the scores from all 16 criteria.
|
| 149 |
+
4. Provide a 2-3 sentence professional summary of the paper's performance.
|
| 150 |
|
| 151 |
+
**RUBRIC:**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
{GRADING_RUBRIC}
|
| 153 |
|
| 154 |
+
**REQUIRED JSON OUTPUT FORMAT:**
|
| 155 |
+
Your output must be a JSON object with three top-level keys: `finalScore`, `gradingBreakdown`, and `summary`.
|
| 156 |
+
The `gradingBreakdown` key must contain a list of 16 objects, one for each criterion in the rubric.
|
| 157 |
+
|
| 158 |
+
**EXAMPLE OF THE REQUIRED JSON OUTPUT FORMAT:**
|
| 159 |
{{
|
| 160 |
+
"finalScore": 89,
|
| 161 |
+
"gradingBreakdown": [
|
| 162 |
+
{{
|
| 163 |
+
"criterion": "1. APA Title Page",
|
| 164 |
+
"score": 5,
|
| 165 |
+
"maxScore": 5,
|
| 166 |
+
"justification": "All 9 required components are present and correctly formatted."
|
| 167 |
+
}},
|
| 168 |
+
{{
|
| 169 |
+
"criterion": "2. APA General Guidelines",
|
| 170 |
+
"score": 2,
|
| 171 |
+
"maxScore": 5,
|
| 172 |
+
"justification": "The document uses 1.25-inch margins instead of the required 1-inch, which is one guideline violation."
|
| 173 |
+
}},
|
| 174 |
+
{{
|
| 175 |
+
"criterion": "3. APA Text - Misc. Errors",
|
| 176 |
+
"score": 4,
|
| 177 |
+
"maxScore": 5,
|
| 178 |
+
"justification": "One typographical error ('hte' instead of 'the') was noted in paragraph 3."
|
| 179 |
+
}}
|
| 180 |
+
],
|
| 181 |
+
"summary": "This is a strong paper with excellent critical analysis. The final grade was primarily impacted by minor but frequent APA formatting errors, which should be the main focus for improvement."
|
| 182 |
}}
|
| 183 |
"""
|
| 184 |
|
|
|
|
| 187 |
"""Holds the structured result of a single graded essay."""
|
| 188 |
file_name: str
|
| 189 |
success: bool
|
| 190 |
+
# This now stores the entire JSON response for detailed output
|
| 191 |
+
raw_response: Optional[dict] = None
|
|
|
|
|
|
|
| 192 |
error_message: Optional[str] = None
|
| 193 |
|
| 194 |
class GeminiGrader:
|
| 195 |
"""Manages interaction with the Google Gemini API for grading."""
|
| 196 |
def __init__(self, api_key: str):
|
|
|
|
| 197 |
genai.configure(api_key=api_key)
|
| 198 |
self.model = genai.GenerativeModel(model_name="gemini-1.5-pro-latest")
|
|
|
|
| 199 |
|
| 200 |
def grade_essay(self, student_paper_file: object, example_paper_file: Optional[object]) -> GradingResult:
|
| 201 |
"""Sends files to Gemini for grading and parses the JSON response."""
|
|
|
|
| 202 |
prompt_text = BASE_PROMPT_TEMPLATE.format(GRADING_RUBRIC=GRADING_RUBRIC)
|
| 203 |
+
content_list = ["Please grade the attached student paper.", student_paper_file, prompt_text]
|
|
|
|
| 204 |
if example_paper_file:
|
| 205 |
+
content_list.insert(2, "Use this second file as a reference example of a perfectly formatted paper.")
|
| 206 |
+
content_list.insert(3, example_paper_file)
|
| 207 |
|
| 208 |
try:
|
| 209 |
+
response = self.model.generate_content(content_list, request_options={'timeout': 600})
|
|
|
|
|
|
|
| 210 |
cleaned_response = response.text.strip().replace("```json", "").replace("```", "")
|
| 211 |
data = json.loads(cleaned_response)
|
| 212 |
|
| 213 |
+
# Basic validation for the new structure
|
| 214 |
+
if not all(key in data for key in ["finalScore", "gradingBreakdown", "summary"]):
|
| 215 |
+
raise KeyError("Model response missing one or more required top-level keys.")
|
| 216 |
+
|
|
|
|
| 217 |
return GradingResult(
|
| 218 |
file_name=student_paper_file.display_name,
|
| 219 |
success=True,
|
| 220 |
+
raw_response=data
|
|
|
|
|
|
|
|
|
|
| 221 |
)
|
| 222 |
except json.JSONDecodeError as e:
|
| 223 |
+
error_details = f"Model returned malformed JSON. Error: {e}. Raw Response: {cleaned_response}"
|
| 224 |
+
return GradingResult(file_name=student_paper_file.display_name, success=False, error_message=error_details)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
except Exception as e:
|
| 226 |
+
return GradingResult(file_name=student_paper_file.display_name, success=False, error_message=f"An API or model error occurred: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
| 228 |
def process_single_file(file_path: str, grader: GeminiGrader, example_paper_file_obj: Optional[object]) -> GradingResult:
|
| 229 |
"""Uploads a single student paper and calls the grader."""
|
|
|
|
|
|
|
| 230 |
student_paper_file_obj = None
|
| 231 |
try:
|
| 232 |
+
student_paper_file_obj = genai.upload_file(path=file_path, display_name=os.path.basename(file_path))
|
|
|
|
|
|
|
| 233 |
return grader.grade_essay(student_paper_file_obj, example_paper_file_obj)
|
| 234 |
except Exception as e:
|
| 235 |
+
return GradingResult(file_name=os.path.basename(file_path), success=False, error_message=str(e))
|
|
|
|
| 236 |
finally:
|
| 237 |
if student_paper_file_obj:
|
|
|
|
| 238 |
genai.delete_file(student_paper_file_obj.name)
|
|
|
|
| 239 |
|
| 240 |
async def grade_papers_concurrently(
|
| 241 |
files: List[gr.File], example_paper_file: gr.File, api_key: str, progress=gr.Progress(track_tqdm=True)
|
| 242 |
) -> (str, str):
|
| 243 |
"""The main asynchronous function that orchestrates the grading process."""
|
|
|
|
|
|
|
| 244 |
start_time = time.time()
|
| 245 |
+
if not api_key: raise gr.Error("Google API Key is required.")
|
| 246 |
+
if not files: raise gr.Error("Please upload at least one paper to grade.")
|
|
|
|
|
|
|
| 247 |
|
| 248 |
example_paper_file_obj = None
|
| 249 |
try:
|
| 250 |
grader = GeminiGrader(api_key)
|
|
|
|
| 251 |
if example_paper_file:
|
|
|
|
| 252 |
progress(0, desc="Uploading example paper...")
|
| 253 |
example_paper_file_obj = genai.upload_file(path=example_paper_file.name, display_name=os.path.basename(example_paper_file.name))
|
|
|
|
| 254 |
|
| 255 |
file_paths = [file.name for file in files]
|
| 256 |
total_files = len(file_paths)
|
|
|
|
| 257 |
|
| 258 |
with ThreadPoolExecutor(max_workers=1) as executor:
|
|
|
|
| 259 |
loop = asyncio.get_event_loop()
|
| 260 |
+
tasks = [loop.run_in_executor(executor, process_single_file, fp, grader, example_paper_file_obj) for fp in file_paths]
|
| 261 |
+
results = [await f for f in asyncio.as_completed(tasks)]
|
| 262 |
+
progress(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
|
| 264 |
+
# This section is updated to format the new, verbose JSON structure
|
| 265 |
+
output_markdown = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
successful_grades = [res for res in results if res.success]
|
| 267 |
failed_grades = [res for res in results if not res.success]
|
| 268 |
+
|
| 269 |
for result in successful_grades:
|
| 270 |
+
response_data = result.raw_response
|
| 271 |
output_markdown += f"### ✅ Grade for: **{result.file_name}**\n"
|
| 272 |
+
output_markdown += f"**Final Score:** {response_data.get('finalScore', 'N/A')}/100\n\n"
|
| 273 |
+
output_markdown += "**Detailed Grading Breakdown:**\n"
|
| 274 |
+
for item in response_data.get('gradingBreakdown', []):
|
| 275 |
+
output_markdown += f"- **{item.get('criterion', 'N/A')}**: {item.get('score', 'N/A')} / {item.get('maxScore', 'N/A')}\n"
|
| 276 |
+
output_markdown += f" - *Justification: {item.get('justification', 'No justification provided.')}*\n"
|
| 277 |
+
output_markdown += f"\n**Summary:** {response_data.get('summary', 'No summary provided.')}\n"
|
|
|
|
|
|
|
|
|
|
| 278 |
output_markdown += "---\n"
|
| 279 |
+
|
| 280 |
if failed_grades:
|
| 281 |
output_markdown += "### ❌ Failed Papers\n"
|
| 282 |
for result in failed_grades:
|
|
|
|
| 287 |
end_time = time.time()
|
| 288 |
runtime = f"Total runtime: {end_time - start_time:.2f} seconds."
|
| 289 |
status = f"Grading complete. {len(successful_grades)} papers graded successfully, {len(failed_grades)} failed."
|
|
|
|
|
|
|
| 290 |
return output_markdown, f"{status}\n{runtime}"
|
| 291 |
|
| 292 |
finally:
|
| 293 |
if example_paper_file_obj:
|
|
|
|
| 294 |
genai.delete_file(example_paper_file_obj.name)
|
|
|
|
| 295 |
|
| 296 |
# --- Build the Gradio Interface ---
|
| 297 |
with gr.Blocks(theme=gr.themes.Soft(), title="Nursing Essay Grader") as demo:
|
|
|
|
| 306 |
"""
|
| 307 |
)
|
| 308 |
api_key_input = gr.Textbox(label="Google API Key", placeholder="Enter your Google API Key here", type="password")
|
|
|
|
| 309 |
with gr.Row():
|
| 310 |
+
file_uploads = gr.File(label="Upload Essays to Grade", file_count="multiple", file_types=['.pdf', '.docx'], type="filepath", scale=2)
|
| 311 |
+
example_paper_upload = gr.File(label="Upload Example Paper (Optional)", file_count="single", file_types=['.pdf', '.docx'], type="filepath", scale=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
grade_button = gr.Button("🚀 Grade All Papers", variant="primary")
|
| 313 |
gr.Markdown("---")
|
| 314 |
gr.Markdown("## 📊 Grading Results")
|
| 315 |
results_output = gr.Markdown(label="Formatted Grades")
|
| 316 |
status_output = gr.Textbox(label="Runtime Status", lines=2, interactive=False)
|
| 317 |
|
| 318 |
+
grade_button.click(fn=grade_papers_concurrently, inputs=[file_uploads, example_paper_upload, api_key_input], outputs=[results_output, status_output])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
|
| 320 |
if __name__ == "__main__":
|
| 321 |
demo.launch(debug=True)
|