Hypertext Rails
Documentation
Getting Started
Communication Center
- Automation Workflow Flow
- Trigger Events and Cadence Rules
- Fallback Channel Implementation
- Fallback Channel Testing (dev)
- Twilio SMS Integration Guide
- Email Tracking Setup (sent, delivered, failed, open, click)
- SMS Tracking & Twilio Free Tier
- AWS SES Delivery Tracking (console setup for delivery webhook)
- Compiled Template Guide (layout, components, variables)
- Workflow & Template Features (project-driven recipients, multi-project format)
Procore / Project Groups
- Procore Integration — Complete Guide (installation single/grouped, project groups, token storage, why no migration)
Other Features
- Heartbeats Dashboard (kiosk connectivity, queries, sample data)
Compiled Template Guide
Overview
The compiled template (@compiled_template) renders all dynamic components in a structured multi-row layout. This guide covers layout structure, component syntax, variable resolution, and display logic.
Layout Structure
The template uses a 4-row grid system with specific column assignments:
┌─────────────────────────────────────────────────────────────┐
│ ROW 1: HEADER (Fixed) │
│ [Project Name] [Check-in Volume] [Month] [Logo] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ROW 2: MORALE + CHARTS (4 columns) │
│ [Label] [Morale Gauges] [Charts] [Risk Indicators] │
│ (60px) (25%) (50%) (25%) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ROW 3: INSIGHTS (4 columns) │
│ [Label] [Col 0] [Col 1] [Col 2] [Col 3] │
│ (60px) (25%) (25%) (25%) (25%) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ROW 4: REGULAR LISTS (Dynamic columns) │
│ [Label] [List 1] [List 2] [List 3] [List 4] │
│ (60px) (25%) (25%) (25%) (25%) │
└─────────────────────────────────────────────────────────────┘
Display Logic: What Gets Displayed?
Dynamic components are displayed, but they use project variables as their data sources.
- ❌ Project variables are NOT displayed directly (e.g.,
{{this_months_morale}}by itself) - ✅ Dynamic components ARE displayed (e.g.,
{{GAUGE:Team Morale:this_months_morale:this_months_morale_delta}}) - ✅ Dynamic components pull data from project variables (the
value_varanddelta_varin component syntax)
How It Works
Project variables are raw data points that exist in the system:
- {{this_months_morale}} - Returns a number (e.g., "85")
- {{baseline_morale}} - Returns a number (e.g., "78")
- {{project_name}} - Returns a string (e.g., "My Project")
These are NOT displayed directly in the compiled template. They are data sources.
Dynamic components are visual elements that get displayed:
- {{GAUGE:Label:value_var:delta_var}} - Displays as a gauge with circular progress
- {{LIST:Label:value_var:delta_var}} - Displays as a list
- {{THEME:Label:value_var:delta_var}} - Displays as a theme component
- {{BAR:Label|config}} - Displays as a bar chart
- {{LINE:Label|config}} - Displays as a line chart
- {{PIE:Label|config}} - Displays as a pie chart
These ARE displayed in the compiled template.
The Connection:
{{GAUGE:Team Morale:this_months_morale:this_months_morale_delta}}
↑ ↑ ↑
Label value_var delta_var
(project variable) (project variable)
Row 1: Header (Fixed Content)
Location: Top row, full width Grid: Single row, no columns Content: Static header information
| Position | Content | Source |
|---|---|---|
| Left | Project Name + Report Type | delivery.project.name + delivery.comms_instance.template.template_type |
| Center-Left | Check-in Volume (51) + Change (38%) | Hardcoded values (placeholder) |
| Center-Right | Current Month | Date.current.strftime('%B %Y') |
| Right | PepTalk Logo | Static text |
Code Location: Lines 590-606 in _compiled_template.html.erb
Row 2: Morale + Charts (4 Columns)
Location: Second row
Grid: grid-template-columns: 60px 1fr 2fr 1fr (4 columns)
Total Width: 100% of container (max-width: 1160px)
Column 0: Vertical Label (60px)
- Content: Vertical text "MORALE"
- Style: Rotated text, blue background
- Code Location: Lines 610-612
Column 1: Morale Section (25% width)
- Content: Team Morale Gauge + Additional Gauges
Components:
- Primary Gauge: First gauge from
gaugesarray (excludinghighest_risk_dayandhighest_risk_hour) - Displays: Gauge value (e.g., 74)
- Shows: Delta percentage (e.g., "↑ 26% from last month")
- Additional Gauges: All remaining regular gauges (if any)
- Displayed as label-value pairs below the main gauge
- Primary Gauge: First gauge from
Gauge Selection Logic:
ruby risk_gauges = gauges.select { |g| %w[highest_risk_day highest_risk_hour].include?(g[:value_var]) } regular_gauges = gauges.reject { |g| %w[highest_risk_day highest_risk_hour].include?(g[:value_var]) } morale_gauge = regular_gauges.firstCode Location: Lines 613-674
Column 2: Charts Section (50% width)
- Content: All chart components stacked vertically
Components: All items from
chartsarray- Each chart rendered with:
- Chart title (from
chart[:label]or configtitle) - Chart.js canvas element
- Chart type: BAR, LINE, or PIE (from
chart[:component_type])
Chart Types Supported:
BAR- Bar chartLINE- Line chartPIE- Pie chartCHART- Generic (inferred from label)
Fallback: If no charts present, displays "Weekly Morale" placeholder chart
Code Location: Lines 676-704
Column 3: Risk Section (25% width)
- Content: Risk indicator gauges
Components: Gauges with
value_varof:highest_risk_day- Displays calendar icon + day namehighest_risk_hour- Displays clock icon + hour
Selection Logic:
ruby risk_gauges = gauges.select { |g| %w[highest_risk_day highest_risk_hour].include?(g[:value_var]) }Code Location: Lines 706-732
Row 3: Insights (4 Columns)
Location: Third row
Grid: grid-template-columns: 60px repeat(4, 1fr) (5 columns total)
Label Column: 60px vertical label "INSIGHTS"
Content Columns: 4 equal-width columns (25% each)
Column 0: Vertical Label (60px)
- Content: Vertical text "INSIGHTS"
- Code Location: Lines 797-799
Column 1 (Index 0): "What Could Be Better"
- Component Type: LIST
- Variable:
value_var == 'what_could_be_better' - Title: "What Could Be Better"
- Data Structure:
ruby { type: 'list', data: { value_var: 'what_could_be_better', value: Array of hashes [ { label: "Answer 1", percentage: 45.5, count: 10, total_responses: 22 }, { label: "Answer 2", percentage: 31.8, count: 7, total_responses: 22 } ], label: "What Could Be Better" }, title: 'What Could Be Better' } - Rendering: Feedback list with numbered items, percentages, and progress bars
- Code Location: Lines 739, 754
Column 2 (Index 1): "What Is Going Well"
- Component Type: LIST
- Variable:
value_var == 'what_is_going_well' - Title: "What Is Going Well"
- Data Structure: Same as "What Could Be Better" but with positive styling
- Rendering: Feedback list with green styling for first item
- Code Location: Lines 740, 755
Column 3 (Index 2): Spotcheck Components
- Component Type: SPOTCHECK
- Variable: Components from
spotchecksarray - Data Structure:
ruby { type: 'spotcheck', data: { question: "Question text", items: Array of hashes [ { label: "Answer", percentage: 45.5, responses: 10 } ], start_date: Date, end_date: Date, display_options: { show_date_range: true, ... } }, title: "Spotcheck Title" } - Rendering: Shows question, top answer, and metadata (date range, response count)
- Distribution Logic: If other columns are empty, spotchecks fill them
- Code Location: Lines 748-750, 756, 759-775
Column 4 (Index 3): Action Tracker
- Component Type: LIST
- Variable:
value_var == 'action_tracker' - Title: From
list[:label]or "Actions Being Taken" - Data Structure:
ruby { type: 'action_tracker', data: { value_var: 'action_tracker', value: "Status|Category|Action\nStatus|Category|Action", # Newline-separated delta_var: "show_status=1|show_feedback=1", # Optional display options label: "Actions Being Taken" }, title: "Actions Being Taken" } - Rendering: Action items with status badges, category, and action text
- Code Location: Lines 741, 757
Column Distribution Logic
The template uses intelligent distribution to fill empty columns:
Primary Assignment:
- Column 0 → "What Could Be Better" (if exists)
- Column 1 → "What Is Going Well" (if exists)
- Column 2 → Spotcheck components (if any)
- Column 3 → Action Tracker (if exists)
Fallback Distribution:
- If any primary columns are empty AND spotchecks exist:
- Spotchecks are distributed to empty columns (starting with column 2)
- If themes exist:
- Themes fill empty columns first
- Then themes are distributed across all columns if needed
- If any primary columns are empty AND spotchecks exist:
Code Location: Lines 752-794
Row 4: Regular Lists (Dynamic Columns)
Location: Fourth row (only shown if regular lists exist)
Grid: grid-template-columns: 60px repeat(4, 1fr)
Content: All lists EXCEPT special lists (whatcouldbebetter, whatisgoingwell, action_tracker)
Column 0: Vertical Label (60px)
- Content: Vertical text "LISTS"
- Code Location: Lines 1031-1033
Columns 1-4: Regular List Components
- Component Type: LIST
Variables: All lists where
value_varis NOT:what_could_be_betterwhat_is_going_wellaction_tracker
Selection Logic:
ruby regular_lists = lists.reject { |l| %w[what_could_be_better what_is_going_well action_tracker].include?(l[:value_var].to_s) }Data Structure:
ruby { type: 'list', data: { value_var: 'regular_list_name', value: "Item 1\nItem 2\nItem 3" # Newline or comma-separated string label: "List Label" }, title: "List Label" }Rendering: Simple numbered list (first 3 items, truncated to 25 chars)
Code Location: Lines 1028-1065
Team Morale Gauge Syntax
The Team Morale gauge in Row 2, Column 1 displays the first regular gauge component found in your template (excluding risk gauges).
Template Syntax
{{GAUGE:Label:value_var:delta_var}}
Syntax Breakdown
GAUGE- Component type (must be uppercase)Label- Display label (e.g., "Team Morale", "Current Morale")value_var- Variable name for the main valuedelta_var- Variable name for the delta/change value (optional)
Common Morale Variables
this_months_morale- Description: Current month's morale score (0-100)
- Resolved by:
resolve_this_months_moralemethod - Calculation: Uses
MoraleCalculationService.calculate_team_morale_from_submissions
baseline_morale- Description: Baseline morale score (0-100)
- Resolved by:
resolve_baseline_moralemethod - Calculation: Uses
MoraleCalculationService.calculate_baseline_morale_from_submissions - Baseline Range: Determined by
BaselineRangeCalculator.default_for_project
multi_project_average_morale- Description: Average morale across multiple projects for a stakeholder
- Resolved by:
resolve_multi_project_average_moralemethod - Note: Falls back to
this_months_moraleif only one project
Delta Variables (Optional)
Delta variables show the change/trend. Common patterns:
- this_months_morale_delta - Change in current month's morale
- baseline_morale_delta - Change in baseline morale
- Any other variable that returns a percentage change
Example Syntax
Example 1: Current Month's Morale
{{GAUGE:Team Morale:this_months_morale:this_months_morale_delta}}
Example 2: Baseline Morale
{{GAUGE:Baseline Morale:baseline_morale:baseline_morale_delta}}
Example 3: Without Delta
{{GAUGE:Team Morale:this_months_morale}}
How It Works in Compiled Template
Selection Logic: ```ruby
Separate risk gauges from regular gauges
riskgauges = gauges.select { |g| %w[highestriskday highestriskhour].include?(g[:valuevar]) } regulargauges = gauges.reject { |g| %w[highestriskday highestriskhour].include?(g[:valuevar]) }
Use the FIRST regular gauge as the Team Morale gauge
moralegauge = regulargauges.present? && regulargauges.any? ? regulargauges.first : nil ```
Key Points:
1. First Gauge Wins: The first regular gauge in your template becomes the "Team Morale" gauge
2. Risk Gauges Excluded: Gauges with value_var of highest_risk_day or highest_risk_hour are excluded
3. Fallback Value: If no gauges are found, defaults to value 74 and delta "↓ 26%"
Display Logic:
Once selected, the gauge displays:
- Value: morale_gauge[:value].to_i (converted to integer, 0-100)
- Delta: morale_gauge[:delta] (string like "↑ 26%" or "↓ 15%")
- Gauge Circle: Visual percentage based on (value / 100 * 75).round(1)
Additional Gauges: If you have multiple regular gauges in your template, the first one becomes the main "Team Morale" gauge, and the rest are displayed as additional gauges below it.
Morale Variables - Data Wiring
this_months_morale
Date Range:
- Uses: current_month_date_range = today.beginning_of_month..today.end_of_month
- Note: The range is the full month, but only submissions that exist up to the current day are included (since future submissions don't exist yet)
- Effectively: Start of month to current day
Code Location: app/services/template_variable_resolver_service.rb:376-395
Formula Used:
- Calls MoraleCalculationService.calculate_team_morale_from_submissions
- For dates >= 2025-01-01: Uses weighted formula
- Score 5 (energised): 1.5x weight
- Score 4 (relaxed): 1.0x weight
- Score 3 (indifferent): 0.5x weight
- Score 1-2 (anxious/uncertain): 0x weight (don't count)
- Formula: ((energised × 1.5) + (relaxed × 1.0) + (indifferent × 0.5)) / total × 100
- For dates < 2025-01-01: Legacy formula
- Only scores 5 and 4 count
- Formula: (energised + relaxed) / total × 100
baseline_morale
Configuration Source:
- Uses DashboardSetting.baseline_calculation_period from the project's dashboard_setting
- Maps period to range via DashboardSetting::PERIOD_TO_RANGE:
- 0 → 'this_month_partial'
- 1 → 'last_month'
- 3 → 'last_3_months' (default)
- 6 → 'last_6_months'
- 12 → 'last_12_months'
Code Location: app/services/template_variable_resolver_service.rb:397-417
Formula Used:
- Routes to different calculation methods based on baseline_range:
- 'last_3_months' → calculate_3_month_baseline_morale
- Calculates morale for each of the last 3 months separately
- Each month uses appropriate formula (2025+ vs pre-2025)
- Averages the 3 monthly scores
- 'last_6_months' or 'last_12_months' → calculate_monthly_baseline_morale
- Calculates morale for each month in the range
- Averages all monthly scores
- 'this_month_partial' or 'last_month' → calculate_daily_baseline_morale
- Calculates morale for each day
- Averages all daily scores
multi_project_average_morale
Project Selection:
- Gets projects from project.dashboard_setting.benchmark_calculation_projects_array
- Filters to only active projects (status != 0)
- Falls back to resolve_this_months_morale if no benchmark projects configured
Code Location: app/services/template_variable_resolver_service.rb:456-486
Current Behavior:
- Uses benchmark projects from dashboard_setting.benchmark_calculation_projects
- Uses baselinecalculationperiod from dashboard_setting to determine date range
- Calculates team morale for each benchmark project using the baseline date range
- Averages all project morale scores
Formula Used:
- Uses calculate_team_morale_from_submissions for each project
- Date range determined by baseline_calculation_period (e.g., 3 = last3months)
- Averages all project morale scores
Summary Table
| Variable | Date Range Source | Formula | Configuration |
|---|---|---|---|
this_months_morale |
Current month (beginningofmonth..endofmonth) | calculate_team_morale_from_submissions |
None - always current month |
baseline_morale |
From dashboard_setting.baseline_calculation_period |
calculate_baseline_morale_from_submissions |
DashboardSetting::PERIOD_TO_RANGE mapping |
multi_project_average_morale |
From dashboard_setting.baseline_calculation_period for benchmark projects |
calculate_team_morale_from_submissions (per project, then average) |
Uses benchmark_calculation_projects and baseline_calculation_period |
Delta/Comparison Variables in Gauge Components
How Delta Variables Work
Template Syntax:
{{GAUGE:Label:value_var:delta_var}}
Example:
{{GAUGE:test:baseline_morale:engagement_rate}}
What Gets Fetched
Both variables are fetched independently:
Value Variable (
value_var):baseline_morale- Fetches: Baseline morale calculation
- Data source:
resolve_baseline_morale - Returns: String number (e.g., "78")
Delta Variable (
delta_var):engagement_rate- Fetches: Engagement rate calculation
- Data source:
resolve_engagement_rate - Returns: String number (e.g., "85")
Code Location: app/services/template_variable_resolver_service.rb:747-755
value = resolve_single_variable(value_var) || '0' # Resolves baseline_morale
delta = resolve_single_variable(delta_var) || '0' # Resolves engagement_rate
What Gets Displayed
In Compiled Template (Row 2, Column 1)
Code Location: app/views/communication_center/dynamic_components/_compiled_template.html.erb:620-661
Display Structure:
1. Main Gauge Value: Shows the value_var result (e.g., "78" for baselinemorale)
2. Delta Display: Shows the `deltavar` result formatted with arrow and percentage
Delta Formatting Logic:
ruby
if morale_delta.present? && morale_delta.to_s != '0'
delta_value = morale_delta.to_s.gsub(/[^0-9.-]/, '').to_f rescue 0
delta_class = delta_value < 0 ? 'negative' : 'positive'
delta_arrow = delta_value < 0 ? '↓' : '↑'
delta_text = morale_delta.to_s.match?(/%/) ? morale_delta : "#{morale_delta.to_s.gsub('-', '')}%"
end
Display Output:
┌─────────────────┐
│ Team Morale │
│ 78 │ ← value_var (baseline_morale)
│ ↑ 85% │ ← delta_var (engagement_rate) formatted
│ from last month│
└─────────────────┘
Important Note: The text says "from last month" but this is hardcoded - it doesn't actually mean the delta is comparing to last month. It's just display text.
Delta Formatting Behavior
The system assumes delta_var contains a delta/change value and formats it accordingly:
Extracts numeric value:
ruby delta_value = delta.to_s.gsub(/[^0-9.-]/, '').to_fDetermines arrow direction:
- Positive number →
↑(up arrow) - Negative number →
↓(down arrow) - Zero → No arrow
- Positive number →
Formats as percentage:
ruby delta_text = delta.to_s.match?(/%/) ? delta : "#{delta}%"Displays:
- Positive:
↑ 85% - Negative:
↓ 15% - Zero:
(0%)
- Positive:
Problem with Non-Delta Variables
If you use a non-delta variable like engagement_rate as delta_var:
engagement_ratereturns "85" (not a change, just a value)- System formats it as:
↑ 85%(treats it as positive change) - Display shows: "↑ 85% from last month" (misleading - it's not a change)
This is misleading because:
- engagement_rate is not a delta/change value
- It's just another metric (average score converted to percentage)
- The arrow and "from last month" text suggest it's a change, but it's not
Intended vs Actual Usage
Intended Usage (Delta Variables):
Delta variables should represent changes/comparisons:
- this_months_morale_delta - Change in current month's morale
- baseline_morale_delta - Change in baseline morale
- Variables that return percentage changes (e.g., "↑ 5%", "↓ 10%")
Actual Usage (Any Variable):
Currently, ANY variable can be used as deltavar:
- `engagementrate- Works but misleading (not a delta)
-responsescount- Works but misleading (not a delta)
-baselinemorale` - Works but misleading (not a delta)
The system doesn't validate that delta_var is actually a delta value.
Component Data Structure Reference
Gauge Component
{
component_type: 'GAUGE',
label: "Team Morale",
value: 74, # Integer (0-100)
delta: "↑ 26%", # String with arrow and percentage
value_var: 'team_morale', # Original variable name
delta_var: 'team_morale_delta',
is_chart: false
}
Chart Component
{
component_type: 'BAR', # or 'LINE', 'PIE'
label: "Weekly Morale",
value: "title=Weekly Morale|color=green|legend=none|x=weekly",
delta: nil,
value_var: nil,
delta_var: nil,
is_chart: true
}
List Component (Regular)
{
component_type: 'LIST',
label: "List Label",
value: "Item 1\nItem 2\nItem 3", # String (newline or comma-separated)
delta: nil,
value_var: 'list_name',
delta_var: nil,
is_chart: false
}
List Component (Feedback - What Could Be Better / What Is Going Well)
{
component_type: 'LIST',
label: "What Could Be Better",
value: [ # Array of hashes
{ label: "Answer 1", percentage: 45.5, count: 10, total_responses: 22 },
{ label: "Answer 2", percentage: 31.8, count: 7, total_responses: 22 }
],
delta: nil,
value_var: 'what_could_be_better', # or 'what_is_going_well'
delta_var: nil,
is_chart: false
}
List Component (Action Tracker)
{
component_type: 'LIST',
label: "Actions Being Taken",
value: "Open|Category|Action text\nClosed|Category|Action text",
delta: nil,
value_var: 'action_tracker',
delta_var: "show_status=1|show_feedback=1", # Optional display options
is_chart: false
}
Theme Component
{
component_type: 'THEME',
label: "Theme Name",
value: "Theme description text",
delta: "↑ 15%", # Percentage change
value_var: 'theme_name',
delta_var: 'theme_delta',
is_chart: false
}
Spotcheck Component
{
component_type: 'SPOTCHECK',
label: "Spotcheck Label",
value_var: 'spotcheck',
question: "Question text?",
items: [ # Sorted by responses (descending)
{ label: "Answer 1", percentage: 45.5, responses: 10, total_responses: 22 },
{ label: "Answer 2", percentage: 31.8, responses: 7, total_responses: 22 }
],
top_answer: { label: "Answer 1", percentage: 45.5, responses: 10 },
start_date: Date,
end_date: Date,
display_options: {
show_date_range: true,
show_top_response_pct: true,
show_total_responses: true
}
}
Quick Reference: Component Placement
| Component Type | Row | Column | Variable Name | Notes |
|---|---|---|---|---|
| Gauges (Regular) | 2 | 1 (Morale) | Any (except risk) | First gauge = main morale, rest = additional |
| Gauges (Risk) | 2 | 3 (Risk) | highest_risk_day, highest_risk_hour |
Special risk indicators |
| Charts | 2 | 2 (Charts) | Any chart type | All charts stacked vertically |
| List (What Could Be Better) | 3 | 0 | what_could_be_better |
Feedback list with percentages |
| List (What Is Going Well) | 3 | 1 | what_is_going_well |
Feedback list with percentages |
| List (Action Tracker) | 3 | 3 | action_tracker |
Action items with status |
| Spotcheck | 3 | 2 (or distributed) | spotcheck |
Question + top answer |
| Theme | 3 | Any empty | Any theme variable | Fills empty columns |
| List (Regular) | 4 | 1-4 | Any other list | Excludes special lists above |
Variable Resolution Flow
Template Content Parsing:
- Service scans template for
{{COMPONENT_TYPE:...}}tokens - Example:
{{GAUGE:Team Morale:team_morale:team_morale_delta}
- Service scans template for
Token Parsing:
- Component type extracted (GAUGE, LIST, BAR, etc.)
- Label, valuevar, deltavar extracted
- Special cases identified (spotcheck feedback, action tracker, risk gauges)
Value Resolution:
- Standard variables →
TemplateVariableResolverService - Special cases → Specialized resolvers
- Values stored in component hash
- Standard variables →
Categorization:
- Components sorted into:
gauges,charts,lists,themes,spotchecks
- Components sorted into:
Template Rendering:
- Each category passed to template as separate arrays
- Template assigns components to specific rows/columns based on
value_var
Key Code Locations
- Service:
app/services/compiled_template_image_service.rb - Template:
app/views/communication_center/dynamic_components/_compiled_template.html.erb - Row 1 Header: Lines 590-606
- Row 2 Content: Lines 608-733
- Row 3 Insights: Lines 737-1025
- Row 4 Lists: Lines 1027-1065
- Component Distribution: Lines 738-794
- Variable Resolver:
app/services/template_variable_resolver_service.rbresolve_this_months_morale: Line 376resolve_baseline_morale: Line 397resolve_multi_project_average_morale: Line 456resolve_single_variable: Line 259
Summary
The compiled template uses a fixed 4-row layout with intelligent column distribution:
- Row 1: Fixed header (project info, stats, logo)
- Row 2: Morale gauges (left), Charts (center), Risk indicators (right)
- Row 3: Four insight columns (What Could Be Better, What Is Going Well, Spotchecks, Action Tracker) with fallback distribution
- Row 4: Regular lists in dynamic columns
Components are identified by their value_var attribute, which determines their placement in the grid. Special variables like what_could_be_better, action_tracker, and highest_risk_day have fixed positions, while regular components fill available space.
To display Team Morale in Row 2, Column 1:
1. Add a GAUGE component to your template
2. Use syntax: {{GAUGE:Label:value_var:delta_var}}
3. Common variables: this_months_morale, baseline_morale
4. The first regular gauge in your template becomes the Team Morale gauge
5. Additional regular gauges appear below it
Example:
{{GAUGE:Team Morale:this_months_morale:this_months_morale_delta}}
This will display in Row 2, Column 1 as the main Team Morale gauge with the current month's morale value and delta.