|
@@ -35,6 +35,9 @@ PG_USER = os.environ.get("POSTGRES_USER", "postgres")
|
|
|
PG_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "postgres")
|
|
PG_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "postgres")
|
|
|
DB_URL = f"postgresql+asyncpg://{PG_USER}:{PG_PASSWORD}@{PG_HOST}:{PG_PORT}/{PG_DB}"
|
|
DB_URL = f"postgresql+asyncpg://{PG_USER}:{PG_PASSWORD}@{PG_HOST}:{PG_PORT}/{PG_DB}"
|
|
|
|
|
|
|
|
|
|
+IMAGES_DIR = os.environ.get("IMAGES_DIR", "/opt/pharmacopoeia-ai/static/images/drugs")
|
|
|
|
|
+IMAGE_URL_PREFIX = "/images/drugs/"
|
|
|
|
|
+
|
|
|
QWEN_API_KEY = os.environ.get("QWEN_API_KEY", "")
|
|
QWEN_API_KEY = os.environ.get("QWEN_API_KEY", "")
|
|
|
EMBEDDING_URL = "https://dashscope.aliyuncs.com/api/v1/services/embeddings/text-embedding/text-embedding"
|
|
EMBEDDING_URL = "https://dashscope.aliyuncs.com/api/v1/services/embeddings/text-embedding/text-embedding"
|
|
|
EMBEDDING_MODEL = "text-embedding-v3"
|
|
EMBEDDING_MODEL = "text-embedding-v3"
|
|
@@ -90,6 +93,48 @@ def _extract_table_text(table) -> str:
|
|
|
return "\n".join(rows)
|
|
return "\n".join(rows)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _extract_images(doc, drug_name):
|
|
|
|
|
+ """从 DOCX 提取所有图片,保存到 IMAGES_DIR,返回 {image_rId: url} 映射"""
|
|
|
|
|
+ os.makedirs(IMAGES_DIR, exist_ok=True)
|
|
|
|
|
+ img_map = {}
|
|
|
|
|
+ for rId, rel in doc.part.rels.items():
|
|
|
|
|
+ if "image" in rel.reltype:
|
|
|
|
|
+ ext = rel.target_ref.split('.')[-1]
|
|
|
|
|
+ if ext.lower() not in ('jpg', 'jpeg', 'png', 'gif', 'bmp'):
|
|
|
|
|
+ ext = 'png'
|
|
|
|
|
+ # 用 rId hash 防止重复
|
|
|
|
|
+ fname = f"{drug_name}_{rId}.{ext}"
|
|
|
|
|
+ fpath = os.path.join(IMAGES_DIR, fname)
|
|
|
|
|
+ if not os.path.exists(fpath):
|
|
|
|
|
+ with open(fpath, 'wb') as f:
|
|
|
|
|
+ f.write(rel.target_part.blob)
|
|
|
|
|
+ img_map[rId] = IMAGE_URL_PREFIX + fname
|
|
|
|
|
+ return img_map
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _get_paragraph_images(p_element, img_map):
|
|
|
|
|
+ """检查段落中是否包含图片,返回对应的 img url 列表"""
|
|
|
|
|
+ urls = []
|
|
|
|
|
+ for run in p_element:
|
|
|
|
|
+ if run.tag == qn("w:r"):
|
|
|
|
|
+ for child in run:
|
|
|
|
|
+ tag = child.tag.split('}')[-1] if '}' in child.tag else child.tag
|
|
|
|
|
+ if tag == 'drawing':
|
|
|
|
|
+ # 尝试从 drawing 的子元素中获取 rId
|
|
|
|
|
+ for dchild in child.iter():
|
|
|
|
|
+ if dchild.tag == qn("a:blip"):
|
|
|
|
|
+ embed = dchild.get(qn("r:embed"))
|
|
|
|
|
+ if embed and embed in img_map:
|
|
|
|
|
+ urls.append(img_map[embed])
|
|
|
|
|
+ elif tag == 'pict':
|
|
|
|
|
+ for pchild in child.iter():
|
|
|
|
|
+ if pchild.tag.endswith('}imagedata'):
|
|
|
|
|
+ rid = pchild.get(qn("r:id"))
|
|
|
|
|
+ if rid and rid in img_map:
|
|
|
|
|
+ urls.append(img_map[rid])
|
|
|
|
|
+ return urls
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def parse_docx(filepath: str) -> dict | None:
|
|
def parse_docx(filepath: str) -> dict | None:
|
|
|
"""
|
|
"""
|
|
|
解析单个 DOCX 文件为药典条目。
|
|
解析单个 DOCX 文件为药典条目。
|
|
@@ -110,6 +155,9 @@ def parse_docx(filepath: str) -> dict | None:
|
|
|
except Exception:
|
|
except Exception:
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
|
|
+ # 提取图片
|
|
|
|
|
+ img_map = _extract_images(doc, display_name)
|
|
|
|
|
+
|
|
|
pinyin = ""
|
|
pinyin = ""
|
|
|
sections = OrderedDict()
|
|
sections = OrderedDict()
|
|
|
current_section = "正文"
|
|
current_section = "正文"
|
|
@@ -134,11 +182,15 @@ def parse_docx(filepath: str) -> dict | None:
|
|
|
|
|
|
|
|
# --- 段落 ---
|
|
# --- 段落 ---
|
|
|
if child.tag != qn("w:p"):
|
|
if child.tag != qn("w:p"):
|
|
|
|
|
+ tag = child.tag.split('}')[-1] if '}' in child.tag else child.tag
|
|
|
|
|
+ if tag in ('drawing', 'pict') and in_drug:
|
|
|
|
|
+ current_text.append("[图片]")
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
# 从段落 XML 中提取文本和样式
|
|
# 从段落 XML 中提取文本和样式
|
|
|
p_text = ""
|
|
p_text = ""
|
|
|
p_style = ""
|
|
p_style = ""
|
|
|
|
|
+ has_image = False
|
|
|
for p_child in child:
|
|
for p_child in child:
|
|
|
if p_child.tag == qn("w:pPr"):
|
|
if p_child.tag == qn("w:pPr"):
|
|
|
for style_child in p_child:
|
|
for style_child in p_child:
|
|
@@ -150,8 +202,22 @@ def parse_docx(filepath: str) -> dict | None:
|
|
|
t = r_child.text
|
|
t = r_child.text
|
|
|
if t:
|
|
if t:
|
|
|
p_text += t
|
|
p_text += t
|
|
|
|
|
+ # 检测段落内嵌图片
|
|
|
|
|
+ rt = r_child.tag.split('}')[-1] if '}' in r_child.tag else r_child.tag
|
|
|
|
|
+ if rt in ('drawing', 'pict'):
|
|
|
|
|
+ has_image = True
|
|
|
|
|
|
|
|
text = p_text.strip()
|
|
text = p_text.strip()
|
|
|
|
|
+ # 内嵌图片的段落
|
|
|
|
|
+ if has_image:
|
|
|
|
|
+ img_urls = _get_paragraph_images(child, img_map) if not p_text else []
|
|
|
|
|
+ if not text and img_urls:
|
|
|
|
|
+ img_tag = ' '.join(f'<img src="{u}" style="max-width:100%"/>' for u in img_urls)
|
|
|
|
|
+ current_text.append(img_tag)
|
|
|
|
|
+ continue
|
|
|
|
|
+ elif not text and not img_urls:
|
|
|
|
|
+ current_text.append("[图片]")
|
|
|
|
|
+ continue
|
|
|
if not text or text.isspace():
|
|
if not text or text.isspace():
|
|
|
continue
|
|
continue
|
|
|
|
|
|