|
|
@@ -134,38 +134,44 @@ public class PdfParseService {
|
|
|
/**
|
|
|
* 从纯文本中解析报告数据
|
|
|
*/
|
|
|
- public ParsedReportResult parseText(String fullText) {
|
|
|
+public ParsedReportResult parseText(String fullText) {
|
|
|
ParsedReportResult result = new ParsedReportResult();
|
|
|
|
|
|
// 1. 归一化 Kangxi 部首
|
|
|
String normalized = normalizeChinese(fullText);
|
|
|
|
|
|
- // 2. 按行分割
|
|
|
- String[] lines = normalized.split("\\r?\\n");
|
|
|
+ // 2. 检测是否为北京菌群报告格式
|
|
|
+ if (isBeijingFormat(normalized)) {
|
|
|
+ log.debug("PDF 检测格式: beijing");
|
|
|
+ return parseBeijingReport(normalized);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 按行分割
|
|
|
+ String[] lines = normalized.split("\r?\n");
|
|
|
List<String> lineList = new ArrayList<>();
|
|
|
for (String line : lines) {
|
|
|
String trimmed = line.trim();
|
|
|
if (!trimmed.isEmpty()) {
|
|
|
- // 去除PDF提取中的特殊Unicode箭头字符(如 )
|
|
|
- trimmed = trimmed.replaceAll("[\\uF000-\\uFFFF]", "").trim();
|
|
|
+ // 去除PDF提取中的特殊Unicode箭头字符
|
|
|
+ trimmed = trimmed.replaceAll("[\uF000-\uFFFF]", "").trim();
|
|
|
if (!trimmed.isEmpty()) {
|
|
|
lineList.add(trimmed);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 3. 检测格式: triplet vs inline
|
|
|
+ // 4. 检测格式: triplet vs inline
|
|
|
String format = detectFormat(lineList);
|
|
|
log.debug("PDF 检测格式: {}", format);
|
|
|
|
|
|
- // 4. 解析各部分
|
|
|
+ // 5. 解析各部分
|
|
|
if ("inline".equals(format)) {
|
|
|
parseInlineReport(normalized, lineList, result);
|
|
|
} else {
|
|
|
parseTripletReport(lineList, result);
|
|
|
}
|
|
|
|
|
|
- // 5. 解析食材推荐(格式无关)
|
|
|
+ // 6. 解析食材推荐(格式无关)
|
|
|
List<ParsedFoodSuitability> foodSuitability = parseFoodSuitability(normalized);
|
|
|
result.setFoodSuitability(foodSuitability);
|
|
|
|
|
|
@@ -1341,4 +1347,847 @@ public class PdfParseService {
|
|
|
}
|
|
|
return section.toString().trim();
|
|
|
}
|
|
|
-}
|
|
|
+ // ======================== 北京菌群报告格式检测 ========================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检测是否为北京菌群报告格式
|
|
|
+ * 特征: 包含"肠道菌群检测" + "高通量测序" + "健康整体评估"
|
|
|
+ */
|
|
|
+ private boolean isBeijingFormat(String text) {
|
|
|
+ return text.contains("肠道菌群检测") && text.contains("高通量测序")
|
|
|
+ && text.contains("健康整体评估");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======================== 北京菌群报告解析 ========================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析北京菌群报告 PDF 文本
|
|
|
+ * 输出结构与募极生物报告一致 (ParsedReportResult),缺失字段留 null
|
|
|
+ */
|
|
|
+ private ParsedReportResult parseBeijingReport(String fullText) {
|
|
|
+ ParsedReportResult result = new ParsedReportResult();
|
|
|
+
|
|
|
+ List<String> lines = new ArrayList<>();
|
|
|
+ for (String line : fullText.split("\r?\n")) {
|
|
|
+ String trimmed = line.trim();
|
|
|
+ if (!trimmed.isEmpty()) {
|
|
|
+ lines.add(trimmed);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 解析基本信息(姓名/年龄/性别/报告编号/日期/肠型)
|
|
|
+ parseBeijingSummary(lines, result);
|
|
|
+
|
|
|
+ // 2. 解析健康评分
|
|
|
+ parseBeijingScores(lines, result);
|
|
|
+
|
|
|
+ // 3. 解析肠道菌群表格(核心菌属/有益菌/中性菌/有害菌)
|
|
|
+ List<ParsedGutFlora> gutFlora = new ArrayList<>();
|
|
|
+ parseBeijingGutFloraTables(fullText, lines, gutFlora);
|
|
|
+ result.setGutFlora(gutFlora);
|
|
|
+
|
|
|
+ // 4. 解析疾病风险评估
|
|
|
+ List<ParsedDiseaseRisk> diseaseRisks = parseBeijingDiseaseRisks(fullText);
|
|
|
+ result.setDiseaseRisks(diseaseRisks);
|
|
|
+
|
|
|
+ // 5. 解析各类指标(营养素/维生素/微量元素/氨基酸/SCFA/抗生素/毒素清除)
|
|
|
+ List<ParsedIndicator> indicators = new ArrayList<>();
|
|
|
+ parseBeijingIndicators(fullText, lines, indicators);
|
|
|
+ result.setIndicators(indicators);
|
|
|
+
|
|
|
+ // 6. 解析分类层级表(门/纲/目/科/属)追加到 gutFlora
|
|
|
+ parseBeijingTaxonomyLevels(fullText, lines, gutFlora);
|
|
|
+
|
|
|
+ // 7. 解析胃肠道感染致病菌追加到 indicators
|
|
|
+ parseBeijingGastroInfections(fullText, lines, indicators);
|
|
|
+
|
|
|
+ // 8. 解析益生菌(species 级别)
|
|
|
+ List<ParsedGutFlora> probioticSpecies = parseBeijingProbiotics(fullText, lines);
|
|
|
+ result.setProbioticSpecies(probioticSpecies);
|
|
|
+
|
|
|
+ // 9. 北京报告无食材推荐表
|
|
|
+ result.setFoodSuitability(new ArrayList<>());
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======================== 北京报告 — 基本信息 ========================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析北京报告基本信息:
|
|
|
+ * 页3: "车艳红 70\n女\n肠道菌群检测\n2026-043-26046\n粪便\n2026-07-28\n2026-07-30\n高通量测序"
|
|
|
+ * 页35: "您属于肠型 I"
|
|
|
+ */
|
|
|
+ private void parseBeijingSummary(List<String> lines, ParsedReportResult result) {
|
|
|
+ // 姓名 + 年龄(同行: "车艳红 70",可能隔一行"女"才到"肠道菌群检测")
|
|
|
+ for (int i = 0; i < lines.size(); i++) {
|
|
|
+ String line = lines.get(i);
|
|
|
+ if (line.contains("肠道菌群检测")) {
|
|
|
+ for (int back = 1; back <= 3 && i - back >= 0; back++) {
|
|
|
+ String cand = lines.get(i - back);
|
|
|
+ String[] parts = cand.split("\\s+");
|
|
|
+ if (parts.length >= 2 && parts[0].matches("[\\u4e00-\\u9fa5·]{2,4}")) {
|
|
|
+ try {
|
|
|
+ int ageVal = Integer.parseInt(parts[1].replaceAll("[^0-9]", ""));
|
|
|
+ if (ageVal >= 1 && ageVal <= 120) {
|
|
|
+ result.setPersonName(parts[0].trim());
|
|
|
+ result.setAge(ageVal);
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) { /* ignore */ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 性别
|
|
|
+ for (String line : lines) {
|
|
|
+ if (line.equals("男") || line.equals("女")) {
|
|
|
+ result.setGender(line.equals("男") ? "male" : "female");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 报告编号(在"肠道菌群检测"之后)
|
|
|
+ for (int i = 0; i < lines.size(); i++) {
|
|
|
+ if (lines.get(i).contains("肠道菌群检测") && i + 1 < lines.size()) {
|
|
|
+ String next = lines.get(i + 1).trim();
|
|
|
+ if (!next.isEmpty() && next.length() <= 30) {
|
|
|
+ result.setReportNumber(next);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 采样日期(在编号之后的第二个日期行)
|
|
|
+ for (int i = 0; i < lines.size(); i++) {
|
|
|
+ if (lines.get(i).contains("肠道菌群检测")) {
|
|
|
+ int dateIdx = i + 3;
|
|
|
+ if (dateIdx < lines.size()) {
|
|
|
+ result.setReportDate(normalizeBeijingDate(lines.get(dateIdx).trim()));
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 肠型
|
|
|
+ for (String line : lines) {
|
|
|
+ if (line.contains("您属于肠型")) {
|
|
|
+ java.util.regex.Matcher m = java.util.regex.Pattern
|
|
|
+ .compile("肠型\\s*([A-Za-z0-9IVX]+)").matcher(line);
|
|
|
+ if (m.find()) {
|
|
|
+ result.setGutType(m.group(1));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String normalizeBeijingDate(String dateStr) {
|
|
|
+ String cleaned = dateStr.replaceAll("[年月日/]", "-").replaceAll("-+", "-").replaceAll("-$", "").trim();
|
|
|
+ if (cleaned.matches("\\d{4}-\\d{2}-\\d{2}")) return cleaned;
|
|
|
+ if (cleaned.matches("\\d{4}-\\d{2}")) return cleaned + "-01";
|
|
|
+ if (cleaned.matches("\\d{8}")) {
|
|
|
+ return cleaned.substring(0, 4) + "-" + cleaned.substring(4, 6) + "-" + cleaned.substring(6, 8);
|
|
|
+ }
|
|
|
+ return dateStr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======================== 北京报告 — 健康评分 ========================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析北京报告健康评分:
|
|
|
+ * 页8: "肠道微生物健康指数 69 肠道亚健康"
|
|
|
+ * 页8: "肠道菌群平衡 78 菌群平衡"
|
|
|
+ */
|
|
|
+ private void parseBeijingScores(List<String> lines, ParsedReportResult result) {
|
|
|
+ Integer gmhi = findBeijingScore(lines, "肠道微生物健康指数");
|
|
|
+ if (gmhi != null) result.setGutHealthScore(gmhi);
|
|
|
+
|
|
|
+ Integer balance = findBeijingScore(lines, "肠道菌群平衡");
|
|
|
+ if (balance != null) result.setBalanceScore(balance);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Integer findBeijingScore(List<String> lines, String keyword) {
|
|
|
+ // 严格匹配"关键词+空白+整数":跳过目录项("2肠道菌群平衡评估 2")、B/E比值参考条件("肠道菌群平衡 > 1")等干扰行
|
|
|
+ java.util.regex.Pattern p = java.util.regex.Pattern.compile(
|
|
|
+ java.util.regex.Pattern.quote(keyword) + "\\s+(\\d{1,3})(?!\\d)");
|
|
|
+ for (int i = lines.size() - 1; i >= 0; i--) {
|
|
|
+ java.util.regex.Matcher m = p.matcher(lines.get(i));
|
|
|
+ while (m.find()) {
|
|
|
+ int v = Integer.parseInt(m.group(1));
|
|
|
+ if (v >= 0 && v <= 100) {
|
|
|
+ return v;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======================== 北京报告 — 菌群表格 ========================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析北京报告菌群表格:
|
|
|
+ * 格式: "拉丁学名 中文名称 检测结果(%) 参考范围" 四列表格
|
|
|
+ */
|
|
|
+ private void parseBeijingGutFloraTables(String fullText, List<String> lines,
|
|
|
+ List<ParsedGutFlora> result) {
|
|
|
+ String[][] sections = {
|
|
|
+ {"8.1肠道核心菌属", "核心菌属"},
|
|
|
+ {"8.2有益菌", "有益菌"},
|
|
|
+ {"8.3中性菌", "中性菌"},
|
|
|
+ {"8.4有害菌", "有害菌"}
|
|
|
+ };
|
|
|
+
|
|
|
+ for (String[] section : sections) {
|
|
|
+ String sectionMarker = section[0];
|
|
|
+ String category = section[1];
|
|
|
+
|
|
|
+ int startIdx = fullText.indexOf(sectionMarker);
|
|
|
+ if (startIdx == -1) continue;
|
|
|
+
|
|
|
+ int endIdx = fullText.length();
|
|
|
+ for (String[] nextSection : sections) {
|
|
|
+ if (nextSection[0].equals(sectionMarker)) continue;
|
|
|
+ int ni = fullText.indexOf(nextSection[0], startIdx + sectionMarker.length());
|
|
|
+ if (ni != -1 && ni < endIdx) {
|
|
|
+ endIdx = ni;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ int foodAllergyIdx = fullText.indexOf("食物过敏风险评估", startIdx);
|
|
|
+ if (foodAllergyIdx != -1 && foodAllergyIdx < endIdx) endIdx = foodAllergyIdx;
|
|
|
+ int giInfectionIdx = fullText.indexOf("胃肠道感染相关的肠菌评估", startIdx);
|
|
|
+ if (giInfectionIdx != -1 && giInfectionIdx < endIdx) endIdx = giInfectionIdx;
|
|
|
+ int nutrientIdx = fullText.indexOf("营养物质代谢及营养素评估", startIdx);
|
|
|
+ if (nutrientIdx != -1 && nutrientIdx < endIdx) endIdx = nutrientIdx;
|
|
|
+
|
|
|
+ String region = fullText.substring(startIdx, endIdx);
|
|
|
+ parseBeijingFloraRegion(region, category, result);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void parseBeijingFloraRegion(String region, String category,
|
|
|
+ List<ParsedGutFlora> result) {
|
|
|
+ String[] regionLines = region.split("\r?\n");
|
|
|
+ boolean pastHeader = false;
|
|
|
+
|
|
|
+ for (String rawLine : regionLines) {
|
|
|
+ String line = rawLine.trim();
|
|
|
+ if (line.isEmpty()) continue;
|
|
|
+ if (line.contains("拉丁学名") && line.contains("中文名称")) {
|
|
|
+ pastHeader = true;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!pastHeader) continue;
|
|
|
+
|
|
|
+ if (line.startsWith("【备注】") || line.startsWith("【注释】") || line.startsWith("注释:")) continue;
|
|
|
+ if (line.startsWith("拉丁学名:") || line.startsWith("中文名称:")) continue;
|
|
|
+ if (line.startsWith("相对丰度:") || line.startsWith("人群水平:") || line.startsWith("人群检出率:")) continue;
|
|
|
+ if (line.contains("汇总结果")) continue;
|
|
|
+ if (line.matches("^\\d+$")) continue;
|
|
|
+ if (line.startsWith("8.") || line.startsWith("9.") || line.startsWith("10.") ||
|
|
|
+ line.startsWith("11.") || line.startsWith("12.") || line.startsWith("13.") || line.startsWith("14.")) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ ParsedGutFlora flora = parseBeijingFloraLine(line, category);
|
|
|
+ if (flora != null) {
|
|
|
+ result.add(flora);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析单行北京菌群数据
|
|
|
+ */
|
|
|
+ private ParsedGutFlora parseBeijingFloraLine(String line, String category) {
|
|
|
+ int chineseStart = -1;
|
|
|
+ for (int i = 0; i < line.length(); i++) {
|
|
|
+ char c = line.charAt(i);
|
|
|
+ if (c >= '\u4e00' && c <= '\u9fff') {
|
|
|
+ chineseStart = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (chineseStart <= 0) return null;
|
|
|
+
|
|
|
+ String latinName = line.substring(0, chineseStart).trim();
|
|
|
+ if (latinName.isEmpty() || !latinName.matches("[A-Z].*")) return null;
|
|
|
+
|
|
|
+ String rest = line.substring(chineseStart);
|
|
|
+ Matcher valueStartMatcher = Pattern.compile(
|
|
|
+ "[\\s]+(ND|\\d+\\.?\\d*)").matcher(rest);
|
|
|
+ if (!valueStartMatcher.find()) return null;
|
|
|
+
|
|
|
+ String chineseName = rest.substring(0, valueStartMatcher.start()).trim();
|
|
|
+ String cleanChineseName = chineseName.replaceAll("[\\s#\\*]+$", "").trim();
|
|
|
+
|
|
|
+ String afterChinese = rest.substring(valueStartMatcher.start()).trim();
|
|
|
+ String cleanedAfter = afterChinese.replaceAll("[\"\\u201C\\u201D]", "").trim();
|
|
|
+
|
|
|
+ String[] tokens = cleanedAfter.split("\\s+");
|
|
|
+ if (tokens.length < 2) return null;
|
|
|
+
|
|
|
+ String detectionValue = tokens[0].trim();
|
|
|
+ if (detectionValue.isEmpty()) return null;
|
|
|
+
|
|
|
+ String cleanValue = detectionValue.replaceAll("[#\\*]", "").trim();
|
|
|
+
|
|
|
+ StringBuilder refRange = new StringBuilder();
|
|
|
+ for (int i = 1; i < tokens.length; i++) {
|
|
|
+ if (refRange.length() > 0) refRange.append(" ");
|
|
|
+ refRange.append(tokens[i].trim());
|
|
|
+ }
|
|
|
+
|
|
|
+ String status = "正常";
|
|
|
+ if (detectionValue.equals("ND") || cleanValue.equals("0") || cleanValue.equals("0.0")) {
|
|
|
+ status = "未检出";
|
|
|
+ } else if (line.contains("\"") || line.contains("\u201C") || line.contains("\u201D")) {
|
|
|
+ status = determineBeijingFloraStatus(cleanValue, refRange.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ ParsedGutFlora flora = new ParsedGutFlora();
|
|
|
+ flora.setBacteriaName(cleanChineseName);
|
|
|
+ flora.setBacteriaValue(detectionValue.equals("ND") ? "ND" : cleanValue);
|
|
|
+ flora.setNormalRange(refRange.toString());
|
|
|
+ flora.setCategory(category);
|
|
|
+ flora.setLevel("GENUS");
|
|
|
+ flora.setStatus(status);
|
|
|
+
|
|
|
+ return flora;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String determineBeijingFloraStatus(String value, String refRange) {
|
|
|
+ try {
|
|
|
+ double val = Double.parseDouble(value);
|
|
|
+ String[] rangeParts = refRange.split("\\s*-\\s*");
|
|
|
+ if (rangeParts.length >= 2) {
|
|
|
+ double low = Double.parseDouble(rangeParts[0].trim());
|
|
|
+ double high = Double.parseDouble(rangeParts[1].trim());
|
|
|
+ if (val < low) return "偏低";
|
|
|
+ if (val > high) return "偏高";
|
|
|
+ return "正常";
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ // 解析失败
|
|
|
+ }
|
|
|
+ return "正常";
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======================== 北京报告 — 疾病风险评估 ========================
|
|
|
+
|
|
|
+ private List<ParsedDiseaseRisk> parseBeijingDiseaseRisks(String fullText) {
|
|
|
+ List<ParsedDiseaseRisk> result = new ArrayList<>();
|
|
|
+ LinkedHashMap<String, ParsedDiseaseRisk> dedupMap = new LinkedHashMap<>();
|
|
|
+
|
|
|
+ int startIdx = fullText.indexOf("疾病风险评估总揽");
|
|
|
+ if (startIdx == -1) {
|
|
|
+ startIdx = fullText.indexOf("慢病控制");
|
|
|
+ }
|
|
|
+ if (startIdx == -1) return result;
|
|
|
+
|
|
|
+ int endIdx = fullText.indexOf("低风险 (0-0.3)", startIdx);
|
|
|
+ if (endIdx == -1) endIdx = fullText.indexOf("低风险", startIdx);
|
|
|
+ if (endIdx == -1) endIdx = Math.min(startIdx + 3000, fullText.length());
|
|
|
+
|
|
|
+ String region = fullText.substring(startIdx, endIdx);
|
|
|
+
|
|
|
+ Pattern riskPattern = Pattern.compile(
|
|
|
+ "([\u4e00-\u9fff][\u4e00-\u9fff\\s]+?)\\s+(0?\\.\\d+|\\d+\\.?\\d*)");
|
|
|
+
|
|
|
+ for (String line : region.split("\r?\n")) {
|
|
|
+ line = line.trim();
|
|
|
+ if (line.isEmpty()) continue;
|
|
|
+ if (line.contains("疾病风险评估") || line.contains("项目名称")
|
|
|
+ || line.contains("风险指数") || line.contains("风险等级")
|
|
|
+ || line.contains("慢病控制") || line.contains("总揽") || line.contains("总览")) continue;
|
|
|
+ if (line.matches("^\\d+$")) continue;
|
|
|
+
|
|
|
+ Matcher m = riskPattern.matcher(line);
|
|
|
+ if (m.matches()) {
|
|
|
+ String name = m.group(1).trim();
|
|
|
+ String value = m.group(2).trim();
|
|
|
+
|
|
|
+ if (name.contains("低风险") || name.contains("较低风险") || name.contains("中度风险")
|
|
|
+ || name.contains("较高风险") || name.contains("高风险")) continue;
|
|
|
+
|
|
|
+ String riskLevel = beijingRiskLevel(value);
|
|
|
+
|
|
|
+ if (!dedupMap.containsKey(name)) {
|
|
|
+ ParsedDiseaseRisk risk = new ParsedDiseaseRisk();
|
|
|
+ risk.setDiseaseName(name);
|
|
|
+ risk.setRiskValue(value);
|
|
|
+ risk.setRiskLevel(riskLevel);
|
|
|
+ dedupMap.put(name, risk);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ result.addAll(dedupMap.values());
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String beijingRiskLevel(String valueStr) {
|
|
|
+ try {
|
|
|
+ double val = Double.parseDouble(valueStr);
|
|
|
+ if (val < 0.3) return "低风险";
|
|
|
+ if (val < 0.5) return "较低风险";
|
|
|
+ if (val < 0.7) return "中度风险";
|
|
|
+ if (val < 0.8) return "较高风险";
|
|
|
+ return "高风险";
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return "未知";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======================== 北京报告 — 指标解析 ========================
|
|
|
+
|
|
|
+ private void parseBeijingIndicators(String fullText, List<String> lines,
|
|
|
+ List<ParsedIndicator> result) {
|
|
|
+ LinkedHashMap<String, ParsedIndicator> dedupMap = new LinkedHashMap<>();
|
|
|
+
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.1.1主要营养素", "主要营养评估", dedupMap);
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.1.2糖类代谢", "糖类代谢", dedupMap);
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.1.3脂类代谢", "脂类代谢", dedupMap);
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.1.4嘌呤代谢", "嘌呤代谢", dedupMap);
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.2.1维生素", "维生素评估", dedupMap);
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.2.2微量元素", "微量元素评估", dedupMap);
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.2.3天然色素", "天然色素", dedupMap);
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.2.4氨基酸", "氨基酸评估", dedupMap);
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.2.5三肽", "三肽", dedupMap);
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.2.6胆汁酸", "胆汁酸", dedupMap);
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.2.7神经递质", "神经递质与激素", dedupMap);
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.2.8抗自由基", "抗自由基", dedupMap);
|
|
|
+ parseBeijingIndicatorSection(fullText, "11.2.9抗氧化", "抗氧化", dedupMap);
|
|
|
+
|
|
|
+ parseBeijingSCFASection(fullText, dedupMap);
|
|
|
+ parseBeijingPhenotypeSection(fullText, dedupMap);
|
|
|
+ parseBeijingImmunityScores(lines, dedupMap);
|
|
|
+ parseBeijingAntibioticSection(fullText, dedupMap);
|
|
|
+ parseBeijingToxinClearance(fullText, dedupMap);
|
|
|
+
|
|
|
+ result.addAll(dedupMap.values());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void parseBeijingIndicatorSection(String fullText, String sectionHeader,
|
|
|
+ String category,
|
|
|
+ LinkedHashMap<String, ParsedIndicator> dedupMap) {
|
|
|
+ int startIdx = fullText.indexOf(sectionHeader);
|
|
|
+ if (startIdx == -1) return;
|
|
|
+
|
|
|
+ int endIdx = fullText.length();
|
|
|
+ String[] endMarkers = {"11.1.1", "11.1.2", "11.1.3", "11.1.4",
|
|
|
+ "11.2.1", "11.2.2", "11.2.3", "11.2.4", "11.2.5",
|
|
|
+ "11.2.6", "11.2.7", "11.2.8", "11.2.9",
|
|
|
+ "12抗生素风险评估", "13毒性物质清除",
|
|
|
+ "3.2.1", "3.2.2", "3.2.3", "3.2.4", "3.2.5",
|
|
|
+ "门级水平结果", "纲级水平结果", "目级水平结果",
|
|
|
+ "科级水平结果", "属级水平结果"};
|
|
|
+ for (String marker : endMarkers) {
|
|
|
+ if (marker.equals(sectionHeader)) continue;
|
|
|
+ int mi = fullText.indexOf(marker, startIdx + sectionHeader.length());
|
|
|
+ if (mi != -1 && mi < endIdx) {
|
|
|
+ endIdx = mi;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ String region = fullText.substring(startIdx, endIdx);
|
|
|
+
|
|
|
+ for (String line : region.split("\r?\n")) {
|
|
|
+ line = line.trim();
|
|
|
+ if (line.isEmpty()) continue;
|
|
|
+ if (line.contains(sectionHeader)) continue;
|
|
|
+ if (line.contains("检测项目") || line.contains("评估值") || line.contains("参考范围")) continue;
|
|
|
+ if (line.contains("营养物质代谢能力") || line.contains("合成能力评估")) continue;
|
|
|
+ if (line.matches("^\\d+$")) continue;
|
|
|
+ if (line.startsWith("【备注】") || line.startsWith("【注释】")) continue;
|
|
|
+
|
|
|
+ ParsedIndicator ind = parseBeijingIndicatorLine(line, category);
|
|
|
+ if (ind != null && ind.getIndicatorName() != null && !ind.getIndicatorName().isEmpty()) {
|
|
|
+ String key = category + "_" + ind.getIndicatorName();
|
|
|
+ if (!dedupMap.containsKey(key)) {
|
|
|
+ dedupMap.put(key, ind);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private ParsedIndicator parseBeijingIndicatorLine(String line, String category) {
|
|
|
+ Matcher refMatcher = Pattern.compile(">\\s*\\d+").matcher(line);
|
|
|
+ if (!refMatcher.find()) return null;
|
|
|
+
|
|
|
+ String refRange = refMatcher.group().trim();
|
|
|
+ String beforeRef = line.substring(0, refMatcher.start()).trim();
|
|
|
+
|
|
|
+ Matcher valMatcher = Pattern.compile("(\\d+)$").matcher(beforeRef);
|
|
|
+ if (!valMatcher.find()) return null;
|
|
|
+
|
|
|
+ String value = valMatcher.group(1);
|
|
|
+ String name = beforeRef.substring(0, valMatcher.start()).trim();
|
|
|
+
|
|
|
+ if (name.isEmpty()) return null;
|
|
|
+
|
|
|
+ String status = "正常";
|
|
|
+ try {
|
|
|
+ int val = Integer.parseInt(value);
|
|
|
+ if (refRange.contains(">")) {
|
|
|
+ String refNum = refRange.replaceAll("[^0-9]", "").trim();
|
|
|
+ if (!refNum.isEmpty()) {
|
|
|
+ int ref = Integer.parseInt(refNum);
|
|
|
+ if (val >= ref) status = "正常";
|
|
|
+ else if (val >= ref * 0.5) status = "偏低";
|
|
|
+ else status = "缺乏";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) { /* ignore */ }
|
|
|
+
|
|
|
+ ParsedIndicator ind = new ParsedIndicator();
|
|
|
+ ind.setCategory(category);
|
|
|
+ ind.setIndicatorName(name);
|
|
|
+ ind.setIndicatorValue(value);
|
|
|
+ ind.setStatus(status);
|
|
|
+ ind.setRefRange(refRange);
|
|
|
+ return ind;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void parseBeijingSCFASection(String fullText,
|
|
|
+ LinkedHashMap<String, ParsedIndicator> dedupMap) {
|
|
|
+ String[] scfaNames = {"甲酸", "乙酸", "丙酸", "丁酸", "戊酸"};
|
|
|
+ for (String name : scfaNames) {
|
|
|
+ int nameIdx = fullText.indexOf(name);
|
|
|
+ if (nameIdx == -1) continue;
|
|
|
+ String context = fullText.substring(nameIdx, Math.min(nameIdx + 100, fullText.length()));
|
|
|
+ Matcher m = Pattern.compile(name + "\\s+(\\d+)\\s*>\\s*60").matcher(context);
|
|
|
+ if (m.find()) {
|
|
|
+ String key = "短链脂肪酸_" + name;
|
|
|
+ if (!dedupMap.containsKey(key)) {
|
|
|
+ ParsedIndicator ind = new ParsedIndicator();
|
|
|
+ ind.setCategory("短链脂肪酸");
|
|
|
+ ind.setIndicatorName(name);
|
|
|
+ ind.setIndicatorValue(m.group(1));
|
|
|
+ ind.setStatus(Integer.parseInt(m.group(1)) >= 60 ? "正常" : "偏低");
|
|
|
+ ind.setRefRange("> 60");
|
|
|
+ dedupMap.put(key, ind);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void parseBeijingPhenotypeSection(String fullText,
|
|
|
+ LinkedHashMap<String, ParsedIndicator> dedupMap) {
|
|
|
+ int startIdx = fullText.indexOf("肠道菌群表型评估");
|
|
|
+ if (startIdx == -1) return;
|
|
|
+
|
|
|
+ int endIdx = fullText.indexOf("肠道免疫力评估", startIdx);
|
|
|
+ if (endIdx == -1) endIdx = fullText.indexOf("6肠道免疫力评估", startIdx);
|
|
|
+ if (endIdx == -1) endIdx = Math.min(startIdx + 2000, fullText.length());
|
|
|
+
|
|
|
+ String region = fullText.substring(startIdx, endIdx);
|
|
|
+
|
|
|
+ for (String line : region.split("\r?\n")) {
|
|
|
+ line = line.trim();
|
|
|
+ if (line.isEmpty()) continue;
|
|
|
+ if (line.contains("检测项目") || line.contains("评估值") || line.contains("参考范围") || line.contains("结果评价")) continue;
|
|
|
+ if (line.contains("肠道菌群表型评估")) continue;
|
|
|
+ if (line.matches("^\\d+$")) continue;
|
|
|
+
|
|
|
+ Pattern p = Pattern.compile(
|
|
|
+ "([\u4e00-\u9fff\\w]+)\\s+(0?\\.?\\d+)\\s+[\"\\u201C]?\\s*(\\d+[\\-~]\\d+\\.?\\d*)\\s+(正常|异常)");
|
|
|
+ Matcher m = p.matcher(line);
|
|
|
+ if (m.find()) {
|
|
|
+ String name = m.group(1).trim();
|
|
|
+ String value = m.group(2).trim();
|
|
|
+ String refRange = m.group(3).trim();
|
|
|
+ String status = m.group(4).trim();
|
|
|
+
|
|
|
+ String key = "肠道菌群表型_" + name;
|
|
|
+ if (!dedupMap.containsKey(key)) {
|
|
|
+ ParsedIndicator ind = new ParsedIndicator();
|
|
|
+ ind.setCategory("肠道菌群表型");
|
|
|
+ ind.setIndicatorName(name);
|
|
|
+ ind.setIndicatorValue(value);
|
|
|
+ ind.setStatus(status);
|
|
|
+ ind.setRefRange(refRange);
|
|
|
+ dedupMap.put(key, ind);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void parseBeijingImmunityScores(List<String> lines,
|
|
|
+ LinkedHashMap<String, ParsedIndicator> dedupMap) {
|
|
|
+ String[] scoreNames = {"肠道抗炎能力", "肠道免疫力", "肠道膳食纤维需求"};
|
|
|
+ for (String scoreName : scoreNames) {
|
|
|
+ for (String line : lines) {
|
|
|
+ if (line.contains(scoreName)) {
|
|
|
+ Pattern p = Pattern.compile(scoreName + "\\s+(\\d+)\\s*>?\\s*(\\d+)?");
|
|
|
+ Matcher m = p.matcher(line);
|
|
|
+ if (m.find()) {
|
|
|
+ String value = m.group(1);
|
|
|
+ String refRange = m.group(2) != null ? "> " + m.group(2) : "";
|
|
|
+ String key = "肠道免疫力评估_" + scoreName;
|
|
|
+ if (!dedupMap.containsKey(key)) {
|
|
|
+ ParsedIndicator ind = new ParsedIndicator();
|
|
|
+ ind.setCategory("肠道免疫力评估");
|
|
|
+ ind.setIndicatorName(scoreName);
|
|
|
+ ind.setIndicatorValue(value);
|
|
|
+ ind.setStatus("正常");
|
|
|
+ ind.setRefRange(refRange);
|
|
|
+ dedupMap.put(key, ind);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void parseBeijingAntibioticSection(String fullText,
|
|
|
+ LinkedHashMap<String, ParsedIndicator> dedupMap) {
|
|
|
+ int startIdx = fullText.indexOf("12抗生素风险评估");
|
|
|
+ if (startIdx == -1) {
|
|
|
+ startIdx = fullText.indexOf("抗生素风险评估");
|
|
|
+ }
|
|
|
+ if (startIdx == -1) return;
|
|
|
+
|
|
|
+ int endIdx = fullText.indexOf("13毒性物质清除", startIdx);
|
|
|
+ if (endIdx == -1) endIdx = fullText.indexOf("毒性物质清除能力评估", startIdx);
|
|
|
+ if (endIdx == -1) endIdx = fullText.indexOf("14慢病控制", startIdx);
|
|
|
+ if (endIdx == -1) endIdx = Math.min(startIdx + 3000, fullText.length());
|
|
|
+
|
|
|
+ String region = fullText.substring(startIdx, endIdx);
|
|
|
+
|
|
|
+ for (String line : region.split("\r?\n")) {
|
|
|
+ line = line.trim();
|
|
|
+ if (line.isEmpty()) continue;
|
|
|
+ if (line.contains("抗生素风险评估") || line.contains("类别") || line.contains("检测项目")) continue;
|
|
|
+ if (line.contains("检测结果") || line.contains("结果评价")) continue;
|
|
|
+ if (line.contains("耐药 (≥90)") || line.contains("注意 (70-90)") || line.contains("正常 (<70)")) continue;
|
|
|
+ if (line.matches("^\\d+$")) continue;
|
|
|
+
|
|
|
+ Matcher m = Pattern.compile("(.+?)\\s+(\\d+)\\s*$").matcher(line);
|
|
|
+ if (m.matches()) {
|
|
|
+ String name = m.group(1).trim();
|
|
|
+ String value = m.group(2).trim();
|
|
|
+
|
|
|
+ if (name.contains(" ")) {
|
|
|
+ String[] parts = name.split("\\s+");
|
|
|
+ name = parts[parts.length - 1].trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ int val;
|
|
|
+ try {
|
|
|
+ val = Integer.parseInt(value);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String status = val >= 90 ? "耐药" : (val >= 70 ? "注意" : "正常");
|
|
|
+
|
|
|
+ String key = "抗生素耐药_" + name;
|
|
|
+ if (!dedupMap.containsKey(key)) {
|
|
|
+ ParsedIndicator ind = new ParsedIndicator();
|
|
|
+ ind.setCategory("抗生素耐药");
|
|
|
+ ind.setIndicatorName(name);
|
|
|
+ ind.setIndicatorValue(value);
|
|
|
+ ind.setStatus(status);
|
|
|
+ dedupMap.put(key, ind);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void parseBeijingToxinClearance(String fullText,
|
|
|
+ LinkedHashMap<String, ParsedIndicator> dedupMap) {
|
|
|
+ int startIdx = fullText.indexOf("13毒性物质清除能力评估");
|
|
|
+ if (startIdx == -1) {
|
|
|
+ startIdx = fullText.indexOf("毒性物质清除能力评估");
|
|
|
+ }
|
|
|
+ if (startIdx == -1) return;
|
|
|
+
|
|
|
+ int endIdx = fullText.indexOf("14慢病控制", startIdx);
|
|
|
+ if (endIdx == -1) endIdx = fullText.indexOf("慢病控制", startIdx);
|
|
|
+ if (endIdx == -1) endIdx = fullText.indexOf("第一部分", startIdx);
|
|
|
+ if (endIdx == -1) endIdx = Math.min(startIdx + 3000, fullText.length());
|
|
|
+
|
|
|
+ String region = fullText.substring(startIdx, endIdx);
|
|
|
+
|
|
|
+ for (String line : region.split("\r?\n")) {
|
|
|
+ line = line.trim();
|
|
|
+ if (line.isEmpty()) continue;
|
|
|
+ if (line.contains("毒性物质清除") || line.contains("检测项目") || line.contains("检测结果")) continue;
|
|
|
+ if (line.contains("结果评价")) continue;
|
|
|
+ if (line.contains("清除能力差") || line.contains("清除能力稍弱") || line.contains("清除能力正常")) continue;
|
|
|
+ if (line.matches("^\\d+$")) continue;
|
|
|
+
|
|
|
+ Matcher m = Pattern.compile("([\u4e00-\u9fff\\w]+)\\s+(\\d+)\\s*$").matcher(line);
|
|
|
+ if (m.matches()) {
|
|
|
+ String name = m.group(1).trim();
|
|
|
+ String value = m.group(2).trim();
|
|
|
+
|
|
|
+ int val;
|
|
|
+ try {
|
|
|
+ val = Integer.parseInt(value);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String status = val > 70 ? "正常" : (val >= 10 ? "稍弱" : "差");
|
|
|
+
|
|
|
+ String key = "毒素清除_" + name;
|
|
|
+ if (!dedupMap.containsKey(key)) {
|
|
|
+ ParsedIndicator ind = new ParsedIndicator();
|
|
|
+ ind.setCategory("毒性物质清除能力");
|
|
|
+ ind.setIndicatorName(name);
|
|
|
+ ind.setIndicatorValue(value);
|
|
|
+ ind.setStatus(status);
|
|
|
+ dedupMap.put(key, ind);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======================== 北京报告 — 分类层级表 ========================
|
|
|
+
|
|
|
+ private void parseBeijingTaxonomyLevels(String fullText, List<String> lines,
|
|
|
+ List<ParsedGutFlora> result) {
|
|
|
+ String[][] levels = {
|
|
|
+ {"门级水平结果总揽", "门级水平结果总览", "门"},
|
|
|
+ {"纲级水平结果总揽", "纲级水平结果总览", "纲"},
|
|
|
+ {"目级水平结果总揽", "目级水平结果总览", "目"},
|
|
|
+ {"科级水平结果总揽", "科级水平结果总览", "科"},
|
|
|
+ {"属级水平结果总揽", "属级水平结果总览", "属"}
|
|
|
+ };
|
|
|
+
|
|
|
+ for (int li = 0; li < levels.length; li++) {
|
|
|
+ String header1 = levels[li][0];
|
|
|
+ String header2 = levels[li][1];
|
|
|
+ String levelChar = levels[li][2];
|
|
|
+
|
|
|
+ int levelStart = fullText.indexOf(header1);
|
|
|
+ if (levelStart == -1) levelStart = fullText.indexOf(header2);
|
|
|
+ if (levelStart == -1) continue;
|
|
|
+
|
|
|
+ int levelEnd = fullText.length();
|
|
|
+ if (li + 1 < levels.length) {
|
|
|
+ int nextStart = fullText.indexOf(levels[li + 1][0], levelStart);
|
|
|
+ if (nextStart == -1) nextStart = fullText.indexOf(levels[li + 1][1], levelStart);
|
|
|
+ if (nextStart != -1) levelEnd = nextStart;
|
|
|
+ }
|
|
|
+
|
|
|
+ String region = fullText.substring(levelStart, levelEnd);
|
|
|
+
|
|
|
+ for (String line : region.split("\r?\n")) {
|
|
|
+ line = line.trim();
|
|
|
+ if (line.isEmpty()) continue;
|
|
|
+ if (line.contains(header1) || line.contains(header2)) continue;
|
|
|
+ if (line.contains("拉丁学名") || line.contains("中文名称")) continue;
|
|
|
+ if (line.contains("相对丰度") || line.contains("人群水平") || line.contains("人群检出率")) continue;
|
|
|
+ if (line.startsWith("注释:") || line.startsWith("拉丁学名:") || line.startsWith("中文名称:")) continue;
|
|
|
+ if (line.startsWith("相对丰度:") || line.startsWith("人群水平:") || line.startsWith("人群检出率:")) continue;
|
|
|
+ if (line.matches("^\\d+$")) continue;
|
|
|
+
|
|
|
+ Pattern p = Pattern.compile(
|
|
|
+ "^([A-Z][a-zA-Z\\-]+)\\s+" +
|
|
|
+ "([\u4e00-\u9fff]+|\\-)\\s+" +
|
|
|
+ "(\\d+\\.?\\d*)%\\s+" +
|
|
|
+ "(\\d+\\.?\\d*)%\\s+" +
|
|
|
+ "(\\d+\\.?\\d*)%?"
|
|
|
+ );
|
|
|
+ Matcher m = p.matcher(line);
|
|
|
+ if (m.find()) {
|
|
|
+ ParsedGutFlora flora = new ParsedGutFlora();
|
|
|
+ String chineseName = m.group(2).trim();
|
|
|
+ flora.setBacteriaName("-".equals(chineseName) ? m.group(1).trim() : chineseName);
|
|
|
+ flora.setBacteriaValue(m.group(3).trim());
|
|
|
+ flora.setPopulationLevel(m.group(4).trim() + "%");
|
|
|
+ flora.setDetectionRate(m.group(5).trim() + "%");
|
|
|
+ flora.setCategory("菌" + levelChar + "构成");
|
|
|
+ flora.setLevel(levelChar);
|
|
|
+ result.add(flora);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======================== 北京报告 — 胃肠道感染致病菌 ========================
|
|
|
+
|
|
|
+ private void parseBeijingGastroInfections(String fullText, List<String> lines,
|
|
|
+ List<ParsedIndicator> result) {
|
|
|
+ int startIdx = fullText.indexOf("10胃肠道感染相关的肠菌评估");
|
|
|
+ if (startIdx == -1) startIdx = fullText.indexOf("胃肠道感染相关的肠菌评估");
|
|
|
+ if (startIdx == -1) return;
|
|
|
+
|
|
|
+ int tableStart = fullText.indexOf("拉丁学名", startIdx);
|
|
|
+ if (tableStart == -1) return;
|
|
|
+
|
|
|
+ int tableEnd = fullText.indexOf("【备注】", tableStart);
|
|
|
+ if (tableEnd == -1) tableEnd = fullText.indexOf("【注释】", tableStart);
|
|
|
+ if (tableEnd == -1) {
|
|
|
+ tableEnd = fullText.indexOf("11营养物质代谢", tableStart);
|
|
|
+ }
|
|
|
+ if (tableEnd == -1) tableEnd = Math.min(tableStart + 3000, fullText.length());
|
|
|
+
|
|
|
+ String region = fullText.substring(tableStart, tableEnd);
|
|
|
+
|
|
|
+ for (String line : region.split("\r?\n")) {
|
|
|
+ line = line.trim();
|
|
|
+ if (line.isEmpty()) continue;
|
|
|
+ if (line.contains("拉丁学名") || line.contains("中文名称")) continue;
|
|
|
+ if (line.contains("检测结果") || line.contains("参考范围")) continue;
|
|
|
+ if (line.contains("胃肠道感染相关的肠菌评估")) continue;
|
|
|
+ if (line.contains("当相对丰度高于")) continue;
|
|
|
+ if (line.matches("^\\d+$")) continue;
|
|
|
+
|
|
|
+ ParsedGutFlora flora = parseBeijingFloraLine(line, "胃肠道感染致病菌");
|
|
|
+ if (flora != null) {
|
|
|
+ ParsedIndicator ind = new ParsedIndicator();
|
|
|
+ ind.setCategory("主要消化道致病菌");
|
|
|
+ ind.setIndicatorName(flora.getBacteriaName());
|
|
|
+ ind.setIndicatorValue(flora.getBacteriaValue());
|
|
|
+ ind.setStatus(flora.getStatus());
|
|
|
+ ind.setRefRange(flora.getNormalRange());
|
|
|
+ result.add(ind);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ======================== 北京报告 — 益生菌 ========================
|
|
|
+
|
|
|
+ private List<ParsedGutFlora> parseBeijingProbiotics(String fullText, List<String> lines) {
|
|
|
+ List<ParsedGutFlora> result = new ArrayList<>();
|
|
|
+
|
|
|
+ int startIdx = fullText.indexOf("8.2有益菌");
|
|
|
+ if (startIdx == -1) return result;
|
|
|
+
|
|
|
+ int endIdx = fullText.indexOf("8.3中性菌", startIdx);
|
|
|
+ if (endIdx == -1) endIdx = fullText.indexOf("8.3", startIdx);
|
|
|
+ if (endIdx == -1) endIdx = Math.min(startIdx + 5000, fullText.length());
|
|
|
+
|
|
|
+ String region = fullText.substring(startIdx, endIdx);
|
|
|
+
|
|
|
+ for (String line : region.split("\r?\n")) {
|
|
|
+ line = line.trim();
|
|
|
+ if (line.isEmpty()) continue;
|
|
|
+ if (line.contains("拉丁学名") || line.contains("中文名称")) continue;
|
|
|
+ if (line.contains("8.2有益菌") || line.contains("8.2")) continue;
|
|
|
+ if (line.contains("【备注】") || line.contains("【注释】")) continue;
|
|
|
+ if (line.matches("^\\d+$")) continue;
|
|
|
+
|
|
|
+ ParsedGutFlora flora = parseBeijingFloraLine(line, "益生菌");
|
|
|
+ if (flora != null) {
|
|
|
+ String firstPart = line.trim();
|
|
|
+ int spaceIdx = firstPart.indexOf(' ');
|
|
|
+ if (spaceIdx > 0) {
|
|
|
+ String afterSpace = firstPart.substring(spaceIdx + 1).trim();
|
|
|
+ if (!afterSpace.isEmpty() && Character.isLowerCase(afterSpace.charAt(0))) {
|
|
|
+ flora.setLevel("SPECIES");
|
|
|
+ result.add(flora);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|