Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions nextstep-backend/src/openapi/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.
Expand Down Expand Up @@ -1595,6 +1597,7 @@ components:
- answer_list
- keywords
- interviewer_mindset
- specialty_tags
properties:
_id:
type: string
Expand Down Expand Up @@ -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
Expand All @@ -1668,6 +1677,7 @@ components:
- answer_list
- keywords
- interviewer_mindset
- specialty_tags
properties:
_id:
type: string
Expand Down Expand Up @@ -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
Expand Down
110 changes: 105 additions & 5 deletions nextstep-backend/src/services/companies_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown, {}, ICompany> & ICompany): CompanyData => {
return {
...company.toJSON(),
Expand Down Expand Up @@ -95,11 +108,12 @@ 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+/),
...quiz_title.split(/\s+/)
].filter(Boolean)));

// Add matched predefined tags from title and content
Expand All @@ -109,6 +123,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,
Expand Down Expand Up @@ -167,7 +214,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();
Expand Down Expand Up @@ -199,7 +245,7 @@ const parseJobQuizzesFromCompanyTablesHtml = (htmlPath: string): CompanyData[] =
company_en,
company_he,
jobRole,
...quiz_title.split(/\s+/),
...quiz_title.split(/\s+/)
].filter(Boolean)));

// Add matched predefined tags from title, process details, and interview questions
Expand All @@ -210,6 +256,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);

Expand Down Expand Up @@ -467,7 +546,10 @@ export const searchQuizzesByTags = async (tags: string[]): Promise<QuizData[]> =
/**
* 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<any> => {
// Take the first 10 best matching quizzes.
Expand All @@ -476,10 +558,24 @@ export const generateQuiz = async (quizSubject: string): Promise<any> => {

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_', ''))
.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}
Expand All @@ -502,6 +598,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
Expand All @@ -517,7 +614,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", ...]` : ''}
}
\`\`\`

Expand All @@ -527,6 +625,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;
}

Expand Down
Loading
Loading