Commit fec84ce8822492f4fde92dfbd520975073c4da5b

Authored by zichun
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
src/main/java/com/xly/tool/FormCollectTool.java
... ... @@ -114,10 +114,6 @@ public class FormCollectTool {
114 114 if (hint != null && !String.valueOf(hint).isBlank()) {
115 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 117 fields.add(f);
122 118 }
123 119  
... ... @@ -157,6 +153,7 @@ public class FormCollectTool {
157 153 if (fields.isEmpty()) {
158 154 return err("该表单没有可填写的业务字段。");
159 155 }
  156 + applyKnownDefaults(known, fields);
160 157  
161 158 Map<String, Object> out = new LinkedHashMap<>();
162 159 out.put("type", "form_collect");
... ... @@ -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 311 * 预填值里的尺寸表达(「50*30*5」「长和宽50,高5cm」)确定性拆成 长/宽/高 三个键
262 312 * (模型拆不对是实测结论——原 SlotFillService 的正则迁于此)。已有的 长/宽/高 预填不覆盖。
263 313 */
... ...
src/test/java/com/xly/tool/FormCollectDimensionTest.java
... ... @@ -65,4 +65,46 @@ class FormCollectDimensionTest {
65 65 assertFalse(m.containsKey("长"), "普通数值/名称/电话不产生尺寸");
66 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 }
... ...