Spaces:
Running
Running
File size: 12,343 Bytes
53361fa | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | /* CareerMatch AI Static: explainable browser-only lexical CV/job alignment.
Original Gradio/Python project retained separately. No embeddings here. */
(function(root) {
'use strict';
const SKILLS = {
'Project Management':['project management','gestión de proyectos','gerenciamento de projetos'],
'Program Management':['program management','gestión de programas','gerenciamento de programas'],
'Operations':['operations','operaciones','operações','operacoes','operational management','gestión operativa'],
'Process Improvement':['process improvement','mejora de procesos','melhoria de processos','continuous improvement'],
'Stakeholder Management':['stakeholder management','gestión de stakeholders','gestão de stakeholders','stakeholders'],
'Change Management':['change management','gestión del cambio','gestão da mudança'],
'Strategic Planning':['strategic planning','planificación estratégica','planejamento estratégico'],
'Team Leadership':['team leadership','liderazgo de equipos','liderança de equipes','people management','gestión de equipos'],
'Customer Success':['customer success','éxito del cliente','sucesso do cliente'],
'Customer Service':['customer service','servicio al cliente','atención al cliente','atendimento ao cliente'],
'Logistics':['logistics','logística','logistica'],
'Supply Chain':['supply chain','cadena de suministro','cadeia de suprimentos'],
'Procurement':['procurement','compras','adquisiciones','suprimentos'],
'Business Analysis':['business analysis','análisis de negocio','análise de negócios'],
'Market Research':['market research','investigación de mercados','pesquisa de mercado'],
'Digital Marketing':['digital marketing','marketing digital'],
'SEO':['seo','search engine optimization','optimización para motores de búsqueda'],
'CRM':['crm','customer relationship management','gestión de relaciones con clientes'],
'Sales':['sales','ventas','vendas','business development','desarrollo de negocios'],
'Financial Analysis':['financial analysis','análisis financiero','análise financeira'],
'Budgeting':['budgeting','presupuesto','orçamento','orcamento'],
'Forecasting':['forecasting','pronóstico','proyecciones','previsão'],
'Risk Management':['risk management','gestión de riesgos','gestão de riscos'],
'Data Analysis':['data analysis','análisis de datos','análise de dados','analítica de datos'],
'Data Visualization':['data visualization','visualización de datos','visualização de dados'],
'Machine Learning':['machine learning','aprendizaje automático','aprendizado de máquina'],
'Generative AI':['generative ai','genai','ia generativa','inteligencia artificial generativa'],
'NLP':['natural language processing','nlp','procesamiento de lenguaje natural','processamento de linguagem natural'],
'RAG':['retrieval augmented generation','rag','generación aumentada por recuperación'],
'Prompt Engineering':['prompt engineering','ingeniería de prompts','engenharia de prompts'],
'Python':['python'], 'SQL':['sql','postgresql','mysql','sql server'],
'Excel':['excel','microsoft excel','spreadsheets','hojas de cálculo','planilhas'],
'Power BI':['power bi','powerbi'], 'Tableau':['tableau'], 'Pandas':['pandas'],
'scikit-learn':['scikit-learn','sklearn'], 'Transformers':['transformers','hugging face transformers'],
'Hugging Face':['hugging face','huggingface'], 'Git':['git','version control','control de versiones'],
'GitHub':['github'], 'GitLab':['gitlab'], 'Docker':['docker','containers','contenedores'],
'APIs':['api','apis','rest api','restful'], 'Cloud':['cloud','aws','azure','gcp','google cloud'],
'Research':['research','investigación','investigação','pesquisa acadêmica'],
'Academic Writing':['academic writing','escritura académica','redacción académica','escrita acadêmica'],
'Literature Review':['literature review','revisión de literatura','revisão de literatura','revisión bibliográfica'],
'Quantitative Research':['quantitative research','investigación cuantitativa','pesquisa quantitativa'],
'Qualitative Research':['qualitative research','investigación cualitativa','pesquisa qualitativa'],
'Teaching':['teaching','docencia','enseñanza','ensino','professor','lecturer'],
'Training':['training','capacitación','treinamento'],
'International Relations':['international relations','relaciones internacionales','relações internacionais'],
'International Trade':['international trade','comercio internacional','comércio internacional'],
'Public Policy':['public policy','políticas públicas','politicas publicas'],
'Economic Development':['economic development','desarrollo económico','desenvolvimento econômico'],
'Agile':['agile','scrum','kanban'],
'KPI Management':['kpi','kpis','key performance indicators','indicadores clave','indicadores de desempenho'],
'Process Documentation':['process documentation','documentación de procesos','documentação de processos','sop'],
'Automation':['automation','automatización','automação','workflow automation']
};
const LANGUAGES = {
'English':['english','inglés','ingles','inglês'],
'Spanish':['spanish','español','espanol','castellano'],
'Portuguese':['portuguese','portugués','portugues','português'],
'Italian':['italian','italiano','italiana'],
'French':['french','francés','frances','francês'],
'German':['german','alemán','aleman','alemão']
};
const STOPWORDS = new Set(('the and for with from that this your you our are will have has job role years year experience must be or of in as an to we a is on not by who can and plus strong ability required preferred position seeking skills skill excellent support demonstrated using and of ' +
'de la el los las y en con para por que se del un una su sus como al es lo las o este esta ser tener experiencia años año trabajo puesto buscamos deberá debe mínima mínimo ' +
'da do das dos e em com para por que um uma sua seu como os as na no profissional vaga').split(/\s+/));
const NECESSARY=['required','requirements','must','minimum','essential','mandatory','requerido','requisitos','debe','deberá','mínimo','obligatorio','necessário','obrigatório','precisa'];
const PREFERRED=['preferred','nice to have','plus','desirable','advantage','deseable','preferido','valorable','desejável','preferencial','diferencial'];
function norm(s) { return String(s||'').normalize('NFD').replace(/[\u0300-\u036f]/g,'').toLowerCase().replace(/\s+/g,' ').trim(); }
function terms(s) { return (norm(s).match(/[\p{L}\p{N}+#.-]{2,}/gu)||[]).map(t=>t.replace(/^[.-]+|[.-]+$/g,'')).filter(t=>t && !STOPWORDS.has(t) && !/^\d+$/.test(t)); }
function hasAlias(text,alias) {
const h=' '+norm(text).replace(/[^\p{L}\p{N}]+/gu,' ')+' ';
const n=' '+norm(alias).replace(/[^\p{L}\p{N}]+/gu,' ').trim()+' ';
return h.includes(n);
}
function extract(text,map){return Object.keys(map).filter(k=>map[k].some(a=>hasAlias(text,a)));}
function splitSentences(text){ return String(text||'').replace(/\r/g,'\n').split(/(?:\n+|(?<=[.!?;])\s+|\s*[•▪◦]\s*)/u).map(s=>s.trim()).filter(s=>s.length>=15).slice(0,100); }
function keywordStats(text){const c=new Map();for(const t of terms(text)){if(t.length<3)continue;c.set(t,(c.get(t)||0)+1);}return Array.from(c.entries()).sort((a,b)=>b[1]-a[1]);}
function tfidfCosine(a,b) {
const docs=[terms(a),terms(b)];
if (!docs[0].length||!docs[1].length) return 0;
const maps=docs.map(arr=>{const features=arr.concat(arr.slice(1).map((x,i)=>arr[i]+' '+x));const count=new Map();for(const t of features)count.set(t,(count.get(t)||0)+1);return count;});
const features=new Set([...maps[0].keys(),...maps[1].keys()]);let dot=0,na=0,nb=0;
for(const t of features){const df=Number(maps[0].has(t))+Number(maps[1].has(t));const idf=1+Math.log(3/(1+df));const aa=(maps[0].get(t)||0)*idf,bb=(maps[1].get(t)||0)*idf;dot+=aa*bb;na+=aa*aa;nb+=bb*bb;}
return na&&nb?dot/Math.sqrt(na*nb):0;
}
const clamp=v=>Math.max(0,Math.min(1,v));
function requirements(job){
const ss=splitSentences(job);
return ss.map(s=>{
const lower=norm(s);const required=NECESSARY.some(t=>hasAlias(lower,t));const preferred=PREFERRED.some(t=>hasAlias(lower,t));
return {text:s.slice(0,300),importance:required?'Required':preferred?'Preferred':'Context',weight:required?1.5:preferred?0.75:0.3};
}).sort((a,b)=>b.weight-a.weight).slice(0,12);
}
function compare(cv,job){
cv=String(cv||'').trim();job=String(job||'').trim();
if(cv.length<40||job.length<40)throw new Error('Enter at least 40 characters in BOTH the CV and job description.');
if(cv.length>60000||job.length>60000)throw new Error('Please limit each document to 60,000 characters.');
const cvSkills=extract(cv,SKILLS), jobSkills=extract(job,SKILLS),jobLang=extract(job,LANGUAGES),cvLang=extract(cv,LANGUAGES);
const matchedSkills=jobSkills.filter(x=>cvSkills.includes(x)),missingSkills=jobSkills.filter(x=>!cvSkills.includes(x));
const matchedLang=jobLang.filter(x=>cvLang.includes(x)),missingLang=jobLang.filter(x=>!cvLang.includes(x));
const semantic=clamp(tfidfCosine(cv,job));
const skill=jobSkills.length?matchedSkills.length/jobSkills.length:0.5;
const cvWords=new Set(terms(cv));
const jwords=[...new Set(keywordStats(job).slice(0,35).map(x=>x[0]))];
const kwMatched=jwords.filter(x=>cvWords.has(x)),kwMissing=jwords.filter(x=>!cvWords.has(x));
const keyword=jwords.length?kwMatched.length/jwords.length:0;
const reqs=requirements(job).map(r=>{
const a=clamp(tfidfCosine(cv,r.text));const ts=terms(r.text);const m=ts.filter(w=>cvWords.has(w));
return {...r, evidence:Math.max(a,ts.length?m.length/ts.length:0),signals:m.slice(0,6)};
});
const weightSum=reqs.reduce((a,r)=>a+r.weight,0);
const reqScore=weightSum?reqs.reduce((a,r)=>a+r.weight*r.evidence,0)/weightSum:0.5;
const langRatio=jobLang.length?matchedLang.length/jobLang.length:1;
const weighted=semantic*.35+skill*.30+reqScore*.20+keyword*.15;
const score=Math.round(100*clamp(weighted*(jobLang.length?0.9+0.1*langRatio:1)));
const recommendations=[];
if(missingSkills.length)recommendations.push('Check whether you have evidence for these unmentioned job skills: '+missingSkills.slice(0,7).join(', ')+'. Add only verifiable experience.');
if(missingLang.length)recommendations.push('The role mentions '+missingLang.join(', ')+'; verify your proficiency before adding language claims.');
if(reqs.filter(x=>x.importance==='Required'&&x.evidence<0.25).length)recommendations.push('Review the required statements with weak textual evidence. Consider adding a specific achievement or example if true.');
if(semantic<0.25)recommendations.push('Your CV and the job description use different vocabulary. Clarify relevant experience with truthful, role-specific terms.');
if(!recommendations.length) recommendations.push('Most detected textual signals are present. Review them manually and focus on clear, specific achievements.');
recommendations.push('This analysis does not assess actual competence, eligibility, years worked or hiring probability.');
return {
score,band:score>=75?'Strong textual alignment':score>=50?'Partial textual alignment':'Limited textual alignment',
components:[{name:'Lexical TF-IDF alignment',value:Math.round(semantic*100),weight:35},{name:'Skill taxonomy coverage',value:Math.round(skill*100),weight:30},{name:'Requirement evidence',value:Math.round(reqScore*100),weight:20},{name:'Keyword overlap',value:Math.round(keyword*100),weight:15}],
matchedSkills,missingSkills,cvSkills,jobSkills,matchedLang,missingLang,jobLang,
reqs,keywordsMatched:kwMatched.slice(0,15),keywordsMissing:kwMissing.slice(0,15),recommendations,
method:'Browser-only TF-IDF (unigrams and bigrams), multilingual alias taxonomy, and heuristic requirement evidence. NO semantic embeddings or generative model.'
};
}
root.CareerMatchEngine=Object.freeze({compare,tfidfCosine,extractSkills:t=>extract(t,SKILLS),extractLanguages:t=>extract(t,LANGUAGES),requirements});
})(typeof window!=='undefined'?window:globalThis);
|