Files
sena_db_api_layer/controllers/vocabController.js
silverpro89 2c7b4675a7 update
2026-01-26 20:23:08 +07:00

1147 lines
34 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const { Vocab, VocabMapping, VocabForm, VocabRelation } = require('../models');
const { Op } = require('sequelize');
/**
* @swagger
* components:
* schemas:
* Vocab:
* type: object
* required:
* - vocab_code
* - base_word
* - translation
* properties:
* vocab_id:
* type: integer
* description: Auto-generated vocab ID
* vocab_code:
* type: string
* description: Unique vocabulary code (e.g., vocab-001-eat)
* base_word:
* type: string
* description: Base form of the word
* translation:
* type: string
* description: Vietnamese translation
* difficulty_score:
* type: integer
* description: Difficulty level (1-10)
* category:
* type: string
* description: Category (e.g., Action Verbs, Nouns)
* images:
* type: array
* items:
* type: string
* description: Array of image URLs
* tags:
* type: array
* items:
* type: string
* description: Array of tags
* VocabComplete:
* allOf:
* - $ref: '#/components/schemas/Vocab'
* - type: object
* properties:
* mappings:
* type: array
* items:
* type: object
* properties:
* book_id:
* type: string
* grade:
* type: integer
* unit:
* type: integer
* lesson:
* type: integer
* form_key:
* type: string
* forms:
* type: object
* additionalProperties:
* type: object
* properties:
* text:
* type: string
* phonetic:
* type: string
* audio:
* type: string
* min_grade:
* type: integer
* relations:
* type: object
* properties:
* synonyms:
* type: array
* items:
* type: string
* antonyms:
* type: array
* items:
* type: string
*/
/**
* Create a new vocabulary entry with all related data
*/
exports.createVocab = async (req, res) => {
const transaction = await Vocab.sequelize.transaction();
try {
const {
vocab_code,
base_word,
translation,
attributes = {},
mappings = [],
forms = {},
relations = {},
syntax = {},
semantics = {},
constraints = {}
} = req.body;
// 1. Create main vocab entry
const vocab = await Vocab.create({
vocab_code,
base_word,
translation,
difficulty_score: attributes.difficulty_score || 1,
category: attributes.category,
images: attributes.images || [],
tags: attributes.tags || [],
syntax: syntax,
semantics: semantics,
constraints: constraints
}, { transaction });
// 2. Create curriculum mappings
if (mappings.length > 0) {
const mappingData = mappings.map(m => ({
vocab_id: vocab.vocab_id,
book_id: m.book_id,
grade: m.grade,
unit: m.unit,
lesson: m.lesson,
form_key: m.form_key,
context_note: m.context_note
}));
await VocabMapping.bulkCreate(mappingData, { transaction });
}
// 3. Create word forms
if (Object.keys(forms).length > 0) {
const formData = Object.entries(forms).map(([form_key, formInfo]) => ({
vocab_id: vocab.vocab_id,
form_key,
text: formInfo.text,
phonetic: formInfo.phonetic,
audio_url: formInfo.audio,
min_grade: formInfo.min_grade || 1,
description: formInfo.description
}));
await VocabForm.bulkCreate(formData, { transaction });
}
// 4. Create relations (synonyms and antonyms)
const relationData = [];
if (relations.synonyms && relations.synonyms.length > 0) {
relations.synonyms.forEach(word => {
relationData.push({
vocab_id: vocab.vocab_id,
relation_type: 'synonym',
related_word: word
});
});
}
if (relations.antonyms && relations.antonyms.length > 0) {
relations.antonyms.forEach(word => {
relationData.push({
vocab_id: vocab.vocab_id,
relation_type: 'antonym',
related_word: word
});
});
}
if (relationData.length > 0) {
await VocabRelation.bulkCreate(relationData, { transaction });
}
await transaction.commit();
// Fetch complete vocab data
const completeVocab = await getCompleteVocab(vocab.vocab_id);
res.status(201).json({
message: 'Vocabulary created successfully',
data: completeVocab
});
} catch (error) {
await transaction.rollback();
console.error('Error creating vocab:', error);
res.status(500).json({
message: 'Error creating vocabulary',
error: error.message
});
}
};
/**
* Get all vocabulary entries with pagination and filters
*/
exports.getAllVocabs = async (req, res) => {
try {
const {
page = 1,
limit = 20,
category,
grade,
book_id,
difficulty_min,
difficulty_max,
search,
include_relations = 'false'
} = req.query;
const offset = (page - 1) * limit;
const where = { is_active: true };
// Apply filters
if (category) {
where.category = category;
}
if (difficulty_min || difficulty_max) {
where.difficulty_score = {};
if (difficulty_min) where.difficulty_score[Op.gte] = parseInt(difficulty_min);
if (difficulty_max) where.difficulty_score[Op.lte] = parseInt(difficulty_max);
}
if (search) {
where[Op.or] = [
{ base_word: { [Op.like]: `%${search}%` } },
{ translation: { [Op.like]: `%${search}%` } },
{ vocab_code: { [Op.like]: `%${search}%` } }
];
}
// Build include array
const include = [
{
model: VocabMapping,
as: 'mappings',
required: false
},
{
model: VocabForm,
as: 'forms',
required: false
}
];
if (include_relations === 'true') {
include.push({
model: VocabRelation,
as: 'relations',
required: false
});
}
// Apply grade or book_id filter through mappings
if (grade || book_id) {
const mappingWhere = {};
if (grade) mappingWhere.grade = parseInt(grade);
if (book_id) mappingWhere.book_id = book_id;
include[0].where = mappingWhere;
include[0].required = true;
}
const { count, rows } = await Vocab.findAndCountAll({
where,
include,
limit: parseInt(limit),
offset: parseInt(offset),
order: [['vocab_code', 'ASC']],
distinct: true
});
// Format response
const formattedVocabs = rows.map(vocab => formatVocabResponse(vocab));
res.json({
message: 'Vocabularies retrieved successfully',
data: formattedVocabs,
pagination: {
total: count,
page: parseInt(page),
limit: parseInt(limit),
totalPages: Math.ceil(count / limit)
}
});
} catch (error) {
console.error('Error getting vocabs:', error);
res.status(500).json({
message: 'Error retrieving vocabularies',
error: error.message
});
}
};
/**
* Get single vocabulary by ID or code
*/
exports.getVocabById = async (req, res) => {
try {
const { id } = req.params;
// Check if id is numeric (vocab_id) or string (vocab_code)
const where = isNaN(id)
? { vocab_code: id, is_active: true }
: { vocab_id: parseInt(id), is_active: true };
const vocab = await Vocab.findOne({
where,
include: [
{ model: VocabMapping, as: 'mappings' },
{ model: VocabForm, as: 'forms' },
{ model: VocabRelation, as: 'relations' }
]
});
if (!vocab) {
return res.status(404).json({
message: 'Vocabulary not found'
});
}
const formattedVocab = formatVocabResponse(vocab);
res.json({
message: 'Vocabulary retrieved successfully',
data: formattedVocab
});
} catch (error) {
console.error('Error getting vocab:', error);
res.status(500).json({
message: 'Error retrieving vocabulary',
error: error.message
});
}
};
/**
* Update vocabulary entry
*/
exports.updateVocab = async (req, res) => {
const transaction = await Vocab.sequelize.transaction();
try {
const { id } = req.params;
const {
base_word,
translation,
attributes,
mappings,
forms,
relations,
syntax,
semantics,
constraints
} = req.body;
// Find vocab
const where = isNaN(id) ? { vocab_code: id } : { vocab_id: parseInt(id) };
const vocab = await Vocab.findOne({ where });
if (!vocab) {
await transaction.rollback();
return res.status(404).json({
message: 'Vocabulary not found'
});
}
// 1. Update main vocab entry
const updateData = {};
if (base_word) updateData.base_word = base_word;
if (translation) updateData.translation = translation;
if (attributes) {
if (attributes.difficulty_score !== undefined) updateData.difficulty_score = attributes.difficulty_score;
if (attributes.category !== undefined) updateData.category = attributes.category;
if (attributes.images !== undefined) updateData.images = attributes.images;
if (attributes.tags !== undefined) updateData.tags = attributes.tags;
}
if (syntax !== undefined) updateData.syntax = syntax;
if (semantics !== undefined) updateData.semantics = semantics;
if (constraints !== undefined) updateData.constraints = constraints;
if (Object.keys(updateData).length > 0) {
await vocab.update(updateData, { transaction });
}
// 2. Update mappings (replace all)
if (mappings !== undefined) {
await VocabMapping.destroy({ where: { vocab_id: vocab.vocab_id }, transaction });
if (mappings.length > 0) {
const mappingData = mappings.map(m => ({
vocab_id: vocab.vocab_id,
book_id: m.book_id,
grade: m.grade,
unit: m.unit,
lesson: m.lesson,
form_key: m.form_key,
context_note: m.context_note
}));
await VocabMapping.bulkCreate(mappingData, { transaction });
}
}
// 3. Update forms (replace all)
if (forms !== undefined) {
await VocabForm.destroy({ where: { vocab_id: vocab.vocab_id }, transaction });
if (Object.keys(forms).length > 0) {
const formData = Object.entries(forms).map(([form_key, formInfo]) => ({
vocab_id: vocab.vocab_id,
form_key,
text: formInfo.text,
phonetic: formInfo.phonetic,
audio_url: formInfo.audio,
min_grade: formInfo.min_grade || 1,
description: formInfo.description
}));
await VocabForm.bulkCreate(formData, { transaction });
}
}
// 4. Update relations (replace all)
if (relations !== undefined) {
await VocabRelation.destroy({ where: { vocab_id: vocab.vocab_id }, transaction });
const relationData = [];
if (relations.synonyms && relations.synonyms.length > 0) {
relations.synonyms.forEach(word => {
relationData.push({
vocab_id: vocab.vocab_id,
relation_type: 'synonym',
related_word: word
});
});
}
if (relations.antonyms && relations.antonyms.length > 0) {
relations.antonyms.forEach(word => {
relationData.push({
vocab_id: vocab.vocab_id,
relation_type: 'antonym',
related_word: word
});
});
}
if (relationData.length > 0) {
await VocabRelation.bulkCreate(relationData, { transaction });
}
}
await transaction.commit();
// Fetch updated vocab
const updatedVocab = await getCompleteVocab(vocab.vocab_id);
res.json({
message: 'Vocabulary updated successfully',
data: updatedVocab
});
} catch (error) {
await transaction.rollback();
console.error('Error updating vocab:', error);
res.status(500).json({
message: 'Error updating vocabulary',
error: error.message
});
}
};
/**
* Delete vocabulary (soft delete)
*/
exports.deleteVocab = async (req, res) => {
try {
const { id } = req.params;
const where = isNaN(id) ? { vocab_code: id } : { vocab_id: parseInt(id) };
const vocab = await Vocab.findOne({ where });
if (!vocab) {
return res.status(404).json({
message: 'Vocabulary not found'
});
}
await vocab.update({ is_active: false });
res.json({
message: 'Vocabulary deleted successfully'
});
} catch (error) {
console.error('Error deleting vocab:', error);
res.status(500).json({
message: 'Error deleting vocabulary',
error: error.message
});
}
};
/**
* Get vocabularies by curriculum (book, grade, unit, lesson)
*/
exports.getVocabsByCurriculum = async (req, res) => {
try {
const { book_id, grade, unit, lesson } = req.query;
if (!book_id && !grade) {
return res.status(400).json({
message: 'At least book_id or grade is required'
});
}
const mappingWhere = {};
if (book_id) mappingWhere.book_id = book_id;
if (grade) mappingWhere.grade = parseInt(grade);
if (unit) mappingWhere.unit = parseInt(unit);
if (lesson) mappingWhere.lesson = parseInt(lesson);
const vocabs = await Vocab.findAll({
where: { is_active: true },
include: [
{
model: VocabMapping,
as: 'mappings',
where: mappingWhere,
required: true
},
{
model: VocabForm,
as: 'forms'
},
{
model: VocabRelation,
as: 'relations'
}
],
order: [['vocab_code', 'ASC']]
});
const formattedVocabs = vocabs.map(vocab => formatVocabResponse(vocab));
res.json({
message: 'Vocabularies retrieved successfully',
data: formattedVocabs,
count: formattedVocabs.length
});
} catch (error) {
console.error('Error getting vocabs by curriculum:', error);
res.status(500).json({
message: 'Error retrieving vocabularies',
error: error.message
});
}
};
/**
* Get vocabulary statistics
*/
exports.getVocabStats = async (req, res) => {
try {
const { sequelize } = Vocab;
// Total vocabs
const totalVocabs = await Vocab.count({ where: { is_active: true } });
// By category
const byCategory = await Vocab.findAll({
where: { is_active: true },
attributes: [
'category',
[sequelize.fn('COUNT', sequelize.col('vocab_id')), 'count']
],
group: ['category'],
raw: true
});
// By difficulty
const byDifficulty = await Vocab.findAll({
where: { is_active: true },
attributes: [
'difficulty_score',
[sequelize.fn('COUNT', sequelize.col('vocab_id')), 'count']
],
group: ['difficulty_score'],
order: [['difficulty_score', 'ASC']],
raw: true
});
// By grade (from mappings)
const byGrade = await VocabMapping.findAll({
attributes: [
'grade',
[sequelize.fn('COUNT', sequelize.fn('DISTINCT', sequelize.col('vocab_id'))), 'count']
],
group: ['grade'],
order: [['grade', 'ASC']],
raw: true
});
res.json({
message: 'Vocabulary statistics retrieved successfully',
data: {
total: totalVocabs,
by_category: byCategory,
by_difficulty: byDifficulty,
by_grade: byGrade
}
});
} catch (error) {
console.error('Error getting vocab stats:', error);
res.status(500).json({
message: 'Error retrieving statistics',
error: error.message
});
}
};
/**
* Get comprehensive guide for AI to create vocabulary entries
*/
exports.getVocabGuide = async (req, res) => {
try {
const guide = {
guide_version: "2.0.0",
last_updated: "2026-01-26",
description: "Comprehensive guide for AI to understand and create vocabulary entries for Grammar Engine",
data_structure: {
required_fields: {
vocab_code: {
type: "string",
format: "vocab-{sequence}-{base_word}",
example: "vocab-001-eat",
rule: "Must be unique, use 3-digit sequence number"
},
base_word: {
type: "string",
example: "eat",
rule: "Base form of the word in English"
},
translation: {
type: "string",
example: "ăn",
rule: "Vietnamese translation"
}
},
optional_fields: {
attributes: {
difficulty_score: {
type: "integer",
range: "1-10",
default: 1,
guide: "1-2: Basic, 3-4: Intermediate, 5-6: Advanced, 7-8: Difficult, 9-10: Expert"
},
category: {
type: "string",
options: ["Action Verbs", "Nouns", "Adjectives", "Adverbs", "Articles", "Pronouns", "Prepositions", "Conjunctions"],
example: "Action Verbs"
},
images: {
type: "array",
item_type: "string (URL)",
example: ["https://cdn.sena.tech/img/eat-main.png"]
},
tags: {
type: "array",
item_type: "string",
example: ["daily-routine", "verb", "food"]
}
},
mappings: {
type: "array of objects",
description: "Curriculum mapping - where this word appears in textbooks",
fields: {
book_id: { type: "string", example: "global-success-1" },
grade: { type: "integer", example: 1 },
unit: { type: "integer", example: 2 },
lesson: { type: "integer", example: 3 },
form_key: { type: "string", example: "v1", description: "Which form to use at this point" },
context_note: { type: "string", optional: true }
}
},
forms: {
type: "object",
description: "Different grammatical forms of the word",
structure: {
"{form_key}": {
text: { type: "string", example: "eat" },
phonetic: { type: "string", format: "IPA", example: "/iːt/" },
audio: { type: "string", format: "URL", example: "https://cdn.sena.tech/audio/eat_v1.mp3" },
min_grade: { type: "integer", example: 1, description: "Minimum grade to unlock" },
description: { type: "string", optional: true }
}
}
},
relations: {
type: "object",
fields: {
synonyms: { type: "array", example: ["consume", "dine"] },
antonyms: { type: "array", example: ["fast", "starve"] },
related: { type: "array", example: ["food", "meal"], optional: true }
}
},
syntax: {
type: "object",
description: "Syntax roles for Grammar Engine",
critical: true,
fields: {
is_subject: { type: "boolean", description: "Can be used as subject" },
is_verb: { type: "boolean", description: "Is a verb" },
is_object: { type: "boolean", description: "Can be used as object" },
is_be: { type: "boolean", description: "Is 'be' verb" },
is_adj: { type: "boolean", description: "Is adjective" },
is_adv: { type: "boolean", description: "Is adverb" },
is_article: { type: "boolean", description: "Is article (a/an/the)" },
is_pronoun: { type: "boolean", description: "Is pronoun" },
is_preposition: { type: "boolean", description: "Is preposition" },
verb_type: { type: "string", options: ["transitive", "intransitive", "linking"], when: "is_verb=true" },
article_type: { type: "string", options: ["definite", "indefinite"], when: "is_article=true" },
adv_type: { type: "string", options: ["manner", "frequency", "degree", "time", "place"], when: "is_adv=true" },
position: { type: "string", description: "Word position in sentence" },
priority: { type: "integer", description: "Selection priority for Grammar Engine" },
person: { type: "string", options: ["first", "second", "third"], when: "is_pronoun=true" },
number: { type: "string", options: ["singular", "plural"], when: "is_pronoun=true" }
}
},
semantics: {
type: "object",
description: "Semantic constraints to ensure meaningful sentences",
critical: true,
fields: {
can_be_subject_type: {
type: "array",
options: ["human", "animal", "object", "food", "plant", "abstract", "place", "time"],
when: "is_verb=true",
description: "What types can be subject with this verb"
},
can_take_object_type: {
type: "array",
options: ["human", "animal", "object", "food", "plant", "abstract", "place", "time"],
when: "verb_type=transitive",
description: "What types this verb can take as object"
},
can_modify: {
type: "array",
options: ["action_verb", "stative_verb", "be_verb", "adjective", "adverb", "noun"],
when: "is_adv=true",
description: "What this adverb can modify"
},
cannot_modify: {
type: "array",
options: ["action_verb", "stative_verb", "be_verb", "adjective", "adverb", "noun"],
when: "is_adv=true",
description: "What this adverb cannot modify"
},
word_type: {
type: "string",
options: ["action", "state", "entity", "property", "concept", "relation"],
required: true,
description: "Semantic type of the word"
},
is_countable: {
type: "boolean",
when: "is_object=true",
description: "Can this noun be counted"
},
person_type: {
type: "string",
options: ["1st", "2nd", "3rd"],
when: "is_pronoun=true"
}
}
},
constraints: {
type: "object",
description: "Grammar constraints for word combination",
fields: {
followed_by: {
type: "string",
options: ["vowel_sound", "consonant_sound", "any"],
when: "is_article=true",
description: "What sound type must follow"
},
match_subject: {
type: "object",
when: "is_be=true",
example: { "I": "am", "he": "is", "you": "are" },
description: "Subject-verb agreement rules"
},
match_with: {
type: "string",
description: "Must match with specific word type"
},
position_rules: {
type: "array",
description: "Possible positions in sentence"
},
requires_object: {
type: "boolean",
when: "verb_type=transitive"
}
}
}
}
},
form_keys_reference: {
verbs: {
v1: "Base form (eat, run)",
v_s_es: "Third person singular (eats, runs)",
v_ing: "Present participle (eating, running)",
v2: "Past simple (ate, ran)",
v3: "Past participle (eaten, run)"
},
nouns: {
n_singular: "Singular form (cat, apple)",
n_plural: "Plural form (cats, apples)"
},
adjectives: {
adj_base: "Base form (big, happy)",
adj_comparative: "Comparative (bigger, happier)",
adj_superlative: "Superlative (biggest, happiest)"
},
adverbs: {
adv_base: "Base form (quickly, slowly)",
adv_comparative: "Comparative (more quickly)",
adv_superlative: "Superlative (most quickly)"
},
pronouns: {
subject: "Subject pronoun (I, you, he)",
object: "Object pronoun (me, you, him)"
}
},
rules: {
vocab_code_format: "Must follow pattern: vocab-{3-digit-number}-{base_word}",
phonetic_format: "Use IPA notation enclosed in /slashes/",
audio_url_format: "Must be valid HTTPS URL pointing to MP3 or OGG file",
semantic_compatibility: {
rule: "Ensure semantic constraints create meaningful sentences",
examples: {
valid: "human eats food (human can eat, food can be eaten)",
invalid: "table eats book (table cannot eat, book is not food)"
}
},
article_selection: {
rule: "Use phonetic to determine a/an",
algorithm: "Check first phoneme: if vowel sound use 'an', else use 'a'",
examples: {
an: "apple (/ˈæp.əl/ starts with /æ/)",
a: "cat (/kæt/ starts with /k/)"
}
},
be_verb_agreement: {
rule: "Match be verb form with subject",
mapping: {
"I": "am",
"you": "are",
"he/she/it": "is",
"we/they": "are"
}
}
},
examples: {
transitive_verb: {
vocab_code: "vocab-001-eat",
base_word: "eat",
translation: "ăn",
attributes: {
difficulty_score: 1,
category: "Action Verbs",
tags: ["verb", "action", "daily-routine"]
},
forms: {
v1: { text: "eat", phonetic: "/iːt/", min_grade: 1 },
v_s_es: { text: "eats", phonetic: "/iːts/", min_grade: 2 },
v_ing: { text: "eating", phonetic: "/ˈiː.tɪŋ/", min_grade: 2 },
v2: { text: "ate", phonetic: "/eɪt/", min_grade: 3 }
},
syntax: {
is_verb: true,
verb_type: "transitive",
is_subject: false,
is_object: false
},
semantics: {
can_be_subject_type: ["human", "animal"],
can_take_object_type: ["food", "plant"],
word_type: "action"
},
constraints: {
requires_object: true
}
},
article: {
vocab_code: "vocab-art-01",
base_word: "a",
translation: "một (mạo từ bất định)",
attributes: {
difficulty_score: 1,
category: "Articles",
tags: ["article", "function-word", "grammar"]
},
forms: {
base: { text: "a", phonetic: "/ə/", min_grade: 1 }
},
syntax: {
is_article: true,
article_type: "indefinite",
priority: 1
},
constraints: {
followed_by: "consonant_sound"
}
},
adverb_manner: {
vocab_code: "vocab-adv-01",
base_word: "quickly",
translation: "nhanh chóng",
attributes: {
difficulty_score: 2,
category: "Adverbs",
tags: ["adverb", "manner"]
},
forms: {
base: { text: "quickly", phonetic: "/ˈkwɪk.li/", min_grade: 2 }
},
syntax: {
is_adv: true,
adv_type: "manner",
position: "after_verb"
},
semantics: {
can_modify: ["action_verb"],
cannot_modify: ["stative_verb", "be_verb"],
word_type: "property"
}
},
noun: {
vocab_code: "vocab-200-apple",
base_word: "apple",
translation: "quả táo",
attributes: {
difficulty_score: 1,
category: "Nouns",
tags: ["noun", "food", "fruit"]
},
forms: {
n_singular: { text: "apple", phonetic: "/ˈæp.əl/", min_grade: 1 },
n_plural: { text: "apples", phonetic: "/ˈæp.əlz/", min_grade: 1 }
},
syntax: {
is_subject: true,
is_object: true,
is_verb: false
},
semantics: {
word_type: "entity",
is_countable: true
}
},
pronoun: {
vocab_code: "vocab-pron-01",
base_word: "I",
translation: "tôi",
attributes: {
difficulty_score: 1,
category: "Pronouns",
tags: ["pronoun", "personal"]
},
forms: {
subject: { text: "I", phonetic: "/aɪ/", min_grade: 1 },
object: { text: "me", phonetic: "/miː/", min_grade: 1 }
},
syntax: {
is_subject: true,
is_pronoun: true,
pronoun_type: "personal",
person: "first",
number: "singular"
},
semantics: {
person_type: "1st",
word_type: "entity"
},
constraints: {
match_subject: { "I": "am" }
}
}
},
validation_checklist: {
before_submit: [
"✓ vocab_code follows format vocab-XXX-{word}",
"✓ All phonetic notations use IPA format with /slashes/",
"✓ At least one form is defined in 'forms' object",
"✓ syntax object has at least one role set to true",
"✓ semantics.word_type is specified",
"✓ If is_verb=true, verb_type is specified",
"✓ If verb_type=transitive, can_take_object_type is specified",
"✓ If is_article=true, constraints.followed_by is specified",
"✓ If is_adv=true, can_modify array is specified"
]
},
common_mistakes: [
{
mistake: "Not setting any syntax role",
fix: "Set at least one is_{role} to true"
},
{
mistake: "Using 'a' before vowel sound words",
fix: "Check phonetic - if starts with vowel sound, use 'an'"
},
{
mistake: "Transitive verb without can_take_object_type",
fix: "Specify what types this verb can take as object"
},
{
mistake: "Missing word_type in semantics",
fix: "Always specify: action, state, entity, property, concept, or relation"
},
{
mistake: "Incorrect phonetic format",
fix: "Use IPA notation: /iːt/ not 'eet' or 'eat'"
}
],
ai_tips: {
efficiency: "Create related words together (eat, eats, eating) to maintain consistency",
accuracy: "Double-check phonetic transcription using Cambridge Dictionary or similar",
completeness: "Fill all relevant fields - more data means better Grammar Engine performance",
testing: "After creating words, test sentence generation to verify semantic constraints work",
documentation: "Use descriptive context_note in mappings to help future AI understand usage"
}
};
res.json({
message: 'Vocabulary guide retrieved successfully',
data: guide
});
} catch (error) {
console.error('Error getting vocab guide:', error);
res.status(500).json({
message: 'Error retrieving guide',
error: error.message
});
}
};
// Helper functions
/**
* Get complete vocabulary data with all relations
*/
async function getCompleteVocab(vocab_id) {
const vocab = await Vocab.findByPk(vocab_id, {
include: [
{ model: VocabMapping, as: 'mappings' },
{ model: VocabForm, as: 'forms' },
{ model: VocabRelation, as: 'relations' }
]
});
return formatVocabResponse(vocab);
}
/**
* Format vocabulary response to match the expected structure
*/
function formatVocabResponse(vocab) {
const vocabJson = vocab.toJSON();
// Format attributes
const attributes = {
difficulty_score: vocabJson.difficulty_score,
category: vocabJson.category,
images: vocabJson.images || [],
tags: vocabJson.tags || []
};
// Format forms as object keyed by form_key
const forms = {};
if (vocabJson.forms) {
vocabJson.forms.forEach(form => {
forms[form.form_key] = {
text: form.text,
phonetic: form.phonetic,
audio: form.audio_url,
min_grade: form.min_grade,
description: form.description
};
});
}
// Format relations grouped by type
const relations = {
synonyms: [],
antonyms: [],
related: []
};
if (vocabJson.relations) {
vocabJson.relations.forEach(rel => {
if (rel.relation_type === 'synonym') {
relations.synonyms.push(rel.related_word);
} else if (rel.relation_type === 'antonym') {
relations.antonyms.push(rel.related_word);
} else if (rel.relation_type === 'related') {
relations.related.push(rel.related_word);
}
});
}
return {
id: vocabJson.vocab_code,
vocab_id: vocabJson.vocab_id,
base_word: vocabJson.base_word,
translation: vocabJson.translation,
attributes,
mappings: vocabJson.mappings || [],
forms,
relations,
syntax: vocabJson.syntax || {},
semantics: vocabJson.semantics || {},
constraints: vocabJson.constraints || {}
};
}