πŸ¦‰ ~/projects/atleo — pwsh

PS andrewfry.dev> cat /projects/atleo.md

cd .. to return to /projects

Atleo

Commercial SaaS Β· Enterprise Platform β€” a cloud PLM & compliance product for regulated industries, built on an engine of its own

STATUS: ACTIVE SOLUTION: 61_PROJECTS PATTERN: ENGINE + CLIENT
[C#][.NET Core 3.1][MongoDB][Elasticsearch][Jint][OpenCqrs][Blazor]
  • 61 projects (engine + client)
  • V1β†’V3 declarative deployments
  • 4 property super-types
  • 1 engine Β· many industries

01 The Problem & The Bet

Atleo is a cloud-based Product Lifecycle Management (PLM) and compliance SaaS aimed at the food & beverage, flavor & fragrance, and personal/health-care industries. In those markets a finished product's data β€” formulas, ingredients, supplier and manufacturer records, safety documentation, regulatory dossiers, change-control history β€” is normally scattered across a handful of disconnected systems and stitched together over email and shared drives. Traceability and accuracy leak out at every hand-off.

Atleo is a single digital workstream: R&D drafts a formula, gets early compliance and regulatory pre-checks before launch, manages supplier authorization, and tracks change control after launch with "where-used" material replacement.

The thing I'm proudest of architecturally is what's underneath Atleo. The product isn't a bespoke PLM app β€” it's a fully declarative configuration layered on top of a general platform I built called WorkPath (branded internally as the WrkEngine): a schemaless, object-oriented system of record with a workflow engine, a permission model, and a scripting layer. Atleo is "just" a client definition on top of it β€” swap the object/workflow definitions and the same engine fits a different industry's data model.

02 Two Layers: the Engine and the Client

The codebase splits cleanly into the reusable WorkPath platform and a thin client definition that turns that platform into Atleo. Everything Atleo "is" β€” its objects, properties, states, forms, menus, roles, and workflows β€” is authored in code as a versioned deployment (Version1 β†’ Version3), then pushed to a tenant through the API driver. New capability is added by writing a new version builder, not by changing the engine.

fig 1 β€” declarative client over a reusable engine
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  ATLEO  (client definition β€” src-clients/DemoPLM)             β”‚
  β”‚  ProductLifeMgmt / Version1 Β· Version2 Β· Version3             β”‚
  β”‚  ObjectBuilder Β· WorkflowBuilder Β· FormBuilder Β· MenuBuilder  β”‚
  β”‚  AccessBuilder Β· ViewBuilder Β· ObjectQueryBuilder             β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚ declared over REST via AtleoApiClient
                              β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  WORKPATH  (WrkEngine platform β€” src/)                        β”‚
  β”‚                                                              β”‚
  β”‚   DynamicObjects   Workflow      Permissions   Searching     β”‚
  β”‚   (schemaless      (phase/step   (role/feature (Elastic/NEST)β”‚
  β”‚    templates)       engine)       access)                    β”‚
  β”‚                                                              β”‚
  β”‚   Services Β· Messaging (OpenCqrs/ServiceBus) Β· Reporting     β”‚
  β”‚   DataAccess.MongoDb Β· Forms Β· Blazor Β· Www (host)           β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
             MongoDB (records + GridFS docs) Β· Elasticsearch

Stack: C# / .NET Core 3.1 Β· MongoDB + GridFS Β· Elasticsearch via NEST Β· Jint embedded JavaScript Β· OpenCqrs over Azure Service Bus Β· ASP.NET Core MVC/Razor with Blazor in progress Β· Serilog.

03 The Dynamic Object Model

WorkPath's core is the dynamic object template β€” a runtime-defined, schemaless entity stored in MongoDB. Atleo's entire PLM data model is declared through these templates.

fig 2 β€” the PLM object model
  BaseIngredient (abstract parent)
    β”œβ”€ Formula      ──< Ingredients >── IngredientList >── Ingredient
    └─ Ingredient        (junction: quantity, UoM)        β”‚
                                                           β”œβ”€ Supplier (M:N)
  Supplier  ──< Supplier Ingredients (M:N) >── Ingredient  β”‚
  KnowledgeBase  (regulatory restrictions: CAS #, limits) β”€β”˜

Each object also carries a state machine (Draft β†’ Pending Review β†’ Approved β†’ Obsoleted) and role-based feature permissions (Engineer, Reviewer, Data Steward, System Administrator, Admin) controlling read/write per object and per state.

04 Properties That Do Work

Template properties are richer than plain fields. Beyond strings, doubles, booleans, choice-lists, text, and document attachments, a property can be:

  • Auto-increment β€” e.g. Formula Id as F00000000.##, Chemical Id as I00000000.##.
  • Formula (computed) β€” a Jint script evaluated against the record. Atleo uses these for ingredient roll-ups: a formula's Cost iterates its IngredientList children, multiplying each ingredient cost by quantity; Total Ingredients sums the weights.
  • Reference / many-to-many β€” typed relationships (Formula↔IngredientList↔Ingredient, Supplier↔Ingredient) with configurable search/display fields and inline-form vs. modal rendering.
  • Query / constraint β€” a live bound query. The Ingredient.Regulations property runs a regex over the KnowledgeBase by CAS number, so every ingredient automatically surfaces the regulations that touch it.

05 The Workflow Engine

The other half of the platform is a phase-and-step workflow engine. A workflow binds to an object type; it's made of phases, each containing ordered steps, with steps branching to a next phase based on outcomes. Step types include Object State Change, Object Task (a human task assigned to a role, with outcome conditions), Object Formula (an in-process Jint script), plus Approval, Form, Email/Alert, Fulfillment Task, and Status.

fig 3 β€” the Formula Review workflow, as declared
  DRAFT ──(submit)──► [Object State: Pending Review]
                             β”‚
                             β–Ό
                   [Task: "Review Formula"]  (role: Reviewer)
                      β”‚         β”‚              β”‚
                  Rejected   Approved   Approved With Conditions
                      β”‚         β”‚              β”‚  (require Reviewer Comments)
                      β–Ό         β–Ό              β–Ό
                 β–Ί Draft    β–Ί Approved    β–Ί Approved With Conditions

Mapped onto the product, these primitives compose into the full set of PLM workflows: Formula Creation, Formula Sampling (study β†’ samples β†’ results β†’ review), Supplier Authorization (audit β†’ site approval), Compliance KB Check, Dossier Preparation / Registration, Material Where-Used (impact analysis β†’ replacement job), and Ingredient Label Generation.

06 Early Compliance: the Heart of the Pitch

Atleo's selling point is catching regulatory problems during R&D instead of after launch. That's not a feature bolted on β€” it falls out of the object model plus a workflow formula step. Ingredients are bound to the regulatory KnowledgeBase by CAS number; the Run Regulatory Assessment workflow then iterates a formula's ingredients against each restriction.

fig 4 β€” Run Regulatory Assessment
  Formula ──► for each restriction in KnowledgeBase:
                 for each ingredient (matched by CAS #):
                    pass = ingredient.quantity < restriction.target_value
                    └─► emit regulatory_assessment_result
                        { success, cas, restriction, ingredient_value }
             ──► SUCCESS / FAILED  ──► state change

Every check writes an auditable result object, so a clearance assessment leaves a trail rather than a yes/no. When a new regulation passes (the worked example is New York's 1,4-dioxane limit), the same machinery answers which products are impacted and feeds the where-used replacement flow β€” all inside one system instead of a migrate-and-email scramble across four.

07 Reflections

The bet that paid off was refusing to write Atleo as an application. By pushing every product-specific decision β€” the Formula/Ingredient/Supplier/KnowledgeBase model, the roll-up math, the approval flows, the role matrix β€” into declarative builders run through an API, the engine itself stayed industry-agnostic. The Version1 β†’ Version3 progression shows it working: the data model grew from formulas-and-ingredients to suppliers, regulatory knowledge bases, and live compliance assessments without the platform underneath changing shape.

The flip side is the cost of generality β€” a schemaless dynamic-object engine with an embedded JS layer is a lot of machinery to carry, and the configuration-in-C# approach trades a visual admin UI for developer-authored deployments. For a regulated, multi-industry PLM where every customer's data model is different, that trade has been the right one.

> next: cat /projects/workpath β€” the metadata-driven engine underneath