This page documents every KPI on the Closing Ratio tab of the FSP Marketing Dashboard (Rails app, fsp-marketing-dash), at both the EC (Energy Consultant) and Team level. It covers exact definitions, formulas, source tables/joins, filters, timezone handling, and the real SQL/Arel behind each metric, plus the deals drilldown.
The Closing Ratio tab measures the sales funnel after an appointment is set: how many appointments were run, how many became contracts, cancellation and cleanliness rates, and the derived ratios (Run %, Close %, S-PTR). EC↔team attribution is driven by HubSpot hubspot_owner_id reconciled against the proposal-tool EC roster.
Reporting Conventions & Glossary
<aside> 🕑
All date bucketing is done in Central time (America/Chicago) via ReportingTimeZone. HubSpot stores timestamps as timestamp without time zone holding UTC wall-clock, so raw UTC bucketing would push late-evening deals into the wrong day/month. See app/services/reporting_time_zone.rb:13.
</aside>
Entry point is Api::V1::ClosingRatioController (app/controllers/api/v1/closing_ratio_controller.rb). All endpoints require authentication (authenticate_user!).
GET /api/v1/closing_ratio — returns the full summary payload (owners + teams + meta). Cached (closing_ratio_controller.rb:10).POST /api/v1/closing_ratio/refresh — busts the summary cache and the proposal-tool EC roster cache (ProposalToolService.refresh_ecs), then recomputes (closing_ratio_controller.rb:23).GET /api/v1/closing_ratio/deals — the drilldown; not cached (closing_ratio_controller.rb:40).CACHE_EXPIRY = 15.minutes # closing_ratio_controller.rb:6
def cache_key
"closing_ratio_metrics_v5_#{start_date}_#{end_date}_alphas_#{include_alphas}_#{region_filter || 'all'}"
end
The cache key varies on the date window, the Include Alphas toggle, and the region filter (closing_ratio_controller.rb:84). The ProposalToolService EC roster has its own 1-hour cache (proposal_tool_service.rb:6).
| Param | Default | Notes |
|---|---|---|
| start_date | Date.current.beginning_of_month | Parsed; falls back to month start on bad input (:60) |
| end_date | Date.current | Parsed; falls back to today on bad input (:70) |
| include_alphas | true | Only "false" disables; anything else is true (:56) |
| region | nil (all) | Optional region scope (:80) |
The same MetricsCalculator region logic powers the ASM Sales Dashboard. Region is attributed by the deal's market_region (matching f2p), not the contact's (metrics_calculator.rb:286). Austin absorbed San Antonio, so the region expands:
REGION_EXPANSION = { "Austin" => ["Austin", "San Antonio"] }.freeze # metrics_calculator.rb:14
When a region is selected, owners and teams are filtered to teams whose name is in the expanded region set (metrics_calculator.rb:239, :259, team_matches_region? at :275).