def proj_hse_totals(daily):
        if daily:
            hse_query = HseWeather.objects.filter(
                daily__production_date__lte=daily.production_date,
                daily__project=daily.project,
            ).order_by('daily__production_date')

        else:
            hse_query = None

        if not hse_query:
            return {f'proj_{key}': '' for key in hse_weather_schema}, None

        hse_series = {
            f'{key}_series':
            nan_array([val[key] for val in hse_query.values()])
            for key in (*hse_weather_schema[:12], *hse_weather_schema[14:])
        }
        hse_series['date_series'] = np.array(
            [val.daily.production_date for val in hse_query])

        p_hse = {
            f'proj_{key}':
            sum(nan_array([val[key] for val in hse_query.values()]))
            for key in (*hse_weather_schema[:12], *hse_weather_schema[14:])
        }

        return p_hse, hse_series
예제 #2
0
    def month_receiver_total(daily, receivertype):
        if daily:
            rcvr_query = ReceiverProduction.objects.filter(
                Q(daily__production_date__year=daily.production_date.year)
                & Q(daily__production_date__month=daily.production_date.month)
                &
                Q(daily__production_date__day__lte=daily.production_date.day),
                receivertype=receivertype,
            )

        else:
            rcvr_query = None

        if not rcvr_query:
            m_rcvr = {f'month_{key}': 0 for key in receiver_prod_schema}
            return m_rcvr

        m_rcvr = {
            f'month_{key}':
            sum(nan_array([val[key] for val in rcvr_query.values()]))
            for key in receiver_prod_schema[0:7]
        }
        m_rcvr['month_qc_field'] = nan_avg_array(
            [val['qc_field'] for val in rcvr_query.values()])
        m_rcvr['month_qc_teams'] = nan_avg_array(
            [val['qc_teams'] for val in rcvr_query.values()])
        m_rcvr['month_fc_teams'] = nan_avg_array(
            [val['fc_teams'] for val in rcvr_query.values()])
        m_rcvr['month_bc_teams'] = nan_avg_array(
            [val['bc_teams'] for val in rcvr_query.values()])

        m_rcvr['month_perc_node_skips'] = calc_ratio(
            m_rcvr['month_node_skips'], daily.project.planned_receivers)

        return m_rcvr
예제 #3
0
    def week_receiver_total(daily, receivertype):
        if daily:
            end_date = daily.production_date
            start_date = end_date - timedelta(days=WEEKDAYS - 1)
            rcvr_query = ReceiverProduction.objects.filter(
                Q(daily__production_date__gte=start_date),
                Q(daily__production_date__lte=end_date),
                receivertype=receivertype,
            )
        else:
            rcvr_query = None

        if not rcvr_query:
            w_rcvr = {f'week_{key}': 0 for key in receiver_prod_schema}
            return w_rcvr

        w_rcvr = {
            f'week_{key}':
            sum(nan_array([val[key] for val in rcvr_query.values()]))
            for key in receiver_prod_schema[0:7]
        }
        w_rcvr['week_qc_field'] = nan_avg_array(
            [val['qc_field'] for val in rcvr_query.values()])
        w_rcvr['week_qc_teams'] = nan_avg_array(
            [val['qc_teams'] for val in rcvr_query.values()])
        w_rcvr['week_fc_teams'] = nan_avg_array(
            [val['fc_teams'] for val in rcvr_query.values()])
        w_rcvr['week_bc_teams'] = nan_avg_array(
            [val['bc_teams'] for val in rcvr_query.values()])

        w_rcvr['week_perc_node_skips'] = calc_ratio(
            w_rcvr['week_node_skips'], daily.project.planned_receivers)

        return w_rcvr
예제 #4
0
    def calc_proj_time_totals(self, daily):
        if daily:
            tb_query = TimeBreakdown.objects.filter(
                daily__production_date__lte=daily.production_date,
                daily__project=daily.project,
            ).order_by('daily__production_date')

        else:
            tb_query = None

        if not tb_query:
            pt = {f'proj_{key}': np.nan for key in time_breakdown_schema}
            pt['proj_rec_time'] = np.nan
            pt['proj_ops_time'] = np.nan
            pt['proj_standby'] = np.nan
            pt['proj_downtime'] = np.nan
            pt['proj_total_time'] = np.nan
            return pt, {}

        ts = {
            f'{key}_series': nan_array([val[key] for val in tb_query.values()])
            for key in time_breakdown_schema
        }

        ts['ops_series'] = sum(ts[f'{key}_series'] for key in ops_time_keys)
        ts['standby_series'] = sum(ts[f'{key}_series'] for key in standby_keys)
        ts['downtime_series'] = sum(ts[f'{key}_series']
                                    for key in downtime_keys)
        ts['total_time_series'] = (ts['ops_series'] + ts['standby_series'] +
                                   ts['downtime_series'])
        ts['date_series'] = np.array(
            [val.daily.production_date for val in tb_query])

        pt = {
            f'proj_{key}': np.nansum(ts[f'{key}_series'])
            for key in time_breakdown_schema
        }
        pt['proj_rec_time'] = pt['proj_rec_hours']
        pt['proj_ops_time'] = np.nansum(
            [pt[f'proj_{key}'] for key in ops_time_keys])
        pt['proj_standby'] = np.nansum(
            [pt[f'proj_{key}'] for key in standby_keys])
        pt['proj_downtime'] = np.nansum(
            [pt[f'proj_{key}'] for key in downtime_keys])
        pt['proj_total_time'] = np.nansum([
            pt['proj_ops_time'],
            pt['proj_standby'],
            pt['proj_downtime'],
        ])

        return pt, ts
예제 #5
0
    def calc_proj_prod_totals(self, daily, sourcetype):
        # filter for all days in the project up to and including the production date
        if daily:
            sp_query = SourceProduction.objects.filter(
                daily__production_date__lte=daily.production_date,
                sourcetype=sourcetype,
            ).order_by('daily__production_date')

        else:
            sp_query = None

        if not sp_query:
            pp = {f'proj_{key[:5]}': np.nan for key in source_prod_schema[0:6]}
            pp[f'proj_{source_prod_schema[6]}'] = np.nan
            pp['proj_total'] = np.nan
            return pp, {}

        p_series = {
            f'{key[:5]}_series':
            nan_array([val[key] for val in sp_query.values()])
            for key in source_prod_schema[0:6]
        }
        p_series = self.calc_ctm_series(p_series, sourcetype)

        p_series[f'{source_prod_schema[6]}_series'] = np.array(
            [val.avg_sources for val in sp_query])
        p_series['date_series'] = np.array(
            [val.daily.production_date for val in sp_query])
        # only include sources and skips
        pp = {
            f'proj_{key[:5]}': np.nansum(p_series[f'{key[:5]}_series'])
            for key in source_prod_schema[0:6]
        }
        # get average of number of sources
        pp[f'proj_{source_prod_schema[6]}'] = nan_avg_array(
            p_series[f'{source_prod_schema[6]}_series'])
        # only include sources from terrain types
        pp['proj_total'] = np.sum(pp[f'proj_{key[:5]}']
                                  for key in source_prod_schema[0:5])
        return pp, p_series
    def month_hse_totals(daily):
        if daily:
            hse_query = HseWeather.objects.filter(
                Q(daily__production_date__year=daily.production_date.year)
                & Q(daily__production_date__month=daily.production_date.month)
                &
                Q(daily__production_date__day__lte=daily.production_date.day),
                daily__project=daily.project,
            )
        else:
            hse_query = None

        if not hse_query:
            return {f'month_{key}': '' for key in hse_weather_schema}

        m_hse = {
            f'month_{key}':
            sum(nan_array([val[key] for val in hse_query.values()]))
            for key in (*hse_weather_schema[:12], *hse_weather_schema[14:])
        }

        return m_hse
    def week_hse_totals(daily):
        if daily:
            end_date = daily.production_date
            start_date = end_date - timedelta(days=WEEKDAYS - 1)

            hse_query = HseWeather.objects.filter(
                Q(daily__production_date__gte=start_date),
                Q(daily__production_date__lte=end_date),
                daily__project=daily.project,
            )

        else:
            return {f'week_{key}': '' for key in hse_weather_schema}

        if not hse_query:
            return {f'week_{key}': '' for key in hse_weather_schema}

        w_hse = {
            f'week_{key}':
            sum(nan_array([val[key] for val in hse_query.values()]))
            for key in (*hse_weather_schema[:12], *hse_weather_schema[14:])
        }

        return w_hse
예제 #8
0
    def project_receiver_total(daily, receivertype):
        if daily:
            rcvr_query = ReceiverProduction.objects.filter(
                daily__production_date__lte=daily.production_date,
                receivertype=receivertype,
            ).order_by('daily__production_date')

        else:
            rcvr_query = None

        if not rcvr_query:
            p_rcvr = {f'proj_{key}': 0 for key in receiver_prod_schema}
            return p_rcvr, {}

        rcvr_series = {
            f'{key}_series': [val[key] for val in rcvr_query.values()]
            for key in receiver_prod_schema
        }
        p_rcvr = {
            f'proj_{key}':
            sum(nan_array([val[key] for val in rcvr_query.values()]))
            for key in receiver_prod_schema[0:7]
        }
        p_rcvr['proj_qc_field'] = nan_avg_array(
            [val['qc_field'] for val in rcvr_query.values()])
        p_rcvr['proj_qc_teams'] = nan_avg_array(
            [val['qc_teams'] for val in rcvr_query.values()])
        p_rcvr['proj_fc_teams'] = nan_avg_array(
            [val['fc_teams'] for val in rcvr_query.values()])
        p_rcvr['proj_bc_teams'] = nan_avg_array(
            [val['bc_teams'] for val in rcvr_query.values()])

        p_rcvr['proj_perc_node_skips'] = calc_ratio(
            p_rcvr['proj_node_skips'], daily.project.planned_receivers)

        return p_rcvr, rcvr_series
    def create_tab_production(self):
        ''' method to create excel weekly tab for production
        '''
        set_column_widths(self.wsp, 'A', [
            0.94,
            23.50,
            12.33,
            12.33,
            12.33,
            12.33,
            12.33,
            12.33,
            12.33,
            12.33,
            12.33,
        ])
        # set day production
        self.wsp.merge_cells('B1:K1')
        set_vertical_cells(self.wsp, 'B1', ['Daily production'], font_bold,
                           Alignment(horizontal='center'))

        set_horizontal_cells(
            self.wsp, 'B2', self.days_prod['header'], font_bold,
            Alignment(
                horizontal='center',
                vertical='top',
                wrap_text=True,
            ))

        row, col = get_row_column('B3')
        weeks_total = np.zeros(len(self.days_prod[0]) - 2)
        for key in range(0, 7):
            vals = self.days_prod[key]
            loc = col + str(row + key)
            set_horizontal_cells(self.wsp, loc, vals, font_normal,
                                 Alignment(horizontal='right'))

            self.wsp[f'B{row + key}'].alignment = Alignment(
                horizontal='center')
            format_range = f'C{row + key}:C{row + key}'
            format_horizontal(self.wsp, format_range, int_hide_zero)
            format_range = f'D{row + key}:D{row + key}'
            format_horizontal(self.wsp, format_range, float_hide_zero)
            format_range = f'E{row + key}:L{row + key}'
            format_horizontal(self.wsp, format_range, int_hide_zero)
            format_range = f'M{row + key}:M{row + key}'
            format_horizontal(self.wsp, format_range, '0.000;-0;;@')

            # sum for indexes 1..10, skip index 0 date and 11 qc_field
            weeks_total += nan_array(vals[1:-1]).astype(np.float)

        # skip the 3rd and 6th elements
        vals = [*weeks_total[0:2], '', *weeks_total[3:]]
        set_vertical_cells(self.wsp, 'B10', ['Weeks total'], font_bold,
                           Alignment(horizontal='center'))
        set_horizontal_cells(self.wsp, 'C10', vals, font_bold,
                             Alignment(horizontal='right'))
        # average vp/ hr
        set_horizontal_cells(self.wsp, 'E10', [self.weeks_prod[5][3]],
                             font_bold, Alignment(horizontal='right'))
        # average qc_field
        set_horizontal_cells(self.wsp, 'M10', [self.weeks_prod[5][11]],
                             font_bold, Alignment(horizontal='right'))
        format_horizontal(self.wsp, 'C10:C10', '#,##0')
        format_horizontal(self.wsp, 'D10:D10', '0.00')
        format_horizontal(self.wsp, 'E10:K10', '#,##0')
        format_horizontal(self.wsp, 'M10:M10', '0.000')

        # set borders
        set_border(self.wsp, 'B2:M10')

        # set week production
        self.wsp.merge_cells('B13:K13')
        set_vertical_cells(self.wsp, 'B13', ['Weekly production'], font_bold,
                           Alignment(horizontal='center'))

        set_horizontal_cells(
            self.wsp, 'B14', self.weeks_prod['header'], font_bold,
            Alignment(
                horizontal='center',
                vertical='top',
                wrap_text=True,
            ))

        row, col = get_row_column('B15')
        for key in range(0, 6):
            vals = self.weeks_prod[key]
            loc = col + str(row + key)
            set_horizontal_cells(self.wsp, loc, vals, font_normal,
                                 Alignment(horizontal='right'))

            self.wsp[f'B{row + key}'].alignment = Alignment(
                horizontal='center')
            format_range = f'C{row + key}:C{row + key}'
            format_horizontal(self.wsp, format_range, int_hide_zero)
            format_range = f'D{row + key}:D{row + key}'
            format_horizontal(self.wsp, format_range, float_hide_zero)
            format_range = f'E{row + key}:L{row + key}'
            format_horizontal(self.wsp, format_range, int_hide_zero)
            format_range = f'M{row + key}:M{row + key}'
            format_horizontal(self.wsp, format_range, '0.000;-0;;@')

        # set borders
        set_border(self.wsp, 'B14:M20')
        set_color(self.wsp, 'B20:M20', color=lightblue)

        # set terrain types for week
        self.wsp.merge_cells('C22:E22')
        set_vertical_cells(self.wsp, 'C22', ['Week terrain'], font_bold,
                           Alignment(horizontal='center'))
        set_horizontal_cells(self.wsp, 'D23', ['VPs', 'Percentage'], font_bold,
                             Alignment(horizontal='center'))
        set_vertical_cells(self.wsp, 'C24', [key for key in self.week_terrain],
                           font_normal, Alignment(horizontal='left'))

        vals = []
        percs = []
        for val in self.week_terrain.values():
            vals.append(val)
            if self.week_terrain['total'] > 0:
                perc = val / self.week_terrain['total']

            else:
                perc = np.nan

            percs.append(perc)

        set_vertical_cells(self.wsp, 'D24', vals, font_normal,
                           Alignment(horizontal='right'))
        set_vertical_cells(self.wsp, 'E24', percs, font_normal,
                           Alignment(horizontal='right'))
        format_vertical(self.wsp, 'D24:D30', '#,##0')
        format_vertical(self.wsp, 'E24:E30', '0.00%')
        set_border(self.wsp, 'D23:E23')
        set_border(self.wsp, 'C24:E30')

        # set terrain types for project
        self.wsp.merge_cells('G22:I22')
        set_vertical_cells(self.wsp, 'G22', ['Project terrain'], font_bold,
                           Alignment(horizontal='center'))
        set_horizontal_cells(self.wsp, 'H23', ['VPs', 'Percentage'], font_bold,
                             Alignment(horizontal='center'))
        set_vertical_cells(self.wsp, 'G24', [key for key in self.proj_terrain],
                           font_normal, Alignment(horizontal='left'))

        vals = []
        percs = []
        for val in self.proj_terrain.values():
            vals.append(val)
            if self.proj_terrain['total'] > 0:
                perc = val / self.proj_terrain['total']

            else:
                perc = np.nan

            percs.append(perc)

        set_vertical_cells(self.wsp, 'H24', vals, font_normal,
                           Alignment(horizontal='right'))
        set_vertical_cells(self.wsp, 'I24', percs, font_normal,
                           Alignment(horizontal='right'))
        format_vertical(self.wsp, 'H24:H30', '#,##0')
        format_vertical(self.wsp, 'I24:I30', '0.00%')
        set_border(self.wsp, 'H23:I23')
        set_border(self.wsp, 'G24:I30')
    def create_tab_times(self):
        ''' method to create excel weekly tab for times
        '''
        set_column_widths(self.wst, 'A', [
            0.94,
            21.56,
            9.50,
            8.33,
            8.33,
            8.33,
            8.33,
            8.33,
            10.11,
            12.33,
            9.89,
            8.33,
            8.33,
            8.33,
            8.80,
            8.33,
            10.00,
            10.00,
            8.50,
            8.33,
            8.33,
            9.80,
        ])

        # set day times
        self.wst.merge_cells('B1:V1')
        set_vertical_cells(self.wst, 'B1', ['Daily times'], font_bold,
                           Alignment(horizontal='center'))
        self.wst.merge_cells('C2:H2')
        set_vertical_cells(self.wst, 'C2', ['Operational time'], font_bold,
                           Alignment(horizontal='center'))
        self.wst.merge_cells('I2:L2')
        set_vertical_cells(self.wst, 'I2', ['Standby time'], font_bold,
                           Alignment(horizontal='center'))
        self.wst.merge_cells('M2:S2')
        set_vertical_cells(self.wst, 'M2', ['Downtime'], font_bold,
                           Alignment(horizontal='center'))
        self.wst.merge_cells('T2:V2')
        set_vertical_cells(self.wst, 'T2', ['Totals'], font_bold,
                           Alignment(horizontal='center'))

        set_horizontal_cells(
            self.wst, 'B3', self.days_times['header'], font_bold,
            Alignment(
                horizontal='center',
                vertical='top',
                wrap_text=True,
            ))

        row, col = get_row_column('B4')
        weeks_total = np.zeros(len(self.days_times[0]) - 1)
        for key in range(0, 7):
            vals = self.days_times[key]
            loc = col + str(row + key)
            set_horizontal_cells(self.wst, loc, vals, font_normal,
                                 Alignment(horizontal='right'))

            self.wst[f'B{row + key}'].alignment = Alignment(
                horizontal='center')
            format_range = f'C{row + key}:V{row + key}'
            format_horizontal(self.wst, format_range, float_hide_zero)

            weeks_total += nan_array(vals[1:]).astype(np.float)

        set_vertical_cells(self.wst, 'B11', ['Weeks total'], font_bold,
                           Alignment(horizontal='center'))
        set_horizontal_cells(self.wst, 'C11', weeks_total, font_bold,
                             Alignment(horizontal='right'))
        format_horizontal(self.wst, 'C11:V11', '0.00')

        # set borders day times
        set_outer_border(self.wst, 'C2:H2')
        set_outer_border(self.wst, 'I2:L2')
        set_outer_border(self.wst, 'M2:S2')
        set_outer_border(self.wst, 'T2:V2')
        set_border(self.wst, 'B3:v11')

        # set week times
        self.wst.merge_cells('B13:V13')
        set_vertical_cells(self.wst, 'B13', ['Weekly times'], font_bold,
                           Alignment(horizontal='center'))
        self.wst.merge_cells('C14:H14')
        set_vertical_cells(self.wst, 'C14', ['Operational time'], font_bold,
                           Alignment(horizontal='center'))
        self.wst.merge_cells('I14:L14')
        set_vertical_cells(self.wst, 'I14', ['Standby time'], font_bold,
                           Alignment(horizontal='center'))
        self.wst.merge_cells('M14:S14')
        set_vertical_cells(self.wst, 'M14', ['Downtime'], font_bold,
                           Alignment(horizontal='center'))
        self.wst.merge_cells('T14:V14')
        set_vertical_cells(self.wst, 'T14', ['Totals'], font_bold,
                           Alignment(horizontal='center'))

        set_horizontal_cells(
            self.wst, 'B15', self.weeks_times['header'], font_bold,
            Alignment(
                horizontal='center',
                vertical='top',
                wrap_text=True,
            ))

        row, col = get_row_column('B16')
        for key in range(0, 6):
            vals = self.weeks_times[key]
            loc = col + str(row + key)
            set_horizontal_cells(self.wst, loc, vals, font_normal,
                                 Alignment(horizontal='right'))

            self.wst[f'B{row + key}'].alignment = Alignment(
                horizontal='center')
            format_range = f'C{row + key}:V{row + key}'
            format_horizontal(self.wst, format_range, float_hide_zero)

        # set borders week times
        set_outer_border(self.wst, 'C14:H14')
        set_outer_border(self.wst, 'I14:L14')
        set_outer_border(self.wst, 'M14:S14')
        set_outer_border(self.wst, 'T14:V14')
        set_border(self.wst, 'B15:v21')
        set_color(self.wst, 'B21:V21', color=lightblue)
예제 #11
0
    def create_weekly_graphs(self):
        date_series = self.proj_df['date'].to_list()
        app_series = self.proj_df['total_sp'].to_list()

        # line plot CTM and app
        plot_filename = self.media_dir / 'images/app_ctm.png'
        ctm_series = self.proj_df['ctm'].to_list()
        plt.plot(date_series, ctm_series, label="CTM", zorder=2)
        plt.plot(date_series, app_series, label="APP", zorder=3)
        plt.gca().xaxis.set_major_formatter(TICK_DATE_FORMAT)
        plt.gca().yaxis.grid(zorder=1)
        plt.tick_params(axis='both', labelsize=7)
        plt.xticks(rotation=70)
        plt.legend()
        plt.tight_layout()
        plt.savefig(plot_filename, format='png')
        plt.close()

        # line plot of cumulative APP & CTM
        plot_filename = self.media_dir / 'images/cumul_app_ctm.png'
        app_cum_series = np.cumsum(app_series) * 0.001
        ctm_cum_series = np.cumsum(nan_array(ctm_series)) * 0.001
        plt.plot(date_series, ctm_cum_series, label="CTM", zorder=2)
        plt.plot(date_series, app_cum_series, label="APP", zorder=3)
        plt.gca().xaxis.set_major_formatter(TICK_DATE_FORMAT)
        plt.gca().yaxis.grid(zorder=1)
        plt.xticks(rotation=70)
        plt.tick_params(axis='both', labelsize=7)
        plt.legend()
        plt.gca().yaxis.set_major_formatter(
            mtick.StrMethodFormatter('{x:,.0f}k'))
        plt.gca().yaxis.set_major_locator(
            mtick.MultipleLocator(TICK_SPACING_CUMUL))
        plt.tight_layout()
        plt.savefig(plot_filename, format='png')
        plt.close()

        # pie chart of terrain distribution week - ignore last 2 (skips, avg sources)
        plot_filename = self.media_dir / 'images/pie_week_terrain.png'
        week_terrain = list(
            zip(*[(key, val) for key, val in self.week_terrain.items()
                  if val > 0][:-2]))
        if week_terrain:
            terrain_labels, terrain_vals = week_terrain

        else:
            terrain_labels, terrain_vals = [], []

        plt.title('Terrain - week')
        plt.pie(terrain_vals, labels=terrain_labels, autopct='%1.2f%%')
        plt.tight_layout()
        plt.savefig(plot_filename, format='png')
        plt.close()

        # pie chart of terrain distribution project - ignore last 2 (skips, avg sources)s
        plot_filename = self.media_dir / 'images/pie_proj_terrain.png'
        proj_terrain = list(
            zip(*[(key, val) for key, val in self.proj_terrain.items()
                  if val > 0][:-2]))
        if proj_terrain:
            terrain_labels, terrain_vals = proj_terrain

        else:
            terrain_labels, terrain_vals = [], []

        plt.title('Terrain - project')
        plt.pie(terrain_vals, labels=terrain_labels, autopct='%1.2f%%')
        plt.tight_layout()
        plt.savefig(plot_filename, format='png')
        plt.close()

        # pie chart of recording times
        plot_filename = self.media_dir / 'images/pie_week_times.png'

        # plot weekly times
        tl_ = ['recording', 'logistics', 'standby', 'downtime']
        tv_ = [
            self.weeks_times[5][1],
            self.weeks_times[5][18] - self.weeks_times[5][1],
            self.weeks_times[5][19],
            self.weeks_times[5][20],
        ]
        week_times = list(
            zip(*[(key, val) for key, val in zip(tl_, tv_) if val > 0]))
        if week_times:
            time_labels, time_vals = week_times

        else:
            time_labels, time_vals = [], []

        plt.title('Weekly time breakdown')
        plt.pie(time_vals, labels=time_labels, autopct='%1.2f%%')
        plt.tight_layout()
        plt.savefig(plot_filename, format='png')
        plt.close()

        # bar chart of weekly production
        plot_filename = self.media_dir / 'images/bar_week_production.png'
        date_series = [
            val[0].replace(' ', '\n') for val in self.weeks_prod.values()
            if val[0] != 'Week'
        ]
        prod_series = nan_array([
            val[1] / 1000 for val in self.weeks_prod.values()
            if not isinstance(val[1], str)
        ])
        plt.bar(date_series, prod_series, zorder=2)
        for i, val in enumerate(prod_series):
            plt.annotate(f'{str(round(val))}k',
                         xy=(date_series[i], prod_series[i]),
                         ha='center',
                         va='bottom')
        plt.grid(axis='y', zorder=1)
        plt.title('Weekly production')
        plt.tick_params(axis='both', labelsize=7)
        plt.gca().yaxis.set_major_formatter(
            mtick.StrMethodFormatter('{x:,.0f}k'))
        plt.tight_layout()
        plt.savefig(plot_filename, format='png')
        plt.close()

        # bar chart of daily production
        plot_filename = self.media_dir / 'images/bar_day_production.png'
        date_series = [
            val[0] for val in self.days_prod.values() if val[0] != 'Day'
        ]
        prod_series = nan_array([
            val[1] for val in self.days_prod.values()
            if not isinstance(val[1], str)
        ])
        plt.bar(date_series, prod_series, zorder=2)
        for i, val in enumerate(prod_series):
            plt.annotate(f'{val:,}',
                         xy=(date_series[i], prod_series[i]),
                         ha='center',
                         va='bottom')
        plt.gca().yaxis.set_major_formatter(
            mtick.StrMethodFormatter('{x:,.0f}k'))
        plt.gca().yaxis.grid(zorder=1)
        plt.title('Daily production')
        plt.tick_params(axis='both', labelsize=7)
        plt.tight_layout()
        plt.savefig(plot_filename, format='png')
        plt.close()

        # bar chart daily recording hours
        plot_filename = self.media_dir / 'images/bar_day_rechours.png'
        rec_series = nan_array([
            val[2] for val in self.days_prod.values()
            if not isinstance(val[2], str)
        ])
        plt.bar(date_series, rec_series, zorder=2)
        for i, val in enumerate(rec_series):
            plt.annotate(f'{val:.2f}',
                         xy=(date_series[i], rec_series[i]),
                         ha='center',
                         va='bottom')
        ax = plt.gca()
        if all(v > RECHRS_LIMITS[0] * 1.1 for v in rec_series):
            ax.set_ylim(RECHRS_LIMITS[0], RECHRS_LIMITS[1])

        else:
            ax.set_ylim(0, RECHRS_LIMITS[1])

        ax.yaxis.set_major_formatter(mtick.StrMethodFormatter('{x:,.0f}'))
        ax.yaxis.grid(zorder=1)
        plt.title('Daily recording hours')
        plt.tick_params(axis='both', labelsize=7)
        plt.tight_layout()
        plt.savefig(plot_filename, format='png')
        plt.close()

        # bar chart daily VPs per hour
        plot_filename = self.media_dir / 'images/bar_day_vphr.png'
        vphr_series = nan_array([
            val[3] for val in self.days_prod.values()
            if not isinstance(val[3], str)
        ])
        plt.bar(date_series, vphr_series, zorder=2)
        for i, val in enumerate(vphr_series):
            plt.annotate(f'{val:.0f}',
                         xy=(date_series[i], vphr_series[i]),
                         ha='center',
                         va='bottom')
        ax = plt.gca()
        if all(v > VPHR_LIMITS[0] * 1.1 for v in vphr_series):
            ax.set_ylim(VPHR_LIMITS[0], VPHR_LIMITS[1])

        else:
            ax.set_ylim(0, VPHR_LIMITS[1])

        ax.yaxis.set_major_formatter(mtick.StrMethodFormatter('{x:,.0f}'))
        ax.yaxis.grid(zorder=1)
        plt.title('Daily VP per hour')
        plt.tick_params(axis='both', labelsize=7)
        plt.tight_layout()
        plt.savefig(plot_filename, format='png')
        plt.close()