diff --git a/package.json b/package.json
index 952afc369..f205a6726 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "qbreader",
- "version": "2.6.1",
+ "version": "2.6.2",
"scripts": {
"start": "node server/server.js",
"sass": "sass scss/light.scss client/bootstrap/light.css && sass scss/dark.scss client/bootstrap/dark.css"
diff --git a/server/quizbowl.js b/server/quizbowl.js
index 43fd21de5..6af4f7015 100644
--- a/server/quizbowl.js
+++ b/server/quizbowl.js
@@ -114,7 +114,14 @@ function parseAnswerline(answerline) {
}
-function stringMatchesReference(string, reference) {
+/**
+ *
+ * @param {String} string
+ * @param {String} reference
+ * @param {Number} strictness - the number of characters per error allowed for two tokens to match.
+ * @returns {Boolean}
+ */
+function stringMatchesReference(string, reference, strictness=4) {
if (string === null || string === undefined || reference === null || reference === undefined) {
return false;
}
@@ -127,9 +134,19 @@ function stringMatchesReference(string, reference) {
return string.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
+ const stemmer = (string) => {
+ if (string.charAt(string.length - 1) === 's') {
+ return string.substring(0, string.length - 1);
+ } else {
+ return string;
+ }
+ }
+
string = removePunctuation(string);
string = replaceSpecialCharacters(string);
string = string.toLowerCase().trim();
+ string = string.replace(/<\/?[biu]>/g, '');
+ string = string.replace(/<\/?em>/g, '');
string = string.replace('-', ' ');
reference = removePunctuation(reference);
@@ -143,12 +160,22 @@ function stringMatchesReference(string, reference) {
let stringTokens = string
.split(' ')
- .filter(token => !METAWORDS.includes(token) && token.length > 0)
- .map(token => isNaN(token) ? token : toWords(parseInt(token)));
+ .filter(token => !METAWORDS.includes(token) && token.length > 0);
+
+ for (let i = stringTokens.length - 1; i >= 0; i--) {
+ if (!isNaN(stringTokens[i])) {
+ stringTokens.push(toWords(parseInt(stringTokens[i])));
+ }
+ }
+
let referenceTokens = reference
.split(' ')
- .filter(token => !METAWORDS.includes(token) && token.length > 0)
- .map(token => isNaN(token) ? token : toWords(parseInt(token)));
+ .filter(token => !METAWORDS.includes(token) && token.length > 0);
+ for (let i = referenceTokens.length - 1; i >= 0; i--) {
+ if (!isNaN(referenceTokens[i])) {
+ referenceTokens.push(toWords(parseInt(referenceTokens[i])));
+ }
+ }
if (stringTokens.length === 0) {
return false;
@@ -163,11 +190,14 @@ function stringMatchesReference(string, reference) {
let tokenMatches = false;
for (let j = 0; j < referenceTokens.length; j++) {
- let errors = dljs.distance(stringTokens[i], referenceTokens[j]);
+ let errors = dljs.distance(stemmer(stringTokens[i]), stemmer(referenceTokens[j]));
- if (4 * errors <= referenceTokens[j].length) {
+ // console.log(stringTokens[i], referenceTokens[j]);
+ if (strictness * errors <= referenceTokens[j].length || referenceTokens[j].includes(stringTokens[i])) {
tokenMatches = true;
break;
+ } else {
+ // console.log(errors, stringTokens[j], referenceTokens[j]);
}
}
@@ -214,7 +244,7 @@ function checkAnswer(answerline, givenAnswer) {
const parsedAnswerline = parseAnswerline(answerline);
for (const answer of parsedAnswerline['reject']) {
- if (stringMatchesReference(answer[2], givenAnswer) && stringMatchesReference(givenAnswer, answer[2])) {
+ if (stringMatchesReference(answer[2], givenAnswer, 7) && stringMatchesReference(givenAnswer, answer[2], 7)) {
return 'reject';
}
}
@@ -237,6 +267,16 @@ function checkAnswer(answerline, givenAnswer) {
}
}
+ if (answerline.includes('[prompt on partial') || answerline.includes('(prompt on partial')) {
+ const [answer1, answer2] = parsedAnswerline.accept[0][0].split(' ');
+ if (answerWorks(answer1, givenAnswer, isFormattedAnswerline)) {
+ return 'prompt';
+ }
+ if (answerWorks(answer2, givenAnswer, isFormattedAnswerline)) {
+ return 'prompt';
+ }
+ }
+
return 'reject';
}
diff --git a/tests/quizbowl.test.js b/tests/quizbowl.test.js
index 8f1635f47..955c4ed6c 100644
--- a/tests/quizbowl.test.js
+++ b/tests/quizbowl.test.js
@@ -12,13 +12,24 @@ const formatted_answers = [
"Louis-Philippe [or Duke d’Orleans; prompt on “Citizen King” before mentioned]",
"Johann Tserclaes, Graf von Tilly (accept either underlined answer as well as Count of Tilly)",
"Paul Bäumer [accept either name]",
- "Matsuo Bashō [accept either underlined part; accept Matsuo Kinsaku or Matsuo Chūemon Munefusa]"
+ "Matsuo Bashō [accept either underlined part; accept Matsuo Kinsaku or Matsuo Chūemon Munefusa]",
+ "Prime Minister of Australia [prompt on partial answers]",
+ "hypothesis test",
+ "graphene [do not accept or prompt on \"graphite\"]",
+ "amides [do not accept or prompt on \"amines\"]",
+ "cosmic microwave background radiation [or CMB; or CMBR]",
+ "1980s [prompt on 80s]",
+ "working memory [prompt on partial answers or on “short-term memory”]",
+ "Pyotr Ilyich Tchaikovsky’s Piano Concerto No. 1 [accept Tchaikovsky’s PianoConcerto in B-flat minor until “B-flat” is read; accept word forms like Tchaikovsky’s first piano concerto; prompt on partial answer]",
];
const answers = [
"Heinrich Böll [or Heinrich Theodor Böll]",
"primatology [or word forms; accept any answers about the study of great apes, nonhuman primates, gorillas, bonobos, or chimpanzees; prompt on the study of monkeys or simians; prompt on word forms of ethology, biology, anthropology, or evolutionary or social psychology; prompt on the study of animals with “what type of animals?”]",
- "China [or People’s Republic of China; do not accept or prompt on “Republic of China”]"
+ "China [or People’s Republic of China; do not accept or prompt on “Republic of China”]",
+ "amides [do not accept or prompt on \"amines\"]",
+ "RAF [or Red Army Faction; accept Baader–Meinhof group; accept Baader–Meinhof gang; accept Rote Armee Fraktion] (The Action Directe communiqué was also signed “kommando elisabeth van dyck,” in reference to a fallen member of RAF.)",
+ "Lenski's longterm E. coli evolution experiment [accept anything mentioning the long term evolution of E. Coli]",
];
const tests = [
@@ -79,19 +90,66 @@ const tests = [
['accept', formatted_answers[11], 'Matsuo Basho'],
['accept', formatted_answers[11], 'Matsuo Bashō'],
+ ['accept', formatted_answers[12], 'prime minister of australia'],
+ ['accept', formatted_answers[12], 'australia prime minister'],
+ ['accept', formatted_answers[12], 'australian prime minister'],
+ ['prompt', formatted_answers[12], 'prime minister'],
+
+ ['accept', formatted_answers[13], 'hypothesis testing'],
+ ['accept', formatted_answers[13], 'testing'],
+ ['accept', formatted_answers[13], 'test'],
+
+ ['accept', formatted_answers[14], 'graphene'],
+ ['reject', formatted_answers[14], 'graphite'],
+
+ ['accept', formatted_answers[15], 'amides'],
+ ['accept', formatted_answers[15], 'amide'],
+ ['reject', formatted_answers[15], 'amine'],
+
+ ['accept', formatted_answers[16], 'cosmic microwave background radiation'],
+ ['accept', formatted_answers[16], 'cosmic microwave background'],
+ ['accept', formatted_answers[16], 'cmb'],
+ ['accept', formatted_answers[16], 'cmbr'],
+
+ ['accept', formatted_answers[17], '1980s'],
+ ['accept', formatted_answers[17], '1980'],
+ ['prompt', formatted_answers[17], '80'],
+ ['prompt', formatted_answers[17], '80s'],
+ ['reject', formatted_answers[17], '90s'],
+ ['reject', formatted_answers[17], '90'],
+ // ['reject', formatted_answers[17], '1990'], // TODO
+ // ['reject', formatted_answers[17], '1990s'], // TODO
+
+ ['accept', formatted_answers[18], 'working memory'],
+ ['prompt', formatted_answers[18], 'memory'],
+
+ ['accept', formatted_answers[19], 'Tchaikovsky Piano Concerto no 1'],
+ // ['prompt', formatted_answers[19], 'Piano Concerto'], // TODO
+
['accept', answers[0], 'boll'],
['accept', answers[0], 'heinrich boll'],
['accept', answers[0], 'Böll'],
['accept', answers[0], 'Heinrich Böll'],
// unformatted answerlines
- ['reject', answers[1], 'chimp'], // TODO: make this accept
+ ['accept', answers[1], 'chimp'],
['accept', answers[1], 'chimpanzee'],
// reject clauses that are a subset of acceptable answer
['accept', answers[2], 'China'],
['accept', answers[2], 'people’s republic of China'],
['reject', answers[2], 'republic of china'],
+
+ ['accept', answers[3], 'amides'],
+ ['accept', answers[3], 'amide'],
+ ['reject', answers[3], 'amine'],
+
+ ['accept', answers[4], 'baader meinhof'],
+ ['accept', answers[4], 'raf'],
+ ['accept', answers[4], 'red army faction'],
+ ['accept', answers[4], 'red army'],
+
+ ['accept', answers[5], 'lenski long term e coli experiment'],
];
let successful = 0, total = 0;