# frozen_string_literal: true module ProcedureStatsConcern extend ActiveSupport::Concern NB_DAYS_RECENT_DOSSIERS = 30 # Percentage of dossiers considered to compute the 'usual traitement time'. # For instance, a value of '90' means that the usual traitement time will return # the duration under which 90% of the given dossiers are closed. USUAL_TRAITEMENT_TIME_PERCENTILE = 90 def stats_usual_traitement_time Rails.cache.fetch("#{cache_key_with_version}/stats_usual_traitement_time", expires_in: 12.hours) do usual_traitement_time_for_recent_dossiers(NB_DAYS_RECENT_DOSSIERS) end end def stats_usual_traitement_time_by_month_in_days Rails.cache.fetch("#{cache_key_with_version}/stats_usual_traitement_time_by_month_in_days", expires_in: 12.hours) do usual_traitement_time_by_month_in_days end end def stats_dossiers_funnel Rails.cache.fetch("#{cache_key_with_version}/stats_dossiers_funnel", expires_in: 12.hours) do [ ['Tous (dont brouillon)', dossiers.visible_by_user_or_administration.count + nb_dossiers_termines_supprimes], ['Déposés', dossiers.visible_by_administration.count + nb_dossiers_termines_supprimes], ['Instruction débutée', dossiers.visible_by_administration.state_instruction_commencee.count + nb_dossiers_termines_supprimes], ['Traités', nb_dossiers_termines] ] end end def stats_termines_states Rails.cache.fetch("#{cache_key_with_version}/stats_termines_states", expires_in: 12.hours) do [ ['Acceptés', percentage(dossiers.visible_by_administration.state_accepte.count, nb_dossiers_termines)], ['Refusés', percentage(dossiers.visible_by_administration.state_refuse.count, nb_dossiers_termines)], ['Classés sans suite', percentage(dossiers.visible_by_administration.state_sans_suite.count, nb_dossiers_termines)] ] end end def stats_termines_by_week Rails.cache.fetch("#{cache_key_with_version}/stats_termines_by_week", expires_in: 12.hours) do now = Time.zone.now chart_data = dossiers.includes(:traitements) .visible_by_administration .state_termine .where(traitements: { processed_at: (now.beginning_of_week - 6.months)..now.end_of_week }) dossier_state_values = chart_data.pluck(:state).sort.uniq # rubocop:disable Style/HashTransformValues dossier_state_values .map do |state| { name: state, data: chart_data .where(state: state) .group_by_week do |dossier| dossier.traitements.first.processed_at end.map { |k, v| [k, v.count] }.to_h.transform_keys { |week| pretty_week(week) } } # rubocop:enable Style/HashTransformValues end end end def traitement_times(date_range) Traitement.for_traitement_time_stats(self) .where(processed_at: date_range) .pluck('dossiers.depose_at', :processed_at) .map { |depose_at, processed_at| { depose_at: depose_at, processed_at: processed_at } } end def usual_traitement_time_by_month_in_days traitement_times(first_processed_at..last_considered_processed_at) .group_by { |t| t[:processed_at].beginning_of_month } .transform_values { |month| month.map { |h| h[:processed_at] - h[:depose_at] } } .transform_values { |traitement_times_for_month| traitement_times_for_month.percentile(USUAL_TRAITEMENT_TIME_PERCENTILE).ceil } .transform_values { |seconds| seconds == 0 ? nil : seconds } .transform_values { |seconds| convert_seconds_in_days(seconds) } .transform_keys { |month| pretty_month(month) } end def usual_traitement_time_for_recent_dossiers(nb_days) now = Time.zone.now clusters_count = 3 traitement_time = traitement_times((now - nb_days.days)..now) .map { |times| times[:processed_at] - times[:depose_at] } .sort if traitement_time.size >= clusters_count traitement_time.each_slice((traitement_time.size.to_f / clusters_count.to_f).ceil) .map { _1.percentile(USUAL_TRAITEMENT_TIME_PERCENTILE) } else nil end end private def nb_dossiers_termines @nb_dossiers_termines ||= dossiers.visible_by_administration.state_termine.count + nb_dossiers_termines_supprimes end def nb_dossiers_termines_supprimes @nb_dossiers_termines_supprimes ||= deleted_dossiers.state_termine.count end def first_processed_at Traitement.for_traitement_time_stats(self).pick(:processed_at) end def last_considered_processed_at (1.month.ago).end_of_month end def convert_seconds_in_days(seconds) (seconds / 60.0 / 60.0 / 24.0).ceil end def percentage(value, total) (100 * value / total.to_f).round(1) end def pretty_month(month) I18n.l(month, format: "%B %Y") end def pretty_week(week) I18n.l(week, format: '%d %b') end end