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:
2026-06-30 20:39:52 +02:00
parent 2f2b295531
commit a5f8fb49ab
1717 changed files with 447332 additions and 0 deletions
+159
View File
@@ -0,0 +1,159 @@
/**
* Completeness Score — calculates how complete an entity's profile is.
*
* Pure function, no I/O. Uses the same field definitions as gap-detection.ts.
*/
import type { Entity, EntityType } from '../schemas/types';
// --- Types ---
export interface CompletenessResult {
/** Completeness ratio from 0.0 to 1.0 */
score: number;
/** Number of filled attributes */
filledCount: number;
/** Total number of defined attributes */
totalCount: number;
/** List of missing field names */
missingFields: string[];
}
// --- Field Definitions per Entity Type ---
interface FieldDef {
field: string;
description: string;
}
const PERSON_FIELDS: FieldDef[] = [
{ field: 'name.first', description: 'First name' },
{ field: 'name.last', description: 'Last name' },
{ field: 'name.display', description: 'Display name' },
{ field: 'contact', description: 'Contact information' },
{ field: 'summary', description: 'Professional summary' },
{ field: 'languages', description: 'Languages spoken' },
{ field: 'education', description: 'Education history' },
{ field: 'skills', description: 'Skills references' },
{ field: 'experiences', description: 'Experience references' },
{ field: 'certifications', description: 'Certification references' },
];
const EXPERIENCE_FIELDS: FieldDef[] = [
{ field: 'title', description: 'Job title' },
{ field: 'organization', description: 'Organization reference' },
{ field: 'start', description: 'Start date' },
{ field: 'description', description: 'Role description' },
{ field: 'achievements', description: 'Achievements list' },
{ field: 'skillsUsed', description: 'Skills used in this role' },
];
const SKILL_FIELDS: FieldDef[] = [
{ field: 'name', description: 'Skill name' },
{ field: 'category', description: 'Skill category' },
{ field: 'level', description: 'Proficiency level' },
{ field: 'years', description: 'Years of experience' },
{ field: 'context', description: 'Context of skill usage' },
];
const ORGANIZATION_FIELDS: FieldDef[] = [
{ field: 'name', description: 'Organization name' },
{ field: 'industry', description: 'Industry sector' },
{ field: 'parent', description: 'Parent organization' },
{ field: 'website', description: 'Website URL' },
];
const PROJECT_FIELDS: FieldDef[] = [
{ field: 'name', description: 'Project name' },
{ field: 'organization', description: 'Organization reference' },
{ field: 'start', description: 'Start date' },
{ field: 'description', description: 'Project description' },
{ field: 'end', description: 'End date' },
{ field: 'skillsUsed', description: 'Skills used in this project' },
{ field: 'persons', description: 'Persons involved' },
];
const CERTIFICATION_FIELDS: FieldDef[] = [
{ field: 'name', description: 'Certification name' },
{ field: 'issuer', description: 'Issuing organization' },
{ field: 'date', description: 'Date obtained' },
{ field: 'person', description: 'Person reference' },
{ field: 'expires', description: 'Expiration date' },
];
const TANDEM_FIELDS: FieldDef[] = [
{ field: 'partners', description: 'Partner references (exactly 2)' },
{ field: 'sharedVision', description: 'Shared professional vision' },
{ field: 'complementarySkills', description: 'Complementary skills description' },
{ field: 'collaborationModel', description: 'Collaboration model description' },
{ field: 'combinedNarrative', description: 'Combined professional narrative' },
{ field: 'jointCompetencies', description: 'Joint competencies list' },
];
const FIELD_DEFS: Record<EntityType, FieldDef[]> = {
person: PERSON_FIELDS,
experience: EXPERIENCE_FIELDS,
skill: SKILL_FIELDS,
organization: ORGANIZATION_FIELDS,
project: PROJECT_FIELDS,
certification: CERTIFICATION_FIELDS,
tandem: TANDEM_FIELDS,
};
// --- Helpers ---
/**
* Resolves a dot-notation field path on an object.
*/
function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
const parts = path.split('.');
let current: unknown = obj;
for (const part of parts) {
if (current === null || current === undefined || typeof current !== 'object') {
return undefined;
}
current = (current as Record<string, unknown>)[part];
}
return current;
}
/**
* Checks whether a value counts as "filled" (not missing/empty).
*/
function isFilled(value: unknown): boolean {
if (value === undefined || value === null) return false;
if (typeof value === 'string' && value.trim() === '') return false;
if (Array.isArray(value) && value.length === 0) return false;
return true;
}
// --- Main Function ---
/**
* Calculates the completeness score for an entity.
* Score = filledCount / totalCount (ratio of filled to total defined attributes).
*/
export function calculateCompleteness(entity: Entity): CompletenessResult {
const fieldDefs = FIELD_DEFS[entity.type];
if (!fieldDefs || fieldDefs.length === 0) {
return { score: 1.0, filledCount: 0, totalCount: 0, missingFields: [] };
}
const obj = entity as unknown as Record<string, unknown>;
const missingFields: string[] = [];
let filledCount = 0;
for (const def of fieldDefs) {
const value = getNestedValue(obj, def.field);
if (isFilled(value)) {
filledCount++;
} else {
missingFields.push(def.field);
}
}
const totalCount = fieldDefs.length;
const score = totalCount > 0 ? filledCount / totalCount : 1.0;
return { score, filledCount, totalCount, missingFields };
}
+97
View File
@@ -0,0 +1,97 @@
/**
* Prioritization — ranks entities for review based on completeness and staleness.
*
* Pure function, no I/O.
* Priority formula: priority = (1 - completenessScore) * 0.6 + staleness * 0.4
* where staleness = normalized days since modification (0 to 1, capped at 365 days).
*/
import type { Entity, EntityType } from '../schemas/types';
import type { CompletenessResult } from './completeness-score';
// --- Types ---
export interface PrioritizedEntity {
entityId: string;
entityType: EntityType;
completenessScore: number;
lastModified: string;
/** Higher value = more urgent */
priority: number;
/** Human-readable explanation */
reason: string;
}
// --- Constants ---
const MAX_STALENESS_DAYS = 365;
const COMPLETENESS_WEIGHT = 0.6;
const STALENESS_WEIGHT = 0.4;
// --- Main Function ---
/**
* Prioritizes entities for review.
* Lower completeness + older modification date = higher priority.
* Returns sorted list (highest priority first).
*/
export function prioritizeForReview(
entities: Array<{ entity: Entity; completeness: CompletenessResult }>,
now: Date = new Date()
): PrioritizedEntity[] {
const results: PrioritizedEntity[] = entities.map(({ entity, completeness }) => {
const lastModified = entity.modified;
const daysSinceModified = getDaysSince(lastModified, now);
const staleness = Math.min(daysSinceModified / MAX_STALENESS_DAYS, 1.0);
const priority =
(1 - completeness.score) * COMPLETENESS_WEIGHT +
staleness * STALENESS_WEIGHT;
const reason = buildReason(completeness.score, daysSinceModified);
return {
entityId: entity.id,
entityType: entity.type,
completenessScore: completeness.score,
lastModified,
priority,
reason,
};
});
// Sort by priority descending (highest priority first)
results.sort((a, b) => b.priority - a.priority);
return results;
}
// --- Helpers ---
function getDaysSince(isoDate: string, now: Date): number {
const modified = new Date(isoDate);
const diffMs = now.getTime() - modified.getTime();
return Math.max(0, Math.floor(diffMs / (1000 * 60 * 60 * 24)));
}
function buildReason(completenessScore: number, daysSinceModified: number): string {
const parts: string[] = [];
if (completenessScore < 0.5) {
parts.push(`low completeness (${Math.round(completenessScore * 100)}%)`);
} else if (completenessScore < 0.8) {
parts.push(`moderate completeness (${Math.round(completenessScore * 100)}%)`);
}
if (daysSinceModified > 180) {
parts.push(`not updated in ${daysSinceModified} days`);
} else if (daysSinceModified > 90) {
parts.push(`last updated ${daysSinceModified} days ago`);
}
if (parts.length === 0) {
return 'Routine review';
}
return parts.join('; ');
}
+219
View File
@@ -0,0 +1,219 @@
import { describe, it, expect } from 'vitest';
import fc from 'fast-check';
import { calculateCompleteness } from './completeness-score';
import { prioritizeForReview } from './prioritization';
import type { Person, Entity } from '../schemas/types';
// --- Arbitraries ---
/** Generates a valid kebab-case ID */
const kebabCaseId = fc
.array(
fc.stringMatching(/^[a-z0-9]{1,8}$/),
{ minLength: 1, maxLength: 4 }
)
.map(parts => parts.join('-'));
/** Generates a valid ISO date string */
const isoDate = fc
.record({
year: fc.integer({ min: 1990, max: 2030 }),
month: fc.integer({ min: 1, max: 12 }),
})
.map(({ year, month }) => `${year}-${String(month).padStart(2, '0')}`);
/** Generates a Person entity with varying completeness */
const personWithVaryingCompleteness = fc.record({
id: kebabCaseId,
created: isoDate,
modified: isoDate,
hasContact: fc.boolean(),
hasSummary: fc.boolean(),
hasLanguages: fc.boolean(),
hasEducation: fc.boolean(),
hasSkills: fc.boolean(),
hasExperiences: fc.boolean(),
hasCertifications: fc.boolean(),
}).map(fields => {
const person: Record<string, unknown> = {
id: fields.id,
type: 'person',
created: fields.created,
modified: fields.modified,
name: { first: 'Andre', last: 'Knie', display: 'Andre Knie' },
};
if (fields.hasContact) person.contact = { email: 'test@example.com' };
if (fields.hasSummary) person.summary = 'A professional summary.';
if (fields.hasLanguages) person.languages = [{ language: 'German', level: 'native' }];
if (fields.hasEducation) person.education = [{ institution: 'TU', degree: 'MSc', field: 'CS', start: '2000-09', end: '2004-06' }];
if (fields.hasSkills) person.skills = ['python', 'typescript'];
if (fields.hasExperiences) person.experiences = ['exp-1'];
if (fields.hasCertifications) person.certifications = ['cert-1'];
return person as unknown as Person;
});
/** Generates a fully complete Person entity */
const fullPerson = fc.record({
id: kebabCaseId,
created: isoDate,
modified: isoDate,
}).map(fields => ({
id: fields.id,
type: 'person' as const,
created: fields.created,
modified: fields.modified,
name: { first: 'Andre', last: 'Knie', display: 'Andre Knie' },
contact: { email: 'test@example.com', linkedin: 'linkedin.com/in/test' },
summary: 'Professional summary text.',
languages: [{ language: 'German', level: 'native' }],
education: [{ institution: 'TU Berlin', degree: 'MSc', field: 'CS', start: '2000-09', end: '2004-06' }],
skills: ['python', 'typescript'],
experiences: ['exp-1', 'exp-2'],
certifications: ['cert-1'],
}));
/** Generates a minimal Person entity (only required header + name) */
const emptyPerson = fc.record({
id: kebabCaseId,
created: isoDate,
modified: isoDate,
}).map(fields => ({
id: fields.id,
type: 'person' as const,
created: fields.created,
modified: fields.modified,
name: { first: 'Andre', last: 'Knie', display: 'Andre Knie' },
}));
// --- Property 13: Completeness score calculation ---
describe('Property 13: Completeness score calculation', () => {
it('completeness score equals ratio of filled attributes to total defined attributes', () => {
fc.assert(
fc.property(personWithVaryingCompleteness, (person) => {
const result = calculateCompleteness(person);
// Score must equal filledCount / totalCount
const expectedScore = result.totalCount > 0
? result.filledCount / result.totalCount
: 1.0;
expect(result.score).toBeCloseTo(expectedScore, 10);
// filledCount + missingFields.length must equal totalCount
expect(result.filledCount + result.missingFields.length).toBe(result.totalCount);
// Score must be between 0 and 1
expect(result.score).toBeGreaterThanOrEqual(0.0);
expect(result.score).toBeLessThanOrEqual(1.0);
}),
{ numRuns: 100 }
);
});
it('a profile with all attributes filled scores 1.0', () => {
fc.assert(
fc.property(fullPerson, (person) => {
const result = calculateCompleteness(person as unknown as Entity);
expect(result.score).toBe(1.0);
expect(result.missingFields).toHaveLength(0);
}),
{ numRuns: 100 }
);
});
it('a profile with only required header fields scores close to 0.0', () => {
fc.assert(
fc.property(emptyPerson, (person) => {
const result = calculateCompleteness(person as unknown as Entity);
// With only name fields filled (3 out of 10 total person fields), score should be low
expect(result.score).toBeLessThan(0.5);
expect(result.missingFields.length).toBeGreaterThan(0);
}),
{ numRuns: 100 }
);
});
/**
* Validates: Requirements 7.3
*/
});
// --- Property 14: Review prioritization ordering ---
describe('Property 14: Review prioritization ordering', () => {
it('entities with lower completeness and older modification dates rank higher than entities with higher completeness and recent modifications', () => {
// Generate a pair: one entity with low completeness + old date, another with high completeness + recent date
const entityPair = fc.record({
id1: kebabCaseId,
id2: kebabCaseId,
// Low completeness entity: modified long ago
daysAgoOld: fc.integer({ min: 100, max: 365 }),
// High completeness entity: modified recently
daysAgoRecent: fc.integer({ min: 0, max: 30 }),
}).filter(f => f.id1 !== f.id2);
fc.assert(
fc.property(entityPair, (pair) => {
const now = new Date('2025-06-01');
// Entity A: low completeness, old modification
const oldDate = new Date(now.getTime() - pair.daysAgoOld * 24 * 60 * 60 * 1000);
const oldModified = oldDate.toISOString().slice(0, 10);
// Entity B: high completeness, recent modification
const recentDate = new Date(now.getTime() - pair.daysAgoRecent * 24 * 60 * 60 * 1000);
const recentModified = recentDate.toISOString().slice(0, 10);
// Entity A: minimal person (low completeness)
const entityA: Entity = {
id: pair.id1,
type: 'person',
created: '2020-01-01',
modified: oldModified,
name: { first: 'Low', last: 'Complete', display: 'Low Complete' },
} as unknown as Entity;
// Entity B: full person (high completeness)
const entityB: Entity = {
id: pair.id2,
type: 'person',
created: '2020-01-01',
modified: recentModified,
name: { first: 'High', last: 'Complete', display: 'High Complete' },
contact: { email: 'test@example.com' },
summary: 'Full summary.',
languages: [{ language: 'English', level: 'native' }],
education: [{ institution: 'MIT', degree: 'PhD', field: 'CS', start: '2010-09', end: '2014-06' }],
skills: ['python'],
experiences: ['exp-1'],
certifications: ['cert-1'],
} as unknown as Entity;
const completenessA = calculateCompleteness(entityA);
const completenessB = calculateCompleteness(entityB);
const results = prioritizeForReview(
[
{ entity: entityA, completeness: completenessA },
{ entity: entityB, completeness: completenessB },
],
now
);
// Entity A (lower completeness + older) should rank higher (appear first)
expect(results[0].entityId).toBe(pair.id1);
expect(results[1].entityId).toBe(pair.id2);
// Priority values should reflect the ordering
expect(results[0].priority).toBeGreaterThan(results[1].priority);
}),
{ numRuns: 100 }
);
});
/**
* Validates: Requirements 7.2
*/
});