Class: PredictabilityEngine::Cli

Inherits:
Thor
  • Object
show all
Includes:
CliBase, JiraConfigPrompter
Defined in:
lib/predictability_engine/cli.rb

Constant Summary collapse

GENERATE_SIZE_DESC =
"Preset volume: #{DataGenerator::PRESETS.map do |n, c|
  "#{n} (#{c[:completed]}/#{c[:wip]})"
end.join(', ')}".freeze

Constants included from CliBase

PredictabilityEngine::CliBase::SIZE_DESC, PredictabilityEngine::CliBase::VALID_SIZES

Instance Method Summary collapse

Methods included from JiraConfigPrompter

#build_profile_data, #prompt_auth_fields

Methods included from CliBase

included, #initialize

Instance Method Details

#ask_ai(source, question) ⇒ Object



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/predictability_engine/cli.rb', line 393

def ask_ai(source, question)
  # Assistant needs the manager or at least items.
  manager = DataManager.new
  manager.load(source)

  assistant = Agents::Assistant.new(manager)
  PredictabilityEngine.logger.info { 'AI Thinking...' }
  response = assistant.ask(question)

  # response is an array of messages or similar depending on langchain version
  # In recent langchainrb versions assistant.run returns the last message
  PredictabilityEngine.logger.info { 'AI Response:' }
  PredictabilityEngine.logger.info { '------------' }
  # Assuming response is a message object with .content
  if response.respond_to?(:content)
    PredictabilityEngine.logger.info { response.content }
  else
    PredictabilityEngine.logger.info { response }
  end
end

#batch(source) ⇒ Object



220
221
222
# File 'lib/predictability_engine/cli.rb', line 220

def batch(source)
  Viz.new([], options).all_formats(source)
end

#calibrate(source) ⇒ Object



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/predictability_engine/cli.rb', line 310

def calibrate(source)
  items = PredictabilityEngine.load_items(source)
  result = Simulators::MonteCarloValidator.calibration(
    items,
    validation_trials: options[:validation_trials],
    primary_trials: options[:primary_trials]
  )
  if result.nil?
    PredictabilityEngine.logger.info do
      'Insufficient data for hindcast calibration (need 10+ completed items with historical WIP).'
    end
    return
  end
  print_calibration_results(result)
end

#forecast(source, backlog_count) ⇒ Object



296
297
298
299
300
301
302
303
# File 'lib/predictability_engine/cli.rb', line 296

def forecast(source, backlog_count)
  items = PredictabilityEngine.load_items(source)

  historical = Calculators::Throughput.daily(items).values
  results = Simulators::MonteCarlo.when_will_it_be_done(backlog_count.to_i, historical)

  print_forecast_results(backlog_count, results)
end

#generate(output) ⇒ Object



336
337
338
339
340
341
342
343
344
# File 'lib/predictability_engine/cli.rb', line 336

def generate(output)
  path = DataGenerator.generate(
    output: output,
    size: options[:size].to_sym,
    completed: options[:completed],
    wip: options[:wip]
  )
  PredictabilityEngine.logger.info { "Synthetic #{options[:size]} dataset written to #{path}" }
end

#init(filename) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/predictability_engine/cli.rb', line 230

def init(filename)
  filename += '.yml' unless filename.end_with?('.yml', '.yaml')
  content = <<~YAML
    # JIRA Data Source Configuration
    # jira_profile: prod-instance # Optional: profile name from ~/.config/jira/jira_credentials.yml
    # project: MYPROJ            # Optional: JIRA Project Key
    # filter_id: "12345"         # Optional: JIRA Filter ID
    # filter_name: "My Filter"   # Optional: JIRA Filter Name
    # query: "project = PROJ"    # Optional: Direct JQL query
  YAML
  File.write(filename, content)
  PredictabilityEngine.logger.info { "Template created at #{filename}" }
end

#jira_config(profile) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/predictability_engine/cli.rb', line 247

def jira_config(profile)
  site = ask('Jira site (e.g., https://your-domain.atlassian.net):')
  context_path = ask('Context path, if any (e.g., /jira — leave blank for Atlassian Cloud):')
  mode = options[:auth_mode]

  profile_data = build_profile_data(site, context_path, mode)

  path = Config.jira_credentials_file
  FileUtils.mkdir_p(File.dirname(path))

  config = File.exist?(path) ? Config.load_yaml_file(path) : {}
  config ||= {}
  config['profiles'] ||= {}
  config['profiles'][profile] = profile_data

  File.write(path, config.to_yaml)
  PredictabilityEngine.logger.info { "Jira credentials for profile '#{profile}' saved to #{path}" }
end

#jira_workflow(profile, output = nil) ⇒ Object



270
271
272
273
274
275
276
277
278
# File 'lib/predictability_engine/cli.rb', line 270

def jira_workflow(profile, output = nil)
  path = output || JiraWorkflow.default_path(profile)
  fresh = JiraWorkflow.extract(profile)
  workflow = File.exist?(path) ? JiraWorkflow.load(path).refresh(fresh) : fresh
  workflow.write(path)
  action = File.exist?(path) ? 'refreshed' : 'written'
  PredictabilityEngine.logger.info { "Workflow for profile '#{profile}' #{action}: #{path}" }
  PredictabilityEngine.logger.info { "Review #{path} and set role: arrival / departure / null per status." }
end

#jira_workflow_merge(output, *sources) ⇒ Object

Raises:



284
285
286
287
288
289
290
291
292
293
# File 'lib/predictability_engine/cli.rb', line 284

def jira_workflow_merge(output, *sources)
  raise Error, 'Need at least one workflow source to merge' if sources.empty?

  configs = sources.map do |src|
    path = File.exist?(src) ? src : JiraWorkflow.default_path(src)
    JiraWorkflow.load(path) || raise(Error, "Workflow not found for '#{src}' (tried #{path})")
  end
  JiraWorkflow.merge(configs).write(output)
  PredictabilityEngine.logger.info { "Merged workflow from #{sources.join(', ')} written to #{output}" }
end

#report(input_source, format = 'terminal', output = nil) ⇒ Object



211
212
213
214
215
216
# File 'lib/predictability_engine/cli.rb', line 211

def report(input_source, format = 'terminal', output = nil)
  if format.to_sym != :terminal && output.nil? && options[:clean]
    ReportGenerator.clean_report_dir(input_source, **options)
  end
  PredictabilityEngine.run_and_print_report(input_source, format, options, output: output)
end

#setupObject



225
226
227
# File 'lib/predictability_engine/cli.rb', line 225

def setup
  SetupManager.new.run
end

#summary(source) ⇒ Object



203
204
205
206
# File 'lib/predictability_engine/cli.rb', line 203

def summary(source)
  items = PredictabilityEngine.load_items(source)
  PredictabilityEngine.logger.info { SummaryVisualizer.metrics_terminal(items, color: options[:color]) }
end