|
@@ -124,16 +124,15 @@ public class DanReportParseService {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. 大五人格 (Big Five)
|
|
|
|
|
- Map<String, String> bigFive = new LinkedHashMap<>();
|
|
|
|
|
- int bigFiveStart = findSectionStart(lines, "大五人格", "人格特质", "人格分析", "人格测评");
|
|
|
|
|
- if (bigFiveStart >= 0) {
|
|
|
|
|
- bigFive.putAll(parseBigFive(lines, bigFiveStart));
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 2. 大五人格 (Big Five,1-5 分制)
|
|
|
|
|
+ // PDF 为自由排版多列文本,按行扫描易漏维度;优先用全文句式正则一次取齐 5 维
|
|
|
|
|
+ Map<String, String> bigFive = parseBigFiveQuoted(fullText);
|
|
|
if (bigFive.isEmpty()) {
|
|
if (bigFive.isEmpty()) {
|
|
|
- bigFive.putAll(searchBigFiveGlobally(lines));
|
|
|
|
|
|
|
+ int bigFiveStart = findSectionStart(lines, "大五人格", "人格特质", "人格分析", "人格测评");
|
|
|
|
|
+ if (bigFiveStart >= 0) {
|
|
|
|
|
+ bigFive.putAll(parseBigFive(lines, bigFiveStart));
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- extractQuotedBigFiveScores(fullText, bigFive);
|
|
|
|
|
if (!bigFive.isEmpty()) {
|
|
if (!bigFive.isEmpty()) {
|
|
|
for (Map.Entry<String, String> entry : bigFive.entrySet()) {
|
|
for (Map.Entry<String, String> entry : bigFive.entrySet()) {
|
|
|
items.add(new DataItem(entry.getKey(), entry.getKey(), entry.getValue(), "bigFive"));
|
|
items.add(new DataItem(entry.getKey(), entry.getKey(), entry.getValue(), "bigFive"));
|
|
@@ -141,31 +140,34 @@ public class DanReportParseService {
|
|
|
extra.put("bigFive", bigFive);
|
|
extra.put("bigFive", bigFive);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 3. 社会关系(与父母/同伴)
|
|
|
|
|
- int socialStart = findSectionStart(lines, "社会关系", "人际关系", "家庭关系", "同伴关系");
|
|
|
|
|
- if (socialStart >= 0) {
|
|
|
|
|
- Map<String, String> social = parseSocialRelations(lines, socialStart);
|
|
|
|
|
- if (!social.isEmpty()) {
|
|
|
|
|
- for (Map.Entry<String, String> entry : social.entrySet()) {
|
|
|
|
|
- items.add(new DataItem(entry.getKey(), entry.getKey(), entry.getValue(), "social"));
|
|
|
|
|
- }
|
|
|
|
|
- extra.put("social", social);
|
|
|
|
|
|
|
+ // 3. 情绪稳定性(自卑/抑郁/焦虑/无力感 4 分测验 + 总分/平均分)
|
|
|
|
|
+ Map<String, String> emotion = parseEmotionStability(fullText);
|
|
|
|
|
+ if (!emotion.isEmpty()) {
|
|
|
|
|
+ for (Map.Entry<String, String> entry : emotion.entrySet()) {
|
|
|
|
|
+ items.add(new DataItem(entry.getKey(), entry.getKey(), entry.getValue(), "emotion"));
|
|
|
}
|
|
}
|
|
|
|
|
+ extra.put("emotion", emotion);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 4. 情绪状态
|
|
|
|
|
- int emotionStart = findSectionStart(lines, "情绪状态", "情绪", "情感");
|
|
|
|
|
- if (emotionStart >= 0) {
|
|
|
|
|
- Map<String, String> emotion = parseEmotionState(lines, emotionStart);
|
|
|
|
|
- if (!emotion.isEmpty()) {
|
|
|
|
|
- for (Map.Entry<String, String> entry : emotion.entrySet()) {
|
|
|
|
|
- items.add(new DataItem(entry.getKey(), entry.getKey(), entry.getValue(), "emotion"));
|
|
|
|
|
- }
|
|
|
|
|
- extra.put("emotion", emotion);
|
|
|
|
|
|
|
+ // 4. 社会关系(依恋:信任/沟通/亲近 × 母亲/父亲/同伴)
|
|
|
|
|
+ Map<String, String> social = parseSocialAttachment(fullText);
|
|
|
|
|
+ if (!social.isEmpty()) {
|
|
|
|
|
+ for (Map.Entry<String, String> entry : social.entrySet()) {
|
|
|
|
|
+ items.add(new DataItem(entry.getKey(), entry.getKey(), entry.getValue(), "social"));
|
|
|
}
|
|
}
|
|
|
|
|
+ extra.put("social", social);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 5. 综合能力
|
|
|
|
|
|
|
+ // 5. 体质健康(身高/体重/BMI + 饮食/睡眠/运动习惯)
|
|
|
|
|
+ Map<String, String> body = parsePhysicalHealth(fullText);
|
|
|
|
|
+ if (!body.isEmpty()) {
|
|
|
|
|
+ for (Map.Entry<String, String> entry : body.entrySet()) {
|
|
|
|
|
+ items.add(new DataItem(entry.getKey(), entry.getKey(), entry.getValue(), "body"));
|
|
|
|
|
+ }
|
|
|
|
|
+ extra.put("body", body);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 综合能力(兜底:多数 A2 报告无独立综合能力页)
|
|
|
int abilityStart = findSectionStart(lines, "综合能力", "综合评估", "综合");
|
|
int abilityStart = findSectionStart(lines, "综合能力", "综合评估", "综合");
|
|
|
if (abilityStart >= 0) {
|
|
if (abilityStart >= 0) {
|
|
|
Map<String, String> ability = parseComprehensiveAbility(lines, abilityStart);
|
|
Map<String, String> ability = parseComprehensiveAbility(lines, abilityStart);
|
|
@@ -177,12 +179,13 @@ public class DanReportParseService {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 6. 通用正则:提取所有 "维度名: 分数" 模式
|
|
|
|
|
|
|
+ // 7. 通用正则:提取所有 "维度名: 分数" 模式(兜底)
|
|
|
Pattern scorePattern = Pattern.compile("([\\u4e00-\\u9fa5]{2,8})[::]\\s*(\\d+(\\.\\d+)?)");
|
|
Pattern scorePattern = Pattern.compile("([\\u4e00-\\u9fa5]{2,8})[::]\\s*(\\d+(\\.\\d+)?)");
|
|
|
Matcher matcher = scorePattern.matcher(fullText);
|
|
Matcher matcher = scorePattern.matcher(fullText);
|
|
|
while (matcher.find()) {
|
|
while (matcher.find()) {
|
|
|
String key = matcher.group(1).trim();
|
|
String key = matcher.group(1).trim();
|
|
|
String val = matcher.group(2).trim();
|
|
String val = matcher.group(2).trim();
|
|
|
|
|
+ if (isNoiseKey(key)) continue;
|
|
|
// 去重:避免与已解析项重复
|
|
// 去重:避免与已解析项重复
|
|
|
boolean exists = false;
|
|
boolean exists = false;
|
|
|
for (DataItem item : items) {
|
|
for (DataItem item : items) {
|
|
@@ -198,7 +201,7 @@ public class DanReportParseService {
|
|
|
|
|
|
|
|
// 总结和建议
|
|
// 总结和建议
|
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
|
- suggestions.append(extractSection(fullText, "成长建议", null));
|
|
|
|
|
|
|
+ suggestions.append(sanitizeDanSuggestions(extractSection(fullText, "成长建议", null)));
|
|
|
|
|
|
|
|
return new DanParsedReport("A2", "mind", items, extra, summary.toString(), suggestions.toString());
|
|
return new DanParsedReport("A2", "mind", items, extra, summary.toString(), suggestions.toString());
|
|
|
}
|
|
}
|
|
@@ -256,7 +259,7 @@ public class DanReportParseService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
|
- suggestions.append(extractSection(fullText, "成长建议", null));
|
|
|
|
|
|
|
+ suggestions.append(sanitizeDanSuggestions(extractSection(fullText, "成长建议", null)));
|
|
|
|
|
|
|
|
return new DanParsedReport("B4", "wisdom", items, extra, summary.toString(), suggestions.toString());
|
|
return new DanParsedReport("B4", "wisdom", items, extra, summary.toString(), suggestions.toString());
|
|
|
}
|
|
}
|
|
@@ -295,12 +298,13 @@ public class DanReportParseService {
|
|
|
String[] cognitiveDims = {"感知觉", "注意力", "记忆力", "推理能力", "空间能力", "加工速度"};
|
|
String[] cognitiveDims = {"感知觉", "注意力", "记忆力", "推理能力", "空间能力", "加工速度"};
|
|
|
String[] dimCodes = {"perception", "attention", "memory", "reasoning", "spatial", "processingSpeed"};
|
|
String[] dimCodes = {"perception", "attention", "memory", "reasoning", "spatial", "processingSpeed"};
|
|
|
|
|
|
|
|
- // 方法1: 从summary页面提取百分位 (格式: "感知觉 | Perception\n百分位(%)\n14")
|
|
|
|
|
|
|
+ // 方法1: 从summary页面提取百分位(DOTALL 支持跨行描述文本)
|
|
|
for (int i = 0; i < cognitiveDims.length; i++) {
|
|
for (int i = 0; i < cognitiveDims.length; i++) {
|
|
|
String dim = cognitiveDims[i];
|
|
String dim = cognitiveDims[i];
|
|
|
String code = dimCodes[i];
|
|
String code = dimCodes[i];
|
|
|
Pattern p1 = Pattern.compile(
|
|
Pattern p1 = Pattern.compile(
|
|
|
- Pattern.quote(dim) + "\\s*\\|[^\\n]*\\n\\s*百分位(%)\\s*\\n\\s*(\\d+)");
|
|
|
|
|
|
|
+ Pattern.quote(dim) + "\\s*\\|[^\\n]*\\n\\s*百分位(%)\\s*\\n\\s*(\\d+)",
|
|
|
|
|
+ Pattern.DOTALL);
|
|
|
Matcher m1 = p1.matcher(fullText);
|
|
Matcher m1 = p1.matcher(fullText);
|
|
|
if (m1.find()) {
|
|
if (m1.find()) {
|
|
|
String val = m1.group(1);
|
|
String val = m1.group(1);
|
|
@@ -309,16 +313,30 @@ public class DanReportParseService {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 方法2: 从detail页面提取原始分和百分位 (格式: "我的感知觉得分 41 | 14%")
|
|
|
|
|
|
|
+ // 方法2: 从detail页面提取原始分和百分位
|
|
|
|
|
+ // PDF 格式: "我的感知觉得分\n41|46%"(分隔符为 U+FF5C 全角竖线,非 ASCII |)
|
|
|
|
|
+ // 策略:取「我的xxx得分」下一行的所有数字,取前两个
|
|
|
for (int i = 0; i < cognitiveDims.length; i++) {
|
|
for (int i = 0; i < cognitiveDims.length; i++) {
|
|
|
String dim = cognitiveDims[i];
|
|
String dim = cognitiveDims[i];
|
|
|
String code = dimCodes[i];
|
|
String code = dimCodes[i];
|
|
|
- Pattern p2 = Pattern.compile(
|
|
|
|
|
- "我的" + Pattern.quote(dim) + "得分\\s*(\\d+)\\s*\\|\\s*(\\d+)%");
|
|
|
|
|
- Matcher m2 = p2.matcher(fullText);
|
|
|
|
|
- if (m2.find()) {
|
|
|
|
|
- String score = m2.group(1);
|
|
|
|
|
- String pct = m2.group(2);
|
|
|
|
|
|
|
+ int idx = fullText.indexOf("我的" + dim + "得分");
|
|
|
|
|
+ if (idx < 0) continue;
|
|
|
|
|
+ // 跳到下一行
|
|
|
|
|
+ int lineStart = fullText.indexOf('\n', idx);
|
|
|
|
|
+ if (lineStart < 0) continue;
|
|
|
|
|
+ lineStart++;
|
|
|
|
|
+ int lineEnd = fullText.indexOf('\n', lineStart);
|
|
|
|
|
+ if (lineEnd < 0) lineEnd = Math.min(lineStart + 30, fullText.length());
|
|
|
|
|
+ String line = fullText.substring(lineStart, lineEnd);
|
|
|
|
|
+ java.util.regex.Matcher numMatcher =
|
|
|
|
|
+ Pattern.compile("\\d+").matcher(line);
|
|
|
|
|
+ java.util.List<String> nums = new java.util.ArrayList<>();
|
|
|
|
|
+ while (numMatcher.find() && nums.size() < 2) {
|
|
|
|
|
+ nums.add(numMatcher.group());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (nums.size() >= 2) {
|
|
|
|
|
+ String score = nums.get(0);
|
|
|
|
|
+ String pct = nums.get(1);
|
|
|
items.add(new DataItem(code + "_score", dim + "得分", score, "cognitive"));
|
|
items.add(new DataItem(code + "_score", dim + "得分", score, "cognitive"));
|
|
|
extra.put(code + "_score", score);
|
|
extra.put(code + "_score", score);
|
|
|
if (!extra.containsKey(code + "_pct")) {
|
|
if (!extra.containsKey(code + "_pct")) {
|
|
@@ -328,7 +346,7 @@ public class DanReportParseService {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 方法3: 后备 - 直接搜索 "维度名\n百分位(%)\n数字" (某些PDF格式)
|
|
|
|
|
|
|
+ // 方法3: 后备 - 精确寻找「维度名...百分位(%)...数字」三者在同一窗口内
|
|
|
int foundPct = 0;
|
|
int foundPct = 0;
|
|
|
for (String code : dimCodes) {
|
|
for (String code : dimCodes) {
|
|
|
if (extra.containsKey(code + "_pct")) foundPct++;
|
|
if (extra.containsKey(code + "_pct")) foundPct++;
|
|
@@ -338,11 +356,15 @@ public class DanReportParseService {
|
|
|
String dim = cognitiveDims[i];
|
|
String dim = cognitiveDims[i];
|
|
|
String code = dimCodes[i];
|
|
String code = dimCodes[i];
|
|
|
if (!extra.containsKey(code + "_pct")) {
|
|
if (!extra.containsKey(code + "_pct")) {
|
|
|
- Pattern p3 = Pattern.compile(
|
|
|
|
|
- Pattern.quote(dim) + ".*?百分位(%).*?(\\d+)", Pattern.DOTALL);
|
|
|
|
|
- Matcher m3 = p3.matcher(fullText);
|
|
|
|
|
- if (m3.find()) {
|
|
|
|
|
- String val = m3.group(1);
|
|
|
|
|
|
|
+ int dimIdx = fullText.indexOf(dim);
|
|
|
|
|
+ if (dimIdx < 0) continue;
|
|
|
|
|
+ // 在该维度名之后 200 字符内找百分位数字
|
|
|
|
|
+ int searchEnd = Math.min(dimIdx + 300, fullText.length());
|
|
|
|
|
+ String window = fullText.substring(dimIdx, searchEnd);
|
|
|
|
|
+ java.util.regex.Matcher pctMatcher =
|
|
|
|
|
+ Pattern.compile("百分位[((]%[))].*?(\\d+)").matcher(window);
|
|
|
|
|
+ if (pctMatcher.find()) {
|
|
|
|
|
+ String val = pctMatcher.group(1);
|
|
|
items.add(new DataItem(code + "_pct", dim + "百分位", val, "cognitive"));
|
|
items.add(new DataItem(code + "_pct", dim + "百分位", val, "cognitive"));
|
|
|
extra.put(code + "_pct", val);
|
|
extra.put(code + "_pct", val);
|
|
|
}
|
|
}
|
|
@@ -370,7 +392,7 @@ public class DanReportParseService {
|
|
|
|
|
|
|
|
// 总结和建议
|
|
// 总结和建议
|
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
|
- suggestions.append(extractSection(fullText, "成长建议", null));
|
|
|
|
|
|
|
+ suggestions.append(sanitizeDanSuggestions(extractSection(fullText, "成长建议", null)));
|
|
|
|
|
|
|
|
return new DanParsedReport("A1", "cognition", items, extra, summary.toString(), suggestions.toString());
|
|
return new DanParsedReport("A1", "cognition", items, extra, summary.toString(), suggestions.toString());
|
|
|
}
|
|
}
|
|
@@ -469,7 +491,7 @@ public class DanReportParseService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
|
- suggestions.append(extractSection(fullText, "成长建议", null));
|
|
|
|
|
|
|
+ suggestions.append(sanitizeDanSuggestions(extractSection(fullText, "成长建议", null)));
|
|
|
|
|
|
|
|
return new DanParsedReport("B3", "learning", items, extra, summary.toString(), suggestions.toString());
|
|
return new DanParsedReport("B3", "learning", items, extra, summary.toString(), suggestions.toString());
|
|
|
}
|
|
}
|
|
@@ -589,7 +611,7 @@ public class DanReportParseService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
|
- suggestions.append(extractSection(fullText, "成长建议", null));
|
|
|
|
|
|
|
+ suggestions.append(sanitizeDanSuggestions(extractSection(fullText, "成长建议", null)));
|
|
|
return new DanParsedReport("B2", "behavior", items, extra, summary.toString(), suggestions.toString());
|
|
return new DanParsedReport("B2", "behavior", items, extra, summary.toString(), suggestions.toString());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -721,7 +743,7 @@ public class DanReportParseService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
|
- suggestions.append(extractSection(fullText, "成长建议", null));
|
|
|
|
|
|
|
+ suggestions.append(sanitizeDanSuggestions(extractSection(fullText, "成长建议", null)));
|
|
|
return new DanParsedReport("B6", "career", items, extra, summary.toString(), suggestions.toString());
|
|
return new DanParsedReport("B6", "career", items, extra, summary.toString(), suggestions.toString());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -843,7 +865,7 @@ public class DanReportParseService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
|
- suggestions.append(extractSection(fullText, "成长建议", null));
|
|
|
|
|
|
|
+ suggestions.append(sanitizeDanSuggestions(extractSection(fullText, "成长建议", null)));
|
|
|
return new DanParsedReport("C1", "campus", items, extra, summary.toString(), suggestions.toString());
|
|
return new DanParsedReport("C1", "campus", items, extra, summary.toString(), suggestions.toString());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1032,7 +1054,7 @@ public class DanReportParseService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
summary.append(extractSection(fullText, "测评总结", "成长建议"));
|
|
|
- suggestions.append(extractSection(fullText, "成长建议", null));
|
|
|
|
|
|
|
+ suggestions.append(sanitizeDanSuggestions(extractSection(fullText, "成长建议", null)));
|
|
|
return new DanParsedReport("B5", "adolescent", items, extra, summary.toString(), suggestions.toString());
|
|
return new DanParsedReport("B5", "adolescent", items, extra, summary.toString(), suggestions.toString());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1232,6 +1254,168 @@ public class DanReportParseService {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 判断通用正则提取到的 key 是否为报告头噪音(联系方式/证件/日期等)。
|
|
|
|
|
+ * 兜底正则会把「联系电话:135****」这类非测评数据也抓成指标。
|
|
|
|
|
+ */
|
|
|
|
|
+ private boolean isNoiseKey(String key) {
|
|
|
|
|
+ if (key == null || key.isEmpty()) return true;
|
|
|
|
|
+ return key.contains("电话") || key.contains("手机") || key.contains("编码")
|
|
|
|
|
+ || key.contains("编号") || key.contains("证件") || key.contains("年份")
|
|
|
|
|
+ || key.contains("日期") || key.contains("地址") || key.contains("联系")
|
|
|
|
|
+ || key.contains("身份证") || key.contains("姓名") || key.contains("性别")
|
|
|
|
|
+ || key.contains("年龄") || key.contains("学校") || key.contains("邮箱")
|
|
|
|
|
+ || key.contains("时间") || key.contains("具体");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 稳健解析大五人格:用全文句式正则一次取齐 5 维。
|
|
|
|
|
+ * PDF 为多列自由排版,逐行扫描只取「行内第一个数字」会漏维度/取错值。
|
|
|
|
|
+ * 句式:您在“开放性”上的得分是 3.9 分,同龄人的平均分是3分
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, String> parseBigFiveQuoted(String fullText) {
|
|
|
|
|
+ Map<String, String> result = new LinkedHashMap<>();
|
|
|
|
|
+ if (fullText == null || fullText.isEmpty()) return result;
|
|
|
|
|
+ String[] dims = {"开放性", "宜人性", "责任心", "外倾性", "神经质"};
|
|
|
|
|
+ Pattern p = Pattern.compile(
|
|
|
|
|
+ "您在[\"“”「」'']?(" + String.join("|", dims) + ")[\"“”「」'']?[^。]*?得分是\\s*(\\d+(?:\\.\\d+)?)",
|
|
|
|
|
+ Pattern.DOTALL);
|
|
|
|
|
+ Matcher m = p.matcher(fullText);
|
|
|
|
|
+ while (m.find()) {
|
|
|
|
|
+ result.put(normalizeBigFiveName(m.group(1)), m.group(2));
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析情绪稳定性(A2 艾森克情绪稳定性测验)
|
|
|
|
|
+ * - 4 个分测验(自卑-自尊、抑郁-愉快、焦虑-安详、无力感-掌控感)
|
|
|
|
|
+ * - 分测验总分 + 同龄平均分
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, String> parseEmotionStability(String fullText) {
|
|
|
|
|
+ Map<String, String> result = new LinkedHashMap<>();
|
|
|
|
|
+ if (fullText == null || fullText.isEmpty()) return result;
|
|
|
|
|
+ // 总分与平均分:您在4个分测验中的总得分是42.5分,同龄人的平均分是40分(中间可能夹换行)
|
|
|
|
|
+ Pattern totalP = Pattern.compile(
|
|
|
|
|
+ "总得分是\\s*(\\d+(?:\\.\\d+)?)\\s*分[,,]?[\\s\\S]{0,30}?平均分是\\s*(\\d+(?:\\.\\d+)?)\\s*分");
|
|
|
|
|
+ Matcher totalM = totalP.matcher(fullText);
|
|
|
|
|
+ if (totalM.find()) {
|
|
|
|
|
+ result.put("情绪总得分", totalM.group(1));
|
|
|
|
|
+ result.put("情绪平均分", totalM.group(2));
|
|
|
|
|
+ }
|
|
|
|
|
+ // 4 个分测验:PDFBox 排版为「分数行\n维度行」如 "7.5\n自卑 自尊"
|
|
|
|
|
+ String[][] subs = {
|
|
|
|
|
+ {"自卑", "自尊"},
|
|
|
|
|
+ {"抑郁", "愉快"},
|
|
|
|
|
+ {"焦虑", "安详"},
|
|
|
|
|
+ {"无力感", "掌控感"}
|
|
|
|
|
+ };
|
|
|
|
|
+ for (String[] sub : subs) {
|
|
|
|
|
+ Pattern p = Pattern.compile(
|
|
|
|
|
+ "(\\d+(?:\\.\\d+)?)\\s*\\n\\s*" + Pattern.quote(sub[0]) + "\\s+" + Pattern.quote(sub[1]),
|
|
|
|
|
+ Pattern.DOTALL);
|
|
|
|
|
+ Matcher m = p.matcher(fullText);
|
|
|
|
|
+ if (m.find()) {
|
|
|
|
|
+ result.put(sub[0], m.group(1));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析社会关系(A2 社会性测评:依恋模式)
|
|
|
|
|
+ * 句式:您和母亲、父亲和同伴在信任方面的得分分别是37分、40分、34分;…在沟通上的得分分别是32分、32分、25分;…在亲近上的得分分别是15分、16分、17分。
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, String> parseSocialAttachment(String fullText) {
|
|
|
|
|
+ Map<String, String> result = new LinkedHashMap<>();
|
|
|
|
|
+ if (fullText == null || fullText.isEmpty()) return result;
|
|
|
|
|
+ Pattern p = Pattern.compile(
|
|
|
|
|
+ "信任方\\s*面的得分分别\\s*是\\s*(\\d+)\\s*分[、,,\\s]*(\\d+)\\s*分[、,,\\s]*(\\d+)\\s*分"
|
|
|
|
|
+ + "[\\s\\S]{0,80}?"
|
|
|
|
|
+ + "沟通上的\\s*得分分别\\s*是\\s*(\\d+)\\s*分[、,,\\s]*(\\d+)\\s*分[、,,\\s]*(\\d+)\\s*分"
|
|
|
|
|
+ + "[\\s\\S]{0,80}?"
|
|
|
|
|
+ + "亲近上的\\s*得分分别\\s*是\\s*(\\d+)\\s*分[、,,\\s]*(\\d+)\\s*分[、,,\\s]*(\\d+)\\s*分",
|
|
|
|
|
+ Pattern.DOTALL);
|
|
|
|
|
+ Matcher m = p.matcher(fullText);
|
|
|
|
|
+ if (m.find()) {
|
|
|
|
|
+ String[][] keys = {
|
|
|
|
|
+ {"母亲信任", "父亲信任", "同伴信任"},
|
|
|
|
|
+ {"母亲沟通", "父亲沟通", "同伴沟通"},
|
|
|
|
|
+ {"母亲亲近", "父亲亲近", "同伴亲近"}
|
|
|
|
|
+ };
|
|
|
|
|
+ for (int row = 0; row < keys.length; row++) {
|
|
|
|
|
+ for (int col = 0; col < 3; col++) {
|
|
|
|
|
+ result.put(keys[row][col], m.group(row * 3 + col + 1));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 依恋类型:母亲/父亲/同伴 的安全型/回避型/焦虑-矛盾型
|
|
|
|
|
+ Pattern typeP = Pattern.compile(
|
|
|
|
|
+ "母亲\\s+父亲\\s+同伴[\\s\\S]{0,200}?(安全型|回避型|焦虑-矛盾型)\\s+(?:[A-Za-z-]+\\s+)?"
|
|
|
|
|
+ + "(安全型|回避型|焦虑-矛盾型)\\s+(?:[A-Za-z-]+\\s+)?"
|
|
|
|
|
+ + "(安全型|回避型|焦虑-矛盾型)");
|
|
|
|
|
+ Matcher typeM = typeP.matcher(fullText);
|
|
|
|
|
+ if (typeM.find()) {
|
|
|
|
|
+ result.put("依恋类型", typeM.group(1) + "/" + typeM.group(2) + "/" + typeM.group(3));
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析体质健康(A2):BMI 得分/等级、身高、体重、饮食/睡眠/运动习惯
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, String> parsePhysicalHealth(String fullText) {
|
|
|
|
|
+ Map<String, String> result = new LinkedHashMap<>();
|
|
|
|
|
+ if (fullText == null || fullText.isEmpty()) return result;
|
|
|
|
|
+ // BMI 得分与等级("17 正常")
|
|
|
|
|
+ Pattern bmiP = Pattern.compile(
|
|
|
|
|
+ "(?:^|\\n)\\s*(\\d+(?:\\.\\d+)?)\\s*(正常|偏瘦|偏重|超重|肥胖|消瘦)\\s*(?=\\n|$)");
|
|
|
|
|
+ Matcher bmiM = bmiP.matcher(fullText);
|
|
|
|
|
+ if (bmiM.find()) {
|
|
|
|
|
+ result.put("BMI得分", bmiM.group(1));
|
|
|
|
|
+ result.put("BMI等级", bmiM.group(2));
|
|
|
|
|
+ }
|
|
|
|
|
+ // 身高/体重/BMI 值:身高:154CM 体重:40KG BMI:17KG/M²
|
|
|
|
|
+ Matcher hM = Pattern.compile("身高:\\s*(\\d+(?:\\.\\d+)?)\\s*CM").matcher(fullText);
|
|
|
|
|
+ if (hM.find()) result.put("身高", hM.group(1));
|
|
|
|
|
+ Matcher wM = Pattern.compile("体重:\\s*(\\d+(?:\\.\\d+)?)\\s*KG").matcher(fullText);
|
|
|
|
|
+ if (wM.find()) result.put("体重", wM.group(1));
|
|
|
|
|
+ Matcher bmiNumM = Pattern.compile("BMI:\\s*(\\d+(?:\\.\\d+)?)").matcher(fullText);
|
|
|
|
|
+ if (bmiNumM.find()) result.put("BMI值", bmiNumM.group(1));
|
|
|
|
|
+ // 生活习惯:三列合并在同一行 "基本均衡 中等 8.8小时/每天 优秀 5小时/周 中等"。
|
|
|
|
|
+ // 不依赖固定行序,直接匹配 6 个 token(数值/时长可能带单位)。
|
|
|
|
|
+ Pattern habitsP = Pattern.compile(
|
|
|
|
|
+ "([一-龥]{2,12})\\s+([一-龥]{2,4})\\s+(\\d+(?:\\.\\d+)?\\s*小时/每天)\\s+([一-龥]{2,4})"
|
|
|
|
|
+ + "\\s+(\\d+(?:\\.\\d+)?\\s*小时/周)\\s+([一-龥]{2,4})");
|
|
|
|
|
+ Matcher habitsM = habitsP.matcher(fullText);
|
|
|
|
|
+ if (habitsM.find()) {
|
|
|
|
|
+ result.put("饮食习惯", habitsM.group(1));
|
|
|
|
|
+ result.put("饮食评价", habitsM.group(2));
|
|
|
|
|
+ result.put("睡眠时长", habitsM.group(3));
|
|
|
|
|
+ result.put("睡眠评价", habitsM.group(4));
|
|
|
|
|
+ result.put("运动时长", habitsM.group(5));
|
|
|
|
|
+ result.put("运动评价", habitsM.group(6));
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 清洗 DAN「成长建议」提取结果:DAN 各类型 PDF 的页眉固定印有「成长建议」字样,
|
|
|
|
|
+ * extractSection(fullText, "成长建议", null) 会截取整份报告正文(含页码的噪音长文),
|
|
|
|
|
+ * 只有真正独立的建议段落才保留;判断为噪音则置空,由展示层按指标生成建议。
|
|
|
|
|
+ */
|
|
|
|
|
+ private String sanitizeDanSuggestions(String raw) {
|
|
|
|
|
+ if (raw == null) return "";
|
|
|
|
|
+ String text = raw.trim();
|
|
|
|
|
+ if (text.isEmpty()) return "";
|
|
|
|
|
+ if (text.length() > 500) return "";
|
|
|
|
|
+ if (text.contains("Results Summary") || text.contains("测评报告")
|
|
|
|
|
+ || text.contains("第 1 页") || text.contains("Total Score")) {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ return text;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 解析社会关系:与父母信任、沟通、亲近等
|
|
* 解析社会关系:与父母信任、沟通、亲近等
|
|
|
*/
|
|
*/
|