Agenda.Allocation (Agenda v0.1.0)

Copy Markdown View Source

One resource, bound to one session, over one interval.

An allocation is keyed by the session that holds it. That single decision is what makes releasing a resource a consequence rather than a workflow: cancelling a session drops its key, and every resource it held is free again because nothing else was holding them. There is no release step to forget.

An allocation is the persisted grain of a Agenda.Arrangement.t/0 — the arrangement proposes a room and a roster for a time, and allocating it records one of these per resource.

The tag

tag records what the claim was for, as a {kind, subject} pair. It is the field that lets one ledger hold work and absence in the same structure:

{:project, "ACME-2026-01"}
{:leave, :annual}
{:holiday, "Brisbane show day"}

Nothing here interprets a tag — agenda does not know what a project or a leave type is, and does not decide which kinds exist. What it gains is that a person's time can be reconciled without a second schema: every claim on a resource is in one place, and the tag says which bucket it belongs to. Agenda.reconcile/3 groups by it.

The consequence worth naming: a day cannot be two things. A consultant on annual leave cannot also be billed to a project, because both would be claims on the same resource over the same span, and that is the overlap the ledger already refuses.

Summary

Types

t()

One resource held by one session for one interval.

What a claim was for, as {kind, subject}.

Functions

The allocations an arrangement implies — one per resource, across every role.

true when two allocations bind the same resource, in the same role, over the same span.

Types

t()

@type t() :: %Agenda.Allocation{
  held_until: Tempo.t() | nil,
  interval: Tempo.Interval.t(),
  resource: String.t(),
  role: atom(),
  series: String.t() | nil,
  session: String.t(),
  tag: tag() | nil
}

One resource held by one session for one interval.

held_until marks a tentative claim — a hold, not a booking. It occupies the resource exactly as a firm allocation does, which is why it lives here rather than in a persistence layer: availability is derived on every call, so a hold nobody can see is a hold nobody subtracts. nil means the claim is firm.

tag records what the claim was for. nil means untagged, which is every allocation made before a caller asks for one.

tag()

@type tag() :: {atom(), term()}

What a claim was for, as {kind, subject}.

The kinds are the caller's, not this library's. {:project, "ACME"}, {:leave, :annual} and {:holiday, "Show day"} are the three this library's documentation uses, because they are the three a timesheet needs, but nothing rejects another.

Functions

from_arrangement(arrangement, options \\ [])

@spec from_arrangement(
  Agenda.Arrangement.t(),
  keyword()
) :: [t()]

The allocations an arrangement implies — one per resource, across every role.

Arguments

Options

  • :tag is what the claim was for, as a tag/0{:project, "ACME-2026-01"} or {:leave, :annual}. Every allocation the arrangement implies carries it, because they are one booking. The default is nil.

Returns

  • the allocations, ordered by role then resource name so that two equal arrangements always yield an equal list.

Examples

iex> import Tempo.Sigils
iex> boardroom = Agenda.resource("Boardroom")
iex> arrangement = %Agenda.Arrangement{
...>   session: "Review",
...>   interval: ~o"2026-06-16T10:00:00/2026-06-16T11:00:00",
...>   allocations: %{room: [boardroom]}
...> }
iex> [allocation] = Agenda.Allocation.from_arrangement(arrangement)
iex> {allocation.session, allocation.role, allocation.resource}
{"Review", :room, "Boardroom"}

Tagging what the claim was for:

iex> import Tempo.Sigils
iex> arrangement = %Agenda.Arrangement{
...>   session: "Discovery",
...>   interval: ~o"2026-06-16T09:00:00/2026-06-16T17:00:00",
...>   allocations: %{consultant: [Agenda.resource("Dana")]}
...> }
iex> [allocation] =
...>   Agenda.Allocation.from_arrangement(arrangement, tag: {:project, "ACME-2026-01"})
iex> allocation.tag
{:project, "ACME-2026-01"}

same?(a, b)

@spec same?(t(), t()) :: boolean()

true when two allocations bind the same resource, in the same role, over the same span.

This is the identity a changeset compares on — it deliberately ignores which session holds it, because rearrange/3 asks "is this same binding still wanted?" within one session.

Arguments

  • a and b are each a t/0.

Returns

  • true or false.

Examples

iex> import Tempo.Sigils
iex> hour = ~o"2026-06-16T10:00:00/2026-06-16T11:00:00"
iex> a = %Agenda.Allocation{role: :room, resource: "Boardroom", interval: hour}
iex> b = %Agenda.Allocation{role: :room, resource: "Boardroom", interval: hour}
iex> Agenda.Allocation.same?(a, b)
true