feat: operator corrections injected into similar questions
This commit is contained in:
@@ -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.
|
||||||
|
*
|
||||||
|
* <p>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<Entry> 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<Entry> all() {
|
||||||
|
return List.copyOf(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Corrections sharing at least one significant word with the question. */
|
||||||
|
static List<Entry> matching(List<Entry> all, String question) {
|
||||||
|
Set<String> asked = significantWords(question);
|
||||||
|
List<Entry> out = new ArrayList<>();
|
||||||
|
for (Entry entry : all) {
|
||||||
|
Set<String> known = significantWords(entry.question());
|
||||||
|
known.retainAll(asked);
|
||||||
|
if (known.size() >= 1) {
|
||||||
|
out.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> significantWords(String text) {
|
||||||
|
Set<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Corrections.Entry> 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<Corrections.Entry> all = List.of(
|
||||||
|
new Corrections.Entry("como pegar um camelo", "…"));
|
||||||
|
assertTrue(Corrections.matching(all, "onde acho diamante?").isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shortWordsDoNotCreateMatches() {
|
||||||
|
List<Corrections.Entry> 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<Corrections.Entry> all = List.of(
|
||||||
|
new Corrections.Entry("como pegar um camelo", "ponha uma sela"));
|
||||||
|
assertEquals(1, Corrections.matching(all, "como eu pego camelo?").size());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user