Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions EDIT.MD
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ There is also a feedback page for practice assignments screen which is displayed

## Example protocol

There is an example protocol for students to follow in the PDF file [LabProtocol](LabProtocol.pdf). It is recommended that you customize it to your teaching class, but the protocol includes all the necessary details for students to follow to understand how to create gRNAs and where they can get the information to input into SciGrade.
There is an example protocol for students to follow in [LabProtocol.pdf](LabProtocol.pdf). It is recommended that you customize it to your teaching class, but the protocol includes details for students to follow to understand how to create guide RNAs (gRNAs) and where they can get the information to input into SciGrade.

## Marking algorithm

SciGrade marks the gRNA strand, PAM sequence, off-target score and the F1 and R1 primers. Using [markAnswers()](core/scripts/crispr_scripts.js), the student's input into the form will be used to determine the marking.
SciGrade validates the gRNA strand, Protospacer Adjacent Motif (PAM) sequence, off-target score, and the F1/R1 primers, and it scores gRNA sequence, PAM, off-target, and primer inputs in [markAnswers()](core/scripts/crispr_scripts.js).

The marking algorithm is fully contained in the client-side code and uses the reference data from [Benchling_gRNA_Outputs.json](core/data/Benchling_gRNA_Outputs.json) to validate answers.

Expand All @@ -32,7 +32,7 @@ Regarding the algorithm, student marks are dependent on 5 inputs:
4. F1 primer
5. R1 primer.

The cut site is used to calculate whether the students have put in a valid gRNA sequence and its corresponding inputs. If the gRNA sequence is not found within a set range the [Benchling gRNA Output results](core/data/Benchling_gRNA_Outputs.json), then it is not a valid gRNA. If the gRNA sequence is not valid, all their answers would be wrong.
The cut site is used to calculate whether the student-selected gRNA sequence includes the target position and satisfies the range checks in [core/scripts/crispr_scripts.js](core/scripts/crispr_scripts.js). If the gRNA sequence does not match a reference sequence in [core/data/Benchling_gRNA_Outputs.json](core/data/Benchling_gRNA_Outputs.json), the remaining validation steps are skipped.

The following image describes where student's marks come from in the marking algorithm.

Expand All @@ -48,7 +48,7 @@ Edit the code within [crispr_scripts.js](core/scripts/crispr_scripts.js). The fo
- `checkR1Primers(seq)` - Validates reverse primers
- `markAnswers()` - Assigns scores based on validation results

For off-target score thresholds, the optimal value is calculated using: `Min_optimal = Max_range - (Max_range * 0.2)` where `Max_range` is the highest possible off-target score for a gene. You can adjust this calculation or implement custom logic in the `checkOffTarget()` function.
For off-target score thresholds, the optimal value is calculated using `Min_optimal = Max_range - (Max_range * 0.2)` and the threshold is set to 80 when `Min_optimal` is greater than 80 or less than 35 in [checkOffTarget()](core/scripts/crispr_scripts.js).

## Adding new genes

Expand Down Expand Up @@ -90,3 +90,9 @@ If you have any questions or would like to get in contact with the maintainers,
Enjoy! :relaxed: :heart:

-[Alex](https://twitter.com/alexjsully)

## Related Documentation

- [docs/index.md](docs/index.md) - Documentation entry point
- [docs/guides/marking-algorithm.md](docs/guides/marking-algorithm.md) - Marking flow details
- [docs/guides/data-structures.md](docs/guides/data-structures.md) - JSON reference data
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ SciGrade is an online web-tool that allows students (or any user with access) to

## Getting Started

It is recommended that you use the web-version available at <https://scigrade.com/> but if you want to download and run the SciGrade locally, it is possible though it requires an internet connection to login and accesses the marking form and student information (exclusive for TA's and admins).
It is recommended that you use the web-version available at <https://scigrade.com/>. For local runs, the landing page [index.html](index.html) links to the runtime page [core/systemrun.html](core/systemrun.html), which initializes the practice flow by default and hides the account UI in the navigation.

## Browser Compatibilities

Expand Down
4 changes: 2 additions & 2 deletions core/scripts/APIandLibraries/jQuery/jquery.min.js

Large diffs are not rendered by default.

77 changes: 37 additions & 40 deletions core/scripts/crispr_scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ const listOfGenes = ["eBFP", "ACTN3", "HBB", "CCR5", "ANKK1", "APOE"];
let possible_gene = "eBFP";
let current_gene = "empty";

/**
* Purpose of this is to assign the current gene and check for errors
*/
/** Purpose of this is to assign the current gene and check for errors */
function select_Gene() {
if (possible_gene !== "" || possible_gene) {
current_gene = possible_gene;
Expand Down Expand Up @@ -198,9 +196,9 @@ function loadWork() {
}

/**
* @param {event} evt - Character key press
* @return {bool} - Returns true if number or dash, else false
* Determine if a number or dash key is pressed
* Determine if a number, dash, or period key is pressed.
* @param {Event} evt - Character key press event
* @returns {boolean} True if the key is a number, dash, or period
*/
function isNumberOrDashKey(evt) {
const charCode = evt.which ? evt.which : evt.keyCode;
Expand Down Expand Up @@ -384,7 +382,7 @@ function checkAnswers() {
// Check if the F1 primer matches the answer's input
checkF1Primers(document?.getElementById("sequence_input")?.value?.trim() || "");

// Check if the F1 primer matches the answer's input
// Check if the R1 primer matches the answer's input
checkR1Primers(document?.getElementById("sequence_input")?.value?.trim() || "");
}
}
Expand All @@ -398,9 +396,8 @@ let offtarget_dict = {};
let offtarget_dictParse = [];
let offtarget_Use = [];
/**
* Checks the off-target score if it is correct
* @param {int} score - The specificity score from possible_comparable_answers
* @return {bool} - Returns true if MAROffTarget is correct
* Checks whether the submitted off-target score matches the reference scoring rules.
* @param {number} score - Reference specificity score from the matched gRNA entry
*/
function checkOffTarget(score) {
// Reset variables:
Expand Down Expand Up @@ -481,9 +478,8 @@ function checkOffTarget(score) {

let possible_F1_primers = [];
/**
* Checks if the F1 primer is correct or not
* @param {String} seq - The gRNA sequence
* @return {bool} - Returns true if MARF1primers is correct
* Checks whether the F1 primer matches one of the generated candidates.
* @param {string} seq - The gRNA sequence
*/
function checkF1Primers(seq) {
// Reset variables:
Expand Down Expand Up @@ -516,9 +512,8 @@ const complementary_nt_dict = {
G: "C",
};
/**
* Checks if the R1 primer is correct or not
* @param {String} seq - The gRNA sequence
* @return {bool} - Returns true if MARR1primers is correct
* Checks whether the R1 primer matches one of the generated candidates.
* @param {string} seq - The gRNA sequence
*/
function checkR1Primers(seq) {
// Reset variables:
Expand All @@ -539,7 +534,8 @@ function checkR1Primers(seq) {
}

/**
* Creates a complementary sequence of the nucleotides
* Builds a complementary sequence from the provided nucleotides.
* Note: This function does not return the computed sequence.
*/
function createComplementarySeq(seq) {
let comp_seq = "";
Expand All @@ -551,8 +547,9 @@ function createComplementarySeq(seq) {
let studentMark = 0;
let studentMarkPercentage = 0;
const markTotal = 10;

/**
* Based on checkAnswers(), returns a float of a mark
* Calculates the student's mark and percentage based on the current marking state.
*/
function markAnswers() {
studentMark = 0;
Expand Down Expand Up @@ -806,9 +803,9 @@ function showFeedback() {

/**
* Determine whether an input form will be displayed or not
* @param {String} docCheck The DOM being checked against
* @param {String} checkFor The value of the DOM being used to check for
* @param {String} docDisplay The DOM what will toggle hidden visibility for
* @param {string} docCheck The DOM being checked against
* @param {string} checkFor The value of the DOM being used to check for
* @param {string} docDisplay The DOM what will toggle hidden visibility for
*/
function showNewInput(docCheck, checkFor, docDisplay) {
if (document.getElementById(String(docCheck)).value === String(checkFor)) {
Expand Down Expand Up @@ -1206,7 +1203,7 @@ function openAccountManagement() {

/**
* Update the choose user's options in the account management's change user type
* @param {String} domUser
* @param {string} domUser
*/
function UpdateChooseUser(domUser) {
ClearSelectOptions(domUser);
Expand All @@ -1219,9 +1216,9 @@ function UpdateChooseUser(domUser) {

/**
* Add more options to a select
* @param {String} domID The DOM ID in the HTML file for the select
* @param {String} optionsValue The value and ID for the options being added
* @param {String} optionsInner The InnerHTML for the options being added
* @param {string} domID The DOM ID in the HTML file for the select
* @param {string} optionsValue The value and ID for the options being added
* @param {string} optionsInner The InnerHTML for the options being added
*/
function AddToOptions(domID, optionsValue, optionsInner) {
const dom = document.getElementById(domID);
Expand All @@ -1233,7 +1230,7 @@ function AddToOptions(domID, optionsValue, optionsInner) {

/**
* Clear an select's options
* @param {String} domID The DOM ID in the HTML for the select
* @param {string} domID The DOM ID in the HTML for the select
*/
function ClearSelectOptions(domID) {
const dom = document.getElementById(domID);
Expand All @@ -1245,7 +1242,7 @@ function ClearSelectOptions(domID) {
let updatedListOfStudents = {};
/**
* Update the list of students available for a class
* @param {String} className The class for which the students belong to
* @param {string} className The class for which the students belong to
*/
function UpdateStudentList(className) {
updatedListOfStudents = {};
Expand All @@ -1259,8 +1256,8 @@ function UpdateStudentList(className) {

/**
* Change a DOM's innerHTML
* @param {String} domID DOM's ID that is being modified
* @param {String} changeTo The content of the innerHTML
* @param {string} domID DOM's ID that is being modified
* @param {string} changeTo The content of the innerHTML
*/
function ChangeDOMInnerhtml(domID, changeTo) {
document.getElementById(domID).innerHTML = changeTo;
Expand All @@ -1270,9 +1267,9 @@ const downloadIndexTable_start = "\t\t<tr>\n\t\t\t<th>Student Number</th>\n\t\t\
const downloadIndexTable_end = "\n\t\t</tr>\n";
let downloadIndexTable_fill = "";
/**
* Generated the base IndexTable for downloading JSON as CSV
* @param {String} whichIndexTable The string of which the index table start as, defaults as downloadIndexTable_start
* @param {boolean} SimpleComplex True is simple, false is complex
* Generates the base index table header used for CSV export.
* @param {string} whichIndexTable Placeholder parameter for compatibility
* @param {boolean} SimpleComplex Placeholder parameter for compatibility
*/
function generateRestOfIndexTable(whichIndexTable, SimpleComplex) {
whichIndexTable = downloadIndexTable_start;
Expand All @@ -1282,7 +1279,7 @@ function generateRestOfIndexTable(whichIndexTable, SimpleComplex) {

/**
* Generated a download button from JSON to CSV
* @param {String} whichClass Which class is being downloaded
* @param {string} whichClass Which class is being downloaded
* @param {boolean} whichType True is simple, false is complex
*/
function generateHiddenStudentDownload(whichClass, whichType) {
Expand Down Expand Up @@ -1322,11 +1319,11 @@ function generateHiddenStudentDownload(whichClass, whichType) {
}

/**
* Changes the input class value to new input
* @param {String} docCheck The DOM being checked against
* @param {String} checkFor The value of the DOM being used to check for
* @param {String} docChange The DOM what will have its value changed
* @param {String} trueChangeValueTo What to change value to
* Updates one input value when another input matches a target value.
* @param {string} docCheck The DOM ID to check
* @param {string} checkFor The value to compare against
* @param {string} docChange The DOM ID to update
* @param {string} trueChangeValueTo The value to apply when matched
*/
function changeInputClass(docCheck, checkFor, docChange, trueChangeValueTo) {
if (trueChangeValueTo === "" || trueChangeValueTo === undefined) {
Expand All @@ -1345,7 +1342,7 @@ let studentAnswers = `student_list.${studentParseNum}.${loadedMode}-${current_ge
let studentOutputs = `student_list.${studentParseNum}.${loadedMode}-${current_gene}-Outputs`;
let studentMarks = `student_list.${studentParseNum}.${loadedMode}-${current_gene}-Marks`;
/**
* Submit and sends the student's answers to the server
* Collects the student's answers, calculates marks, and triggers feedback display.
*/
function submitAnswers() {
all_answers = [];
Expand Down Expand Up @@ -1395,7 +1392,7 @@ function submitAnswers() {
/**
* Determine if a enter was pressed and if so, click a button
* @param {Event} event The key press
* @param {String} toClickButton Which button to click
* @param {string} toClickButton Which button to click
*/
function IfPressEnter(event, toClickButton) {
if (event.which === 13 || event.keyCode === 13) {
Expand Down
5 changes: 0 additions & 5 deletions core/scripts/crispr_scripts.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
/**
* Unit tests for crispr_scripts.js
* Tests isolated utility functions and logic
*/

describe("crispr_scripts.js - Utility Functions", () => {
// Test the isNumberOrDashKey function without DOM dependencies
describe("isNumberOrDashKey()", () => {
Expand Down
22 changes: 11 additions & 11 deletions core/scripts/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,20 @@

let student_reg_information;

/** Let users continue with practice application without logging in (true) (default false) */
/** Let users continue with practice application without logging in (default true) */
let continueWithoutLogin = true;

let checkStudentNum = false;
let studentNumber = 0;
let studentUmail;
let alreadyRegistered = false;
let classRegister;

/**
* Check to determine if the student is within
* @param {Num} student_num Student number
* @param {String} student_umail Student's email/uMail
* @return {bool} checkStudentNum - Whether the student is a student in the system or not
* Checks whether a student number and email match the class roster.
* @param {number} student_num Student number
* @param {string} student_umail Student email/uMail
*/

function checkStudentNumber(student_num, student_umail) {
alreadyRegistered = false;
checkStudentNum = false;
Expand Down Expand Up @@ -67,8 +66,8 @@ function checkStudentNumber(student_num, student_umail) {
}

/**
* Check to determine if the student is registered in the system
* @param {Num} student_num - Student number
* Checks whether a student number exists in the registration list.
* @param {number} student_NumVerify - Student number
*/
function loginVerify(student_NumVerify) {
alreadyRegistered = false;
Expand Down Expand Up @@ -103,8 +102,8 @@ function loginVerify(student_NumVerify) {
}

/**
* Which reg error shows
* @param {Num} whichOne - Number indicator for which error to show
* Shows a registration error message by code.
* @param {number} whichOne - Error code to display
*/
function showRegError(whichOne) {
if (whichOne === 1) {
Expand Down Expand Up @@ -201,8 +200,9 @@ function loadGeneContent() {
}

const changeLogin = '<i class="material-icons" style="font-size:inherit;">&#xE7FD;</i>';

/**
* Once users have registered OR logged in, the page dynamically generates the CRISPR assignment page
* Builds the selection UI and loads the reference data for the runtime page.
*/
async function redirectCRISPR() {
$("#mainContainer").empty();
Expand Down
5 changes: 0 additions & 5 deletions core/scripts/login.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
/**
* Unit tests for login.js
* Tests isolated utility functions and logic
*/

describe("login.js - Utility Functions", () => {
// Test the checkStudentNumber function logic
describe("checkStudentNumber()", () => {
Expand Down
2 changes: 1 addition & 1 deletion core/systemrun.html
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@

/**
* Handle changing navigation tabs between login and register
* @param {String} [which='login'] Which tab is being switched to (default login)
* @param {string} [which='login'] Which tab is being switched to (default login)
* @example <caption>Use this function to handle switching between the login and register content</caption>
* handleLoginTabChange('login')
* // returns null (does not return anything but rather un-hides the login content)
Expand Down
Loading