Overview
Banca Intelligence uses Natural Language Processing (NLP) and fuzzy matching algorithms to analyze which topics from your exam syllabus are most likely to appear on the actual exam, based on historical incidence data from the exam board (banca).
This feature uses advanced text similarity algorithms including Levenshtein distance , tokenization , and stopword filtering to match your syllabus topics against known high-incidence themes.
How It Works
1. Topic Matching Pipeline
Your Syllabus Topic
↓
Tokenization & Normalization
↓
Fuzzy Match Against Hot Topics Database
↓
Calculate Relevance Score (0-100)
↓
Assign Priority (P1, P2, P3)
2. NLP Engine
The system uses multiple techniques to find matches:
Tokenization
Breaks text into meaningful words, removes stopwords:
// Tokenization (relevance.js:21-30)
export function tokenize ( text ) {
if ( ! text ) return [];
const normalized = text . toLowerCase ()
. normalize ( 'NFD' ). replace ( / [ \u0300 - \u036f ] / g , '' ) // Remove accents
. replace ( / [ ^ \w\s ] / gi , ' ' ) // Remove punctuation
. replace ( / \s + / g , ' ' )
. trim ();
return normalized . split ( ' ' ). filter ( word =>
word . length > 2 && ! STOPWORDS . has ( word )
);
}
Stopwords (ignored words):
const STOPWORDS = new Set ([
'o' , 'a' , 'os' , 'as' , 'de' , 'do' , 'da' , 'em' , 'no' , 'na' ,
'para' , 'com' , 'sem' , 'e' , 'ou' , 'que' , 'se' ,
'lei' , 'artigo' , 'art' , 'inciso' , 'capítulo' ,
'conceito' , 'noções' , 'introdução' , 'teoria' , 'geral'
]);
Fuzzy Similarity (Levenshtein Distance)
Calculates how similar two words are:
// Levenshtein distance (relevance.js:33-53)
export function levenshteinDistance ( a , b ) {
if ( a . length === 0 ) return b . length ;
if ( b . length === 0 ) return a . length ;
const matrix = Array ( a . length + 1 ). fill ( null ). map (() =>
Array ( b . length + 1 ). fill ( null )
);
for ( let i = 0 ; i <= a . length ; i ++ ) matrix [ i ][ 0 ] = i ;
for ( let j = 0 ; j <= b . length ; j ++ ) matrix [ 0 ][ j ] = j ;
for ( let i = 1 ; i <= a . length ; i ++ ) {
for ( let j = 1 ; j <= b . length ; j ++ ) {
const cost = a [ i - 1 ] === b [ j - 1 ] ? 0 : 1 ;
matrix [ i ][ j ] = Math . min (
matrix [ i - 1 ][ j ] + 1 , // deletion
matrix [ i ][ j - 1 ] + 1 , // insertion
matrix [ i - 1 ][ j - 1 ] + cost // substitution
);
}
}
return matrix [ a . length ][ b . length ];
}
// Similarity percentage (relevance.js:56-61)
export function fuzzySimiliarity ( word1 , word2 ) {
const dist = levenshteinDistance ( word1 , word2 );
const maxLen = Math . max ( word1 . length , word2 . length );
if ( maxLen === 0 ) return 1.0 ;
return ( maxLen - dist ) / maxLen ; // 0.0 to 1.0
}
Token Matching
Compares entire phrases:
// Token set matching (relevance.js:64-81)
export function computeTokenMatch ( tokensA , tokensB ) {
if ( tokensA . length === 0 || tokensB . length === 0 ) return 0 ;
let matches = 0 ;
for ( const ta of tokensA ) {
let bestWordScore = 0 ;
for ( const tb of tokensB ) {
const sc = fuzzySimiliarity ( ta , tb );
if ( sc > bestWordScore ) bestWordScore = sc ;
}
if ( bestWordScore > 0.8 ) { // 80% similarity threshold
matches ++ ;
}
}
return matches / Math . max ( tokensA . length , tokensB . length );
}
3. Best Match Algorithm
Finds the most relevant hot topic for each syllabus item:
// Best match finder (relevance.js:92-163)
export function findBestMatch ( editalSubjectName , disciplinaId = null ) {
const hotTopics = state . bancaRelevance ?. hotTopics || [];
const userMap = state . bancaRelevance ?. userMappings ?.[ editalSubjectName ];
// 1. Manual override check
if ( userMap ) {
if ( userMap === 'NONE' ) {
return {
matchedItem: null ,
score: 0.05 ,
confidence: 'HIGH' ,
reason: 'Marcado como sem incidência pelo usuário'
};
}
const forcedMatch = hotTopics . find ( h => h . id === userMap );
if ( forcedMatch ) {
return {
matchedItem: forcedMatch ,
score: 1.0 ,
confidence: 'HIGH' ,
reason: 'Mapeamento Fixado Manualmente'
};
}
}
// 2. Automatic matching
const tokensA = tokenize ( editalSubjectName );
const strA = editalSubjectName . toLowerCase (). trim ();
let bestMatch = null ;
let highestScore = 0 ;
let bestReason = '' ;
let bestConf = 'LOW' ;
for ( const ht of hotTopics ) {
const strB = ht . nome . toLowerCase (). trim ();
// Exact match
if ( strA === strB ) {
return {
matchedItem: ht ,
score: 1.0 ,
confidence: 'HIGH' ,
reason: 'Match Exato de Título'
};
}
// Substring match
if ( strA . includes ( strB ) || strB . includes ( strA )) {
const sc = 0.9 ;
if ( sc > highestScore ) {
highestScore = sc ;
bestMatch = ht ;
bestReason = 'Contém Nome Parcial' ;
bestConf = 'HIGH' ;
}
}
// Token-based fuzzy match
const tokensB = tokenize ( ht . nome );
const tokenScore = computeTokenMatch ( tokensA , tokensB );
if ( tokenScore > highestScore ) {
highestScore = tokenScore ;
bestMatch = ht ;
if ( tokenScore > 0.8 ) {
bestConf = 'MEDIUM' ;
bestReason = 'Termos Altamente Similares (Algoritmo)' ;
} else {
bestConf = 'LOW' ;
bestReason = `Plausível Semelhança Textual ( ${ Math . round ( tokenScore * 100 ) } %)` ;
}
}
}
// No good match found
if ( highestScore < 0.4 ) {
return {
matchedItem: null ,
score: 0.05 ,
confidence: 'HIGH' ,
reason: 'Sem incidência detectada nas chaves da Banca'
};
}
return { matchedItem: bestMatch , score: highestScore , confidence: bestConf , reason: bestReason };
}
4. Relevance Score Calculation
// Final score calculation (relevance.js:169-214)
export function calculateFinalRelevance ( editalSubjectCtx ) {
const matchStruct = findBestMatch (
editalSubjectCtx . assuntoNome ,
editalSubjectCtx . disciplinaId
);
if ( ! matchStruct . matchedItem ) {
return { finalScore: 0.0 , priority: 'P3' , matchData: matchStruct };
}
const ht = matchStruct . matchedItem ;
// A. Incidence score (how often the topic appears in exams)
let incidenceScore = 0 ;
if ( ht . weight !== undefined ) {
incidenceScore = ht . weight ; // 0-1 scale
if ( incidenceScore > 1 ) incidenceScore = incidenceScore / 100 ;
} else if ( ht . rank !== undefined ) {
// Rank #1 = highest weight, decays progressively
incidenceScore = 1 / Math . pow ( ht . rank , 0.7 );
} else if ( ht . level !== undefined ) {
// 'ALTA' = 1.0, 'MEDIA' = 0.6, 'BAIXA' = 0.3
incidenceScore = ht . level === 'ALTA' ? 1.0 : ( ht . level === 'MEDIA' ? 0.6 : 0.3 );
}
// B. Combine incidence with match quality
let finalScore = incidenceScore * matchStruct . score ;
// C. Bonus for weak areas (more errors = higher priority)
if ( editalSubjectCtx . erros / ( editalSubjectCtx . acertos + editalSubjectCtx . erros + 1 ) > 0.6 ) {
finalScore += 0.1 ;
}
// D. Assign priority tier
let priority = 'P3' ;
if ( finalScore >= 0.75 ) priority = 'P1' ;
else if ( finalScore >= 0.40 ) priority = 'P2' ;
return {
finalScore: Math . round ( finalScore * 100 ), // 0-100 scale
priority ,
matchData: matchStruct
};
}
Priority Tiers
Priority Score Range Color Meaning P1 75-100 Red Critical - High incidence, study immediatelyP2 40-74 Yellow Important - Moderate incidence, study soonP3 0-39 Gray Low - Rare or no incidence, deprioritize
Applying Ranking to Edital
// Apply ranking to entire exam (relevance.js:217-257)
export function applyRankingToEdital ( editalId ) {
const edt = state . editais . find ( e => e . id === editalId );
if ( ! edt ) return [];
let flatList = [];
// Calculate score for each topic
edt . disciplinas . forEach ( d => {
d . assuntos . forEach ( a => {
const result = calculateFinalRelevance ({
assuntoNome: a . nome ,
disciplinaId: d . id ,
acertos: a . acertos || 0 ,
erros: a . erros || 0
});
flatList . push ({
discId: d . id ,
discNome: d . nome ,
assuntoId: a . id ,
assuntoNome: a . nome ,
... result
});
});
});
// Sort by score (highest first)
flatList . sort (( a , b ) => b . finalScore - a . finalScore );
// Reassign priorities based on percentile
const top20Index = Math . max ( 0 , Math . floor ( flatList . length * 0.2 ) - 1 );
const top60Index = Math . max ( 0 , Math . floor ( flatList . length * 0.6 ) - 1 );
flatList . forEach (( item , index ) => {
if ( index <= top20Index ) item . priority = 'P1' ;
else if ( index <= top60Index ) item . priority = 'P2' ;
else item . priority = 'P3' ;
});
return flatList ;
}
Committing the Ranking
Saves the calculated priorities to the database:
// Persist ranking (relevance.js:260-304)
export function commitEditalOrdering ( editalId , rankedFlatList ) {
const edt = state . editais . find ( e => e . id === editalId );
if ( ! edt ) return false ;
// Group by discipline
const grouped = {};
rankedFlatList . forEach ( item => {
if ( ! grouped [ item . discId ]) grouped [ item . discId ] = [];
grouped [ item . discId ]. push ( item );
});
// Reorder topics within each discipline
edt . disciplinas . forEach ( d => {
const sortedItemsForDisc = grouped [ d . id ] || [];
const newAssuntosArray = [];
sortedItemsForDisc . forEach ( sItem => {
const originalAssunto = d . assuntos . find ( a => a . id === sItem . assuntoId );
if ( originalAssunto ) {
// Inject relevance metadata
originalAssunto . relevance = {
priority: sItem . priority ,
finalScore: sItem . finalScore ,
reason: sItem . matchData . reason ,
confidence: sItem . matchData . confidence
};
newAssuntosArray . push ( originalAssunto );
}
});
// Preserve any topics that weren't ranked
d . assuntos . forEach ( a => {
if ( ! newAssuntosArray . find ( na => na . id === a . id )) {
a . relevance = {
priority: 'P3' ,
finalScore: 0 ,
reason: 'Não Rankeado' ,
confidence: 'LOW'
};
newAssuntosArray . push ( a );
}
});
d . assuntos = newAssuntosArray ;
});
scheduleSave ();
return true ;
}
Using Banca Intelligence
Set Up Hot Topics Database
Navigate to Inteligência de Banca and click “Gerenciar Base de Dados”. Add known high-incidence topics for your exam board. state . bancaRelevance = {
hotTopics: [
{
id: 'ht_1' ,
nome: 'Direitos Fundamentais' ,
weight: 0.95 ,
disciplinaId: null
},
{
id: 'ht_2' ,
nome: 'Controle de Constitucionalidade' ,
rank: 2 ,
disciplinaId: null
}
],
userMappings: {}
};
Run Analysis
Select an edital and click “Analisar Relevância”. The system matches each syllabus topic against the hot topics database.
Review Results
The analysis shows:
Match quality (Exact, High, Medium, Low)
Relevance score (0-100)
Priority tier (P1, P2, P3)
Matching reason
Manual Adjustments (Optional)
Override incorrect matches by clicking “Corrigir” and selecting the correct hot topic or marking as “Sem Incidência”. // Manual mapping (stored in state)
state . bancaRelevance . userMappings [ 'Direitos Sociais' ] = 'ht_5' ;
// or
state . bancaRelevance . userMappings [ 'Tópico Irrelevante' ] = 'NONE' ;
Commit Ranking
Click “Salvar Ordenação” to apply the ranking permanently. Topics are reordered by priority within each discipline.
Study by Priority
Focus on P1 topics first. The Study Organizer and Calendar can filter by priority to optimize your study plan.
Banca Intelligence is only as good as your hot topics database. Ensure you have accurate historical incidence data for best results.
Reverting Rankings
Restore alphabetical order and remove priority tags:
// Revert to alphabetical order (relevance.js:307-324)
export function revertEditalOrdering ( editalId , disciplinaId ) {
const edt = state . editais . find ( e => e . id === editalId );
if ( ! edt ) return false ;
const disc = edt . disciplinas . find ( d => d . id === disciplinaId );
if ( ! disc ) return false ;
// Remove relevance metadata
disc . assuntos . forEach ( a => {
delete a . relevance ;
});
// Sort alphabetically
disc . assuntos . sort (( a , b ) => a . nome . localeCompare ( b . nome ));
scheduleSave ();
return true ;
}
Best Practices
Curate Hot Topics Carefully
Only add topics with verified high incidence. Garbage in = garbage out. Use official exam board statistics if available.
Match wording from official exam notices as closely as possible. This improves fuzzy matching accuracy.
Review Low Confidence Matches
Always check matches with “LOW” confidence. These often need manual correction.
Combine with Performance Data
The algorithm boosts priority for topics where you have high error rates. This ensures weak areas get attention.
Re-run analysis after adding new hot topics or when exam boards release updated statistics.