Add help tags for enhanced user guidance in templates and improve feedback descriptions in models
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
|
{% load help_tags %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Anatomy
|
Anatomy
|
||||||
@@ -11,69 +12,69 @@
|
|||||||
{% block js %}
|
{% block js %}
|
||||||
<!--<script type="text/javascript" src="/admin/jsi18n/"></script>-->
|
<!--<script type="text/javascript" src="/admin/jsi18n/"></script>-->
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function showEditPopup(url) {
|
function showEditPopup(url) {
|
||||||
var win = window.open(url, "Edit",
|
var win = window.open(url, "Edit",
|
||||||
'height=500,width=800,resizable=yes,scrollbars=yes');
|
'height=500,width=800,resizable=yes,scrollbars=yes');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function showAddPopup(triggeringLink) {
|
function showAddPopup(triggeringLink) {
|
||||||
var name = triggeringLink.id.replace(/^add_/, '');
|
var name = triggeringLink.id.replace(/^add_/, '');
|
||||||
href = triggeringLink.href;
|
href = triggeringLink.href;
|
||||||
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
|
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
|
||||||
win.focus();
|
win.focus();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function closePopup(win, newID, newRepr, id) {
|
function closePopup(win, newID, newRepr, id) {
|
||||||
console.log(id)
|
console.log(id)
|
||||||
$(id).append('<option value=' + newID + ` title=${newRepr} >` + newRepr + '</option>').removeAttr("selected").val(newID)
|
$(id).append('<option value=' + newID + ` title=${newRepr} >` + newRepr + '</option>').removeAttr("selected").val(newID)
|
||||||
win.close();
|
win.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('drop', function (e) { e.preventDefault(); }, false);
|
document.addEventListener('drop', function (e) { e.preventDefault(); }, false);
|
||||||
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$("#add_examination").appendTo($("label[for='id_examination']"));
|
$("#add_examination").appendTo($("label[for='id_examination']"));
|
||||||
$("#add_body_part").appendTo($("label[for='id_body_part']"));
|
$("#add_body_part").appendTo($("label[for='id_body_part']"));
|
||||||
|
|
||||||
|
|
||||||
dropContainer = document.getElementById("drop-container");
|
dropContainer = document.getElementById("drop-container");
|
||||||
|
|
||||||
dropContainer.ondragover = function (evt) {
|
dropContainer.ondragover = function (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
dropContainer.ondragenter = function (evt) {
|
dropContainer.ondragenter = function (evt) {
|
||||||
$(evt.target).addClass("drop-target-active")
|
$(evt.target).addClass("drop-target-active")
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
dropContainer.ondragleave = function (evt) {
|
dropContainer.ondragleave = function (evt) {
|
||||||
$(evt.target).removeClass("drop-target-active")
|
$(evt.target).removeClass("drop-target-active")
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
dropContainer.ondrop = function (evt) {
|
dropContainer.ondrop = function (evt) {
|
||||||
console.log("drop event, ", evt);
|
console.log("drop event, ", evt);
|
||||||
$(evt.target).removeClass("drop-target-active");
|
$(evt.target).removeClass("drop-target-active");
|
||||||
|
|
||||||
// Get all input elements
|
// Get all input elements
|
||||||
input = document.getElementById("id_image");
|
input = document.getElementById("id_image");
|
||||||
//fileInput = document.getElementById("id_images-0-image");
|
//fileInput = document.getElementById("id_images-0-image");
|
||||||
|
|
||||||
f = evt.dataTransfer.files[0]
|
f = evt.dataTransfer.files[0]
|
||||||
|
|
||||||
let dT = new DataTransfer();
|
let dT = new DataTransfer();
|
||||||
dT.clearData();
|
dT.clearData();
|
||||||
dT.items.add(f);
|
dT.items.add(f);
|
||||||
|
|
||||||
input.files = dT.files;
|
input.files = dT.files;
|
||||||
|
|
||||||
ocr(input);
|
ocr(input);
|
||||||
|
|
||||||
// Make sure we have enough input targets
|
// Make sure we have enough input targets
|
||||||
//input_diff = (evt.dataTransfer.files.length - inputs.length)
|
//input_diff = (evt.dataTransfer.files.length - inputs.length)
|
||||||
@@ -119,50 +120,50 @@
|
|||||||
// dT.items.add(evt.dataTransfer.files[3]);
|
// dT.items.add(evt.dataTransfer.files[3]);
|
||||||
// fileInput.files = dT.files;
|
// fileInput.files = dT.files;
|
||||||
|
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
updateFileList();
|
updateFileList();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
$('#add_more').click(() => { add_input_form() });
|
$('#add_more').click(() => { add_input_form() });
|
||||||
|
|
||||||
function updateFileList() {
|
function updateFileList() {
|
||||||
$("#drop-filenames").empty()
|
$("#drop-filenames").empty()
|
||||||
$("#id_image").each((n, el) => {
|
$("#id_image").each((n, el) => {
|
||||||
console.log(el);
|
console.log(el);
|
||||||
if (el.files.length > 0) {
|
if (el.files.length > 0) {
|
||||||
extra_class = " image-ident-loading";
|
extra_class = " image-ident-loading";
|
||||||
if ($(el).hasClass("image-ident-warning")) {
|
if ($(el).hasClass("image-ident-warning")) {
|
||||||
extra_class = " image-ident-warning"
|
extra_class = " image-ident-warning"
|
||||||
} else if ($(el).hasClass("image-ident-ok")) {
|
} else if ($(el).hasClass("image-ident-ok")) {
|
||||||
extra_class = " image-ident-ok"
|
extra_class = " image-ident-ok"
|
||||||
|
}
|
||||||
|
$("#drop-filenames").append(
|
||||||
|
`<span>${n}: ${el.files[0].name}</span><img class='uploading${extra_class}' data-input-id='${el.id}' src=${URL.createObjectURL(el.files[0])}>`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
$("#drop-filenames").append(
|
})
|
||||||
`<span>${n}: ${el.files[0].name}</span><img class='uploading${extra_class}' data-input-id='${el.id}' src=${URL.createObjectURL(el.files[0])}>`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$("input[type=file]").on('change', function () {
|
$("input[type=file]").on('change', function () {
|
||||||
$(this).removeClass("image-ident-warning");
|
$(this).removeClass("image-ident-warning");
|
||||||
$(this).removeClass("image-ident-ok");
|
$(this).removeClass("image-ident-ok");
|
||||||
updateFileList();
|
updateFileList();
|
||||||
console.log("input change1");
|
console.log("input change1");
|
||||||
console.log("input change", this);
|
console.log("input change", this);
|
||||||
ocr(this);
|
ocr(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
//document.getElementById("id_question_type").selectedIndex = 1;
|
//document.getElementById("id_question_type").selectedIndex = 1;
|
||||||
answer_1_status = document.getElementById("id_answers-0-status").selectedIndex;
|
answer_1_status = document.getElementById("id_answers-0-status").selectedIndex;
|
||||||
if (answer_1_status == 0) {
|
if (answer_1_status == 0) {
|
||||||
document.getElementById("id_answers-0-status").selectedIndex = 3;
|
document.getElementById("id_answers-0-status").selectedIndex = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
function ocr(input) {
|
function ocr(input) {
|
||||||
// Tesseract.recognize(f, "eng",{ logger: m => console.log(m) }
|
// Tesseract.recognize(f, "eng",{ logger: m => console.log(m) }
|
||||||
// ).then(({ data: { text } }) => {
|
// ).then(({ data: { text } }) => {
|
||||||
// console.log(text);
|
// console.log(text);
|
||||||
@@ -172,79 +173,78 @@
|
|||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
|
|
||||||
const worker = Tesseract.createWorker({
|
const worker = Tesseract.createWorker({
|
||||||
workerPath: '{% static "worker.min.js" %}',
|
workerPath: '{% static "worker.min.js" %}',
|
||||||
langPath: '{% static "" %}',
|
langPath: '{% static "" %}',
|
||||||
//langPath: '{{ STATIC_URL }}',
|
//langPath: '{{ STATIC_URL }}',
|
||||||
corePath: '{% static "tesseract-core.wasm.js" %}',
|
corePath: '{% static "tesseract-core.wasm.js" %}',
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = URL.createObjectURL(input.files[0]);
|
const url = URL.createObjectURL(input.files[0]);
|
||||||
let img = new Image;
|
let img = new Image;
|
||||||
img.src = url;
|
img.src = url;
|
||||||
|
|
||||||
img.onload = function () {
|
img.onload = function () {
|
||||||
|
|
||||||
|
|
||||||
width = img.width;
|
width = img.width;
|
||||||
|
|
||||||
// const rectangle = { left: 0, top: 0, width: width, height: 10 }
|
// const rectangle = { left: 0, top: 0, width: width, height: 10 }
|
||||||
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await worker.load();
|
await worker.load();
|
||||||
await worker.loadLanguage('eng');
|
await worker.loadLanguage('eng');
|
||||||
await worker.initialize('eng');
|
await worker.initialize('eng');
|
||||||
// const { data: { text } } = await worker.recognize(input.files[0], { rectangle });
|
// const { data: { text } } = await worker.recognize(input.files[0], { rectangle });
|
||||||
const { data: { text } } = await worker.recognize(input.files[0]);
|
const { data: { text } } = await worker.recognize(input.files[0]);
|
||||||
//console.log(text);
|
//console.log(text);
|
||||||
l = text.toLowerCase();
|
l = text.toLowerCase();
|
||||||
$(`img[data-input-id='${input.id}'`).removeClass("image-ident-loading");
|
$(`img[data-input-id='${input.id}'`).removeClass("image-ident-loading");
|
||||||
console.log(l);
|
console.log(l);
|
||||||
if (l.includes("accesion") || l.includes("number") || l.search(/(ref|rk9|rh8|ra9|rbz)\d+/g) > -1) {
|
if (l.includes("accesion") || l.includes("number") || l.search(/(ref|rk9|rh8|ra9|rbz)\d+/g) > -1) {
|
||||||
console.log("SHIT", input);
|
console.log("SHIT", input);
|
||||||
$(input).addClass("image-ident-warning");
|
$(input).addClass("image-ident-warning");
|
||||||
$(`img[data-input-id='${input.id}'`).addClass("image-ident-warning");
|
$(`img[data-input-id='${input.id}'`).addClass("image-ident-warning");
|
||||||
} else {
|
} else {
|
||||||
$(input).addClass("image-ident-ok");
|
$(input).addClass("image-ident-ok");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await worker.terminate();
|
await worker.terminate();
|
||||||
})();
|
})();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{ form.media }}
|
{{ form.media }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% if object %}
|
{% if object %}
|
||||||
{% include 'anatomy/question_link_header.html' %}
|
{% include 'anatomy/question_link_header.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h2>{{object|yesno:"Update,Create"}} Question</h2>
|
<h2>{{object|yesno:"Update,Create"}} Question</h2>
|
||||||
<details class="help-text">
|
{% help "Help" %}
|
||||||
<summary><i class="bi bi-info-circle"></i> Help</summary>
|
<p>This form can be used to create an Anatomy question.</p>
|
||||||
<p>This form can be used to create an Anatomy question.</p>
|
|
||||||
|
|
||||||
<p>Question type can be selected from a predefined list. If you require a more please contact ross.kruger@nhs.net.</p>
|
<p>Question type can be selected from a predefined list. If you require a more please contact ross.kruger@nhs.net.</p>
|
||||||
|
|
||||||
<p>Correct and incorrect answers can be defined once the question has been created.</p>
|
<p>Correct and incorrect answers can be defined once the question has been created.</p>
|
||||||
</details>
|
{% endhelp %}
|
||||||
<form action="" method="post" enctype="multipart/form-data" id="anatomyquestion-form">
|
<form action="" method="post" enctype="multipart/form-data" id="anatomyquestion-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<a href="/anatomy/examination/create" id="add_examination" class="add-popup"
|
<a href="/anatomy/examination/create" id="add_examination" class="add-popup"
|
||||||
onclick="return showAddPopup(this);"><img src="{% static '/img/icon-addlink.svg' %}"></a>
|
onclick="return showAddPopup(this);"><img src="{% static '/img/icon-addlink.svg' %}"></a>
|
||||||
<a href="/anatomy/body_part/create" id="add_body_part" class="add-popup"
|
<a href="/anatomy/body_part/create" id="add_body_part" class="add-popup"
|
||||||
onclick="return showAddPopup(this);"><img src="{% static '/img/icon-addlink.svg' %}"></a>
|
onclick="return showAddPopup(this);"><img src="{% static '/img/icon-addlink.svg' %}"></a>
|
||||||
|
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<div id="drop-container" class="drop-target">Drop image here
|
<div id="drop-container" class="drop-target">Drop image here
|
||||||
<div id="drop-filenames"></div>
|
<div id="drop-filenames"></div>
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" class="submit-button" value="Submit" name="submit">
|
<input type="submit" class="submit-button" value="Submit" name="submit">
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
from django import template
|
||||||
|
from django.template import Node, TemplateSyntaxError, Variable
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
class HelpNode(Node):
|
||||||
|
def __init__(self, nodelist, title, icon, extra_class):
|
||||||
|
self.nodelist = nodelist
|
||||||
|
self.title = title
|
||||||
|
self.icon = icon
|
||||||
|
self.extra_class = extra_class
|
||||||
|
|
||||||
|
def resolve(self, val, context):
|
||||||
|
if isinstance(val, Variable):
|
||||||
|
try:
|
||||||
|
return val.resolve(context)
|
||||||
|
except Exception:
|
||||||
|
return ''
|
||||||
|
return val
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
title = self.resolve(self.title, context) or "Help"
|
||||||
|
icon = self.resolve(self.icon, context) or "bi bi-info-circle"
|
||||||
|
extra_class = self.resolve(self.extra_class, context) or "help-text"
|
||||||
|
body = self.nodelist.render(context)
|
||||||
|
return f'<details class="{extra_class}"><summary><i class="{icon}"></i> {title}</summary>{body}</details>'
|
||||||
|
|
||||||
|
@register.tag(name="help")
|
||||||
|
def do_help(parser, token):
|
||||||
|
bits = token.split_contents()
|
||||||
|
title = "Help"
|
||||||
|
icon = "bi bi-info-circle"
|
||||||
|
extra_class = "help-text"
|
||||||
|
|
||||||
|
for bit in bits[1:]:
|
||||||
|
if "=" in bit:
|
||||||
|
key, val = bit.split("=", 1)
|
||||||
|
if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")):
|
||||||
|
val = val[1:-1]
|
||||||
|
else:
|
||||||
|
val = Variable(val)
|
||||||
|
if key == "icon":
|
||||||
|
icon = val
|
||||||
|
elif key in ("class", "extra_class"):
|
||||||
|
extra_class = val
|
||||||
|
else:
|
||||||
|
raise TemplateSyntaxError(f"Unknown keyword for help tag: {key}")
|
||||||
|
else:
|
||||||
|
v = bit
|
||||||
|
if (v.startswith('"') and v.endswith('"')) or (v.startswith("'") and v.endswith("'")):
|
||||||
|
title = v[1:-1]
|
||||||
|
else:
|
||||||
|
title = Variable(v)
|
||||||
|
|
||||||
|
nodelist = parser.parse(("endhelp",))
|
||||||
|
parser.delete_first_token()
|
||||||
|
return HelpNode(nodelist, title, icon, extra_class)
|
||||||
+3
-2
@@ -545,11 +545,12 @@ class UserAnswer(UserAnswerBase):
|
|||||||
|
|
||||||
# If score is null then the answer is unmarked
|
# If score is null then the answer is unmarked
|
||||||
score = models.IntegerField(
|
score = models.IntegerField(
|
||||||
default=0, blank=True, validators=[MinValueValidator(0), MaxValueValidator(5)], null=True
|
default=0, blank=True, validators=[MinValueValidator(0), MaxValueValidator(5)], null=True,
|
||||||
|
help_text="Score for the answer. If null then the answer is unmarked. This should be number 0-5.",
|
||||||
)
|
)
|
||||||
|
|
||||||
candidate_feedback = models.TextField(
|
candidate_feedback = models.TextField(
|
||||||
null=True, blank=True, help_text="Feedback for the candidate"
|
null=True, blank=True, help_text="Feedback for the candidate, this is optional but WILL be shown to the candidate."
|
||||||
)
|
)
|
||||||
|
|
||||||
class CallStateOptions(models.TextChoices):
|
class CallStateOptions(models.TextChoices):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{% extends 'shorts/exams.html' %}
|
{% extends 'shorts/exams.html' %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
|
{% load help_tags %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<a href="{% url 'shorts:exam_question_detail' exam.id question_details.current|add:'-1' %}" title="View the Question">View</a>
|
<a href="{% url 'shorts:exam_question_detail' exam.id question_details.current|add:'-1' %}" title="View the Question">View</a>
|
||||||
@@ -7,47 +8,153 @@
|
|||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
<a href="{% url 'admin:shorts_question_change' question.id %}" title="Edit the Question using the admin interface">Admin
|
<a href="{% url 'admin:shorts_question_change' question.id %}" title="Edit the Question using the admin interface">Admin
|
||||||
Edit</a> <a href="{% url 'admin:shorts_useranswer_change' answer.id %}"
|
Edit</a> <a href="{% url 'admin:shorts_useranswer_change' answer.id %}"
|
||||||
title="Edit the user answer using the admin interface">Admin
|
title="Edit the user answer using the admin interface">Admin
|
||||||
Edit (user answer)</a>
|
Edit (user answer)</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h2>Marking question <a href="{% url 'shorts:mark' exam.id question_details.current|add:'-1' %}"
|
<h2>Marking question <a href="{% url 'shorts:mark' exam.id question_details.current|add:'-1' %}"
|
||||||
title="View question answers">{{question_details.current}}</a> of {{question_details.total}}</h2>
|
title="View question answers">{{question_details.current}}</a> of {{question_details.total}}</h2>
|
||||||
|
|
||||||
{% if discrepancy_form %}
|
{% if discrepancy_form %}
|
||||||
<div class="alert alert-info sticky-alert" role="alert">
|
<div class="alert alert-info sticky-alert" role="alert">
|
||||||
<details open>
|
<details open>
|
||||||
<summary><h3>This answer has discrepant scores</h3></summary>
|
<summary><h3>This answer has discrepant scores</h3></summary>
|
||||||
You can set the overall score here.<br/>
|
You can set the overall score here.<br/>
|
||||||
|
|
||||||
<form method="POST" class="post-form">{% csrf_token %}
|
<form method="POST" class="post-form">{% csrf_token %}
|
||||||
<input type="hidden" name="form_id" value="discrepancy_form">
|
<input type="hidden" name="form_id" value="discrepancy_form">
|
||||||
{{ discrepancy_form.as_p }}
|
{{ discrepancy_form.as_p }}
|
||||||
<button type="submit" name="save" class="" title="Save score">Save Score</button>
|
<button type="submit" name="save" class="" title="Save score">Save Score</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h4>Marker scores and reason</h4>
|
<h4>Marker scores and reason</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{% for mark_object in answer.mark.all %}
|
{% for mark_object in answer.mark.all %}
|
||||||
<li>{{mark_object.marker}}: {{mark_object.score}}<br/>
|
<li>{{mark_object.marker}}: {{mark_object.score}}<br/>
|
||||||
{{mark_object.mark_reason}}
|
{{mark_object.mark_reason}}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% help "Help" %}
|
||||||
|
<p>Marking guidance for the 2B exam can be viewed on the RCR site at <a href="https://www.rcr.ac.uk/exams-training/rcr-exams/clinical-radiology-exams/frcr-part-2b-radiology-cr2b/frcr-part-2b-radiology-cr2b-scoring-system/" target="_blank">here</a></p>
|
||||||
|
|
||||||
|
<p>For convienience the info is also copied below:</p>
|
||||||
|
<div class="alert alert-secondary" role="alert">
|
||||||
|
<h3>FRCR Part 2B Short Cases Marking Guidance</h3>
|
||||||
|
<p>
|
||||||
|
Candidates report on <b>25 cases</b> and have the opportunity to attain <b>five marks per case</b>, so a maximum of <b>125 marks</b> across the exam. <b>No half marks</b> will be allocated. Candidate responses are independently double marked and the final score awarded will be the average of the two examiner marks. The marking framework permits a difference of up to one mark between the two examiners. Any score difference of two or above will be flagged for review and discussion.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="#">Click here for short case example questions.</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
All questions include question-specific marking guidance to indicate key findings, diagnosis and recommended onward management. Examiners will use the following mark scheme descriptors for examiners alongside the marking guidance to score candidates’ responses:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table class="table table-bordered table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Score</th>
|
||||||
|
<th>Marking descriptors</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><b>5</b></td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<li>Detects all major findings and most minor observations.</li>
|
||||||
|
<li>Makes correct diagnosis or differential diagnoses using clinical information provided.</li>
|
||||||
|
<li>Makes safe further management plans with clear recommendations (including MDT or specialist referral if appropriate).</li>
|
||||||
|
<li>Demonstrates a clear understanding of likely further management required by different MDTs.</li>
|
||||||
|
<li>Report is clear, logical and concise.</li>
|
||||||
|
<li>May demonstrate knowledge beyond the core curriculum.</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>4</b></td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<li>Detects most major findings (including those marked essential) and most minor observations.</li>
|
||||||
|
<li>Makes correct diagnosis or differential diagnoses using clinical information provided.</li>
|
||||||
|
<li>Makes safe further management plans with clear recommendations (including MDT or specialist referral if appropriate).</li>
|
||||||
|
<li>Report is clear, logical and concise.</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>3</b></td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<li>Detects most major findings (including those marked essential) and some minor observations.</li>
|
||||||
|
<li>May or may not reach the correct diagnosis but demonstrates sensible clinical reasoning.</li>
|
||||||
|
<li>Safe differential diagnoses offered.</li>
|
||||||
|
<li>May not know full management plan but demonstrates enough knowledge not to compromise patient safety.</li>
|
||||||
|
<li>Report is clear although it may not be ideally structured.</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>2</b></td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<li>Major/critical observations missed.</li>
|
||||||
|
<li>Undue significance placed on irrelevant or minor findings.</li>
|
||||||
|
<li>Limited understanding of pathology.</li>
|
||||||
|
<li>Fails to make correct diagnosis or provide reasonable differential diagnoses.</li>
|
||||||
|
<li>Failure to refer to appropriate MDT/clinical team or inappropriate over-investigation e.g. MRI and orthopaedic referral for benign bone lesions.</li>
|
||||||
|
<li>Unstructured, scattergun report.</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>1</b></td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<li>Major/critical observations missed.</li>
|
||||||
|
<li>Undue significance placed on minor or incidental findings.</li>
|
||||||
|
<li>Demonstrates no understanding of pathology.</li>
|
||||||
|
<li>Inappropriate patient management (including over-investigation).</li>
|
||||||
|
<li>Incoherent, rambling report.</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>0</b></td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<li>No answer provided or answer is entirely incorrect or inappropriate/unsafe patient management.</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
An Angoff standard setting process is used to set the pass mark for each sitting based on the content used.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Ideally each question will have individual marking guidance that will be shown below.</p>
|
||||||
|
{% endhelp %}
|
||||||
|
|
||||||
{% if not next_unmarked_id and not unmarked %}
|
{% if not next_unmarked_id and not unmarked %}
|
||||||
<div class="alert alert-info sticky-alert" role="alert">Success! Marking question complete. <br />
|
<div class="alert alert-info sticky-alert" role="alert">Success! Marking question complete. <br />
|
||||||
|
|
||||||
|
|
||||||
{% if exam.double_mark %}
|
{% if exam.double_mark %}
|
||||||
<a href="{% url 'shorts:mark_answer_override' exam_id=exam.pk question_number=question_details.current|add:'-1' answer_id=answer.id %}">Set final score</a><br/>
|
<a href="{% url 'shorts:mark_answer_override' exam_id=exam.pk question_number=question_details.current|add:'-1' answer_id=answer.id %}">Set final score</a><br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
Return to <a class="marking-link" href="{% url 'shorts:mark' exam.id question_details.current|add:'-1' %}">question
|
Return to <a class="marking-link" href="{% url 'shorts:mark' exam.id question_details.current|add:'-1' %}">question
|
||||||
overview</a>, <a class="marking-link" href="{% url 'shorts:mark_overview' pk=exam.pk %}">marking
|
overview</a>, <a class="marking-link" href="{% url 'shorts:mark_overview' pk=exam.pk %}">marking
|
||||||
overview</a><br />
|
overview</a><br />
|
||||||
{% if previous_answer_id %}
|
{% if previous_answer_id %}
|
||||||
<a href="{% url 'shorts:mark_answer' exam_id=exam.pk question_number=question_details.current|add:'-1' answer_id=previous_answer_id %}">Previous
|
<a href="{% url 'shorts:mark_answer' exam_id=exam.pk question_number=question_details.current|add:'-1' answer_id=previous_answer_id %}">Previous
|
||||||
candidate</a>
|
candidate</a>
|
||||||
@@ -60,22 +167,23 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<span>Marking Candidate: {{answer.get_candidate_masked}} ({{unmarked|length}} answer(s) left to mark)</span>
|
<span>Marking Candidate: {{answer.get_candidate_masked}} ({{unmarked|length}} answer(s) left to mark)</span>
|
||||||
|
|
||||||
<details closed>
|
<details closed>
|
||||||
<summary title="click to view/hide the question details">Question Details</summary>
|
<summary title="click to view/hide the question details">Question Details</summary>
|
||||||
<p class="pre-whitespace"><b>History:</b> {{ question.history }}</p>
|
<p class="pre-whitespace"><b>History:</b> {{ question.history }}</p>
|
||||||
<p class="pre-whitespace"><b>Region:</b> {{ question.get_regions }}</p>
|
<p class="pre-whitespace"><b>Region:</b> {{ question.get_regions }}</p>
|
||||||
<p class="pre-whitespace"><b>Examination:</b> {{ question.get_examinations }}</p>
|
<p class="pre-whitespace"><b>Examination:</b> {{ question.get_examinations }}</p>
|
||||||
<p class="pre-whitespace"><b>Abnormality:</b> {{ question.get_abnormalities }}</p>
|
<p class="pre-whitespace"><b>Abnormality:</b> {{ question.get_abnormalities }}</p>
|
||||||
<div class="pre-whitespace multi-image-block"><b>Images:</b>
|
<div class="pre-whitespace multi-image-block"><b>Images:</b>
|
||||||
{% for image in question.images.all %}
|
{% for image in question.images.all %}
|
||||||
<span class="image-block">
|
<span class="image-block">
|
||||||
Image {{ forloop.counter }}{% if image.description %} ({{image.description}}){% endif %}{% if image.feedback_image %} [feedback image]{% endif %}:
|
Image {{ forloop.counter }}{% if image.description %} ({{image.description}}){% endif %}{% if image.feedback_image %} [feedback image]{% endif %}:
|
||||||
<div class="dicom-image shorts-img {% if image.feedback_image %}feedback-img{% endif %}"
|
<div class="dicom-image shorts-img {% if image.feedback_image %}feedback-img{% endif %}"
|
||||||
data-url="{{ remote_url }}{{ image.image.url}}"></div>
|
data-url="{{ remote_url }}{{ image.image.url}}"></div>
|
||||||
</span>
|
</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<p class="pre-whitespace"><b>Feedback:</b> {{ question.feedback }}</p>
|
<p class="pre-whitespace"><b>Feedback:</b> {{ question.feedback }}</p>
|
||||||
</details>
|
</details>
|
||||||
<div class="marking-block">
|
<div class="marking-block">
|
||||||
<details open>
|
<details open>
|
||||||
@@ -98,7 +206,7 @@
|
|||||||
<button type="submit" name="save" class="save btn btn-default" title="Save answer">Save</button>
|
<button type="submit" name="save" class="save btn btn-default" title="Save answer">Save</button>
|
||||||
{% if next_unmarked_id %}
|
{% if next_unmarked_id %}
|
||||||
<button type="submit" name="next" class="save btn btn-default"
|
<button type="submit" name="next" class="save btn btn-default"
|
||||||
title="Save answer and move to next question">Next answer</button>
|
title="Save answer and move to next question">Next answer</button>
|
||||||
<!-- <button type="submit" name="skip" class="save btn btn-default">Skip</button> -->
|
<!-- <button type="submit" name="skip" class="save btn btn-default">Skip</button> -->
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if not unmarked %}
|
{% if not unmarked %}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
{% load django_htmx %}
|
{% load django_htmx %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
{% load auth_extras %}
|
{% load auth_extras %}
|
||||||
|
{% load help_tags %}
|
||||||
<html data-bs-theme="dark">
|
<html data-bs-theme="dark">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
|
|||||||
Reference in New Issue
Block a user