|
|
@@ -11,6 +11,7 @@ import com.zksy.cockpit.foundation.CockpitSnapshotExpiredException;
|
|
|
import com.zksy.cockpit.point.CockpitMonitoringPointDeviceSnapshot;
|
|
|
import com.zksy.cockpit.point.CockpitMonitoringPointStatus;
|
|
|
import com.zksy.cockpit.point.CockpitMonitoringPointStatusResolver;
|
|
|
+import com.zksy.cockpit.point.CockpitPointStatusQuery;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
@@ -32,9 +33,8 @@ import java.util.Set;
|
|
|
/**
|
|
|
* Read-only aggregation for 运行监测 / 监测分布.
|
|
|
*
|
|
|
- * <p>Statistics and pagination use selected business objects, including empty points
|
|
|
- * and objects without renderable coordinates. coordinateValidation controls map
|
|
|
- * presentation only; excluded counts rejected objects, not their associated devices.
|
|
|
+ * <p>业务对象参与计数、台账与筛选,坐标校验仅决定能否上图。
|
|
|
+ * 八桶分布取全局未筛全集(六态/UNKNOWN 数点位,UNASSOCIATED 数未关联设备)。
|
|
|
*/
|
|
|
@Service
|
|
|
public class CockpitMonitoringDistributionService {
|
|
|
@@ -74,7 +74,7 @@ public class CockpitMonitoringDistributionService {
|
|
|
Integer pageSize,
|
|
|
String snapshotAt) {
|
|
|
String selectedIndustry = resolveIndustry(industry);
|
|
|
- CockpitMonitoringPointStatus selectedStatus = resolvePointStatus(pointStatus);
|
|
|
+ CockpitPointStatusQuery selectedStatus = CockpitPointStatusQuery.parse(pointStatus);
|
|
|
CockpitDistrict.Query selectedDistrict = resolveDistrict(district);
|
|
|
int safePage = page == null ? 1 : page;
|
|
|
int safePageSize = pageSize == null ? DEFAULT_PAGE_SIZE : pageSize;
|
|
|
@@ -102,7 +102,7 @@ public class CockpitMonitoringDistributionService {
|
|
|
String snapshotAt,
|
|
|
CockpitMonitoringDistributionDeviceStatusDelta delta) {
|
|
|
String selectedIndustry = resolveIndustry(industry);
|
|
|
- CockpitMonitoringPointStatus selectedStatus = resolvePointStatus(pointStatus);
|
|
|
+ CockpitPointStatusQuery selectedStatus = CockpitPointStatusQuery.parse(pointStatus);
|
|
|
CockpitDistrict.Query selectedDistrict = resolveDistrict(district);
|
|
|
int safePage = page == null ? 1 : page;
|
|
|
int safePageSize = pageSize == null ? DEFAULT_PAGE_SIZE : pageSize;
|
|
|
@@ -231,7 +231,7 @@ public class CockpitMonitoringDistributionService {
|
|
|
String requestId,
|
|
|
String selectedIndustry,
|
|
|
String deviceType,
|
|
|
- CockpitMonitoringPointStatus selectedStatus,
|
|
|
+ CockpitPointStatusQuery selectedStatus,
|
|
|
CockpitDistrict.Query selectedDistrict,
|
|
|
int page,
|
|
|
int pageSize) {
|
|
|
@@ -241,22 +241,14 @@ public class CockpitMonitoringDistributionService {
|
|
|
int unknownTypeCount = 0;
|
|
|
int typeKindMismatchCount = 0;
|
|
|
int nonFormalIndustryCount = 0;
|
|
|
- // Compatibility field: coordinates no longer exclude business objects.
|
|
|
int invalidCoordinateCount = 0;
|
|
|
|
|
|
List<Marker> pointMarkers = new ArrayList<>();
|
|
|
List<Marker> deviceMarkers = new ArrayList<>();
|
|
|
Set<String> pointCodes = new LinkedHashSet<>();
|
|
|
Set<String> deviceCodes = new LinkedHashSet<>();
|
|
|
- // Association is a source fact, independent of all display filters.
|
|
|
- Set<String> associatedDeviceCodes = new LinkedHashSet<>();
|
|
|
- if (snapshot.getMonitoringPoints() != null) {
|
|
|
- for (CockpitMonitoringDistributionSnapshot.SourceMonitoringPoint point : snapshot.getMonitoringPoints()) {
|
|
|
- if (point != null && !isBlank(point.getPointCode())) {
|
|
|
- associatedDeviceCodes.addAll(codes(safeDevices(point.getDevices())));
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ Set<String> associatedCodes = new LinkedHashSet<>();
|
|
|
+ CockpitPointStatusQuery.Distribution global = new CockpitPointStatusQuery.Distribution();
|
|
|
|
|
|
if (snapshot.getMonitoringPoints() != null) {
|
|
|
for (CockpitMonitoringDistributionSnapshot.SourceMonitoringPoint point : snapshot.getMonitoringPoints()) {
|
|
|
@@ -264,54 +256,39 @@ public class CockpitMonitoringDistributionService {
|
|
|
continue;
|
|
|
}
|
|
|
CockpitMonitoringDistributionSnapshot.SourceType type = resolveType(types, point.getTypeCode());
|
|
|
- String mismatch = classify(type);
|
|
|
- List<CockpitMonitoringPointDeviceSnapshot> devices = safeDevices(point.getDevices());
|
|
|
- boolean emptyPointWithUnknownType = UNKNOWN_TYPE.equals(mismatch) && devices.isEmpty();
|
|
|
- if (mismatch != null && !emptyPointWithUnknownType) {
|
|
|
- if (UNKNOWN_TYPE.equals(mismatch)) {
|
|
|
- unknownTypeCount++;
|
|
|
- } else if (TYPE_KIND_MISMATCH.equals(mismatch)) {
|
|
|
- typeKindMismatchCount++;
|
|
|
- } else {
|
|
|
- nonFormalIndustryCount++;
|
|
|
- }
|
|
|
- continue;
|
|
|
- }
|
|
|
- String markerIndustry = type == null ? null : type.getIndustry();
|
|
|
- String markerTypeCode = type == null ? null : type.getCode();
|
|
|
- if (selectedIndustry != null && !selectedIndustry.equals(markerIndustry)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- if (!isBlank(deviceType) && !deviceType.trim().equals(markerTypeCode)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- CockpitCoordinateValidation validation = CockpitCoordinateValidator.validate(
|
|
|
- point.getLongitude(), point.getLatitude(), point.isCoordinateVerified());
|
|
|
- // 点位片区按「经其上挂设备归纳」取得(适配器已归纳,这里只做归一与筛选)
|
|
|
- Integer markerDistrict = CockpitDistrict.codeOf(point.getDistrict());
|
|
|
- if (!CockpitDistrict.matches(selectedDistrict, markerDistrict)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
List<CockpitMonitoringPointDeviceSnapshot> effective = new ArrayList<>();
|
|
|
- for (CockpitMonitoringPointDeviceSnapshot device : devices) {
|
|
|
- if (device == null || isBlank(device.getDeviceCode())) {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ for (CockpitMonitoringPointDeviceSnapshot device : safeDevices(point.getDevices())) {
|
|
|
+ if (device == null || isBlank(device.getDeviceCode())) continue;
|
|
|
+ associatedCodes.add(device.getDeviceCode().trim());
|
|
|
effective.add(effectiveDevice(device));
|
|
|
}
|
|
|
CockpitMonitoringPointStatus status = CockpitMonitoringPointStatusResolver.resolve(effective);
|
|
|
- if (selectedStatus != null && selectedStatus != status) {
|
|
|
+ global.addPoint(status);
|
|
|
+ String mismatch = classify(type);
|
|
|
+ boolean emptyPointWithUnknownType = UNKNOWN_TYPE.equals(mismatch) && effective.isEmpty();
|
|
|
+ if (mismatch != null && !emptyPointWithUnknownType) {
|
|
|
+ if (UNKNOWN_TYPE.equals(mismatch)) unknownTypeCount++;
|
|
|
+ else if (TYPE_KIND_MISMATCH.equals(mismatch)) typeKindMismatchCount++;
|
|
|
+ else nonFormalIndustryCount++;
|
|
|
continue;
|
|
|
}
|
|
|
- pointCodes.add(point.getPointCode().trim());
|
|
|
+ String markerIndustry = industryOf(type);
|
|
|
+ String markerTypeCode = point.getTypeCode();
|
|
|
+ Integer markerDistrict = CockpitDistrict.codeOf(point.getDistrict());
|
|
|
+ if (!matchesFilters(selectedIndustry, deviceType, selectedDistrict,
|
|
|
+ markerIndustry, point.getTypeCode(), markerDistrict, type)
|
|
|
+ || !selectedStatus.matchesPoint(status)) continue;
|
|
|
+ CockpitCoordinateValidation validation = CockpitCoordinateValidator.validate(
|
|
|
+ point.getLongitude(), point.getLatitude(), point.isCoordinateVerified());
|
|
|
deviceCodes.addAll(codes(effective));
|
|
|
+ pointCodes.add(point.getPointCode().trim());
|
|
|
pointMarkers.add(new Marker(
|
|
|
point.getPointCode().trim(),
|
|
|
point.getPointName(),
|
|
|
markerDistrict,
|
|
|
markerIndustry,
|
|
|
markerTypeCode,
|
|
|
- type == null ? "未知" : type.getName(),
|
|
|
+ type == null ? null : type.getName(),
|
|
|
status,
|
|
|
CockpitMonitoringPointStatusResolver.summaryReasons(status, effective),
|
|
|
point.getLongitude(),
|
|
|
@@ -324,39 +301,30 @@ public class CockpitMonitoringDistributionService {
|
|
|
|
|
|
if (snapshot.getSmartDevices() != null) {
|
|
|
for (CockpitMonitoringDistributionSnapshot.SourceSmartDevice device : snapshot.getSmartDevices()) {
|
|
|
- if (device == null || isBlank(device.getDeviceCode())
|
|
|
- || associatedDeviceCodes.contains(device.getDeviceCode().trim())) {
|
|
|
+ if (device == null || isBlank(device.getDeviceCode())) {
|
|
|
continue;
|
|
|
}
|
|
|
+ if (associatedCodes.contains(device.getDeviceCode().trim())) continue;
|
|
|
+ global.addUnassociatedDevice();
|
|
|
CockpitMonitoringDistributionSnapshot.SourceType type = resolveType(types, device.getTypeCode());
|
|
|
String mismatch = classify(type);
|
|
|
if (mismatch != null) {
|
|
|
- if (UNKNOWN_TYPE.equals(mismatch)) {
|
|
|
- unknownTypeCount++;
|
|
|
- } else if (TYPE_KIND_MISMATCH.equals(mismatch)) {
|
|
|
- typeKindMismatchCount++;
|
|
|
- } else {
|
|
|
- nonFormalIndustryCount++;
|
|
|
- }
|
|
|
- continue;
|
|
|
- }
|
|
|
- if (selectedIndustry != null && !selectedIndustry.equals(type.getIndustry())) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- if (!isBlank(deviceType) && !deviceType.trim().equals(type.getCode())) {
|
|
|
+ if (UNKNOWN_TYPE.equals(mismatch)) unknownTypeCount++;
|
|
|
+ else if (TYPE_KIND_MISMATCH.equals(mismatch)) typeKindMismatchCount++;
|
|
|
+ else nonFormalIndustryCount++;
|
|
|
continue;
|
|
|
}
|
|
|
+ String markerIndustry = industryOf(type);
|
|
|
+ String markerTypeCode = device.getTypeCode();
|
|
|
+ Integer markerDistrict = CockpitDistrict.codeOf(device.getDistrict());
|
|
|
+ if (!matchesFilters(selectedIndustry, deviceType, selectedDistrict,
|
|
|
+ markerIndustry, device.getTypeCode(), markerDistrict, type)) continue;
|
|
|
CockpitCoordinateValidation validation = CockpitCoordinateValidator.validate(
|
|
|
device.getLongitude(), device.getLatitude(), device.isCoordinateVerified());
|
|
|
- // 设备自身片区(equipment_base.district)即权威来源
|
|
|
- Integer markerDistrict = CockpitDistrict.codeOf(device.getDistrict());
|
|
|
- if (!CockpitDistrict.matches(selectedDistrict, markerDistrict)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
CockpitMonitoringPointDeviceSnapshot fact = new CockpitMonitoringPointDeviceSnapshot(
|
|
|
device.getDeviceCode().trim(),
|
|
|
device.getDeviceName(),
|
|
|
- type.getName(),
|
|
|
+ type == null ? null : type.getName(),
|
|
|
device.getLongitude(),
|
|
|
device.getLatitude(),
|
|
|
validation,
|
|
|
@@ -365,23 +333,24 @@ public class CockpitMonitoringDistributionService {
|
|
|
device.isWarning(),
|
|
|
device.isWorkOrderInProgress(),
|
|
|
device.getStatusReasons(),
|
|
|
- device.getStatusTime());
|
|
|
+ device.getStatusTime(), device.isStatusPublished());
|
|
|
CockpitMonitoringPointDeviceSnapshot applied = effectiveDevice(fact);
|
|
|
- CockpitMonitoringPointStatus status = CockpitMonitoringPointStatusResolver.deviceStatus(applied);
|
|
|
- if (selectedStatus != null && selectedStatus != status) {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ String publishedStatus = publishedStatus(applied);
|
|
|
+ CockpitMonitoringPointStatus status = "UNKNOWN".equals(publishedStatus)
|
|
|
+ ? null : CockpitMonitoringPointStatus.valueOf(publishedStatus);
|
|
|
+ if (!selectedStatus.matchesDevice(publishedStatus)) continue;
|
|
|
deviceCodes.add(applied.getDeviceCode().trim());
|
|
|
deviceMarkers.add(new Marker(
|
|
|
applied.getDeviceCode().trim(),
|
|
|
applied.getDeviceName(),
|
|
|
markerDistrict,
|
|
|
- type.getIndustry(),
|
|
|
- type.getCode(),
|
|
|
- type.getName(),
|
|
|
+ markerIndustry,
|
|
|
+ markerTypeCode,
|
|
|
+ type == null ? null : type.getName(),
|
|
|
status,
|
|
|
- CockpitMonitoringPointStatusResolver.summaryReasons(
|
|
|
- status, Collections.singletonList(applied)),
|
|
|
+ status == null ? applied.getStatusReasons()
|
|
|
+ : CockpitMonitoringPointStatusResolver.summaryReasons(
|
|
|
+ status, Collections.singletonList(applied)),
|
|
|
device.getLongitude(),
|
|
|
device.getLatitude(),
|
|
|
validation,
|
|
|
@@ -397,18 +366,15 @@ public class CockpitMonitoringDistributionService {
|
|
|
Map<Integer, Accumulator> byDistrict = new LinkedHashMap<>();
|
|
|
Map<String, Accumulator> byIndustry = new LinkedHashMap<>();
|
|
|
Map<String, Accumulator> byType = new LinkedHashMap<>();
|
|
|
- int[] statusCounts = new int[CockpitMonitoringPointStatus.values().length];
|
|
|
for (Marker marker : pointMarkers) {
|
|
|
accumulate(byDistrict, marker.district, marker);
|
|
|
accumulate(byIndustry, marker.industry, marker);
|
|
|
accumulate(byType, marker.typeCode, marker);
|
|
|
- statusCounts[marker.status.ordinal()]++;
|
|
|
}
|
|
|
for (Marker marker : deviceMarkers) {
|
|
|
accumulate(byDistrict, marker.district, marker);
|
|
|
accumulate(byIndustry, marker.industry, marker);
|
|
|
accumulate(byType, marker.typeCode, marker);
|
|
|
- statusCounts[marker.status.ordinal()]++;
|
|
|
}
|
|
|
|
|
|
List<CockpitMonitoringDistributionResponse.DistrictDistribution> districtRows =
|
|
|
@@ -417,12 +383,7 @@ public class CockpitMonitoringDistributionService {
|
|
|
industryRows(byIndustry);
|
|
|
List<CockpitMonitoringDistributionResponse.DeviceTypeDistribution> typeRows =
|
|
|
typeRows(byType);
|
|
|
- List<CockpitMonitoringDistributionResponse.PointStatusCount> statusRows =
|
|
|
- new ArrayList<>();
|
|
|
- for (CockpitMonitoringPointStatus status : CockpitMonitoringPointStatus.values()) {
|
|
|
- statusRows.add(new CockpitMonitoringDistributionResponse.PointStatusCount(
|
|
|
- status, statusCounts[status.ordinal()]));
|
|
|
- }
|
|
|
+ List<CockpitPointStatusQuery.Count> statusRows = global.rows();
|
|
|
|
|
|
int total = pointMarkers.size() + deviceMarkers.size();
|
|
|
int totalPages = total == 0 ? 0 : (total + pageSize - 1) / pageSize;
|
|
|
@@ -468,7 +429,7 @@ public class CockpitMonitoringDistributionService {
|
|
|
new CockpitMonitoringDistributionResponse.MapFilter(
|
|
|
selectedIndustry,
|
|
|
isBlank(deviceType) ? null : deviceType.trim(),
|
|
|
- selectedStatus == null ? null : selectedStatus.name(),
|
|
|
+ selectedStatus.echo(),
|
|
|
// 未筛与「只看未分区」的编码都是 null,靠 districtFilter 区分(口径同 map-snapshot)
|
|
|
selectedDistrict.code(),
|
|
|
!selectedDistrict.active() ? "NONE"
|
|
|
@@ -502,7 +463,10 @@ public class CockpitMonitoringDistributionService {
|
|
|
source.isWarning(),
|
|
|
source.isWorkOrderInProgress(),
|
|
|
source.getStatusReasons(),
|
|
|
- source.getStatusTime());
|
|
|
+ source.getStatusTime(),
|
|
|
+ runtime == null || runtime.serverUpdatedAt == null
|
|
|
+ ? source.isStatusPublished()
|
|
|
+ : "ONLINE".equals(runtime.monitoringState) || "OFFLINE".equals(runtime.monitoringState));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -637,18 +601,20 @@ public class CockpitMonitoringDistributionService {
|
|
|
return query;
|
|
|
}
|
|
|
|
|
|
- private static CockpitMonitoringPointStatus resolvePointStatus(String pointStatus) {
|
|
|
- if (isBlank(pointStatus)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- String resolved = pointStatus.trim();
|
|
|
- for (CockpitMonitoringPointStatus status : CockpitMonitoringPointStatus.values()) {
|
|
|
- if (status.name().equals(resolved)) {
|
|
|
- return status;
|
|
|
- }
|
|
|
- }
|
|
|
- throw new CockpitInvalidRequestException(
|
|
|
- "pointStatus must be ALARM, WARNING, WORK_ORDER, NORMAL, OFFLINE or EMPTY");
|
|
|
+ private String industryOf(CockpitMonitoringDistributionSnapshot.SourceType type) {
|
|
|
+ return classify(type) == null ? type.getIndustry() : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static boolean matchesFilters(String industry, String deviceType, CockpitDistrict.Query district,
|
|
|
+ String actualIndustry, String typeCode, Integer actualDistrict,
|
|
|
+ CockpitMonitoringDistributionSnapshot.SourceType type) {
|
|
|
+ return (industry == null || industry.equals(actualIndustry))
|
|
|
+ && (isBlank(deviceType) || (type != null && deviceType.trim().equals(typeCode)))
|
|
|
+ && CockpitDistrict.matches(district, actualDistrict);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String publishedStatus(CockpitMonitoringPointDeviceSnapshot device) {
|
|
|
+ return CockpitPointStatusQuery.deviceBucket(device.getPublishedDeviceStatus());
|
|
|
}
|
|
|
|
|
|
private static void validatePagination(int page, int pageSize, String snapshotAt) {
|
|
|
@@ -822,7 +788,7 @@ public class CockpitMonitoringDistributionService {
|
|
|
} else {
|
|
|
deviceCount++;
|
|
|
}
|
|
|
- statusCounts[marker.status.ordinal()]++;
|
|
|
+ if (marker.status != null) statusCounts[marker.status.ordinal()]++;
|
|
|
if (typeName == null) {
|
|
|
typeName = marker.typeName;
|
|
|
}
|