Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CAPT-1797] Ops reports #3284

Merged
merged 15 commits into from
Jan 27, 2025
18 changes: 18 additions & 0 deletions app/controllers/admin/reports_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Admin
class ReportsController < BaseAdminController
before_action :ensure_service_operator

def index
@reports = Report.order(created_at: :desc)
end

def show
respond_to do |format|
format.csv {
report = Report.find(params[:id])
send_data report.csv, filename: "#{report.name.parameterize(separator: "_")}_#{report.created_at.iso8601}.csv"
}
end
end
end
end
25 changes: 25 additions & 0 deletions app/jobs/reports_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class ReportsJob < CronJob
self.cron_expression = "0 6 * * *" # 6AM daily

def perform
Rails.logger.info "Generating Ops reports"

csv = Reports::FailedQualificationClaims.new.to_csv

Report
.find_or_initialize_by(name: Reports::FailedQualificationClaims::NAME)
.update!(csv: csv, number_of_rows: csv.lines.count - 1)

csv = Reports::FailedProviderCheckClaims.new.to_csv

Report
.find_or_initialize_by(name: Reports::FailedProviderCheckClaims::NAME)
.update!(csv: csv, number_of_rows: csv.lines.count - 1)

csv = Reports::DuplicateClaims.new.to_csv

Report
.find_or_initialize_by(name: Reports::DuplicateClaims::NAME)
.update!(csv: csv, number_of_rows: csv.lines.count - 1)
end
end
10 changes: 10 additions & 0 deletions app/models/policies/further_education_payments/eligibility.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ def eligible_itt_subject
nil
end

def verification_assertion(name)
assertion_hash[name]
end

private

def provider_and_claimant_names_match?
Expand All @@ -116,6 +120,12 @@ def provider_first_name
def provider_last_name
verification.dig("verifier", "last_name")
end

def assertion_hash
@assertion_hash ||= verification.fetch("assertions").map do |assertion|
[assertion["name"], assertion["outcome"]]
end.to_h
end
end
end
end
2 changes: 2 additions & 0 deletions app/models/report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Report < ApplicationRecord
end
64 changes: 64 additions & 0 deletions app/models/reports/duplicate_claims.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require "csv"
require "excel_utils"

module Reports
class DuplicateClaims
include Admin::ClaimsHelper

NAME = "Duplicate Approved Claims"
HEADERS = [
"Claim reference",
"Teacher reference number",
"Full name",
"Policy name",
"Claim amount",
"Claim status",
"Decision date",
"Decision agent"
].freeze

def initialize
@claims = Claim
.includes(:eligibility)
.approved
.where(academic_year: AcademicYear.current)
.select { |claim| Claim::MatchingAttributeFinder.new(claim).matching_claims.any? }
end

def to_csv
CSV.generate(write_headers: true, headers: HEADERS) do |csv|
@claims.each do |claim|
csv << ClaimPresenter.new(claim).to_a
end
end
end

private

class ClaimPresenter
include Admin::ClaimsHelper
include ActionView::Helpers::NumberHelper

def initialize(claim)
@claim = claim
end

def to_a
[
claim.reference,
claim.eligibility.try(:teacher_reference_number),
claim.full_name,
claim.policy,
claim.award_amount,
status(claim),
claim.latest_decision&.created_at,
claim.latest_decision&.created_by&.full_name
]
end

private

attr_reader :claim
end
end
end
106 changes: 106 additions & 0 deletions app/models/reports/failed_provider_check_claims.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
require "csv"
require "excel_utils"

module Reports
class FailedProviderCheckClaims
NAME = "Claims with failed provider check"

HEADERS = [
"Claim reference",
"Full name",
"Claim amount",
"Claim status",
"Decision date",
"Decision agent",
"Contract of employment",
"Teaching responsibilities",
"First 5 years of teaching",
"One full term",
"Timetabled teaching hours",
"Age range taught",
"Subject",
"Course",
"2.5 hours weekly teaching",
"Performance",
"Disciplinary"
]

def initialize
@claims = Claim
.by_policy(Policies::FurtherEducationPayments)
.approved
.where(academic_year: AcademicYear.current)
.joins(:tasks)
.merge(Task.where(name: "provider_verification", passed: false))
.includes(:eligibility, decisions: :created_by)
end

def to_csv
CSV.generate(
row_sep: "\r\n",
write_headers: true,
headers: HEADERS
) do |csv|
@claims.each do |claim|
csv << ClaimPresenter.new(claim).to_a
end
end
end

private

class ClaimPresenter
include Admin::ClaimsHelper
include ActionView::Helpers::NumberHelper

def initialize(claim)
@claim = claim
end

def to_a
[
claim.reference,
claim.full_name,
number_to_currency(claim.award_amount, precision: 0),
status(claim),
approval_date,
approval.created_by.full_name,
present_assertion("contract_type"),
present_assertion("teaching_responsibilities"),
present_assertion("further_education_teaching_start_year"),
present_assertion("taught_at_least_one_term"),
present_assertion("teaching_hours_per_week"),
present_assertion("half_teaching_hours"),
present_assertion("subjects_taught"),
# The provider verifies the courses taught question as part of
# verifying the subjects taught question, so these two columns will
# always be the same.
present_assertion("subjects_taught"),
present_assertion("teaching_hours_per_week_next_term"),
present_assertion("subject_to_formal_performance_action"),
present_assertion("subject_to_disciplinary_action")
]
end

private

attr_reader :claim

def approval_date
I18n.l(approval.created_at.to_date, format: :day_month_year)
end

def approval
@approval ||= claim.decisions.reject(&:undone).last
end

def present_assertion(name)
case claim.eligibility.verification_assertion(name)
when true then "Yes"
when false then "No"
else "N/A" # fixed and variable contracts have different assertions
end
end
end
end
end
118 changes: 118 additions & 0 deletions app/models/reports/failed_qualification_claims.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
require "csv"
require "excel_utils"

module Reports
class FailedQualificationClaims
NAME = "Claims with failed qualification status"
HEADERS = [
"Claim reference",
"Teacher reference number",
"Policy",
"Status",
"Decision date",
"Decision agent",
"Applicant answers - Qualification",
"Applicant answers - ITT start year",
"Applicant answers - ITT subject",
"DQT API - ITT subjects",
"DQT API - ITT start date",
"DQT API - QTS award date",
"DQT API - Qualification name"
].freeze

def initialize
@claims = Claim
.approved
.where(academic_year: AcademicYear.current)
.joins(:tasks).merge(Task.where(name: "qualifications", passed: false))
.includes(:eligibility, decisions: :created_by)
end

def to_csv
CSV.generate(write_headers: true, headers: HEADERS) do |csv|
@claims.each do |claim|
csv << ClaimPresenter.new(claim).to_a
end
end
end

class ClaimPresenter
include Admin::ClaimsHelper

def initialize(claim)
@claim = claim
end

def to_a
[
claim.reference,
claim.eligibility.teacher_reference_number,
I18n.t("#{claim.policy.locale_key}.policy_acronym"),
status(claim),
I18n.l(approval.created_at.to_date, format: :day_month_year),
approval.created_by.full_name,
qualification,
itt_academic_year,
eligible_itt_subject,
itt_subjects,
itt_start_date,
qts_award_date,
qualification_name
]
end

private

attr_reader :claim

def approval
@approval ||= claim.decisions.reject(&:undone).last
end

# StudentLoans doesn't have an eligible_itt_subject
def eligible_itt_subject
claim.eligibility.try(:eligible_itt_subject)
end

# StudentLoans doesn't have an itt_academic_year
def itt_academic_year
claim.eligibility.try(:itt_academic_year)
end

# StudentLoans doesn't have a qualification
def qualification
claim.eligibility.try(:qualification)
end

def itt_subjects
dqt_teacher_record&.itt_subjects&.join(", ")
end

def itt_start_date
date = dqt_teacher_record&.itt_start_date

return unless date

I18n.l(date, format: :day_month_year)
end

def qts_award_date
date = dqt_teacher_record&.qts_award_date

return unless date

I18n.l(date, format: :day_month_year)
end

def qualification_name
dqt_teacher_record&.qualification_name
end

def dqt_teacher_record
@dqt_teacher_record ||= if claim.has_dqt_record?
Dqt::Teacher.new(claim.dqt_teacher_status)
end
end
end
end
end
1 change: 1 addition & 0 deletions app/views/admin/claims/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<%= link_to "Upload TPS data", new_admin_tps_data_upload_path, class: "govuk-button govuk-button--secondary", data: { module: "govuk-button" }, role: :button %>
<%= link_to "Upload SLC data", new_admin_student_loans_data_upload_path, class: "govuk-button govuk-button--secondary", data: { module: "govuk-button" }, role: :button %>
<%= link_to "Upload fraud prevention data", new_admin_fraud_risk_csv_upload_path, class: "govuk-button govuk-button--secondary", data: { module: "govuk-button" }, role: :button %>
<%= link_to "Reports", admin_reports_path, class: "govuk-button govuk-button--secondary", data: { module: "govuk-button" }, role: :button %>

<%= render "allocations_form" %>

Expand Down
Loading
Loading