Class: Report::DailyImportSummary
- Inherits:
-
Object
- Object
- Report::DailyImportSummary
- Defined in:
- app/services/report/daily_import_summary.rb
Overview
Daily operational digest of the call-recording import pipeline for the prior
day, emailed to heatwaveteam@ each morning as an "is something amiss" canary:
- how many CallRecords were imported (Switchvox vs Twilio), and how many
were voicemails; - the transcription outcomes for those imports (completed / error / skipped /
still in-flight) + a success rate; - the CURRENT SFTP/R2 file state — recordings waiting to import and recordings
parked in errors/ — both of which should sit at ~0 when healthy.
Anything non-zero in the file-state or transcription-error lines (or zero
imports on a normal day) is surfaced in #issues. Mirrors
CallRecordingGaps: for_scheduled_run + an America/Chicago day window
- memoized data the mailer view reads.
Constant Summary collapse
- TZ =
'America/Chicago'- CALL_SOURCES =
CallRecord#recording_source is set EXPLICITLY by the importers (the model's
historical "NULL = switchvox" note is stale). Call recordings come from the
SFTP/R2 importer (switchvox) and the Twilio API (twilio); voicemail
recordings arrive separately via the VoicemailsMailbox / Switchvox webhook
(switchvox_voicemail,pbx_voicemail). %w[switchvox twilio].freeze
- VOICEMAIL_SOURCES =
%w[switchvox_voicemail pbx_voicemail].freeze
- PENDING_BACKLOG =
The importer runs hourly (6am-7pm CT), so a handful of recordings sitting at
the ingest root is NORMAL — late-evening uploads waiting for the 6am cycle, or
anything uploaded since the last run. Only flag the root as a problem when it's
a real backlog (many files at once) or a recording has sat unimported for a
full day (it has missed every cycle = genuinely stuck). 25- PENDING_STUCK_AFTER =
1.day
Instance Attribute Summary collapse
-
#date ⇒ Date
readonly
The day being summarized.
- #window_end ⇒ ActiveSupport::TimeWithZone readonly
- #window_start ⇒ ActiveSupport::TimeWithZone readonly
Class Method Summary collapse
-
.for_scheduled_run ⇒ Report::DailyImportSummary
Previous calendar day in America/Chicago — the scheduled default.
Instance Method Summary collapse
- #all_clear? ⇒ Boolean
- #business_day? ⇒ Boolean
-
#failed_files ⇒ Integer?
Recordings the importer parked in errors/ (failed import).
-
#file_state_error ⇒ String?
Populated when the R2 listing raised.
-
#imported_calls ⇒ Integer
Switchvox + Twilio CALL recordings.
- #imported_switchvox ⇒ Integer
-
#imported_total ⇒ Integer
Every CallRecord created in the window (call recordings + voicemails + any other source).
- #imported_twilio ⇒ Integer
-
#initialize(date:) ⇒ DailyImportSummary
constructor
A new instance of DailyImportSummary.
-
#issues ⇒ Array<String>
Human-readable problems worth a look; empty when all healthy.
-
#other_imports ⇒ Integer
Anything created in the window that isn't a recognised call/voicemail source — non-zero here means a new source string slipped in unnoticed.
-
#pending_backlog? ⇒ Boolean
True only when the ingest root looks genuinely backed up — many files at once, or a recording stuck for a full day.
-
#pending_files ⇒ Integer?
Recordings uploaded by the PBX but not yet imported (delimiter-scoped to the ingest root, so it never scans the processed/ archive).
-
#pending_oldest ⇒ Time?
The oldest pending recording's upload time, or nil when none / unknown.
- #transcribed_ok ⇒ Integer
-
#transcription_breakdown ⇒ Hash{String=>Integer}
Transcription_state => count.
- #transcription_errors ⇒ Integer
-
#transcription_in_flight ⇒ Integer
Still pending/processing at report time (transcription is async).
-
#transcription_skipped ⇒ Integer
no_audio + too_short — deliberately not transcribed.
-
#transcription_success_rate ⇒ Float?
completed / (completed + error).
-
#voicemails ⇒ Integer
Voicemail recordings (switchvox_voicemail + pbx_voicemail).
-
#weekend? ⇒ Boolean
WarmlyYours has no Sunday call volume and only a trickle on Saturdays, so zero call imports on a weekend is EXPECTED.
Constructor Details
#initialize(date:) ⇒ DailyImportSummary
Returns a new instance of DailyImportSummary.
46 47 48 49 50 51 |
# File 'app/services/report/daily_import_summary.rb', line 46 def initialize(date:) @date = date tz = Time.find_zone(TZ) @window_start = tz.local(date.year, date.month, date.day).beginning_of_day @window_end = @window_start.end_of_day end |
Instance Attribute Details
#date ⇒ Date (readonly)
Returns the day being summarized.
41 42 43 |
# File 'app/services/report/daily_import_summary.rb', line 41 def date @date end |
#window_end ⇒ ActiveSupport::TimeWithZone (readonly)
43 44 45 |
# File 'app/services/report/daily_import_summary.rb', line 43 def window_end @window_end end |
#window_start ⇒ ActiveSupport::TimeWithZone (readonly)
43 44 45 |
# File 'app/services/report/daily_import_summary.rb', line 43 def window_start @window_start end |
Class Method Details
.for_scheduled_run ⇒ Report::DailyImportSummary
Previous calendar day in America/Chicago — the scheduled default.
38 |
# File 'app/services/report/daily_import_summary.rb', line 38 def self.for_scheduled_run = new(date: Time.find_zone(TZ).now.to_date - 1) |
Instance Method Details
#all_clear? ⇒ Boolean
152 |
# File 'app/services/report/daily_import_summary.rb', line 152 def all_clear? = issues.empty? |
#business_day? ⇒ Boolean
161 |
# File 'app/services/report/daily_import_summary.rb', line 161 def business_day? = date.on_weekday? |
#failed_files ⇒ Integer?
Recordings the importer parked in errors/ (failed import). Healthy ≈ 0.
131 |
# File 'app/services/report/daily_import_summary.rb', line 131 def failed_files = file_state[:failed] |
#file_state_error ⇒ String?
Returns populated when the R2 listing raised.
134 |
# File 'app/services/report/daily_import_summary.rb', line 134 def file_state_error = file_state[:error] |
#imported_calls ⇒ Integer
Switchvox + Twilio CALL recordings.
62 |
# File 'app/services/report/daily_import_summary.rb', line 62 def imported_calls = scope.where(recording_source: CALL_SOURCES).count |
#imported_switchvox ⇒ Integer
65 |
# File 'app/services/report/daily_import_summary.rb', line 65 def imported_switchvox = scope.where(recording_source: 'switchvox').count |
#imported_total ⇒ Integer
Every CallRecord created in the window (call recordings + voicemails + any
other source).
58 |
# File 'app/services/report/daily_import_summary.rb', line 58 def imported_total = scope.count |
#imported_twilio ⇒ Integer
68 |
# File 'app/services/report/daily_import_summary.rb', line 68 def imported_twilio = scope.where(recording_source: 'twilio').count |
#issues ⇒ Array<String>
Human-readable problems worth a look; empty when all healthy.
140 141 142 143 144 145 146 147 148 149 |
# File 'app/services/report/daily_import_summary.rb', line 140 def issues [].tap do |list| list << "#{pending_files} recording(s) waiting to import — possible importer backlog or stall" if pending_backlog? list << "#{failed_files} recording(s) parked in errors/ — failed import" if failed_files.to_i.positive? list << "#{transcription_errors} transcription failure(s)" if transcription_errors.positive? list << 'no call recordings imported on a business day — the import pipeline may be down' if imported_calls.zero? && business_day? list << "#{other_imports} record(s) with an unrecognised recording_source" if other_imports.positive? list << "SFTP/R2 file-state check failed: #{file_state_error}" if file_state_error end end |
#other_imports ⇒ Integer
Anything created in the window that isn't a recognised call/voicemail source —
non-zero here means a new source string slipped in unnoticed.
77 |
# File 'app/services/report/daily_import_summary.rb', line 77 def other_imports = imported_total - imported_calls - voicemails |
#pending_backlog? ⇒ Boolean
True only when the ingest root looks genuinely backed up — many files at once,
or a recording stuck for a full day. A few stragglers awaiting the next import
cycle do NOT trip this (that's the normal hourly-importer state).
122 123 124 125 126 127 |
# File 'app/services/report/daily_import_summary.rb', line 122 def pending_backlog? return false if pending_files.nil? pending_files >= PENDING_BACKLOG || (pending_oldest.present? && pending_oldest < Time.current - PENDING_STUCK_AFTER) end |
#pending_files ⇒ Integer?
Recordings uploaded by the PBX but not yet imported (delimiter-scoped to the
ingest root, so it never scans the processed/ archive). Healthy ≈ 0.
112 |
# File 'app/services/report/daily_import_summary.rb', line 112 def pending_files = file_state[:pending] |
#pending_oldest ⇒ Time?
The oldest pending recording's upload time, or nil when none / unknown.
116 |
# File 'app/services/report/daily_import_summary.rb', line 116 def pending_oldest = file_state[:pending_oldest] |
#transcribed_ok ⇒ Integer
87 |
# File 'app/services/report/daily_import_summary.rb', line 87 def transcribed_ok = transcription_breakdown['completed'].to_i |
#transcription_breakdown ⇒ Hash{String=>Integer}
Returns transcription_state => count.
82 83 84 |
# File 'app/services/report/daily_import_summary.rb', line 82 def transcription_breakdown @transcription_breakdown ||= scope.group(:transcription_state).count.transform_keys(&:to_s) end |
#transcription_errors ⇒ Integer
90 |
# File 'app/services/report/daily_import_summary.rb', line 90 def transcription_errors = transcription_breakdown['error'].to_i |
#transcription_in_flight ⇒ Integer
Still pending/processing at report time (transcription is async).
98 |
# File 'app/services/report/daily_import_summary.rb', line 98 def transcription_in_flight = transcription_breakdown.values_at('pending', 'processing').compact.sum |
#transcription_skipped ⇒ Integer
no_audio + too_short — deliberately not transcribed.
94 |
# File 'app/services/report/daily_import_summary.rb', line 94 def transcription_skipped = transcription_breakdown.values_at('no_audio', 'too_short').compact.sum |
#transcription_success_rate ⇒ Float?
completed / (completed + error). Nil when nothing reached a transcribe verdict.
102 103 104 105 |
# File 'app/services/report/daily_import_summary.rb', line 102 def transcription_success_rate decided = transcribed_ok + transcription_errors decided.zero? ? nil : (transcribed_ok.to_f / decided * 100).round(1) end |
#voicemails ⇒ Integer
Voicemail recordings (switchvox_voicemail + pbx_voicemail).
72 |
# File 'app/services/report/daily_import_summary.rb', line 72 def voicemails = scope.where(recording_source: VOICEMAIL_SOURCES).count |
#weekend? ⇒ Boolean
WarmlyYours has no Sunday call volume and only a trickle on Saturdays, so zero
call imports on a weekend is EXPECTED. The "pipeline down" alarm is gated to
business days; the view shows a reassuring note on a quiet weekend instead.
158 |
# File 'app/services/report/daily_import_summary.rb', line 158 def weekend? = date.on_weekend? |