From 8f288b3065a3c61d446e09915d7473e66d11d15a Mon Sep 17 00:00:00 2001 From: KolawoleHOseni Date: Mon, 28 Jul 2025 15:42:02 -0500 Subject: [PATCH 1/9] Report update --- lib/reporting/irs_verification_report.rb | 88 ++++++++---------------- 1 file changed, 30 insertions(+), 58 deletions(-) diff --git a/lib/reporting/irs_verification_report.rb b/lib/reporting/irs_verification_report.rb index 6fa55a9d7eb..48c273eeaee 100644 --- a/lib/reporting/irs_verification_report.rb +++ b/lib/reporting/irs_verification_report.rb @@ -37,17 +37,12 @@ def initialize(time_range:, issuers:, verbose: false, progress: false, slice: 1. @threads = threads end - def verbose? - @verbose - end + def verbose? = @verbose - def progress? - @progress - end + def progress? = @progress def as_tables - [overview_table, - funnel_table] + [overview_table, funnel_table] end def as_emailable_reports @@ -81,39 +76,30 @@ def as_emailable_reports def to_csvs as_emailable_reports.map do |report| - CSV.generate do |csv| - report.table.each { |row| csv << row } - end + CSV.generate { |csv| report.table.each { |row| csv << row } } end end def overview_table [ ['Report Timeframe', "#{time_range.begin.to_date} to #{time_range.end.to_date}"], - ['Report Generated', Date.today.to_s], # rubocop:disable Rails/Date + ['Report Generated', Date.today.to_s], ['Issuer', issuers.join(', ')], ] end def funnel_table - verification_demand = verification_demand_results - document_auth_success = document_authentication_success_results - info_validation_success = information_validation_success_results - phone_verification_success = phone_verification_success_results - total_verified = total_verified_results - [ ['Metric', 'Count', 'Rate'], ['Verification Demand', verification_demand, to_percent(verification_demand, verification_demand)], - ['Document Authentication Success', document_auth_success, - to_percent(document_auth_success, verification_demand)], - ['Information Verification Success', info_validation_success, - to_percent(info_validation_success, verification_demand)], + ['Document Authentication Success', document_authentication_success, + to_percent(document_authentication_success, verification_demand)], + ['Information Validation Success', information_validation_success, + to_percent(information_validation_success, verification_demand)], ['Phone Verification Success', phone_verification_success, to_percent(phone_verification_success, verification_demand)], - ['Verification Successes', total_verified, - to_percent(total_verified, verification_demand)], + ['Verification Successes', total_verified, to_percent(total_verified, verification_demand)], ['Verification Failures', verification_demand - total_verified, to_percent(verification_demand - total_verified, verification_demand)], ] @@ -152,10 +138,6 @@ def cloudwatch_client ) end - # def quote(array) - # '[' + array.map { |e| %("#{e}") }.join(', ') + ']' - # end - def query params = { issuers: quote(issuers), @@ -163,15 +145,13 @@ def query } format(<<~QUERY, params) - filter properties.sp_request.facial_match - and name in %{event_names} + filter properties.sp_request.facial_match and name in %{event_names} | fields (name = '#{Events::VERIFICATION_DEMAND}') as @IdV_IAL2_start, (name = '#{Events::DOCUMENT_AUTHENTICATION_SUCCESS}') as @Doc_auth_success, (name = '#{Events::INFORMATION_VALIDATION_SUCCESS}') as @Verify_info_success, (name = '#{Events::PHONE_VERIFICATION_SUCCESS}') as @Verify_phone_success, - (name = '#{Events::TOTAL_VERIFIED}' and properties.event_properties.ial2) as @Verified_1, - coalesce(@Verified_1, 0) as @Verified, + (name = '#{Events::TOTAL_VERIFIED}' and properties.event_properties.ial2) as @Verified, properties.user_id | stats max(@IdV_IAL2_start) as max_idv_ial2_start, @@ -179,65 +159,57 @@ def query max(@Verify_info_success) as max_verify_info_success, max(@Verify_phone_success) as max_verify_phone_success, max(@Verified) as max_verified - by properties.user_id, bin(1y) - | stats - sum(max_idv_ial2_start) as IdV_IAL2_Start_User_count, - sum(max_doc_auth_success) as Doc_auth_success_User_count, - sum(max_verify_info_success) as Verify_info_success_User_count, - sum(max_verify_phone_success) as Verify_phone_success_User_count, - sum(max_verified) as Verified_User_count - | limit 10000 + by properties.user_id + | filter max_idv_ial2_start = 1 + | limit 10000 QUERY end def fetch_results Rails.logger.info('Executing unified query') - results = cloudwatch_client.fetch( + cloudwatch_client.fetch( query: query, from: time_range.begin.beginning_of_day, to: time_range.end.end_of_day, ) - Rails.logger.info("Results: #{results.inspect}") - results rescue StandardError => e Rails.logger.error("Failed to fetch results for unified query: #{e.message}") [] end def data - @data ||= fetch_results.first || {} + @data ||= fetch_results end - def verification_demand_results - data['IdV_IAL2_Start_User_count'].to_i || 0 + def verification_demand + data.sum { |row| row['max_idv_ial2_start'].to_i } end - def document_authentication_success_results - data['Doc_auth_success_User_count'].to_i || 0 + def document_authentication_success + data.sum { |row| row['max_doc_auth_success'].to_i } end - def information_validation_success_results - data['Verify_info_success_User_count'].to_i || 0 + def information_validation_success + data.sum { |row| row['max_verify_info_success'].to_i } end - def phone_verification_success_results - data['Verify_phone_success_User_count'].to_i || 0 + def phone_verification_success + data.sum { |row| row['max_verify_phone_success'].to_i } end - def total_verified_results - data['Verified_User_count'].to_i || 0 + def total_verified + data.sum { |row| row['max_verified'].to_i } end def to_percent(numerator, denominator) return 0.0 if denominator.nil? || denominator.zero? - ((numerator.to_f / denominator)).round(2) + (numerator.to_f / denominator).round(2) end end end +# Run via Rails runner or CLI if __FILE__ == $PROGRAM_NAME options = Reporting::CommandLineOptions.new.parse!(ARGV) - Reporting::IrsVerificationReport.new(**options).to_csvs.each do |csv| - puts csv - end + Reporting::IrsVerificationReport.new(**options).to_csvs.each { |csv| puts csv } end From 7e9b58985e343a7662323e8ecbb3fb20f8fce6f5 Mon Sep 17 00:00:00 2001 From: KolawoleHOseni Date: Mon, 28 Jul 2025 20:09:08 -0500 Subject: [PATCH 2/9] report update --- lib/reporting/irs_verification_report.rb | 96 ++++++++++++++---------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/lib/reporting/irs_verification_report.rb b/lib/reporting/irs_verification_report.rb index 48c273eeaee..67b9873354a 100644 --- a/lib/reporting/irs_verification_report.rb +++ b/lib/reporting/irs_verification_report.rb @@ -37,9 +37,13 @@ def initialize(time_range:, issuers:, verbose: false, progress: false, slice: 1. @threads = threads end - def verbose? = @verbose + def verbose? + @verbose + end - def progress? = @progress + def progress? + @progress + end def as_tables [overview_table, funnel_table] @@ -76,27 +80,35 @@ def as_emailable_reports def to_csvs as_emailable_reports.map do |report| - CSV.generate { |csv| report.table.each { |row| csv << row } } + CSV.generate do |csv| + report.table.each { |row| csv << row } + end end end def overview_table [ ['Report Timeframe', "#{time_range.begin.to_date} to #{time_range.end.to_date}"], - ['Report Generated', Date.today.to_s], + ['Report Generated', Date.today.to_s], # rubocop:disable Rails/Date ['Issuer', issuers.join(', ')], ] end def funnel_table + verification_demand = verification_demand_results + document_auth_success = document_authentication_success_results + info_validation_success = information_validation_success_results + phone_verification_success = phone_verification_success_results + total_verified = total_verified_results + [ ['Metric', 'Count', 'Rate'], ['Verification Demand', verification_demand, to_percent(verification_demand, verification_demand)], - ['Document Authentication Success', document_authentication_success, - to_percent(document_authentication_success, verification_demand)], - ['Information Validation Success', information_validation_success, - to_percent(information_validation_success, verification_demand)], + ['Document Authentication Success', document_auth_success, + to_percent(document_auth_success, verification_demand)], + ['Information Verification Success', info_validation_success, + to_percent(info_validation_success, verification_demand)], ['Phone Verification Success', phone_verification_success, to_percent(phone_verification_success, verification_demand)], ['Verification Successes', total_verified, to_percent(total_verified, verification_demand)], @@ -145,60 +157,67 @@ def query } format(<<~QUERY, params) - filter properties.sp_request.facial_match and name in %{event_names} - | fields - (name = '#{Events::VERIFICATION_DEMAND}') as @IdV_IAL2_start, - (name = '#{Events::DOCUMENT_AUTHENTICATION_SUCCESS}') as @Doc_auth_success, - (name = '#{Events::INFORMATION_VALIDATION_SUCCESS}') as @Verify_info_success, - (name = '#{Events::PHONE_VERIFICATION_SUCCESS}') as @Verify_phone_success, - (name = '#{Events::TOTAL_VERIFIED}' and properties.event_properties.ial2) as @Verified, - properties.user_id - | stats - max(@IdV_IAL2_start) as max_idv_ial2_start, - max(@Doc_auth_success) as max_doc_auth_success, - max(@Verify_info_success) as max_verify_info_success, - max(@Verify_phone_success) as max_verify_phone_success, - max(@Verified) as max_verified - by properties.user_id - | filter max_idv_ial2_start = 1 + filter properties.sp_request.facial_match + and name in %{event_names} + and properties.sp_request.issuer in %{issuers} + | fields + (name = '#{Events::VERIFICATION_DEMAND}') as @IdV_IAL2_start, + (name = '#{Events::DOCUMENT_AUTHENTICATION_SUCCESS}') as @Doc_auth_success, + (name = '#{Events::INFORMATION_VALIDATION_SUCCESS}') as @Verify_info_success, + (name = '#{Events::PHONE_VERIFICATION_SUCCESS}') as @Verify_phone_success, + (name = '#{Events::TOTAL_VERIFIED}' and properties.event_properties.ial2) as @Verified, + properties.user_id + | stats + max(@IdV_IAL2_start) as idv_ial2_start, + max(@Doc_auth_success) as doc_auth_success, + max(@Verify_info_success) as verify_info_success, + max(@Verify_phone_success) as verify_phone_success, + max(@Verified) as verified + by properties.user_id | limit 10000 QUERY end def fetch_results Rails.logger.info('Executing unified query') - cloudwatch_client.fetch( + results = cloudwatch_client.fetch( query: query, from: time_range.begin.beginning_of_day, to: time_range.end.end_of_day, ) + Rails.logger.info("Results: #{results.inspect}") + results rescue StandardError => e Rails.logger.error("Failed to fetch results for unified query: #{e.message}") [] end def data - @data ||= fetch_results + @data ||= fetch_results || [] end - def verification_demand - data.sum { |row| row['max_idv_ial2_start'].to_i } + def started_users + @started_users ||= data.select { |row| row['idv_ial2_start'].to_i == 1 } end - def document_authentication_success - data.sum { |row| row['max_doc_auth_success'].to_i } + def verification_demand_results + started_users.size end - def information_validation_success - data.sum { |row| row['max_verify_info_success'].to_i } + def document_authentication_success_results + started_users.count { |row| row['doc_auth_success'].to_i == 1 } end - def phone_verification_success - data.sum { |row| row['max_verify_phone_success'].to_i } + def information_validation_success_results + started_users.count { |row| row['verify_info_success'].to_i == 1 } end - def total_verified - data.sum { |row| row['max_verified'].to_i } + def phone_verification_success_results + started_users.count { |row| row['verify_phone_success'].to_i == 1 } + end + + def total_verified_results + started_users.count { |row| row['verified'].to_i == 1 } end def to_percent(numerator, denominator) @@ -208,8 +227,9 @@ def to_percent(numerator, denominator) end end -# Run via Rails runner or CLI if __FILE__ == $PROGRAM_NAME options = Reporting::CommandLineOptions.new.parse!(ARGV) - Reporting::IrsVerificationReport.new(**options).to_csvs.each { |csv| puts csv } + Reporting::IrsVerificationReport.new(**options).to_csvs.each do |csv| + puts csv + end end From 1887844b49902e5c247b7ab52d62deca04f853ed Mon Sep 17 00:00:00 2001 From: KolawoleHOseni Date: Tue, 29 Jul 2025 14:06:03 -0500 Subject: [PATCH 3/9] Update --- lib/reporting/irs_verification_report.rb | 64 ++++++++---------------- 1 file changed, 21 insertions(+), 43 deletions(-) diff --git a/lib/reporting/irs_verification_report.rb b/lib/reporting/irs_verification_report.rb index 67b9873354a..37ac2e5a8ce 100644 --- a/lib/reporting/irs_verification_report.rb +++ b/lib/reporting/irs_verification_report.rb @@ -1,12 +1,13 @@ # frozen_string_literal: true require 'csv' + begin require 'reporting/cloudwatch_client' require 'reporting/cloudwatch_query_quoting' require 'reporting/command_line_options' rescue LoadError => e - warn 'could not load paths, try running with "bundle exec rails runner"' + warn 'Could not load paths, try running with "bundle exec rails runner"' raise e end @@ -80,16 +81,14 @@ def as_emailable_reports def to_csvs as_emailable_reports.map do |report| - CSV.generate do |csv| - report.table.each { |row| csv << row } - end + CSV.generate { |csv| report.table.each { |row| csv << row } } end end def overview_table [ ['Report Timeframe', "#{time_range.begin.to_date} to #{time_range.end.to_date}"], - ['Report Generated', Date.today.to_s], # rubocop:disable Rails/Date + ['Report Generated', Date.today.to_s], ['Issuer', issuers.join(', ')], ] end @@ -150,74 +149,53 @@ def cloudwatch_client ) end - def query + def query_for_event(event_name) params = { + event_name: quote(event_name), issuers: quote(issuers), - event_names: quote(Events.all_events), } format(<<~QUERY, params) - filter properties.sp_request.facial_match - and name in %{event_names} - and properties.sp_request.issuer in %{issuers} - | fields - (name = '#{Events::VERIFICATION_DEMAND}') as @IdV_IAL2_start, - (name = '#{Events::DOCUMENT_AUTHENTICATION_SUCCESS}') as @Doc_auth_success, - (name = '#{Events::INFORMATION_VALIDATION_SUCCESS}') as @Verify_info_success, - (name = '#{Events::PHONE_VERIFICATION_SUCCESS}') as @Verify_phone_success, - (name = '#{Events::TOTAL_VERIFIED}' and properties.event_properties.ial2) as @Verified, - properties.user_id - | stats - max(@IdV_IAL2_start) as idv_ial2_start, - max(@Doc_auth_success) as doc_auth_success, - max(@Verify_info_success) as verify_info_success, - max(@Verify_phone_success) as verify_phone_success, - max(@Verified) as verified - by properties.user_id + filter properties.sp_request.facial_match + and name = %{event_name} + and properties.service_provider in %{issuers} + | fields properties.user_id | limit 10000 QUERY end - def fetch_results - Rails.logger.info('Executing unified query') + def fetch_event_user_ids(event_name) + Rails.logger.info("Fetching results for event: #{event_name}") results = cloudwatch_client.fetch( - query: query, + query: query_for_event(event_name), from: time_range.begin.beginning_of_day, to: time_range.end.end_of_day, ) - Rails.logger.info("Results: #{results.inspect}") - results + Rails.logger.info("Fetched #{results.count} rows for #{event_name}") + results.map { |row| row['properties.user_id'] }.uniq rescue StandardError => e - Rails.logger.error("Failed to fetch results for unified query: #{e.message}") + Rails.logger.error("Failed to fetch event #{event_name}: #{e.message}") [] end - def data - @data ||= fetch_results || [] - end - - def started_users - @started_users ||= data.select { |row| row['idv_ial2_start'].to_i == 1 } - end - def verification_demand_results - started_users.size + fetch_event_user_ids(Events::VERIFICATION_DEMAND).count end def document_authentication_success_results - started_users.count { |row| row['doc_auth_success'].to_i == 1 } + fetch_event_user_ids(Events::DOCUMENT_AUTHENTICATION_SUCCESS).count end def information_validation_success_results - started_users.count { |row| row['verify_info_success'].to_i == 1 } + fetch_event_user_ids(Events::INFORMATION_VALIDATION_SUCCESS).count end def phone_verification_success_results - started_users.count { |row| row['verify_phone_success'].to_i == 1 } + fetch_event_user_ids(Events::PHONE_VERIFICATION_SUCCESS).count end def total_verified_results - started_users.count { |row| row['verified'].to_i == 1 } + fetch_event_user_ids(Events::TOTAL_VERIFIED).count end def to_percent(numerator, denominator) From 6bb6b2be1c0c09b28e1885cd2462b3b652630d9e Mon Sep 17 00:00:00 2001 From: KolawoleHOseni Date: Wed, 30 Jul 2025 17:47:30 -0500 Subject: [PATCH 4/9] lint fix --- lib/reporting/irs_verification_report.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reporting/irs_verification_report.rb b/lib/reporting/irs_verification_report.rb index 37ac2e5a8ce..8b221f9187a 100644 --- a/lib/reporting/irs_verification_report.rb +++ b/lib/reporting/irs_verification_report.rb @@ -88,7 +88,7 @@ def to_csvs def overview_table [ ['Report Timeframe', "#{time_range.begin.to_date} to #{time_range.end.to_date}"], - ['Report Generated', Date.today.to_s], + ['Report Generated', Time.zone.today.to_s], ['Issuer', issuers.join(', ')], ] end From 0238053897c633b0fbdc3a571fd911c65ce638ba Mon Sep 17 00:00:00 2001 From: KolawoleHOseni Date: Wed, 30 Jul 2025 18:11:01 -0500 Subject: [PATCH 5/9] Changelog: Internal, Internal Report, IRS Verification Funnel Report From a0e010f43ec42fed76f4b6420bdc171d7466b269 Mon Sep 17 00:00:00 2001 From: KolawoleHOseni Date: Fri, 1 Aug 2025 10:46:55 -0500 Subject: [PATCH 6/9] Table header --- lib/reporting/irs_verification_report.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/reporting/irs_verification_report.rb b/lib/reporting/irs_verification_report.rb index 8b221f9187a..a0e4fc0cc12 100644 --- a/lib/reporting/irs_verification_report.rb +++ b/lib/reporting/irs_verification_report.rb @@ -69,12 +69,12 @@ def as_emailable_reports filename: 'Overview Report', ), Reporting::EmailableReport.new( - title: 'Funnel Metrics', + title: 'Verification Funnel Metrics', subtitle: '', float_as_percent: true, precision: 2, table: funnel_table, - filename: 'Funnel Metrics', + filename: 'Verification Funnel Metrics', ), ] end From d65b9e833e0ae32677cf2382541c83c15f9ecde5 Mon Sep 17 00:00:00 2001 From: KolawoleHOseni Date: Mon, 4 Aug 2025 18:46:06 -0500 Subject: [PATCH 7/9] error handling for events --- lib/reporting/irs_verification_report.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/reporting/irs_verification_report.rb b/lib/reporting/irs_verification_report.rb index a0e4fc0cc12..11eeb0783e0 100644 --- a/lib/reporting/irs_verification_report.rb +++ b/lib/reporting/irs_verification_report.rb @@ -165,14 +165,27 @@ def query_for_event(event_name) end def fetch_event_user_ids(event_name) + unless Events.all_events.include?(event_name) + Rails.logger.warn("Event name '#{event_name}' is not in the list of known events.") + return [] + end + Rails.logger.info("Fetching results for event: #{event_name}") + results = cloudwatch_client.fetch( query: query_for_event(event_name), from: time_range.begin.beginning_of_day, to: time_range.end.end_of_day, ) - Rails.logger.info("Fetched #{results.count} rows for #{event_name}") - results.map { |row| row['properties.user_id'] }.uniq + + if results.nil? || results.empty? + Rails.logger.warn("No results returned for event: #{event_name}") + return [] + end + + user_ids = results.map { |row| row['properties.user_id'] }.compact.uniq + Rails.logger.info("Fetched #{user_ids.count} unique user IDs for #{event_name}") + user_ids rescue StandardError => e Rails.logger.error("Failed to fetch event #{event_name}: #{e.message}") [] From 78f94beb06e19a6335dee3b61158af3d6672f4a2 Mon Sep 17 00:00:00 2001 From: KolawoleHOseni Date: Mon, 4 Aug 2025 19:58:34 -0500 Subject: [PATCH 8/9] spec fix --- spec/lib/reporting/irs_verification_report_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/reporting/irs_verification_report_spec.rb b/spec/lib/reporting/irs_verification_report_spec.rb index 1069aa9c2b0..ded51688ce2 100644 --- a/spec/lib/reporting/irs_verification_report_spec.rb +++ b/spec/lib/reporting/irs_verification_report_spec.rb @@ -21,7 +21,7 @@ def previous_week_range describe '#overview_table' do it 'generates the overview table with the correct data' do - freeze_time do + freeze_time(Time.zone.local(2025, 8, 4)) do expected_generated_date = Time.current.utc.to_date.to_s table = report.overview_table From bde9b2b398932b4aa5ea91f461fdedbf32860aba Mon Sep 17 00:00:00 2001 From: KolawoleHOseni Date: Mon, 4 Aug 2025 20:14:04 -0500 Subject: [PATCH 9/9] spec fix --- spec/lib/reporting/irs_verification_report_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/reporting/irs_verification_report_spec.rb b/spec/lib/reporting/irs_verification_report_spec.rb index ded51688ce2..fe953a158db 100644 --- a/spec/lib/reporting/irs_verification_report_spec.rb +++ b/spec/lib/reporting/irs_verification_report_spec.rb @@ -21,7 +21,7 @@ def previous_week_range describe '#overview_table' do it 'generates the overview table with the correct data' do - freeze_time(Time.zone.local(2025, 8, 4)) do + travel_to Time.zone.local(2025, 8, 4) do expected_generated_date = Time.current.utc.to_date.to_s table = report.overview_table