/** * 안전 체크리스트 확장 마이그레이션 * * 1. tbm_safety_checks 테이블 확장 (check_type, weather_condition, task_id) * 2. weather_conditions 테이블 생성 (날씨 조건 코드) * 3. tbm_weather_records 테이블 생성 (세션별 날씨 기록) * 4. 초기 날씨별 체크항목 데이터 * * @since 2026-02-02 */ exports.up = function(knex) { return knex.schema // 1. tbm_safety_checks 테이블 확장 .alterTable('tbm_safety_checks', function(table) { table.enum('check_type', ['basic', 'weather', 'task']).defaultTo('basic').after('check_category'); table.string('weather_condition', 50).nullable().after('check_type'); table.integer('task_id').unsigned().nullable().after('weather_condition'); // 인덱스 추가 table.index('check_type'); table.index('weather_condition'); table.index('task_id'); }) // 2. weather_conditions 테이블 생성 .createTable('weather_conditions', function(table) { table.string('condition_code', 50).primary(); table.string('condition_name', 100).notNullable(); table.text('description').nullable(); table.string('icon', 50).nullable(); table.decimal('temp_threshold_min', 4, 1).nullable(); // 최소 기온 기준 table.decimal('temp_threshold_max', 4, 1).nullable(); // 최대 기온 기준 table.decimal('wind_threshold', 4, 1).nullable(); // 풍속 기준 (m/s) table.decimal('precip_threshold', 5, 1).nullable(); // 강수량 기준 (mm) table.boolean('is_active').defaultTo(true); table.integer('display_order').defaultTo(0); table.timestamp('created_at').defaultTo(knex.fn.now()); }) // 3. tbm_weather_records 테이블 생성 .createTable('tbm_weather_records', function(table) { table.increments('record_id').primary(); table.integer('session_id').unsigned().notNullable(); table.date('weather_date').notNullable(); table.decimal('temperature', 4, 1).nullable(); // 기온 (섭씨) table.integer('humidity').nullable(); // 습도 (%) table.decimal('wind_speed', 4, 1).nullable(); // 풍속 (m/s) table.decimal('precipitation', 5, 1).nullable(); // 강수량 (mm) table.string('sky_condition', 50).nullable(); // 하늘 상태 table.string('weather_condition', 50).nullable(); // 주요 날씨 상태 table.json('weather_conditions').nullable(); // 복수 조건 ['rain', 'wind'] table.string('data_source', 50).defaultTo('api'); // 데이터 출처 table.timestamp('fetched_at').nullable(); table.timestamp('created_at').defaultTo(knex.fn.now()); // 외래키 table.foreign('session_id').references('session_id').inTable('tbm_sessions').onDelete('CASCADE'); // 인덱스 table.index('weather_date'); table.unique(['session_id']); }) // 4. 초기 데이터 삽입 .then(function() { // 기존 체크항목을 'basic' 유형으로 업데이트 return knex('tbm_safety_checks').update({ check_type: 'basic' }); }) .then(function() { // 날씨 조건 코드 삽입 return knex('weather_conditions').insert([ { condition_code: 'clear', condition_name: '맑음', description: '맑은 날씨', icon: 'sunny', display_order: 1 }, { condition_code: 'rain', condition_name: '비', description: '비 오는 날씨', icon: 'rainy', precip_threshold: 0.1, display_order: 2 }, { condition_code: 'snow', condition_name: '눈', description: '눈 오는 날씨', icon: 'snowy', display_order: 3 }, { condition_code: 'heat', condition_name: '폭염', description: '기온 35도 이상', icon: 'hot', temp_threshold_min: 35, display_order: 4 }, { condition_code: 'cold', condition_name: '한파', description: '기온 영하 10도 이하', icon: 'cold', temp_threshold_max: -10, display_order: 5 }, { condition_code: 'wind', condition_name: '강풍', description: '풍속 10m/s 이상', icon: 'windy', wind_threshold: 10, display_order: 6 }, { condition_code: 'fog', condition_name: '안개', description: '시정 1km 미만', icon: 'foggy', display_order: 7 }, { condition_code: 'dust', condition_name: '미세먼지', description: '미세먼지 나쁨 이상', icon: 'dusty', display_order: 8 } ]); }) .then(function() { // 날씨별 안전 체크항목 삽입 return knex('tbm_safety_checks').insert([ // 비 (rain) { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'rain', check_item: '우의/우산 준비 확인', description: '비 오는 날 우의 또는 우산 준비 여부', is_required: true, display_order: 1 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'rain', check_item: '미끄럼 방지 조치 확인', description: '빗물로 인한 미끄러움 방지 조치', is_required: true, display_order: 2 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'rain', check_item: '전기 작업 중단 여부 확인', description: '우천 시 전기 작업 중단 필요성 확인', is_required: true, display_order: 3 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'rain', check_item: '배수 상태 확인', description: '작업장 배수 상태 점검', is_required: false, display_order: 4 }, // 눈 (snow) { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'snow', check_item: '제설 작업 완료 확인', description: '작업장 주변 제설 작업 완료 여부', is_required: true, display_order: 1 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'snow', check_item: '동파 방지 조치 확인', description: '배관 및 설비 동파 방지 조치', is_required: true, display_order: 2 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'snow', check_item: '미끄럼 방지 모래/염화칼슘 비치', description: '미끄럼 방지를 위한 모래 또는 염화칼슘 비치', is_required: true, display_order: 3 }, // 폭염 (heat) { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'heat', check_item: '그늘막/휴게소 확보', description: '무더위 휴식을 위한 그늘막 또는 휴게소 확보', is_required: true, display_order: 1 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'heat', check_item: '음료수/식염 포도당 비치', description: '열사병 예방을 위한 음료수 및 염분 보충제 비치', is_required: true, display_order: 2 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'heat', check_item: '무더위 휴식 시간 확보', description: '10~15시 사이 충분한 휴식 시간 확보', is_required: true, display_order: 3 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'heat', check_item: '작업자 건강 상태 확인', description: '열사병 증상 체크 및 건강 상태 확인', is_required: true, display_order: 4 }, // 한파 (cold) { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'cold', check_item: '방한복/방한장갑 착용 확인', description: '동상 방지를 위한 방한복 및 방한장갑 착용', is_required: true, display_order: 1 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'cold', check_item: '난방시설 가동 확인', description: '휴게 공간 난방시설 가동 상태 확인', is_required: true, display_order: 2 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'cold', check_item: '온열 음료 비치', description: '체온 유지를 위한 따뜻한 음료 비치', is_required: false, display_order: 3 }, // 강풍 (wind) { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'wind', check_item: '고소 작업 중단 여부 확인', description: '강풍 시 고소 작업 중단 필요성 확인', is_required: true, display_order: 1 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'wind', check_item: '자재/장비 결박 확인', description: '바람에 날릴 수 있는 자재 및 장비 고정', is_required: true, display_order: 2 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'wind', check_item: '가설물 안전 점검', description: '가설 구조물 및 비계 안전 상태 점검', is_required: true, display_order: 3 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'wind', check_item: '크레인 작업 중단 여부 확인', description: '강풍 시 크레인 작업 중단 필요성 확인', is_required: true, display_order: 4 }, // 안개 (fog) { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'fog', check_item: '경광등/조명 확보', description: '시정 확보를 위한 경광등 및 조명 설치', is_required: true, display_order: 1 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'fog', check_item: '차량 운행 주의 안내', description: '안개로 인한 차량 운행 주의 안내', is_required: true, display_order: 2 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'fog', check_item: '작업 구역 표시 강화', description: '시인성 확보를 위한 작업 구역 표시 강화', is_required: false, display_order: 3 }, // 미세먼지 (dust) { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'dust', check_item: '보호 마스크 착용 확인', description: 'KF94 이상 마스크 착용 여부 확인', is_required: true, display_order: 1 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'dust', check_item: '실외 작업 시간 조정', description: '미세먼지 농도에 따른 실외 작업 시간 조정', is_required: true, display_order: 2 }, { check_category: 'WEATHER', check_type: 'weather', weather_condition: 'dust', check_item: '호흡기 질환자 실내 배치', description: '호흡기 질환 작업자 실내 작업 배치', is_required: false, display_order: 3 } ]); }); }; exports.down = function(knex) { return knex.schema .dropTableIfExists('tbm_weather_records') .dropTableIfExists('weather_conditions') .then(function() { return knex.schema.alterTable('tbm_safety_checks', function(table) { table.dropIndex('check_type'); table.dropIndex('weather_condition'); table.dropIndex('task_id'); table.dropColumn('check_type'); table.dropColumn('weather_condition'); table.dropColumn('task_id'); }); }); };