Matematisk Djupdykning

Algoritmen

Formell specifikation av NordiqFlow:s matchningsalgoritm, graf-traversering, och prediktiva modeller med fullstandig matematisk notation.

Huvudscoring-Funktionen

Den centrala scoring-funktionen som rankar alla karriaroverganger for en given kandidat.

Definition 1: Composite Career Score
S(c, t) = w1 · sub(c, t) + w2 · M(c, t) + w3 · D(t) + w4 · K(t) + w5 · ΔL(c, t)
Dar:
S(c,t) Total score for kandidat c till malyrke t
sub(c,t) Substitutability-varde fran AF (0-100)
M(c,t) Skill match-procent
D(t) Efterfrageprognos (Yrkesbarometer)
K(t) Nuvarande marknadssituation
ΔL(c,t) Normaliserad loneskillnad
Standardvikter:
w = [0.30, 0.30, 0.20, 0.10, 0.10]T    dar    Σwi = 1

Varfor dessa vikter?

Substitutability (w1=30%) och Skill Match (w2=30%) ar karnmetrikerna - de mater faktisk kompetensoverlapp. Efterfrageprognos (w3=20%) saker att vi rekommenderar yrken med framtida anstallningsbarhet. Lonedelta (w5=10%) ger ekonomiskt incitament utan att dominera rekommendationerna.

Skill Matching: Mangdlara

Berakning av kompetensoverlapp med hjararkisk expansion.

Definition 2: Skill Match Score
M(c, t) = |Sc ∩ Rt| / |Rt|
Dar:
  • Sc = Mangden av kandidatens kompetenser (concept_ids)
  • Rt = Mangden av malyrkets kravda kompetenser
  • |·| = Kardinalitet (antal element)

Hierarkisk Expansion

For att hantera granularitetsgapet expanderar vi kandidatens kompetenser uppat i taxonomin:

Sc* = Sc ∪ { parent(s) : s ∈ Sc } ∪ { parent(parent(s)) : s ∈ Sc }

Exempel: Om kandidaten har "Google Ads" expanderar vi till: {"Google Ads", "PPC", "Digital Marketing", "Marketing"}

# Implementering av hierarkisk skill-expansion def expand_skills(skills: Set[str], taxonomy: Graph, max_depth: int = 2) -> Set[str]: expanded = set(skills) current_level = skills for depth in range(max_depth): parents = set() for skill in current_level: if taxonomy.has_parent(skill): parents.add(taxonomy.get_parent(skill)) expanded |= parents current_level = parents return expanded # Berakna skill match med expansion def skill_match(candidate_skills, job_requirements, taxonomy): expanded = expand_skills(candidate_skills, taxonomy) intersection = expanded & job_requirements return len(intersection) / len(job_requirements) if job_requirements else 0

Graf-Traversering

Karriaroverganger modelleras som en viktad riktad graf.

Grafnotation

G = (V, E)
Karriargrafen
V = yrken, E = overgangsmojligheter
w(e)
Kantvikt
Substitutability-varde (25, 50, 75)
N(v)
Grannar
Direkt narbara yrken
d(u,v)
Avstand
Kortaste vagen i hopp
Definition 3: Viktad BFS (Breadth-First Search)
Kandidater(c, k) = { v ∈ V : d(occ(c), v) ≤ k ∧ Πe ∈ path w(e) ≥ θ }
Tolkning: Alla yrken v som kan nas fran kandidatens nuvarande yrke inom k hopp, dar den kumulativa substitutability-produkten overskrider troskel θ.

Traverseringsalgoritm

# Modifierad BFS med viktade kanter def find_career_paths(graph, start_occupation, max_hops=2, min_score=0.25): results = [] queue = [(start_occupation, 1.0, [])] # (node, cumulative_score, path) visited = {start_occupation} while queue: current, score, path = queue.pop(0) for neighbor, edge_weight in graph.get_transitions(current): new_score = score * (edge_weight / 100) # Normalize 0-1 if neighbor not in visited and new_score >= min_score: new_path = path + [(current, neighbor, edge_weight)] if len(new_path) <= max_hops: results.append({ 'target': neighbor, 'path_score': new_score, 'path': new_path }) visited.add(neighbor) queue.append((neighbor, new_score, new_path)) return sorted(results, key=lambda x: x['path_score'], reverse=True)

Komplexitetsanalys

Operation Tidskomplexitet Kommentar
BFS traversering O(V + E) V=10,000 yrken, E=51,000 relationer
Skill match (per yrke) O(|S| log |S|) Set intersection med sorterade listor
Total ranking O(n log n) Sortering av kandidater
Hierarkisk expansion O(|S| · d) d = max djup (vanligtvis 2)

Substitutability-Matrisen

Arbetsformedlingens forberaknade yrkes-till-yrkes-relationer som en adjacensmatris.

Definition 4: Substitutability Matrix
A =
0
75
50
...
75
0
25
...
50
25
0
...
...
...
...
0
∈ Rn×n
n = 10,000+ yrken | Aij ∈ {0, 25, 50, 75}
Egenskaper:
  • Matrisen ar icke-symmetrisk: Aij ≠ Aji i allmanhet
  • Diagonal: Aii = 0 (ingen overgang till samma yrke)
  • Gles matris: ~51,000 icke-noll element av 100M mojliga

Icke-symmetri: Varfor riktning spelar roll

En Butikschef har hog substitutability mot Verksamhetsledare (Abutik,verk = 75), men omvant ar det lagre (Averk,butik = 50). Detta beror pa att:

  • Butikschefen har ledarskap + kundfokus + saljkunskap
  • Verksamhetsledaren har mer specifik vardkompetens
  • Det ar lattare att generalisera an att specialisera

Prediktiva Modeller

Efterfrageprediktion och loneprognoser.

Efterfrageprognos: Bayesiansk Uppdatering

P(Dt+1 | data) = P(data | Dt+1) · P(Dt+1) / P(data)

Vi kombinerar Yrkesbarometerns 5-ars prognos (prior) med realtidsdata fran JobStream API:t (likelihood) for att fa uppdaterade efterfrageprognoser.

Definition 5: Normaliserat Lonedelta
ΔL(c, t) = (Lt - Lc) / σL
Dar:
  • Lt = Medianlone for malyrket
  • Lc = Kandidatens nuvarande (uppskattade) lone
  • σL = Standardavvikelse for loneskillnader (for normalisering)
1

Data Aggregering

Varje natt aggregerar vi jobblistningar fran JobSearch API och beraknar antal lediga jobb per yrke (SSYK-kod) och region (NUTS-3).

demand[ssyk][region] = count(active_listings)
2

Trendanalys

Med 10+ ar av historisk data (Historical Ads API) beraknar vi trender med exponentiell utjamning:

Tt = α · Dt + (1 - α) · Tt-1
3

Ensemble-prediktion

Slutlig efterfrageprognos kombinerar tre kallor:

  • Yrkesbarometerns expertbedomning (40%)
  • Historisk trend (35%)
  • Realtids-momentum (25%)

Komplett Algoritmflode

# NordiqFlow - Huvudalgoritm (pseudokod) def recommend_careers(candidate: Candidate, top_k: int = 10) -> List[Recommendation]: """ Huvudfunktion for karriarrekommendationer. Args: candidate: Kandidatobjekt med skills, nuvarande yrke, lon top_k: Antal rekommendationer att returnera Returns: Lista av Recommendation-objekt sorterade efter score """ # Steg 1: Expandera kandidatens kompetenser hierarkiskt expanded_skills = expand_skills(candidate.skills, taxonomy, max_depth=2) # Steg 2: Hitta potentiella malyrken via graf-traversering potential_targets = find_career_paths( graph=substitutability_graph, start=candidate.current_occupation, max_hops=2, min_score=0.25 ) # Steg 3: Berakna composite score for varje malyrke scored_targets = [] for target in potential_targets: score = compute_score( sub=target.substitutability, match=skill_match(expanded_skills, target.required_skills), demand=get_demand_forecast(target.occupation_id), market=get_current_market(target.occupation_id), salary_delta=normalize_salary_delta(candidate.salary, target.median_salary) ) scored_targets.append((target, score)) # Steg 4: Sortera och returnera top-k scored_targets.sort(key=lambda x: x[1], reverse=True) return [ Recommendation( occupation=t.occupation, score=s, skill_gap=compute_gap(expanded_skills, t.required_skills), training_time=estimate_training(gap) ) for t, s in scored_targets[:top_k] ]