Add regional task templates API with climate zone lookup
Adds a new endpoint GET /api/tasks/templates/by-region/?zip= that resolves ZIP codes to IECC climate regions and returns relevant home maintenance task templates. Includes climate region model, region lookup service with tests, seed data for all 8 climate zones with 50+ templates, and OpenAPI spec. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -523,6 +523,39 @@ paths:
|
||||
items:
|
||||
$ref: '#/components/schemas/TaskTemplateResponse'
|
||||
|
||||
/tasks/templates/by-region/:
|
||||
get:
|
||||
tags: [Static Data]
|
||||
operationId: getTaskTemplatesByRegion
|
||||
summary: Get task templates for a climate region by state or ZIP code
|
||||
description: Returns templates matching the climate zone for a given US state abbreviation or ZIP code. At least one parameter is required. If both are provided, state takes priority.
|
||||
parameters:
|
||||
- name: state
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
example: MA
|
||||
description: US state abbreviation (e.g., MA, FL, TX)
|
||||
- name: zip
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
example: "02101"
|
||||
description: US ZIP code (resolved to state on the server)
|
||||
responses:
|
||||
'200':
|
||||
description: Regional templates for the climate zone
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/TaskTemplateResponse'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
|
||||
/tasks/templates/{id}/:
|
||||
get:
|
||||
tags: [Static Data]
|
||||
|
||||
@@ -131,7 +131,9 @@ func Migrate() error {
|
||||
&models.TaskPriority{},
|
||||
&models.TaskFrequency{},
|
||||
&models.ContractorSpecialty{},
|
||||
&models.TaskTemplate{}, // Task templates reference category and frequency
|
||||
&models.TaskTemplate{}, // Task templates reference category and frequency
|
||||
&models.ClimateRegion{}, // IECC climate regions for regional templates
|
||||
&models.ZipClimateRegion{}, // ZIP to climate region lookup
|
||||
|
||||
// User and auth tables
|
||||
&models.User{},
|
||||
|
||||
@@ -21,6 +21,8 @@ type TaskTemplateResponse struct {
|
||||
Tags []string `json:"tags"`
|
||||
DisplayOrder int `json:"display_order"`
|
||||
IsActive bool `json:"is_active"`
|
||||
RegionID *uint `json:"region_id,omitempty"`
|
||||
RegionName string `json:"region_name,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -63,6 +65,11 @@ func NewTaskTemplateResponse(t *models.TaskTemplate) TaskTemplateResponse {
|
||||
resp.Frequency = NewTaskFrequencyResponse(t.Frequency)
|
||||
}
|
||||
|
||||
if len(t.Regions) > 0 {
|
||||
resp.RegionID = &t.Regions[0].ID
|
||||
resp.RegionName = t.Regions[0].Name
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,24 @@ func (h *TaskTemplateHandler) GetTemplatesByCategory(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, templates)
|
||||
}
|
||||
|
||||
// GetTemplatesByRegion handles GET /api/tasks/templates/by-region/?state=XX or ?zip=12345
|
||||
// Returns templates specific to the user's climate region based on state abbreviation or ZIP code
|
||||
func (h *TaskTemplateHandler) GetTemplatesByRegion(c echo.Context) error {
|
||||
state := c.QueryParam("state")
|
||||
zip := c.QueryParam("zip")
|
||||
|
||||
if state == "" && zip == "" {
|
||||
return apperrors.BadRequest("error.state_or_zip_required")
|
||||
}
|
||||
|
||||
templates, err := h.templateService.GetByRegion(state, zip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, templates)
|
||||
}
|
||||
|
||||
// GetTemplate handles GET /api/tasks/templates/:id/
|
||||
// Returns a single template by ID
|
||||
func (h *TaskTemplateHandler) GetTemplate(c echo.Context) error {
|
||||
|
||||
@@ -98,6 +98,8 @@ var specEndpointsKMPSkips = map[routeKey]bool{
|
||||
{Method: "POST", Path: "/notifications/devices/"}: true, // KMP uses /notifications/devices/register/
|
||||
{Method: "POST", Path: "/notifications/devices/unregister/"}: true, // KMP uses DELETE on device ID
|
||||
{Method: "PATCH", Path: "/notifications/preferences/"}: true, // KMP uses PUT
|
||||
// Regional templates — not yet implemented in KMP (planned)
|
||||
{Method: "GET", Path: "/tasks/templates/by-region/"}: true,
|
||||
// Stripe web-only and server-to-server endpoints — not implemented in mobile KMP
|
||||
{Method: "POST", Path: "/subscription/checkout/"}: true, // Web-only (Stripe Checkout)
|
||||
{Method: "POST", Path: "/subscription/portal/"}: true, // Web-only (Stripe Customer Portal)
|
||||
|
||||
28
internal/models/climate_region.go
Normal file
28
internal/models/climate_region.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package models
|
||||
|
||||
// ClimateRegion represents an IECC climate zone for regional task templates
|
||||
type ClimateRegion struct {
|
||||
BaseModel
|
||||
Name string `gorm:"column:name;size:100;not null;uniqueIndex" json:"name"`
|
||||
ZoneNumber int `gorm:"column:zone_number;not null;index" json:"zone_number"`
|
||||
Description string `gorm:"column:description;type:text" json:"description"`
|
||||
IsActive bool `gorm:"column:is_active;default:true;index" json:"is_active"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for GORM
|
||||
func (ClimateRegion) TableName() string {
|
||||
return "task_climateregion"
|
||||
}
|
||||
|
||||
// ZipClimateRegion maps ZIP codes to climate regions (static lookup)
|
||||
type ZipClimateRegion struct {
|
||||
BaseModel
|
||||
ZipCode string `gorm:"column:zip_code;size:10;uniqueIndex;not null" json:"zip_code"`
|
||||
ClimateRegionID uint `gorm:"column:climate_region_id;index;not null" json:"climate_region_id"`
|
||||
ClimateRegion ClimateRegion `gorm:"foreignKey:ClimateRegionID" json:"climate_region,omitempty"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for GORM
|
||||
func (ZipClimateRegion) TableName() string {
|
||||
return "task_zipclimateregion"
|
||||
}
|
||||
@@ -13,7 +13,8 @@ type TaskTemplate struct {
|
||||
IconAndroid string `gorm:"column:icon_android;size:100" json:"icon_android"`
|
||||
Tags string `gorm:"column:tags;type:text" json:"tags"` // Comma-separated tags for search
|
||||
DisplayOrder int `gorm:"column:display_order;default:0" json:"display_order"`
|
||||
IsActive bool `gorm:"column:is_active;default:true;index" json:"is_active"`
|
||||
IsActive bool `gorm:"column:is_active;default:true;index" json:"is_active"`
|
||||
Regions []ClimateRegion `gorm:"many2many:task_tasktemplate_regions;" json:"regions,omitempty"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for GORM
|
||||
|
||||
@@ -104,6 +104,20 @@ func (r *TaskTemplateRepository) Count() (int64, error) {
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetByRegion returns active templates associated with a specific climate region
|
||||
func (r *TaskTemplateRepository) GetByRegion(regionID uint) ([]models.TaskTemplate, error) {
|
||||
var templates []models.TaskTemplate
|
||||
err := r.db.
|
||||
Preload("Category").
|
||||
Preload("Frequency").
|
||||
Preload("Regions").
|
||||
Joins("JOIN task_tasktemplate_regions ON task_tasktemplate_regions.task_template_id = task_tasktemplate.id").
|
||||
Where("task_tasktemplate_regions.climate_region_id = ? AND task_tasktemplate.is_active = ?", regionID, true).
|
||||
Order("task_tasktemplate.display_order ASC, task_tasktemplate.title ASC").
|
||||
Find(&templates).Error
|
||||
return templates, err
|
||||
}
|
||||
|
||||
// GetGroupedByCategory returns templates grouped by category name
|
||||
func (r *TaskTemplateRepository) GetGroupedByCategory() (map[string][]models.TaskTemplate, error) {
|
||||
templates, err := r.GetAll()
|
||||
|
||||
@@ -339,6 +339,7 @@ func setupPublicDataRoutes(api *echo.Group, residenceHandler *handlers.Residence
|
||||
templates.GET("/grouped/", taskTemplateHandler.GetTemplatesGrouped)
|
||||
templates.GET("/search/", taskTemplateHandler.SearchTemplates)
|
||||
templates.GET("/by-category/:category_id/", taskTemplateHandler.GetTemplatesByCategory)
|
||||
templates.GET("/by-region/", taskTemplateHandler.GetTemplatesByRegion)
|
||||
templates.GET("/:id/", taskTemplateHandler.GetTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
157
internal/services/region_lookup.go
Normal file
157
internal/services/region_lookup.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StateToClimateRegion maps US state abbreviations to IECC climate zone IDs.
|
||||
// Some states span multiple zones — this uses the dominant zone for the most populated areas.
|
||||
var StateToClimateRegion = map[string]uint{
|
||||
// Zone 1: Hot-Humid
|
||||
"HI": 1, "FL": 1, "LA": 1,
|
||||
// Zone 2: Hot-Dry / Hot-Humid mix
|
||||
"TX": 2, "AZ": 2, "NV": 2, "NM": 2,
|
||||
// Zone 3: Mixed-Humid
|
||||
"GA": 3, "SC": 3, "AL": 3, "MS": 3, "AR": 3, "NC": 3, "TN": 3, "OK": 3, "CA": 3,
|
||||
// Zone 4: Mixed
|
||||
"VA": 4, "KY": 4, "MO": 4, "KS": 4, "DE": 4, "MD": 4, "DC": 4, "WV": 4, "OR": 4,
|
||||
// Zone 5: Cold
|
||||
"NJ": 5, "PA": 5, "CT": 5, "RI": 5, "MA": 5, "OH": 5, "IN": 5, "IL": 5,
|
||||
"IA": 5, "NE": 5, "CO": 5, "UT": 5, "WA": 5, "ID": 5, "NY": 5, "MI": 5,
|
||||
// Zone 6: Very Cold
|
||||
"WI": 6, "MN": 6, "ND": 6, "SD": 6, "MT": 6, "WY": 6, "VT": 6, "NH": 6, "ME": 6,
|
||||
// Zone 8: Arctic
|
||||
"AK": 8,
|
||||
}
|
||||
|
||||
// GetClimateRegionIDByState returns the climate region ID for a US state abbreviation.
|
||||
// Returns 0 if the state is not found.
|
||||
func GetClimateRegionIDByState(state string) uint {
|
||||
regionID, ok := StateToClimateRegion[strings.ToUpper(strings.TrimSpace(state))]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return regionID
|
||||
}
|
||||
|
||||
// ZipToState maps a US ZIP code to a state abbreviation using the 3-digit ZIP prefix.
|
||||
// Returns empty string if the ZIP is invalid or unrecognized.
|
||||
func ZipToState(zip string) string {
|
||||
zip = strings.TrimSpace(zip)
|
||||
if len(zip) < 3 {
|
||||
return ""
|
||||
}
|
||||
prefix, err := strconv.Atoi(zip[:3])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ZIP prefix → state mapping (USPS ranges)
|
||||
switch {
|
||||
case prefix >= 10 && prefix <= 27:
|
||||
return "MA"
|
||||
case prefix >= 28 && prefix <= 29:
|
||||
return "RI"
|
||||
case prefix >= 30 && prefix <= 38:
|
||||
return "NH"
|
||||
case prefix >= 39 && prefix <= 49:
|
||||
return "ME"
|
||||
case prefix >= 50 && prefix <= 59:
|
||||
return "VT"
|
||||
case prefix >= 60 && prefix <= 69:
|
||||
return "CT"
|
||||
case prefix >= 70 && prefix <= 89:
|
||||
return "NJ"
|
||||
case prefix >= 100 && prefix <= 149:
|
||||
return "NY"
|
||||
case prefix >= 150 && prefix <= 196:
|
||||
return "PA"
|
||||
case prefix >= 197 && prefix <= 199:
|
||||
return "DE"
|
||||
case prefix >= 200 && prefix <= 205:
|
||||
return "DC"
|
||||
case prefix >= 206 && prefix <= 219:
|
||||
return "MD"
|
||||
case prefix >= 220 && prefix <= 246:
|
||||
return "VA"
|
||||
case prefix >= 247 && prefix <= 268:
|
||||
return "WV"
|
||||
case prefix >= 270 && prefix <= 289:
|
||||
return "NC"
|
||||
case prefix >= 290 && prefix <= 299:
|
||||
return "SC"
|
||||
case prefix >= 300 && prefix <= 319:
|
||||
return "GA"
|
||||
case prefix >= 320 && prefix <= 349:
|
||||
return "FL"
|
||||
case prefix >= 350 && prefix <= 369:
|
||||
return "AL"
|
||||
case prefix >= 370 && prefix <= 385:
|
||||
return "TN"
|
||||
case prefix >= 386 && prefix <= 397:
|
||||
return "MS"
|
||||
case prefix >= 400 && prefix <= 427:
|
||||
return "KY"
|
||||
case prefix >= 430 && prefix <= 458:
|
||||
return "OH"
|
||||
case prefix >= 460 && prefix <= 479:
|
||||
return "IN"
|
||||
case prefix >= 480 && prefix <= 499:
|
||||
return "MI"
|
||||
case prefix >= 500 && prefix <= 528:
|
||||
return "IA"
|
||||
case prefix >= 530 && prefix <= 549:
|
||||
return "WI"
|
||||
case prefix >= 550 && prefix <= 567:
|
||||
return "MN"
|
||||
case prefix >= 570 && prefix <= 577:
|
||||
return "SD"
|
||||
case prefix >= 580 && prefix <= 588:
|
||||
return "ND"
|
||||
case prefix >= 590 && prefix <= 599:
|
||||
return "MT"
|
||||
case prefix >= 600 && prefix <= 629:
|
||||
return "IL"
|
||||
case prefix >= 630 && prefix <= 658:
|
||||
return "MO"
|
||||
case prefix >= 660 && prefix <= 679:
|
||||
return "KS"
|
||||
case prefix >= 680 && prefix <= 693:
|
||||
return "NE"
|
||||
case prefix >= 700 && prefix <= 714:
|
||||
return "LA"
|
||||
case prefix >= 716 && prefix <= 729:
|
||||
return "AR"
|
||||
case prefix >= 730 && prefix <= 749:
|
||||
return "OK"
|
||||
case prefix >= 750 && prefix <= 799:
|
||||
return "TX"
|
||||
case prefix >= 800 && prefix <= 816:
|
||||
return "CO"
|
||||
case prefix >= 820 && prefix <= 831:
|
||||
return "WY"
|
||||
case prefix >= 832 && prefix <= 838:
|
||||
return "ID"
|
||||
case prefix >= 840 && prefix <= 847:
|
||||
return "UT"
|
||||
case prefix >= 850 && prefix <= 865:
|
||||
return "AZ"
|
||||
case prefix >= 870 && prefix <= 884:
|
||||
return "NM"
|
||||
case prefix >= 889 && prefix <= 898:
|
||||
return "NV"
|
||||
case prefix >= 900 && prefix <= 966:
|
||||
return "CA"
|
||||
case prefix >= 967 && prefix <= 968:
|
||||
return "HI"
|
||||
case prefix >= 970 && prefix <= 979:
|
||||
return "OR"
|
||||
case prefix >= 980 && prefix <= 994:
|
||||
return "WA"
|
||||
case prefix >= 995 && prefix <= 999:
|
||||
return "AK"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
81
internal/services/region_lookup_test.go
Normal file
81
internal/services/region_lookup_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetClimateRegionIDByState(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
state string
|
||||
expected uint
|
||||
}{
|
||||
{"Massachusetts → Cold (5)", "MA", 5},
|
||||
{"Florida → Hot-Humid (1)", "FL", 1},
|
||||
{"Arizona → Hot-Dry (2)", "AZ", 2},
|
||||
{"Alaska → Arctic (8)", "AK", 8},
|
||||
{"Minnesota → Very Cold (6)", "MN", 6},
|
||||
{"Tennessee → Mixed-Humid (3)", "TN", 3},
|
||||
{"Missouri → Mixed (4)", "MO", 4},
|
||||
{"Unknown state → 0", "XX", 0},
|
||||
{"Empty string → 0", "", 0},
|
||||
{"Lowercase input", "ma", 5},
|
||||
{"Whitespace trimmed", " TX ", 2},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := GetClimateRegionIDByState(tt.state)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestZipToState(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
zip string
|
||||
expected string
|
||||
}{
|
||||
{"Boston MA", "02101", "MA"},
|
||||
{"Miami FL", "33101", "FL"},
|
||||
{"Phoenix AZ", "85001", "AZ"},
|
||||
{"Anchorage AK", "99501", "AK"},
|
||||
{"New York NY", "10001", "NY"},
|
||||
{"Chicago IL", "60601", "IL"},
|
||||
{"Houston TX", "77001", "TX"},
|
||||
{"Denver CO", "80201", "CO"},
|
||||
{"DC", "20001", "DC"},
|
||||
{"Too short", "12", ""},
|
||||
{"Empty", "", ""},
|
||||
{"Non-numeric", "abcde", ""},
|
||||
{"Unrecognized prefix", "00100", ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ZipToState(tt.zip)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateToClimateRegion_AllStatesPresent(t *testing.T) {
|
||||
// All 50 states + DC should be in the map
|
||||
allStates := []string{
|
||||
"AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL", "GA",
|
||||
"HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD",
|
||||
"MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ",
|
||||
"NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC",
|
||||
"SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY",
|
||||
"DC",
|
||||
}
|
||||
|
||||
for _, state := range allStates {
|
||||
t.Run(state, func(t *testing.T) {
|
||||
regionID := GetClimateRegionIDByState(state)
|
||||
assert.Greater(t, regionID, uint(0), "State %s should map to a region", state)
|
||||
assert.LessOrEqual(t, regionID, uint(8), "State %s region should be 1-8, got %d", state, regionID)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,26 @@ func (s *TaskTemplateService) GetByID(id uint) (*responses.TaskTemplateResponse,
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetByRegion returns templates for a specific climate region.
|
||||
// Accepts either a state abbreviation or ZIP code (state takes priority).
|
||||
// ZIP codes are resolved to a state via the ZipToState lookup.
|
||||
func (s *TaskTemplateService) GetByRegion(state, zip string) ([]responses.TaskTemplateResponse, error) {
|
||||
// Resolve ZIP to state if no state provided
|
||||
if state == "" && zip != "" {
|
||||
state = ZipToState(zip)
|
||||
}
|
||||
|
||||
regionID := GetClimateRegionIDByState(state)
|
||||
if regionID == 0 {
|
||||
return []responses.TaskTemplateResponse{}, nil
|
||||
}
|
||||
templates, err := s.templateRepo.GetByRegion(regionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return responses.NewTaskTemplateListResponse(templates), nil
|
||||
}
|
||||
|
||||
// Count returns the total count of active templates
|
||||
func (s *TaskTemplateService) Count() (int64, error) {
|
||||
return s.templateRepo.Count()
|
||||
|
||||
21
seeds/004_climate_regions.sql
Normal file
21
seeds/004_climate_regions.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
-- Seed the 8 IECC climate regions for regional task templates
|
||||
-- Run after 003_task_templates.sql
|
||||
|
||||
INSERT INTO task_climateregion (id, created_at, updated_at, name, zone_number, description, is_active)
|
||||
VALUES
|
||||
(1, NOW(), NOW(), 'Hot-Humid', 1, 'South Florida, Hawaii, Gulf Coast lowlands', true),
|
||||
(2, NOW(), NOW(), 'Hot-Dry', 2, 'Arizona, Southern Nevada, West Texas, Southern California deserts', true),
|
||||
(3, NOW(), NOW(), 'Mixed-Humid', 3, 'Mid-Atlantic, North Carolina, Tennessee, Arkansas', true),
|
||||
(4, NOW(), NOW(), 'Mixed-Dry', 4, 'Kansas, Missouri, Southern Illinois, Central Virginia', true),
|
||||
(5, NOW(), NOW(), 'Cold', 5, 'Northern Illinois, Michigan, New York, Southern New England', true),
|
||||
(6, NOW(), NOW(), 'Very Cold', 6, 'Buffalo NY, Northern Ohio, Northern Wisconsin, Minnesota', true),
|
||||
(7, NOW(), NOW(), 'Subarctic', 7, 'Upper Peninsula MI, Northern Minnesota, Mountain regions', true),
|
||||
(8, NOW(), NOW(), 'Arctic', 8, 'Alaska, Far Northern regions', true)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
zone_number = EXCLUDED.zone_number,
|
||||
description = EXCLUDED.description,
|
||||
is_active = EXCLUDED.is_active,
|
||||
updated_at = NOW();
|
||||
|
||||
SELECT setval('task_climateregion_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM task_climateregion), false);
|
||||
486
seeds/005_regional_templates.sql
Normal file
486
seeds/005_regional_templates.sql
Normal file
@@ -0,0 +1,486 @@
|
||||
-- Region-specific task templates
|
||||
-- Run after 004_climate_regions.sql
|
||||
-- These extend the existing 66 global templates (IDs 1-66) with region-specific tasks.
|
||||
-- Existing global templates remain region-agnostic (shown to everyone by default).
|
||||
--
|
||||
-- Category IDs (from 001_lookups.sql):
|
||||
-- 1 = Appliances, 2 = Cleaning, 3 = Electrical, 4 = Exterior
|
||||
-- 5 = General, 6 = HVAC, 7 = Interior, 8 = Pest Control, 9 = Plumbing, 10 = Safety
|
||||
--
|
||||
-- Frequency IDs (from 001_lookups.sql):
|
||||
-- 1 = Once, 2 = Daily, 3 = Weekly, 4 = Bi-Weekly, 5 = Monthly
|
||||
-- 6 = Quarterly, 7 = Semi-Annually, 8 = Annually
|
||||
|
||||
INSERT INTO task_tasktemplate (id, created_at, updated_at, title, description, category_id, frequency_id, icon_ios, icon_android, tags, display_order, is_active)
|
||||
VALUES
|
||||
-- =============================================
|
||||
-- ZONE 1: Hot-Humid (South FL, Hawaii, Gulf Coast)
|
||||
-- =============================================
|
||||
(100, NOW(), NOW(), 'Hurricane Season Prep',
|
||||
'Install storm shutters, trim deadwood, secure outdoor furniture, photo-document exterior for insurance',
|
||||
10, 7, 'hurricane', 'Storm', 'hurricane,storm,shutters,prep,tropical', 100, true),
|
||||
|
||||
(101, NOW(), NOW(), 'Inspect for Mold & Mildew',
|
||||
'Check under sinks, attic, crawl space, and bathrooms for mold growth. Address within 24 hours if found',
|
||||
2, 5, 'humidity.fill', 'WaterDamage', 'mold,mildew,humidity,inspection', 101, true),
|
||||
|
||||
(102, NOW(), NOW(), 'Check AC Condensate Drain',
|
||||
'Clear the AC condensate drain line to prevent water backup and mold growth in humid climate',
|
||||
6, 5, 'drop.degreesign.fill', 'WaterDamage', 'ac,condensate,drain,humidity', 102, true),
|
||||
|
||||
(103, NOW(), NOW(), 'Termite Inspection (Tropical)',
|
||||
'Professional inspection — tropical climates have year-round termite activity. Check wood structures, attic, crawl space',
|
||||
10, 7, 'ant.fill', 'BugReport', 'termite,tropical,pest,inspection', 103, true),
|
||||
|
||||
(104, NOW(), NOW(), 'Dehumidifier Maintenance',
|
||||
'Clean filter, check drain, verify indoor humidity stays between 30-50%. Critical in humid climates',
|
||||
6, 5, 'humidity.fill', 'Air', 'dehumidifier,humidity,filter,maintenance', 104, true),
|
||||
|
||||
(105, NOW(), NOW(), 'Storm Shutter Test',
|
||||
'Test all storm shutters for proper operation. Lubricate tracks, replace damaged hardware',
|
||||
10, 8, 'shield.lefthalf.filled', 'Shield', 'storm,shutter,hurricane,test', 105, true),
|
||||
|
||||
-- =============================================
|
||||
-- ZONE 2: Hot-Dry (AZ, NV, TX, desert SW)
|
||||
-- =============================================
|
||||
(110, NOW(), NOW(), 'Wildfire Defensible Space — Zone 0',
|
||||
'Clear combustible materials within 5 feet of house. Replace mulch with gravel, remove dead plants, clear roof and gutters of debris',
|
||||
4, 7, 'flame.fill', 'LocalFireDepartment', 'wildfire,defensible,fire,zone0', 110, true),
|
||||
|
||||
(111, NOW(), NOW(), 'Wildfire Defensible Space — Zone 1',
|
||||
'Within 5-30 feet: space tree canopies 10 ft apart, prune branches 6 ft from ground, clear all dead vegetation',
|
||||
4, 7, 'flame.fill', 'LocalFireDepartment', 'wildfire,defensible,fire,zone1,trees', 111, true),
|
||||
|
||||
(112, NOW(), NOW(), 'Inspect Roof for UV Damage',
|
||||
'Check shingles for cracks, brittleness, and curling caused by intense sun exposure. Desert sun ages roofs faster',
|
||||
4, 7, 'sun.max.fill', 'WbSunny', 'roof,uv,sun,shingles,damage', 112, true),
|
||||
|
||||
(113, NOW(), NOW(), 'Check for Scorpions & Desert Pests',
|
||||
'Inspect around exterior lights, under mulch, and entry points for scorpions and spiders. Seal gaps in foundation and doors',
|
||||
10, 6, 'ant.fill', 'BugReport', 'scorpion,pest,desert,inspection', 113, true),
|
||||
|
||||
(114, NOW(), NOW(), 'Dust AC Condenser Unit',
|
||||
'Clear accumulated dust and sand from outdoor AC unit. Clean condenser fins. Desert environments clog units faster',
|
||||
6, 5, 'fan', 'AcUnit', 'ac,dust,condenser,desert,sand', 114, true),
|
||||
|
||||
(115, NOW(), NOW(), 'Check Foundation for Heat Cracks',
|
||||
'Inspect foundation for cracks caused by soil expansion and contraction from extreme temperature swings',
|
||||
4, 7, 'house.circle.fill', 'Foundation', 'foundation,cracks,heat,expansion', 115, true),
|
||||
|
||||
(170, NOW(), NOW(), 'Service Evaporative Cooler (Swamp Cooler)',
|
||||
'Replace pads, clean water reservoir, oil pump and motor bearings. Prepare for cooling season',
|
||||
6, 8, 'fan.and.light.ceiling.fill', 'AcUnit', 'swamp cooler,evaporative,pads,service', 170, true),
|
||||
|
||||
(171, NOW(), NOW(), 'Flush Water Heater (Hard Water)',
|
||||
'Drain and flush sediment from water heater tank. Desert regions have hard water that accelerates buildup',
|
||||
9, 7, 'drop.fill', 'WaterDamage', 'water heater,flush,sediment,hard water', 171, true),
|
||||
|
||||
(172, NOW(), NOW(), 'Inspect Stucco & Exterior Walls',
|
||||
'Check stucco or siding for cracks and gaps caused by UV exposure and temperature swings. Patch before monsoon season',
|
||||
4, 7, 'sun.max.fill', 'WbSunny', 'stucco,exterior,cracks,uv,walls', 172, true),
|
||||
|
||||
(173, NOW(), NOW(), 'Clean Solar Panels',
|
||||
'Remove dust, pollen, and debris from solar panels. Desert dust reduces efficiency by 15-25% if not cleaned regularly',
|
||||
3, 6, 'sun.max.fill', 'SolarPower', 'solar,panels,clean,dust,efficiency', 173, true),
|
||||
|
||||
(174, NOW(), NOW(), 'Check Pool Equipment & Chemistry',
|
||||
'Inspect pool pump, filter, and chemical levels. High evaporation in hot climates requires more frequent balancing',
|
||||
4, 5, 'drop.circle.fill', 'Pool', 'pool,pump,filter,chemistry,evaporation', 174, true),
|
||||
|
||||
(175, NOW(), NOW(), 'Inspect Irrigation System',
|
||||
'Check drip irrigation lines and sprinkler heads for leaks, clogs, and UV damage. Adjust watering schedule for season',
|
||||
4, 6, 'sprinkler.and.droplets.fill', 'Grass', 'irrigation,sprinkler,drip,water,landscape', 175, true),
|
||||
|
||||
(176, NOW(), NOW(), 'Replace HVAC Air Filter',
|
||||
'Replace AC air filter. Desert dust and pollen clog filters faster — change monthly during peak summer',
|
||||
6, 5, 'aqi.medium', 'Air', 'hvac,filter,air,dust,replacement', 176, true),
|
||||
|
||||
(177, NOW(), NOW(), 'Seal Windows & Doors for Heat',
|
||||
'Check and replace caulk and weather stripping around windows and doors. Prevents cool air loss and dust intrusion',
|
||||
7, 8, 'wind', 'Air', 'seal,windows,doors,caulk,weatherstrip,heat', 177, true),
|
||||
|
||||
(178, NOW(), NOW(), 'Inspect Attic Ventilation',
|
||||
'Verify attic vents and ridge vents are clear. Proper ventilation prevents attic temps from exceeding 150°F and damaging roof sheathing',
|
||||
4, 7, 'thermometer.sun.fill', 'Thermostat', 'attic,ventilation,heat,ridge vent', 178, true),
|
||||
|
||||
(179, NOW(), NOW(), 'Treat Wood Fence & Deck (UV Protection)',
|
||||
'Apply UV-resistant stain or sealant to wood fences, decks, and pergolas. Desert sun degrades untreated wood in 1-2 years',
|
||||
4, 8, 'sun.max.fill', 'Deck', 'fence,deck,wood,uv,stain,seal', 179, true),
|
||||
|
||||
(180, NOW(), NOW(), 'Service Garage Door in Extreme Heat',
|
||||
'Lubricate springs, hinges, and rollers. Heat causes metal expansion — check alignment and weatherseal at bottom',
|
||||
5, 7, 'door.garage.open', 'Garage', 'garage,door,lubricate,heat,alignment', 180, true),
|
||||
|
||||
(181, NOW(), NOW(), 'Test Smoke & CO Detectors',
|
||||
'Test all smoke and carbon monoxide detectors. Replace batteries. Extreme heat can reduce battery life faster',
|
||||
10, 7, 'smoke.fill', 'SmokeFree', 'smoke,detector,co,battery,safety', 181, true),
|
||||
|
||||
-- =============================================
|
||||
-- ZONE 1 additional: Hot-Humid extras
|
||||
-- =============================================
|
||||
(190, NOW(), NOW(), 'Service Whole-House Dehumidifier',
|
||||
'Clean evaporator coils, check refrigerant, empty and clean drain pan. Critical for preventing mold in humid climates',
|
||||
6, 6, 'humidity.fill', 'Air', 'dehumidifier,coils,service,humidity', 190, true),
|
||||
|
||||
(191, NOW(), NOW(), 'Check Exterior Paint for Peeling',
|
||||
'Inspect exterior paint for peeling, bubbling, or mildew. Humidity causes paint failure faster than dry climates',
|
||||
4, 8, 'paintbrush.fill', 'FormatPaint', 'paint,exterior,peeling,humidity,mildew', 191, true),
|
||||
|
||||
(192, NOW(), NOW(), 'Clean Dryer Vent',
|
||||
'Remove lint buildup from dryer vent duct and exterior flap. Humidity makes lint stick and accumulate faster',
|
||||
1, 7, 'wind', 'LocalLaundryService', 'dryer,vent,lint,fire,safety', 192, true),
|
||||
|
||||
(193, NOW(), NOW(), 'Inspect Siding for Moisture Damage',
|
||||
'Check wood, vinyl, or fiber cement siding for swelling, warping, or rot. Look behind trim and at ground level',
|
||||
4, 8, 'house.fill', 'Home', 'siding,moisture,rot,inspection', 193, true),
|
||||
|
||||
(194, NOW(), NOW(), 'Flush Water Heater',
|
||||
'Drain and flush sediment from water heater. Humid climates accelerate mineral buildup',
|
||||
9, 8, 'drop.fill', 'WaterDamage', 'water heater,flush,sediment,maintenance', 194, true),
|
||||
|
||||
(195, NOW(), NOW(), 'Service Pool & Deck (Algae Prevention)',
|
||||
'Shock-treat pool, scrub deck for algae growth. Warm humid conditions cause rapid algae blooms',
|
||||
4, 5, 'drop.circle.fill', 'Pool', 'pool,deck,algae,shock,humidity', 195, true),
|
||||
|
||||
(196, NOW(), NOW(), 'Inspect Attic for Moisture & Ventilation',
|
||||
'Check attic for moisture, ensure soffit vents and ridge vents are clear. Look for mold on roof sheathing',
|
||||
4, 7, 'humidity.fill', 'Roofing', 'attic,moisture,ventilation,mold', 196, true),
|
||||
|
||||
(197, NOW(), NOW(), 'Test Sump Pump',
|
||||
'Test sump pump operation and check discharge line. Heavy tropical rains can overwhelm drainage',
|
||||
9, 6, 'drop.triangle.fill', 'WaterDamage', 'sump pump,test,flood,drainage', 197, true),
|
||||
|
||||
-- =============================================
|
||||
-- ZONE 3 additional: Mixed-Humid extras
|
||||
-- =============================================
|
||||
(200, NOW(), NOW(), 'Clean Dryer Vent',
|
||||
'Remove lint buildup from dryer vent and exterior flap. Reduce fire risk and improve drying efficiency',
|
||||
1, 7, 'wind', 'LocalLaundryService', 'dryer,vent,lint,fire,safety', 200, true),
|
||||
|
||||
(201, NOW(), NOW(), 'Service Lawn Mower for Season',
|
||||
'Change oil, replace spark plug, sharpen blade, clean air filter. Prepare for growing season',
|
||||
5, 8, 'leaf.fill', 'Grass', 'lawn,mower,service,season,maintenance', 201, true),
|
||||
|
||||
(202, NOW(), NOW(), 'Check Siding & Trim for Rot',
|
||||
'Inspect wood siding, fascia, and trim for soft spots and rot. Repair before rainy season',
|
||||
4, 8, 'house.fill', 'Home', 'siding,trim,rot,wood,repair', 202, true),
|
||||
|
||||
(203, NOW(), NOW(), 'Replace HVAC Air Filter',
|
||||
'Replace furnace/AC air filter. Changed climate means both heating and cooling seasons — replace more often',
|
||||
6, 6, 'aqi.medium', 'Air', 'hvac,filter,air,replacement', 203, true),
|
||||
|
||||
(204, NOW(), NOW(), 'Flush Water Heater',
|
||||
'Drain and flush sediment from water heater tank. Prevents efficiency loss and extends lifespan',
|
||||
9, 8, 'drop.fill', 'WaterDamage', 'water heater,flush,sediment,maintenance', 204, true),
|
||||
|
||||
(205, NOW(), NOW(), 'Aerate & Overseed Lawn',
|
||||
'Core aerate lawn and overseed bare patches in early fall. Best recovery window for mixed-humid lawns',
|
||||
4, 8, 'leaf.fill', 'Grass', 'aerate,overseed,lawn,fall', 205, true),
|
||||
|
||||
(206, NOW(), NOW(), 'Test Smoke & CO Detectors',
|
||||
'Test all smoke and carbon monoxide detectors. Replace batteries annually or when chirping',
|
||||
10, 7, 'smoke.fill', 'SmokeFree', 'smoke,detector,co,battery,safety', 206, true),
|
||||
|
||||
-- =============================================
|
||||
-- ZONE 4 additional: Mixed-Dry extras
|
||||
-- =============================================
|
||||
(210, NOW(), NOW(), 'Replace HVAC Air Filter',
|
||||
'Replace furnace/AC air filter. Dusty dry conditions and seasonal heating/cooling require frequent changes',
|
||||
6, 6, 'aqi.medium', 'Air', 'hvac,filter,air,dust,replacement', 210, true),
|
||||
|
||||
(211, NOW(), NOW(), 'Clean Dryer Vent',
|
||||
'Remove lint buildup from dryer vent and exterior flap. Fire prevention and efficiency',
|
||||
1, 7, 'wind', 'LocalLaundryService', 'dryer,vent,lint,fire,safety', 211, true),
|
||||
|
||||
(212, NOW(), NOW(), 'Flush Water Heater',
|
||||
'Drain and flush sediment from water heater. Hard water common in dry regions',
|
||||
9, 8, 'drop.fill', 'WaterDamage', 'water heater,flush,sediment,hard water', 212, true),
|
||||
|
||||
(213, NOW(), NOW(), 'Service Lawn & Irrigation',
|
||||
'Check irrigation heads, adjust watering schedule for season, reseed bare patches',
|
||||
4, 6, 'sprinkler.and.droplets.fill', 'Grass', 'irrigation,lawn,water,sprinkler', 213, true),
|
||||
|
||||
(214, NOW(), NOW(), 'Test Smoke & CO Detectors',
|
||||
'Test all smoke and carbon monoxide detectors. Replace batteries',
|
||||
10, 7, 'smoke.fill', 'SmokeFree', 'smoke,detector,co,battery,safety', 214, true),
|
||||
|
||||
(215, NOW(), NOW(), 'Clean Gutters & Downspouts',
|
||||
'Clear leaves and debris from gutters. Ensure downspouts direct water 4-6 ft from foundation',
|
||||
4, 7, 'cloud.rain.fill', 'Water', 'gutters,downspouts,clean,drainage', 215, true),
|
||||
|
||||
-- =============================================
|
||||
-- ZONE 5 additional: Cold extras
|
||||
-- =============================================
|
||||
(220, NOW(), NOW(), 'Replace HVAC Air Filter',
|
||||
'Replace furnace air filter. Long heating season means more frequent changes',
|
||||
6, 6, 'aqi.medium', 'Air', 'hvac,filter,air,furnace,replacement', 220, true),
|
||||
|
||||
(221, NOW(), NOW(), 'Clean Dryer Vent',
|
||||
'Remove lint buildup from dryer vent. Winter increases dryer use — clean more often',
|
||||
1, 7, 'wind', 'LocalLaundryService', 'dryer,vent,lint,fire,safety', 221, true),
|
||||
|
||||
(222, NOW(), NOW(), 'Flush Water Heater',
|
||||
'Drain and flush sediment from water heater tank',
|
||||
9, 8, 'drop.fill', 'WaterDamage', 'water heater,flush,sediment', 222, true),
|
||||
|
||||
(223, NOW(), NOW(), 'Test Smoke & CO Detectors',
|
||||
'Test all smoke and carbon monoxide detectors. Critical during heating season with furnace and fireplace use',
|
||||
10, 7, 'smoke.fill', 'SmokeFree', 'smoke,detector,co,battery,safety,heating', 223, true),
|
||||
|
||||
(224, NOW(), NOW(), 'Inspect & Clean Chimney',
|
||||
'Professional chimney sweep and inspection before heating season. Check for creosote buildup and flue damage',
|
||||
10, 8, 'flame.fill', 'Fireplace', 'chimney,sweep,creosote,flue,fireplace', 224, true),
|
||||
|
||||
-- =============================================
|
||||
-- ZONE 6 additional: Very Cold extras
|
||||
-- =============================================
|
||||
(230, NOW(), NOW(), 'Flush Water Heater',
|
||||
'Drain and flush sediment from water heater. Extended heating season means heater works harder',
|
||||
9, 8, 'drop.fill', 'WaterDamage', 'water heater,flush,sediment', 230, true),
|
||||
|
||||
(231, NOW(), NOW(), 'Test Smoke & CO Detectors',
|
||||
'Test all detectors monthly during heating season. Furnace and fireplace use increases CO risk',
|
||||
10, 5, 'smoke.fill', 'SmokeFree', 'smoke,detector,co,battery,safety,heating', 231, true),
|
||||
|
||||
(232, NOW(), NOW(), 'Replace HVAC Air Filter',
|
||||
'Replace furnace filter. Extended heating season requires monthly changes November through April',
|
||||
6, 5, 'aqi.medium', 'Air', 'hvac,filter,air,furnace,winter', 232, true),
|
||||
|
||||
(233, NOW(), NOW(), 'Inspect & Clean Chimney',
|
||||
'Professional sweep before heating season. Heavy use demands annual inspection for creosote and cracks',
|
||||
10, 8, 'flame.fill', 'Fireplace', 'chimney,sweep,creosote,flue', 233, true),
|
||||
|
||||
-- =============================================
|
||||
-- ZONE 3: Mixed-Humid (Mid-Atlantic, mid-South)
|
||||
-- =============================================
|
||||
(120, NOW(), NOW(), 'Blow Out Irrigation System',
|
||||
'Drain and blow out sprinkler lines before first frost to prevent pipe freeze damage',
|
||||
4, 8, 'sprinkler.and.droplets.fill', 'Grass', 'irrigation,winterize,sprinkler,frost', 120, true),
|
||||
|
||||
(121, NOW(), NOW(), 'Insulate Exposed Pipes',
|
||||
'Add pipe insulation to exposed pipes under sinks, in crawl spaces, and in unheated areas before winter',
|
||||
9, 8, 'snowflake', 'AcUnit', 'pipes,insulation,freeze,crawlspace', 121, true),
|
||||
|
||||
(122, NOW(), NOW(), 'Check Foundation for Moisture',
|
||||
'Inspect basement or crawl space for standing water, moisture, and cracks after heavy rain events',
|
||||
4, 6, 'house.circle.fill', 'Foundation', 'foundation,moisture,basement,cracks', 122, true),
|
||||
|
||||
(123, NOW(), NOW(), 'Test Radon Level',
|
||||
'Use radon test kit or hire professional. Common concern in Mid-Atlantic region basements',
|
||||
10, 8, 'aqi.medium', 'Air', 'radon,test,safety,basement', 123, true),
|
||||
|
||||
(124, NOW(), NOW(), 'Spring Flood Assessment',
|
||||
'After spring thaw, check for water damage, inspect foundation for standing water, clean and extend downspouts 4-6 ft from foundation',
|
||||
4, 8, 'cloud.rain.fill', 'Water', 'flood,spring,foundation,downspout', 124, true),
|
||||
|
||||
-- =============================================
|
||||
-- ZONE 4: Mixed (Upper South, lower Midwest)
|
||||
-- =============================================
|
||||
(130, NOW(), NOW(), 'Winterize Exterior Faucets & Hoses',
|
||||
'Disconnect all garden hoses, shut off water supply to exterior faucets, and drain the lines completely',
|
||||
9, 8, 'snowflake', 'AcUnit', 'winterize,faucet,hose,freeze', 130, true),
|
||||
|
||||
(131, NOW(), NOW(), 'Check Attic Insulation Level',
|
||||
'Verify attic insulation is R-38 or higher. Add insulation if below threshold for energy efficiency and freeze prevention',
|
||||
7, 8, 'thermometer.snowflake', 'Thermostat', 'insulation,attic,r-value,energy', 131, true),
|
||||
|
||||
(132, NOW(), NOW(), 'Inspect Weather Stripping & Caulk',
|
||||
'Check all exterior doors and windows for worn seals. Replace weather stripping and re-caulk gaps to prevent drafts',
|
||||
4, 8, 'wind', 'Air', 'weatherstrip,caulk,seal,draft', 132, true),
|
||||
|
||||
(133, NOW(), NOW(), 'Seal Foundation Cracks',
|
||||
'Inspect foundation for cracks and seal with hydraulic cement. Prevents rodent entry and water infiltration',
|
||||
4, 8, 'house.circle.fill', 'Foundation', 'foundation,cracks,seal,rodent', 133, true),
|
||||
|
||||
(134, NOW(), NOW(), 'Test Heating System Before Winter',
|
||||
'Run heating system before peak demand. Check for strange noises, smells, or uneven heating. Schedule service if needed',
|
||||
6, 8, 'flame.fill', 'LocalFireDepartment', 'heating,furnace,test,winter', 134, true),
|
||||
|
||||
-- =============================================
|
||||
-- ZONE 5: Cold (Northeast, Great Lakes)
|
||||
-- =============================================
|
||||
(140, NOW(), NOW(), 'Roof Snow Raking',
|
||||
'Rake snow from roof within 24 hours of 12+ inch accumulation to prevent ice dams and structural stress',
|
||||
4, 3, 'snowflake', 'AcUnit', 'snow,roof,rake,ice dam,winter', 140, true),
|
||||
|
||||
(141, NOW(), NOW(), 'Ice Dam Prevention Check',
|
||||
'Inspect for ice dams forming at roof edge. Apply de-ice products if forming. Check attic ventilation is adequate',
|
||||
4, 3, 'snowflake.circle.fill', 'AcUnit', 'ice dam,roof,prevention,winter', 141, true),
|
||||
|
||||
(142, NOW(), NOW(), 'Test Sump Pump & Backup',
|
||||
'Test sump pump operation. Verify battery backup is charged. Check discharge line is clear of ice and debris',
|
||||
9, 6, 'drop.triangle.fill', 'WaterDamage', 'sump pump,backup,basement,flood', 142, true),
|
||||
|
||||
(143, NOW(), NOW(), 'Winter Gutter Maintenance',
|
||||
'Clear gutters of ice and snow buildup 2-3x weekly during snow season. Check downspout extensions are clear',
|
||||
4, 3, 'cloud.snow.fill', 'AcUnit', 'gutter,ice,snow,winter,maintenance', 143, true),
|
||||
|
||||
(144, NOW(), NOW(), 'Check Foundation for Frost Heave',
|
||||
'Inspect basement and foundation for new cracks, settling, or water intrusion caused by freeze-thaw cycles',
|
||||
4, 8, 'house.circle.fill', 'Foundation', 'foundation,frost heave,cracks,settling', 144, true),
|
||||
|
||||
(145, NOW(), NOW(), 'Winterize Entire Plumbing System',
|
||||
'Insulate all exterior pipes, disconnect hoses, shut off exterior water valves, open cabinet doors under sinks for warm air circulation',
|
||||
9, 8, 'snowflake', 'AcUnit', 'winterize,plumbing,pipes,insulate', 145, true),
|
||||
|
||||
-- =============================================
|
||||
-- ZONE 6: Very Cold (Upstate NY, northern Midwest)
|
||||
-- =============================================
|
||||
(150, NOW(), NOW(), 'Install/Check Heated Gutter System',
|
||||
'Verify heated gutter guards are operational before winter. Test heating elements and check for damage from previous season',
|
||||
4, 8, 'bolt.horizontal.fill', 'ElectricalServices', 'heated gutter,ice dam,winter,prevention', 150, true),
|
||||
|
||||
(151, NOW(), NOW(), 'Verify Pipe Routing (Interior Walls)',
|
||||
'Confirm all water pipes run through heated interior walls. Plan to relocate any exterior-wall pipes before winter',
|
||||
9, 8, 'wrench.and.screwdriver.fill', 'Build', 'pipes,interior,freeze,prevention', 151, true),
|
||||
|
||||
(152, NOW(), NOW(), 'Heavy Snow Roof Raking',
|
||||
'Mandatory after every 8+ inches of snow. Critical for preventing structural stress, ice dams, and roof collapse',
|
||||
4, 3, 'snowflake', 'AcUnit', 'snow,roof,rake,heavy,structural', 152, true),
|
||||
|
||||
(153, NOW(), NOW(), 'Weekly Ice Dam & Gutter Inspection',
|
||||
'During winter months, inspect gutters and roof edge weekly for ice dam formation. Apply de-icing as needed',
|
||||
4, 3, 'eye.fill', 'Visibility', 'ice dam,gutter,weekly,inspection,winter', 153, true),
|
||||
|
||||
-- =============================================
|
||||
-- ZONE 7-8: Subarctic / Arctic
|
||||
-- =============================================
|
||||
(160, NOW(), NOW(), 'Test Backup Heating System',
|
||||
'Test backup/secondary heating system monthly. Dual furnaces are standard in extreme climates — both must be operational',
|
||||
6, 5, 'flame.fill', 'LocalFireDepartment', 'backup,heating,furnace,test', 160, true),
|
||||
|
||||
(161, NOW(), NOW(), 'Maintain Basement Above 55°F',
|
||||
'Verify basement auxiliary heater maintains minimum 55°F at all times. Prevents pipe freeze and foundation damage',
|
||||
6, 5, 'thermometer.low', 'Thermostat', 'basement,heating,minimum,temperature', 161, true),
|
||||
|
||||
(162, NOW(), NOW(), 'Inspect Heat-Traced Pipes',
|
||||
'Check heat tape on any exposed or vulnerable pipes. Replace damaged sections. Verify thermostat operation',
|
||||
9, 6, 'bolt.horizontal.fill', 'ElectricalServices', 'heat trace,pipes,tape,freeze', 162, true),
|
||||
|
||||
(163, NOW(), NOW(), 'Verify Roof Structural Integrity for Snow Load',
|
||||
'Before winter, verify roof pitch supports heavy snow load (6:12 minimum). Check for sagging or previous damage',
|
||||
4, 8, 'house.fill', 'Roofing', 'roof,structure,snow load,pitch', 163, true)
|
||||
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
title = EXCLUDED.title,
|
||||
description = EXCLUDED.description,
|
||||
category_id = EXCLUDED.category_id,
|
||||
frequency_id = EXCLUDED.frequency_id,
|
||||
icon_ios = EXCLUDED.icon_ios,
|
||||
icon_android = EXCLUDED.icon_android,
|
||||
tags = EXCLUDED.tags,
|
||||
display_order = EXCLUDED.display_order,
|
||||
is_active = EXCLUDED.is_active,
|
||||
updated_at = NOW();
|
||||
|
||||
-- =============================================
|
||||
-- Associate templates with regions via join table
|
||||
-- =============================================
|
||||
|
||||
-- Zone 1: Hot-Humid templates
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(100, 1), (101, 1), (102, 1), (103, 1), (104, 1), (105, 1),
|
||||
(190, 1), (191, 1), (192, 1), (193, 1), (194, 1), (195, 1), (196, 1), (197, 1)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Zone 2: Hot-Dry templates
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(110, 2), (111, 2), (112, 2), (113, 2), (114, 2), (115, 2),
|
||||
(170, 2), (171, 2), (172, 2), (173, 2), (174, 2), (175, 2), (176, 2), (177, 2), (178, 2), (179, 2), (180, 2), (181, 2)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Zone 3: Mixed-Humid templates
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(120, 3), (121, 3), (122, 3), (123, 3), (124, 3),
|
||||
(200, 3), (201, 3), (202, 3), (203, 3), (204, 3), (205, 3), (206, 3)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Zone 4: Mixed templates
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(130, 4), (131, 4), (132, 4), (133, 4), (134, 4),
|
||||
(210, 4), (211, 4), (212, 4), (213, 4), (214, 4), (215, 4)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Zone 5: Cold templates
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(140, 5), (141, 5), (142, 5), (143, 5), (144, 5), (145, 5),
|
||||
(220, 5), (221, 5), (222, 5), (223, 5), (224, 5)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Zone 6: Very Cold templates (own + inherit zone 5 cold-weather tasks)
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(150, 6), (151, 6), (152, 6), (153, 6),
|
||||
(230, 6), (231, 6), (232, 6), (233, 6),
|
||||
(140, 6), (141, 6), (142, 6), (143, 6), (144, 6), (145, 6),
|
||||
(220, 6), (221, 6), (222, 6), (223, 6), (224, 6)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Zone 7: Subarctic (own + inherit zone 5 + 6 tasks)
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(160, 7), (161, 7), (162, 7), (163, 7),
|
||||
(150, 7), (151, 7), (152, 7), (153, 7),
|
||||
(230, 7), (231, 7), (232, 7), (233, 7),
|
||||
(140, 7), (141, 7), (142, 7), (143, 7), (144, 7), (145, 7),
|
||||
(220, 7), (221, 7), (222, 7), (223, 7), (224, 7)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Zone 8: Arctic (own + inherit zone 5 + 6 + 7 tasks)
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(160, 8), (161, 8), (162, 8), (163, 8),
|
||||
(150, 8), (151, 8), (152, 8), (153, 8),
|
||||
(230, 8), (231, 8), (232, 8), (233, 8),
|
||||
(140, 8), (141, 8), (142, 8), (143, 8), (144, 8), (145, 8),
|
||||
(220, 8), (221, 8), (222, 8), (223, 8), (224, 8)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- =============================================
|
||||
-- Cross-tag existing global templates with regions where especially relevant
|
||||
-- =============================================
|
||||
|
||||
-- "Winterize Outdoor Faucets" (ID 13) — Zones 3-8 (freeze risk)
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(13, 3), (13, 4), (13, 5), (13, 6), (13, 7), (13, 8)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- "Clean Gutters" (ID 57) — All zones (universal)
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(57, 1), (57, 2), (57, 3), (57, 4), (57, 5), (57, 6), (57, 7), (57, 8)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- "Inspect Roof" (ID 59) — All zones (universal)
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(59, 1), (59, 2), (59, 3), (59, 4), (59, 5), (59, 6), (59, 7), (59, 8)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- "Termite Inspection" (ID 23) — Zones 1-3 (warm/humid)
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(23, 1), (23, 2), (23, 3)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- "Pest Control Treatment" (ID 24) — All zones
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(24, 1), (24, 2), (24, 3), (24, 4), (24, 5), (24, 6), (24, 7), (24, 8)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- "Schedule Chimney Cleaning" (ID 22) — Zones 4-8 (heating climates)
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(22, 4), (22, 5), (22, 6), (22, 7), (22, 8)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- "Service Sprinkler System" (ID 64) — Zones 3-6 (freeze winterization)
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(64, 3), (64, 4), (64, 5), (64, 6)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- "Inspect Weather Stripping" (ID 61) — Zones 4-8 (cold/energy efficiency)
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(61, 4), (61, 5), (61, 6), (61, 7), (61, 8)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- "Clean and Reverse Ceiling Fans" (ID 56) — All zones (seasonal direction change)
|
||||
INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES
|
||||
(56, 1), (56, 2), (56, 3), (56, 4), (56, 5), (56, 6), (56, 7), (56, 8)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Reset template sequence
|
||||
SELECT setval('task_tasktemplate_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM task_tasktemplate), false);
|
||||
Reference in New Issue
Block a user