# 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 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{
' 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 :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}
诊疗大模型
记录 ID
##{exam.id}
微信用户
#{exam.wx_user_contact}
称用户名
#{exam.scale_user_name}
状态
#{status_text}
创建:#{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