Module: DailyFocus
- Defined in:
- app/services/daily_focus.rb,
app/services/daily_focus/prompt.rb,
app/services/daily_focus/run_policy.rb,
app/models/daily_focus/coaching_note.rb,
app/services/daily_focus/chat_runner.rb,
app/services/daily_focus/data_gatherer.rb,
app/services/daily_focus/review_counts.rb,
app/services/daily_focus/stats_section.rb,
app/services/daily_focus/timezone_hints.rb,
app/services/daily_focus/grounding_check.rb,
app/services/daily_focus/coverage_section.rb,
app/services/daily_focus/support_sections.rb,
app/services/daily_focus/follow_up_section.rb,
app/services/daily_focus/work_day_indicator.rb,
app/services/daily_focus/review_data_builder.rb,
app/services/daily_focus/conversation_sharing.rb,
app/services/daily_focus/user_context_builder.rb,
app/services/daily_focus/review_work_day_preloader.rb
Overview
Namespace for Daily Focus automation; see DailyFocus in app/services/daily_focus.rb.
Defined Under Namespace
Modules: ConversationSharing, CoverageSection, FollowUpSection, GroundingCheck, Prompt, StatsSection, SupportSections, TimezoneHints Classes: ChatRunner, CoachingNote, DataGatherer, EmptyBriefingError, FabricatedBriefingError, ReviewCounts, ReviewDataBuilder, ReviewWorkDayPreloader, RunPolicy, UserContextBuilder, WorkDayIndicator
Constant Summary collapse
- TOOL_SERVICE_KEYS =
Service keys passed to Assistant::ChatToolBuilder for tool-based generation.
%w[sales_management app_db support_cases content].freeze
- CHAT_MODEL_KEY =
Model key for Assistant::ChatService (batch + unified path).
GLM 5.2 via OpenRouter, promoted 2026-08-12 after the model A/B on real
briefings: 0 grounding findings on both audiences, the coverage table
reproduced byte-exact (20/20 rows), and ~$0.08/briefing vs
gemini-flash's ~$0.35 (convs 4741/4745; see
doc/development/OPENROUTER.md). gemini-flash stays registered — a
one-line pin back if the scoreboard (daily_focus_grounding_findings
metadata) regresses, exactly as it caught the 3.5→3.6 swap. 'glm-5.2'- ORPHAN_AFTER =
A briefing left in
processinglonger than this with no live worker is
treated as orphaned (its worker died — deploy/OOM/hard-kill — before
advancing the status toready/failed). Generous headroom over the
slowest healthy generation (~7 min) so a merely-slow run is never reaped. 15.minutes
Class Method Summary collapse
-
.audience_for(employee) ⇒ String
Which briefing variant an employee gets.
-
.briefing_live?(conversation) ⇒ Boolean
True when a worker is actively generating this briefing.
-
.rep_scope(audience) ⇒ ActiveRecord::Relation<Employee>
Rep population for an audience.
-
.review_stream_for(audience) ⇒ String
Turbo stream name for the audience's review page, so support-row updates never land on the sales managers' review screen.
Class Method Details
.audience_for(employee) ⇒ String
Which briefing variant an employee gets. Sales wins for dual-role
employees so they keep their original briefing.
27 28 29 30 31 |
# File 'app/services/daily_focus.rb', line 27 def self.audience_for(employee) return 'sales' if employee.sales_rep? employee.tech_rep? ? 'support' : 'sales' end |
.briefing_live?(conversation) ⇒ Boolean
True when a worker is actively generating this briefing. The daily-focus
path runs synchronously through Assistant::ChatService, which — unlike
AssistantChatWorker — does NOT heartbeat the column lock, so a long run's
lock can expire (LOCK_STALE_AFTER = 5 min) while it's still alive. The
PlanOrchestrator rewrites the execution_plan metadata as each step lands,
so a healthy run keeps bumping updated_at; we treat recent activity as
"live" to avoid reclaiming/reaping a slow-but-running briefing.
86 87 88 89 |
# File 'app/services/daily_focus.rb', line 86 def self.briefing_live?(conversation) conversation.processing? || (conversation.updated_at.present? && conversation.updated_at > ORPHAN_AFTER.ago) end |
.rep_scope(audience) ⇒ ActiveRecord::Relation<Employee>
Rep population for an audience. Single source for RunPolicy, the review
page, and ReviewCounts so all three always agree on the denominator.
38 39 40 41 42 43 44 45 46 47 48 |
# File 'app/services/daily_focus.rb', line 38 def self.rep_scope(audience) if audience.to_s == 'support' # distinct: the role join yields one row per matching role # (technical_support_rep + technical_support_manager). Dual-role # employees stay in the sales population (see .audience_for). Employee.technical_support_reps.active_employees.distinct .where.not(id: Employee.sales_reps.select(:id)) else Employee.sales_reps.active_employees end end |
.review_stream_for(audience) ⇒ String
Turbo stream name for the audience's review page, so support-row updates
never land on the sales managers' review screen.
55 56 57 |
# File 'app/services/daily_focus.rb', line 55 def self.review_stream_for(audience) audience.to_s == 'support' ? 'daily_focus:review:support' : 'daily_focus:review' end |