LiveCoordinateAdmissionProbe.java 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import java.math.BigDecimal;
  2. import java.nio.charset.StandardCharsets;
  3. import java.nio.file.Files;
  4. import java.nio.file.Path;
  5. import java.util.ArrayList;
  6. import java.util.LinkedHashMap;
  7. import java.util.List;
  8. import java.util.Map;
  9. /**
  10. * T32 真实数据 × 生产判据 联测探针。
  11. *
  12. * <p>只读:读取从真实后端导出的坐标文件,逐条调用**已编译的生产类**
  13. * {@code com.zksy.cockpit.foundation.CockpitCoordinateValidator},统计准入分布。
  14. * 不修改数据、不启动服务,只回答一个问题:维护者重启后端后,真实数据会有多少可上图。
  15. *
  16. * <p>被测判据就是线上要跑的那一份,因此本探针验证的是真实实现而非另写的复算脚本。
  17. * 用法:java -cp <classes> LiveCoordinateAdmissionProbe <live-coordinates.txt>
  18. */
  19. public final class LiveCoordinateAdmissionProbe {
  20. public static void main(String[] args) throws Exception {
  21. Path input = Path.of(args[0]);
  22. List<String> lines = Files.readAllLines(input, StandardCharsets.UTF_8);
  23. Map<String, Integer> byReason = new LinkedHashMap<>();
  24. int mapRenderable = 0;
  25. int mapTotal = 0;
  26. int geometryRenderable = 0;
  27. int geometryTotal = 0;
  28. List<String> blockedSamples = new ArrayList<>();
  29. int boundaryHits = 0;
  30. for (String raw : lines) {
  31. String line = raw.trim();
  32. if (line.isEmpty()) continue;
  33. String[] parts = line.split("\\|", -1);
  34. if (parts.length < 3) continue;
  35. // 行格式:地图对象 `M|code|lon|lat`;几何 `G|source|lon,lat[;lon,lat...]`。
  36. boolean geometry = "G".equals(parts[0]);
  37. String label = geometry ? parts[1] : "map-object";
  38. String code = geometry ? "" : parts[1];
  39. String payload = geometry ? parts[2] : (parts[2] + "," + parts[3]);
  40. boolean renderable = true;
  41. String reason = "";
  42. // 管段按逐端点校验:任一端不可渲染则整段不可渲染(与服务层同口径)。
  43. for (String pair : payload.split(";")) {
  44. if (pair.isEmpty()) continue;
  45. String[] xy = pair.split(",", -1);
  46. BigDecimal lon = parse(xy.length > 0 ? xy[0] : null);
  47. BigDecimal lat = parse(xy.length > 1 ? xy[1] : null);
  48. var validation = com.zksy.cockpit.foundation.CockpitCoordinateValidator
  49. .validate(lon, lat, false);
  50. if (lon != null && lat != null
  51. && (lon.compareTo(com.zksy.cockpit.foundation.CockpitCoordinateValidator.WEST) == 0
  52. || lon.compareTo(com.zksy.cockpit.foundation.CockpitCoordinateValidator.EAST) == 0
  53. || lat.compareTo(com.zksy.cockpit.foundation.CockpitCoordinateValidator.SOUTH) == 0
  54. || lat.compareTo(com.zksy.cockpit.foundation.CockpitCoordinateValidator.NORTH) == 0)) {
  55. boundaryHits++;
  56. }
  57. if (!validation.isRenderable()) {
  58. renderable = false;
  59. reason = validation.getReason();
  60. break;
  61. }
  62. }
  63. if (!renderable) {
  64. byReason.merge(reason, 1, Integer::sum);
  65. if (blockedSamples.size() < 6) {
  66. blockedSamples.add(label + " " + code + " → " + reason);
  67. }
  68. }
  69. if (geometry) {
  70. geometryTotal++;
  71. if (renderable) geometryRenderable++;
  72. } else {
  73. mapTotal++;
  74. if (renderable) mapRenderable++;
  75. }
  76. }
  77. System.out.println("input=" + input.getFileName());
  78. System.out.println("map-snapshot : renderable " + mapRenderable + "/" + mapTotal);
  79. System.out.println("infrastructure/geo: renderable " + geometryRenderable + "/" + geometryTotal);
  80. System.out.println("blocked by reason : " + byReason);
  81. System.out.println("boundary hits : " + boundaryHits);
  82. System.out.println("blocked samples : " + blockedSamples);
  83. boolean mapMatches = mapRenderable == 12 && mapTotal == 15;
  84. boolean geomMatches = geometryRenderable == 5269 && geometryTotal == 5272;
  85. boolean invalidOnly = byReason.keySet().stream()
  86. .allMatch(r -> "ZERO_COORDINATE".equals(r) || "LONGITUDE_OUT_OF_RANGE".equals(r)
  87. || "LATITUDE_OUT_OF_RANGE".equals(r) || "MISSING_LONGITUDE".equals(r)
  88. || "MISSING_LATITUDE".equals(r) || "OUTSIDE_PRIMARY_AREA".equals(r));
  89. // 原因码分布也要钉住:只断言总数会掩盖「本来缺坐标的被误报成别的合法原因」。
  90. Map<String, Integer> expected = new LinkedHashMap<>();
  91. expected.put("MISSING_LONGITUDE", 3);
  92. expected.put("LONGITUDE_OUT_OF_RANGE", 2);
  93. expected.put("OUTSIDE_PRIMARY_AREA", 1);
  94. boolean reasonsMatch = expected.equals(byReason);
  95. System.out.println("EXPECT map 12/15 : " + (mapMatches ? "PASS" : "FAIL"));
  96. System.out.println("EXPECT geo 5269/5272: " + (geomMatches ? "PASS" : "FAIL"));
  97. System.out.println("EXPECT reason split: " + (reasonsMatch ? "PASS" : "FAIL")
  98. + " (expected " + expected + ")");
  99. System.out.println("no UNVERIFIED left : " + (invalidOnly ? "PASS" : "FAIL"));
  100. if (!mapMatches || !geomMatches || !invalidOnly || !reasonsMatch) {
  101. System.exit(1);
  102. }
  103. }
  104. private static BigDecimal parse(String value) {
  105. if (value == null) return null;
  106. String trimmed = value.trim();
  107. if (trimmed.isEmpty() || "null".equalsIgnoreCase(trimmed)) return null;
  108. return new BigDecimal(trimmed);
  109. }
  110. }