Commit fec84ce8822492f4fde92dfbd520975073c4da5b
1 parent
5ffcf59f
fix(form): fuzzy prefill alignment key<->label (e2e-found: model says 客户, field …
…is 客户名称 — exact match silently dropped the prefill); deterministic tie-break prefers unique fkselect/名称 field, ambiguous cases skip
Showing
2 changed files
with
96 additions
and
4 deletions
src/main/java/com/xly/tool/FormCollectTool.java
| @@ -114,10 +114,6 @@ public class FormCollectTool { | @@ -114,10 +114,6 @@ public class FormCollectTool { | ||
| 114 | if (hint != null && !String.valueOf(hint).isBlank()) { | 114 | if (hint != null && !String.valueOf(hint).isBlank()) { |
| 115 | f.put("hint", String.valueOf(hint)); | 115 | f.put("hint", String.valueOf(hint)); |
| 116 | } | 116 | } |
| 117 | - String pre = known.get(normLabel(String.valueOf(f.get("label")))); | ||
| 118 | - if (pre != null && !pre.isBlank()) { | ||
| 119 | - f.put("default", pre); // 预填用户已说的值 | ||
| 120 | - } | ||
| 121 | fields.add(f); | 117 | fields.add(f); |
| 122 | } | 118 | } |
| 123 | 119 | ||
| @@ -157,6 +153,7 @@ public class FormCollectTool { | @@ -157,6 +153,7 @@ public class FormCollectTool { | ||
| 157 | if (fields.isEmpty()) { | 153 | if (fields.isEmpty()) { |
| 158 | return err("该表单没有可填写的业务字段。"); | 154 | return err("该表单没有可填写的业务字段。"); |
| 159 | } | 155 | } |
| 156 | + applyKnownDefaults(known, fields); | ||
| 160 | 157 | ||
| 161 | Map<String, Object> out = new LinkedHashMap<>(); | 158 | Map<String, Object> out = new LinkedHashMap<>(); |
| 162 | out.put("type", "form_collect"); | 159 | out.put("type", "form_collect"); |
| @@ -258,6 +255,59 @@ public class FormCollectTool { | @@ -258,6 +255,59 @@ public class FormCollectTool { | ||
| 258 | } | 255 | } |
| 259 | 256 | ||
| 260 | /** | 257 | /** |
| 258 | + * 把用户已说的值预填进表单字段。键与字段标签先按归一化全等匹配;对不上再做**宽松对齐** | ||
| 259 | + * (「客户」→「客户名称」这类互含关系),多个候选字段时用确定性决胜:唯一的外键下拉字段 | ||
| 260 | + * 或唯一以「名称」结尾的字段,仍二义则放弃预填(宁缺勿错,用户在表单里自己填)。 | ||
| 261 | + */ | ||
| 262 | + static void applyKnownDefaults(Map<String, String> known, List<Map<String, Object>> fields) { | ||
| 263 | + List<String> normLabels = new ArrayList<>(); | ||
| 264 | + for (Map<String, Object> f : fields) { | ||
| 265 | + normLabels.add(normLabel(String.valueOf(f.get("label")))); | ||
| 266 | + } | ||
| 267 | + Map<String, String> aligned = new HashMap<>(); | ||
| 268 | + for (Map.Entry<String, String> e : known.entrySet()) { | ||
| 269 | + String key = e.getKey(); | ||
| 270 | + if (key.isEmpty() || e.getValue() == null || e.getValue().isBlank()) { | ||
| 271 | + continue; | ||
| 272 | + } | ||
| 273 | + if (normLabels.contains(key)) { | ||
| 274 | + aligned.put(key, e.getValue()); | ||
| 275 | + continue; | ||
| 276 | + } | ||
| 277 | + List<Integer> hits = new ArrayList<>(); | ||
| 278 | + for (int i = 0; i < normLabels.size(); i++) { | ||
| 279 | + String l = normLabels.get(i); | ||
| 280 | + if (!l.isEmpty() && (l.contains(key) || key.contains(l))) { | ||
| 281 | + hits.add(i); | ||
| 282 | + } | ||
| 283 | + } | ||
| 284 | + Integer pick = null; | ||
| 285 | + if (hits.size() == 1) { | ||
| 286 | + pick = hits.get(0); | ||
| 287 | + } else if (hits.size() > 1) { | ||
| 288 | + List<Integer> fk = hits.stream() | ||
| 289 | + .filter(i -> "fkselect".equals(fields.get(i).get("type"))).toList(); | ||
| 290 | + List<Integer> named = hits.stream() | ||
| 291 | + .filter(i -> normLabels.get(i).endsWith("名称")).toList(); | ||
| 292 | + if (fk.size() == 1) { | ||
| 293 | + pick = fk.get(0); | ||
| 294 | + } else if (named.size() == 1) { | ||
| 295 | + pick = named.get(0); | ||
| 296 | + } | ||
| 297 | + } | ||
| 298 | + if (pick != null) { | ||
| 299 | + aligned.putIfAbsent(normLabels.get(pick), e.getValue()); | ||
| 300 | + } | ||
| 301 | + } | ||
| 302 | + for (int i = 0; i < fields.size(); i++) { | ||
| 303 | + String pre = aligned.get(normLabels.get(i)); | ||
| 304 | + if (pre != null && !pre.isBlank()) { | ||
| 305 | + fields.get(i).put("default", pre); // 用户说过的值优先于表单默认值 | ||
| 306 | + } | ||
| 307 | + } | ||
| 308 | + } | ||
| 309 | + | ||
| 310 | + /** | ||
| 261 | * 预填值里的尺寸表达(「50*30*5」「长和宽50,高5cm」)确定性拆成 长/宽/高 三个键 | 311 | * 预填值里的尺寸表达(「50*30*5」「长和宽50,高5cm」)确定性拆成 长/宽/高 三个键 |
| 262 | * (模型拆不对是实测结论——原 SlotFillService 的正则迁于此)。已有的 长/宽/高 预填不覆盖。 | 312 | * (模型拆不对是实测结论——原 SlotFillService 的正则迁于此)。已有的 长/宽/高 预填不覆盖。 |
| 263 | */ | 313 | */ |
src/test/java/com/xly/tool/FormCollectDimensionTest.java
| @@ -65,4 +65,46 @@ class FormCollectDimensionTest { | @@ -65,4 +65,46 @@ class FormCollectDimensionTest { | ||
| 65 | assertFalse(m.containsKey("长"), "普通数值/名称/电话不产生尺寸"); | 65 | assertFalse(m.containsKey("长"), "普通数值/名称/电话不产生尺寸"); |
| 66 | assertEquals("大16开画册", m.get("产品名称"), "值逐字保留"); | 66 | assertEquals("大16开画册", m.get("产品名称"), "值逐字保留"); |
| 67 | } | 67 | } |
| 68 | + | ||
| 69 | + // ---- 预填键 ↔ 字段标签的宽松对齐(e2e 实测缺陷:模型给「客户」,字段叫「客户名称」,全等匹配丢预填)---- | ||
| 70 | + | ||
| 71 | + private static java.util.List<Map<String, Object>> fields(String... labelTypePairs) { | ||
| 72 | + java.util.List<Map<String, Object>> out = new java.util.ArrayList<>(); | ||
| 73 | + for (int i = 0; i < labelTypePairs.length; i += 2) { | ||
| 74 | + Map<String, Object> f = new LinkedHashMap<>(); | ||
| 75 | + f.put("label", labelTypePairs[i]); | ||
| 76 | + f.put("type", labelTypePairs[i + 1]); | ||
| 77 | + out.add(f); | ||
| 78 | + } | ||
| 79 | + return out; | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + @Test | ||
| 83 | + void fuzzyKeyPrefillsContainingLabel() { | ||
| 84 | + var fs = fields("客户名称", "fkselect", "产品名称", "fkselect", "数量", "number"); | ||
| 85 | + FormCollectTool.applyKnownDefaults(known("客户", "必胜客", "数量", "5000"), fs); | ||
| 86 | + assertEquals("必胜客", fs.get(0).get("default"), "「客户」对齐到「客户名称」"); | ||
| 87 | + assertEquals("5000", fs.get(2).get("default")); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + @Test | ||
| 91 | + void ambiguousKeyPrefersFkOrNamedField() { | ||
| 92 | + var fs = fields("客户代码", "text", "客户名称", "fkselect"); | ||
| 93 | + FormCollectTool.applyKnownDefaults(known("客户", "必胜客"), fs); | ||
| 94 | + assertEquals("必胜客", fs.get(1).get("default"), "多候选时唯一外键下拉字段胜出"); | ||
| 95 | + assertFalse(fs.get(0).containsKey("default")); | ||
| 96 | + | ||
| 97 | + var fs2 = fields("客户代码", "text", "客户简称", "text"); | ||
| 98 | + FormCollectTool.applyKnownDefaults(known("客户", "必胜客"), fs2); | ||
| 99 | + assertFalse(fs2.get(0).containsKey("default"), "无法决胜的二义 → 宁缺勿错"); | ||
| 100 | + assertFalse(fs2.get(1).containsKey("default")); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + @Test | ||
| 104 | + void exactMatchAndParenLabelStillWork() { | ||
| 105 | + var fs = fields("长(L)", "number", "宽(W)", "number"); | ||
| 106 | + FormCollectTool.applyKnownDefaults(known("长", "50", "宽", "30"), fs); | ||
| 107 | + assertEquals("50", fs.get(0).get("default"), "括号标签归一化后全等命中"); | ||
| 108 | + assertEquals("30", fs.get(1).get("default")); | ||
| 109 | + } | ||
| 68 | } | 110 | } |