diff --git a/schema-mappings/base-mapping.js b/schema-mappings/base-mapping.js new file mode 100644 index 0000000..417866b --- /dev/null +++ b/schema-mappings/base-mapping.js @@ -0,0 +1,209 @@ +// Base mapping structure for all ODMDB schemas +export const createSchemaMapping = (schemaData, objectName) => { + if (!schemaData || !schemaData.properties) { + return { + objectName, + available: false, + error: "Schema not found or invalid", + properties: {}, + synonyms: {}, + indexes: [], + }; + } + + const properties = schemaData.properties; + const synonyms = {}; + const fieldMappings = {}; + + // Generate comprehensive synonyms for each field + Object.entries(properties).forEach(([fieldName, fieldDef]) => { + const fieldSynonyms = generateFieldSynonyms( + fieldName, + fieldDef, + objectName + ); + fieldMappings[fieldName] = { + field: fieldName, + title: fieldDef.title?.toLowerCase(), + description: fieldDef.description?.toLowerCase(), + type: fieldDef.type, + synonyms: fieldSynonyms, + }; + + // Index by synonyms + fieldSynonyms.forEach((synonym) => { + synonyms[synonym.toLowerCase()] = fieldName; + }); + + // Index by title + if (fieldDef.title) { + synonyms[fieldDef.title.toLowerCase()] = fieldName; + } + }); + + // Extract indexes if available + const indexes = schemaData.apxidx + ? schemaData.apxidx.map((idx) => ({ + name: idx.name, + type: idx.type, + keyval: idx.keyval, + })) + : []; + + // Extract access rights if available + const accessRights = schemaData.apxaccessrights || {}; + + return { + objectName, + available: true, + propertyCount: Object.keys(properties).length, + properties: fieldMappings, + synonyms, + indexes, + accessRights, + rawSchema: schemaData, + }; +}; + +// Generate field-specific synonyms based on field name and context +const generateFieldSynonyms = (fieldName, fieldDef, objectName) => { + const synonyms = []; + + // Add the field name itself + synonyms.push(fieldName); + + // Add title if available + if (fieldDef.title) { + synonyms.push(fieldDef.title.toLowerCase()); + } + + // Common patterns across all objects + const commonPatterns = { + // Identity & References + alias: ["id", "identifier", "username", "user id"], + owner: ["owner", "belongs to", "owned by"], + + // Dates & Timestamps + dt_create: [ + "created", + "creation date", + "new", + "recent", + "since", + "registration date", + ], + dt_update: ["updated", "last update", "modified", "last modified"], + dt_publish: ["published", "publication date", "went live"], + dt_close: ["closed", "closing date", "ended"], + + // Contact Information + email: ["contact", "mail", "contact email", "e-mail"], + phone: ["telephone", "phone number", "contact number"], + + // Status & State + state: ["status", "condition", "current state"], + status: ["state", "condition", "current status"], + + // Location + location: ["where", "place", "address", "position"], + + // Descriptions + description: ["desc", "details", "info", "about"], + shortdescription: ["short desc", "summary", "brief", "overview"], + + // Common business fields + siret: ["company id", "business id", "organization id"], + sirets: ["companies", "businesses", "organizations"], + }; + + // Object-specific patterns + const objectSpecificPatterns = { + seekers: { + seekstatus: [ + "status", + "availability", + "looking", + "job search status", + "urgency", + ], + seekworkingyear: [ + "experience", + "years of experience", + "work experience", + "career length", + ], + seekjobtitleexperience: [ + "job titles", + "positions", + "roles", + "work history", + ], + salaryexpectation: [ + "salary", + "pay", + "compensation", + "wage", + "expected salary", + ], + seeklocation: [ + "location", + "where", + "work location", + "preferred location", + ], + skills: ["competencies", "abilities", "technical skills"], + mbti: ["personality", "personality type", "MBTI", "profile"], + }, + jobads: { + jobadid: ["job id", "ad id", "posting id"], + jobtitle: ["job title", "position", "role", "job name"], + salary: ["pay", "compensation", "wage", "remuneration"], + joblocation: ["job location", "work location", "where"], + description: ["job description", "details", "requirements"], + state: ["status", "publication status", "availability"], + }, + recruiters: { + sirets: ["companies", "businesses", "clients", "employers"], + tipsadvice: ["tips", "advice", "articles", "guidance"], + }, + persons: { + firstname: ["first name", "given name", "name"], + lastname: ["last name", "family name", "surname"], + dt_birth: ["birth date", "birthday", "date of birth", "age"], + }, + }; + + // Apply common patterns + if (commonPatterns[fieldName]) { + synonyms.push(...commonPatterns[fieldName]); + } + + // Apply object-specific patterns + if ( + objectSpecificPatterns[objectName] && + objectSpecificPatterns[objectName][fieldName] + ) { + synonyms.push(...objectSpecificPatterns[objectName][fieldName]); + } + + // Generate semantic synonyms based on field name patterns + if (fieldName.includes("salary")) { + synonyms.push("pay", "compensation", "wage", "remuneration"); + } + if (fieldName.includes("location")) { + synonyms.push("where", "place", "address", "position"); + } + if (fieldName.includes("experience")) { + synonyms.push("background", "history", "expertise"); + } + if (fieldName.includes("skill")) { + synonyms.push("competencies", "abilities", "talents"); + } + if (fieldName.includes("date") || fieldName.startsWith("dt_")) { + synonyms.push("when", "time", "timestamp"); + } + + return [...new Set(synonyms)]; // Remove duplicates +}; + +export default createSchemaMapping; diff --git a/schema-mappings/jobads-mapping.js b/schema-mappings/jobads-mapping.js new file mode 100644 index 0000000..44683db --- /dev/null +++ b/schema-mappings/jobads-mapping.js @@ -0,0 +1,151 @@ +// JobAds schema mapping - job posting natural language support +import { createSchemaMapping } from "./base-mapping.js"; +import fs from "node:fs"; + +const SCHEMA_PATH = "../smatchitObjectOdmdb/schema/jobads.json"; + +let jobadsSchema = null; +try { + if (fs.existsSync(SCHEMA_PATH)) { + jobadsSchema = JSON.parse(fs.readFileSync(SCHEMA_PATH, "utf-8")); + } +} catch (error) { + console.warn(`Warning: Could not load jobads schema: ${error.message}`); +} + +export const jobadsMapping = createSchemaMapping(jobadsSchema, "jobads"); + +// Additional jobads-specific enhancements +if (jobadsMapping.available) { + const jobadsEnhancements = { + // Job Identification + "job id": "jobadid", + "posting id": "jobadid", + "ad id": "jobadid", + "advertisement id": "jobadid", + "job posting": "jobadid", + + // Job Details + "job title": "jobtitle", + position: "jobtitle", + role: "jobtitle", + "job name": "jobtitle", + "position title": "jobtitle", + "job role": "jobtitle", + + // Job Status & State + "job status": "state", + "posting status": "state", + "publication status": "state", + availability: "state", + "job state": "state", + active: "state", + published: "state", + draft: "state", + archived: "state", + + // Company & Organization + company: "siret", + employer: "siret", + organization: "siret", + business: "siret", + firm: "siret", + + // Job Compensation + salary: "salary", + pay: "salary", + compensation: "salary", + wage: "salary", + remuneration: "salary", + payment: "salary", + + // Job Location + "job location": "joblocation", + "work location": "joblocation", + workplace: "joblocation", + "office location": "joblocation", + where: "joblocation", + place: "joblocation", + + // Job Description & Requirements + "job description": "description", + "job details": "description", + requirements: "description", + responsibilities: "description", + duties: "description", + "job requirements": "description", + "role description": "description", + + // Job Type & Contract + "employment type": "jobtype", + "contract type": "jobtype", + "job type": "jobtype", + "work type": "jobtype", + "position type": "jobtype", + + // Remote Work + "remote work": "remote", + "work from home": "remote", + telecommute: "remote", + "remote job": "remote", + "home office": "remote", + + // Dates & Timeline + "published date": "dt_publish", + "posting date": "dt_publish", + "publication date": "dt_publish", + "went live": "dt_publish", + "closing date": "dt_close", + deadline: "dt_close", + "application deadline": "dt_close", + expires: "dt_close", + + // Skills & Qualifications + "required skills": "skills", + qualifications: "skills", + competencies: "skills", + "abilities required": "skills", + "expertise needed": "skills", + + // Experience Requirements + "experience required": "experiencerequired", + "years of experience": "experiencerequired", + "work experience": "experiencerequired", + "professional experience": "experiencerequired", + + // Education Requirements + "education required": "education", + "degree required": "education", + "qualification needed": "education", + "educational background": "education", + }; + + // Merge enhancements into synonyms + Object.entries(jobadsEnhancements).forEach(([synonym, fieldName]) => { + jobadsMapping.synonyms[synonym.toLowerCase()] = fieldName; + }); + + // Add state value mappings + jobadsMapping.statusValues = { + state: { + active: "publish", + published: "publish", + live: "publish", + online: "publish", + available: "publish", + draft: "draft", + unpublished: "draft", + "in progress": "draft", + ready: "ready", + pending: "ready", + waiting: "ready", + closed: "archive", + archived: "archive", + expired: "archive", + inactive: "archive", + ended: "archive", + }, + }; +} + +export default jobadsMapping; diff --git a/schema-mappings/mapping-manager.js b/schema-mappings/mapping-manager.js new file mode 100644 index 0000000..e917a2f --- /dev/null +++ b/schema-mappings/mapping-manager.js @@ -0,0 +1,412 @@ +// Comprehensive ODMDB Schema Mapping Manager +// Handles all objects, detects data availability, and provides intelligent query routing + +import fs from "node:fs"; +import { seekersMapping } from "./seekers-mapping.js"; +import { jobadsMapping } from "./jobads-mapping.js"; +import { recruitersMapping } from "./recruiters-mapping.js"; +import { personsMapping } from "./persons-mapping.js"; +import { createSchemaMapping } from "./base-mapping.js"; + +const SCHEMA_BASE_PATH = "../smatchitObjectOdmdb/schema"; +const OBJECTS_BASE_PATH = "../smatchitObjectOdmdb/objects"; + +class ODMDBMappingManager { + constructor() { + this.mappings = new Map(); + this.dataAvailability = new Map(); + this.loadAllMappings(); + this.checkDataAvailability(); + } + + loadAllMappings() { + // Load primary mappings (with custom enhancements) + this.mappings.set("seekers", seekersMapping); + this.mappings.set("jobads", jobadsMapping); + this.mappings.set("recruiters", recruitersMapping); + this.mappings.set("persons", personsMapping); + + // Load remaining schemas dynamically + const remainingSchemas = [ + "jobsteps", + "jobtitles", + "quizz", + "screens", + "sirets", + "trainingprovider", + "trainings", + ]; + + remainingSchemas.forEach((schemaName) => { + const schemaPath = `${SCHEMA_BASE_PATH}/${schemaName}.json`; + try { + if (fs.existsSync(schemaPath)) { + const schemaData = JSON.parse(fs.readFileSync(schemaPath, "utf-8")); + const mapping = createSchemaMapping(schemaData, schemaName); + this.mappings.set(schemaName, mapping); + } + } catch (error) { + console.warn( + `Warning: Could not load ${schemaName} schema: ${error.message}` + ); + this.mappings.set(schemaName, { + objectName: schemaName, + available: false, + error: error.message, + properties: {}, + synonyms: {}, + }); + } + }); + + console.log(`πŸ—ΊοΈ Loaded ${this.mappings.size} object mappings`); + } + + checkDataAvailability() { + // Check which objects have actual data files + this.mappings.forEach((mapping, objectName) => { + const objectPath = `${OBJECTS_BASE_PATH}/${objectName}`; + const itemsPath = `${objectPath}/itm`; + + let availability = { + schemaAvailable: mapping.available, + dataAvailable: false, + dataPath: itemsPath, + fileCount: 0, + sampleFiles: [], + }; + + try { + if (fs.existsSync(itemsPath)) { + const files = fs + .readdirSync(itemsPath) + .filter((f) => f.endsWith(".json") && f !== "backup") + .filter((f) => !fs.statSync(`${itemsPath}/${f}`).isDirectory()); + + availability.dataAvailable = files.length > 0; + availability.fileCount = files.length; + availability.sampleFiles = files.slice(0, 3); // First 3 files as samples + } + } catch (error) { + console.warn( + `Warning: Could not check data for ${objectName}: ${error.message}` + ); + } + + this.dataAvailability.set(objectName, availability); + }); + + // Log availability summary + const availableObjects = Array.from(this.dataAvailability.entries()) + .filter(([_, availability]) => availability.dataAvailable) + .map( + ([objectName, availability]) => + `${objectName}(${availability.fileCount})` + ) + .join(", "); + + console.log(`πŸ“Š Data available for: ${availableObjects}`); + } + + // Intelligent object detection from natural language + detectObjectFromQuery(nlQuery) { + const query = nlQuery.toLowerCase(); + const detectedObjects = []; + + // Direct object name mentions + this.mappings.forEach((mapping, objectName) => { + if ( + query.includes(objectName) || + query.includes(objectName.slice(0, -1)) + ) { + // singular form + detectedObjects.push({ + object: objectName, + confidence: 0.9, + reason: `Direct mention of '${objectName}'`, + }); + } + }); + + // Semantic object detection + const objectIndicators = { + seekers: [ + "seekers", + "seeker", + "job seekers", + "candidates", + "applicants", + "people looking for jobs", + "job hunters", + "looking for work", + "experience", + "skills", + "salary expectation", + "availability", + ], + jobads: [ + "jobs", + "job postings", + "job ads", + "positions", + "openings", + "vacancies", + "employment opportunities", + "job offers", + "job description", + "job requirements", + "salary range", + ], + recruiters: [ + "recruiters", + "recruiter", + "hiring managers", + "hr", + "employers", + "hiring", + "recruitment", + "talent acquisition", + "headhunters", + ], + persons: [ + "people", + "users", + "profiles", + "personal information", + "contact details", + "names", + "demographics", + "biography", + ], + sirets: [ + "companies", + "businesses", + "organizations", + "employers", + "firms", + "corporations", + "enterprises", + ], + }; + + Object.entries(objectIndicators).forEach(([objectName, indicators]) => { + const matches = indicators.filter((indicator) => + query.includes(indicator) + ); + if (matches.length > 0) { + const confidence = Math.min(0.8, matches.length * 0.3); + detectedObjects.push({ + object: objectName, + confidence, + reason: `Semantic match: ${matches.join(", ")}`, + }); + } + }); + + // Sort by confidence and remove duplicates + const uniqueObjects = detectedObjects.reduce((acc, current) => { + const existing = acc.find((item) => item.object === current.object); + if (!existing || current.confidence > existing.confidence) { + acc = acc.filter((item) => item.object !== current.object); + acc.push(current); + } + return acc; + }, []); + + return uniqueObjects.sort((a, b) => b.confidence - a.confidence); + } + + // Get data availability statistics + getDataAvailabilityStats() { + const availableObjects = []; + const objectStats = {}; + + for (const [objectType, mapping] of Object.entries(this.mappings)) { + if (mapping.available) { + availableObjects.push(objectType); + objectStats[objectType] = mapping.dataStats.fileCount; + } + } + + const summary = availableObjects + .map((obj) => `${obj}(${objectStats[obj]})`) + .join(", "); + + return { + availableObjects, + objectStats, + summary, + totalObjects: availableObjects.length, + }; + } + + // Check if a query is feasible given available data + validateQueryFeasibility(nlQuery, suggestedObject = null) { + const detectedObjects = suggestedObject + ? [ + { + object: suggestedObject, + confidence: 1.0, + reason: "Explicitly specified", + }, + ] + : this.detectObjectFromQuery(nlQuery); + + if (detectedObjects.length === 0) { + return { + feasible: false, + reason: "Cannot determine which object type this query refers to", + suggestion: + "Please specify if you're looking for seekers, jobs, recruiters, or companies", + availableObjects: Array.from(this.dataAvailability.keys()).filter( + (obj) => this.dataAvailability.get(obj).dataAvailable + ), + }; + } + + const primaryObject = detectedObjects[0]; + const availability = this.dataAvailability.get(primaryObject.object); + + if (!availability) { + return { + feasible: false, + reason: `Unknown object type: ${primaryObject.object}`, + suggestion: `Available objects: ${Array.from(this.mappings.keys()).join( + ", " + )}`, + }; + } + + if (!availability.schemaAvailable) { + return { + feasible: false, + reason: `Schema not available for ${primaryObject.object}`, + suggestion: `Cannot process queries for ${primaryObject.object} - schema missing`, + }; + } + + if (!availability.dataAvailable) { + return { + feasible: false, + reason: `No data available for ${primaryObject.object}`, + suggestion: `${ + primaryObject.object + } schema exists but no data files found. Available data: ${Array.from( + this.dataAvailability.entries() + ) + .filter(([_, avail]) => avail.dataAvailable) + .map(([name, avail]) => `${name}(${avail.fileCount})`) + .join(", ")}`, + }; + } + + // Check if requested fields exist + const mapping = this.mappings.get(primaryObject.object); + const queryWords = nlQuery.toLowerCase().split(/\s+/); + const unmappedWords = []; + + queryWords.forEach((word) => { + if ( + word.length > 2 && // Skip short words + !mapping.synonyms[word] && + !Object.keys(mapping.properties).includes(word) && + ![ + "show", + "get", + "find", + "with", + "their", + "and", + "the", + "me", + "all", + ].includes(word) + ) { + unmappedWords.push(word); + } + }); + + return { + feasible: true, + primaryObject, + detectedObjects, + dataStats: { + fileCount: availability.fileCount, + sampleFiles: availability.sampleFiles, + }, + fieldWarnings: + unmappedWords.length > 0 + ? `Some terms might not map to fields: ${unmappedWords.join(", ")}` + : null, + }; + } + + // Get mapping for a specific object + getMapping(objectName) { + return this.mappings.get(objectName); + } + + // Get all available objects with data + getAvailableObjects() { + return Array.from(this.dataAvailability.entries()) + .filter( + ([_, availability]) => + availability.dataAvailable && availability.schemaAvailable + ) + .map(([objectName, availability]) => ({ + object: objectName, + fileCount: availability.fileCount, + propertyCount: this.mappings.get(objectName)?.propertyCount || 0, + })); + } + + // Get comprehensive field suggestions for an object + getFieldSuggestions(objectName, queryTerms = []) { + const mapping = this.getMapping(objectName); + if (!mapping || !mapping.available) return []; + + const suggestions = []; + + // Find fields that match query terms + queryTerms.forEach((term) => { + const field = mapping.synonyms[term.toLowerCase()]; + if (field) { + const fieldInfo = mapping.properties[field]; + suggestions.push({ + field, + matchedTerm: term, + title: fieldInfo.title, + type: fieldInfo.type, + synonyms: fieldInfo.synonyms.slice(0, 3), // Top 3 synonyms + }); + } + }); + + return suggestions; + } + + // Generate intelligent error messages with suggestions + generateErrorMessage(nlQuery, error) { + const feasibility = this.validateQueryFeasibility(nlQuery); + + if (!feasibility.feasible) { + return { + error: feasibility.reason, + suggestion: feasibility.suggestion, + availableObjects: + feasibility.availableObjects || this.getAvailableObjects(), + }; + } + + return { + error: error.message || "Unknown error", + suggestion: "Query seems valid but processing failed", + queryAnalysis: feasibility, + }; + } +} + +// Export class and singleton instance +export { ODMDBMappingManager }; +export const odmdbMappingManager = new ODMDBMappingManager(); +export default ODMDBMappingManager; diff --git a/schema-mappings/persons-mapping.js b/schema-mappings/persons-mapping.js new file mode 100644 index 0000000..92e94ed --- /dev/null +++ b/schema-mappings/persons-mapping.js @@ -0,0 +1,104 @@ +// Persons schema mapping - person profile natural language support +import { createSchemaMapping } from "./base-mapping.js"; +import fs from "node:fs"; + +const SCHEMA_PATH = "../smatchitObjectOdmdb/schema/persons.json"; + +let personsSchema = null; +try { + if (fs.existsSync(SCHEMA_PATH)) { + personsSchema = JSON.parse(fs.readFileSync(SCHEMA_PATH, "utf-8")); + } +} catch (error) { + console.warn(`Warning: Could not load persons schema: ${error.message}`); +} + +export const personsMapping = createSchemaMapping(personsSchema, "persons"); + +// Additional persons-specific enhancements +if (personsMapping.available) { + const personsEnhancements = { + // Personal Information + "first name": "firstname", + "given name": "firstname", + name: "firstname", + "last name": "lastname", + "family name": "lastname", + surname: "lastname", + "full name": "fullname", + "display name": "fullname", + + // Demographics + "birth date": "dt_birth", + birthday: "dt_birth", + "date of birth": "dt_birth", + age: "dt_birth", + born: "dt_birth", + gender: "pronom", + pronouns: "pronom", + + // Contact & Communication + "personal email": "emailcom", + "communication email": "emailcom", + "contact email": "emailcom", + "email address": "emailcom", + + // Profile Information + biography: "biography", + bio: "biography", + about: "biography", + description: "biography", + "personal story": "biography", + background: "biography", + + hobbies: "hobbies", + interests: "hobbies", + activities: "hobbies", + pastimes: "hobbies", + leisure: "hobbies", + + // Visual Profile + "profile picture": "imgavatar", + avatar: "imgavatar", + photo: "imgavatar", + image: "imgavatar", + picture: "imgavatar", + + // Access & Privacy + "profile access": "profilaccess", + "privacy settings": "profilaccess", + visibility: "profilaccess", + "profile visibility": "profilaccess", + + // Activity & Status + "last login": "last_login", + "last active": "last_login", + "last seen": "last_login", + "login time": "last_login", + + // Account Information + "account created": "dt_create", + registration: "dt_create", + joined: "dt_create", + "sign up": "dt_create", + "profile updated": "dt_update", + "last modified": "dt_update", + }; + + // Merge enhancements into synonyms + Object.entries(personsEnhancements).forEach(([synonym, fieldName]) => { + personsMapping.synonyms[synonym.toLowerCase()] = fieldName; + }); + + // Add persons-specific context + personsMapping.context = { + description: "Personal profile information for users in the system", + primaryData: ["identity", "contact", "demographics", "profile"], + relationships: { + seekers: "Person who is seeking employment", + recruiters: "Person who is recruiting for companies", + }, + }; +} + +export default personsMapping; diff --git a/schema-mappings/recruiters-mapping.js b/schema-mappings/recruiters-mapping.js new file mode 100644 index 0000000..143dbd7 --- /dev/null +++ b/schema-mappings/recruiters-mapping.js @@ -0,0 +1,118 @@ +// Recruiters schema mapping - recruiter natural language support +import { createSchemaMapping } from "./base-mapping.js"; +import fs from "node:fs"; + +const SCHEMA_PATH = "../smatchitObjectOdmdb/schema/recruiters.json"; + +let recruitersSchema = null; +try { + if (fs.existsSync(SCHEMA_PATH)) { + recruitersSchema = JSON.parse(fs.readFileSync(SCHEMA_PATH, "utf-8")); + } +} catch (error) { + console.warn(`Warning: Could not load recruiters schema: ${error.message}`); +} + +export const recruitersMapping = createSchemaMapping( + recruitersSchema, + "recruiters" +); + +// Additional recruiters-specific enhancements +if (recruitersMapping.available) { + const recruitersEnhancements = { + // Identity & Contact + "recruiter id": "alias", + "recruiter name": "alias", + "hr id": "alias", + "hiring manager": "alias", + + // Contact Information + "contact email": "email", + "recruiter email": "email", + "hiring email": "email", + "hr email": "email", + "contact phone": "phone", + "recruiter phone": "phone", + telephone: "phone", + "phone number": "phone", + + // Company Associations + companies: "sirets", + employers: "sirets", + clients: "sirets", + businesses: "sirets", + organizations: "sirets", + "company list": "sirets", + "employer list": "sirets", + + // Professional Information + tips: "tipsadvice", + advice: "tipsadvice", + articles: "tipsadvice", + guidance: "tipsadvice", + recommendations: "tipsadvice", + "help articles": "tipsadvice", + + // Activity & Dates + joined: "dt_create", + registration: "dt_create", + "sign up": "dt_create", + "account created": "dt_create", + "last updated": "dt_update", + "profile updated": "dt_update", + modified: "dt_update", + + // Status & Activity + "active recruiter": "status", + "recruiter status": "status", + "hiring status": "status", + availability: "status", + + // Job Management + "job postings": "jobads", + "job ads": "jobads", + postings: "jobads", + advertisements: "jobads", + vacancies: "jobads", + openings: "jobads", + + // Recruitment Activity + candidates: "candidates", + applicants: "applicants", + seekers: "seekers", + prospects: "prospects", + "talent pool": "candidates", + + // Performance & Metrics + placements: "placements", + hires: "hires", + "successful hires": "placements", + "recruitment success": "placements", + }; + + // Merge enhancements into synonyms + Object.entries(recruitersEnhancements).forEach(([synonym, fieldName]) => { + recruitersMapping.synonyms[synonym.toLowerCase()] = fieldName; + }); + + // Add recruiter-specific context + recruitersMapping.context = { + description: + "Recruiters create and manage job postings, recruit for companies (sirets), and manage the hiring process", + primaryActions: [ + "create jobads", + "manage candidates", + "process applications", + "schedule interviews", + ], + relationships: { + sirets: "Companies the recruiter works for", + jobads: "Job postings created by this recruiter", + candidates: "People being recruited", + seekers: "Job seekers in recruitment process", + }, + }; +} + +export default recruitersMapping; diff --git a/schema-mappings/seekers-mapping.js b/schema-mappings/seekers-mapping.js new file mode 100644 index 0000000..c2a74f5 --- /dev/null +++ b/schema-mappings/seekers-mapping.js @@ -0,0 +1,134 @@ +// Seekers schema mapping - comprehensive natural language support +import { createSchemaMapping } from "./base-mapping.js"; +import fs from "node:fs"; + +const SCHEMA_PATH = "../smatchitObjectOdmdb/schema/seekers.json"; + +let seekersSchema = null; +try { + if (fs.existsSync(SCHEMA_PATH)) { + seekersSchema = JSON.parse(fs.readFileSync(SCHEMA_PATH, "utf-8")); + } +} catch (error) { + console.warn(`Warning: Could not load seekers schema: ${error.message}`); +} + +export const seekersMapping = createSchemaMapping(seekersSchema, "seekers"); + +// Additional seeker-specific enhancements +if (seekersMapping.available) { + // Add seeker-specific synonym enhancements + const seekerEnhancements = { + // Work & Career + "work experience": "seekworkingyear", + "career experience": "seekworkingyear", + "professional experience": "seekworkingyear", + "years working": "seekworkingyear", + "job experience": "seekjobtitleexperience", + "previous jobs": "seekjobtitleexperience", + "work history": "seekjobtitleexperience", + "positions held": "seekjobtitleexperience", + + // Job Search Status + urgency: "seekstatus", + "how fast": "seekstatus", + "job urgency": "seekstatus", + "looking urgently": "seekstatus", + "need job quickly": "seekstatus", + + // Compensation + "expected salary": "salaryexpectation", + "salary expectation": "salaryexpectation", + "desired salary": "salaryexpectation", + "target salary": "salaryexpectation", + "pay expectation": "salaryexpectation", + "wage expectation": "salaryexpectation", + + // Location preferences + "work location": "seeklocation", + "job location": "seeklocation", + "where to work": "seeklocation", + "preferred location": "seeklocation", + "work geography": "seeklocation", + + // Skills & Abilities + "technical skills": "skills", + "professional skills": "skills", + competencies: "skills", + abilities: "skills", + expertise: "skills", + languages: "languageskills", + "language abilities": "languageskills", + "linguistic skills": "languageskills", + + // Personality & Profile + "personality type": "mbti", + "personality profile": "mbti", + "MBTI type": "mbti", + "psychological profile": "mbti", + + // Job Preferences + "job type": "seekjobtype", + "employment type": "seekjobtype", + "contract type": "seekjobtype", + "work type": "seekjobtype", + + // Availability & Schedule + "working hours": "preferedworkinghours", + "work schedule": "preferedworkinghours", + "preferred hours": "preferedworkinghours", + "schedule preference": "preferedworkinghours", + "not available": "notavailabletowork", + unavailable: "notavailabletowork", + "blocked times": "notavailabletowork", + + // Job Search Activity + "job applications": "jobadapply", + "applied jobs": "jobadapply", + "applications sent": "jobadapply", + "bookmarked jobs": "jobadsaved", + "saved jobs": "jobadsaved", + "favorite jobs": "jobadsaved", + "job invitations": "jobadinvitedtoapply", + "invited to apply": "jobadinvitedtoapply", + + // Education & Training + education: "educations", + degree: "educations", + qualifications: "educations", + diploma: "educations", + studies: "educations", + + // Communication preferences + notifications: "notificationformatches", + alerts: "notificationformatches", + "email preferences": "emailactivityreportweekly", + newsletter: "emailnewsletter", + }; + + // Merge enhancements into synonyms + Object.entries(seekerEnhancements).forEach(([synonym, fieldName]) => { + seekersMapping.synonyms[synonym.toLowerCase()] = fieldName; + }); + + // Add status value mappings + seekersMapping.statusValues = { + seekstatus: { + urgent: "startasap", + urgently: "startasap", + asap: "startasap", + quickly: "startasap", + immediately: "startasap", + fast: "startasap", + "no rush": "norush", + "taking time": "norush", + leisurely: "norush", + "not urgent": "norush", + "not looking": "notlooking", + "not active": "notlooking", + inactive: "notlooking", + }, + }; +} + +export default seekersMapping;