Class: ProjectConfiguration

Inherits:
Object
  • Object
show all
Defined in:
lib/almirah/project_configuration.rb

Constant Summary collapse

DEFAULT_WIP_LIMIT =
2
DEFAULT_BUFFER_RATIO =
0.5
DEFAULT_HOURS_PER_DAY =
8

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ ProjectConfiguration

Returns a new instance of ProjectConfiguration.



11
12
13
14
15
# File 'lib/almirah/project_configuration.rb', line 11

def initialize(path)
  @project_root_directory = File.expand_path(path)
  @parameters = {}
  load_project_file
end

Instance Attribute Details

#parametersObject

Returns the value of attribute parameters.



9
10
11
# File 'lib/almirah/project_configuration.rb', line 9

def parameters
  @parameters
end

#project_root_directoryObject

Returns the value of attribute project_root_directory.



9
10
11
# File 'lib/almirah/project_configuration.rb', line 9

def project_root_directory
  @project_root_directory
end

Instance Method Details

#get_buffer_ratioObject

The CCPM project-buffer ratio (ADR-195): a fraction in (0, 1] cutting the aggregated chain safety. Absent or out-of-range falls back to the 0.5 default.



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/almirah/project_configuration.rb', line 51

def get_buffer_ratio
  return DEFAULT_BUFFER_RATIO unless @parameters.is_a?(Hash)

  planning = @parameters['planning']
  return DEFAULT_BUFFER_RATIO unless planning.is_a?(Hash)

  value = planning['buffer_ratio']
  return DEFAULT_BUFFER_RATIO unless value.is_a?(Numeric) && value.positive? && value <= 1

  value
end

#get_design_inputsObject



25
26
27
28
29
# File 'lib/almirah/project_configuration.rb', line 25

def get_design_inputs
  return @parameters['specifications']['input'] if (@parameters.key? 'specifications') and (@parameters['specifications'].key? 'input')

  []
end

#get_group_start_datesObject

Per-group planning start dates (ADR-211): a map from a decision group's first-level folder name (under decisions/) to its start Date, read from planning.groups as DD-MM-YYYY entries. Non-string or unparseable values are dropped; empty when unset. A group absent from the map sequences after the previous group rather than carrying a declared start.



76
77
78
79
80
81
82
83
84
# File 'lib/almirah/project_configuration.rb', line 76

def get_group_start_dates
  value = planning_value('groups')
  return {} unless value.is_a?(Hash)

  value.each_with_object({}) do |(name, raw), acc|
    date = parse_planning_date(raw)
    acc[name.to_s] = date if date
  end
end

#get_holidaysObject

The non-working holiday dates (ADR-205) from planning.holidays, each a DD-MM-YYYY entry; unparseable entries are dropped. Empty when unset.



88
89
90
91
92
93
# File 'lib/almirah/project_configuration.rb', line 88

def get_holidays
  value = planning_value('holidays')
  return [] unless value.is_a?(Array)

  value.filter_map { |entry| parse_planning_date(entry) }
end

#get_hours_per_dayObject

Working hours per day (ADR-196): converts logged effort hours into the working-day unit the estimates use. Absent or non-positive falls back to 8.



97
98
99
100
101
102
103
104
105
106
107
# File 'lib/almirah/project_configuration.rb', line 97

def get_hours_per_day
  return DEFAULT_HOURS_PER_DAY unless @parameters.is_a?(Hash)

  planning = @parameters['planning']
  return DEFAULT_HOURS_PER_DAY unless planning.is_a?(Hash)

  value = planning['hours_per_day']
  return DEFAULT_HOURS_PER_DAY unless value.is_a?(Numeric) && value.positive?

  value
end

#get_repositoriesObject



31
32
33
34
35
# File 'lib/almirah/project_configuration.rb', line 31

def get_repositories
  return @parameters['repositories'] if @parameters.key? 'repositories'

  []
end

#get_risk_columns(folder) ⇒ Object

The ordered register-column list for a risk registry folder (ADR-216), read from the risks: root — a list of { folder:, columns: } entries. nil when the registry carries no configuration; such a registry renders the implicit columns plus Status only.



113
114
115
116
117
118
# File 'lib/almirah/project_configuration.rb', line 113

def get_risk_columns(folder)
  entry = risk_entry(folder)
  return nil unless entry.is_a?(Hash) && entry['columns'].is_a?(Array)

  entry['columns'].map(&:to_s)
end

#get_risk_rpn_groups(folder) ⇒ Object

The named RPN groups of a risk registry folder (ADR-217), from the rpn: list of its risks: entry, in configured order: [{ name:, inputs: [..], acceptable:, unacceptable: }, ...]. Groups without a name or a non-empty inputs list are dropped; a threshold bound that is absent or not numeric is nil. Empty when the registry declares no groups.



125
126
127
128
129
130
# File 'lib/almirah/project_configuration.rb', line 125

def get_risk_rpn_groups(folder)
  entry = risk_entry(folder)
  return [] unless entry.is_a?(Hash) && entry['rpn'].is_a?(Array)

  entry['rpn'].filter_map { |raw| risk_rpn_group(raw) }
end

#get_start_dateObject

The calendar anchor for working day 1 (ADR-205), a Date parsed from a DD-MM-YYYY planning.start_date. Absent or unparseable falls back to today, so an unconfigured project still renders (a moving anchor).



66
67
68
69
# File 'lib/almirah/project_configuration.rb', line 66

def get_start_date
  date = parse_planning_date(planning_value('start_date'))
  date || Date.today
end

#get_wip_limitObject



37
38
39
40
41
42
43
44
45
46
47
# File 'lib/almirah/project_configuration.rb', line 37

def get_wip_limit
  return DEFAULT_WIP_LIMIT unless @parameters.is_a?(Hash)

  planning = @parameters['planning']
  return DEFAULT_WIP_LIMIT unless planning.is_a?(Hash)

  value = planning['wip_limit']
  return DEFAULT_WIP_LIMIT unless value.is_a?(Integer) && value.positive?

  value
end

#is_spec_db_shall_be_createdObject



132
133
134
135
136
137
138
139
# File 'lib/almirah/project_configuration.rb', line 132

def is_spec_db_shall_be_created
  if @parameters.key? 'output'
    @parameters['output'].each do |p|
      return true if p == 'specifications_db'
    end
  end
  false
end

#load_project_fileObject



17
18
19
20
21
22
23
# File 'lib/almirah/project_configuration.rb', line 17

def load_project_file
  @parameters = YAML.load_file(@project_root_directory + '/project.yml')
rescue Psych::SyntaxError => e
  puts "YAML syntax error: #{e.message}"
rescue Errno::ENOENT
  puts 'Project file not found: project.yml'
end

#numeric_threshold(value) ⇒ Object



164
165
166
# File 'lib/almirah/project_configuration.rb', line 164

def numeric_threshold(value)
  value.is_a?(Numeric) ? value : nil
end

#parse_planning_date(value) ⇒ Object

Parse a planning date that may already be a Date (YAML ISO form) or a DD-MM-YYYY string; nil when neither.



178
179
180
181
182
183
184
185
186
187
188
# File 'lib/almirah/project_configuration.rb', line 178

def parse_planning_date(value)
  return value if value.is_a?(Date)
  return nil unless value.is_a?(String)

  match = /\A(\d{2})-(\d{2})-(\d{4})\z/.match(value.strip)
  return nil unless match

  Date.new(match[3].to_i, match[2].to_i, match[1].to_i)
rescue ArgumentError
  nil
end

#planning_value(key) ⇒ Object

A value under the planning: key, or nil when planning is absent.



169
170
171
172
173
174
# File 'lib/almirah/project_configuration.rb', line 169

def planning_value(key)
  return nil unless @parameters.is_a?(Hash)

  planning = @parameters['planning']
  planning.is_a?(Hash) ? planning[key] : nil
end

#risk_entry(folder) ⇒ Object

The risks: entry configuring a registry folder, or nil when absent.



142
143
144
145
146
147
148
149
# File 'lib/almirah/project_configuration.rb', line 142

def risk_entry(folder)
  return nil unless @parameters.is_a?(Hash)

  entries = @parameters['risks']
  return nil unless entries.is_a?(Array)

  entries.find { |e| e.is_a?(Hash) && e['folder'].to_s == folder }
end

#risk_rpn_group(raw) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/almirah/project_configuration.rb', line 151

def risk_rpn_group(raw)
  return nil unless raw.is_a?(Hash)

  name = raw['name'].to_s
  inputs = raw['inputs']
  return nil if name.empty? || !inputs.is_a?(Array) || inputs.empty?

  thresholds = raw['thresholds'].is_a?(Hash) ? raw['thresholds'] : {}
  { name: name, inputs: inputs.map(&:to_s),
    acceptable: numeric_threshold(thresholds['acceptable']),
    unacceptable: numeric_threshold(thresholds['unacceptable']) }
end