# encoding:utf-8
class TcmExam < ActiveRecord::Base
has_paper_trail
self.table_name = "tcm_exams"
belongs_to :wx_user
belongs_to :scale_user, :foreign_key => :scale_user_id
STATUS_ENUM = [["待处理", "pending"], ["处理中", "processing"], ["已完成", "completed"], ["失败", "failed"]]
# 大模型名称映射(技术名 → 展示名)
AI_MODEL_LABELS = {
'GLM-5.1' => 'GLM-5.1',
'glm-5.1' => 'GLM-5.1',
'gemini-3.1-flash-lite-preview' => 'GLM-5.1',
'gemini-3-pro-preview' => 'GLM-5.1',
}.freeze
def wx_user_contact
return "-" if wx_user.blank?
user = wx_user.user
return "-" if user.blank?
contact = user.tel
contact = user.email if contact.blank?
contact.blank? ? "-" : contact
end
def scale_user_name
return "-" if scale_user.blank?
scale_user.nick_name || "-"
end
# 诊疗大模型展示名称,默认 GLM-5.1
def report_no
"FH-TCM-#{id}"
end
def ai_model_display
AI_MODEL_LABELS[model.to_s] || model.presence || 'GLM-5.1'
end
# 解析 AI 报告内容
def parsed_ai_summary
return {} if ai_summary.blank?
begin
JSON.parse(ai_summary)
rescue
{}
end
end
# 解析观察图片
def parsed_observation_images
return {} if observation_images.blank?
begin
JSON.parse(observation_images)
rescue
{}
end
end
# 解析问诊答案
def parsed_question_answers
return {} if question_answers.blank?
begin
JSON.parse(question_answers)
rescue
{}
end
end
# 解析最新体脂秤结果
def parsed_latest_scale_result
return {} if latest_scale_result.blank?
begin
JSON.parse(latest_scale_result)
rescue
{}
end
end
# 状态标签
def status_label
case status
when "pending" then "待处理"
when "processing" then "处理中"
when "completed" then "已完成"
when "failed" then "失败"
else status || "-"
end
end
# 虚拟方法:调养与建议
def advice_section
parsed_ai_summary["advice"] || "-"
end
# 虚拟方法:望诊记录
def observation_section
parsed_ai_summary["observation"] || "-"
end
# 虚拟方法:AI 总结段落
def summary_section
parsed_ai_summary["summary"] || "-"
end
# 格式化内容,处理换行和 markdown 格式
def format_content(content)
return "-" if content.blank?
formatted = content.to_s
.gsub(/\\n/, "
")
.gsub(/\n/, "
")
.gsub(/\*\*([^*]+)\*\*/, '\1')
.gsub(/\*([^*]+)\*/, '\1')
formatted
end
# 格式化调养与建议的嵌套 JSON 结构
def format_advice(advice)
return "-" if advice.blank?
if advice.is_a?(String)
begin
advice = JSON.parse(advice)
rescue
return format_content(advice)
end
end
return format_content(advice.to_s) unless advice.is_a?(Hash)
category_info = {
'饮食' => { icon: '🍵', color: '#059669' },
'运动' => { icon: '🏃', color: '#2563eb' },
'作息' => { icon: '🌙', color: '#7c3aed' },
'情志' => { icon: '💆', color: '#db2777' }
}
html = ''
advice.each do |category, items|
info = category_info[category] || { icon: '📌', color: '#6b7280' }
html += %Q{
}
html += %Q{
#{info[:icon]} #{category}
}
html += %Q{
}
if items.is_a?(Array)
items.each { |item| html += %Q{- #{format_content(item)}
} }
else
html += %Q{- #{format_content(items)}
}
end
html += '
'
end
html
end
rails_admin do
navigation_label 'AI诊疗记录'
parent ScaleDevice
weight 3
list do
filters [:wx_user, :scale_user, :status, :created_at]
field :id do
label 'ID'
column_width 60
end
field :report_no do
label '报告ID'
column_width 140
pretty_value do
%Q{FH-TCM-#{bindings[:object].id}}.html_safe
end
end
field :wx_user do
label '微信用户'
pretty_value { bindings[:object].wx_user_contact }
end
field :scale_user do
label '用户名'
pretty_value { bindings[:object].scale_user_name }
end
field :model do
label '诊疗大模型'
column_width 130
pretty_value do
name = bindings[:object].ai_model_display
%Q{🤖 #{name}}.html_safe
end
end
field :status do
label '状态'
column_width 80
pretty_value do
label = bindings[:object].status_label
color = case bindings[:object].status
when 'completed' then '#059669'
when 'processing' then '#d97706'
when 'failed' then '#dc2626'
else '#6b7280'
end
%Q{#{label}}.html_safe
end
end
field :ai_summary do
label '报告详情'
formatted_value do
exam = bindings[:object]
bindings[:view].link_to('查看报告', bindings[:view].rails_admin.show_path(model_name: 'tcm_exam', id: exam.id), class: 'btn btn-info btn-sm')
end
end
field :created_at do
label '创建时间'
column_width 150
strftime_format '%Y-%m-%d %H:%M'
end
end
show do
# ── 顶部信息卡:大模型徽章 + 基础信息 ──────────────────────────────
field :id do
label '诊疗概览'
pretty_value do
exam = bindings[:object]
model_name = exam.ai_model_display
status_color = case exam.status
when 'completed' then '#059669'
when 'processing' then '#d97706'
when 'failed' then '#dc2626'
else '#6b7280'
end
status_text = exam.status_label
%Q{
🤖
#{model_name}
诊疗大模型
FH-TCM-#{exam.id}
微信用户
#{exam.wx_user_contact}
称用户名
#{exam.scale_user_name}
创建:#{exam.created_at&.strftime('%Y-%m-%d %H:%M:%S')} | 更新:#{exam.updated_at&.strftime('%Y-%m-%d %H:%M:%S')}
}.html_safe
end
end
# 望诊图片
field :observation_images do
label '📷 望诊图片'
pretty_value do
exam = bindings[:object]
images = exam.parsed_observation_images
return "-".html_safe if images.empty?
image_labels = { 'tongue' => '舌面照', 'tongueBottom' => '舌底照', 'face' => '面部照', 'body' => '全身照' }
html = ''
['tongue', 'tongueBottom', 'face', 'body'].each do |key|
urls = images[key]
next if urls.blank? || urls.empty?
url = urls.first
next if url.blank?
html += %Q{
#{image_labels[key]}
}
end
html += '
'
html.html_safe
end
end
# ── 诊断内容各节(紧凑样式)─────────────────────────────────────────
field :ai_summary do
label '壹 · 一般情况'
pretty_value do
exam = bindings[:object]
content = exam.parsed_ai_summary['general'] || exam.parsed_ai_summary['summary'] || '-'
%Q{#{exam.format_content(content)}
}.html_safe
end
end
field :question_answers do
label '贰 · 问诊 (十问歌)'
pretty_value do
exam = bindings[:object]
content = exam.parsed_ai_summary['inquiry'] || '-'
%Q{#{exam.format_content(content)}
}.html_safe
end
end
field :observation_section do
label '叁 · 望诊与闻诊'
pretty_value do
exam = bindings[:object]
content = exam.parsed_ai_summary['observation'] || '-'
%Q{#{exam.format_content(content)}
}.html_safe
end
end
field :latest_scale_result do
label '肆 · 总体评估与体质辨识'
pretty_value do
exam = bindings[:object]
content = exam.parsed_ai_summary['constitution'] || '-'
%Q{#{exam.format_content(content)}
}.html_safe
end
end
field :advice_section do
label '伍 · 调养与建议'
pretty_value do
exam = bindings[:object]
advice = exam.parsed_ai_summary['advice']
return "-".html_safe if advice.blank?
formatted = exam.format_advice(advice)
%Q{#{formatted}
}.html_safe
end
end
field :summary_section do
label '陆 · 总结'
pretty_value do
exam = bindings[:object]
content = exam.parsed_ai_summary['summary']
return "-".html_safe if content.blank?
%Q{#{exam.format_content(content)}
}.html_safe
end
end
end
edit do
field :status, :enum do
enum { STATUS_ENUM }
end
end
end
end