There is an ongoing outage on the primary CPAN mirror. It is possible to work around the issue by using MetaCPAN as a mirror.

NAME

Schedule::Activity - Generate random activity schedules

VERSION

Version 0.1.3

SYNOPSIS

use Schedule::Activity;
my %schedule=Schedule::Activity::buildSchedule(
  configuration=>{
    node=>{
      Activity=>{
        message=>'Begin Activity',
        next=>['action 1'],
        tmmin=>5,tmavg=>5,tmmax=>5,
        finish=>'Activity, conclude',
      },
      'action 1'=>{
        message=>'Begin action 1',
        tmmin=>5,tmavg=>10,tmmax=>15,
        next=>['action 2'],
      },
      'action 2'=>{
        message=>'Begin action 2',
        tmmin=>5,tmavg=>10,tmmax=>15,
        next=>['Activity, conclude'],
      },
      'Activity, conclude'=>{
        message=>'Conclude Activity',
        tmmin=>5,tmavg=>5,tmmax=>5,
      },
    },
    annotations=>{...},
    attributes =>{...},
    messages   =>{...},
  },
  activities=>[
    [30,'Activity'],
    ...
  ],
);
print join("\n",map {"$$_[0]:  $$_[1]{message}"} @{$schedule{activities}});

DESCRIPTION

This module permits building schedules of activities each containing randomly-generated lists of actions. This two-level approach uses explicit goal times to construct the specified list of activities. Within activities, actions are chosen within configured limits, possibly with randomization and cycling, using slack and buffer timing adjustments to achieve the goal.

For additional examples, see the samples/ directory.

Areas subject to change are documented below. Configurations and goals may lead to cases that currently die(), so callers should plan to trap and handle these exceptions accordingly.

CONFIGURATION

A configuration for scheduling contains the following sections:

%configuration=(
  node       =>{...}
  attributes =>...  # see below
  annotations=>...  # see below
  messages   =>...  # see below
)

Both activities and actions are configured as named node entries. With this structure, an action and activity may have the same message, but must use different key names.

'activity name'=>{
  message=>...    # an optional message string or object
  next   =>[...], # list of child node names
  finish =>'activity conclusion',
  #
  (time specification)
  (attributes specification)
}
'action name'=>{
  message=>...    # an optional message string or object
  next   =>[...], # list of child node names
  #
  (time specification)
  (attributes specification)
}

The list of next nodes is a list of names, which must be defined in the configuration. During schedule construction, entries will be chosen randomly from the list of next nodes. The conclusion must be reachable from the initial activity, or scheduling will fail. There is no further restriction on the items in next: Scheduling specifically supports cyclic/recursive actions, including self-cycles.

There is no functional difference between activities and actions except that a node must contain finish to be used for activity scheduling. Nomenclature is primarily to support schedule organization: A collection of random actions is used to build an activity; a sequence of activities is used to build a schedule.

Time specification

The only time specification currently supported is:

tmmin=>seconds, tmavg=>seconds, tmmax=>seconds

Values must be non-negative numbers. All three values may be identical. Note that scheduling to a given goal may be impossible without slack or buffer within some of the actions:

slack =tmavg-tmmin
buffer=tmmax-tmavg

The slack is the amount of time that could be reduced in an action before it would need to be removed/replaced in the schedule. The buffer is the amount of time that could be added to an action before additional actions would be needed in the schedule.

Providing any time value will automatically set any missing values at the fixed ratios 3,4,5. EG, specifying only tmmax=40 will set tmmin=24 and tmavg=32. If provided two time values, priority is given to tmavg to set the third.

Future changes may support adjusting these ratios, automatic slack/buffering, univeral slack/buffer ratios, and open-ended/relaxed slack/buffering.

Messages

Each activity/action node may contain an optional message. Messages are provided so the caller can easily format the returned schedules. While message attributes may be used during schedule, the message strings themselves are not used during scheduling. Messages may be:

message=>'A message string'
message=>'named message key'
message=>['An array','of alternates','chosen randomly']
message=>{name=>'named message key'}
message=>{
  alternates=>[
    {message=>'A hash containing an array', attributes=>{...}}
    {message=>'of alternates',              attributes=>{...}}
    {message=>'with optional attributes',   attributes=>{...}}
    {message=>'named message key'}
    {name=>'named message key'}
  ]
}

Message selection is randomized for arrays and a hash of alternates. Any attributes are emitted with the attribute response values, described below.

RESPONSE

The response from buildSchedule is:

%schedule=(
  error=>['list of validation errors, if any',...],
  activities=>[
    [seconds, message],
    ..,
  ],
  annotations=>{
    'group'=>{
      events=>[
        [seconds, message],
      ]
    },
    ...
  },
  attributes=>{
    name=>{
      y  =>(final value),
      xy =>[[tm,value],...],
      avg=>(average, depends on type),
    },
    ...
  },
)

Failures

In addition to validation failures returned through error, the following may cause the scheduler to die(): The activity name is undefined. The scheduler was not able to reach the named finish node. The number of retries or backtracking attempts has been exhausted.

The difference between the result time and the goal may cause retries when an excess exceeds the available slack, or when a shortage exceeds the available buffer.

Caution: While startup/conclusion of activities may have fixed time specifications, at this time it is recommended that actions always contain some slack/buffer. There is currently no "relaxing mechanism" during scheduling, so a configured with no slack nor buffer must exactly meet the goal time requested.

ATTRIBUTES

Attributes permit tracking boolean or numeric values during schedule construction. The result of buildSchedule contains attribute information that can be used to verify or adjust the schedule.

Types

The two types of attributes are bool or int, which is the default. A boolean attribute is primarily used as a state flag. An integer attribute can be used both as a counter or gauge, either to track the number of occurrences of an activity or event, or to log varying numeric values.

Configuration

Multiple attributes can be referenced from any activity/action. For example:

'activity/action name'=>{
  attributes=>{
    temperature=>{set=>value, incr=>value, decr=>value, note=>'comment'},
    counter    =>{set=>value, incr=>value, note=>'comment'},
    flag       =>{set=>0/1, note=>'comment'},
  },
}

Any attribute may include a note for convenience, but this value is not stored nor reported.

The main configuration can also declare attribute names and starting values. It is recommended to set any non-zero initial values in this fashion, since calling set requires that activity to always be the first requested in the schedule. Boolean values must be declared in this section:

%configuration=(
  attributes=>{
    flagA  =>{type=>'bool'},
    flagB  =>{type=>'bool', value=>1},
    counter=>{type=>'int',  value=>0},
  },
)

Attributes within message alternate configurations and named messages are identified during configuration validation. Together with activity/action configurations, attributes are verified before schedule construction, which will fail if an attribute name is referenced in a conflicting manner.

Response values

The response from buildSchedule includes an attributes section as:

attributes=>{
  name=>{
    y  =>(final value),
    xy =>[[tm,value],...],
    avg=>(average, depends on type),
  },
  ...
}

The y value is the final value at the conclusion of the final activity in the schedule. The xy contains an array of all values and the times at which they changed; see Logging. The avg is roughly the time-weighted average of the value, but this depends on the attribute type.

If an activity containing a unique attribute is not used during construction, the attribute will still be included in the response with its default and initial value.

Integer attributes

The int type is the default for attributes. If initialized in %configuration, it may specify the type, or the value, or both. The default value is zero, but this may be overwritten if the first activity node specifically calls set.

Integer attributes within activity/actions support all of: set, incr, decr. There is no current restriction on values; they may be integers or real numbers, positive or negative.

The reported avg is the overall time-weighted average of the values, computed via a trapezoid rule. That is, if tm=0, value=2 and tm=10, value=12, the average is 7 with a weight of 10. See Logging for more details about averages over activity boundaries.

Boolean attributes

The bool type must be declared in %configuration. The value may be specified, but defaults to zero/false.

Boolean attributes within activity/actions support: set. Currently there is no restriction on values, but the behavior is only defined for values 0/1.

The reported avg is the percentage of time in the schedule for which the flag was true. That is, if tm=0, value=0, and tm=7, value=1, and tm=10, value=1 is the complete schedule, then the reported average for the boolean will be 0.3.

Precedence

When an activity/action node and a selected message both contain attributes, the value of the attribute is updated first from the action node and then from the message node. For boolean attributes, this means the "value set in the message has precedence". For integer attributes, suppose that the value is initially zero; then, if both the action and message have attribute operators, the result will be:

Action  Message  Value
set=1   set=2      2
incr=3  set=4      4
set=5   incr=6    11
incr=7  incr=8    15

Logging

The reported xy is an array of values of the form (tm, value), with each representing an activity/action referencing that attribute built into the schedule. Each attribute will have its initial value of (0, value), either the default or the value specified in configuration{attributes}.

Undecided behavior: As of version 0.1.1, attribute logging will also occur at the end of every activity, so changes in attributes across activity boundaries do not affect the average value calculation. In particular, the starting value in any given activity is the most recent value in the previous activity, adjusted by any operator in the activity node itself. For example, suppose two activities go from tm=0 to 10, and from tm=10 to 20. If an attribute is set to tm=0, value=5 and not set again until tm=15, value=0, then the average in the first activity is five.

ANNOTATIONS

A scheduling configuration may contain a list of annotations:

%configuration=(
  annotations=>{
    'annotation group'=>[
      {annotation configuration},
      ...
    ]
  },
)

Scheduling annotations are a collection of secondary events to be attached to the built schedule and are configured as described in Schedule::Activity::Annotation. Each named group can have one or more annotation. Each annotation will be inserted around the matching actions in the schedule and be reported from buildSchedule in the annotations section as:

annotations=>{
  'group'=>{
    events=>[
      [seconds, message],
      ...
    ]
  },
}

Within an individual group, earlier annotations take priority if two events are scheduled at the same time. Multiple groups of annotations may have conflicting event schedules with event overlap. Note that the between setting is only enforced for each annotation individually at this time.

As of version 0.1.1, annotations do not update the attributes response from buildSchedule. Because annotations may themselves contain attributes, they are retained separately from the main schedule of activities to permit easier rebuilding. At this time, however, the caller must verify that annotation schedules before merging them and their attributes into the schedule. Annotations may also be built separately after schedule construction as described in Schedule::Activity::Annotation.

Annotations may use named messages, and messages in the annotations response structure are materialized using the named message configuration passed to buildSchedule.

NAMED MESSAGES

A scheduling configuration may contain a list of common messages. This is particularly useful when there are a large number of common alternate messages where copy/pasting through the scheduling configuration would be egregious.

  %configuration=(
    messages=>{
      'key name'=>{ any regular message configuration }
			...
    },
  )

Any message configuration within activity/action nodes may then reference the message by its key as shown above. During message selection, any string message or configured name will return the message configuration for key=name, if it exists, or will return the string message. If a configured message string matches a referenced name, the name takes precedence.

The configuration of a named message may only create string, array, or hash alternative messages; it cannot reference another name.

This feature is experimental starting with version 0.1.2.

IMPORT MECHANISMS

Markdown

Rudimentary markdown support is included for lists of actions that are all equally likely for a given activity:

* Activity One, 5min
  - Action one, 1min
  - Action two, 2min
  - Action three, 3min
2. Activity Two, 5min
  * Action one, 1min
  * Action two, 2min
  * Action three, 3min
- Activity Three, 5min
  * Action one, 5min

Any list identification markers may be used interchangably (number plus period, asterisks, hyphen). One or more leading whitespace (tabs or spaces) indicates an action; otherwise the line indicates an activity. Times are specified as \d+min or \d+sec. If only a single action is included in an activity, its configured time should be equal to the activity time.

The imported configuration permits an activity to be followed by any of its actions, and any action can be followed by any other action within the activity (but not itself). Any action can terminate the activity.

The full settings needed to build a schedule can be loaded with %settings=loadMarkdown(text), and both $settings{configuration} and $settings{activities} will be defined so an immediate call to %schedule=buildSchedule(%settings) can be made.

BUGS

It is possible for some settings to get stuck in an infinite loop: Be cautious setting tmavg=0 for actions.

SEE ALSO

Schedule::LongSteps and Chronic address the same type of schedules with slightly different goals.