From Scorecard APIs to Business Workflows

2026-06-15

I used to think the scorecard system was complicated because the code was complicated.

There were large handlers, overloaded APIs, branching behavior, old data models, new data models, permissions, submission rules, calibration rules, appeal rules, and analytics projection rules. Every time I looked at the code, the shape of the problem seemed to be in the implementation:

So my first instinct was to solve the complexity at the code level.

Looking back, that instinct was only half right. The code was complex, but the deeper issue was that the business model had outgrown the names in the code.

When One Workflow Owned the Domain

Originally, the mental model was simple:

In that world, APIs like these were natural:

The names matched the product. A scorecard was mostly an evaluation artifact. Updating a scorecard meant editing an evaluation. Submitting a scorecard meant finalizing an evaluation. The workflow was so obvious that it did not need a name.

That is the trap.

When there is only one workflow, the workflow disappears into the artifact. “Scorecard” silently means “evaluation scorecard.” “Submit” silently means “submit an evaluation.” “Update” silently means “update the evaluation result.”

The API looks clean because the business context is implicit.

Then the Product Added More Workflows

Over time, scorecards started appearing in more places:

The same scorecard-shaped data was still useful, but it no longer played one role.

In evaluation, a scorecard is the evaluation result.

In calibration, a scorecard may be the benchmark answer set, or it may be a participant response being compared with the benchmark.

In appeal, a scorecard may represent the original evaluation, the requested correction, or the resolved final decision for an appeal round.

In analytics, a scorecard is historical evidence projected into a reporting shape.

In repair, a scorecard is the authoritative source used to reconstruct downstream state.

Structurally, these things look similar. Semantically, they are not the same.

But the API names did not change at the same pace as the product model. We kept stretching generic verbs:

The verbs stayed artifact-centric while the business had become workflow-centric.

My First Attempt: More Scorecard Types

The natural engineering response was object-oriented.

If scorecards behave differently in different situations, create different scorecard types. Keep a base scorecard abstraction, then layer specific implementations on top:

Each type can implement familiar operations:

Then an API like updateScorecard can load the scorecard, inspect its type, dispatch to the corresponding implementation, and let each implementation handle its own rules.

That approach is not wrong. It can be a reasonable transitional design. It creates places to put behavior. It may reduce some branching in a large handler. It gives engineers a way to stop adding every new case to the same function.

But it has several flaws.

The Abstraction Boundary Is Still the Artifact

The design still starts from the question:

What type of scorecard is this?

But the real business question is often:

What workflow is this action part of?

Those are not always equivalent.

An appeal may involve the original scorecard, a proposed correction, reviewer decisions, audit history, and analytics impact. Calling the whole thing an “appeal scorecard” pushes workflow semantics into one artifact-shaped box.

Calibration has a similar issue. The benchmark and participant response may both be scorecard-shaped, but their roles are different. Treating them as different scorecard subtypes can work, but it hides the more important concept: calibration is a workflow with actors, roles, comparison rules, visibility rules, and state transitions.

Generic Verbs Stay Ambiguous

Even with separate scorecard types, updateScorecard still carries too much meaning.

Does update mean:

Those operations may touch similar fields, but they are not the same business action. When a single API name covers all of them, the caller has to know hidden context.

The implementation may become cleaner internally, but the product semantics remain undernamed.

Type Dispatch Replaces Domain Modeling

A type switch can make code easier to organize, but it can also become a disguised version of the original problem.

Instead of one large updateScorecard function with many branches, we get:

The code looks more object-oriented, but the domain model is still incomplete if workflow is not explicit.

The result is often a system that is cleaner locally but still hard to reason about globally.

Workflow Rules Leak Into Artifact Helpers

Once workflow is not named, basic artifact helpers start accumulating workflow rules:

The helper that was supposed to “just update a scorecard” becomes a policy engine. Over time, nobody knows whether a change is safe because the function name sounds generic but the behavior is not.

The Missing Concept: Behavioral Frames

The shift for me was separating the domain into two categories.

Domain artifacts are the things that exist as product and system objects:

Behavioral frames are the lenses that explain how those artifacts are used:

The short version:

A template is still a template. A scorecard is still a scorecard. But their meaning changes depending on the lifecycle stage and workflow.

Lifecycle explains how an artifact changes over time:

Workflow explains why the artifact exists and what role it plays:

Once I saw that split, the complexity started to move to the right place.

A Better Design Direction

The better design is not necessarily “replace all generic APIs with workflow APIs.” It is to make each layer honest about what it owns.

Layer 1: Artifact Primitives

At the bottom, keep small artifact-level helpers.

These should be boring and narrow:

These helpers should not decide what an appeal means or when a calibration participant can edit a response. They should enforce low-level invariants and perform well-defined state changes.

In this layer, names like updateScorecard can still exist, but they should mean exactly that: update the scorecard artifact, not execute a business workflow.

Layer 2: Workflow Commands

Above the primitives, define workflow-level commands with business names:

These names are longer, but they carry meaning. They tell the caller which business action is happening. They give the implementation a clear home for actor checks, state transitions, validation, audit behavior, and side effects.

This layer can call the artifact primitives, but it should own workflow policy.

Layer 3: Workflow State and Audit

Some workflows deserve explicit state models.

Appeal is the clearest example. An appeal is not just an update to a scorecard. It has:

Trying to express all of that through updateScorecard is how a simple API becomes a confusing API.

For workflows with real state, model the workflow state directly. The scorecard remains an artifact inside the workflow, not the workflow itself.

The Design Options

There are a few reasonable options, depending on how mature the workflow is.

Option 1: Keep Generic APIs, Add Workflow Parameters

Example:

This is the smallest change, but it is also the weakest model. It can help with logging, permissions, and routing, but the API name still hides business intent. Over time, workflow_context risks becoming a bag of flags.

This option is useful as a migration step, not as the end state.

Option 2: Scorecard Type Dispatch

Example:

This organizes code better than a single handler. It works when each scorecard type maps cleanly to one business meaning.

The flaw is that it still treats workflow as a property of the artifact. That breaks down when one workflow contains multiple scorecard roles, or when the same scorecard participates in multiple workflows.

This option is useful for local cleanup, but it does not fully solve the domain-modeling problem.

Option 3: Workflow-Specific Commands Over Shared Artifact Primitives

Example:

Each command uses shared scorecard/template helpers underneath.

This is the best general direction. It keeps reusable code reusable while making business actions explicit. It also gives permissions, validation, lifecycle transitions, and audit behavior a natural home.

The cost is more API surface area and more naming work. But that cost is often worth paying once the product has multiple workflows.

Option 4: Full Workflow Engine or State Machine

For mature workflows with many states and transitions, a state machine may be appropriate.

This can make transitions explicit:

But a workflow engine is not automatically better. If introduced too early, it can become ceremony around unclear concepts. The domain language has to come first.

This option is strongest when transitions, actors, and audit requirements are stable enough to model directly.

What AI Can and Cannot Help With

AI can help refactor the code.

It can read handlers, identify duplication, propose abstractions, generate type hierarchies, and draft migration plans. Those are useful capabilities.

But AI will usually follow the concepts you give it.

If you ask it to clean up scorecard code, it will clean up scorecard code. If your mental model is artifact-centric, the output will probably be artifact-centric too:

That may look reasonable because it is reasonable inside the frame you supplied.

The harder part is noticing that the frame itself is wrong.

That usually requires hands-on product context:

AI can help once you name the frame. It is much less likely to discover the frame for you.

The General Lesson

When a domain grows, complexity often appears first in the code. The temptation is to answer with code structure:

Sometimes that is necessary. But before choosing the abstraction, ask whether the business language has changed.

In this case, the product moved from one implicit workflow to multiple explicit workflows. The code kept using artifact-centric names because those names had been correct in the old world.

The real shift was not from bad code to good code. It was from artifact thinking to behavior-frame thinking.

Once workflows are named, the design becomes clearer:

That is the kind of clarity that does not come from staring harder at the code. It comes from understanding what the business is now asking the code to represent.