import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* T32 真实数据 × 生产判据 联测探针。
*
*
只读:读取从真实后端导出的坐标文件,逐条调用**已编译的生产类**
* {@code com.zksy.cockpit.foundation.CockpitCoordinateValidator},统计准入分布。
* 不修改数据、不启动服务,只回答一个问题:维护者重启后端后,真实数据会有多少可上图。
*
*
被测判据就是线上要跑的那一份,因此本探针验证的是真实实现而非另写的复算脚本。
* 用法:java -cp LiveCoordinateAdmissionProbe
*/
public final class LiveCoordinateAdmissionProbe {
public static void main(String[] args) throws Exception {
Path input = Path.of(args[0]);
List lines = Files.readAllLines(input, StandardCharsets.UTF_8);
Map byReason = new LinkedHashMap<>();
int mapRenderable = 0;
int mapTotal = 0;
int geometryRenderable = 0;
int geometryTotal = 0;
List blockedSamples = new ArrayList<>();
int boundaryHits = 0;
for (String raw : lines) {
String line = raw.trim();
if (line.isEmpty()) continue;
String[] parts = line.split("\\|", -1);
if (parts.length < 3) continue;
// 行格式:地图对象 `M|code|lon|lat`;几何 `G|source|lon,lat[;lon,lat...]`。
boolean geometry = "G".equals(parts[0]);
String label = geometry ? parts[1] : "map-object";
String code = geometry ? "" : parts[1];
String payload = geometry ? parts[2] : (parts[2] + "," + parts[3]);
boolean renderable = true;
String reason = "";
// 管段按逐端点校验:任一端不可渲染则整段不可渲染(与服务层同口径)。
for (String pair : payload.split(";")) {
if (pair.isEmpty()) continue;
String[] xy = pair.split(",", -1);
BigDecimal lon = parse(xy.length > 0 ? xy[0] : null);
BigDecimal lat = parse(xy.length > 1 ? xy[1] : null);
var validation = com.zksy.cockpit.foundation.CockpitCoordinateValidator
.validate(lon, lat, false);
if (lon != null && lat != null
&& (lon.compareTo(com.zksy.cockpit.foundation.CockpitCoordinateValidator.WEST) == 0
|| lon.compareTo(com.zksy.cockpit.foundation.CockpitCoordinateValidator.EAST) == 0
|| lat.compareTo(com.zksy.cockpit.foundation.CockpitCoordinateValidator.SOUTH) == 0
|| lat.compareTo(com.zksy.cockpit.foundation.CockpitCoordinateValidator.NORTH) == 0)) {
boundaryHits++;
}
if (!validation.isRenderable()) {
renderable = false;
reason = validation.getReason();
break;
}
}
if (!renderable) {
byReason.merge(reason, 1, Integer::sum);
if (blockedSamples.size() < 6) {
blockedSamples.add(label + " " + code + " → " + reason);
}
}
if (geometry) {
geometryTotal++;
if (renderable) geometryRenderable++;
} else {
mapTotal++;
if (renderable) mapRenderable++;
}
}
System.out.println("input=" + input.getFileName());
System.out.println("map-snapshot : renderable " + mapRenderable + "/" + mapTotal);
System.out.println("infrastructure/geo: renderable " + geometryRenderable + "/" + geometryTotal);
System.out.println("blocked by reason : " + byReason);
System.out.println("boundary hits : " + boundaryHits);
System.out.println("blocked samples : " + blockedSamples);
boolean mapMatches = mapRenderable == 12 && mapTotal == 15;
boolean geomMatches = geometryRenderable == 5269 && geometryTotal == 5272;
boolean invalidOnly = byReason.keySet().stream()
.allMatch(r -> "ZERO_COORDINATE".equals(r) || "LONGITUDE_OUT_OF_RANGE".equals(r)
|| "LATITUDE_OUT_OF_RANGE".equals(r) || "MISSING_LONGITUDE".equals(r)
|| "MISSING_LATITUDE".equals(r) || "OUTSIDE_PRIMARY_AREA".equals(r));
// 原因码分布也要钉住:只断言总数会掩盖「本来缺坐标的被误报成别的合法原因」。
Map expected = new LinkedHashMap<>();
expected.put("MISSING_LONGITUDE", 3);
expected.put("LONGITUDE_OUT_OF_RANGE", 2);
expected.put("OUTSIDE_PRIMARY_AREA", 1);
boolean reasonsMatch = expected.equals(byReason);
System.out.println("EXPECT map 12/15 : " + (mapMatches ? "PASS" : "FAIL"));
System.out.println("EXPECT geo 5269/5272: " + (geomMatches ? "PASS" : "FAIL"));
System.out.println("EXPECT reason split: " + (reasonsMatch ? "PASS" : "FAIL")
+ " (expected " + expected + ")");
System.out.println("no UNVERIFIED left : " + (invalidOnly ? "PASS" : "FAIL"));
if (!mapMatches || !geomMatches || !invalidOnly || !reasonsMatch) {
System.exit(1);
}
}
private static BigDecimal parse(String value) {
if (value == null) return null;
String trimmed = value.trim();
if (trimmed.isEmpty() || "null".equalsIgnoreCase(trimmed)) return null;
return new BigDecimal(trimmed);
}
}