A requirement — what a session demands of the resources allocated to it.
A requirement is named, so that an arrangement can say which role a resource was chosen for, and comes in two shapes:
by attribute — "a room seating at least eight with video conferencing"; any resource satisfying the predicates will do.
by roster — "Alice, Bob and Carol"; these named resources specifically.
Both shapes are the same struct, so a session can hold a list of requirements without the caller sorting them into kinds.
Summary
Functions
The resources from candidates that satisfy requirement.
true when resource satisfies every attribute of requirement.
Fold the requirements induced by resources into requirement.
Combine two requirements on the same role into one.
Build a requirement from attribute predicates.
Build a requirement naming specific resources.
Every way resource fails requirement, as readable phrases.
Types
@type t() :: %Agenda.Requirement{ attributes: %{optional(atom()) => Agenda.Predicate.t()}, name: atom(), roster: [Agenda.Resource.t()] }
What a session demands, under a role name.
Functions
@spec eligible(t(), [Agenda.Resource.t()]) :: [Agenda.Resource.t()]
The resources from candidates that satisfy requirement.
Arguments
requirementis at/0.candidatesis a list ofAgenda.Resource.t/0.
Returns
- the eligible resources, in the order given.
Examples
iex> import Agenda.Predicate
iex> big = Agenda.Resource.new("Boardroom", seats: 8)
iex> small = Agenda.Resource.new("Meeting room 2", seats: 4)
iex> requirement = Agenda.Requirement.new(:room, seats: at_least(8))
iex> Agenda.Requirement.eligible(requirement, [big, small]) |> Enum.map(& &1.name)
["Boardroom"]
@spec eligible?(t(), Agenda.Resource.t()) :: boolean()
true when resource satisfies every attribute of requirement.
Arguments
requirementis at/0.resourceis aAgenda.Resource.t/0.
Returns
trueorfalse.
Examples
iex> import Agenda.Predicate
iex> boardroom = Agenda.Resource.new("Boardroom", seats: 8)
iex> requirement = Agenda.Requirement.new(:room, seats: at_least(8))
iex> Agenda.Requirement.eligible?(requirement, boardroom)
true
@spec induce(t(), [Agenda.Resource.t()]) :: t()
Fold the requirements induced by resources into requirement.
This is what makes requires: on a person bind the room: allocating
Alice, who requires step-free access, tightens the room requirement
so that an inaccessible room stops being eligible.
Arguments
requirementis thet/0to tighten.resourcesis a list ofAgenda.Resource.t/0whoserequiresshould be folded in.
Returns
- a
t/0carrying the additional predicates.
Examples
iex> alice = Agenda.Resource.new("Alice", requires: [step_free_access: true])
iex> room = Agenda.Requirement.new(:room, seats: 8)
iex> tightened = Agenda.Requirement.induce(room, [alice])
iex> Map.keys(tightened.attributes) |> Enum.sort()
[:seats, :step_free_access]
Combine two requirements on the same role into one.
Naming a role twice is one demand stated in two calls, not two
demands: roster(:speaker, [alice]) followed by
roster(:speaker, [bob]) wants both people on stage. The
alternative — keeping both requirements — cannot be honoured,
because an arrangement fills each role once, so the second would
quietly displace the first.
Attributes combine the way induce/2 combines them, with the later
predicate winning where both name the same attribute. Rosters are
concatenated, and a resource named twice is named once.
Arguments
Returns
- one
t/0demanding everything both demanded.
Examples
iex> alice = Agenda.Resource.new("Alice")
iex> bob = Agenda.Resource.new("Bob")
iex> first = Agenda.Requirement.roster(:speaker, [alice])
iex> second = Agenda.Requirement.roster(:speaker, [bob])
iex> Agenda.Requirement.merge(first, second).roster |> Enum.map(& &1.name)
["Alice", "Bob"]
iex> import Agenda.Predicate
iex> seating = Agenda.Requirement.new(:room, seats: at_least(250))
iex> wired = Agenda.Requirement.new(:room, projector: true)
iex> Agenda.Requirement.merge(seating, wired).attributes |> Map.keys() |> Enum.sort()
[:projector, :seats]
Build a requirement from attribute predicates.
Bare values are coerced to Agenda.Predicate.exactly/1, so
video_conferencing: true and seats: at_least(8) may be mixed
freely.
Arguments
nameis the role name, such as:room.predicatesis a keyword list of attribute predicates.
Returns
- a
t/0.
Examples
iex> import Agenda.Predicate
iex> requirement = Agenda.Requirement.new(:room, seats: at_least(8))
iex> requirement.attributes[:seats].op
:at_least
@spec roster(atom(), [Agenda.Resource.t()]) :: t()
Build a requirement naming specific resources.
Arguments
nameis the role name, such as:attendees.resourcesis the list of requiredAgenda.Resource.t/0.
Returns
- a
t/0.
Examples
iex> alice = Agenda.Resource.new("Alice")
iex> requirement = Agenda.Requirement.roster(:attendees, [alice])
iex> Enum.map(requirement.roster, & &1.name)
["Alice"]
@spec unmet(t(), Agenda.Resource.t()) :: [String.t()]
Every way resource fails requirement, as readable phrases.
An empty list means the resource is eligible — so eligible?/2 is
unmet/2 returning nothing, and the explanation is never computed
separately from the decision.
Arguments
requirementis at/0.resourceis aAgenda.Resource.t/0.
Returns
- a list of phrases, one per unsatisfied attribute, empty when the resource qualifies.
Examples
iex> import Agenda.Predicate
iex> small = Agenda.Resource.new("Meeting room 2", seats: 4)
iex> requirement = Agenda.Requirement.new(:room, seats: at_least(8))
iex> Agenda.Requirement.unmet(requirement, small)
["seats is 4 — needs at least 8"]