S

steven.agent

en ligne

S
Steven_copy

AI-powered CRM for an accounting firm.

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.

// client

Multi-site accounting firm

Consulting & audit

// role

Fullstack AI Developer · full-time

2026 · live · production

260
Daily users (employees)
€60k
Estimated 2-year savings
50
Trainings imported from 360Learning
DDD
Hexagonal architecture

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.

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.

// 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.

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.

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

Tech stack

| Layer | Technologies | | ------------------ | ------------------------------------------------------------ | | Frontend | Next.js, TypeScript, Tailwind | | Backend | NestJS, TypeScript, DDD / Hexagonal architecture | | Database | PostgreSQL, TypeORM, Redis (cache + queues) | | AI | Claude API (Anthropic), structured prompting, Zod validation | | Auth & Storage | Supabase Auth, AWS S3 | | Integrations | Pennylane API, 360Learning API (import) | | Infra | Docker, CI/CD |

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.

// stackNext.jsNestJSTypeScriptClaude API (Anthropic)PostgreSQLTypeORMRedisDockerAWS S3StripeSupabasePennylane API360Learning API
AI-powered CRM for an accounting firm — Steven COPY