This commit is contained in:
geoffsee
2025-07-11 16:18:34 -04:00
parent 8956579eff
commit 8545aa8699
32 changed files with 4448 additions and 0 deletions

252
lib/__tests__/v1.test.ts Normal file
View File

@@ -0,0 +1,252 @@
import { expect, test, describe } from "bun:test";
import { ProfessionModel, Domain, Specialization, Role, Responsibility, Attribute } from "../v1.ts";
describe("V1 Professional Hierarchy Model", () => {
describe("Attribute Model", () => {
test("should create an attribute with required fields", () => {
const attribute = Attribute.create({
name: "JavaScript",
type: "Skill"
});
expect(attribute.name).toBe("JavaScript");
expect(attribute.type).toBe("Skill");
expect(attribute.description).toBe("");
});
test("should create an attribute with description", () => {
const attribute = Attribute.create({
name: "React",
type: "Tool",
description: "Frontend library for building user interfaces"
});
expect(attribute.name).toBe("React");
expect(attribute.type).toBe("Tool");
expect(attribute.description).toBe("Frontend library for building user interfaces");
});
test("should accept all valid attribute types", () => {
const skill = Attribute.create({ name: "Problem Solving", type: "Skill" });
const tool = Attribute.create({ name: "VS Code", type: "Tool" });
const trait = Attribute.create({ name: "Leadership", type: "Trait" });
expect(skill.type).toBe("Skill");
expect(tool.type).toBe("Tool");
expect(trait.type).toBe("Trait");
});
});
describe("Responsibility Model", () => {
test("should create a responsibility with required attributes", () => {
const jsAttribute = Attribute.create({ name: "JavaScript", type: "Skill" });
const reactAttribute = Attribute.create({ name: "React", type: "Tool" });
const responsibility = Responsibility.create({
title: "Build User Interfaces",
outcome: "Functional and responsive web applications",
requiredAttributes: [jsAttribute, reactAttribute]
});
expect(responsibility.title).toBe("Build User Interfaces");
expect(responsibility.outcome).toBe("Functional and responsive web applications");
expect(responsibility.requiredAttributes).toHaveLength(2);
expect(responsibility.requiredAttributes[0].name).toBe("JavaScript");
expect(responsibility.requiredAttributes[1].name).toBe("React");
});
test("should create a responsibility with empty attributes array", () => {
const responsibility = Responsibility.create({
title: "Code Review",
outcome: "High quality code",
requiredAttributes: []
});
expect(responsibility.requiredAttributes).toHaveLength(0);
});
});
describe("Role Model", () => {
test("should create a role with all seniority levels", () => {
const seniorityLevels = ["Intern", "Junior", "Mid", "Senior", "Lead", "Principal"];
seniorityLevels.forEach(level => {
const role = Role.create({
title: `${level} Developer`,
responsibilities: [],
requiredAttributes: [],
seniority: level as any
});
expect(role.seniority).toBe(level);
});
});
test("should create a role with responsibilities and attributes", () => {
const attributeForResponsibility = Attribute.create({ name: "TypeScript", type: "Skill" });
const attributeForRole = Attribute.create({ name: "TypeScript", type: "Skill" });
const responsibility = Responsibility.create({
title: "Develop Features",
outcome: "Working software features",
requiredAttributes: [attributeForResponsibility]
});
const role = Role.create({
title: "Frontend Developer",
responsibilities: [responsibility],
requiredAttributes: [attributeForRole],
seniority: "Mid"
});
expect(role.title).toBe("Frontend Developer");
expect(role.responsibilities).toHaveLength(1);
expect(role.requiredAttributes).toHaveLength(1);
expect(role.seniority).toBe("Mid");
});
});
describe("Specialization Model", () => {
test("should create a specialization with roles and attributes", () => {
const coreAttribute = Attribute.create({ name: "Web Development", type: "Skill" });
const role = Role.create({
title: "Web Developer",
responsibilities: [],
requiredAttributes: [],
seniority: "Mid"
});
const specialization = Specialization.create({
name: "Frontend Development",
focus: "User interface and experience",
coreAttributes: [coreAttribute],
roles: [role]
});
expect(specialization.name).toBe("Frontend Development");
expect(specialization.focus).toBe("User interface and experience");
expect(specialization.coreAttributes).toHaveLength(1);
expect(specialization.roles).toHaveLength(1);
});
});
describe("Domain Model", () => {
test("should create a domain with specializations", () => {
const attribute = Attribute.create({ name: "Programming", type: "Skill" });
const specialization = Specialization.create({
name: "Software Engineering",
focus: "Building software systems",
coreAttributes: [],
roles: []
});
const domain = Domain.create({
name: "Technology",
description: "Technology and software development",
specializations: [specialization],
coreAttributes: [attribute]
});
expect(domain.name).toBe("Technology");
expect(domain.description).toBe("Technology and software development");
expect(domain.specializations).toHaveLength(1);
expect(domain.coreAttributes).toHaveLength(1);
});
test("should create a domain with empty description", () => {
const domain = Domain.create({
name: "Engineering",
specializations: [],
coreAttributes: []
});
expect(domain.description).toBe("");
});
});
describe("ProfessionModel", () => {
test("should create a profession model with domains", () => {
const domain = Domain.create({
name: "Healthcare",
specializations: [],
coreAttributes: []
});
const professionModel = ProfessionModel.create({
domains: [domain]
});
expect(professionModel.domains).toHaveLength(1);
expect(professionModel.domains[0].name).toBe("Healthcare");
});
test("should create an empty profession model", () => {
const professionModel = ProfessionModel.create({
domains: []
});
expect(professionModel.domains).toHaveLength(0);
});
});
describe("Complete Hierarchy Integration", () => {
test("should create a complete professional hierarchy", () => {
const jsSkillForResponsibility = Attribute.create({ name: "JavaScript", type: "Skill" });
const reactToolForResponsibility = Attribute.create({ name: "React", type: "Tool" });
// Create attributes for role
const jsSkillForRole = Attribute.create({ name: "JavaScript", type: "Skill" });
const reactToolForRole = Attribute.create({ name: "React", type: "Tool" });
const leadershipTrait = Attribute.create({ name: "Leadership", type: "Trait" });
const jsSkillForSpecialization = Attribute.create({ name: "JavaScript", type: "Skill" });
const reactToolForSpecialization = Attribute.create({ name: "React", type: "Tool" });
const jsSkillForDomain = Attribute.create({ name: "JavaScript", type: "Skill" });
const responsibility = Responsibility.create({
title: "Build React Applications",
outcome: "Scalable web applications",
requiredAttributes: [jsSkillForResponsibility, reactToolForResponsibility]
});
// Create role
const role = Role.create({
title: "Senior Frontend Developer",
responsibilities: [responsibility],
requiredAttributes: [jsSkillForRole, reactToolForRole, leadershipTrait],
seniority: "Senior"
});
const specialization = Specialization.create({
name: "Frontend Development",
focus: "User interfaces and client-side applications",
coreAttributes: [jsSkillForSpecialization, reactToolForSpecialization],
roles: [role]
});
// Create domain
const domain = Domain.create({
name: "Software Engineering",
description: "Building software systems and applications",
specializations: [specialization],
coreAttributes: [jsSkillForDomain]
});
const professionModel = ProfessionModel.create({
domains: [domain]
});
// Verify the complete hierarchy
expect(professionModel.domains).toHaveLength(1);
expect(professionModel.domains[0].specializations).toHaveLength(1);
expect(professionModel.domains[0].specializations[0].roles).toHaveLength(1);
expect(professionModel.domains[0].specializations[0].roles[0].responsibilities).toHaveLength(1);
const retrievedRole = professionModel.domains[0].specializations[0].roles[0];
expect(retrievedRole.title).toBe("Senior Frontend Developer");
expect(retrievedRole.seniority).toBe("Senior");
expect(retrievedRole.responsibilities[0].title).toBe("Build React Applications");
expect(retrievedRole.requiredAttributes).toHaveLength(3);
});
});
});

476
lib/__tests__/v2.test.ts Normal file
View File

@@ -0,0 +1,476 @@
import { expect, test, describe, beforeEach } from "bun:test";
import {
TaskModel,
RoleModel,
FieldModel,
ProfessionModel,
IndustryModel,
DomainModel,
Enterprise,
Task,
Role,
Field,
Profession,
Industry,
Domain,
IRootStore
} from "../v2.ts";
describe("V2 Professional Hierarchy Model", () => {
describe("TaskModel", () => {
test("should create a task with UUID identifier", () => {
const task = TaskModel.create({
name: "Design REST endpoints"
});
expect(task.name).toBe("Design REST endpoints");
expect(task.id).toBeDefined();
expect(typeof task.id).toBe("string");
expect(task.id.length).toBeGreaterThan(0);
expect(task.description).toBeUndefined();
});
test("should create a task with description", () => {
const task = TaskModel.create({
name: "Write unit tests",
description: "Create comprehensive test coverage for new features"
});
expect(task.name).toBe("Write unit tests");
expect(task.description).toBe("Create comprehensive test coverage for new features");
});
test("should update task properties", () => {
const task = TaskModel.create({
name: "Initial task"
});
task.update({
name: "Updated task",
description: "Updated description"
});
expect(task.name).toBe("Updated task");
expect(task.description).toBe("Updated description");
});
test("should have unique IDs for different tasks", () => {
const task1 = TaskModel.create({ name: "Task 1" });
const task2 = TaskModel.create({ name: "Task 2" });
expect(task1.id).not.toBe(task2.id);
});
});
describe("RoleModel", () => {
test("should create a role with tasks", () => {
const role = RoleModel.create({
title: "API Engineer",
summary: "Designs and implements REST APIs"
});
expect(role.title).toBe("API Engineer");
expect(role.summary).toBe("Designs and implements REST APIs");
expect(role.tasks).toHaveLength(0);
expect(role.id).toBeDefined();
});
test("should add and remove tasks", () => {
const role = RoleModel.create({
title: "Backend Developer"
});
role.addTask({ name: "Design database schema" });
role.addTask({ name: "Implement API endpoints" });
expect(role.tasks).toHaveLength(2);
expect((role.tasks[0] as any).name).toBe("Design database schema");
expect((role.tasks[1] as any).name ).toBe("Implement API endpoints");
// Remove a task
const taskToRemove = role.tasks[0];
role.removeTask(taskToRemove);
expect(role.tasks).toHaveLength(1);
expect((role.tasks[0] as any).name).toBe("Implement API endpoints");
});
test("should return all tasks through view", () => {
const role = RoleModel.create({
title: "Frontend Developer"
});
role.addTask({ name: "Build components" });
role.addTask({ name: "Write tests" });
const allTasks = role.allTasks;
expect(allTasks).toHaveLength(2);
expect(allTasks[0].name).toBe("Build components");
expect(allTasks[1].name).toBe("Write tests");
});
});
describe("FieldModel", () => {
test("should create a field with roles", () => {
const field = FieldModel.create({
name: "Backend Development",
description: "Server-side application development"
});
expect(field.name).toBe("Backend Development");
expect(field.description).toBe("Server-side application development");
expect(field.roles).toHaveLength(0);
});
test("should add and remove roles", () => {
const field = FieldModel.create({
name: "Frontend Development"
});
field.addRole({ title: "React Developer" });
field.addRole({ title: "Vue Developer" });
expect(field.roles).toHaveLength(2);
expect(field.roles[0].title).toBe("React Developer");
expect(field.roles[1].title).toBe("Vue Developer");
const roleToRemove = field.roles[0];
field.removeRole(roleToRemove);
expect(field.roles).toHaveLength(1);
expect(field.roles[0].title).toBe("Vue Developer");
});
test("should return all tasks from nested roles", () => {
const field = FieldModel.create({
name: "Full Stack Development"
});
field.addRole({ title: "Frontend Developer" });
field.addRole({ title: "Backend Developer" });
// Add tasks to roles
field.roles[0].addTask({ name: "Build UI components" });
field.roles[0].addTask({ name: "Handle user interactions" });
field.roles[1].addTask({ name: "Design APIs" });
const allTasks = field.allTasks;
expect(allTasks).toHaveLength(3);
expect(allTasks.map(t => t.name)).toContain("Build UI components");
expect(allTasks.map(t => t.name)).toContain("Handle user interactions");
expect(allTasks.map(t => t.name)).toContain("Design APIs");
});
});
describe("ProfessionModel", () => {
test("should create a profession with fields", () => {
const profession = ProfessionModel.create({
name: "Software Engineering",
description: "Building software systems and applications"
});
expect(profession.name).toBe("Software Engineering");
expect(profession.description).toBe("Building software systems and applications");
expect(profession.fields).toHaveLength(0);
});
test("should add and remove fields", () => {
const profession = ProfessionModel.create({
name: "Web Development"
});
profession.addField({ name: "Frontend" });
profession.addField({ name: "Backend" });
expect(profession.fields).toHaveLength(2);
expect(profession.fields[0].name).toBe("Frontend");
expect(profession.fields[1].name).toBe("Backend");
const fieldToRemove = profession.fields[0];
profession.removeField(fieldToRemove);
expect(profession.fields).toHaveLength(1);
expect(profession.fields[0].name).toBe("Backend");
});
test("should return all tasks from nested hierarchy", () => {
const profession = ProfessionModel.create({
name: "Software Engineering"
});
profession.addField({ name: "Backend" });
profession.fields[0].addRole({ title: "API Developer" });
profession.fields[0].roles[0].addTask({ name: "Design REST endpoints" });
profession.fields[0].roles[0].addTask({ name: "Implement authentication" });
const allTasks = profession.allTasks;
expect(allTasks).toHaveLength(2);
expect(allTasks.map(t => t.name)).toContain("Design REST endpoints");
expect(allTasks.map(t => t.name)).toContain("Implement authentication");
});
});
describe("IndustryModel", () => {
test("should create an industry with professions", () => {
const industry = IndustryModel.create({
name: "Software",
description: "Software development and technology"
});
expect(industry.name).toBe("Software");
expect(industry.description).toBe("Software development and technology");
expect(industry.professions).toHaveLength(0);
});
test("should add and remove professions", () => {
const industry = IndustryModel.create({
name: "Technology"
});
industry.addProfession({ name: "Software Engineering" });
industry.addProfession({ name: "Data Science" });
expect(industry.professions).toHaveLength(2);
expect(industry.professions[0].name).toBe("Software Engineering");
expect(industry.professions[1].name).toBe("Data Science");
// Remove a profession
const professionToRemove = industry.professions[0];
industry.removeProfession(professionToRemove);
expect(industry.professions).toHaveLength(1);
expect(industry.professions[0].name).toBe("Data Science");
});
test("should return all tasks from nested hierarchy", () => {
const industry = IndustryModel.create({
name: "Software"
});
industry.addProfession({ name: "Web Development" });
industry.professions[0].addField({ name: "Frontend" });
industry.professions[0].fields[0].addRole({ title: "React Developer" });
industry.professions[0].fields[0].roles[0].addTask({ name: "Build components" });
industry.professions[0].fields[0].roles[0].addTask({ name: "Manage state" });
const allTasks = industry.allTasks;
expect(allTasks).toHaveLength(2);
expect(allTasks.map(t => t.name)).toContain("Build components");
expect(allTasks.map(t => t.name)).toContain("Manage state");
});
});
describe("DomainModel", () => {
test("should create a domain with industries", () => {
const domain = DomainModel.create({
name: "STEM",
description: "Science, Technology, Engineering, and Mathematics"
});
expect(domain.name).toBe("STEM");
expect(domain.description).toBe("Science, Technology, Engineering, and Mathematics");
expect(domain.industries).toHaveLength(0);
});
test("should add and remove industries", () => {
const domain = DomainModel.create({
name: "Technology"
});
domain.addIndustry({ name: "Software" });
domain.addIndustry({ name: "Hardware" });
expect(domain.industries).toHaveLength(2);
expect(domain.industries[0].name).toBe("Software");
expect(domain.industries[1].name).toBe("Hardware");
const industryToRemove = domain.industries[0];
domain.removeIndustry(industryToRemove);
expect(domain.industries).toHaveLength(1);
expect(domain.industries[0].name).toBe("Hardware");
});
test("should return all tasks from nested hierarchy", () => {
const domain = DomainModel.create({
name: "STEM"
});
domain.addIndustry({ name: "Software" });
domain.industries[0].addProfession({ name: "Software Engineering" });
domain.industries[0].professions[0].addField({ name: "Backend" });
domain.industries[0].professions[0].fields[0].addRole({ title: "API Engineer" });
domain.industries[0].professions[0].fields[0].roles[0].addTask({ name: "Design REST endpoints" });
const allTasks = domain.allTasks;
expect(allTasks).toHaveLength(1);
expect(allTasks[0].name).toBe("Design REST endpoints");
});
});
describe("Enterprise", () => {
let store: IRootStore;
beforeEach(() => {
store = Enterprise.create({});
});
test("should create an empty root store", () => {
expect(store.domains).toHaveLength(0);
});
test("should add domains", () => {
store.addDomain({ name: "STEM" });
store.addDomain({ name: "Arts" });
expect(store.domains).toHaveLength(2);
expect(store.domains[0].name).toBe("STEM");
expect(store.domains[1].name).toBe("Arts");
});
test("should return all tasks from entire hierarchy", () => {
store.addDomain({ name: "STEM" });
store.domains[0].addIndustry({ name: "Software" });
store.domains[0].industries[0].addProfession({ name: "Software Engineering" });
store.domains[0].industries[0].professions[0].addField({ name: "Backend" });
store.domains[0].industries[0].professions[0].fields[0].addRole({ title: "API Engineer" });
store.domains[0].industries[0].professions[0].fields[0].roles[0].addTask({ name: "Design REST endpoints" });
store.domains[0].industries[0].professions[0].fields[0].roles[0].addTask({ name: "Implement authentication" });
const allTasks = store.allTasks;
expect(allTasks).toHaveLength(2);
expect(allTasks.map(t => t.name)).toContain("Design REST endpoints");
expect(allTasks.map(t => t.name)).toContain("Implement authentication");
});
});
describe("Complete Hierarchy Integration", () => {
test("should create and manipulate a complete 6-layer hierarchy", () => {
const store = Enterprise.create({});
// Build the complete hierarchy as shown in the example
store.addDomain({ name: "STEM" });
store.domains[0].addIndustry({ name: "Software" });
store.domains[0].industries[0].addProfession({ name: "Software Engineering" });
store.domains[0].industries[0].professions[0].addField({ name: "Backend" });
store.domains[0].industries[0].professions[0].fields[0].addRole({ title: "API Engineer" });
store.domains[0].industries[0].professions[0].fields[0].roles[0].addTask({ name: "Design REST endpoints" });
expect(store.domains).toHaveLength(1);
expect(store.domains[0].industries).toHaveLength(1);
expect(store.domains[0].industries[0].professions).toHaveLength(1);
expect(store.domains[0].industries[0].professions[0].fields).toHaveLength(1);
expect(store.domains[0].industries[0].professions[0].fields[0].roles).toHaveLength(1);
expect(store.domains[0].industries[0].professions[0].fields[0].roles[0].tasks).toHaveLength(1);
// Verify data integrity through the hierarchy
const task = store.domains[0].industries[0].professions[0].fields[0].roles[0].tasks[0];
expect(task.name).toBe("Design REST endpoints");
expect(store.allTasks).toHaveLength(1);
expect(store.domains[0].allTasks).toHaveLength(1);
expect(store.domains[0].industries[0].allTasks).toHaveLength(1);
expect(store.domains[0].industries[0].professions[0].allTasks).toHaveLength(1);
expect(store.domains[0].industries[0].professions[0].fields[0].allTasks).toHaveLength(1);
// Add more tasks and verify aggregation
store.domains[0].industries[0].professions[0].fields[0].roles[0].addTask({ name: "Implement authentication" });
store.domains[0].industries[0].professions[0].fields[0].addRole({ title: "Database Engineer" });
store.domains[0].industries[0].professions[0].fields[0].roles[1].addTask({ name: "Design database schema" });
expect(store.allTasks).toHaveLength(3);
expect(store.domains[0].industries[0].professions[0].fields[0].allTasks).toHaveLength(3);
});
test("should handle multiple parallel hierarchies", () => {
const store = Enterprise.create({});
store.addDomain({ name: "STEM" });
store.domains[0].addIndustry({ name: "Software" });
store.domains[0].industries[0].addProfession({ name: "Web Development" });
store.domains[0].industries[0].professions[0].addField({ name: "Frontend" });
store.domains[0].industries[0].professions[0].fields[0].addRole({ title: "React Developer" });
store.domains[0].industries[0].professions[0].fields[0].roles[0].addTask({ name: "Build components" });
// Create second hierarchy branch
store.addDomain({ name: "Arts" });
store.domains[1].addIndustry({ name: "Digital Media" });
store.domains[1].industries[0].addProfession({ name: "Graphic Design" });
store.domains[1].industries[0].professions[0].addField({ name: "Web Design" });
store.domains[1].industries[0].professions[0].fields[0].addRole({ title: "UI Designer" });
store.domains[1].industries[0].professions[0].fields[0].roles[0].addTask({ name: "Create mockups" });
expect(store.domains).toHaveLength(2);
expect(store.allTasks).toHaveLength(2);
expect(store.allTasks.map(t => t.name)).toContain("Build components");
expect(store.allTasks.map(t => t.name)).toContain("Create mockups");
// Verify each domain has its own tasks
expect(store.domains[0].allTasks).toHaveLength(1);
expect(store.domains[1].allTasks).toHaveLength(1);
expect(store.domains[0].allTasks[0].name).toBe("Build components");
expect(store.domains[1].allTasks[0].name).toBe("Create mockups");
});
});
describe("CRUD Operations", () => {
test("should support task updates and removal", () => {
const role = RoleModel.create({ title: "Developer" });
role.addTask({ name: "Initial task", description: "Initial description" });
const task = role.tasks[0];
const originalId = task.id;
task.update({ name: "Updated task", description: "Updated description" });
expect(task.name).toBe("Updated task");
expect(task.description).toBe("Updated description");
expect(task.id).toBe(originalId); // ID should remain the same
// Remove task through parent
expect(role.tasks).toHaveLength(1);
role.removeTask(task);
expect(role.tasks).toHaveLength(0);
});
test("should support role removal", () => {
const field = FieldModel.create({ name: "Development" });
field.addRole({ title: "Developer" });
expect(field.roles).toHaveLength(1);
const role = field.roles[0];
field.removeRole(role);
expect(field.roles).toHaveLength(0);
});
test("should support field removal", () => {
const profession = ProfessionModel.create({ name: "Engineering" });
profession.addField({ name: "Software" });
expect(profession.fields).toHaveLength(1);
const field = profession.fields[0];
profession.removeField(field);
expect(profession.fields).toHaveLength(0);
});
test("should support profession removal", () => {
const industry = IndustryModel.create({ name: "Tech" });
industry.addProfession({ name: "Software Engineering" });
expect(industry.professions).toHaveLength(1);
const profession = industry.professions[0];
industry.removeProfession(profession);
expect(industry.professions).toHaveLength(0);
});
test("should support industry removal", () => {
const domain = DomainModel.create({ name: "STEM" });
domain.addIndustry({ name: "Software" });
expect(domain.industries).toHaveLength(1);
const industry = domain.industries[0];
domain.removeIndustry(industry);
expect(domain.industries).toHaveLength(0);
});
});
});

203
lib/agent-wrapper.ts Normal file
View File

@@ -0,0 +1,203 @@
import { Agent, StreamedRunResult } from '@openai/agents';
import HierarchyGenerator, { GenerationParams } from './components/hierarchy-generator';
import TemplateManager, { DomainTemplate } from './components/template-manager';
import OutputFormatter, { OutputOptions, FormattedOutput } from './components/output-formatter';
export interface AgentConfig {
name: string;
instructions: string;
model?: string;
}
export interface HierarchyGenerationOptions extends GenerationParams {
outputFormat?: OutputOptions;
templateKey?: string;
stream?: boolean;
}
export class HierarchyAgent {
private agent: Agent;
private generator: HierarchyGenerator;
private templateManager: TemplateManager;
private outputFormatter: OutputFormatter;
constructor(config: AgentConfig) {
this.agent = new Agent({
name: config.name,
instructions: config.instructions,
model: config.model || 'gpt-4o-mini'
});
this.generator = new HierarchyGenerator(this.agent);
this.templateManager = new TemplateManager();
this.outputFormatter = new OutputFormatter();
}
async generateHierarchy(options: HierarchyGenerationOptions): Promise<FormattedOutput | StreamedRunResult> {
let template: DomainTemplate;
if (options.templateKey) {
template = this.templateManager.getTemplate(options.templateKey);
if (!template) {
throw new Error(`Template not found: ${options.templateKey}`);
}
} else {
// Create a default template for the domain
const version = options.version || 'v2';
template = this.templateManager.createCustomTemplate(
`${options.domain}-${version}`,
options.domain,
version
);
}
const generationParams: GenerationParams = {
domain: options.domain,
complexity: options.complexity || 'medium',
includeSkills: options.includeSkills ?? true,
includeTools: options.includeTools ?? true,
includeExamples: options.includeExamples ?? true,
stream: options.stream
};
const content = await this.generator.generateFromTemplate(template, generationParams);
// If streaming, return the stream directly
if (options.stream && content instanceof Object && 'toStream' in content) {
return content as StreamedRunResult;
}
const outputOptions = options.outputFormat || this.outputFormatter.getDefaultOptions();
const formattedOutput = this.outputFormatter.formatOutput(
content as string,
options.domain,
template.version,
outputOptions
);
return formattedOutput;
}
// New method for streaming with enhanced visibility
async generateHierarchyWithStreaming(
options: HierarchyGenerationOptions,
onStreamEvent?: (event: any) => void
): Promise<FormattedOutput> {
let template: DomainTemplate;
if (options.templateKey) {
template = this.templateManager.getTemplate(options.templateKey);
if (!template) {
throw new Error(`Template not found: ${options.templateKey}`);
}
} else {
// Create a default template for the domain
const version = options.version || 'v2';
template = this.templateManager.createCustomTemplate(
`${options.domain}-${version}`,
options.domain,
version
);
}
const generationParams: GenerationParams = {
domain: options.domain,
complexity: options.complexity || 'medium',
includeSkills: options.includeSkills ?? true,
includeTools: options.includeTools ?? true,
includeExamples: options.includeExamples ?? true
};
const content = await this.generator.generateFromTemplateWithStreaming(
template,
generationParams,
onStreamEvent
);
// Format the output
const outputOptions = options.outputFormat || this.outputFormatter.getDefaultOptions();
const formattedOutput = this.outputFormatter.formatOutput(
content,
options.domain,
template.version,
outputOptions
);
return formattedOutput;
}
async generateExample(
domain: string,
version: 'v1' | 'v2' = 'v2',
complexity: 'simple' | 'medium' | 'complex' = 'medium'
): Promise<FormattedOutput> {
return this.generateHierarchy({
domain,
version,
complexity,
includeSkills: true,
includeTools: true,
includeExamples: true
});
}
// Template management methods
getAvailableTemplates(): DomainTemplate[] {
return this.templateManager.getAllTemplates();
}
getTemplatesByDomain(domain: string): DomainTemplate[] {
return this.templateManager.getTemplatesByDomain(domain);
}
getTemplatesByVersion(version: 'v1' | 'v2'): DomainTemplate[] {
return this.templateManager.getTemplatesByVersion(version);
}
addCustomTemplate(key: string, template: DomainTemplate): void {
this.templateManager.addTemplate(key, template);
}
// Validation and optimization methods
async validateHierarchy(hierarchyData: any): Promise<boolean> {
// TODO: Implement validation logic using the agent
// Could validate structure, naming conventions, completeness, etc.
return true;
}
async optimizeHierarchy(hierarchyData: any): Promise<any> {
// TODO: Implement optimization logic using the agent
// Could suggest improvements, fill gaps, optimize structure, etc.
return hierarchyData;
}
// Batch generation methods
async generateMultipleExamples(
domains: string[],
version: 'v1' | 'v2' = 'v2',
outputFormat?: OutputOptions
): Promise<FormattedOutput[]> {
const results: FormattedOutput[] = [];
for (const domain of domains) {
try {
const result = await this.generateHierarchy({
domain,
version,
complexity: 'medium',
includeSkills: true,
includeTools: true,
includeExamples: true,
outputFormat
});
results.push(result);
} catch (error) {
console.error(`Error generating example for ${domain}:`, error);
}
}
return results;
}
}
export default HierarchyAgent;

View File

@@ -0,0 +1,115 @@
import { Agent, run, StreamedRunResult } from '@openai/agents';
export interface HierarchyTemplate {
version: 'v1' | 'v2';
structure: string[];
description: string;
}
export interface GenerationParams {
domain: string;
complexity: 'simple' | 'medium' | 'complex';
includeSkills: boolean;
includeTools: boolean;
includeExamples: boolean;
stream?: boolean;
}
export class HierarchyGenerator {
private agent: Agent;
constructor(agent: Agent) {
this.agent = agent;
}
async generateFromTemplate(template: HierarchyTemplate, params: GenerationParams): Promise<string | StreamedRunResult> {
const prompt = this.buildPrompt(template, params);
if (params.stream) {
return await run(this.agent, prompt, { stream: true });
} else {
const result = await run(this.agent, prompt);
return result.finalOutput;
}
}
// Helper method to stream and collect final output
async generateFromTemplateWithStreaming(
template: HierarchyTemplate,
params: GenerationParams,
onStreamEvent?: (event: any) => void
): Promise<string> {
const prompt = this.buildPrompt(template, params);
console.log('🔄 Starting hierarchy generation...');
const stream = await run(this.agent, prompt, { stream: true });
let content = '';
for await (const event of stream) {
if (event.type === 'raw_model_stream_event' && event.data.delta) {
// console.log(event.data.delta)
content += event.delta;
process.stdout.write(event.data.delta);
}
if (event.type === 'agent_updated_stream_event') {
console.log(`\n📝 Agent: ${event.agent.name} is processing...`);
} else if (event.type === 'run_item_stream_event') {
if (event.item.type === 'tool_call_item') {
console.log('\n🔧 Tool being called...');
} else if (event.item.type === 'message_output_item') {
console.log('\n💬 Generating response...');
}
}
// Allow custom event handling
if (onStreamEvent) {
onStreamEvent(event);
}
}
console.log('\n✅ Hierarchy generation complete!');
return stream.finalOutput;
}
private buildPrompt(template: HierarchyTemplate, params: GenerationParams): string {
const structureDescription = template.version === 'v1'
? 'Domain → Specialization → Role → Responsibility'
: 'Domain → Industry → Profession → Field → Role → Task';
const importStatement = template.version === 'v1'
? 'import { Enterprise, DomainModel, SpecializationModel, RoleModel, ResponsibilityModel } from "../../lib/v1";'
: 'import { Enterprise, DomainModel, IndustryModel, ProfessionModel, FieldModel, RoleModel, TaskModel } from "../../lib/v2";';
return `
Generate ONLY TypeScript code for a professional hierarchy in the ${params.domain} domain using the ${template.version} structure.
Structure: ${structureDescription}
Complexity Level: ${params.complexity}
${params.includeSkills ? '✓ Include relevant skills and competencies' : ''}
${params.includeTools ? '✓ Include tools and technologies' : ''}
${params.includeExamples ? '✓ Include practical examples and use cases' : ''}
IMPORTANT: Output ONLY valid TypeScript code. Do not include any markdown, explanations, or comments outside of TypeScript comments.
Requirements:
1. Start with the import statement: ${importStatement}
2. Create a realistic, comprehensive hierarchy using MobX State Tree models
3. Use appropriate professional terminology
4. Ensure logical relationships between levels
5. ${params.complexity === 'complex' ? 'Include multiple branches and detailed attributes' :
params.complexity === 'medium' ? 'Include moderate detail with key attributes' :
'Keep it simple with essential elements only'}
The code should demonstrate:
- Creating the hierarchy structure using the imported models
- Adding relevant attributes (skills, tools, examples)
- Basic operations (create, read, update)
- Real-world application examples as TypeScript code
Output format: Pure TypeScript code only, no markdown or explanations.
`;
}
}
export default HierarchyGenerator;

View File

@@ -0,0 +1,278 @@
export interface OutputOptions {
format: 'typescript' | 'markdown' | 'json' | 'yaml';
includeMetadata: boolean;
includeTimestamp: boolean;
includeComments: boolean;
}
export interface FormattedOutput {
content: string;
filename: string;
extension: string;
metadata?: any;
}
export class OutputFormatter {
formatOutput(
content: string,
domain: string,
version: 'v1' | 'v2',
options: OutputOptions
): FormattedOutput {
switch (options.format) {
case 'typescript':
return this.formatTypeScript(content, domain, version, options);
case 'markdown':
return this.formatMarkdown(content, domain, version, options);
case 'json':
return this.formatJSON(content, domain, version, options);
case 'yaml':
return this.formatYAML(content, domain, version, options);
default:
throw new Error(`Unsupported format: ${options.format}`);
}
}
private formatTypeScript(
content: string,
domain: string,
version: 'v1' | 'v2',
options: OutputOptions
): FormattedOutput {
const header = options.includeComments ? `/**
* ${domain} Professional Hierarchy Example
* Generated using OpenAI Agents SDK and Sumpin Professional Hierarchy Models
* Model Version: ${version} (${version === 'v1' ? '4-layer' : '6-layer'} hierarchy)
* ${options.includeTimestamp ? `Generated on: ${new Date().toISOString()}` : ''}
*/
` : '';
const imports = `import {
Enterprise,
${version === 'v1' ? 'DomainModel, SpecializationModel, RoleModel, ResponsibilityModel' : 'DomainModel, IndustryModel, ProfessionModel, FieldModel, RoleModel, TaskModel'}
} from "../../lib/${version}";
`;
const cleanedContent = this.extractTypeScriptCode(content);
return {
content: header + imports + cleanedContent,
filename: `${domain.toLowerCase()}-hierarchy-example`,
extension: 'ts',
metadata: {
domain,
version,
generatedAt: options.includeTimestamp ? new Date().toISOString() : undefined
}
};
}
private extractTypeScriptCode(content: string): string {
let cleaned = content;
cleaned = cleaned.replace(/^#{1,6}\s+.*$/gm, '');
cleaned = cleaned.replace(/\*\*([^*]+)\*\*/g, '$1');
cleaned = cleaned.replace(/\*([^*]+)\*/g, '$1');
// Remove markdown lists that aren't TypeScript code
cleaned = cleaned.replace(/^[\s]*[-*+]\s+\*\*([^*]+)\*\*$/gm, '');
cleaned = cleaned.replace(/^[\s]*[-*+]\s+([^:]+):$/gm, '');
// Extract TypeScript code blocks
const codeBlockRegex = /```typescript\s*([\s\S]*?)```/g;
const codeBlocks = [];
let match;
while ((match = codeBlockRegex.exec(content)) !== null) {
codeBlocks.push(match[1].trim());
}
// If we found code blocks, use them
if (codeBlocks.length > 0) {
return codeBlocks.join('\n\n');
}
// Otherwise, try to extract TypeScript-like content
const lines = cleaned.split('\n');
const tsLines = [];
let inCodeSection = false;
for (const line of lines) {
const trimmed = line.trim();
// Skip empty lines and markdown-like content
if (!trimmed ||
trimmed.startsWith('#') ||
trimmed.startsWith('*') ||
trimmed.startsWith('-') ||
trimmed.includes('Below is') ||
trimmed.includes('Here\'s') ||
trimmed.includes('TypeScript Code') ||
trimmed.includes('Professional Hierarchy')) {
continue;
}
// Look for TypeScript patterns
if (trimmed.includes('interface ') ||
trimmed.includes('class ') ||
trimmed.includes('type ') ||
trimmed.includes('const ') ||
trimmed.includes('let ') ||
trimmed.includes('var ') ||
trimmed.includes('function ') ||
trimmed.includes('export ') ||
trimmed.includes('import ') ||
trimmed.includes('{') ||
trimmed.includes('}') ||
trimmed.includes(';') ||
inCodeSection) {
tsLines.push(line);
inCodeSection = true;
// End code section on certain patterns
if (trimmed === '}' && !line.includes(',')) {
inCodeSection = false;
}
}
}
return tsLines.join('\n').trim() || cleaned.trim();
}
private formatMarkdown(
content: string,
domain: string,
version: 'v1' | 'v2',
options: OutputOptions
): FormattedOutput {
const header = `# ${domain.charAt(0).toUpperCase() + domain.slice(1)} Professional Hierarchy Example
Generated using OpenAI Agents SDK and Sumpin Professional Hierarchy Models
## Overview
This example demonstrates a ${version} professional hierarchy model for the ${domain} domain.
**Model Version:** ${version} (${version === 'v1' ? '4-layer' : '6-layer'} hierarchy)
${options.includeTimestamp ? `**Generated on:** ${new Date().toISOString()}` : ''}
## Structure
${version === 'v1' ? 'Domain → Specialization → Role → Responsibility' : 'Domain → Industry → Profession → Field → Role → Task'}
## Generated Content
\`\`\`typescript
${content}
\`\`\`
## Usage
To use this example:
1. Ensure you have the required dependencies installed:
\`\`\`bash
bun add mobx-state-tree mobx uuid
bun add -d @types/uuid
\`\`\`
2. Run the example:
\`\`\`bash
bun run examples/generated/${domain.toLowerCase()}-hierarchy-example.ts
\`\`\`
---
*This example was generated automatically and demonstrates best practices for professional hierarchy modeling.*
`;
return {
content: header,
filename: `${domain.toLowerCase()}-hierarchy-example`,
extension: 'md',
metadata: {
domain,
version,
generatedAt: options.includeTimestamp ? new Date().toISOString() : undefined
}
};
}
private formatJSON(
content: string,
domain: string,
version: 'v1' | 'v2',
options: OutputOptions
): FormattedOutput {
const data = {
domain,
version,
structure: version === 'v1'
? ['Domain', 'Specialization', 'Role', 'Responsibility']
: ['Domain', 'Industry', 'Profession', 'Field', 'Role', 'Task'],
generatedContent: content,
...(options.includeMetadata && {
metadata: {
generatedAt: options.includeTimestamp ? new Date().toISOString() : undefined,
generator: 'OpenAI Agents SDK + Sumpin',
hierarchyType: `${version === 'v1' ? '4' : '6'}-layer hierarchy`
}
})
};
return {
content: JSON.stringify(data, null, 2),
filename: `${domain.toLowerCase()}-hierarchy-example`,
extension: 'json',
metadata: data.metadata
};
}
private formatYAML(
content: string,
domain: string,
version: 'v1' | 'v2',
options: OutputOptions
): FormattedOutput {
const yamlContent = `domain: ${domain}
version: ${version}
structure:
${(version === 'v1'
? ['Domain', 'Specialization', 'Role', 'Responsibility']
: ['Domain', 'Industry', 'Profession', 'Field', 'Role', 'Task']
).map(item => ` - ${item}`).join('\n')}
generated_content: |
${content.split('\n').map(line => ` ${line}`).join('\n')}
${options.includeMetadata ? `metadata:
generated_at: ${options.includeTimestamp ? new Date().toISOString() : 'null'}
generator: "OpenAI Agents SDK + Sumpin"
hierarchy_type: "${version === 'v1' ? '4' : '6'}-layer hierarchy"` : ''}`;
return {
content: yamlContent,
filename: `${domain.toLowerCase()}-hierarchy-example`,
extension: 'yaml',
metadata: {
domain,
version,
generatedAt: options.includeTimestamp ? new Date().toISOString() : undefined
}
};
}
getDefaultOptions(): OutputOptions {
return {
format: 'typescript',
includeMetadata: true,
includeTimestamp: true,
includeComments: true
};
}
}
export default OutputFormatter;

View File

@@ -0,0 +1,81 @@
import { HierarchyTemplate } from './hierarchy-generator';
import v1Finance from "./templates/v1-finance.ts";
import v1Tech from "./templates/v1-tech.ts";
import v2Edu from "./templates/v2-edu.ts";
import v2Healthcare from "./templates/v2-healthcare.ts";
import v2Tech from "./templates/v2-tech.ts";
export interface DomainTemplate extends HierarchyTemplate {
domain: string;
commonSkills: string[];
commonTools: string[];
examples: string[];
}
export class TemplateManager {
private templates: Map<string, DomainTemplate> = new Map();
constructor() {
this.initializeDefaultTemplates();
}
private initializeDefaultTemplates() {
// V2 Templates (6-layer hierarchy)
this.addTemplate('technology-v2', v2Tech);
this.addTemplate('healthcare-v2', v2Healthcare);
this.addTemplate('education-v2', v2Edu);
// V1 Templates (4-layer hierarchy)
this.addTemplate('technology-v1', v1Tech);
this.addTemplate('finance-v1', v1Finance);
}
addTemplate(key: string, template: DomainTemplate): void {
this.templates.set(key, template);
}
getTemplate(key: string): DomainTemplate | undefined {
return this.templates.get(key);
}
getTemplatesByVersion(version: 'v1' | 'v2'): DomainTemplate[] {
return Array.from(this.templates.values()).filter(t => t.version === version);
}
getTemplatesByDomain(domain: string): DomainTemplate[] {
return Array.from(this.templates.values()).filter(t =>
t.domain.toLowerCase().includes(domain.toLowerCase())
);
}
getAllTemplates(): DomainTemplate[] {
return Array.from(this.templates.values());
}
createCustomTemplate(
key: string,
domain: string,
version: 'v1' | 'v2',
options: Partial<DomainTemplate> = {}
): DomainTemplate {
const structure = version === 'v1'
? ['Domain', 'Specialization', 'Role', 'Responsibility']
: ['Domain', 'Industry', 'Profession', 'Field', 'Role', 'Task'];
const template: DomainTemplate = {
version,
domain,
structure,
description: `${domain} professional hierarchy`,
commonSkills: [],
commonTools: [],
examples: [],
...options
};
this.addTemplate(key, template);
return template;
}
}
export default TemplateManager;

View File

@@ -0,0 +1,9 @@
export default {
version: 'v1',
domain: 'Finance',
structure: ['Domain', 'Specialization', 'Role', 'Responsibility'],
description: 'Simplified finance sector hierarchy',
commonSkills: ['Financial Analysis', 'Risk Assessment', 'Compliance', 'Reporting'],
commonTools: ['Excel', 'Financial Software', 'Analytics Tools', 'Reporting Systems'],
examples: ['Investment Banking', 'Corporate Finance', 'Risk Management', 'Accounting']
}

View File

@@ -0,0 +1,9 @@
export default {
version: 'v1',
domain: 'Technology',
structure: ['Domain', 'Specialization', 'Role', 'Responsibility'],
description: 'Simplified technology sector hierarchy',
commonSkills: ['Programming', 'System Design', 'Testing', 'Documentation'],
commonTools: ['Code Editors', 'Version Control', 'Build Tools', 'Monitoring'],
examples: ['Web Development', 'Mobile Development', 'Backend Systems', 'Frontend UI']
}

View File

@@ -0,0 +1,9 @@
export default {
version: 'v2',
domain: 'Education',
structure: ['Domain', 'Industry', 'Profession', 'Field', 'Role', 'Task'],
description: 'Comprehensive education sector hierarchy',
commonSkills: ['Teaching', 'Curriculum Development', 'Assessment', 'Student Engagement'],
commonTools: ['LMS', 'Educational Software', 'Assessment Tools', 'Communication Platforms'],
examples: ['K-12 Education', 'Higher Education', 'Corporate Training', 'Online Learning']
}

View File

@@ -0,0 +1,9 @@
export default {
version: 'v2',
domain: 'Healthcare',
structure: ['Domain', 'Industry', 'Profession', 'Field', 'Role', 'Task'],
description: 'Comprehensive healthcare sector hierarchy',
commonSkills: ['Patient Care', 'Medical Knowledge', 'Communication', 'Empathy'],
commonTools: ['EMR Systems', 'Medical Devices', 'Diagnostic Tools', 'Communication Systems'],
examples: ['Clinical Care', 'Medical Research', 'Healthcare Administration', 'Public Health']
}

View File

@@ -0,0 +1,9 @@
export default {
version: 'v2',
domain: 'Technology',
structure: ['Domain', 'Industry', 'Profession', 'Field', 'Role', 'Task'],
description: 'Comprehensive technology sector hierarchy',
commonSkills: ['Problem Solving', 'Critical Thinking', 'Communication', 'Collaboration'],
commonTools: ['Git', 'IDE', 'Testing Frameworks', 'Documentation Tools'],
examples: ['Software Development', 'DevOps', 'Data Science', 'Cybersecurity']
}

35
lib/hierarchy-model.ts Normal file
View File

@@ -0,0 +1,35 @@
import { types } from "mobx-state-tree";
/**
* Reusable abstraction for a domain/sector hierarchy.
* Works for both v1 and v2
*/
export const HierarchyModel = types.model("HierarchyModel", {
version: types.string, // "v1" | "v2"
domain: types.string, // e.g. "Finance", "Technology"
structure: types.array(types.string), // ordered hierarchy labels
description: types.string, // plain-text description
commonSkills: types.array(types.string),
commonTools: types.array(types.string),
examples: types.array(types.string)
});
/* ------------------------------------------------------------------ *
* Example usage
* ------------------------------------------------------------------ */
// Create individual instances
// import { HierarchyModel } from "./hierarchy-model";
//
// const finance = HierarchyModel.create(v1Finance);
// const tech = HierarchyModel.create(v2Tech);
export const HierarchyStore = types
.model("HierarchyStore", {
items: types.map(HierarchyModel) // keyed by domain name
})
.actions(self => ({
add(h: Instance<typeof HierarchyModel>) {
self.items.set(h.domain, h);
}
}));

4
lib/index.ts Normal file
View File

@@ -0,0 +1,4 @@
import v1 from './v1';
import v2 from './v2';
export { v1, v2 };

41
lib/v1.ts Normal file
View File

@@ -0,0 +1,41 @@
import { types } from "mobx-state-tree"
const Attribute = types.model("Attribute", {
name: types.string,
type: types.enumeration("AttributeType", ["Skill", "Tool", "Trait"]),
description: types.optional(types.string, "")
})
const Responsibility = types.model("Responsibility", {
title: types.string,
outcome: types.string,
requiredAttributes: types.array(Attribute)
})
const Role = types.model("Role", {
title: types.string,
responsibilities: types.array(Responsibility),
requiredAttributes: types.array(Attribute),
seniority: types.enumeration("Seniority", ["Intern", "Junior", "Mid", "Senior", "Lead", "Principal"])
})
// Specialization within a domain (e.g., cardiologist within medicine)
const Specialization = types.model("Specialization", {
name: types.string,
focus: types.string,
coreAttributes: types.array(Attribute),
roles: types.array(Role)
})
const Domain = types.model("Domain", {
name: types.string,
description: types.optional(types.string, ""),
specializations: types.array(Specialization),
coreAttributes: types.array(Attribute)
})
const ProfessionModel = types.model("ProfessionModel", {
domains: types.array(Domain)
});
export { ProfessionModel, Domain, Specialization, Role, Responsibility, Attribute }

194
lib/v2.ts Normal file
View File

@@ -0,0 +1,194 @@
// Profession Hierarchy Model using mobx-state-tree (MST)
// -----------------------------------------------------
// This file defines a generic, extensible hierarchy that captures the essence
// of professions. It intentionally separates concerns across six conceptual
// layers, from the broad Domain level down to atomic Tasks. Each layer owns
// its children through stronglytyped arrays, enabling finegrained reactivity,
// traversal, and manipulation.
//
// Layering
// ┌ Domain e.g. "STEM", "Arts", "Public Service"
// │ └ Industry e.g. "Software", "Healthcare", "Finance"
// │ └ Profession e.g. "Software Engineering", "Nursing"
// │ └ Field e.g. "Backend", "Pediatrics"
// │ └ Role e.g. "API Engineer", "Pediatric Nurse"
// │ └ Task e.g. "Design REST endpoints", "Administer vaccine"
//
// Each model exposes actions to mutate its children as well as compositional
// views to rapidly surface nested information (e.g., all tasks under an
// Industry). Identifiers are UUIDv4 strings to guarantee uniqueness across
// distributed systems.
// -----------------------------------------------------
import { types, Instance, SnapshotIn, getParent, destroy } from "mobx-state-tree";
import { v4 as uuidv4 } from "uuid";
/* Utility --------------------------------------------------------------- */
const withId = <T extends Record<string, unknown>>(modelName: string, definition: T) =>
types.model(modelName, {
id: types.optional(types.identifier, uuidv4),
...definition
});
/* Task ------------------------------------------------------------------ */
export const TaskModel = withId("Task", {
name: types.string,
description: types.maybe(types.string)
})
.actions(self => ({
update(attrs: Partial<SnapshotIn<typeof TaskModel>>) {
Object.assign(self, attrs);
},
remove() {
destroy(self);
}
}));
export interface Task extends Instance<typeof TaskModel> {}
/* Role ------------------------------------------------------------------ */
export const RoleModel = withId("Role", {
title: types.string,
summary: types.maybe(types.string),
tasks: types.optional(types.array(TaskModel), [])
})
.actions(self => ({
addTask(task: SnapshotIn<typeof TaskModel>) {
self.tasks.push(TaskModel.create(task));
},
removeTask(task: Task) {
self.tasks.remove(task);
},
remove() {
destroy(self);
}
}))
.views(self => ({
get allTasks() {
return self.tasks.slice();
}
}));
export interface Role extends Instance<typeof RoleModel> {}
/* Field (Specialization) ------------------------------------------------- */
export const FieldModel = withId("Field", {
name: types.string,
description: types.maybe(types.string),
roles: types.optional(types.array(RoleModel), [])
})
.actions(self => ({
addRole(role: SnapshotIn<typeof RoleModel>) {
self.roles.push(RoleModel.create(role));
},
removeRole(role: Role) {
self.roles.remove(role);
},
remove() {
destroy(self);
}
}))
.views(self => ({
get allTasks() {
return self.roles.flatMap(r => r.allTasks);
}
}));
export interface Field extends Instance<typeof FieldModel> {}
/* Profession ------------------------------------------------------------ */
export const ProfessionModel = withId("Profession", {
name: types.string,
description: types.maybe(types.string),
fields: types.optional(types.array(FieldModel), [])
})
.actions(self => ({
addField(field: SnapshotIn<typeof FieldModel>) {
self.fields.push(FieldModel.create(field));
},
removeField(field: Field) {
self.fields.remove(field);
},
remove() {
destroy(self);
}
}))
.views(self => ({
get allTasks() {
return self.fields.flatMap(f => f.allTasks);
}
}));
export interface Profession extends Instance<typeof ProfessionModel> {}
/* Industry -------------------------------------------------------------- */
export const IndustryModel = withId("Industry", {
name: types.string,
description: types.maybe(types.string),
professions: types.optional(types.array(ProfessionModel), [])
})
.actions(self => ({
addProfession(prof: SnapshotIn<typeof ProfessionModel>) {
self.professions.push(ProfessionModel.create(prof));
},
removeProfession(prof: Profession) {
self.professions.remove(prof);
},
remove() {
destroy(self);
}
}))
.views(self => ({
get allTasks() {
return self.professions.flatMap(p => p.allTasks);
}
}));
export interface Industry extends Instance<typeof IndustryModel> {}
/* Domain ---------------------------------------------------------------- */
export const DomainModel = withId("Domain", {
name: types.string,
description: types.maybe(types.string),
industries: types.optional(types.array(IndustryModel), [])
})
.actions(self => ({
addIndustry(ind: SnapshotIn<typeof IndustryModel>) {
self.industries.push(IndustryModel.create(ind));
},
removeIndustry(ind: Industry) {
self.industries.remove(ind);
},
remove() {
destroy(self);
}
}))
.views(self => ({
get allTasks() {
return self.industries.flatMap(i => i.allTasks);
}
}));
export interface Domain extends Instance<typeof DomainModel> {}
/* Enterprise ------------------------------------------------------------- */
export const Enterprise = types
.model("Enterprise", {
domains: types.optional(types.array(DomainModel), [])
})
.actions(self => ({
addDomain(domain: SnapshotIn<typeof DomainModel>) {
self.domains.push(DomainModel.create(domain));
}
}))
.views(self => ({
// Convenience: get a flat list of everything
get allTasks() {
return self.domains.flatMap(d => d.allTasks);
}
}));
export interface IRootStore extends Instance<typeof Enterprise> {}
/* Example usage --------------------------------------------------------- */
// const store = Enterprise.create({});
// store.addDomain({ name: "STEM" });
// store.domains[0].addIndustry({ name: "Software" });
// store.domains[0].industries[0].addProfession({ name: "Software Engineering" });
// store.domains[0].industries[0].professions[0].addField({ name: "Backend" });
// store.domains[0].industries[0].professions[0].fields[0].addRole({ title: "API Engineer" });
// store.domains[0].industries[0].professions[0].fields[0].roles[0].addTask({ name: "Design REST endpoints" });
// console.log(store.allTasks.map(t => t.name)); // → ["Design REST endpoints"]