Ssteven_copy
case_study · 04 / 06·Consulting & audit·live · production

AI CRM · Accounting.

Refactor of an accounting firm's internal CRM with Claude API in production. AI-driven training generation from raw documents, Pennylane integration, 360Learning import, DDD / Hexagonal architecture.

260
Daily users (employees)
€60k
Estimated 2-year savings
50
Trainings imported from 360Learning
DDD
Hexagonal architecture
Next.jsNestJSTypeScriptClaude API (Anthropic)PostgreSQLTypeORMRedisDockerAWS S3StripeSupabasePennylane API360Learning API

// 01 · the-context

The context

A multi-site accounting firm with 260 employees. Daily reality: business tools scattered everywhere (Pennylane for client management, 360Learning for training, a shared drive, emails), redundant manual data entry, constant copy-paste.

The mission goal: build a single internal platform that centralizes everything, automates flows between tools, and integrates AI where it brings real business value.

A parallel business priority: reduce dependency on costly third-party tools. On the training side specifically, internalize content to progressively cut the 360Learning subscription.

// 02 · the-solution

The solution

I joined a project already kicked off by another developer, built on a Domain-Driven Design / Hexagonal architecture. I took the time to understand the existing structure before extending — domain, application, infrastructure clearly separated — to stay consistent with what was in place.

Modules I contributed to:

  • Client management: Pennylane API integration, enriched client records, mission and fiscal deadline tracking
  • Training module: import from 360Learning (50 existing trainings consolidated in-house, to progressively reduce subscription dependency)
  • AI training generation module (the most interesting piece — detailed below)
  • Employee onboarding: HR documents workflow, access provisioning
  • Internal knowledge base: full-text search for all employees
  • Pilot dashboards: missions, deadlines, workload per partner

AI integration: generating training from raw documents

Before, creating internal training would take hours from a trainer: take a PDF or slides, structure an outline, write learning objectives, split into modules.

I built a training generation module that takes a raw document as input (PDF, slides, internal doc) and outputs a structured, ready-to-deploy training: title, objectives, modules, key points.

5 trainings have already been AI-generated and deployed internally, alongside the 50 imported from 360Learning. The logic: import to consolidate the existing, generate to stop depending on an external tool.

// code
// Simplified pipeline
async function generateTrainingFromDocument(
  documentUrl: string,
  options: TrainingOptions,
): Promise<Training> {
  // 1. Content extraction (PDF, slides, .docx)
  const content = await extractContent(documentUrl);

  // 2. Structured generation via Claude API
  const training = await claude.generate({
    schema: TrainingSchema, // Strict Zod validation
    prompt: buildTrainingPrompt(content, options),
  });

  // 3. Business validation + persistence
  await validateTrainingRules(training);
  return await trainingRepository.save(training);
}

The module enforces a Zod-validated output structure: Claude never returns free text, only a typed object that can be directly persisted. If the LLM deviates from the schema (rare), we retry with a prompt enriched by the validation error.

Cross-tool automations

I also built automations to eliminate redundant entries between the firm's tools — notably via the Pennylane API for client management and the 360Learning import for trainings.

// 03 · technical-challenges

Technical challenges

Fitting into an existing architecture. The project was already running in DDD/Hexagonal when I joined. Rather than impose my habits, I took time to understand the existing patterns and extend in the same style. It's rarely the default reflex for a new dev on a project, but it's what keeps the codebase coherent months down the line.

Guaranteeing AI-generated content quality. A poorly generated training is worse than no training at all — it discredits the tool and nobody uses it. Strict Zod output validation, mandatory human review before publishing, free editing after generation.

Integrating a critical third-party API (Pennylane). Pennylane manages the firm's client data. A bad integration could corrupt the source of truth. Built a clean abstraction layer with error handling and retry, so a third-party outage never blocks the CRM user.

// 04 · business-results

Business results

  • 260 employees use the platform daily
  • 50 trainings imported from 360Learning consolidated in the internal CRM
  • 5 AI-generated trainings already in production
  • Approximately €60k estimated savings over 2 years on the client side, mainly tied to reduced 360Learning dependency and tool consolidation

// 05 · tech-stack

Tech stack

LayerTechnologies
FrontendNext.js, TypeScript, Tailwind
BackendNestJS, TypeScript, DDD / Hexagonal architecture
DatabasePostgreSQL, TypeORM, Redis (cache + queues)
AIClaude API (Anthropic), structured prompting, Zod validation
Auth & StorageSupabase Auth, AWS S3
IntegrationsPennylane API, 360Learning API (import)
InfraDocker, CI/CD

// 06 · what-im-taking-away

What I'm taking away

This mission confirmed that AI is only useful when integrated into existing workflows. A standalone AI assistant, nobody uses. An AI module that takes a document you already have on hand and delivers a structured, ready-to-deploy training — that gets used every day.

And the real skill on this kind of mission isn't LLM prowess. It's clean integration into an existing architecture, with strict validations, fallbacks, and a user experience that makes the AI invisible.