|
|
|
|
@@ -177,577 +177,425 @@ func getAllReportDefinitions() map[string][]ReportDefinition {
|
|
|
|
|
return map[string][]ReportDefinition{
|
|
|
|
|
"users": {
|
|
|
|
|
{
|
|
|
|
|
ID: "volunteer_participation_rate", // get all the appointment(done, notdone, total) poll(done, not doen, total)
|
|
|
|
|
Name: "Volunteer participation rate",
|
|
|
|
|
Description: "Count of users grouped by their role",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.user_id,
|
|
|
|
|
u.first_name,
|
|
|
|
|
u.last_name,
|
|
|
|
|
COUNT(p.poll_id) AS total_polls,
|
|
|
|
|
COUNT(a.user_id) AS total_appointments,
|
|
|
|
|
case
|
|
|
|
|
WHEN COUNT(a.user_id) = 0 THEN NULL -- avoid division by zero
|
|
|
|
|
ELSE ROUND(CAST(COUNT(p.poll_id) AS numeric) / COUNT(a.user_id), 2)
|
|
|
|
|
END AS poll_to_appointment_rate
|
|
|
|
|
from users u
|
|
|
|
|
LEFT JOIN poll p ON u.user_id = p.user_id
|
|
|
|
|
LEFT JOIN appointment a ON u.user_id = a.user_id
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name;`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "volunteer_activity",
|
|
|
|
|
Name: "Volunteer Activity Summary",
|
|
|
|
|
Description: "Summary of volunteer activities including appointments and polls",
|
|
|
|
|
ID: "volunteer_participation_rate",
|
|
|
|
|
Name: "Volunteer Participation Rate",
|
|
|
|
|
Description: "Poll responses and donations collected by each volunteer/team lead",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as volunteer_name,
|
|
|
|
|
u.email,
|
|
|
|
|
COUNT(DISTINCT a.sched_id) as appointments_count,
|
|
|
|
|
COUNT(DISTINCT p.poll_id) as polls_created,
|
|
|
|
|
u.created_at as joined_date
|
|
|
|
|
FROM users u
|
|
|
|
|
LEFT JOIN appointment a ON u.user_id = a.user_id AND a.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
LEFT JOIN poll p ON u.user_id = p.user_id AND p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
WHERE u.role_id = 2
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name, u.email, u.created_at
|
|
|
|
|
ORDER BY appointments_count DESC, polls_created DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "team_performance",
|
|
|
|
|
Name: "Team Performance Report",
|
|
|
|
|
Description: "Performance metrics for each team",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
t.team_id,
|
|
|
|
|
ul.first_name || ' ' || ul.last_name as team_lead,
|
|
|
|
|
uv.first_name || ' ' || uv.last_name as volunteer,
|
|
|
|
|
COUNT(DISTINCT a.sched_id) as appointments,
|
|
|
|
|
COUNT(DISTINCT p.poll_id) as polls_created,
|
|
|
|
|
t.created_at as team_formed
|
|
|
|
|
FROM team t
|
|
|
|
|
LEFT JOIN users ul ON t.team_lead_id = ul.user_id
|
|
|
|
|
LEFT JOIN users uv ON t.volunteer_id = uv.user_id
|
|
|
|
|
LEFT JOIN appointment a ON uv.user_id = a.user_id AND a.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
LEFT JOIN poll p ON uv.user_id = p.user_id AND p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY t.team_id, ul.first_name, ul.last_name, uv.first_name, uv.last_name, t.created_at
|
|
|
|
|
ORDER BY appointments DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "admin_workload",
|
|
|
|
|
Name: "Admin Workload Analysis",
|
|
|
|
|
Description: "Workload distribution across admins",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as admin_name,
|
|
|
|
|
u.email,
|
|
|
|
|
COUNT(DISTINCT t.team_id) as teams_managed,
|
|
|
|
|
r.name as role,
|
|
|
|
|
COUNT(DISTINCT p.poll_id) as polls_created,
|
|
|
|
|
COUNT(DISTINCT pr.poll_response_id) as responses_collected,
|
|
|
|
|
COALESCE(SUM(pr.question6_donation_amount), 0) as total_donations,
|
|
|
|
|
COUNT(DISTINCT a.sched_id) as appointments_scheduled
|
|
|
|
|
FROM users u
|
|
|
|
|
LEFT JOIN team t ON u.user_id = t.team_lead_id
|
|
|
|
|
JOIN role r ON u.role_id = r.role_id
|
|
|
|
|
LEFT JOIN poll p ON u.user_id = p.user_id AND p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
LEFT JOIN poll_response pr ON p.poll_id = pr.poll_id
|
|
|
|
|
LEFT JOIN appointment a ON u.user_id = a.user_id AND a.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
WHERE u.role_id = 1
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name, u.email
|
|
|
|
|
ORDER BY teams_managed DESC, polls_created DESC`,
|
|
|
|
|
WHERE u.role_id IN (2, 3)
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name, u.email, r.name
|
|
|
|
|
ORDER BY responses_collected DESC, total_donations DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "inactive_users",
|
|
|
|
|
Name: "Inactive Users Report",
|
|
|
|
|
Description: "Users with no recent activity",
|
|
|
|
|
ID: "top_performing_volunteers",
|
|
|
|
|
Name: "Top-Performing Volunteers & Team Leads",
|
|
|
|
|
Description: "Volunteers ranked by responses collected and donations secured",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as user_name,
|
|
|
|
|
u.email,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN u.role_id = 1 THEN 'Admin'
|
|
|
|
|
WHEN u.role_id = 2 THEN 'Volunteer'
|
|
|
|
|
ELSE 'Unknown'
|
|
|
|
|
END as role,
|
|
|
|
|
u.created_at as joined_date,
|
|
|
|
|
COALESCE(MAX(a.created_at), MAX(p.created_at)) as last_activity
|
|
|
|
|
u.first_name || ' ' || u.last_name as volunteer_name,
|
|
|
|
|
r.name as role,
|
|
|
|
|
COUNT(DISTINCT pr.poll_response_id) as responses_collected,
|
|
|
|
|
COALESCE(SUM(pr.question6_donation_amount), 0) as donations_secured,
|
|
|
|
|
COUNT(DISTINCT p.poll_id) as polls_created,
|
|
|
|
|
AVG(pr.question6_donation_amount) as avg_donation_per_poll
|
|
|
|
|
FROM users u
|
|
|
|
|
LEFT JOIN appointment a ON u.user_id = a.user_id
|
|
|
|
|
LEFT JOIN poll p ON u.user_id = p.user_id
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name, u.email, u.role_id, u.created_at
|
|
|
|
|
HAVING COALESCE(MAX(a.created_at), MAX(p.created_at)) < ?1 OR COALESCE(MAX(a.created_at), MAX(p.created_at)) IS NULL
|
|
|
|
|
ORDER BY last_activity DESC`,
|
|
|
|
|
JOIN role r ON u.role_id = r.role_id
|
|
|
|
|
JOIN poll p ON u.user_id = p.user_id
|
|
|
|
|
LEFT JOIN poll_response pr ON p.poll_id = pr.poll_id
|
|
|
|
|
WHERE u.role_id IN (2, 3) AND p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name, r.name
|
|
|
|
|
HAVING COUNT(DISTINCT pr.poll_response_id) > 0
|
|
|
|
|
ORDER BY responses_collected DESC, donations_secured DESC
|
|
|
|
|
LIMIT 20`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "response_donation_ratio",
|
|
|
|
|
Name: "Response-to-Donation Ratio per Volunteer",
|
|
|
|
|
Description: "Efficiency measure showing donation amount per response collected",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as volunteer_name,
|
|
|
|
|
COUNT(DISTINCT pr.poll_response_id) as total_responses,
|
|
|
|
|
COALESCE(SUM(pr.question6_donation_amount), 0) as total_donations,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN COUNT(DISTINCT pr.poll_response_id) > 0
|
|
|
|
|
THEN COALESCE(SUM(pr.question6_donation_amount), 0) / COUNT(DISTINCT pr.poll_response_id)
|
|
|
|
|
ELSE 0
|
|
|
|
|
END as donation_per_response,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN COALESCE(SUM(pr.question6_donation_amount), 0) > 0
|
|
|
|
|
THEN COUNT(DISTINCT pr.poll_response_id) / COALESCE(SUM(pr.question6_donation_amount), 1)
|
|
|
|
|
ELSE COUNT(DISTINCT pr.poll_response_id)
|
|
|
|
|
END as responses_per_dollar
|
|
|
|
|
FROM users u
|
|
|
|
|
JOIN poll p ON u.user_id = p.user_id
|
|
|
|
|
LEFT JOIN poll_response pr ON p.poll_id = pr.poll_id
|
|
|
|
|
WHERE u.role_id IN (2, 3) AND p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name
|
|
|
|
|
HAVING COUNT(DISTINCT pr.poll_response_id) > 0
|
|
|
|
|
ORDER BY donation_per_response DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "user_address_coverage",
|
|
|
|
|
Name: "User Address Coverage",
|
|
|
|
|
Description: "Number of unique addresses covered by each volunteer/team lead",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as volunteer_name,
|
|
|
|
|
COUNT(DISTINCT p.address_id) as unique_addresses_polled,
|
|
|
|
|
COUNT(DISTINCT a.address_id) as unique_addresses_appointed,
|
|
|
|
|
COUNT(DISTINCT COALESCE(p.address_id, a.address_id)) as total_unique_addresses,
|
|
|
|
|
COUNT(DISTINCT pr.poll_response_id) as total_responses
|
|
|
|
|
FROM users u
|
|
|
|
|
LEFT JOIN poll p ON u.user_id = p.user_id AND p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
LEFT JOIN appointment a ON u.user_id = a.user_id AND a.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
LEFT JOIN poll_response pr ON p.poll_id = pr.poll_id
|
|
|
|
|
WHERE u.role_id IN (2, 3)
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name
|
|
|
|
|
HAVING COUNT(DISTINCT COALESCE(p.address_id, a.address_id)) > 0
|
|
|
|
|
ORDER BY total_unique_addresses DESC, total_responses DESC`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"addresses": {
|
|
|
|
|
{
|
|
|
|
|
ID: "coverage_by_area",
|
|
|
|
|
Name: "Coverage by Area",
|
|
|
|
|
Description: "Address coverage statistics by geographical area",
|
|
|
|
|
ID: "poll_responses_by_address",
|
|
|
|
|
Name: "Total Poll Responses by Address",
|
|
|
|
|
Description: "Shows engagement hotspots - addresses with most poll responses",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
COALESCE(NULLIF(TRIM(SPLIT_PART(address, ',', -1)), ''), 'Unknown') as area,
|
|
|
|
|
COUNT(*) as total_addresses,
|
|
|
|
|
COUNT(CASE WHEN visited_validated = true THEN 1 END) as visited_count,
|
|
|
|
|
ROUND(COUNT(CASE WHEN visited_validated = true THEN 1 END) * 100.0 / COUNT(*), 2) as coverage_percentage
|
|
|
|
|
FROM address_database
|
|
|
|
|
WHERE created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY area
|
|
|
|
|
ORDER BY total_addresses DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "visits_by_postal",
|
|
|
|
|
Name: "Visits by Postal Code",
|
|
|
|
|
Description: "Visit statistics grouped by postal code",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
COALESCE(NULLIF(TRIM(SUBSTRING(address FROM '[A-Za-z][0-9][A-Za-z] ?[0-9][A-Za-z][0-9]')), ''), 'No Postal Code') as postal_code,
|
|
|
|
|
COUNT(*) as addresses,
|
|
|
|
|
COUNT(CASE WHEN visited_validated = true THEN 1 END) as visited,
|
|
|
|
|
COUNT(CASE WHEN visited_validated = false THEN 1 END) as unvisited
|
|
|
|
|
FROM address_database
|
|
|
|
|
WHERE created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY postal_code
|
|
|
|
|
ORDER BY addresses DESC
|
|
|
|
|
ad.address,
|
|
|
|
|
ad.postal_code,
|
|
|
|
|
ad.street_quadrant,
|
|
|
|
|
COUNT(DISTINCT pr.poll_response_id) as total_responses,
|
|
|
|
|
COUNT(DISTINCT p.poll_id) as polls_conducted,
|
|
|
|
|
COALESCE(SUM(pr.question6_donation_amount), 0) as total_donations,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN COUNT(DISTINCT pr.poll_response_id) > 0
|
|
|
|
|
THEN COALESCE(SUM(pr.question6_donation_amount), 0) / COUNT(DISTINCT pr.poll_response_id)
|
|
|
|
|
ELSE 0
|
|
|
|
|
END as avg_donation_per_response
|
|
|
|
|
FROM address_database ad
|
|
|
|
|
JOIN poll p ON ad.address_id = p.address_id
|
|
|
|
|
LEFT JOIN poll_response pr ON p.poll_id = pr.poll_id
|
|
|
|
|
WHERE p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY ad.address_id, ad.address, ad.postal_code, ad.street_quadrant
|
|
|
|
|
ORDER BY total_responses DESC, total_donations DESC
|
|
|
|
|
LIMIT 50`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "unvisited_addresses",
|
|
|
|
|
Name: "Unvisited Addresses",
|
|
|
|
|
Description: "List of addresses that haven't been visited",
|
|
|
|
|
ID: "donations_by_address",
|
|
|
|
|
Name: "Total Donations by Address",
|
|
|
|
|
Description: "Shows financially supportive areas - addresses with highest donations",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
address_id,
|
|
|
|
|
address,
|
|
|
|
|
latitude,
|
|
|
|
|
longitude,
|
|
|
|
|
created_at as added_date
|
|
|
|
|
FROM address_database
|
|
|
|
|
WHERE visited_validated = false
|
|
|
|
|
AND created_at BETWEEN ?1 AND ?2
|
|
|
|
|
ORDER BY created_at DESC
|
|
|
|
|
LIMIT 100`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "donations_by_location",
|
|
|
|
|
Name: "Donations by Location",
|
|
|
|
|
Description: "Donation amounts grouped by address location",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
a.address,
|
|
|
|
|
COUNT(p.poll_id) as total_polls,
|
|
|
|
|
COALESCE(SUM(p.amount_donated), 0) as total_donations,
|
|
|
|
|
COALESCE(AVG(p.amount_donated), 0) as avg_donation
|
|
|
|
|
FROM address_database a
|
|
|
|
|
LEFT JOIN poll p ON a.address_id = p.address_id AND p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY a.address_id, a.address
|
|
|
|
|
HAVING COUNT(p.poll_id) > 0
|
|
|
|
|
ad.address,
|
|
|
|
|
ad.postal_code,
|
|
|
|
|
ad.street_quadrant,
|
|
|
|
|
COALESCE(SUM(pr.question6_donation_amount), 0) as total_donations,
|
|
|
|
|
COUNT(DISTINCT p.poll_id) as polls_conducted,
|
|
|
|
|
COUNT(DISTINCT pr.poll_response_id) as total_responses,
|
|
|
|
|
AVG(pr.question6_donation_amount) as avg_donation_per_poll
|
|
|
|
|
FROM address_database ad
|
|
|
|
|
JOIN poll p ON ad.address_id = p.address_id
|
|
|
|
|
LEFT JOIN poll_response pr ON p.poll_id = pr.poll_id
|
|
|
|
|
WHERE p.created_at BETWEEN ?1 AND ?2 AND pr.question6_donation_amount > 0
|
|
|
|
|
GROUP BY ad.address_id, ad.address, ad.postal_code, ad.street_quadrant
|
|
|
|
|
ORDER BY total_donations DESC
|
|
|
|
|
LIMIT 50`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "address_validation_status",
|
|
|
|
|
Name: "Address Validation Status",
|
|
|
|
|
Description: "Status of address validation across the database",
|
|
|
|
|
ID: "street_level_breakdown",
|
|
|
|
|
Name: "Street-Level Breakdown (Responses & Donations)",
|
|
|
|
|
Description: "Granular view for targeting - responses and donations by street",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
CASE
|
|
|
|
|
WHEN visited_validated = true THEN 'Validated'
|
|
|
|
|
WHEN visited_validated = false THEN 'Not Validated'
|
|
|
|
|
ELSE 'Unknown'
|
|
|
|
|
END as validation_status,
|
|
|
|
|
COUNT(*) as address_count,
|
|
|
|
|
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM address_database), 2) as percentage
|
|
|
|
|
FROM address_database
|
|
|
|
|
WHERE created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY visited_validated
|
|
|
|
|
ORDER BY address_count DESC`,
|
|
|
|
|
ad.street_name,
|
|
|
|
|
ad.street_type,
|
|
|
|
|
ad.street_quadrant,
|
|
|
|
|
COUNT(DISTINCT ad.address_id) as unique_addresses,
|
|
|
|
|
COUNT(DISTINCT pr.poll_response_id) as total_responses,
|
|
|
|
|
COALESCE(SUM(pr.question6_donation_amount), 0) as total_donations,
|
|
|
|
|
COUNT(DISTINCT p.poll_id) as polls_conducted
|
|
|
|
|
FROM address_database ad
|
|
|
|
|
LEFT JOIN poll p ON ad.address_id = p.address_id AND p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
LEFT JOIN poll_response pr ON p.poll_id = pr.poll_id
|
|
|
|
|
WHERE ad.street_name IS NOT NULL
|
|
|
|
|
GROUP BY ad.street_name, ad.street_type, ad.street_quadrant
|
|
|
|
|
HAVING COUNT(DISTINCT pr.poll_response_id) > 0 OR COALESCE(SUM(pr.question6_donation_amount), 0) > 0
|
|
|
|
|
ORDER BY total_responses DESC, total_donations DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "quadrant_summary",
|
|
|
|
|
Name: "Quadrant-Level Summary (NE, NW, SE, SW)",
|
|
|
|
|
Description: "Higher-level trend view by city quadrants",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
COALESCE(ad.street_quadrant, 'Unknown') as quadrant,
|
|
|
|
|
COUNT(DISTINCT ad.address_id) as unique_addresses,
|
|
|
|
|
COUNT(DISTINCT p.poll_id) as polls_conducted,
|
|
|
|
|
COUNT(DISTINCT pr.poll_response_id) as total_responses,
|
|
|
|
|
COALESCE(SUM(pr.question6_donation_amount), 0) as total_donations,
|
|
|
|
|
AVG(pr.question6_donation_amount) as avg_donation_per_poll,
|
|
|
|
|
COUNT(DISTINCT a.sched_id) as appointments_scheduled
|
|
|
|
|
FROM address_database ad
|
|
|
|
|
LEFT JOIN poll p ON ad.address_id = p.address_id AND p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
LEFT JOIN poll_response pr ON p.poll_id = pr.poll_id
|
|
|
|
|
LEFT JOIN appointment a ON ad.address_id = a.address_id AND a.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY ad.street_quadrant
|
|
|
|
|
ORDER BY total_responses DESC, total_donations DESC`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"appointments": {
|
|
|
|
|
{
|
|
|
|
|
ID: "appointments_by_day",
|
|
|
|
|
Name: "Appointments by Day",
|
|
|
|
|
Description: "Daily breakdown of appointment scheduling",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
appointment_date,
|
|
|
|
|
COUNT(*) as appointments_scheduled,
|
|
|
|
|
COUNT(DISTINCT user_id) as unique_volunteers,
|
|
|
|
|
COUNT(DISTINCT address_id) as unique_addresses
|
|
|
|
|
FROM appointment
|
|
|
|
|
WHERE appointment_date BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY appointment_date
|
|
|
|
|
ORDER BY appointment_date DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "completion_rates",
|
|
|
|
|
Name: "Completion Rates",
|
|
|
|
|
Description: "Appointment completion statistics by volunteer",
|
|
|
|
|
ID: "upcoming_appointments",
|
|
|
|
|
Name: "Upcoming Appointments per Volunteer/Team Lead",
|
|
|
|
|
Description: "Scheduling load - upcoming appointments by user",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as volunteer_name,
|
|
|
|
|
COUNT(a.sched_id) as total_appointments,
|
|
|
|
|
COUNT(CASE WHEN ad.visited_validated = true THEN 1 END) as completed_visits,
|
|
|
|
|
ROUND(COUNT(CASE WHEN ad.visited_validated = true THEN 1 END) * 100.0 / COUNT(a.sched_id), 2) as completion_rate
|
|
|
|
|
r.name as role,
|
|
|
|
|
COUNT(*) as upcoming_appointments,
|
|
|
|
|
MIN(a.appointment_date) as earliest_appointment,
|
|
|
|
|
MAX(a.appointment_date) as latest_appointment,
|
|
|
|
|
COUNT(CASE WHEN a.appointment_date = CURRENT_DATE THEN 1 END) as today_appointments,
|
|
|
|
|
COUNT(CASE WHEN a.appointment_date BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '7 days' THEN 1 END) as week_appointments
|
|
|
|
|
FROM appointment a
|
|
|
|
|
JOIN users u ON a.user_id = u.user_id
|
|
|
|
|
JOIN role r ON u.role_id = r.role_id
|
|
|
|
|
WHERE a.appointment_date >= CURRENT_DATE AND a.appointment_date BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name, r.name
|
|
|
|
|
ORDER BY upcoming_appointments DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "missed_vs_completed",
|
|
|
|
|
Name: "Missed vs Completed Appointments",
|
|
|
|
|
Description: "Reliability metric - appointment completion rates",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as volunteer_name,
|
|
|
|
|
COUNT(*) as total_appointments,
|
|
|
|
|
COUNT(CASE WHEN a.appointment_date < CURRENT_DATE THEN 1 END) as past_appointments,
|
|
|
|
|
COUNT(CASE WHEN a.appointment_date >= CURRENT_DATE THEN 1 END) as upcoming_appointments,
|
|
|
|
|
COUNT(CASE WHEN a.appointment_date < CURRENT_DATE AND EXISTS (
|
|
|
|
|
SELECT 1 FROM poll p WHERE p.user_id = a.user_id AND p.address_id = a.address_id
|
|
|
|
|
AND DATE(p.created_at) = a.appointment_date
|
|
|
|
|
) THEN 1 END) as completed_appointments,
|
|
|
|
|
COUNT(CASE WHEN a.appointment_date < CURRENT_DATE AND NOT EXISTS (
|
|
|
|
|
SELECT 1 FROM poll p WHERE p.user_id = a.user_id AND p.address_id = a.address_id
|
|
|
|
|
AND DATE(p.created_at) = a.appointment_date
|
|
|
|
|
) THEN 1 END) as missed_appointments,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN COUNT(CASE WHEN a.appointment_date < CURRENT_DATE THEN 1 END) > 0
|
|
|
|
|
THEN COUNT(CASE WHEN a.appointment_date < CURRENT_DATE AND EXISTS (
|
|
|
|
|
SELECT 1 FROM poll p WHERE p.user_id = a.user_id AND p.address_id = a.address_id
|
|
|
|
|
AND DATE(p.created_at) = a.appointment_date
|
|
|
|
|
) THEN 1 END) * 100.0 / COUNT(CASE WHEN a.appointment_date < CURRENT_DATE THEN 1 END)
|
|
|
|
|
ELSE 0
|
|
|
|
|
END as completion_rate_percent
|
|
|
|
|
FROM appointment a
|
|
|
|
|
JOIN users u ON a.user_id = u.user_id
|
|
|
|
|
LEFT JOIN address_database ad ON a.address_id = ad.address_id
|
|
|
|
|
WHERE a.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name
|
|
|
|
|
HAVING COUNT(a.sched_id) > 0
|
|
|
|
|
ORDER BY completion_rate DESC, total_appointments DESC`,
|
|
|
|
|
ORDER BY completion_rate_percent DESC, total_appointments DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "volunteer_schedules",
|
|
|
|
|
Name: "Volunteer Schedules",
|
|
|
|
|
Description: "Current volunteer scheduling overview",
|
|
|
|
|
ID: "appointments_by_quadrant",
|
|
|
|
|
Name: "Appointments by Quadrant/Region",
|
|
|
|
|
Description: "Geographic distribution of appointments",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
COALESCE(ad.street_quadrant, 'Unknown') as quadrant,
|
|
|
|
|
COUNT(*) as total_appointments,
|
|
|
|
|
COUNT(CASE WHEN a.appointment_date >= CURRENT_DATE THEN 1 END) as upcoming,
|
|
|
|
|
COUNT(CASE WHEN a.appointment_date < CURRENT_DATE THEN 1 END) as past,
|
|
|
|
|
COUNT(DISTINCT a.user_id) as unique_volunteers,
|
|
|
|
|
COUNT(DISTINCT a.address_id) as unique_addresses
|
|
|
|
|
FROM appointment a
|
|
|
|
|
JOIN address_database ad ON a.address_id = ad.address_id
|
|
|
|
|
WHERE a.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY ad.street_quadrant
|
|
|
|
|
ORDER BY total_appointments DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "scheduling_lead_time",
|
|
|
|
|
Name: "Average Lead Time (Scheduled vs Actual Date)",
|
|
|
|
|
Description: "Scheduling efficiency - time between scheduling and appointment",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as volunteer_name,
|
|
|
|
|
a.appointment_date,
|
|
|
|
|
a.appointment_time,
|
|
|
|
|
ad.address,
|
|
|
|
|
a.created_at as scheduled_date
|
|
|
|
|
COUNT(*) as total_appointments,
|
|
|
|
|
AVG(a.appointment_date - DATE(a.created_at)) as avg_lead_time_days,
|
|
|
|
|
MIN(a.appointment_date - DATE(a.created_at)) as min_lead_time_days,
|
|
|
|
|
MAX(a.appointment_date - DATE(a.created_at)) as max_lead_time_days,
|
|
|
|
|
COUNT(CASE WHEN a.appointment_date - DATE(a.created_at) < 1 THEN 1 END) as same_day_appointments,
|
|
|
|
|
COUNT(CASE WHEN a.appointment_date - DATE(a.created_at) BETWEEN 1 AND 7 THEN 1 END) as week_ahead_appointments
|
|
|
|
|
FROM appointment a
|
|
|
|
|
JOIN users u ON a.user_id = u.user_id
|
|
|
|
|
JOIN address_database ad ON a.address_id = ad.address_id
|
|
|
|
|
WHERE a.appointment_date BETWEEN ?1 AND ?2
|
|
|
|
|
ORDER BY a.appointment_date, a.appointment_time`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "missed_appointments",
|
|
|
|
|
Name: "Missed Appointments",
|
|
|
|
|
Description: "Appointments that were scheduled but addresses remain unvisited",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as volunteer_name,
|
|
|
|
|
a.appointment_date,
|
|
|
|
|
a.appointment_time,
|
|
|
|
|
ad.address,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN a.appointment_date < CURRENT_DATE THEN 'Overdue'
|
|
|
|
|
ELSE 'Upcoming'
|
|
|
|
|
END as status
|
|
|
|
|
FROM appointment a
|
|
|
|
|
JOIN users u ON a.user_id = u.user_id
|
|
|
|
|
JOIN address_database ad ON a.address_id = ad.address_id
|
|
|
|
|
WHERE ad.visited_validated = false
|
|
|
|
|
AND a.appointment_date BETWEEN ?1 AND ?2
|
|
|
|
|
ORDER BY a.appointment_date DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "peak_hours",
|
|
|
|
|
Name: "Peak Activity Hours",
|
|
|
|
|
Description: "Most popular appointment times",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
appointment_time,
|
|
|
|
|
COUNT(*) as appointment_count,
|
|
|
|
|
COUNT(DISTINCT user_id) as unique_volunteers
|
|
|
|
|
FROM appointment
|
|
|
|
|
WHERE appointment_date BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY appointment_time
|
|
|
|
|
ORDER BY appointment_count DESC`,
|
|
|
|
|
WHERE a.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name
|
|
|
|
|
HAVING COUNT(*) > 0
|
|
|
|
|
ORDER BY avg_lead_time_days ASC`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"polls": {
|
|
|
|
|
{
|
|
|
|
|
ID: "poll_creation_stats",
|
|
|
|
|
Name: "Poll Creation Statistics",
|
|
|
|
|
Description: "Overview of poll creation activity",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as creator_name,
|
|
|
|
|
COUNT(p.poll_id) as polls_created,
|
|
|
|
|
COUNT(CASE WHEN p.is_active = true THEN 1 END) as active_polls,
|
|
|
|
|
COALESCE(SUM(p.amount_donated), 0) as total_donations,
|
|
|
|
|
COALESCE(AVG(p.amount_donated), 0) as avg_donation_per_poll
|
|
|
|
|
FROM poll p
|
|
|
|
|
JOIN users u ON p.user_id = u.user_id
|
|
|
|
|
WHERE p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name
|
|
|
|
|
ORDER BY polls_created DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "donation_analysis",
|
|
|
|
|
Name: "Donation Analysis",
|
|
|
|
|
Description: "Detailed analysis of donation patterns",
|
|
|
|
|
ID: "response_distribution",
|
|
|
|
|
Name: "Response Distribution (Yes/No/Neutral)",
|
|
|
|
|
Description: "Outcome summary - distribution of poll responses",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
CASE
|
|
|
|
|
WHEN amount_donated = 0 THEN 'No Donation'
|
|
|
|
|
WHEN amount_donated BETWEEN 0.01 AND 25 THEN '$1 - $25'
|
|
|
|
|
WHEN amount_donated BETWEEN 25.01 AND 50 THEN '$26 - $50'
|
|
|
|
|
WHEN amount_donated BETWEEN 50.01 AND 100 THEN '$51 - $100'
|
|
|
|
|
ELSE 'Over $100'
|
|
|
|
|
END as donation_range,
|
|
|
|
|
COUNT(*) as poll_count,
|
|
|
|
|
COALESCE(SUM(amount_donated), 0) as total_amount,
|
|
|
|
|
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM poll WHERE created_at BETWEEN ?1 AND ?2), 2) as percentage
|
|
|
|
|
FROM poll
|
|
|
|
|
WHERE created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY donation_range
|
|
|
|
|
ORDER BY
|
|
|
|
|
CASE donation_range
|
|
|
|
|
WHEN 'No Donation' THEN 1
|
|
|
|
|
WHEN '$1 - $25' THEN 2
|
|
|
|
|
WHEN '$26 - $50' THEN 3
|
|
|
|
|
WHEN '$51 - $100' THEN 4
|
|
|
|
|
WHEN 'Over $100' THEN 5
|
|
|
|
|
END`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "active_vs_inactive",
|
|
|
|
|
Name: "Active vs Inactive Polls",
|
|
|
|
|
Description: "Comparison of active and inactive polls",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
CASE
|
|
|
|
|
WHEN is_active = true THEN 'Active'
|
|
|
|
|
ELSE 'Inactive'
|
|
|
|
|
END as poll_status,
|
|
|
|
|
COUNT(*) as poll_count,
|
|
|
|
|
COALESCE(SUM(amount_donated), 0) as total_donations,
|
|
|
|
|
COALESCE(AVG(amount_donated), 0) as avg_donation
|
|
|
|
|
FROM poll
|
|
|
|
|
WHERE created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY is_active
|
|
|
|
|
ORDER BY poll_count DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "poll_trends",
|
|
|
|
|
Name: "Poll Activity Trends",
|
|
|
|
|
Description: "Poll creation trends over time",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
DATE(created_at) as creation_date,
|
|
|
|
|
COUNT(*) as polls_created,
|
|
|
|
|
COUNT(CASE WHEN is_active = true THEN 1 END) as active_polls,
|
|
|
|
|
COALESCE(SUM(amount_donated), 0) as daily_donations
|
|
|
|
|
FROM poll
|
|
|
|
|
WHERE created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY DATE(created_at)
|
|
|
|
|
ORDER BY creation_date DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "creator_performance",
|
|
|
|
|
Name: "Creator Performance",
|
|
|
|
|
Description: "Performance metrics for poll creators",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as creator_name,
|
|
|
|
|
u.email,
|
|
|
|
|
COUNT(p.poll_id) as total_polls,
|
|
|
|
|
COALESCE(SUM(p.amount_donated), 0) as total_raised,
|
|
|
|
|
COALESCE(MAX(p.amount_donated), 0) as highest_donation,
|
|
|
|
|
COUNT(CASE WHEN p.is_active = true THEN 1 END) as active_polls
|
|
|
|
|
FROM users u
|
|
|
|
|
JOIN poll p ON u.user_id = p.user_id
|
|
|
|
|
WHERE p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name, u.email
|
|
|
|
|
ORDER BY total_raised DESC, total_polls DESC`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"responses": {
|
|
|
|
|
{
|
|
|
|
|
ID: "voter_status",
|
|
|
|
|
Name: "Voter Status Report",
|
|
|
|
|
Description: "Analysis of voter status from poll responses",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
voter_before as voted_before,
|
|
|
|
|
WHEN question1_voted_before = true AND question2_vote_again = true THEN 'Previous Voter - Will Vote Again'
|
|
|
|
|
WHEN question1_voted_before = true AND question2_vote_again = false THEN 'Previous Voter - Will Not Vote Again'
|
|
|
|
|
WHEN question1_voted_before = false AND question2_vote_again = true THEN 'New Voter - Will Vote'
|
|
|
|
|
WHEN question1_voted_before = false AND question2_vote_again = false THEN 'New Voter - Will Not Vote'
|
|
|
|
|
WHEN question1_voted_before IS NULL OR question2_vote_again IS NULL THEN 'Incomplete Response'
|
|
|
|
|
END as response_category,
|
|
|
|
|
COUNT(*) as response_count,
|
|
|
|
|
COUNT(CASE WHEN will_vote_again = true THEN 1 END) as will_vote_again_count,
|
|
|
|
|
ROUND(COUNT(CASE WHEN will_vote_again = true THEN 1 END) * 100.0 / COUNT(*), 2) as vote_again_percentage
|
|
|
|
|
COUNT(*) * 100.0 / (SELECT COUNT(*) FROM poll_response pr2
|
|
|
|
|
JOIN poll p2 ON pr2.poll_id = p2.poll_id
|
|
|
|
|
WHERE p2.created_at BETWEEN ?1 AND ?2) as percentage
|
|
|
|
|
FROM poll_response pr
|
|
|
|
|
JOIN poll p ON pr.poll_id = p.poll_id
|
|
|
|
|
WHERE p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY voter_before
|
|
|
|
|
GROUP BY response_category
|
|
|
|
|
ORDER BY response_count DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "sign_requests",
|
|
|
|
|
Name: "Sign Requests Summary",
|
|
|
|
|
Description: "Summary of lawn sign and banner requests",
|
|
|
|
|
ID: "average_poll_response",
|
|
|
|
|
Name: "Average Poll Response (Yes/No %)",
|
|
|
|
|
Description: "Overall sentiment - percentage breakdown of responses",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
'Lawn Signs' as sign_type,
|
|
|
|
|
SUM(lawn_sign) as total_requested,
|
|
|
|
|
SUM(CASE WHEN lawn_sign_status = 'delivered' THEN lawn_sign ELSE 0 END) as delivered,
|
|
|
|
|
SUM(CASE WHEN lawn_sign_status = 'cancelled' THEN lawn_sign ELSE 0 END) as cancelled
|
|
|
|
|
'Previous Voters' as voter_type,
|
|
|
|
|
COUNT(*) as total_responses,
|
|
|
|
|
COUNT(CASE WHEN question2_vote_again = true THEN 1 END) as positive_responses,
|
|
|
|
|
COUNT(CASE WHEN question2_vote_again = false THEN 1 END) as negative_responses,
|
|
|
|
|
COUNT(CASE WHEN question2_vote_again = true THEN 1 END) * 100.0 / COUNT(*) as positive_percentage
|
|
|
|
|
FROM poll_response pr
|
|
|
|
|
JOIN poll p ON pr.poll_id = p.poll_id
|
|
|
|
|
WHERE p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
WHERE p.created_at BETWEEN ?1 AND ?2 AND question1_voted_before = true
|
|
|
|
|
UNION ALL
|
|
|
|
|
SELECT
|
|
|
|
|
'Banner Signs' as sign_type,
|
|
|
|
|
SUM(banner_sign) as total_requested,
|
|
|
|
|
SUM(CASE WHEN banner_sign_status = 'delivered' THEN banner_sign ELSE 0 END) as delivered,
|
|
|
|
|
SUM(CASE WHEN banner_sign_status = 'cancelled' THEN banner_sign ELSE 0 END) as cancelled
|
|
|
|
|
'New Voters' as voter_type,
|
|
|
|
|
COUNT(*) as total_responses,
|
|
|
|
|
COUNT(CASE WHEN question2_vote_again = true THEN 1 END) as positive_responses,
|
|
|
|
|
COUNT(CASE WHEN question2_vote_again = false THEN 1 END) as negative_responses,
|
|
|
|
|
COUNT(CASE WHEN question2_vote_again = true THEN 1 END) * 100.0 / COUNT(*) as positive_percentage
|
|
|
|
|
FROM poll_response pr
|
|
|
|
|
JOIN poll p ON pr.poll_id = p.poll_id
|
|
|
|
|
WHERE p.created_at BETWEEN ?1 AND ?2 AND question1_voted_before = false
|
|
|
|
|
UNION ALL
|
|
|
|
|
SELECT
|
|
|
|
|
'Overall' as voter_type,
|
|
|
|
|
COUNT(*) as total_responses,
|
|
|
|
|
COUNT(CASE WHEN question2_vote_again = true THEN 1 END) as positive_responses,
|
|
|
|
|
COUNT(CASE WHEN question2_vote_again = false THEN 1 END) as negative_responses,
|
|
|
|
|
COUNT(CASE WHEN question2_vote_again = true THEN 1 END) * 100.0 / COUNT(*) as positive_percentage
|
|
|
|
|
FROM poll_response pr
|
|
|
|
|
JOIN poll p ON pr.poll_id = p.poll_id
|
|
|
|
|
WHERE p.created_at BETWEEN ?1 AND ?2`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "feedback_analysis",
|
|
|
|
|
Name: "Feedback Analysis",
|
|
|
|
|
Description: "Analysis of open-text feedback from responses",
|
|
|
|
|
ID: "donations_by_poll",
|
|
|
|
|
Name: "Donations by Poll",
|
|
|
|
|
Description: "Which polls drive donations - donation amounts per poll",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
LENGTH(thoughts) as feedback_length_category,
|
|
|
|
|
COUNT(*) as response_count
|
|
|
|
|
p.poll_id,
|
|
|
|
|
u.first_name || ' ' || u.last_name as creator_name,
|
|
|
|
|
ad.address,
|
|
|
|
|
pr.question6_donation_amount,
|
|
|
|
|
COUNT(pr.poll_response_id) as response_count,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN COUNT(pr.poll_response_id) > 0
|
|
|
|
|
THEN pr.question6_donation_amount / COUNT(pr.poll_response_id)
|
|
|
|
|
ELSE 0
|
|
|
|
|
END as donation_per_response,
|
|
|
|
|
p.created_at as poll_date
|
|
|
|
|
FROM poll p
|
|
|
|
|
JOIN users u ON p.user_id = u.user_id
|
|
|
|
|
JOIN address_database ad ON p.address_id = ad.address_id
|
|
|
|
|
LEFT JOIN poll_response pr ON p.poll_id = pr.poll_id
|
|
|
|
|
WHERE p.created_at BETWEEN ?1 AND ?2 AND pr.question6_donation_amount > 0
|
|
|
|
|
GROUP BY p.poll_id, u.first_name, u.last_name, ad.address, pr.question6_donation_amount, p.created_at
|
|
|
|
|
ORDER BY pr.question6_donation_amount DESC, response_count DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "response_donation_correlation",
|
|
|
|
|
Name: "Response-to-Donation Correlation",
|
|
|
|
|
Description: "Are positive responses linked to donations?",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
CASE
|
|
|
|
|
WHEN question2_vote_again = true THEN 'Will Vote Again'
|
|
|
|
|
WHEN question2_vote_again = false THEN 'Will Not Vote Again'
|
|
|
|
|
ELSE 'No Response'
|
|
|
|
|
END as response_type,
|
|
|
|
|
COUNT(*) as response_count,
|
|
|
|
|
COUNT(CASE WHEN pr.question6_donation_amount > 0 THEN 1 END) as responses_with_donations,
|
|
|
|
|
COALESCE(SUM(pr.question6_donation_amount), 0) as total_donations,
|
|
|
|
|
AVG(pr.question6_donation_amount) as avg_donation,
|
|
|
|
|
COUNT(CASE WHEN pr.question6_donation_amount > 0 THEN 1 END) * 100.0 / COUNT(*) as donation_rate_percent
|
|
|
|
|
FROM poll_response pr
|
|
|
|
|
JOIN poll p ON pr.poll_id = p.poll_id
|
|
|
|
|
WHERE p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
AND thoughts IS NOT NULL
|
|
|
|
|
AND TRIM(thoughts) != ''
|
|
|
|
|
GROUP BY
|
|
|
|
|
CASE
|
|
|
|
|
WHEN LENGTH(thoughts) <= 50 THEN 'Short (1-50 chars)'
|
|
|
|
|
WHEN LENGTH(thoughts) <= 150 THEN 'Medium (51-150 chars)'
|
|
|
|
|
ELSE 'Long (150+ chars)'
|
|
|
|
|
END
|
|
|
|
|
ORDER BY response_count DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "response_trends",
|
|
|
|
|
Name: "Response Trends",
|
|
|
|
|
Description: "Poll response trends over time",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
DATE(pr.created_at) as response_date,
|
|
|
|
|
COUNT(*) as responses,
|
|
|
|
|
COUNT(CASE WHEN voter_before = true THEN 1 END) as returning_voters,
|
|
|
|
|
COUNT(CASE WHEN will_vote_again = true THEN 1 END) as committed_future_voters
|
|
|
|
|
FROM poll_response pr
|
|
|
|
|
JOIN poll p ON pr.poll_id = p.poll_id
|
|
|
|
|
WHERE pr.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY DATE(pr.created_at)
|
|
|
|
|
ORDER BY response_date DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "repeat_voters",
|
|
|
|
|
Name: "Repeat Voters Analysis",
|
|
|
|
|
Description: "Analysis of voters who have responded to multiple polls",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
pr.name,
|
|
|
|
|
pr.email,
|
|
|
|
|
COUNT(DISTINCT pr.poll_id) as polls_responded,
|
|
|
|
|
SUM(CASE WHEN voter_before = true THEN 1 ELSE 0 END) as times_voted_before,
|
|
|
|
|
SUM(CASE WHEN will_vote_again = true THEN 1 ELSE 0 END) as times_will_vote_again
|
|
|
|
|
FROM poll_response pr
|
|
|
|
|
JOIN poll p ON pr.poll_id = p.poll_id
|
|
|
|
|
WHERE p.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY pr.name, pr.email
|
|
|
|
|
HAVING COUNT(DISTINCT pr.poll_id) > 1
|
|
|
|
|
ORDER BY polls_responded DESC`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"posts": {
|
|
|
|
|
{
|
|
|
|
|
ID: "posts_by_user",
|
|
|
|
|
Name: "Posts by User",
|
|
|
|
|
Description: "Post creation statistics by user",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as author_name,
|
|
|
|
|
u.email,
|
|
|
|
|
COUNT(po.post_id) as total_posts,
|
|
|
|
|
MIN(po.created_at) as first_post,
|
|
|
|
|
MAX(po.created_at) as latest_post
|
|
|
|
|
FROM users u
|
|
|
|
|
JOIN posts po ON u.user_id = po.user_id
|
|
|
|
|
WHERE po.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name, u.email
|
|
|
|
|
ORDER BY total_posts DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "engagement_timeline",
|
|
|
|
|
Name: "Engagement Timeline",
|
|
|
|
|
Description: "Post creation timeline",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
DATE(created_at) as post_date,
|
|
|
|
|
COUNT(*) as posts_created,
|
|
|
|
|
COUNT(DISTINCT user_id) as active_users
|
|
|
|
|
FROM posts
|
|
|
|
|
WHERE created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY DATE(created_at)
|
|
|
|
|
ORDER BY post_date DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "content_analysis",
|
|
|
|
|
Name: "Content Analysis",
|
|
|
|
|
Description: "Analysis of post content length and characteristics",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
CASE
|
|
|
|
|
WHEN LENGTH(content) <= 100 THEN 'Short (1-100 chars)'
|
|
|
|
|
WHEN LENGTH(content) <= 300 THEN 'Medium (101-300 chars)'
|
|
|
|
|
ELSE 'Long (300+ chars)'
|
|
|
|
|
END as content_length,
|
|
|
|
|
COUNT(*) as post_count,
|
|
|
|
|
ROUND(AVG(LENGTH(content)), 2) as avg_length
|
|
|
|
|
FROM posts
|
|
|
|
|
WHERE created_at BETWEEN ?1 AND ?2
|
|
|
|
|
AND content IS NOT NULL
|
|
|
|
|
GROUP BY content_length
|
|
|
|
|
ORDER BY post_count DESC`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "post_frequency",
|
|
|
|
|
Name: "Post Frequency Report",
|
|
|
|
|
Description: "Posting frequency patterns",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as author_name,
|
|
|
|
|
COUNT(*) as total_posts,
|
|
|
|
|
ROUND(COUNT(*) * 1.0 / GREATEST(1, EXTRACT(days FROM (?2::date - ?1::date))), 2) as posts_per_day,
|
|
|
|
|
DATE(MIN(po.created_at)) as first_post,
|
|
|
|
|
DATE(MAX(po.created_at)) as last_post
|
|
|
|
|
FROM posts po
|
|
|
|
|
JOIN users u ON po.user_id = u.user_id
|
|
|
|
|
WHERE po.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name
|
|
|
|
|
HAVING COUNT(*) > 1
|
|
|
|
|
ORDER BY posts_per_day DESC`,
|
|
|
|
|
GROUP BY question2_vote_again
|
|
|
|
|
ORDER BY total_donations DESC`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"availability": {
|
|
|
|
|
{
|
|
|
|
|
ID: "volunteer_availability",
|
|
|
|
|
Name: "Volunteer Availability",
|
|
|
|
|
Description: "Current volunteer availability schedules",
|
|
|
|
|
ID: "volunteer_availability_schedule",
|
|
|
|
|
Name: "Volunteer Availability by Date Range",
|
|
|
|
|
Description: "Who can work when - current volunteer availability schedules",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as volunteer_name,
|
|
|
|
|
u.email,
|
|
|
|
|
av.day_of_week,
|
|
|
|
|
av.start_time,
|
|
|
|
|
av.end_time,
|
|
|
|
|
EXTRACT(EPOCH FROM (av.end_time - av.start_time))/3600 as hours_available,
|
|
|
|
|
av.created_at as schedule_updated
|
|
|
|
|
FROM volunteer_availability av
|
|
|
|
|
FROM availability av
|
|
|
|
|
JOIN users u ON av.user_id = u.user_id
|
|
|
|
|
WHERE av.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
ORDER BY u.first_name, u.last_name, av.day_of_week, av.start_time`,
|
|
|
|
|
WHERE u.role_id IN (2, 3) AND av.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
ORDER BY u.first_name, u.last_name,
|
|
|
|
|
CASE av.day_of_week
|
|
|
|
|
WHEN 'Monday' THEN 1
|
|
|
|
|
WHEN 'Tuesday' THEN 2
|
|
|
|
|
WHEN 'Wednesday' THEN 3
|
|
|
|
|
WHEN 'Thursday' THEN 4
|
|
|
|
|
WHEN 'Friday' THEN 5
|
|
|
|
|
WHEN 'Saturday' THEN 6
|
|
|
|
|
WHEN 'Sunday' THEN 7
|
|
|
|
|
END, av.start_time`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "peak_availability",
|
|
|
|
|
Name: "Peak Availability Times",
|
|
|
|
|
Description: "Times when most volunteers are available",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
day_of_week,
|
|
|
|
|
start_time,
|
|
|
|
|
end_time,
|
|
|
|
|
COUNT(*) as volunteers_available
|
|
|
|
|
FROM volunteer_availability av
|
|
|
|
|
JOIN users u ON av.user_id = u.user_id
|
|
|
|
|
WHERE av.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY day_of_week, start_time, end_time
|
|
|
|
|
ORDER BY volunteers_available DESC, day_of_week, start_time`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "coverage_gaps",
|
|
|
|
|
Name: "Coverage Gaps",
|
|
|
|
|
Description: "Time periods with limited volunteer availability",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
day_of_week,
|
|
|
|
|
start_time,
|
|
|
|
|
end_time,
|
|
|
|
|
COUNT(*) as volunteers_available
|
|
|
|
|
FROM volunteer_availability av
|
|
|
|
|
WHERE av.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
GROUP BY day_of_week, start_time, end_time
|
|
|
|
|
HAVING COUNT(*) <= 2
|
|
|
|
|
ORDER BY volunteers_available ASC, day_of_week, start_time`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "schedule_conflicts",
|
|
|
|
|
Name: "Schedule Conflicts",
|
|
|
|
|
Description: "Appointments scheduled outside volunteer availability",
|
|
|
|
|
ID: "volunteer_fulfillment",
|
|
|
|
|
Name: "Volunteer Fulfillment (Available vs Actually Worked)",
|
|
|
|
|
Description: "Reliability measure - comparing availability to actual appointments",
|
|
|
|
|
SQL: `SELECT
|
|
|
|
|
u.first_name || ' ' || u.last_name as volunteer_name,
|
|
|
|
|
a.appointment_date,
|
|
|
|
|
a.appointment_time,
|
|
|
|
|
ad.address,
|
|
|
|
|
'No availability recorded' as conflict_reason
|
|
|
|
|
FROM appointment a
|
|
|
|
|
JOIN users u ON a.user_id = u.user_id
|
|
|
|
|
JOIN address_database ad ON a.address_id = ad.address_id
|
|
|
|
|
LEFT JOIN volunteer_availability av ON u.user_id = av.user_id
|
|
|
|
|
AND EXTRACT(dow FROM a.appointment_date) = av.day_of_week
|
|
|
|
|
AND a.appointment_time BETWEEN av.start_time AND av.end_time
|
|
|
|
|
WHERE a.appointment_date BETWEEN ?1 AND ?2
|
|
|
|
|
AND av.user_id IS NULL
|
|
|
|
|
ORDER BY a.appointment_date, a.appointment_time`,
|
|
|
|
|
COUNT(DISTINCT av.availability_id) as availability_slots,
|
|
|
|
|
SUM(EXTRACT(EPOCH FROM (av.end_time - av.start_time))/3600) as total_hours_available,
|
|
|
|
|
COUNT(DISTINCT a.sched_id) as appointments_scheduled,
|
|
|
|
|
COUNT(DISTINCT CASE WHEN a.appointment_date < CURRENT_DATE THEN a.sched_id END) as past_appointments,
|
|
|
|
|
COUNT(DISTINCT CASE WHEN a.appointment_date < CURRENT_DATE AND EXISTS (
|
|
|
|
|
SELECT 1 FROM poll p WHERE p.user_id = a.user_id AND p.address_id = a.address_id
|
|
|
|
|
AND DATE(p.created_at) = a.appointment_date
|
|
|
|
|
) THEN a.sched_id END) as completed_appointments,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN COUNT(DISTINCT CASE WHEN a.appointment_date < CURRENT_DATE THEN a.sched_id END) > 0
|
|
|
|
|
THEN COUNT(DISTINCT CASE WHEN a.appointment_date < CURRENT_DATE AND EXISTS (
|
|
|
|
|
SELECT 1 FROM poll p WHERE p.user_id = a.user_id AND p.address_id = a.address_id
|
|
|
|
|
AND DATE(p.created_at) = a.appointment_date
|
|
|
|
|
) THEN a.sched_id END) * 100.0 / COUNT(DISTINCT CASE WHEN a.appointment_date < CURRENT_DATE THEN a.sched_id END)
|
|
|
|
|
ELSE 0
|
|
|
|
|
END as fulfillment_rate_percent
|
|
|
|
|
FROM users u
|
|
|
|
|
LEFT JOIN availability av ON u.user_id = av.user_id AND av.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
LEFT JOIN appointment a ON u.user_id = a.user_id AND a.created_at BETWEEN ?1 AND ?2
|
|
|
|
|
WHERE u.role_id IN (2, 3)
|
|
|
|
|
GROUP BY u.user_id, u.first_name, u.last_name
|
|
|
|
|
HAVING COUNT(DISTINCT av.availability_id) > 0 OR COUNT(DISTINCT a.sched_id) > 0
|
|
|
|
|
ORDER BY fulfillment_rate_percent DESC, total_hours_available DESC`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|