diff --git a/hebikani/hebikani.py b/hebikani/hebikani.py
index a644c67..136865b 100755
--- a/hebikani/hebikani.py
+++ b/hebikani/hebikani.py
@@ -829,6 +829,7 @@ def __init__(self, data):
super().__init__(data)
self._readings = None
self._auxiliary_readings = None
+ self._auxiliary_meanings = None
self._meanings = None
self._meaning_question = Question(self, QuestionType.MEANING)
self._reading_question = None
@@ -940,6 +941,20 @@ def meanings(self):
)
return self._meanings
+ @property
+ def auxiliary_meanings(self):
+ # Only works for vocabulary. We want to set kanji answer as inexact
+ if (
+ self.object == SubjectObject.VOCABULARY
+ and len(self.characters) == 1
+ and self._auxiliary_meanings is None
+ ):
+ component_subject_ids = self.data["data"]["component_subject_ids"]
+ kanji = Cache.get_subject(component_subject_ids[0])
+ if kanji:
+ self._auxiliary_meanings = kanji.meanings
+ return self._auxiliary_meanings
+
@property
def reading_question(self):
"""Get the reading question."""
@@ -1043,6 +1058,11 @@ def solve(self, inputed_answer: str, hard_mode: bool = False) -> AnswerType:
)
else:
_answer = self.subject.meanings.solve(inputed_answer)
+ if _answer == AnswerType.INCORRECT and self.subject.auxiliary_meanings:
+ _answer = self.subject.auxiliary_meanings.solve(inputed_answer)
+ _answer = (
+ AnswerType.INEXACT if _answer == AnswerType.CORRECT else _answer
+ )
if _answer == AnswerType.INCORRECT:
self.wrong_answer_count += 1
diff --git a/hebikani/typing.py b/hebikani/typing.py
index 1f4ca5b..906bdf1 100644
--- a/hebikani/typing.py
+++ b/hebikani/typing.py
@@ -31,6 +31,7 @@ class SubjectObject(enumerate):
RADICAL = "radical"
KANJI = "kanji"
VOCABULARY = "vocabulary"
+ KANA_VOCABULARY = "kana_vocabulary"
class Gender(enumerate):
diff --git a/tests/data.py b/tests/data.py
index 7b7e728..7e8243c 100644
--- a/tests/data.py
+++ b/tests/data.py
@@ -409,6 +409,243 @@
],
}
+get_subject_fresh_kanji_vocab = {
+ "object": "collection",
+ "url": "https://api.wanikani.com/v2/subjects?ids=850%2C2579",
+ "pages": {"per_page": 1000, "next_url": None, "previous_url": None},
+ "total_count": 2,
+ "data_updated_at": "2023-09-20T03:36:15.305537Z",
+ "data": [
+ {
+ "id": 850,
+ "object": "kanji",
+ "url": "https://api.wanikani.com/v2/subjects/850",
+ "data_updated_at": "2023-09-20T03:36:15.305537Z",
+ "data": {
+ "created_at": "2012-06-05T23:58:47.796331Z",
+ "level": 3,
+ "slug": "生",
+ "hidden_at": None,
+ "document_url": "https://www.wanikani.com/kanji/%E7%94%9F",
+ "characters": "生",
+ "meanings": [
+ {"meaning": "Life", "primary": True, "accepted_answer": True}
+ ],
+ "auxiliary_meanings": [],
+ "readings": [
+ {
+ "type": "onyomi",
+ "primary": True,
+ "reading": "せい",
+ "accepted_answer": True,
+ },
+ {
+ "type": "onyomi",
+ "primary": True,
+ "reading": "しょう",
+ "accepted_answer": True,
+ },
+ {
+ "type": "kunyomi",
+ "primary": False,
+ "reading": "い",
+ "accepted_answer": False,
+ },
+ {
+ "type": "kunyomi",
+ "primary": False,
+ "reading": "なま",
+ "accepted_answer": False,
+ },
+ {
+ "type": "kunyomi",
+ "primary": False,
+ "reading": "う",
+ "accepted_answer": False,
+ },
+ {
+ "type": "kunyomi",
+ "primary": False,
+ "reading": "は",
+ "accepted_answer": False,
+ },
+ {
+ "type": "kunyomi",
+ "primary": False,
+ "reading": "き",
+ "accepted_answer": False,
+ },
+ ],
+ "component_subject_ids": [210],
+ "amalgamation_subject_ids": [
+ 2576,
+ 2577,
+ 2578,
+ 2579,
+ 2653,
+ 2665,
+ 2788,
+ 2916,
+ 3407,
+ 3436,
+ 3449,
+ 3451,
+ 3473,
+ 3715,
+ 3806,
+ 3827,
+ 4059,
+ 4195,
+ 4498,
+ 4659,
+ 4969,
+ 5154,
+ 5326,
+ 5548,
+ 5571,
+ 6225,
+ 6346,
+ 6432,
+ 6471,
+ 6640,
+ 6903,
+ 6980,
+ 7189,
+ 7429,
+ 7500,
+ 7617,
+ 7671,
+ 7731,
+ 7738,
+ 8196,
+ 8668,
+ 8701,
+ 8745,
+ 9141,
+ ],
+ "visually_similar_subject_ids": [526, 654, 1887, 546, 503, 511],
+ "meaning_mnemonic": "The life radical and the life kanji are the same, so hopefully you now just know them both.",
+ "meaning_hint": "Study your radicals and you'll read good.",
+ "reading_mnemonic": "You can save life or take it away with your new saber (せい). This is the saber you got from doing the correct thing (remember?), and now you need to figure out how to use it with this life. So what do you do?",
+ "reading_hint": "Imagine having someone's life in the palm of your hands. It's up to you whether you protect it with your saber or... well... do the other thing sabers are for.",
+ "lesson_position": 50,
+ "spaced_repetition_system_id": 1,
+ },
+ },
+ {
+ "id": 2579,
+ "object": "vocabulary",
+ "url": "https://api.wanikani.com/v2/subjects/2579",
+ "data_updated_at": "2023-09-18T23:37:01.015481Z",
+ "data": {
+ "created_at": "2012-02-29T07:50:29.000000Z",
+ "level": 3,
+ "slug": "生",
+ "hidden_at": None,
+ "document_url": "https://www.wanikani.com/vocabulary/%E7%94%9F",
+ "characters": "生",
+ "meanings": [
+ {"meaning": "Fresh", "primary": True, "accepted_answer": True},
+ {"meaning": "Raw", "primary": False, "accepted_answer": True},
+ {"meaning": "Live", "primary": False, "accepted_answer": True},
+ ],
+ "auxiliary_meanings": [],
+ "readings": [
+ {"primary": True, "reading": "なま", "accepted_answer": True}
+ ],
+ "parts_of_speech": ["noun", "の adjective"],
+ "component_subject_ids": [850],
+ "meaning_mnemonic": "Normally a single kanji alone like this would mean the same thing as its parent kanji, but in the case of 生 it's a little different (but still related). When you think of something that has life, it's fresh or raw. Think sashimi or sushi, for example. You want that to be as close as possible to life as you can, so you eat it when it's fresh and raw. This word can also be used to describe a live event, like a concert or sporting event. \r\n\r\nYou'll see this word at the beginning of other words. For example, 生たまご is \"raw egg.\"",
+ "reading_mnemonic": "This uses one of the kun'yomi readings. Here's a mnemonic to help you remember it:\r\n\r\nAfter eating raw fish, my yoga teacher and I say namaste (なま) to the sushi chef, bowing low to show our appreciation.",
+ "context_sentences": [
+ {"en": "One draft beer, please.", "ja": "生ビール、一つ下さい。"},
+ {"en": "Are raw peppers tasty?", "ja": "生のパプリカは、おいしいですか?"},
+ {
+ "en": "Grandma has seen The Beatles in person.",
+ "ja": "おばあちゃんはビートルズを生でみたことがある。",
+ },
+ ],
+ "pronunciation_audios": [
+ {
+ "url": "https://files.wanikani.com/u93fhhk0hyq8ob2e28fz4e1kwknn",
+ "metadata": {
+ "gender": "male",
+ "source_id": 7144,
+ "pronunciation": "なま",
+ "voice_actor_id": 2,
+ "voice_actor_name": "Kenichi",
+ "voice_description": "Tokyo accent",
+ },
+ "content_type": "audio/webm",
+ },
+ {
+ "url": "https://files.wanikani.com/shgr3klx57ehp6x6nltpwwodgi0l",
+ "metadata": {
+ "gender": "female",
+ "source_id": 24080,
+ "pronunciation": "なま",
+ "voice_actor_id": 1,
+ "voice_actor_name": "Kyoko",
+ "voice_description": "Tokyo accent",
+ },
+ "content_type": "audio/webm",
+ },
+ {
+ "url": "https://files.wanikani.com/cx0jxv2xowp0726ahkfgjgj2vrar",
+ "metadata": {
+ "gender": "male",
+ "source_id": 7144,
+ "pronunciation": "なま",
+ "voice_actor_id": 2,
+ "voice_actor_name": "Kenichi",
+ "voice_description": "Tokyo accent",
+ },
+ "content_type": "audio/ogg",
+ },
+ {
+ "url": "https://files.wanikani.com/cew3bcc59rg5h3px9lnm78nph9nf",
+ "metadata": {
+ "gender": "male",
+ "source_id": 7144,
+ "pronunciation": "なま",
+ "voice_actor_id": 2,
+ "voice_actor_name": "Kenichi",
+ "voice_description": "Tokyo accent",
+ },
+ "content_type": "audio/mpeg",
+ },
+ {
+ "url": "https://files.wanikani.com/ppwiwn9q1xc3c9zozrxkox7o06kh",
+ "metadata": {
+ "gender": "female",
+ "source_id": 24080,
+ "pronunciation": "なま",
+ "voice_actor_id": 1,
+ "voice_actor_name": "Kyoko",
+ "voice_description": "Tokyo accent",
+ },
+ "content_type": "audio/mpeg",
+ },
+ {
+ "url": "https://files.wanikani.com/vdvg8qhcg3kwenjensadk3tg392c",
+ "metadata": {
+ "gender": "female",
+ "source_id": 24080,
+ "pronunciation": "なま",
+ "voice_actor_id": 1,
+ "voice_actor_name": "Kyoko",
+ "voice_description": "Tokyo accent",
+ },
+ "content_type": "audio/ogg",
+ },
+ ],
+ "lesson_position": 77,
+ "spaced_repetition_system_id": 1,
+ },
+ },
+ ],
+}
+
vocabulary_subject = {
"id": 2467,
"object": "vocabulary",
diff --git a/tests/test_hebikani.py b/tests/test_hebikani.py
index 22bf86a..8a97f8a 100644
--- a/tests/test_hebikani.py
+++ b/tests/test_hebikani.py
@@ -46,6 +46,7 @@
get_specific_subjects_next,
get_updated_subjects,
get_subject_without_utf_entry,
+ get_subject_fresh_kanji_vocab,
get_summary,
post_review,
vocab_katakana_equals_hiragna_subject,
@@ -345,8 +346,25 @@ def test_question_answer_values():
assert question.answer_values == "what"
-def test_use_kanji_reading_on_vocabulary():
+def test_use_kanji_meaning_on_vocabulary():
"""When using the meaning of a kanji in the vocabulary it should return inexact."""
+ kanji, vocabulary = [Subject(s) for s in get_subject_fresh_kanji_vocab["data"]]
+ Cache.subjects[kanji.id] = kanji
+ Cache.subjects[vocabulary.id] = vocabulary
+ question = Question(vocabulary, QuestionType.MEANING)
+ assert question.solve("Life") == AnswerType.INEXACT
+ assert question.solve("Fresh") == AnswerType.CORRECT
+ assert question.solve("Test") == AnswerType.INCORRECT
+
+ question = Question(kanji, QuestionType.MEANING)
+ assert question.solve("Life") == AnswerType.CORRECT
+ assert question.solve("Fresh") == AnswerType.INCORRECT
+ assert question.solve("Test") == AnswerType.INCORRECT
+ Cache.subjects = {}
+
+
+def test_use_kanji_reading_on_vocabulary():
+ """When using the reading of a kanji in the vocabulary it should return inexact."""
kanji = Subject(subject_water_kanji)
Cache.subjects[kanji.id] = kanji
vocabulary = Subject(subject_water_vocabulary)
@@ -355,6 +373,7 @@ def test_use_kanji_reading_on_vocabulary():
assert question.solve("すい") == AnswerType.INEXACT
assert question.solve("みず") == AnswerType.CORRECT
assert question.solve("み") == AnswerType.INCORRECT
+ Cache.subjects = {}
def test_question_primary():
@@ -369,6 +388,8 @@ def test_question_primary():
def test_card_is_solved():
"""Test the card is solved."""
subject = Subject(vocabulary_subject)
+ # To avoid making a query online to get the kanji auxiliaries
+ Cache.subjects[440] = subject
assert subject.solved is False
subject.meaning_question.solve("one")
@@ -384,6 +405,7 @@ def test_card_is_solved():
subject.reading_question.solve("いち")
assert subject.solved is False
+ Cache.subjects = {}
def test_hard_mode():