document_templates.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. from __future__ import annotations
  2. from pathlib import Path
  3. from sqlalchemy import select
  4. from sqlalchemy.orm import Session
  5. from app.core.errors import APIError
  6. from app.db.models.document import DocumentExtractionTemplate
  7. DOCUMENT_TYPE_BY_EXTENSION = {
  8. ".pdf": "pdf",
  9. ".doc": "word",
  10. ".docx": "word",
  11. ".xls": "excel",
  12. ".xlsx": "excel",
  13. ".csv": "excel",
  14. }
  15. DEFAULT_EXTRACTION_TEMPLATES = [
  16. {
  17. "code": "pdf_document_v1",
  18. "name": "PDF 文档关键信息",
  19. "document_type": "pdf",
  20. "description": "抽取 PDF 规程、报告、措施和验收资料中的关键业务信息。",
  21. "prompt": (
  22. "你是一名矿山通防业务资料抽取专家。请根据 PDF 转出的 Markdown,"
  23. "按照字段定义提取信息;找不到时填 null,不得编造。"
  24. ),
  25. "extraction_schema": {
  26. "fields": [
  27. {"key": "document_title", "name": "资料名称", "type": "text", "required": True},
  28. {"key": "document_code", "name": "资料编号", "type": "text", "required": False},
  29. {"key": "issuing_department", "name": "编制或发布单位", "type": "text", "required": False},
  30. {"key": "effective_date", "name": "生效日期", "type": "date", "required": False},
  31. {"key": "applicable_scope", "name": "适用范围", "type": "text", "required": False},
  32. {"key": "key_requirements", "name": "关键要求", "type": "list", "required": True},
  33. {"key": "key_parameters", "name": "关键参数", "type": "object", "required": False},
  34. {"key": "risk_points", "name": "风险点", "type": "list", "required": False},
  35. {"key": "conclusion", "name": "结论或执行要求", "type": "text", "required": False},
  36. ]
  37. },
  38. },
  39. {
  40. "code": "word_document_v1",
  41. "name": "Word 文档关键信息",
  42. "document_type": "word",
  43. "description": "抽取 Word 措施、制度、设计和审批资料中的关键业务信息。",
  44. "prompt": (
  45. "你是一名矿山通防业务资料抽取专家。请根据 Word 转出的 Markdown,"
  46. "重点识别编制依据、责任部门、执行要求、审批信息和时间节点;找不到时填 null。"
  47. ),
  48. "extraction_schema": {
  49. "fields": [
  50. {"key": "document_title", "name": "资料名称", "type": "text", "required": True},
  51. {"key": "document_type", "name": "资料类型", "type": "text", "required": False},
  52. {"key": "responsible_department", "name": "责任部门", "type": "text", "required": False},
  53. {"key": "applicable_area", "name": "适用区域", "type": "text", "required": False},
  54. {"key": "effective_date", "name": "执行日期", "type": "date", "required": False},
  55. {"key": "requirements", "name": "执行要求", "type": "list", "required": True},
  56. {"key": "approval_info", "name": "审批信息", "type": "object", "required": False},
  57. {"key": "attachments", "name": "附件清单", "type": "list", "required": False},
  58. {"key": "conclusion", "name": "结论或落款", "type": "text", "required": False},
  59. ]
  60. },
  61. },
  62. {
  63. "code": "excel_document_v1",
  64. "name": "Excel 表格关键信息",
  65. "document_type": "excel",
  66. "description": "抽取 Excel 报表、台账和测风记录中的指标、范围和异常信息。",
  67. "prompt": (
  68. "你是一名矿山通防业务数据抽取专家。请根据 Excel 转出的 Markdown 表格,"
  69. "识别表头含义、统计周期、关键指标和异常记录;找不到时填 null,数值保持原始单位。"
  70. ),
  71. "extraction_schema": {
  72. "fields": [
  73. {"key": "workbook_title", "name": "报表名称", "type": "text", "required": True},
  74. {"key": "sheet_names", "name": "工作表名称", "type": "list", "required": False},
  75. {"key": "date_range", "name": "统计周期", "type": "text", "required": False},
  76. {"key": "row_count", "name": "数据行数", "type": "number", "required": False},
  77. {"key": "key_metrics", "name": "关键指标", "type": "object", "required": True},
  78. {"key": "abnormal_rows", "name": "异常记录", "type": "list", "required": False},
  79. {"key": "summary", "name": "数据摘要", "type": "text", "required": False},
  80. ]
  81. },
  82. },
  83. ]
  84. def detect_document_type(file_ext: str | None) -> str:
  85. extension = Path(file_ext or "").suffix.lower()
  86. if not extension:
  87. extension = f".{str(file_ext or '').strip().lstrip('.').lower()}"
  88. document_type = DOCUMENT_TYPE_BY_EXTENSION.get(extension)
  89. if document_type is None:
  90. supported = "、".join(sorted(DOCUMENT_TYPE_BY_EXTENSION))
  91. raise APIError(
  92. 422,
  93. "unsupported_document_type",
  94. f"暂不支持该文档类型,可用扩展名:{supported}",
  95. )
  96. return document_type
  97. def ensure_default_templates(db: Session) -> None:
  98. for item in DEFAULT_EXTRACTION_TEMPLATES:
  99. template = db.scalar(
  100. select(DocumentExtractionTemplate).where(
  101. DocumentExtractionTemplate.code == item["code"]
  102. )
  103. )
  104. if template is None:
  105. template = DocumentExtractionTemplate(**item)
  106. db.add(template)
  107. else:
  108. template.name = item["name"]
  109. template.document_type = item["document_type"]
  110. template.description = item["description"]
  111. template.prompt = item["prompt"]
  112. template.extraction_schema = item["extraction_schema"]
  113. template.enabled = True
  114. db.commit()
  115. def get_template_for_type(
  116. db: Session,
  117. document_type: str,
  118. ) -> DocumentExtractionTemplate:
  119. template = db.scalar(
  120. select(DocumentExtractionTemplate)
  121. .where(
  122. DocumentExtractionTemplate.document_type == document_type,
  123. DocumentExtractionTemplate.enabled.is_(True),
  124. )
  125. .order_by(DocumentExtractionTemplate.updated_at.desc())
  126. )
  127. if template is None:
  128. raise APIError(
  129. 422,
  130. "extraction_template_not_found",
  131. f"未配置 {document_type} 类型的抽取模板",
  132. )
  133. return template