text: #!/usr/bin/env python
  
  # copyright (c) 2014 Stein Fletcher <steinfletcher@gmail.com>
  #
  # Purpose: A simple utility to estimate tasks using PERT
  # Usage:
  #    pert --tasks="1,2,4 5,7,11 7,11,22"
  #
  # which calculates the total duration (including risk) of 3 tasks
  #     task1: optimistic: 1, nominal: 2, pessimistic: 4
  #     task2: optimistic: 5, nominal: 7, pessimistic: 11
  #     task3: optimistic: 7, nominal: 11, pessimistic: 22
  
  from math import pow, sqrt
  import errno
  import argparse
  
  
  class Task(object):
      def __init__(self, opt, nom, pes):
          self.opt = opt
          self.nom = nom
          self.pes = pes
  
  
  class Estimation:
      def __init__(self):
          self.tasks = []
  
      def add_task(self, task):
          self.tasks.append(task)
  
      def get_duration(self):
          return Calculator.total_duration(self.tasks)
  
      def get_uncertainty(self):
          return Calculator.total_uncertainty(self.tasks)
  
      def get_estimate(self):
          return Calculator.estimate(self.tasks)
  
      def print_report(self):
          for index, task in enumerate(self.tasks):
              print "[{0}: (O:{1}), (N:{2}), (P:{3})] | duration: {4}, risk: {5}".format(
                    index + 1
                  , task.opt
                  , task.nom
                  , task.pes
                  , round(Calculator.expected_duration(task), 2)
                  , round(Calculator.uncertainty(task), 2)
              )
          print "Final estimate: {}".format(round(self.get_estimate(), 2))
  
  
  class Calculator(object):
  
      @staticmethod
      def estimate(tasks):
          return (Calculator.total_duration(tasks) +
                  Calculator.total_uncertainty(tasks))
  
      @staticmethod
      def total_duration(tasks):
          return sum([Calculator.expected_duration(task) for task in tasks])
  
      @staticmethod
      def total_uncertainty(tasks):
          return sqrt(sum([pow(Calculator.uncertainty(task), 2) for task in tasks]))
  
      @staticmethod
      def expected_duration(task):
          return (task.opt + 4*task.nom + task.pes) / 6
  
      @staticmethod
      def uncertainty(task):
          return (task.pes - task.opt) / 6
  
  
  def main():
  
      def validate_params(params):
          task_list = params['tasks'].split()
          if len(task_list) < 1:
              print "No tasks specified"
              parser.print_help()
              exit(errno.EINVAL)
          for element in task_list:
              element_params = element.split(',')
              if len(element_params) != 3:
                  print "Invalid number of task attributes"
                  parser.print_help()
                  exit(errno.EINVAL)
          return task_list
  
      parser = argparse.ArgumentParser(description='A command line PERT calculator for quick \'n dirty estimates')
      parser.add_argument(
          '--tasks',
          help='a comma separated task list in the form "1,2,12 4,5,9 2,3,6", where whitespace separates tasks',
          required=True)
      args = vars(parser.parse_args())
      tasks = validate_params(args)
      estimation = Estimation()
      for task in tasks:
          attrs = [float(val) for val in task.split(',')]
          t = Task(opt=attrs[0], nom=attrs[1], pes=attrs[2])
          estimation.add_task(t)
  
      estimation.print_report()
      exit(0)
  
  
  if __name__ == '__main__':
      main()
  