From 6f84bd7ac5feeb698576e89048d7e2b476ebc5a6 Mon Sep 17 00:00:00 2001 From: Tal Jacob Date: Sun, 1 Jun 2025 19:05:33 +0300 Subject: [PATCH 1/7] Add Specialty Tags For Quizzes Signed-off-by: Tal Jacob --- .../src/services/companies_service.ts | 83 ++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/nextstep-backend/src/services/companies_service.ts b/nextstep-backend/src/services/companies_service.ts index fa36bf5..a526cf3 100644 --- a/nextstep-backend/src/services/companies_service.ts +++ b/nextstep-backend/src/services/companies_service.ts @@ -35,6 +35,19 @@ function matchTags(text: string, tags: string[]): string[] { return Array.from(found); } +// Helper function to check if content matches any of the given terms +function hasMatchingTerms(content: string, terms: string[]): boolean { + const lowerContent = content.toLowerCase(); + return terms.some(term => lowerContent.includes(term.toLowerCase())); +} + +// Helper function to check if content matches any of the given terms in both English and Hebrew +function matchTagsBilingual(content: string, englishTerms: string[], hebrewTerms: string[]): boolean { + const lowerContent = content.toLowerCase(); + return englishTerms.some(term => lowerContent.includes(term.toLowerCase())) || + hebrewTerms.some(term => lowerContent.includes(term)); +} + const companyToCompanyData = (company: Document & ICompany): CompanyData => { return { ...company.toJSON(), @@ -95,11 +108,13 @@ const parseJobQuizzesFromJobHuntHtml = (htmlPath: string): CompanyData[] => { const content = article.find('.faq-content').html() || ''; const forum_link = article.find('.meta-faq-data-fields a[href]').attr('href') || ''; + // Generate tags: company, job role, technology, year, etc. Deduplicate. let quiz_tags = Array.from(new Set([ company_en_final, company_he_final, ...quiz_title.split(/\s+/), + 'SPECIALTY_GENERIC', // Add generic specialty tag to all quizzes ].filter(Boolean))); // Add matched predefined tags from title and content @@ -109,6 +124,39 @@ const parseJobQuizzesFromJobHuntHtml = (htmlPath: string): CompanyData[] => { ]); quiz_tags = Array.from(new Set([...quiz_tags, ...matched_tags])); + // Add specialty tags based on content analysis + const contentText = $(content).text().toLowerCase(); + + // Check for code-related content + const codeTerms = [ + 'code', 'programming', 'algorithm', 'function', 'class', 'method', 'variable', 'loop', 'condition', 'syntax', + 'קוד', 'תכנות', 'אלגוריתם', 'פונקציה', 'מחלקה', 'מתודה', 'משתנה', 'לולאה', 'תנאי', 'תחביר', + 'מבנה נתונים', 'אובייקט', 'מערך', 'רשימה', 'עץ', 'גרף', 'חיפוש', 'מיון', 'רקורסיה', 'סיבוכיות' + ]; + if (hasMatchingTerms(contentText, codeTerms)) { + quiz_tags.push('SPECIALTY_CODE'); + } + + // Check for system design content + const designTerms = [ + 'design', 'architecture', 'system', 'component', 'service', 'microservice', 'scalability', 'performance', 'database', 'api', + 'עיצוב', 'ארכיטקטורה', 'מערכת', 'רכיב', 'שירות', 'מיקרו-שירות', 'הרחבה', 'ביצועים', 'מסד נתונים', 'ממשק תכנות', + 'תשתית', 'עומסים', 'זמינות', 'שחזור', 'גיבוי', 'אבטחה', 'אימות', 'הרשאות', 'תקשורת', 'פרוטוקול' + ]; + if (hasMatchingTerms(contentText, designTerms)) { + quiz_tags.push('SPECIALTY_DESIGN'); + } + + // Check for specific technologies + const techTerms = [ + 'framework', 'library', 'technology', 'stack', 'tool', 'platform', 'language', 'framework', 'library', + 'מסגרת', 'ספרייה', 'טכנולוגיה', 'סט', 'כלי', 'פלטפורמה', 'שפה', 'מסגרת', 'ספרייה', + 'פיתוח', 'תשתית', 'סביבה', 'מערכת הפעלה', 'שרת', 'לקוח', 'דפדפן', 'מובייל', 'ענן', 'תשתית' + ]; + if (hasMatchingTerms(contentText, techTerms)) { + quiz_tags.push('SPECIALTY_TECHNOLOGIES'); + } + quizzes.push({ title: quiz_title, quiz_id, @@ -167,7 +215,6 @@ const parseJobQuizzesFromCompanyTablesHtml = (htmlPath: string): CompanyData[] = // In a real application, you might generate a UUID or use a counter. const quiz_id = Math.abs(quiz_title_full.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)); - // Extract process details const processDetailsRow = $table.find('td:contains("פרטים לגבי התהליך")').closest('tr'); const process_details = processDetailsRow.find('td[style*="font-size:14px;font-weight:normal;"]').text().trim(); @@ -200,6 +247,7 @@ const parseJobQuizzesFromCompanyTablesHtml = (htmlPath: string): CompanyData[] = company_he, jobRole, ...quiz_title.split(/\s+/), + 'SPECIALTY_GENERIC', // Add generic specialty tag to all quizzes ].filter(Boolean))); // Add matched predefined tags from title, process details, and interview questions @@ -210,6 +258,39 @@ const parseJobQuizzesFromCompanyTablesHtml = (htmlPath: string): CompanyData[] = ]); quiz_tags = Array.from(new Set([...quiz_tags, ...matched_tags])); + // Add specialty tags based on content analysis + const contentText = (process_details + ' ' + interview_questions).toLowerCase(); + + // Check for code-related content + const codeTerms = [ + 'code', 'programming', 'algorithm', 'function', 'class', 'method', 'variable', 'loop', 'condition', 'syntax', + 'קוד', 'תכנות', 'אלגוריתם', 'פונקציה', 'מחלקה', 'מתודה', 'משתנה', 'לולאה', 'תנאי', 'תחביר', + 'מבנה נתונים', 'אובייקט', 'מערך', 'רשימה', 'עץ', 'גרף', 'חיפוש', 'מיון', 'רקורסיה', 'סיבוכיות' + ]; + if (hasMatchingTerms(contentText, codeTerms)) { + quiz_tags.push('SPECIALTY_CODE'); + } + + // Check for system design content + const designTerms = [ + 'design', 'architecture', 'system', 'component', 'service', 'microservice', 'scalability', 'performance', 'database', 'api', + 'עיצוב', 'ארכיטקטורה', 'מערכת', 'רכיב', 'שירות', 'מיקרו-שירות', 'הרחבה', 'ביצועים', 'מסד נתונים', 'ממשק תכנות', + 'תשתית', 'עומסים', 'זמינות', 'שחזור', 'גיבוי', 'אבטחה', 'אימות', 'הרשאות', 'תקשורת', 'פרוטוקול' + ]; + if (hasMatchingTerms(contentText, designTerms)) { + quiz_tags.push('SPECIALTY_DESIGN'); + } + + // Check for specific technologies + const techTerms = [ + 'framework', 'library', 'technology', 'stack', 'tool', 'platform', 'language', 'framework', 'library', + 'מסגרת', 'ספרייה', 'טכנולוגיה', 'סט', 'כלי', 'פלטפורמה', 'שפה', 'מסגרת', 'ספרייה', + 'פיתוח', 'תשתית', 'סביבה', 'מערכת הפעלה', 'שרת', 'לקוח', 'דפדפן', 'מובייל', 'ענן', 'תשתית' + ]; + if (hasMatchingTerms(contentText, techTerms)) { + quiz_tags.push('SPECIALTY_TECHNOLOGIES'); + } + // Find if company already exists to add the quiz to it let existingCompany = companies.find(c => c.company === company_en && c.company_he === company_he); From a0616faa015a5ab037973bff57925b03c676eba4 Mon Sep 17 00:00:00 2001 From: Tal Jacob Date: Sun, 1 Jun 2025 19:48:56 +0300 Subject: [PATCH 2/7] Add Specialty Tags In generateQuiz Signed-off-by: Tal Jacob --- .../src/services/companies_service.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/nextstep-backend/src/services/companies_service.ts b/nextstep-backend/src/services/companies_service.ts index a526cf3..acc0707 100644 --- a/nextstep-backend/src/services/companies_service.ts +++ b/nextstep-backend/src/services/companies_service.ts @@ -548,7 +548,10 @@ export const searchQuizzesByTags = async (tags: string[]): Promise = /** * Generate a custom-tailored quiz on any subject, based on our real quiz database. * @param quizSubject The subject you want to generate a quiz for. - * Example: "Java Spring Boot Microservices Interview Questions" or "QA Automation Python" + * Example: "Java Spring Boot Microservices Interview Questions" or + * "QA Automation Python" or + * "Java Spring Boot Microservices Interview Questions SPECIALTY_CODE SPECIALTY_DESIGN SPECIALTY_TECHNOLOGIES" + * Note: The SPECIALTY_ tags are optional, and can be used to specify the specialty of the quiz. */ export const generateQuiz = async (quizSubject: string): Promise => { // Take the first 10 best matching quizzes. @@ -557,10 +560,25 @@ export const generateQuiz = async (quizSubject: string): Promise => { const GEN_QUIZ_SYSTEM_PROMPT = "You are an AI assistant specialized in generating personalized interview quiz content based on real-world interview data. Your goal is to create a new, well-structured interview quiz tailored to a user's specific search query, drawing insights and patterns from a provided set of actual interview quizzes."; + const userQuizSpecialties = quizSubject.split(' ') + .filter((tag: string) => tag.startsWith('SPECIALTY_')) + .map((tag: string) => tag.replace('SPECIALTY_', '')) + .filter((tag: string) => tag !== 'GENERIC') + .join(', '); + const prompt = ` **User Search Query:** ${quizSubject} +${userQuizSpecialties.length > 0 ? `**User Quiz Specialties:** +${userQuizSpecialties}` : ''} + +${userQuizSpecialties.length > 0 ? `**Specialty Tags Description:** +Added content analysis to detect and add the specialty tags: +CODE: Added when content contains code-related terms like 'code', 'programming', 'algorithm', 'function', 'class', 'method', 'variable', 'loop', 'condition', or 'syntax' +DESIGN: Added when content contains system design terms like 'design', 'architecture', 'system', 'component', 'service', 'microservice', 'scalability', 'performance', 'database', or 'api' +TECHNOLOGIES: Added when content contains technology-related terms like 'framework', 'library', 'technology', 'stack', 'tool', 'platform', 'language'` : ''} + **Relevant Real Quiz Data (JSON Array):** \`\`\`json ${quizzes} @@ -583,6 +601,7 @@ Content Details: answer_list: Crucially, parse the question_list string into an array of individual answers. Each element should be a distinct answer, corresponding to the questions. keywords: Extract 5-10 additional relevant technical or conceptual keywords that the user might find useful for preparation. interviewer_mindset: Describe the soft skills, characteristics, temperament, and professional attributes that an interviewer for this specific job role (based on the user's query and the context from real quizzes) would likely be looking for. Focus on traits that would give the applicant "extra points," such as straightforwardness, curiosity, social skills, professionalism, collaboration, communication (with colleagues, 3rd parties, customers), problem-solving approach, adaptability, initiative, attention to detail, etc. Aim for a paragraph or two. + ${userQuizSpecialties.length > 0 ? `specialty_tags: Define the quiz with specialty tags, as provided to you earlier, relevant technical or conceptual keywords that the user might find useful for preparation. The possible tags are: Code, Design, Technologies. The quiz should have at least one specialty tag.` : ''} **Desired Output Format (JSON)**: \`\`\`json @@ -598,7 +617,8 @@ Content Details: "question_list": ["string", "string", ...], "answer_list": ["string", "string", ...], "keywords": ["string", "string", ...], - "interviewer_mindset": "string" + "interviewer_mindset": "string", + ${userQuizSpecialties.length > 0 ? `"specialty_tags": ["string", "string", ...]` : ''} } \`\`\` @@ -608,6 +628,8 @@ Return ONLY the JSON, without any other text, so I could easily retrieve it. const aiResponse = await chatWithAI(GEN_QUIZ_SYSTEM_PROMPT, [prompt]); const parsed = JSON.parse(aiResponse.trim().replace("```json", "").replace("```", "")) as any; + parsed.specialty_tags = parsed.specialty_tags || []; + return parsed; } From 327ab38b939d9e49e4b8d8ea501aca500524de9a Mon Sep 17 00:00:00 2001 From: Tal Jacob Date: Sun, 1 Jun 2025 20:07:43 +0300 Subject: [PATCH 3/7] Added Quiz Specialties To Swagger Signed-off-by: Tal Jacob --- nextstep-backend/src/openapi/swagger.yaml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/nextstep-backend/src/openapi/swagger.yaml b/nextstep-backend/src/openapi/swagger.yaml index 7827a98..704b938 100644 --- a/nextstep-backend/src/openapi/swagger.yaml +++ b/nextstep-backend/src/openapi/swagger.yaml @@ -1282,6 +1282,7 @@ paths: - Quizzes summary: Generate a quiz for a certain subject. description: Generate a quiz for a certain subject. The quiz is generated based on our real-world quizzes database. + In the subject field, the SPECIALTY_ tags are optional, and can be used to specify the specialty of the quiz. security: - BearerAuth: [] requestBody: @@ -1295,8 +1296,9 @@ paths: properties: subject: type: string - description: The subject of the quiz to be generated (e.g., "Java Spring Boot Microservices") - example: "Java Spring Boot Microservices" + description: The subject of the quiz to be generated (e.g., "Java Spring Boot Microservices") or (e.g., "Java Spring Boot Microservices SPECIALTY_CODE SPECIALTY_DESIGN SPECIALTY_TECHNOLOGIES") + example: "Java Spring Boot Microservices SPECIALTY_CODE SPECIALTY_DESIGN SPECIALTY_TECHNOLOGIES" + note: The SPECIALTY_ tags are optional, and can be used to specify the specialty of the quiz. responses: '200': description: A generated quiz custom-tailored for the targeted subject. @@ -1595,6 +1597,7 @@ components: - answer_list - keywords - interviewer_mindset + - specialty_tags properties: _id: type: string @@ -1652,6 +1655,12 @@ components: type: string description: A description of the soft skills, characteristics, and professional attributes an interviewer looks for. example: "מחפשים מועמד עם סקרנות טכנולוגית גבוהה, יכולת פתרון בעיות יצירתית, תקשורת ברורה ותמציתית, וגישה פרואקטיבית לשיתוף פעולה בצוות." + specialty_tags: + type: array + description: Additional relevant technical or conceptual keywords that the user might find useful for preparation. + items: + type: string + example: ["Code", "Design", "Technologies"] AnsweredQuiz: type: object @@ -1668,6 +1677,7 @@ components: - answer_list - keywords - interviewer_mindset + - specialty_tags properties: _id: type: string @@ -1731,6 +1741,12 @@ components: type: string description: A description of the soft skills, characteristics, and professional attributes an interviewer looks for. example: "מחפשים מועמד עם סקרנות טכנולוגית גבוהה, יכולת פתרון בעיות יצירתית, תקשורת ברורה ותמציתית, וגישה פרואקטיבית לשיתוף פעולה בצוות." + specialty_tags: + type: array + description: Additional relevant technical or conceptual keywords that the user might find useful for preparation. + items: + type: string + example: ["Code", "Design", "Technologies"] GradedQuiz: type: object From ea2a5aa4faf6e972ad52bef19c3a3583d9b9da9d Mon Sep 17 00:00:00 2001 From: Tal Jacob Date: Sun, 1 Jun 2025 20:20:54 +0300 Subject: [PATCH 4/7] Add Quiz Specialty Inputs Signed-off-by: Tal Jacob --- nextstep-frontend/src/pages/Quiz.tsx | 56 +++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/nextstep-frontend/src/pages/Quiz.tsx b/nextstep-frontend/src/pages/Quiz.tsx index 6847b32..8fd67fa 100644 --- a/nextstep-frontend/src/pages/Quiz.tsx +++ b/nextstep-frontend/src/pages/Quiz.tsx @@ -16,6 +16,9 @@ import { Divider, Grid, Avatar, + FormGroup, + FormControlLabel, + Checkbox, } from '@mui/material'; import { Visibility, @@ -106,6 +109,15 @@ interface QuizState { const Quiz: React.FC = () => { const [searchParams] = useSearchParams(); const [subject, setSubject] = useState(searchParams.get('subject') || ''); + const [selectedSpecialties, setSelectedSpecialties] = useState<{ + code: boolean; + design: boolean; + technologies: boolean; + }>({ + code: false, + design: false, + technologies: false, + }); const [quiz, setQuiz] = useState(null); const [loading, setLoading] = useState(false); const [showAnswer, setShowAnswer] = useState<{ [key: number]: boolean }>({}); @@ -117,8 +129,15 @@ const Quiz: React.FC = () => { setQuiz(null); setQuizSubmitted(false); setShowAnswer({}); + + // Build the full subject with specialties + let fullSubject = subject; + if (selectedSpecialties.code) fullSubject += ' SPECIALTY_CODE'; + if (selectedSpecialties.design) fullSubject += ' SPECIALTY_DESIGN'; + if (selectedSpecialties.technologies) fullSubject += ' SPECIALTY_TECHNOLOGIES'; + try { - const response = await api.post(`${config.app.backend_url()}/quiz/generate`, { subject }); + const response = await api.post(`${config.app.backend_url()}/quiz/generate`, { subject: fullSubject }); const generatedQuestions: QuizStateQuestion[] = response.data.question_list.map((q: string, idx: number) => ({ originalQuestion: q, @@ -261,6 +280,41 @@ const Quiz: React.FC = () => { onKeyDown={e => e.key === 'Enter' && handleGenerateQuiz()} sx={{ mb: 2 }} /> + + {/* Specialty Selection */} + + Select Specialties (Optional): + + + setSelectedSpecialties(prev => ({ ...prev, code: e.target.checked }))} + /> + } + label="Code" + /> + setSelectedSpecialties(prev => ({ ...prev, design: e.target.checked }))} + /> + } + label="Design" + /> + setSelectedSpecialties(prev => ({ ...prev, technologies: e.target.checked }))} + /> + } + label="Technologies" + /> + +