Ver Fonte

Added the necessary fields for the Assistant

victor_passos há 2 anos atrás
pai
commit
0d8a293957
1 ficheiros alterados com 295 adições e 242 exclusões
  1. 295 242
      js/assessment/output_matching/assessment_result.js

+ 295 - 242
js/assessment/output_matching/assessment_result.js

@@ -1,279 +1,332 @@
-import { Decimal } from "decimal.js";
-import { InputAssessment } from "../../util/input_assessment";
-import { OutputTest } from "../../util/outputTest";
-import { Config } from "../../util/config";
-import { levenshteinDistance } from "../../util/utils";
-import { OutputAssessmentResult } from "./assessment_result";
-import * as TypeParser from "./../../typeSystem/parsers";
-import * as LocalizedStringsService from "../../services/localizedStringsService";
-import * as OutputResult from "./output_result";
+import StringDiff from "./../../util/string_diff";
+import { LocalizedStrings } from "./../../services/localizedStringsService";
 
-const LocalizedStrings = LocalizedStringsService.getInstance();
+export class OutputAssessmentResult {
+  static get PAGE_TEMPLATE () {
+    return `<!DOCTYPE html>
+    <html>
+      <head>
+        <meta http-equiv='content-type' content='text/html; charset=UTF-8'>
+        <link rel='stylesheet' href='css/ivprog-assessment.css' type='text/css'/>
+      </head>
+    <body>
+      :assessment-result:
+    </body>
+    </html>`;
+  }
 
-export class OutputMatching {
-  static get NUM_REGEX () {
-    return /^[+-]?([0-9]+([.][0-9]*)?(e[+-]?[0-9]+)?)$/;
+  static get DETAIL_TEMPLATE () {
+    return `<div class='details-body'>
+        <div class='details-header'>
+          <h2>:test-name:</h2>
+          <p>:time-label:: <span>:time:ms</span></p>
+          <p>:grade-label:: <span>:grade:%</span></p>
+        </div>
+        <div>
+          <h3>:input-label:</h3>
+          <ul>
+            <li>:input-list:</li>
+          </ul>
+        </div>
+        <div>
+          <h3>:output-label:</h3>
+          :output-result:
+        </div>
+      </div>`;
   }
 
-  static get NUM_IN_STRING_REGEX () {
-    return /[+-]?([0-9]+([.][0-9]*)?(e[+-]?[0-9]+)?)/g;
+  static get ASSISTANT_TEMPLATE () {
+    return `<div class='details-body'>
+        <div class='assistant-div-left'>
+          <h1>Desempenho</h1>
+          <p style='padding-left:0.5rem;'>:performance-text:</p>
+        </div>
+        <div class='assistant-div-right'>
+          <h1>Sugestões</h1>
+          <p style='padding-left:0.5rem;'>:suggestion-text:</p>
+        </div>
+      </div>`;
   }
 
-  static get BOOLEAN_REGEX () {
-    const str = `^(${LocalizedStrings.getUI(
-      "logic_value_true"
-    )}|${LocalizedStrings.getUI("logic_value_false")})$`;
-    return new RegExp(str);
+  static get OUPUT_TABLE_TEMPLATE () {
+    return `<div class='detaisl-div-table'>
+        <table class='assessment-output-table'>
+          <tr>
+            <th>:expected-label:</th>
+            <th>:generated-label:</th>
+            <th>:result-label:</th>
+          </tr>
+          :results:
+        </table>
+      </div>`;
   }
 
-  static get BOOLEAN_IN_STRING_REGEX () {
-    const str = `(${LocalizedStrings.getUI(
-      "logic_value_true"
-    )}|${LocalizedStrings.getUI("logic_value_false")})`;
-    return new RegExp(str, "g");
+  static get OUTPUT_TEMPLATE () {
+    return `<tr><td class=':class-expected:'>$0</td>
+            <td class=':class-generated:'>$1</td>
+            <td class=':class-result:'>$2</td></tr>`;
   }
 
-  constructor (program, input_list, expected_output, test_name, tag = null) {
-    this.program = program;
-    this.name = test_name;
-    this.input_list = input_list;
-    this.expected_output = expected_output;
-    this.tag = tag;
+  static get EMPTY_OUTPUT_TEMPLATE () {
+    return `<div class='assessment-popup'><img class='assessment-empty-output' src='img/empty.svg'>
+      <span class='assessment-popuptext'>$0</span></div>`;
   }
 
-  eval () {
-    const input = new InputAssessment(this.input_list);
-    const gen_output = new OutputTest();
-    this.program.registerInput(input);
-    this.program.registerOutput(gen_output);
-    const start_time = Date.now();
-    return this.program
-      .interpretAST()
-      .then((sto) => {
-        const final_time = Date.now() - start_time;
-        if (input.isInputAvailable()) {
-          const error = this.getErrorMessage(
-            "test_case_few_reads",
-            this.name + 1
-          );
-          return new OutputAssessmentResult(
-            this.name,
-            1,
-            input.input_list,
-            null,
-            sto,
-            final_time,
-            this.tag,
-            error,
-            error.id
-          );
-        }
-        const result = gen_output.list.map((g_out, i) => {
-          if (i >= this.expected_output.length) {
-            return new OutputResult.OutputMatchResult(
-              null,
-              g_out,
-              0,
-              this.getPotentialOutputType(g_out)
-            );
-          }
-          return this.outputMatch(g_out, this.expected_output[i]);
-        }, this);
-        if (this.expected_output.length > gen_output.list.length) {
-          console.log(
-            "Saída insuficientes!",
-            this.expected_output.length,
-            gen_output.list.length
-          );
-          for (
-            let i = gen_output.list.length;
-            i < this.expected_output.length;
-            ++i
-          ) {
-            const e_out = this.expected_output[i];
-            result.push(
-              new OutputResult.OutputMatchResult(
-                e_out,
-                null,
-                0,
-                this.getPotentialOutputType(e_out)
-              )
-            );
-          }
-        } else if (
-          this.expected_output.length == 0 &&
-          this.expected_output.length == gen_output.list.length
-        ) {
-          // no output expected....
-          result.push(new OutputResult.OutputMatchResult("", "", 1, "string"));
-        }
-        return new OutputAssessmentResult(
-          this.name,
-          0,
-          input.input_list,
-          result,
-          sto,
-          final_time,
-          this.tag
-        );
-      })
-      .catch((error) => {
-        return new OutputAssessmentResult(
-          this.name,
-          1,
-          input.input_list,
-          null,
-          null,
-          null,
-          this.tags,
-          this.getErrorMessage(
-            "test_case_exception",
-            this.name + 1,
-            error.message
-          ),
-          error.id
-        );
-      });
+  static get FAILED_TEMPLATE () {
+    return `<p class='assessment-failed-execution'><span class='assessment-failed-case'>✗</span>$0</p>`;
   }
 
-  getPotentialOutputType (output) {
-    if (OutputMatching.NUM_REGEX.test(output)) {
-      return "number";
-    } else if (OutputMatching.BOOLEAN_REGEX.test(output)) {
-      return "bool";
-    } else {
-      return "string";
+  static get INPUT_INFO_TEMPLATE () {
+    return `<span class='$0'>$1</span>`;
+  }
+
+  // Status code - it is not grade related!
+  // 0 - Succesful execution
+  // 1 - failed execution
+  constructor (
+    name,
+    status,
+    inputs,
+    result,
+    store,
+    time,
+    tags,
+    error_msg = "",
+    error_id = ""
+  ) {
+    this.name = name;
+    this.status = status;
+    this.inputs = inputs;
+    this.results = result;
+    this.store = store;
+    this.time = time;
+    this.error_msg = error_msg;
+    this.tags = tags;
+    this.error_id = error_id;
+  }
+
+  get grade () {
+    if (this.results == null) {
+      return 0;
     }
+    return (
+      this.results.reduce((prev, val) => prev + val.grade, 0) /
+      this.results.length
+    );
   }
 
-  outputMatch (g_output, e_output) {
-    if (OutputMatching.NUM_REGEX.test(e_output)) {
-      if (!OutputMatching.NUM_REGEX.test(g_output)) {
-        return this.checkStrings(g_output, e_output);
-      }
-      const g_num = new Decimal(g_output);
-      const e_num = new Decimal(e_output);
-      return this.checkNumbers(g_num, e_num);
-    } else if (OutputMatching.BOOLEAN_REGEX.test(e_output)) {
-      if (!OutputMatching.BOOLEAN_REGEX.test(g_output)) {
-        return this.checkStrings(g_output, e_output);
-      }
-      const g_bool = TypeParser.toBool(g_output);
-      const e_bool = TypeParser.toBool(e_output);
-      return this.checkBoolean(g_bool, e_bool);
+  prepareResults () {
+    let template = OutputAssessmentResult.DETAIL_TEMPLATE;
+    const grade = (this.grade * 100).toFixed(2);
+    const time = this.time || "-";
+    template = template.replace(
+      ":test-name:",
+      LocalizedStrings.getUI("assessment-detail-title", [this.name + 1])
+    );
+    template = template.replace(
+      ":time-label:",
+      LocalizedStrings.getUI("assessment-detail-time-label")
+    );
+    template = template.replace(":time:", time);
+    template = template.replace(
+      ":grade-label:",
+      LocalizedStrings.getUI("assessment-detail-grade-label")
+    );
+    template = template.replace(":grade:", grade);
+    const input_spans = this.prepareInputList(this.inputs);
+    template = template.replace(
+      ":input-label:",
+      LocalizedStrings.getUI("assessment-detail-input-label")
+    );
+    template = template.replace(":input-list:", input_spans);
+    template = template.replace(
+      ":output-label:",
+      LocalizedStrings.getUI("assessment-detail-output-label")
+    );
+    if (this.status == 0) {
+      const output_rows = this.results.map((result) => {
+        if (result.type == "string") {
+          return this.formatString(result);
+        } else if (result.type == "number") {
+          return this.formatNumber(result);
+        } else {
+          return this.formatBool(result);
+        }
+      }, this);
+      template = template.replace(
+        ":output-result:",
+        this.prepareOutputTable(output_rows)
+      );
     } else {
-      return this.checkStrings(g_output, e_output);
+      let failed_text = OutputAssessmentResult.FAILED_TEMPLATE;
+      failed_text = failed_text.replace("$0", this.error_msg);
+      template = template.replace(":output-result:", failed_text);
     }
+    return template;
   }
 
-  checkNumbers (g_num, e_num) {
-    const decimalPlaces = Math.min(e_num.dp(), Config.decimalPlaces);
-    g_num = new Decimal(g_num.toFixed(decimalPlaces, Decimal.ROUND_FLOOR));
-    e_num = new Decimal(e_num.toFixed(decimalPlaces, Decimal.ROUND_FLOOR));
-    const result = g_num.eq(e_num);
-    const grade = result ? 1 : 0;
-    return OutputResult.createNumberResult(
-      e_num.toNumber(),
-      g_num.toNumber(),
-      grade
+  prepareInputList (input_list) {
+    const list = input_list.map((input) => {
+      let template = OutputAssessmentResult.INPUT_INFO_TEMPLATE;
+      template = template.replace("$1", input.value);
+      if (input.read) {
+        template = template.replace("$0", "assessment-input-read");
+      } else {
+        template = template.replace("$0", "assessment-input-unread");
+      }
+      return template;
+    }, this);
+    return list.join(LocalizedStrings.getUI("text_join_assessment_outputs"));
+  }
+
+  prepareOutputTable (output_rows) {
+    let template = OutputAssessmentResult.OUPUT_TABLE_TEMPLATE;
+    template = template.replace(
+      ":expected-label:",
+      LocalizedStrings.getUI("assessment-detail-expected-label")
     );
+    template = template.replace(
+      ":generated-label:",
+      LocalizedStrings.getUI("assessment-detail-generated-label")
+    );
+    template = template.replace(
+      ":result-label:",
+      LocalizedStrings.getUI("assessment-detail-result-label")
+    );
+    template = template.replace(":results:", output_rows.join(""));
+    return template;
   }
 
-  checkBoolean (g_bool, e_bool) {
-    const grade = g_bool == e_bool ? 1 : 0;
-    const g_bool_text = TypeParser.convertBoolToString(g_bool);
-    const e_bool_text = TypeParser.convertBoolToString(e_bool);
-    return OutputResult.createBoolResult(e_bool_text, g_bool_text, grade);
+  generateOutput () {
+    const assessment_result = this.prepareResults();
+    let page = OutputAssessmentResult.PAGE_TEMPLATE;
+    page = page.replace(":assessment-result:", assessment_result);
+    page = page.replace(/(\r|\n|\t)/gm, "").replace(/> *</g, "><");
+    return page;
   }
 
-  checkStrings (g_output, e_ouput) {
-    const assessmentList = [];
-    let e_output_clean = e_ouput.trim();
-    let g_output_clean = g_output.trim();
-    if (OutputMatching.NUM_IN_STRING_REGEX.test(e_ouput)) {
-      const expected_numbers = e_ouput.match(
-        OutputMatching.NUM_IN_STRING_REGEX
-      );
-      const generated_numbers =
-        g_output.match(OutputMatching.NUM_IN_STRING_REGEX) || [];
-      const result = generated_numbers.map((val, i) => {
-        if (i >= expected_numbers.length) {
-          return OutputResult.createNumberResult(null, val, 0);
-        }
-        const g_val = new Decimal(val);
-        const e_val = new Decimal(expected_numbers[i]);
-        return this.checkNumbers(g_val, e_val);
-      }, this);
-      if (expected_numbers.length > generated_numbers.length) {
-        for (
-          let i = generated_numbers.length;
-          i < expected_numbers.length;
-          ++i
-        ) {
-          result.push(
-            OutputResult.createNumberResult(expected_numbers[i], null, 0)
-          );
-        }
-      }
-      e_output_clean = e_output_clean.replace(
-        OutputMatching.NUM_IN_STRING_REGEX,
-        ""
+  generateAssistantOutput (performanceText,suggestionText) {
+    let template = OutputAssessmentResult.ASSISTANT_TEMPLATE;
+    template = template.replace(":performance-text:",performanceText);
+    template = template.replace(":suggestion-text:",suggestionText);
+    let page = OutputAssessmentResult.PAGE_TEMPLATE;
+    page = page.replace(":assessment-result:", template);
+    page = page.replace(/(\r|\n|\t)/gm, "").replace(/> *</g, "><");
+    return page;
+  }
+
+  formatNumber (result) {
+    const result_class =
+      result.grade == 1
+        ? "assessment-number-result"
+        : "assessment-number-result-failed";
+    const template = this.formatOutput(
+      "assessment-number-expected",
+      "assessment-number-generated",
+      result_class,
+      result
+    );
+    return template;
+  }
+
+  formatBool (result) {
+    const result_class =
+      result.grade == 1
+        ? "assessment-bool-result"
+        : "assessment-bool-result-failed";
+    const template = this.formatOutput(
+      "assessment-bool-expected",
+      "assessment-bool-generated",
+      result_class,
+      result
+    );
+    return template;
+  }
+
+  formatOutput (expected_class, generated_class, result_class, result) {
+    let template = OutputAssessmentResult.OUTPUT_TEMPLATE;
+    template = template.replace(":class-expected:", expected_class);
+    template = template.replace(":class-generated:", generated_class);
+    template = template.replace(":class-result:", result_class);
+    let expected_tmpl = result.expected;
+    let generated_tmpl = result.generated;
+    if (expected_tmpl == null) {
+      expected_tmpl = OutputAssessmentResult.EMPTY_OUTPUT_TEMPLATE.replace(
+        "$0",
+        LocalizedStrings.getMessage("assessment-empty-expected-tooltip")
       );
-      g_output_clean = g_output_clean.replace(
-        OutputMatching.NUM_IN_STRING_REGEX,
-        ""
+    } else if (generated_tmpl == null) {
+      generated_tmpl = OutputAssessmentResult.EMPTY_OUTPUT_TEMPLATE.replace(
+        "$0",
+        LocalizedStrings.getMessage("assessment-empty-generated-tooltip")
       );
-      const numberGrade =
-        result.reduce((prev, r) => prev + r.grade, 0) / result.length;
-      assessmentList.push(numberGrade);
     }
-    if (OutputMatching.BOOLEAN_IN_STRING_REGEX.test(e_ouput)) {
-      const expected_bools = e_ouput.match(
-        OutputMatching.BOOLEAN_IN_STRING_REGEX
+    template = template.replace("$0", expected_tmpl);
+    template = template.replace("$1", generated_tmpl);
+    const final_result = result.grade == 1 ? "✓" : "✗";
+    template = template.replace("$2", final_result);
+    return template;
+  }
+
+  formatString (result) {
+    const expected_class = "assessment-string-expected";
+    const generated_class = "assessment-string-generated";
+    //const result_class = 'assessment-string-result';
+
+    let template = OutputAssessmentResult.OUTPUT_TEMPLATE;
+    template = template.replace(":class-expected:", expected_class);
+    template = template.replace(":class-generated:", generated_class);
+    //template = template.replace(":class-result:", result_class);
+
+    const g_string = result.generated || "";
+    const e_string = result.expected || "";
+    // console.log("generated: ", g_string,"expected: ", e_string);
+    let g_string_tmpl = g_string;
+    let e_string_tmpl = e_string;
+    if (result.generated == null) {
+      g_string_tmpl = OutputAssessmentResult.EMPTY_OUTPUT_TEMPLATE.replace(
+        "$0",
+        LocalizedStrings.getMessage("assessment-empty-generated-tooltip")
       );
-      const generated_bools =
-        g_output.match(OutputMatching.BOOLEAN_IN_STRING_REGEX) || [];
-      const result = generated_bools.map((val, i) => {
-        if (i >= expected_bools.length) {
-          return OutputResult.createBoolResult(null, val, 0);
-        }
-        const g_bool = TypeParser.toBool(val);
-        const e_bool = TypeParser.toBool(expected_bools[i]);
-        return this.checkBoolean(g_bool, e_bool);
-      }, this);
-      if (expected_bools.length > generated_bools.length) {
-        for (let i = generated_bools.length; i < expected_bools.length; ++i) {
-          result.push(
-            OutputResult.createBoolResult(expected_bools[i], null, 0)
-          );
-        }
-      }
-      e_output_clean = e_output_clean.replace(
-        OutputMatching.BOOLEAN_IN_STRING_REGEX,
-        ""
+    } else if (result.expected == null) {
+      e_string_tmpl = OutputAssessmentResult.EMPTY_OUTPUT_TEMPLATE.replace(
+        "$0",
+        LocalizedStrings.getMessage("assessment-empty-expected-tooltip")
+      );
+    }
+    template = template.replace("$0", e_string_tmpl);
+    template = template.replace("$1", g_string_tmpl);
+    if (result.grade == 1) {
+      template = template.replace("$2", "✓");
+      template = template.replace(":class-result:", "assessment-string-result");
+    } else {
+      const diff = StringDiff(g_string, e_string);
+      // console.log(diff);
+      const diff_vec = diff.map(
+        (part) => this.getDiffStringStyle(part[1], part[0]),
+        this
       );
-      g_output_clean = g_output_clean.replace(
-        OutputMatching.BOOLEAN_IN_STRING_REGEX,
-        ""
+      const diff_string = diff_vec.reduce((prev, actual) => prev + actual, "");
+      template = template.replace(
+        "$2",
+        "<span class='assessment-failed-case'>✗</span>" + diff_string
       );
-      const boolGrade =
-        result.reduce((prev, r) => prev + r.grade, 0) / result.length;
-      assessmentList.push(boolGrade);
+      template = template.replace(":class-result:", "assessment-string-diff");
     }
-    const dist = levenshteinDistance(g_output_clean, e_output_clean);
-    let gradeDiff =
-      Math.max(0, e_output_clean.length - dist) / e_output_clean.length;
-    gradeDiff = Number.isNaN(gradeDiff) ? 0 : gradeDiff;
-    const assessment_size = assessmentList.length + 1;
-    const gradeAcc = assessmentList.reduce(
-      (prev, val) => prev + val / assessment_size,
-      0
-    );
-    const finalGrade = 1 * (gradeDiff / assessment_size + gradeAcc);
-    return OutputResult.createStringResult(e_ouput, g_output, finalGrade);
+    return template;
   }
 
-  getErrorMessage (errorID, ...args) {
-    return LocalizedStrings.getError(errorID, args);
+  getDiffStringStyle (text, action) {
+    const template = "<span class='$0'>$1</span>";
+    // Fix missing whitespace when its a single element
+    text = text.replace(/\s/g, "&#160;");
+    switch (action) {
+      case StringDiff.INSERT:
+        return template.replace("$0", "stringdiff-insert").replace("$1", text);
+      case StringDiff.DELETE:
+        return template.replace("$0", "stringdiff-delete").replace("$1", text);
+      case StringDiff.EQUAL:
+        return template.replace("$0", "stringdiff-equal").replace("$1", text);
+    }
   }
 }