Class: Decision

Inherits:
PersistentDocument show all
Defined in:
lib/almirah/doc_types/decision.rb

Instance Attribute Summary collapse

Attributes inherited from PersistentDocument

#controlled_items, #frontmatter, #headings, #items, #up_link_docs

Attributes inherited from BaseDocument

#dom, #headings, #id, #output_rel_path, #title

Instance Method Summary collapse

Methods inherited from BaseDocument

#critical_chain_link, #decisions_link, #home_link, #index_link, #needs_chartjs?, #rel_to, #save_html_to_file

Constructor Details

#initialize(file_path) ⇒ Decision

Returns a new instance of Decision.



14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/almirah/doc_types/decision.rb', line 14

def initialize(file_path)
  super
  @path = file_path
  stem = File.basename(file_path, File.extname(file_path))
  assign_id_parts(stem)
  @current_status = nil
  @start_date = nil
  @target_date = nil
  @target_release_version = nil
  @wrong_links_hash = {}
  @owners = []
end

Instance Attribute Details

#current_statusObject

Returns the value of attribute current_status.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def current_status
  @current_status
end

#html_rel_pathObject

Returns the value of attribute html_rel_path.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def html_rel_path
  @html_rel_path
end

#ownersObject

Returns the value of attribute owners.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def owners
  @owners
end

#pathObject

Returns the value of attribute path.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def path
  @path
end

#record_typeObject

Returns the value of attribute record_type.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def record_type
  @record_type
end

#root_prefixObject

Returns the value of attribute root_prefix.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def root_prefix
  @root_prefix
end

#scope_tableObject

Returns the value of attribute scope_table.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def scope_table
  @scope_table
end

#sequence_numberObject

Returns the value of attribute sequence_number.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def sequence_number
  @sequence_number
end

#specifications_pathObject

Returns the value of attribute specifications_path.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def specifications_path
  @specifications_path
end

#start_dateObject

Returns the value of attribute start_date.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def start_date
  @start_date
end

#target_dateObject

Returns the value of attribute target_date.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def target_date
  @target_date
end

#target_release_versionObject

Returns the value of attribute target_release_version.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def target_release_version
  @target_release_version
end

Returns the value of attribute wrong_links_hash.



10
11
12
# File 'lib/almirah/doc_types/decision.rb', line 10

def wrong_links_hash
  @wrong_links_hash
end

Instance Method Details

#actual_hoursObject

Total logged effort in hours across the # Effort table (ADR-196); 0 when the record has no Effort section. Undated and unparseable entries still count toward the record total.



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

def actual_hours
  effort_entries.sum { |e| e[:hours] }
end

#actual_hours_on(date) ⇒ Object

Logged hours dated on or before date (ADR-196). Undated entries are excluded since they cannot be placed on the timeline.



170
171
172
# File 'lib/almirah/doc_types/decision.rb', line 170

def actual_hours_on(date)
  effort_entries.sum { |e| e[:date] && e[:date] <= date ? e[:hours] : 0.0 }
end

#declared_dependencies?Boolean

A record declares prerequisites when any of its rows carries a Depends On reference; the overview Kit column is empty otherwise.

Returns:

  • (Boolean)


101
102
103
# File 'lib/almirah/doc_types/decision.rb', line 101

def declared_dependencies?
  scope_work_items.any? { |wi| wi.depends_on_refs.any? }
end

#effective_status_on(date) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/almirah/doc_types/decision.rb', line 136

def effective_status_on(date)
  table = find_section_table('Status')
  return nil if table.nil?

  date_idx = column_index(table, 'Date')
  status_idx = column_index(table, 'Status')
  return nil if date_idx.nil? || status_idx.nil?

  best_idx = nil
  best_date = nil
  table.rows.each_with_index do |row, i|
    parsed = parse_dd_mm_yyyy(row[date_idx])
    next if parsed.nil? || parsed > date

    if best_date.nil? || parsed > best_date || (parsed == best_date && i > best_idx)
      best_date = parsed
      best_idx = i
    end
  end
  return nil if best_idx.nil?

  status = table.rows[best_idx][status_idx].to_s.strip
  status.empty? ? nil : status
end

#effort_date_range(item) ⇒ Object

The [earliest, latest] dated # Effort entry crediting the Scope row whose Item matches item (case-insensitive) — the real logged span the Gantt's tracking lane draws (ADR-213). nil when the row has no dated effort. Undated entries are excluded since they cannot be placed on the timeline.



190
191
192
193
194
195
196
# File 'lib/almirah/doc_types/decision.rb', line 190

def effort_date_range(item)
  key = item.to_s.strip.downcase
  return nil if key.empty?

  dates = effort_entries.filter_map { |e| e[:date] if e[:item] == key }
  dates.empty? ? nil : dates.minmax
end

#extract_current_statusObject



42
43
44
45
46
47
48
49
# File 'lib/almirah/doc_types/decision.rb', line 42

def extract_current_status
  status_table = find_section_table('Status')
  return if status_table.nil?

  status_table.is_decision_status_table = true
  marker_rows = status_table.rows.select { |row| row[0].to_s.strip == '*' }
  @current_status = marker_rows.length == 1 ? marker_rows[0][-1].to_s.strip : nil
end

#extract_ownersObject

The distinct, first-seen-ordered list of non-empty Owner cells in the Scope table. Empty when there is no Scope table, no Owner column, or no owner values. The Owner column is located by header text, not position.



73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/almirah/doc_types/decision.rb', line 73

def extract_owners
  @owners = []
  table = find_section_table('Scope')
  return if table.nil?

  owner_idx = column_index(table, 'Owner')
  return if owner_idx.nil?

  table.cells.each do |row|
    owner = row[owner_idx].to_s.strip
    @owners << owner unless owner.empty? || @owners.include?(owner)
  end
end

#extract_scope_tableObject

The parsed Scope ScopeTable (ADR-194), or nil when the record has no Scope section. Memoised so the work-item network and the readers share one table.



89
90
91
92
# File 'lib/almirah/doc_types/decision.rb', line 89

def extract_scope_table
  table = find_section_table('Scope')
  @scope_table = table.is_a?(ScopeTable) ? table : nil
end

#extract_start_dateObject



51
52
53
54
# File 'lib/almirah/doc_types/decision.rb', line 51

def extract_start_date
  dates = collect_dates('Status', 'Date') + collect_dates('Scope', 'Start Date')
  @start_date = dates.min
end

#extract_target_dateObject



56
57
58
59
# File 'lib/almirah/doc_types/decision.rb', line 56

def extract_target_date
  dates = collect_dates('Status', 'Date') + collect_dates('Scope', 'Target Date')
  @target_date = dates.max
end

#extract_target_release_versionObject



61
62
63
64
65
66
67
68
# File 'lib/almirah/doc_types/decision.rb', line 61

def extract_target_release_version
  @target_release_version = lookup_cell(
    section_name: 'Software Versions',
    key_column: 'Software Version Category',
    value_column: 'Software Version ID',
    key: 'Target Release Version'
  )
end

#fully_kitted?Boolean

Kitted when every one of its work items is (a record with no rows is kitted).

Returns:

  • (Boolean)


106
107
108
# File 'lib/almirah/doc_types/decision.rb', line 106

def fully_kitted?
  scope_work_items.all?(&:fully_kitted?)
end

#in_progress_owner_tallyObject

One entry per Scope row whose row Status is In-Progress, yielding that row's owner. Rows without an owner, or not In-Progress, contribute nothing. The per-row Status is read directly and is independent of the record's lifecycle status. Owner and Status columns are located by header text, not position.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/almirah/doc_types/decision.rb', line 120

def in_progress_owner_tally
  table = find_section_table('Scope')
  return [] if table.nil?

  owner_idx = column_index(table, 'Owner')
  status_idx = column_index(table, 'Status')
  return [] if owner_idx.nil? || status_idx.nil?

  table.cells.filter_map do |row|
    owner = row[owner_idx].to_s.strip
    next if owner.empty?

    owner if row[status_idx].to_s.strip == 'In-Progress'
  end
end

#kit_started_violation?Boolean

A started row blocked by an unsatisfied cross-record predecessor — the overview emphasises such a record, matching the console warning.

Returns:

  • (Boolean)


112
113
114
# File 'lib/almirah/doc_types/decision.rb', line 112

def kit_started_violation?
  scope_work_items.any?(&:cross_record_violation?)
end

#row_actual_hours_on(item, date) ⇒ Object

Logged hours dated on or before date for the Scope row whose Item matches item (case-insensitive) — the per-chain-row actual the fever chart credits (ADR-196). Entries with no Item credit no row and are excluded here.



177
178
179
180
181
182
183
184
# File 'lib/almirah/doc_types/decision.rb', line 177

def row_actual_hours_on(item, date)
  key = item.to_s.strip.downcase
  return 0.0 if key.empty?

  effort_entries.sum do |e|
    e[:item] == key && e[:date] && e[:date] <= date ? e[:hours] : 0.0
  end
end

#scope_work_itemsObject

The Scope rows as WorkItem nodes (ADR-194); empty when there is no ScopeTable.



95
96
97
# File 'lib/almirah/doc_types/decision.rb', line 95

def scope_work_items
  @scope_table ? @scope_table.work_items : []
end

#to_consoleObject



27
28
29
# File 'lib/almirah/doc_types/decision.rb', line 27

def to_console
  puts "\e[36mDecision: #{@id}\e[0m"
end

#to_html(nav_pane, output_file_path) ⇒ Object



31
32
33
34
35
36
37
38
39
40
# File 'lib/almirah/doc_types/decision.rb', line 31

def to_html(nav_pane, output_file_path)
  html_rows = []
  html_rows.append('')

  @items.each do |item|
    html_rows.append item.to_html
  end

  save_html_to_file(html_rows, nav_pane, output_file_path)
end