From 5cfa5809975b209d9d7e64b9cc5f3596a5821096 Mon Sep 17 00:00:00 2001 From: marcos Date: Thu, 6 Aug 2026 03:09:11 +0000 Subject: [PATCH] feat: operator corrections injected into similar questions --- .../marcospaulo/canalhandia/Corrections.java | 89 +++++++++++++++++++ .../canalhandia/CorrectionsTest.java | 39 ++++++++ 2 files changed, 128 insertions(+) create mode 100644 src/main/java/dev/marcospaulo/canalhandia/Corrections.java create mode 100644 src/test/java/dev/marcospaulo/canalhandia/CorrectionsTest.java diff --git a/src/main/java/dev/marcospaulo/canalhandia/Corrections.java b/src/main/java/dev/marcospaulo/canalhandia/Corrections.java new file mode 100644 index 0000000..476291b --- /dev/null +++ b/src/main/java/dev/marcospaulo/canalhandia/Corrections.java @@ -0,0 +1,89 @@ +package dev.marcospaulo.canalhandia; + +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** + * Operator corrections, injected when a new question resembles one that was + * answered wrongly before. + * + *

This is the cheap alternative to fine-tuning: a wrong answer becomes + * context, so the same mistake stops recurring. + */ +final class Corrections { + + record Entry(String question, String answer) { + } + + private final File file; + private final List entries = new ArrayList<>(); + + Corrections(File file) { + this.file = file; + load(); + } + + void load() { + entries.clear(); + if (!file.exists()) { + return; + } + YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file); + for (String key : yaml.getKeys(false)) { + String question = yaml.getString(key + ".pergunta"); + String answer = yaml.getString(key + ".resposta"); + if (question != null && answer != null) { + entries.add(new Entry(question, answer)); + } + } + } + + void add(String question, String answer) { + entries.add(new Entry(question, answer)); + YamlConfiguration yaml = new YamlConfiguration(); + for (int i = 0; i < entries.size(); i++) { + yaml.set("c" + i + ".pergunta", entries.get(i).question()); + yaml.set("c" + i + ".resposta", entries.get(i).answer()); + } + try { + yaml.save(file); + } catch (Exception e) { + throw new IllegalStateException("não consegui gravar " + file, e); + } + } + + List all() { + return List.copyOf(entries); + } + + /** Corrections sharing at least one significant word with the question. */ + static List matching(List all, String question) { + Set asked = significantWords(question); + List out = new ArrayList<>(); + for (Entry entry : all) { + Set known = significantWords(entry.question()); + known.retainAll(asked); + if (known.size() >= 1) { + out.add(entry); + } + } + return out; + } + + private static Set significantWords(String text) { + Set words = new HashSet<>(); + for (String word : text.toLowerCase(Locale.ROOT).split("[^\\p{L}0-9]+")) { + // Short words are almost all articles and prepositions in Portuguese. + if (word.length() > 4) { + words.add(word); + } + } + return words; + } +} \ No newline at end of file diff --git a/src/test/java/dev/marcospaulo/canalhandia/CorrectionsTest.java b/src/test/java/dev/marcospaulo/canalhandia/CorrectionsTest.java new file mode 100644 index 0000000..ed80c92 --- /dev/null +++ b/src/test/java/dev/marcospaulo/canalhandia/CorrectionsTest.java @@ -0,0 +1,39 @@ +package dev.marcospaulo.canalhandia; + +import org.junit.jupiter.api.Test; +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; + +class CorrectionsTest { + + @Test + void matchesOnSharedSignificantWords() { + List all = List.of( + new Corrections.Entry("como pegar um camelo", + "camelos são mansos, basta pôr uma sela")); + assertEquals(1, Corrections.matching(all, "como eu pego camelo?").size()); + } + + @Test + void ignoresUnrelatedCorrections() { + List all = List.of( + new Corrections.Entry("como pegar um camelo", "…")); + assertTrue(Corrections.matching(all, "onde acho diamante?").isEmpty()); + } + + @Test + void shortWordsDoNotCreateMatches() { + List all = List.of( + new Corrections.Entry("o que e um creeper", "explode")); + assertTrue(Corrections.matching(all, "o que e um zumbi").isEmpty()); + } + + @Test + void singleSharedContentWordMatches() { + // "camelo" is the only word > 4 chars shared with the conjugated + // question; the threshold is one, not two, precisely for this case. + List all = List.of( + new Corrections.Entry("como pegar um camelo", "ponha uma sela")); + assertEquals(1, Corrections.matching(all, "como eu pego camelo?").size()); + } +} \ No newline at end of file