Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)
Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
/**
|
||||
* LinkedIn Experience Generator
|
||||
*
|
||||
* Generates result-oriented LinkedIn experience descriptions from KB data.
|
||||
* Reads person experiences, skills, and projects, then produces optimized
|
||||
* descriptions with measurable achievements and relevant keywords.
|
||||
*/
|
||||
|
||||
import type { Person, Experience, Organization, Skill, Project } from '../schemas/types';
|
||||
import type { ExperienceOptions, ExperienceResult } from './types';
|
||||
import { validateExperience, EXPERIENCE_CHAR_LIMIT } from './constraints';
|
||||
import { readEntity } from '../io/entity-files';
|
||||
|
||||
// --- Internal Types ---
|
||||
|
||||
interface ExperienceData {
|
||||
experience: Experience;
|
||||
orgName: string;
|
||||
skills: Skill[];
|
||||
projects: Project[];
|
||||
}
|
||||
|
||||
// --- Experience Positioning Strategies ---
|
||||
|
||||
/**
|
||||
* Maps experience IDs to positioning strategies that generate
|
||||
* result-oriented descriptions instead of task lists.
|
||||
*/
|
||||
const POSITIONING_STRATEGIES: Record<string, (data: ExperienceData, keywords: string[]) => PositionedContent> = {
|
||||
'db-infrago-digitalisierung': positionDbInfraGo,
|
||||
'data-hive-cassel-gruender': positionDataHive,
|
||||
'db-netz-einfachbahn-jobsharing': positionJobSharing,
|
||||
'db-netz-einfachbahn-leiter': positionEinfachbahnLeiter,
|
||||
'uni-kassel-teilgruppenleiter': positionAcademicLeader,
|
||||
'uni-kassel-doktorand': positionAcademicFoundation,
|
||||
'uni-kassel-dozent-physik': positionAcademicTeaching,
|
||||
'hochschule-fresenius-dozent': positionFreseniusDozent,
|
||||
};
|
||||
|
||||
interface PositionedContent {
|
||||
description: string;
|
||||
achievements: string[];
|
||||
keywords: string[];
|
||||
}
|
||||
|
||||
// --- Public API ---
|
||||
|
||||
/**
|
||||
* Generates LinkedIn experience descriptions from KB data.
|
||||
*
|
||||
* Reads the person's experiences, enriches them with skills and projects,
|
||||
* and produces result-oriented descriptions validated against LinkedIn constraints.
|
||||
*/
|
||||
export async function generateExperiences(options: ExperienceOptions): Promise<ExperienceResult[]> {
|
||||
const { personId, experienceIds, keywords = [] } = options;
|
||||
|
||||
const person = (await readEntity('person', personId)) as Person;
|
||||
const targetIds = experienceIds ?? person.experiences ?? [];
|
||||
|
||||
const results: ExperienceResult[] = [];
|
||||
|
||||
for (const expId of targetIds) {
|
||||
try {
|
||||
const data = await loadExperienceData(expId);
|
||||
const positioned = generatePositionedContent(data, keywords);
|
||||
const description = truncateToLimit(positioned.description);
|
||||
const validation = validateExperience(description);
|
||||
|
||||
results.push({
|
||||
experienceId: expId,
|
||||
title: data.experience.title,
|
||||
organization: data.orgName,
|
||||
description,
|
||||
keywords: positioned.keywords,
|
||||
achievements: positioned.achievements,
|
||||
validation,
|
||||
});
|
||||
} catch {
|
||||
// Skip experiences that cannot be loaded
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// --- Data Loading ---
|
||||
|
||||
async function loadExperienceData(expId: string): Promise<ExperienceData> {
|
||||
const experience = (await readEntity('experience', expId)) as Experience;
|
||||
|
||||
let orgName = experience.organization;
|
||||
try {
|
||||
const org = (await readEntity('organization', experience.organization)) as Organization;
|
||||
orgName = org.name;
|
||||
} catch {
|
||||
// Use raw org ID as fallback
|
||||
}
|
||||
|
||||
const skills: Skill[] = [];
|
||||
for (const skillId of experience.skillsUsed ?? []) {
|
||||
try {
|
||||
const skill = (await readEntity('skill', skillId)) as Skill;
|
||||
skills.push(skill);
|
||||
} catch {
|
||||
// Skip missing skills
|
||||
}
|
||||
}
|
||||
|
||||
const projects: Project[] = [];
|
||||
for (const projId of experience.projects ?? []) {
|
||||
try {
|
||||
const project = (await readEntity('project', projId)) as Project;
|
||||
projects.push(project);
|
||||
} catch {
|
||||
// Skip missing projects
|
||||
}
|
||||
}
|
||||
|
||||
return { experience, orgName, skills, projects };
|
||||
}
|
||||
|
||||
// --- Content Generation ---
|
||||
|
||||
function generatePositionedContent(data: ExperienceData, requestedKeywords: string[]): PositionedContent {
|
||||
const strategy = POSITIONING_STRATEGIES[data.experience.id];
|
||||
|
||||
if (strategy) {
|
||||
return strategy(data, requestedKeywords);
|
||||
}
|
||||
|
||||
return generateDefaultContent(data, requestedKeywords);
|
||||
}
|
||||
|
||||
// --- Positioning Strategies ---
|
||||
|
||||
function positionDbInfraGo(data: ExperienceData, requestedKeywords: string[]): PositionedContent {
|
||||
const description = [
|
||||
'Digitalen Kundennutzen schaffen — mit einem Blick für Technologie und Menschen.',
|
||||
'',
|
||||
'Als Experte für Digitalisierung bei DB InfraGO treibe ich die digitale Transformation der Schieneninfrastruktur voran. Mein Fokus: Projekte effizient zum Abschluss bringen und das System Schiene zukunftsfähig machen.',
|
||||
'',
|
||||
'Schwerpunkte:',
|
||||
'→ Digitale Transformation großer Infrastrukturprojekte',
|
||||
'→ Change Management an der Schnittstelle von IT und Fachbereichen',
|
||||
'→ Teamführung mit Fokus auf Ergebnisse und Wirkung',
|
||||
].join('\n');
|
||||
|
||||
const achievements = [
|
||||
'Digitale Transformation der Schieneninfrastruktur vorangetrieben',
|
||||
'Effiziente Projektsteuerung in komplexem Konzernumfeld',
|
||||
];
|
||||
|
||||
const keywords = mergeKeywords(
|
||||
['Digitalisierung', 'Digitale Transformation', 'Change Management', 'Infrastruktur', 'DB', 'Schienenverkehr'],
|
||||
requestedKeywords,
|
||||
);
|
||||
|
||||
return { description, achievements, keywords };
|
||||
}
|
||||
|
||||
function positionDataHive(data: ExperienceData, requestedKeywords: string[]): PositionedContent {
|
||||
const description = [
|
||||
'Wir hassen Verschwendung und lieben Wandel.',
|
||||
'',
|
||||
'Als Gründer von Data Hive Cassel verbinde ich KI-Expertise mit echtem Veränderungswillen. Mein Anspruch: nicht nur technische Lösungen liefern, sondern nachhaltigen Wandel ermöglichen.',
|
||||
'',
|
||||
'Ergebnisse:',
|
||||
'→ 50+ Projekte erfolgreich umgesetzt',
|
||||
'→ 15+ Kunden aus Mittelstand und öffentlichem Sektor',
|
||||
'→ ROI > 2 für Kunden durch datengetriebene Optimierung',
|
||||
'→ KI praxistauglich gemacht — von der Strategie bis zur Implementierung',
|
||||
'',
|
||||
'Schwerpunkte: KI-Beratung, Prozessautomatisierung, Energieeffizienz, Change Management',
|
||||
].join('\n');
|
||||
|
||||
const achievements = [
|
||||
'50+ Projekte erfolgreich umgesetzt',
|
||||
'15+ Kunden aus Mittelstand und öffentlichem Sektor',
|
||||
'ROI > 2 für Kunden durch datengetriebene Optimierung',
|
||||
'KI praxistauglich gemacht — von der Strategie bis zur Implementierung',
|
||||
];
|
||||
|
||||
const keywords = mergeKeywords(
|
||||
['Künstliche Intelligenz', 'KI-Beratung', 'Startup', 'Gründer', 'Prozessautomatisierung', 'Change Management', 'Energieeffizienz', 'ROI'],
|
||||
requestedKeywords,
|
||||
);
|
||||
|
||||
return { description, achievements, keywords };
|
||||
}
|
||||
|
||||
function positionJobSharing(data: ExperienceData, requestedKeywords: string[]): PositionedContent {
|
||||
const description = [
|
||||
'Führung ist keine One-Man-Show — und das haben wir bewiesen.',
|
||||
'',
|
||||
'Im JobSharing-Tandem mit Claudia Froldi haben wir geteilte Führung auf Augenhöhe im Konzernumfeld etabliert. Ein bewusstes Experiment: Gehaltsverzicht für ein innovatives Führungsmodell.',
|
||||
'',
|
||||
'Ergebnisse:',
|
||||
'→ Shared Leadership als Führungsinnovation im DB-Konzern etabliert',
|
||||
'→ Komplementäre Stärken: Fachliche Führung + disziplinarische Führung',
|
||||
'→ Team #Einfachbahn erfolgreich durch Transformation begleitet',
|
||||
'→ Vorbild für neue Arbeitsmodelle in der Bahnbranche',
|
||||
].join('\n');
|
||||
|
||||
const achievements = [
|
||||
'Shared Leadership als Führungsinnovation im DB-Konzern etabliert',
|
||||
'Bewusster Gehaltsverzicht für geteilte Führung auf Augenhöhe',
|
||||
'Team erfolgreich durch Transformation begleitet',
|
||||
];
|
||||
|
||||
const keywords = mergeKeywords(
|
||||
['Shared Leadership', 'JobSharing', 'Führungsinnovation', 'New Work', 'Tandem-Führung', 'Transformation'],
|
||||
requestedKeywords,
|
||||
);
|
||||
|
||||
return { description, achievements, keywords };
|
||||
}
|
||||
|
||||
function positionEinfachbahnLeiter(data: ExperienceData, requestedKeywords: string[]): PositionedContent {
|
||||
const description = [
|
||||
'Die Digitalisierung auf die Schiene bringen — eines der ambitioniertesten Vorhaben im Konzernumfeld.',
|
||||
'',
|
||||
'Als Leiter #Einfachbahn habe ich ein interdisziplinäres Team aufgebaut und zu Höchstleistungen geführt. Agile Methoden und innovative Arbeitsformen etabliert, Silos aufgebrochen.',
|
||||
'',
|
||||
'Ergebnisse:',
|
||||
'→ Interdisziplinäres Team aufgebaut und agile Methoden etabliert',
|
||||
'→ Kultur des offenen Lernens geschaffen',
|
||||
'→ Technische und kulturelle Transformation nachhaltig vorangetrieben',
|
||||
].join('\n');
|
||||
|
||||
const achievements = data.experience.achievements ?? [
|
||||
'Interdisziplinäres Team aufgebaut und agile Methoden etabliert',
|
||||
'Silos aufgebrochen und Kultur des offenen Lernens geschaffen',
|
||||
];
|
||||
|
||||
const keywords = mergeKeywords(
|
||||
['Digitalisierung', 'Agile Führung', 'Transformation', 'Teamaufbau', 'Change Management'],
|
||||
requestedKeywords,
|
||||
);
|
||||
|
||||
return { description, achievements, keywords };
|
||||
}
|
||||
|
||||
function positionAcademicLeader(data: ExperienceData, requestedKeywords: string[]): PositionedContent {
|
||||
const description = [
|
||||
'Vom Labor in die Praxis — hier wurde das Fundament für meine KI-Expertise gelegt.',
|
||||
'',
|
||||
'Als Teilgruppenleiter Spektroskopie habe ich Forschung, Teamführung und Technologieentwicklung vereint. Machine Learning seit 2012 in der Praxis angewandt — lange bevor es Mainstream wurde.',
|
||||
'',
|
||||
'Ergebnisse:',
|
||||
'→ 50+ wissenschaftliche Veröffentlichungen, 2.000+ Zitationen',
|
||||
'→ 12 Mio. € Drittmittel eingeworben (DFG, BMBF, EU)',
|
||||
'→ Internationale Forschungsteams aufgebaut und geführt',
|
||||
'→ ML/PyTorch seit 2012 für Datenanalyse eingesetzt',
|
||||
].join('\n');
|
||||
|
||||
const achievements = [
|
||||
'50+ wissenschaftliche Veröffentlichungen, 2.000+ Zitationen',
|
||||
'12 Mio. € Drittmittel eingeworben (DFG, BMBF, EU)',
|
||||
'Internationale Forschungsteams aufgebaut und geführt',
|
||||
'ML/PyTorch seit 2012 für Datenanalyse eingesetzt',
|
||||
];
|
||||
|
||||
const keywords = mergeKeywords(
|
||||
['Machine Learning', 'Forschung', 'Künstliche Intelligenz', 'Teamführung', 'Drittmittel', 'Publikationen'],
|
||||
requestedKeywords,
|
||||
);
|
||||
|
||||
return { description, achievements, keywords };
|
||||
}
|
||||
|
||||
function positionAcademicFoundation(data: ExperienceData, requestedKeywords: string[]): PositionedContent {
|
||||
const description = [
|
||||
'Wissenschaftliche Exzellenz als Grundlage für praxisnahe KI-Anwendungen.',
|
||||
'',
|
||||
'Promotion in Atom- und Molekülphysik mit Fokus auf experimentelle Methoden und Datenanalyse. Die analytische Denkweise und der systematische Ansatz prägen bis heute meine Arbeit mit KI und Digitalisierung.',
|
||||
'',
|
||||
'Ergebnisse:',
|
||||
'→ 10 wissenschaftliche Paper und 10 internationale Konferenzbeiträge',
|
||||
'→ Elektronenstrahlquelle von Simulation bis Konstruktion aufgebaut',
|
||||
'→ Grundstein für 13+ Jahre KI-Erfahrung gelegt',
|
||||
].join('\n');
|
||||
|
||||
const achievements = [
|
||||
'10 wissenschaftliche Paper und 10 internationale Konferenzbeiträge',
|
||||
'Elektronenstrahlquelle von Simulation bis Konstruktion aufgebaut',
|
||||
'Grundstein für 13+ Jahre KI-Erfahrung gelegt',
|
||||
];
|
||||
|
||||
const keywords = mergeKeywords(
|
||||
['Promotion', 'Physik', 'Forschung', 'Datenanalyse', 'Wissenschaft'],
|
||||
requestedKeywords,
|
||||
);
|
||||
|
||||
return { description, achievements, keywords };
|
||||
}
|
||||
|
||||
function positionAcademicTeaching(data: ExperienceData, requestedKeywords: string[]): PositionedContent {
|
||||
const description = [
|
||||
'Komplexe Themen verständlich machen — eine Kompetenz, die von der Physik bis zur KI-Beratung trägt.',
|
||||
'',
|
||||
'Als Dozent für experimentelle Atom- und Molekülphysik habe ich gelernt, anspruchsvolle Inhalte so aufzubereiten, dass sie begeistern und verstanden werden.',
|
||||
].join('\n');
|
||||
|
||||
const achievements = [
|
||||
'Komplexe wissenschaftliche Inhalte verständlich vermittelt',
|
||||
];
|
||||
|
||||
const keywords = mergeKeywords(
|
||||
['Lehre', 'Wissensvermittlung', 'Physik', 'Universität'],
|
||||
requestedKeywords,
|
||||
);
|
||||
|
||||
return { description, achievements, keywords };
|
||||
}
|
||||
|
||||
function positionFreseniusDozent(data: ExperienceData, requestedKeywords: string[]): PositionedContent {
|
||||
const description = [
|
||||
'Agilität, Change und Innovation erlebbar machen — nicht nur theoretisch, sondern am eigenen Leib.',
|
||||
'',
|
||||
'Als Dozent an der Hochschule Fresenius habe ich MBA-Studierenden gezeigt, warum Agilität, Change, Innovation und Führung zusammengehören. Praxisnah, interaktiv und mit echten Erfahrungen aus Konzern und Startup.',
|
||||
].join('\n');
|
||||
|
||||
const achievements = [
|
||||
'MBA-Studierende für Agilität und Innovation begeistert',
|
||||
'Praxiswissen aus Konzern und Startup in die Lehre eingebracht',
|
||||
];
|
||||
|
||||
const keywords = mergeKeywords(
|
||||
['Agilität', 'Change Management', 'Innovation', 'Führung', 'Lehre', 'MBA'],
|
||||
requestedKeywords,
|
||||
);
|
||||
|
||||
return { description, achievements, keywords };
|
||||
}
|
||||
|
||||
// --- Default Content Generation ---
|
||||
|
||||
function generateDefaultContent(data: ExperienceData, requestedKeywords: string[]): PositionedContent {
|
||||
const description = data.experience.description?.trim() ?? data.experience.title;
|
||||
const achievements = data.experience.achievements ?? [];
|
||||
const keywords = mergeKeywords(
|
||||
extractSkillKeywords(data.skills),
|
||||
requestedKeywords,
|
||||
);
|
||||
|
||||
return { description, achievements, keywords };
|
||||
}
|
||||
|
||||
// --- Keyword Helpers ---
|
||||
|
||||
const SKILL_KEYWORD_MAP: Record<string, string> = {
|
||||
'kuenstliche-intelligenz': 'Künstliche Intelligenz',
|
||||
'change-management': 'Change Management',
|
||||
'digitale-transformation': 'Digitale Transformation',
|
||||
'shared-leadership': 'Shared Leadership',
|
||||
'agilitaet': 'Agilität',
|
||||
'coaching': 'Coaching',
|
||||
'team-management': 'Teamführung',
|
||||
'it-strategie': 'IT-Strategie',
|
||||
};
|
||||
|
||||
function extractSkillKeywords(skills: Skill[]): string[] {
|
||||
const keywords: string[] = [];
|
||||
for (const skill of skills) {
|
||||
const mapped = SKILL_KEYWORD_MAP[skill.id];
|
||||
if (mapped) {
|
||||
keywords.push(mapped);
|
||||
} else {
|
||||
keywords.push(skill.name);
|
||||
}
|
||||
}
|
||||
return keywords;
|
||||
}
|
||||
|
||||
function mergeKeywords(base: string[], requested: string[]): string[] {
|
||||
const merged = new Set<string>(base);
|
||||
for (const kw of requested) {
|
||||
merged.add(kw);
|
||||
}
|
||||
return Array.from(merged);
|
||||
}
|
||||
|
||||
// --- Constraint Helpers ---
|
||||
|
||||
function truncateToLimit(text: string): string {
|
||||
if (text.length <= EXPERIENCE_CHAR_LIMIT) {
|
||||
return text;
|
||||
}
|
||||
const truncated = text.slice(0, EXPERIENCE_CHAR_LIMIT - 1);
|
||||
const lastNewline = truncated.lastIndexOf('\n');
|
||||
if (lastNewline > EXPERIENCE_CHAR_LIMIT * 0.7) {
|
||||
return truncated.slice(0, lastNewline);
|
||||
}
|
||||
const lastSpace = truncated.lastIndexOf(' ');
|
||||
if (lastSpace > EXPERIENCE_CHAR_LIMIT * 0.7) {
|
||||
return truncated.slice(0, lastSpace) + '…';
|
||||
}
|
||||
return truncated + '…';
|
||||
}
|
||||
Reference in New Issue
Block a user