From 11d4be720620abf502d35000e2ed40d30c4023bf Mon Sep 17 00:00:00 2001
From: yangys <y_ys79@sina.com>
Date: 星期一, 24 十一月 2025 16:33:34 +0800
Subject: [PATCH] 修复离线时间展示

---
 collect/src/main/java/com/qianwen/mdc/collect/service/DeviceStateFixPointService.java |  273 ++++++++++++++++++++++++++++++++++++------------------
 1 files changed, 181 insertions(+), 92 deletions(-)

diff --git a/collect/src/main/java/com/qianwen/mdc/collect/service/DeviceStateFixPointService.java b/collect/src/main/java/com/qianwen/mdc/collect/service/DeviceStateFixPointService.java
index 4656b9c..8db7e81 100644
--- a/collect/src/main/java/com/qianwen/mdc/collect/service/DeviceStateFixPointService.java
+++ b/collect/src/main/java/com/qianwen/mdc/collect/service/DeviceStateFixPointService.java
@@ -1,131 +1,125 @@
 package com.qianwen.mdc.collect.service;
 
-import cn.hutool.core.date.DatePattern;
-import cn.hutool.core.date.DateTime;
-import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.util.ObjectUtil;
-
-import com.google.common.collect.Lists;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.stream.Collectors;
+
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.write.record.Tablet;
+import org.apache.iotdb.tsfile.write.schema.MeasurementSchema;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.common.collect.Lists;
 import com.qianwen.core.tool.utils.Func;
 import com.qianwen.core.tool.utils.SpringUtil;
 import com.qianwen.mdc.collect.cache.WorkstationCache;
+import com.qianwen.mdc.collect.config.IotDBSessionConfig;
 import com.qianwen.mdc.collect.constants.CommonConstant;
+import com.qianwen.mdc.collect.constants.IOTDBConstant;
 import com.qianwen.mdc.collect.dto.CalendarShiftInfoDTO;
 import com.qianwen.mdc.collect.dto.WorkstationDTO;
 import com.qianwen.mdc.collect.entity.iotdb.DeviceState;
 import com.qianwen.mdc.collect.enums.FeedbackTimePointEnum;
-import com.qianwen.mdc.collect.mapper.iotdb.DeviceStateMapper;
 import com.qianwen.mdc.collect.mapper.mgr.CalendarMapper;
 import com.qianwen.mdc.collect.utils.LocalDateTimeUtils;
 
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
 
 @Service
 public class DeviceStateFixPointService{
     private static final Logger log = LoggerFactory.getLogger(DeviceStateFixPointService.class);
-    @Autowired
-    private DeviceStateMapper deviceStateMapper;
+    
     @Autowired
     private CalendarMapper calendarMapper;
     @Autowired
     private WorkstationCache workstationCache;
-    
+    @Autowired
+	private IotDBCommonService iotDBCommonService;
+    @Autowired
+	private IotDBSessionConfig iotdbConfig;
+   
     /**
      * 鍔犲叆鍥哄畾鐐�,鍘焪orkStationStateFixPoint
-     * @param dateTime
-     * @param includeWorkstationIds
+     * @param dateTime 鎵撳浐瀹氱偣鐨勬棩鏈�
+     * @param includeWorkstationIds 鎸囧畾鎵撳浐瀹氱偣鐨勫伐浣峣d锛屽叏閮芥墦鐐逛紶null
      */
     public void deviceStateFixPoint(DateTime dateTime, List<String> includeWorkstationIds) {
         List<DeviceState> result;
-        //CalendarMapper calendarMapper = SpringUtil.getBean(CalendarMapper.class);
-        Map<Long, WorkstationDTO> workStations = workstationCache.getWorkStations();
+        
+        Map<String, WorkstationDTO> workStations = workstationCache.getWorkstations();
         if (ObjectUtil.isEmpty(workStations)) {
             return;
         }
         Collection<WorkstationDTO> values = workStations.values();
-        //boolean b = ObjectUtil.isAllNotEmpty(includeWorkstationIds);//yys
-        //boolean b2 = Func.isNotEmpty(includeWorkstationIds);
+   
         if (ObjectUtil.isNotEmpty(includeWorkstationIds)) {
-        	/*
-            values = (Collection) values.stream().filter(item -> {
-                return includeWorkstationIds.contains(item.getId().toString());
-            }).collect(Collectors.toList());*/
-            
             values = values.stream().filter(item -> includeWorkstationIds.contains(item.getId().toString())).collect(Collectors.toList());
         }
         List<String> calendarCodeList = workStations.values().stream().map(WorkstationDTO::getCalendarCode).distinct().filter(Func::isNotEmpty).collect(Collectors.toList());
-        /*
-        List<String> calendarCodeList = (List) workStations.values().stream().map((v0) -> {
-            return v0.getCalendarCode();
-        }).distinct().filter((v0) -> {
-            return Func.isNotEmpty(v0);
-        }).collect(Collectors.toList());*/
-        Map<String, List<CalendarShiftInfoDTO>> map = new HashMap<>(32);
+        
+        Map<String, List<CalendarShiftInfoDTO>> shiftMap ;
         String formatDateTime = DateUtil.formatDate(dateTime);
         if (Func.isNotEmpty(calendarCodeList)) {
-        	/*
-            List<CalendarShiftInfoDTO> calendarShiftInfoDTOList = calendarMapper.getCalendarShiftInfo(calendarCodeList, DateUtil.year(dateTime), formatDateTime);
-            map = (Map) calendarShiftInfoDTOList.stream().collect(Collectors.groupingBy((v0) -> {
-                return v0.getCode();
-            }));*/
         	List<CalendarShiftInfoDTO> calendarShiftInfoDTOList = calendarMapper.getCalendarShiftInfo(calendarCodeList, DateUtil.year(dateTime), formatDateTime);
-            map = calendarShiftInfoDTOList.stream().collect(Collectors.groupingBy(CalendarShiftInfoDTO::getCode));
+            shiftMap = calendarShiftInfoDTOList.stream().collect(Collectors.groupingBy(CalendarShiftInfoDTO::getCode));
+        }else {
+        	shiftMap = Collections.emptyMap();
         }
         List<DeviceState> res = Lists.newArrayList();
         for (WorkstationDTO workstationDTO : values) {
         	
         	result = Lists.newArrayList();
-            List<CalendarShiftInfoDTO> list = map.getOrDefault(workstationDTO.getCalendarCode(), Collections.singletonList(new CalendarShiftInfoDTO().setCode("0").setCalendarDay(formatDateTime).setFactoryYear(Integer.valueOf(DateUtil.year(dateTime))).setFactoryWeek(LocalDateTimeUtils.getWeek(dateTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate())).setFactoryMonth(Integer.valueOf(DateUtil.month(dateTime) + 1)).setFactoryDate(formatDateTime).setStartTime(DateUtil.beginOfDay(dateTime)).setShiftIndex(CommonConstant.DEFAULT_SHIFT_INDEX).setShiftTimeType(CommonConstant.DEFAULT_SHIFT_TYPE)));
-            if (map.containsKey(workstationDTO.getCalendarCode())) {
+            List<CalendarShiftInfoDTO> calendarShiftList = shiftMap.getOrDefault(workstationDTO.getCalendarCode(), Collections.singletonList(new CalendarShiftInfoDTO().setCode("0").setCalendarDay(formatDateTime).setFactoryYear(Integer.valueOf(DateUtil.year(dateTime))).setFactoryWeek(LocalDateTimeUtils.getWeek(dateTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate())).setFactoryMonth(Integer.valueOf(DateUtil.month(dateTime) + 1)).setFactoryDate(formatDateTime).setStartTime(DateUtil.beginOfDay(dateTime)).setShiftIndex(CommonConstant.DEFAULT_SHIFT_INDEX).setShiftTimeType(CommonConstant.DEFAULT_SHIFT_TYPE)));
+            if (shiftMap.containsKey(workstationDTO.getCalendarCode())) {
                 result = getNoDefaultShift24HourPointDTOS(workstationDTO.getId(), workstationDTO.getCalendarCode(), dateTime);
             } else {
-                result = getDefaultShift24HourPointDTOS(workstationDTO.getId(), workstationDTO.getCalendarCode(), list.get(0));
+                result = getDefaultShift24HourPointDTOS(workstationDTO.getId(), workstationDTO.getCalendarCode(), calendarShiftList.get(0));
             }
             
             //灏嗙敓浜ф棩鍘嗕腑鐨勬椂闂磋妭鐐瑰姞鍏ュ浐瀹氱偣
-            for (int i = 0; i < list.size(); i++) {
+            for (int i = 0; i < calendarShiftList.size(); i++) {
             	//DeviceState workstationState = WorkstationStateConvert.INSTANCE.convert(list.get(i));
-            	CalendarShiftInfoDTO calendarShiftDTO = list.get(i);
+            	CalendarShiftInfoDTO calendarShiftDTO = calendarShiftList.get(i);
             	
-            	DeviceState workstationState = new DeviceState();
+            	DeviceState deviceState = new DeviceState();
             	
-                workstationState.setIsSync(false);
-                workstationState.setIsFixPoint(true);
-                workstationState.setValueCollect(0);
-                workstationState.setRps(0);
-                workstationState.setWcs(0);
-                workstationState.setWorkstationId(workstationDTO.getId());
-                workstationState.setTime(Long.valueOf(calendarShiftDTO.getStartTime().getTime()));
-                workstationState.setIsDeleted(Boolean.FALSE);
-                workstationState.setFeedbackPointType(FeedbackTimePointEnum.NO_FEED_BACK_POINT.getValue());
-                workstationState.setCalendarCode(workstationDTO.getCalendarCode());
+                deviceState.setIsSync(false);
+                deviceState.setIsFixPoint(true);
+                deviceState.setValueCollect(0);
+                deviceState.setRps(0);
+                deviceState.setWcs(0);
+                deviceState.setWorkstationId(workstationDTO.getId());
+                deviceState.setTime(Long.valueOf(calendarShiftDTO.getStartTime().getTime()));
+                deviceState.setIsDeleted(Boolean.FALSE);
+                deviceState.setFeedbackPointType(FeedbackTimePointEnum.NO_FEED_BACK_POINT.getValue());
+                deviceState.setCalendarCode(workstationDTO.getCalendarCode());
                 
                 String factoryDate = calendarShiftDTO.getFactoryDate();
                 String[] split = Func.split(factoryDate, "-");
-                workstationState.setFactoryDate(Integer.valueOf(String.join("", split)));
-                workstationState.setFactoryYear(calendarShiftDTO.getFactoryYear());
-                workstationState.setFactoryMonth(calendarShiftDTO.getFactoryMonth());
-                workstationState.setFactoryWeek(calendarShiftDTO.getFactoryWeek());
+                deviceState.setFactoryDate(Integer.valueOf(String.join("", split)));
+                deviceState.setFactoryYear(calendarShiftDTO.getFactoryYear());
+                deviceState.setFactoryMonth(calendarShiftDTO.getFactoryMonth());
+                deviceState.setFactoryWeek(calendarShiftDTO.getFactoryWeek());
                 
-                workstationState.setShiftIndex(calendarShiftDTO.getShiftIndex());
-                workstationState.setShiftTimeType(calendarShiftDTO.getShiftTimeType());
+                deviceState.setShiftIndex(calendarShiftDTO.getShiftIndex());
+                deviceState.setShiftTimeType(calendarShiftDTO.getShiftTimeType());
                 
         
-               // buildDefaultFactoryDay(dateTime, workstationState);
+                buildDefaultFactoryDay(dateTime, deviceState);
                 
-                result.add(workstationState);
+                result.add(deviceState);
             }
             res.addAll(result);
         }
@@ -140,7 +134,88 @@
     }
 
     
+    /**
+     * 淇濆瓨鐘舵�佸浐瀹氱偣鏁版嵁(state_{workstationId})
+     * @param stateList
+     */
     void saveDeviceStates(List<DeviceState> stateList) {
+    	//employees.stream().collect(Collectors.groupingBy(Employee::getCity)
+    	Map<Long,List<DeviceState>> maps = stateList.stream().collect(Collectors.groupingBy(DeviceState::getWorkstationId));
+    	List<Long> workstationIds = stateList.stream().map(DeviceState::getWorkstationId).distinct().collect(Collectors.toList());
+    	String deviceId;
+    	for(Long wid : workstationIds) {
+    		//鍒涘缓鏃堕棿搴忓垪
+    		deviceId = IOTDBConstant.DB_PREFIX+"state_"+wid;
+    		iotDBCommonService.setTemmplateIfNotSet(IOTDBConstant.TEMPLATE_STATE, deviceId);
+    		
+    	}
+    	
+    	List<MeasurementSchema> schemas = new ArrayList<>();
+		
+		schemas.add(new MeasurementSchema("workstation_id", TSDataType.INT64));
+		schemas.add(new MeasurementSchema("value_collect", TSDataType.INT32));
+
+		schemas.add(new MeasurementSchema("calendar_code", TSDataType.TEXT));
+		schemas.add(new MeasurementSchema("factory_year", TSDataType.INT32));
+		schemas.add(new MeasurementSchema("factory_month", TSDataType.INT32));
+		schemas.add(new MeasurementSchema("factory_week", TSDataType.INT32));
+		schemas.add(new MeasurementSchema("factory_date", TSDataType.INT32));
+		schemas.add(new MeasurementSchema("shift_index", TSDataType.INT32));
+		schemas.add(new MeasurementSchema("shift_time_type", TSDataType.INT32));
+		schemas.add(new MeasurementSchema("wcs", TSDataType.INT32));
+		schemas.add(new MeasurementSchema("rps", TSDataType.INT32));
+		schemas.add(new MeasurementSchema("is_fix_point", TSDataType.BOOLEAN));
+		schemas.add(new MeasurementSchema("is_sync", TSDataType.BOOLEAN));
+		schemas.add(new MeasurementSchema("is_plan", TSDataType.INT32));//TODO 杩欎釜灞炴�у簲璇ユ槸GlobalWcsOfRps涓殑鍊硷紝濡備綍濉啓锛�
+		schemas.add(new MeasurementSchema("feedback_point_type", TSDataType.INT32));
+		schemas.add(new MeasurementSchema("feedback_id", TSDataType.INT64));
+		schemas.add(new MeasurementSchema("is_deleted", TSDataType.BOOLEAN));
+		schemas.add(new MeasurementSchema("employee_id", TSDataType.INT64));
+		
+		
+		
+		Long wid;
+		List<DeviceState> states;
+		for(Entry<Long, List<DeviceState>>  entry: maps.entrySet()) {
+			wid = entry.getKey();
+			
+			deviceId = IOTDBConstant.DB_PREFIX+"state_"+wid;
+			Tablet tablet = new Tablet(deviceId, schemas);
+			
+			states = entry.getValue();
+			tablet.rowSize = states.size();
+			DeviceState state;
+			for(int i=0;i<states.size();i++) {
+				state = states.get(i);
+				tablet.addTimestamp(i, state.getTime());
+				tablet.addValue("workstation_id", i, state.getWorkstationId());
+				tablet.addValue("value_collect", i, state.getValueCollect());
+				
+				tablet.addValue("calendar_code", i, state.getCalendarCode());
+				tablet.addValue("factory_year", i, state.getFactoryYear());
+				tablet.addValue("factory_month", i, state.getFactoryMonth());
+				tablet.addValue("factory_week", i, state.getFactoryWeek());
+				tablet.addValue("factory_date", i, state.getFactoryDate());
+				tablet.addValue("shift_index", i, state.getShiftIndex());
+				tablet.addValue("shift_time_type", i, state.getShiftTimeType());
+				tablet.addValue("wcs", i, state.getWcs());
+				tablet.addValue("rps", i, state.getRps());
+				tablet.addValue("is_fix_point", i, state.getIsFixPoint());
+				tablet.addValue("is_sync", i, state.getIsSync());
+				tablet.addValue("is_plan", i, state.getIsPlan()==null ? -1 : state.getIsPlan());
+				tablet.addValue("feedback_point_type", i, state.getFeedbackPointType());
+				tablet.addValue("feedback_id", i, state.getFeedbackId() == null?0:state.getFeedbackId());
+				tablet.addValue("is_deleted", i, state.getIsDeleted());
+				tablet.addValue("employee_id", i, state.getEmployeeId() == null?0:state.getEmployeeId());
+				
+			}
+			try {
+				this.iotdbConfig.getSessionPool().insertAlignedTablet(tablet);
+			} catch (Exception e) {
+				log.error("淇濆瓨state鍥哄畾鐐规暟鎹紓甯�",e);
+			} 
+			
+		}
     	
     }
     private List<DeviceState> getDefaultShift24HourPointDTOS(Long workStationId, String calendarCode, CalendarShiftInfoDTO shift) {
@@ -172,7 +247,6 @@
            	state.setFactoryWeek(shift.getFactoryWeek());
             state.setShiftIndex(shift.getShiftIndex());
             state.setShiftTimeType(shift.getShiftTimeType());
-            //workstationState.setShiftIndex(calendarShiftDTO.getShiftIndex());
             
             state.setIsDeleted(Boolean.FALSE);
             default24HourPointDTO.add(state);
@@ -180,8 +254,15 @@
         return default24HourPointDTO;
     }
 
-    private List<DeviceState> getNoDefaultShift24HourPointDTOS(Long workStationId, String calendarCode, DateTime dateTime) {
-        List<DeviceState> default24HourPointDTO = new ArrayList<>();
+    /**
+     * 鐢熸垚闈為粯璁ょ殑24灏忔椂鍥哄畾鐐逛綅
+     * @param worksationId 宸ヤ綅id
+     * @param calendarCode
+     * @param dateTime 鍥哄畾鐐圭敓鎴愮殑鏃ユ湡
+     * @return
+     */
+    private List<DeviceState> getNoDefaultShift24HourPointDTOS(Long worksationId, String calendarCode, DateTime dateTime) {
+        List<DeviceState> default24HourPointDTOList = new ArrayList<>();
         CalendarMapper calendarMapper = (CalendarMapper) SpringUtil.getBean(CalendarMapper.class);
         try {
             List<CalendarShiftInfoDTO> calendarShiftList = calendarMapper.getCalendarShiftInfoToday(calendarCode, DateUtil.formatDate(dateTime));
@@ -193,43 +274,51 @@
                 state.setValueCollect(0);
                 state.setRps(0);
                 state.setWcs(0);
-                state.setWorkstationId(workStationId);
+                state.setWorkstationId(worksationId);
                 state.setTime(Long.valueOf(time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
                 state.setCalendarCode(calendarCode);
                 state.setFeedbackPointType(FeedbackTimePointEnum.NO_FEED_BACK_POINT.getValue());
-                state.setIsDeleted(Boolean.FALSE);
+                state.setIsDeleted(false);
                 
-              //濡傛灉state.ts鍦╟alendarShiftList鍖洪棿鍐咃紝鍒欏~鍏呯彮鍒跺拰鐢熶骇鏃ュ巻淇℃伅锛屽苟灏唖tate鍔犲叆鍒癲efault24HourPointDTO涓�
-                packCalendarShiftInfoForTimePoint(calendarShiftList, default24HourPointDTO, state);
+                //濡傛灉state.ts鍦╟alendarShiftList鍖洪棿鍐咃紝鍒欏~鍏呯彮鍒跺拰鐢熶骇鏃ュ巻淇℃伅锛屽苟灏唖tate鍔犲叆鍒癲efault24HourPointDTO涓�
+                packCalendarShiftInfoForTimePoint(calendarShiftList, default24HourPointDTOList, state);
             }
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("鐢熸垚鍥哄畾鐐规暟鎹紓甯�",e);
         }
-        return default24HourPointDTO;
+        return default24HourPointDTOList;
     }
 
-    private void packCalendarShiftInfoForTimePoint(List<CalendarShiftInfoDTO> calendarShiftList, List<DeviceState> default24HourPointDTO, DeviceState state) {
-        if (Func.isNotEmpty(calendarShiftList)) {
-            CalendarShiftInfoDTO relatedShift = calendarShiftList.stream().filter(item -> {
-                return item.getStartTime().getTime() <= state.getTime().longValue() && item.getEndTime().getTime() > state.getTime().longValue();
-            }).findFirst().orElse(null);
-            if (Func.isNotEmpty(relatedShift)) {
-                state.setShiftIndex(relatedShift.getShiftIndex());
-                state.setShiftTimeType(relatedShift.getShiftTimeType());
-                state.setFactoryYear(relatedShift.getFactoryYear());
-                state.setFactoryMonth(relatedShift.getFactoryMonth());
-                state.setFactoryWeek(relatedShift.getFactoryWeek());
-                String factoryDate = relatedShift.getFactoryDate();
-                String[] split = Func.split(factoryDate, "-");
-                state.setFactoryDate(Integer.valueOf(String.join("", split)));
-                state.setIsDeleted(Boolean.FALSE);
-                default24HourPointDTO.add(state);
-                return;
-            }
-            log.error("宸ヤ綅{} 鏃ュ巻{} 娌℃湁鏁寸偣寰楃彮娆′俊鎭�", state.getWorkstationId(), state.getCalendarCode());
-            return;
+    /**
+     * 濉厖璁惧鐘舵�佺殑鐝淇℃伅
+     * @param calendarShiftList
+     * @param default24HourPointDTOList
+     * @param state
+     */
+    private void packCalendarShiftInfoForTimePoint(List<CalendarShiftInfoDTO> calendarShiftList, List<DeviceState> default24HourPointDTOList, DeviceState state) {
+    	if (Func.isEmpty(calendarShiftList)) {
+    		log.error("宸ヤ綅{} 鏃ュ巻{} 鏃犳棩鏈�:[{}]鐨勭彮娆′俊鎭�", new Object[]{state.getWorkstationId(), state.getCalendarCode(), state.getTime()});
+    		return;
+    	}
+        
+        CalendarShiftInfoDTO relatedShift = calendarShiftList.stream().filter(item -> {
+            return item.getStartTime().getTime() <= state.getTime() && item.getEndTime().getTime() > state.getTime();
+        }).findFirst().orElse(null);
+        
+        if (Func.isNotEmpty(relatedShift)) {
+            state.setShiftIndex(relatedShift.getShiftIndex());
+            state.setShiftTimeType(relatedShift.getShiftTimeType());
+            state.setFactoryYear(relatedShift.getFactoryYear());
+            state.setFactoryMonth(relatedShift.getFactoryMonth());
+            state.setFactoryWeek(relatedShift.getFactoryWeek());
+            String factoryDate = relatedShift.getFactoryDate();
+            String[] split = Func.split(factoryDate, "-");
+            state.setFactoryDate(Integer.valueOf(String.join("", split)));
+            state.setIsDeleted(false);
+            default24HourPointDTOList.add(state);
+        }else {
+        	log.warn("宸ヤ綅{} 鏃ュ巻{} 鏈壘鍒板浐瀹氱偣鐝淇℃伅", state.getWorkstationId(), state.getCalendarCode());
         }
-        log.error("宸ヤ綅{} 鏃ュ巻{} 娌℃湁鑷劧澶﹞}寰楃彮娆′俊鎭�", new Object[]{state.getWorkstationId(), state.getCalendarCode(), state.getTime()});
     }
 
     //涓嶄竴瀹氳兘鐢ㄤ笂
@@ -243,7 +332,7 @@
             workstationState.setShiftIndex(CommonConstant.DEFAULT_SHIFT_INDEX);
             workstationState.setShiftTimeType(CommonConstant.DEFAULT_SHIFT_TYPE);
             workstationState.setFeedbackPointType(FeedbackTimePointEnum.NO_FEED_BACK_POINT.getValue());
-            workstationState.setIsDeleted(Boolean.FALSE);
+            workstationState.setIsDeleted(false);
         }
     }
 }

--
Gitblit v1.9.3