From fce96ef468291fb9a0e6a4d34ab371315e9485d4 Mon Sep 17 00:00:00 2001
From: yxh <172933527@qq.com>
Date: 星期六, 03 十月 2026 00:59:44 +0800
Subject: [PATCH] ai优化
---
jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queue/QueueServiceImpl.java | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 206 insertions(+), 18 deletions(-)
diff --git a/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queue/QueueServiceImpl.java b/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queue/QueueServiceImpl.java
index 3c99e02..4b6f9e4 100644
--- a/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queue/QueueServiceImpl.java
+++ b/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queue/QueueServiceImpl.java
@@ -10,7 +10,9 @@
import cn.lihu.jh.module.ecg.dal.dataobject.checktype.CheckTypeDO;
import cn.lihu.jh.module.ecg.dal.dataobject.devrent.DevRentDO;
import cn.lihu.jh.module.ecg.dal.mysql.appointment.AppointmentMapper;
+import cn.lihu.jh.module.ecg.dal.mysql.checktype.CheckTypeMapper;
import cn.lihu.jh.module.ecg.dal.mysql.devrent.DevRentMapper;
+import cn.lihu.jh.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.lihu.jh.module.ecg.enums.DevRentStateEnum;
import cn.lihu.jh.module.system.api.oauth2.OAuth2TokenApi;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
@@ -66,6 +68,13 @@
@Resource
private DevRentMapper devRentMapper;
+
+ /**
+ * 鐢ㄤ簬銆岃澶囬鐢�/瑁呮満銆嶅彇鏁版椂杩囨护鍑� {@code need_dev_ready = 1} 鐨勬鏌ラ」鐩�
+ * 锛堣 {@link #filterNeedDevReady}锛夈��
+ */
+ @Resource
+ private CheckTypeMapper checkTypeMapper;
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
@@ -433,7 +442,8 @@
* queue 琛ㄤ笉瀛樿繖浜涘瓧娈碉紝浣嗗尰鐢熺鍒楄〃鎺ュ彛闇�瑕佸睍绀猴紝鎵�浠ユ煡璇㈠悗鎸� applyNo 鍥炴煡棰勭害鍗曞~鍏咃細
* 澶氳�愯嵂銆佹�у埆銆佸嚭鐢熸棩鏈熴�佺櫥璁版椂闂淬�佸紑鍗曞尰甯堛�佸紑鍗曟棩鏈熴��
*/
- private void fillAppointmentInfo(List<QueueDO> queueDOList) {
+ @Override
+ public void fillAppointmentInfo(List<QueueDO> queueDOList) {
if (CollectionUtils.isEmpty(queueDOList)) {
return;
}
@@ -446,11 +456,26 @@
return;
}
queueDO.setTolerance(appointment.getTolerance());
+ // 闇�姹�2锛氬簥杈规爣璁帮紙HIS zdy.ISCB锛夈�俼ueue 琛ㄥ凡鏈夎鍒楋紝
+ // 浣嗗銆�22_* 鑴氭湰鎵ц鍓嶅氨宸插叆闃熴�嶇殑琛岋紝闈犺繖閲屽洖鏌ヨˉ濉紝淇濊瘉灞忓箷/椤甸潰鑳界湅鍒般��
+ if (queueDO.getIsBedside() == null) {
+ queueDO.setIsBedside(appointment.getIsBedside());
+ }
queueDO.setPatGender(appointment.getPatGender());
queueDO.setPatBirthday(appointment.getPatBirthday());
queueDO.setRegisterDate(appointment.getRegisterDate());
queueDO.setDoctor(appointment.getDoctor());
queueDO.setDoctorTime(appointment.getDoctorTime());
+ // 闇�姹�1锛氳ˉ骞抽摵鐨勬偅鑰呬俊鎭潵婧愬瓧娈碉紙鐥呭巻鍖�/搴婂彿/鐢佃瘽/鏉ユ簮/灏辫瘖鍗″彿绛夛級
+ queueDO.setPatSrc(appointment.getPatSrc());
+ queueDO.setPatMobile(appointment.getPatMobile());
+ queueDO.setPatPhone(appointment.getPatPhone());
+ queueDO.setPatWardDesc(appointment.getPatWardDesc());
+ queueDO.setPatBedNo(appointment.getPatBedNo());
+ queueDO.setMedicalCard(appointment.getMedicalCard());
+ queueDO.setDoctorAdviceNo(appointment.getDoctorAdviceNo());
+ queueDO.setOutpatientNo(appointment.getOutpatientNo());
+ queueDO.setPatIdentityId(appointment.getPatIdentityId());
});
}
@@ -471,18 +496,33 @@
patientStatisticVO.setReadyNum(item.getTotalInStatus());
} else if (QueueStatusEnum.FINISH.getStatus() == item.getStatus()) {
patientStatisticVO.setFinishedNum(item.getTotalInStatus());
- } else if (QueueStatusEnum.PASSED.getStatus() == item.getStatus()) {
- patientStatisticVO.setPassedNum(item.getTotalInStatus());
+ } else if (QueueStatusEnum.PASSED.getStatus() == item.getStatus()
+ || QueueStatusEnum.PASSED_WAITING.getStatus() == item.getStatus()) {
+ // 銆愰渶姹�4 鍙e緞缁熶竴銆戣繃鍙� = 宸茶繃鍙�(5) + 宸茶繃鍙�-鎺掗槦(3)銆�
+ // 鍘熷厛鍙畻 5锛岃�屽垪琛ㄤ笌灞忓箷閮藉惈 3锛屽鑷�"杩囧彿"鏁板瓧姣斿疄闄呭皯銆�
+ int cur = patientStatisticVO.getPassedNum() == null ? 0 : patientStatisticVO.getPassedNum();
+ patientStatisticVO.setPassedNum(cur + item.getTotalInStatus());
}
});
// 缁熻 璇ュ伐浣嶇被鍨嬬殑銆愭帓闃熶腑銆戜汉鍛樻暟閲�
+ // 銆怮7 纭銆戞澶勫埢鎰忕粺璁�**鍏ㄩ」鐩�**锛堣宸ヤ綅鎵�鍚鏌ョ被鍨嬶級鐨�"鎺掗槦涓�"鎬绘暟锛�
+ // 璇箟涓�"鍏ㄩ櫌杩樻湁澶氬皯浜虹瓑鐫�鍋氳繖绫绘鏌�"锛屼笌"鏈伐浣嶄竴鐪煎彲瑙佺殑鍊欒瘖浜烘暟"浜掕ˉ銆�
+ // 涓斿凡琛� deleted=0 / book_date=浠婃棩锛堣 QueueMapper.checkTypeAndStatusStatistic锛夈��
RoomDO roomDO = getRoomDO(roomId, bedNo);
Integer[] checkTypes = roomDO.getCheckTypes();
- List<Integer> statusList = new ArrayList<>();
- statusList.add(QueueStatusEnum.WAITING.getStatus());
- Integer num = queueMapper.checkTypeAndStatusStatistic(checkTypes, statusList);
- patientStatisticVO.setQueuingNum(num);
+ int queuingNum = 0;
+ // 銆愰槻寰°�慶heckTypes 涓虹┖鏃朵笉鑳芥嫾 `book_check_type in ()`锛圡ySQL 璇硶閿欒锛夈��
+ // 瀹炴祴璇ュ満鏅細璁╃粺璁℃帴鍙� 500锛岃繘鑰屽鑷村尰鐢熺椤堕儴缁熻鏁村潡鎶ラ敊銆�
+ if (checkTypes != null && checkTypes.length > 0) {
+ List<Integer> statusList = new ArrayList<>();
+ statusList.add(QueueStatusEnum.WAITING.getStatus());
+ Integer num = queueMapper.checkTypeAndStatusStatistic(checkTypes, statusList);
+ queuingNum = num == null ? 0 : num;
+ } else {
+ log.warn("[getPatientStatistic][宸ヤ綅鏈厤缃鏌ョ被鍨嬶紝鎺掗槦涓寜 0 璁 roomId={} bedNo={}", roomId, bedNo);
+ }
+ patientStatisticVO.setQueuingNum(queuingNum);
return patientStatisticVO;
}
@@ -524,8 +564,19 @@
bedQueueStatisticDOList.forEach(item -> {
if (QueueStatusEnum.READY.getStatus() == item.getStatus()) {
patientStatisticVO.setReadyNum(item.getTotalInStatus());
- } else if (QueueStatusEnum.PASSED.getStatus() == item.getStatus()) {
- patientStatisticVO.setPassedNum(item.getTotalInStatus());
+ } else if (QueueStatusEnum.PASSED.getStatus() == item.getStatus()
+ || QueueStatusEnum.PASSED_WAITING.getStatus() == item.getStatus()) {
+ // 銆愰渶姹�4 鍙e緞缁熶竴銆戣繃鍙� = 宸茶繃鍙�(5) + 宸茶繃鍙�-鎺掗槦(3)銆�
+ //
+ // 涓� getPatientStatistic 淇濇寔鍚屼竴鍙e緞銆傚師瀹炵幇鍙垽 PASSED(5)锛�
+ // 鑰岄鐢ㄧ晫闈㈢殑鍒楄〃/灞忓箷閮藉惈 status=3锛屽鑷淬�岃繃鍙枫�嶆暟瀛楁瘮瀹為檯灏�
+ // 鈥斺�� 杩欐鏄敤鎴峰弽棣堢殑銆岀粺璁′笌瀹為檯涓嶇銆嶃��
+ //
+ // 鍙︼細鍘熷疄鐜扮敤 setPassedNum(item.getTotalInStatus())锛�**瑕嗙洊**鑰岄潪绱姞锛夛紝
+ // 鍥� SQL 鏄� group by status锛宻tatus=3 涓� status=5 鏄袱琛岋紝
+ // 瑕嗙洊浼氬彧淇濈暀鏈�鍚庝竴琛� 鈫� 鏀逛负绱姞銆�
+ int cur = patientStatisticVO.getPassedNum() == null ? 0 : patientStatisticVO.getPassedNum();
+ patientStatisticVO.setPassedNum(cur + item.getTotalInStatus());
}
});
@@ -545,10 +596,17 @@
// 缁熻 璇ュ伐浣嶇被鍨嬬殑銆愭帓闃熶腑銆戜汉鍛樻暟閲�
RoomDO roomDO = getRoomDO(roomId, bedNo);
Integer[] checkTypes = roomDO.getCheckTypes();
- List<Integer> statusList = new ArrayList<>();
- statusList.add(QueueStatusEnum.WAITING.getStatus());
- Integer num = queueMapper.checkTypeAndStatusStatistic(checkTypes, statusList);
- patientStatisticVO.setQueuingNum(num);
+ Integer num = 0;
+ // 銆愰槻寰°�慶heckTypes 涓虹┖鏃朵笉鑳芥嫾 `book_check_type in ()`锛圡ySQL 璇硶閿欒锛夛紝
+ // 瀹炴祴璇ュ満鏅細璁╃粺璁℃帴鍙� 500銆佸尰鐢熺椤堕儴缁熻鏁村潡鎶ラ敊銆備笌 getPatientStatistic 淇濇寔涓�鑷淬��
+ if (checkTypes != null && checkTypes.length > 0) {
+ List<Integer> statusList = new ArrayList<>();
+ statusList.add(QueueStatusEnum.WAITING.getStatus());
+ num = queueMapper.checkTypeAndStatusStatistic(checkTypes, statusList);
+ } else {
+ log.warn("[getBedDevReadyStatistic][宸ヤ綅鏈厤缃鏌ョ被鍨嬶紝鎺掗槦涓寜 0 璁 roomId={} bedNo={}", roomId, bedNo);
+ }
+ patientStatisticVO.setQueuingNum(num == null ? 0 : num);
return patientStatisticVO;
}
@@ -578,6 +636,73 @@
startHurryUpOneBed(roomId, bedNo);
return updateNum;
+ }
+
+ // ==================== 闇�姹�2锛氭挙閿�銆屽彨鍙� / 妫�鏌ュ畬鎴愩�� ====================
+
+ /**
+ * 鎾ら攢銆屾鏌ュ畬鎴愩�嶏細鎶婃湰宸ヤ綅浠婃棩鐨勬寚瀹氭偅鑰呬粠 宸插氨璇�(40) 閫�鍥� **鍊欒瘖涓�(20)**銆�
+ * <p>
+ * <b>Q9 纭</b>锛氭挙閿�鍚庡洖鍒�**鍊欒瘖鍖�**锛堣�岄潪"灏辫瘖涓�"锛夛紝渚夸簬鍖荤敓閲嶆柊鍙彿銆�
+ * <p>
+ * 鏀跺熬蹇呴』璋� {@link #startHurryUpOneBed}锛氬畠浼氶噸鏂拌绠楄宸ヤ綅鐨�
+ * {@code readyPatNum} 骞跺埛鏂板垎娴侀槦鍒椾紭鍏堢骇銆傝嫢涓嶅悓姝ュ唴瀛樿鏁帮紝
+ * {@code hurryupOneCheckType} 浼氭姏鍑� {@code exceed max queue number}銆�
+ *
+ * @return 鍙楀奖鍝嶈鏁帮紙0 琛ㄧず娌℃壘鍒拌鎮h�呬粖鏃ョ殑宸插畬鎴愯褰曪級
+ */
+ @Override
+ public Integer undoFinishPatient(Long roomId, String bedNo, String patId, Integer checkType) {
+ Integer updateNum = queueMapper.undoFinishPatient(roomId, bedNo, patId, checkType,
+ QueueStatusEnum.FINISH.getStatus(), QueueStatusEnum.READY.getStatus());
+ if (null != updateNum && updateNum > 0) {
+ startHurryUpOneBed(roomId, bedNo);
+ }
+ return updateNum;
+ }
+
+ /**
+ * 鎾ら攢銆岃鐐瑰彨鍙枫�嶏細鎶婃湰宸ヤ綅褰撳墠 灏辫瘖涓�(30) 鐨勬偅鑰呴��鍥� **鍊欒瘖涓�(20)**銆�
+ *
+ * @return 鍙楀奖鍝嶈鏁帮紙0 琛ㄧず褰撳墠娌℃湁灏辫瘖涓殑鎮h�咃級
+ */
+ @Override
+ public Integer undoCallPatient(Long roomId, String bedNo) {
+ Integer updateNum = queueMapper.undoCallPatient(roomId, bedNo,
+ QueueStatusEnum.ONSTAGE.getStatus(), QueueStatusEnum.READY.getStatus());
+ if (null != updateNum && updateNum > 0) {
+ startHurryUpOneBed(roomId, bedNo);
+ }
+ return updateNum;
+ }
+
+ @Override
+ public QueueDO getBedLastFinishedQueueItem(Long roomId, String bedNo) {
+ return queueMapper.getBedLastFinishedQueueItem(roomId, bedNo, QueueStatusEnum.FINISH.getStatus());
+ }
+
+ // ==================== 闇�姹�3锛氬仠姝㈠�欒瘖寮�鍏� ====================
+
+ @Override
+ public ErrorCode setStopReady(Long roomId, String bedNo, boolean stop) {
+ // 璧板崟绾跨▼鎵ц鍣紝涓� bedDoctorPause/Resume 淇濇寔涓�鑷寸殑涓茶鍖栧鐞嗭紝
+ // 閬垮厤涓庡垎娴侀槦鍒楃殑骞跺彂淇敼鍐茬獊銆�
+ Future<ErrorCode> future = singleThreadExecutor.submit(
+ () -> queueServiceTxFunctions.setStopReady(roomId, bedNo, stop));
+ try {
+ return future.get();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ log.error("[setStopReady] 琚腑鏂� roomId={} bedNo={}", roomId, bedNo, e);
+ } catch (ExecutionException e) {
+ log.error("[setStopReady] 鎵ц寮傚父 roomId={} bedNo={}", roomId, bedNo, e);
+ }
+ return ECG_INNER_ERROR;
+ }
+
+ @Override
+ public Boolean getStopReady(Long roomId, String bedNo) {
+ return queueServiceTxFunctions.getStopReady(roomId, bedNo);
}
@Override
@@ -632,6 +757,10 @@
queueStatusList.add(QueueStatusEnum.READY.getStatus());
queueStatusList.add(QueueStatusEnum.ONSTAGE.getStatus());
queueStatusList.add(QueueStatusEnum.PASSED.getStatus());
+ // 銆愰渶姹�4 鍙e緞缁熶竴銆戣ˉ涓� PASSED_WAITING(3)銆屽凡杩囧彿-鎺掗槦銆嶃��
+ // 鍘熷厛鍒楄〃鍙惈 5锛岃�屽ぇ灞�/璇婇棿灞忔煡璇㈠惈 3锛屽鑷村悓涓�鎮h�呭湪涓嶅悓鐣岄潰鍑虹幇涓庡惁涓嶄竴鑷达紱
+ // 缁熻渚т篃灏� 3 璁″叆"杩囧彿"锛堣 getPatientStatistic / getBedDevReadyStatistic锛夈��
+ queueStatusList.add(QueueStatusEnum.PASSED_WAITING.getStatus());
queueStatusList.add(QueueStatusEnum.AFFINITY.getStatus());
List<QueueDO> queueDOList = getBedQueueByStatus(roomId, bedNo, queueStatusList);
@@ -677,9 +806,31 @@
queueStatusList.add(QueueStatusEnum.INSTALLING.getStatus());
queueDOList.addAll(0, getBedQueueByStatus(roomId, bedNo, queueStatusList)); // 褰撳墠宸ヤ綅鐨�
+ // ======================================================================
+ // 銆愪慨澶嶃�戝彧淇濈暀銆岄渶瑕佽澶囬鐢ㄣ�嶇殑妫�鏌ラ」鐩��
+ //
+ // 鑳屾櫙锛氫笂闈袱澶勬煡璇㈢殑鏉′欢鏄� `room_id = ? AND status IN (...)`
+ // 锛坓etRoomQueueByStatus 鐢氳嚦**涓嶅惈 bed_no**锛夛紝**瀹屽叏涓嶇湅 book_check_type**銆�
+ // 鑰岃嚜鍔ㄥ垎娴侊紙preemptWaitingPatientWithBedCheckTypes锛夋槸**鎸夊伐浣� checkTypes 杩囨护**鐨勶紝
+ // 鎵�浠ュ彧鏈�"浜哄伐鎶婃偅鑰呮媺杩涙潵"杩欐潯璺緞浼氭紡 鈥斺�� 琛ㄧ幇涓�
+ // 銆岃澶囬鐢ㄣ�嶉〉閿欒鍑虹幇甯歌蹇冪數鍥�(100)銆佸钩鏉胯繍鍔ㄥ績鐢�(400)绛�**涓嶉渶棰嗙敤璁惧**鐨勬偅鑰呫��
+ //
+ // 鍒ゅ畾渚濇嵁鐢ㄩ厤缃� `check_type.need_dev_ready`锛�
+ // 1 = 闇�瑕佽澶囬鐢紙濡� 鍔ㄦ�佸績鐢� 200銆佸姩鎬佽鍘� 300锛�
+ // 0 = 涓嶉渶瑕侊紙甯歌蹇冪數鍥俱�佸钩鏉胯繍鍔ㄥ績鐢� 鈥︼級
+ //
+ // 鏀惧湪鏈嶅姟灞傝�屼笉鏄敼 SQL锛氫竴娆℃煡璇㈠緱鍒伴泦鍚堝悗鍦ㄥ唴瀛樿繃婊わ紝閫昏緫闆嗕腑銆佹槗璇伙紝
+ // 涓斾笉闇�瑕佷负澶氭暟鎹簮/鍔ㄦ�佽〃鍚嶉澶栬皟鏁� SQL銆傛湰璇婂寰呭畨瑁呮偅鑰呴噺绾у緢灏忥紙鍑犲崄琛岋級銆�
+ // ======================================================================
+ queueDOList = filterNeedDevReady(queueDOList);
+
+ // 娉ㄦ剰锛氫笅闈㈢敤 lambda锛屾崟鑾风殑鍙橀噺蹇呴』鏄� effectively final锛�
+ // 鏁呰繖閲屽彟璧蜂竴涓� final 鍙橀噺锛岃�屼笉鏄鐢ㄤ笂闈㈢殑 queueDOList锛堝畠宸茶閲嶆柊璧嬪�硷級銆�
+ final List<QueueDO> installQueueList = queueDOList;
+
//鎶� [浜插拰鐨刔 鎸埌涓婚」鍚庨潰
List<QueueDO> affinityReceivedList = new ArrayList<>();
- ListIterator<QueueDO> iterator = queueDOList.listIterator();
+ ListIterator<QueueDO> iterator = installQueueList.listIterator();
while (iterator.hasNext()) {
QueueDO item = iterator.next();
if (item.getStatus() == QueueStatusEnum.AFFINITY_INSTALL.getStatus()) {
@@ -688,15 +839,52 @@
}
}
affinityReceivedList.forEach(affinityReceivedItem -> {
- for (int i = queueDOList.size() - 1; i >= 0; i--) {
- if (affinityReceivedItem.getSeqNum() == queueDOList.get(i).getSeqNum()) {
- queueDOList.add(i + 1, affinityReceivedItem);
+ for (int i = installQueueList.size() - 1; i >= 0; i--) {
+ if (affinityReceivedItem.getSeqNum() == installQueueList.get(i).getSeqNum()) {
+ installQueueList.add(i + 1, affinityReceivedItem);
break;
}
}
});
- return queueDOList;
+ return installQueueList;
+ }
+
+ /**
+ * 鍙繚鐣欍�岄渶瑕佽澶囬鐢ㄣ�嶏紙{@code check_type.need_dev_ready = 1}锛夌殑妫�鏌ラ」鐩殑鎮h�呫��
+ *
+ * <p>鐢ㄤ簬銆岃澶囬鐢� / 瑁呮満銆嶅彇鏁帮細杩欎簺宸ヤ綅鍙簲澶勭悊闇�瑕侀鐢ㄨ澶囩殑妫�鏌ラ」鐩紝
+ * 鍚﹀垯浼氬嚭鐜板父瑙勫績鐢靛浘绛夋偅鑰呮贩鍏ヨ澶囨祦绋嬨��
+ *
+ * <p>閰嶇疆寮傚父锛堟煡涓嶅埌妫�鏌ラ」鐩級鏃�**杩斿洖鍘熷垪琛�**锛屽畞鍙鏄剧ず涔熶笉瑕佹妸鎮h��"钘忚捣鏉�"銆�
+ */
+ private List<QueueDO> filterNeedDevReady(List<QueueDO> queueDOList) {
+ if (null == queueDOList || queueDOList.isEmpty()) {
+ return queueDOList;
+ }
+ List<CheckTypeDO> needDevList = checkTypeMapper.selectList(
+ new LambdaQueryWrapperX<CheckTypeDO>().eq(CheckTypeDO::getNeedDevReady, 1));
+ if (null == needDevList || needDevList.isEmpty()) {
+ log.warn("[filterNeedDevReady] 鏈厤缃换浣昜闇�瑕佽澶囬鐢╙鐨勬鏌ラ」鐩紝璺宠繃杩囨护浠ュ厤鎮h�呭叏閮ㄤ笉鍙");
+ return queueDOList;
+ }
+ Set<Integer> needDevCheckTypes = new HashSet<>();
+ needDevList.forEach(ct -> {
+ if (null != ct.getValue()) {
+ needDevCheckTypes.add(ct.getValue());
+ }
+ });
+
+ List<QueueDO> filtered = new ArrayList<>();
+ for (QueueDO item : queueDOList) {
+ if (needDevCheckTypes.contains(item.getBookCheckType())) {
+ filtered.add(item);
+ } else {
+ log.info("[filterNeedDevReady] 杩囨护鎺変笉闇�瑕佽澶囬鐢ㄧ殑鎮h��. patId={} 妫�鏌ラ」鐩�={} 鐘舵��={}",
+ item.getPatId(), item.getBookCheckType(), item.getStatus());
+ }
+ }
+ return filtered;
}
private RoomDO getRoomDO(Long roomId, String bedNo) {
--
Gitblit v1.9.3