Eyup - it's that Mark Smith!

ECSx Kanban Simulator: Work Creators

In our simulation of software development in a Kanban system, we can imagine three roles responsible for creating technical work for the development team:

Let's think about what attributes a Work Creator should have in our system:

Talking about moments reminds me that we've not yet calibrated system ticks against the passage of time in the simulated world. For now, I'm going to arbitrarily rule that one second (one tick) in the system is maybe five minutes in the simulation. That way a simulated eight-hour day will take 96 seconds in real time. We'll revisit this later when we've actually got something to judge it against.

I used the term 'attributes' above, but in an Entity-Component System, you think in terms of Components. Our PO is composed of a Name, Probability, Identity and Role. We can quickly specify those.

Let's begin with the Name. We can add a Component like this:

mix ecsx.gen.component Name binary

* injecting lib/taiichi/manager.ex
* creating lib/taiichi/components/name.ex

Elixir uses binary for text representations. Let's look at the code in name.ex:

defmodule Taiichi.Components.Name do
  @moduledoc """
  Documentation for Name components.
  """
  use ECSx.Component,
    value: :binary
end

And in manager.ex:

...
  # Declare all valid Component types
  def components do
    [
      Taiichi.Components.Name
    ]
  end
...

Now let's work out how to store the identity of the Product Owner. ECSx has a Tag component which stores binary values. We can use that, maybe. The ecsx.gen.component command also takes atoms so we could use that too. I'm going to go with the tag option for now. It might not work out - let's see!

I'm going to create a tag called WorkCreator:

mix ecsx.gen.tag WorkCreator
* injecting lib/taiichi/manager.ex
* creating lib/taiichi/components/work_creator.ex

The generated code is very similar to that for the Name component we created earlier, apart from the key bit is just use ECSx.Tag. The last thing we need to add for now is a probability:

mix ecsx.gen.component Probability float
* injecting lib/taiichi/manager.ex
* creating lib/taiichi/components/probability.ex

Great, we can actually start writing some code! In manager.ex, lets add the below to startup/0:

  def startup do
    # Load ephemeral components during first server start and again
    # on every subsequent app restart
    Logger.info("ECSx.Manager startup")

    entity = Ecto.UUID.generate()
    WorkCreator.add(entity)
    Name.add(entity, "Product Owner")
    Probability.add(entity, 0.05)

    entity = Ecto.UUID.generate()
    WorkCreator.add(entity)
    Name.add(entity, "Customer Support")
    Probability.add(entity, 0.04)

    entity = Ecto.UUID.generate()
    WorkCreator.add(entity)
    Name.add(entity, "Technical Debt")
    Probability.add(entity, 0.03)

    :ok
  end

My thinking is that these three are the most common creators of work for development teas. For now, I'm giving them different probabilities of creating work items. In the future I'll probably give them different sizes for the work, once I've worked out how I want to represent that. In kanban.ex we can put some code in the run/0 function that will tap into the work creators above:

  def run do
    # System logic
    {:ok, datetime} = DateTime.now("Etc/UTC")
    Logger.info("Taiichi.Systems.Kanban.run/0 #{datetime}")

     for worker_id <- Taiichi.Components.WorkCreator.get_all() do
         worker_name = Taiichi.Components.Name.get(worker_id)
         worker_probability = Taiichi.Components.Probability.get(worker_id)

         Logger.info("Found #{worker_id}: #{worker_name}: #{worker_probability}")
     end

    :ok
  end

The only odd bit is the <- arrow showing the worker_ids being retrieved from the WorkCreator Component. Looks a bit weird to a C# developer though there's the same thing in F# for changing a mutable value.

Now, when we run our application we see the output:

mix phx.server
...
[info] Taiichi.Systems.Kanban.run/0 2025-12-01 22:14:34.486355Z
[info] Found 62b7b819-0362-4bfc-a886-4cd25c057225: Product Owner: 0.05
[info] Found 28a786e0-6196-449d-a25c-491a6f7e2dfd: Technical Debt: 0.03
[info] Found d2af68cb-a6b9-4406-b44b-31f7f9ad01cd: Customer Support: 0.04
[info] Taiichi.Systems.Kanban.run/0 2025-12-01 22:14:35.486713Z
[info] Found 62b7b819-0362-4bfc-a886-4cd25c057225: Product Owner: 0.05
[info] Found 28a786e0-6196-449d-a25c-491a6f7e2dfd: Technical Debt: 0.03
[info] Found d2af68cb-a6b9-4406-b44b-31f7f9ad01cd: Customer Support: 0.04

Let's finish today by adding some code to run/0 about to tap into that probability value to see if that work creator wants to create a work item at that point.

  target = :rand.uniform()
  if (target < worker_probability), do: Logger.info("  -> Creating work item from #{worker_name}.")

In Elixir, if is a macro that takes (I'm guessing) an expression parameter and a code block parameter called do, hence the funky comma after the (target < worker_probability) condition.

Finally for today, let's run it:

[info] Taiichi.Systems.Kanban.run/0 2025-12-01 22:50:54.525660Z
[info] Found fc20e954-a78b-421b-82b4-3070e9f6d8c1: Product Owner: 0.05
[info] Found 7c75e761-b889-4e6b-ad63-278673ca49f9: Technical Debt: 0.03
[info] Found fff397f0-31ab-4fe7-99d8-122017919be1: Customer Support: 0.04
[info] Taiichi.Systems.Kanban.run/0 2025-12-01 22:50:55.525563Z
[info] Found fc20e954-a78b-421b-82b4-3070e9f6d8c1: Product Owner: 0.05
[info] Found 7c75e761-b889-4e6b-ad63-278673ca49f9: Technical Debt: 0.03
[info]   -> Creating work item from Technical Debt.
[info] Found fff397f0-31ab-4fe7-99d8-122017919be1: Customer Support: 0.04

The result we wanted - work items can be created, every so often. Next time, actually creating work items.

Other posts in this series:

#Dashboard #ECSx #Elixir #Kanban #Lean #Observability #Phoenix #taiichi