cancer_predict/app/models/cancerpredictfields.rb

319 lines
16 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class Cancerpredictfields
require 'pathname'
require 'json'
include Mongoid::Document
include Mongoid::Timestamps
include OrbitModel::Status
include OrbitModel::Impression
# encoding: utf-8
include OrbitTag::Taggable
include OrbitCategory::Categorizable
FIELDINFO = {"variable"=>"String","name"=>"String","is_num"=>"Fixnum","hint"=>"String","comment_text"=>"String","choice_fields"=>"Array","range"=>"Array","right"=>"Fixnum","is_float"=>"Fixnum","revert_value"=>"Fixnum","map_values"=>"Array","cancer_predict_mapping_file"=>"String"}
NonLoclaized = ["variable","is_num","range","right","is_float","revert_value","map_values","cancer_predict_mapping_file"]
field :title ,type:String ,default:""
field :form_show , :type=> Hash ,default:{0=>{:variable=>"sex",:name=>{"zh_tw"=>"性別<br/>(Sex)","en"=>"Sex"},:is_num=>0, :hint=>{'zh_tw'=>'','en'=>''} , :comment_text=>{'zh_tw'=>'','en'=>''}, :choice_fields=> {"zh_tw"=>['男','女'],"en"=>['Male','Female']},:range=>[],:right=>0,:is_float=>0,:revert_value=>0,:map_values=>[],:cancer_predict_mapping_file=>""},
1=>{:variable=>"age",:name=>{"zh_tw"=>"年齡<br/>(Age)","en"=>"Age"},:is_num=>1, :hint=>{'zh_tw'=>'從 20 歲(含)開始至 98 歲','en'=>'Age must be between 20 and 98'} , :comment_text=>{'zh_tw'=>'','en'=>''}, :choice_fields=> {"zh_tw"=>[],"en"=>[]},:range=>[20,98],:right=>0,:is_float=>0,:revert_value=>0,:map_values=>[],:cancer_predict_mapping_file=>""},
2=>{:variable=>"calcification_score",:name=>{"zh_tw"=>"鈣化指數<br/>(Calcification score)","en"=>"Calcification score"},:is_num=>1,:hint=>{'zh_tw'=>'請輸入0到5000的數字','en'=>'Please enter a number between 0 and 5000'}, :comment_text=>{'zh_tw'=>'','en'=>''}, :choice_fields=> {"zh_tw"=>[],"en"=>[]},:range=>[0,5000],:right=>0,:is_float=>1,:revert_value=>0,:map_values=>[],:cancer_predict_mapping_file=>""}
}
field :form_show_in_result , :type=> Hash ,default:{}#{0=>{:variable=>"hormone_therapy",:name=>{"zh_tw"=>"賀爾蒙治療","en"=>"Hormone/Steroid therapy"},:is_num=>0, :hint=>{'zh_tw'=>'適用賀爾蒙受體陽性病人','en'=>'Hormone/ steroid therapy is available when ER status is positive'} , :comment_text=>{'zh_tw'=>'','en'=>''}, :choice_fields=> {"zh_tw"=>['否','是'],"en"=>['No','Yes']},:range=>[]},
#1=>{:variable=>"Chemotherapy",:name=>{"zh_tw"=>"化學治療","en"=>"Chemotherapy"},:is_num=>0,:hint=>{'zh_tw'=>'','en'=>''}, :comment_text=>{'zh_tw'=>'','en'=>''}, :choice_fields=> {"zh_tw"=>['否','是'],"en"=>['No','Yes']},:range=>[]},
#2=>{:variable=>"Radiotherapy",:name=>{"zh_tw"=>"放射治療","en"=>"Radiotherapy"},:is_num=>0,:hint=>{'zh_tw'=>'','en'=>''}, :comment_text=>{'zh_tw'=>'','en'=>''}, :choice_fields=> {"zh_tw"=>['否','是'],"en"=>['No','Yes']},:range=>[]},
#3=>{:variable=>"Targeted_therapy",:name=>{"zh_tw"=>"標靶治療","en"=>"Targeted therapy"},:is_num=>0,:hint=>{'zh_tw'=>'抗HER2治療','en'=>''}, :comment_text=>{'zh_tw'=>'','en'=>''}, :choice_fields=> {"zh_tw"=>['否','是'],"en"=>['No','Yes']},:range=>[]}
#}
field :form_result_is_right , :type=> Integer ,default: 0
field :text_descibe ,type:Hash ,default:{"zh_tw"=>"歡迎使用台灣心血管疾病預後預測系統<br />本預測系統由全民健保資料庫2017年~2020年間共1964位病人電腦斷層影像所建立之預測模型<br />請在下方填入相關資訊","en"=>"Welcome to the Taiwan Breast Cancer Prediction System!<br/>The prediction system is constructed using clinical data from 90,841 breast cancer patients in the Taiwan Cancer Registry database between 2011 to 2015, and validated using clinical data from 49,374 breast cancer patients in the U.S.-based Surveillance, Epidemiology and End Results (SEER) database. <br/>To start, please select the information below."}
field :small ,type:Hash ,default:{'font_size'=>"0.825em",'active'=>0}
field :medium ,type:Hash ,default:{'font_size'=>"1em",'active'=>1}
field :large ,type:Hash ,default:{'font_size'=>"1.25em",'active'=>0}
field :head_images_id ,type:Array , default: []
field :title_images_id ,type:Array , default: []
field :title_texts ,type:Hash ,default:{'zh_tw'=>'臺灣心血管疾病存活預測','en'=>'Cardiovascular Disease Survival Forecast in Taiwan'}
field :table_above_texts ,type:Hash ,default:{'zh_tw'=>"下表之分析為針對手術後病人,根據選定的術後治療,分別估計在半年、一年及一年半的再住院或死亡機率。",'en'=>'The analysis is for women who had undergone surgery.The table shows the 0.5-, 1- and 1.5-year survival rates,based on the treatment you have selected.'}
field :text_above_texts ,type:Hash ,default:{'zh_tw'=>"此研究分析來自於照射胸部電腦斷層所得之結果,根據您所輸入的資訊,在第{{years}}年內有2.69%的機率可能再住院或死亡{{Surgery_only}}%。",'en'=>'此研究分析來自於照射胸部電腦斷層所得之結果,根據您所輸入的資訊,在第{{years}}年內有2.69%的機率可能再住院或死亡{{Surgery_only}}%。'}
field :surgery_only_texts ,type:Hash ,default:{'zh_tw'=>'','en'=>''}
field :extra_texts ,type:Hash ,default:{'zh_tw'=>',此外','en'=>''}
field :extra_therapy_texts ,type:Hash ,default:{'zh_tw'=>'100 位在術後有接受{{extra_therapy}}的婦女中,有{{survival_num}}位婦女,術後{{surgery_year}}年仍為存活(多了{{Additional_Benefit}}位)','en'=>'{{survival_num}} out of 100 women treated with {{extra_therapy}} are alive (an extra {{Additional_Benefit}})'}
field :danger_texts ,type:Hash ,default:{'zh_tw'=>'請注意紅框的輸入資料是否符合要求!','en'=>'Please check whether input data in red blocks are correct!'}
field :years ,type:Array ,default:[1,1.5,2,2.5]
field :texts_between_Result_and_result_block ,type:Hash ,default:{'zh_tw'=>'如果欲將預測結果應用於臨床上,請務必與您的主治醫師討論後再做最後決定。','en'=>'Please note that the patients need to consult with their medical doctors before making any decision.'}
#field :image_uploader ,type:Object
field :prediction_formula , type: String ,default: "A = 0.1327868* (sex_value- 0.4858824)
+ 0.0371720* (age_test1 - 61.56000) -0.07447278* (age_test2 - 13.10152)
+ 0.4315686* (age_test3 - 0.9844332)
+ 0.0009163615*( calH_test1 - 182.9347)
-0.0007536899*( calH_test2 - 124.8706) -0.00004697183*( calH_test3 -80.75636)
+ 0.0001401325*( calAH_test1 - 700.7824)
-0.001349783*( calAH_test2 - 634.2167) +0.001753832*( calAH_test3 -419.3361)
+ 0.0001906046*( calDH_test1 -835.2894) -0.000251567*( calDH_test2 - 213.1630)
-0.002173942*( fat_test1 -108.4149)+0.003066541*( fat_test2 - 28.33497)
+0.6700708*(N4-0.3241176)
+0.3336162*(O3-0.4994118)
+0.1322476*(O20-0.1741176)
+0.9084972*(O18-0.008823529)
+0.2978388*(N12-0.1152941)
+0.1777935*(N20-0.3582353)
+1.588042*(N31-0.002352941)
+0.2197419*(O6-0.07823529)
+1.791159*(N34-0.001176471)
+0.4305973*(N14-0.02176471)
-0.4472885*(N29-0.02411765)
+0.2601319*(N26-0.04941176)
-0.2364269*(O11-0.1164706)
+0.1784179*(N6-0.1070588)
+0.6023170*(O14-0.01294118)
-1.031959*(N43-0.007058824)
+0.4257809*(O17-0.01823529)
+0.2002546*(O9-0.06176471)"
field :years_settings , type: Array , default: ["0.8095037^( exp(A) )","0.729158^( exp(A) )","0.6717211^( exp(A) )","0.6056773^( exp(A) )"]
field :tmp_years_settings , type: Array , default: []
field :tmp_years_settings_for_ruby , type: Array , default: []
field :lpv_calc, type: Hash, default: {} #for js code
field :tmp_lpv_ruby_code, type: String, default: ""
field :tmp_lpv_variables, type: Array, default: []
field :mapping_data_from_csv , type: String ,default: ""
scope :can_display, ->{where(:is_hidden=>false,:is_preview => false).any_of({:postdate.lt=>Time.now, :deadline.gt=>Time.now},{:postdate.lt=>Time.now, :deadline=>nil}).order_by([:is_top, :desc],[:postdate, :desc])}
scope :is_approved, ->{where(:approved => true)}
#before_create :set_expire
before_save do
self.form_show.each do |num,property|
property[:need_map_values] = (property[:map_values].class == Array && property[:choice_fields].class == Array && property[:map_values].length == property[:choice_fields].length) ? 1 : 0
end
result_keys = []
self.form_show.each do |num,property|
variable_name = property[:variable]
if variable_name.present?
result_keys << variable_name
end
end
mapping_data = JSON.parse(self.mapping_data_from_csv) rescue {}
if mapping_data.present?
mapping_data.each do |k,v|
result_keys += (v.keys rescue [])
end
end
formula = self.prediction_formula.gsub("\r\n"," ").gsub("^","**")
result_keys.each do |k|
formula = formula.gsub(/#{k}?(-|\+|\*|\/|\s|\=)/){ "result[\"#{k}\"]#{$1}" }
end
formula = formula.split(/^([^=]+)=([^=])/).select{|s| s.present?}.each_slice(2).map do |a,b|
if b
("@"+ a + "=" + b)
else
"@"+ a
end
end.join("\n")
self.tmp_lpv_ruby_code = formula
formula_variables = formula.enum_for(:scan,/([^\=\(\)]+)?=[^=]/).map {|x| x[-1] }.compact.map{|s| s.strip}
self.tmp_lpv_variables = formula_variables
self.tmp_years_settings = self.years_settings.map do |s|
s.gsub('^','**').gsub('exp','Math.exp')
end
self.tmp_years_settings_for_ruby = self.tmp_years_settings.clone
formula_variables.each do |variable_name|
self.tmp_years_settings_for_ruby = self.tmp_years_settings_for_ruby.map do |y|
y.gsub(variable_name[1..-1],variable_name)
end
end
self.lpv_calc = get_years_settings_dict
self.generate_eval_formula
end
def generate_eval_formula
eval_formula = "def eval_formula(result); #{self.tmp_lpv_ruby_code}; end"
CancerpredictsController.module_eval(eval_formula)
end
def update_user
User.find(update_user_id) rescue nil
end
def update_user=(user)
self.update_user_id = user.id
end
def email_members
MemberProfile.find(self.email_member_ids) rescue []
end
def email_addresses
addresses = self.email_members.collect{|member| member.email} rescue []
addresses = addresses +[self.other_mailaddress] if !self.other_mailaddress.blank?
addresses.flatten
end
def email
mail = Email.find(self.email_id) rescue nil
end
def expired?
(self.deadline < Time.now) rescue false
end
def destroy_email
mail = Email.find(self.email_id) rescue nil
mail.destroy if !mail.nil?
end
def self.remove_expired_status
self.where(:is_top => true, :top_end_date.ne => nil, :top_end_date.lt => Time.now).each do |b|
b.is_top = false
b.top_end_date = nil
b.save
end
end
def generate_jscode
js_code = "var map_values , mapping_hash , temp_index ,temp_value , index , closest_value;\r\n"
mapping_data_from_csv = YAML.load(self.mapping_data_from_csv) rescue {}
variable_keys = []
self.form_show.each do |num,property|
@variable = property[:variable]
if @variable.present?
if property[:is_num] == 1
js_code += "\t\t\t\tresult['#{@variable}'] = Number(result_json['#{@variable}']);\r\n"
elsif property[:choice_fields].present?
if property[:map_values].class == Array && property[:choice_fields].class == Array && property[:map_values].length == property[:choice_fields].length
js_code += "\t\t\t\tmap_values = #{property[:map_values]};\r\n"
js_code += "\t\t\t\tresult['#{@variable}'] = map_values[Number(result_json['#{@variable}'']) - 1];\r\n"
else
if property[:revert_value] != 1
js_code += "\t\t\t\tresult['#{@variable}'] = Number(result_json['#{@variable}']) - 1;\r\n"
else
js_code += "\t\t\t\tresult['#{@variable}'] = (#{property[:choice_fields].length} - Number(result_json['#{@variable}']));\r\n"
end
end
end
variable_keys.push(@variable)
if property[:cancer_predict_mapping_file].present?
if (mapping_data_from_csv != {} && !mapping_data_from_csv[@variable].blank?)
variable_keys.concat(mapping_data_from_csv[@variable].keys)
js_code += "\t\t\t\tmapping_hash = mapping_data_from_csv['#{@variable}'];\r\n"
js_code += "\t\t\t\ttemp_index = 0;\r\n"
js_code += "\t\t\t\ttemp_value = result[#{@variable}];\r\n"
js_code += "\t\t\t\tindex = 0;
$.each(mapping_hash,function(k,v){
if( i == 0 ){
var index_val = v.indexOf(temp_value);
if( index_val != -1 ){
temp_index = index_val;
}else{
closest_value = v.get_nearest_value(temp_value);
temp_index = v.indexOf(closest_value)
}
}
result[k] = v[temp_index];
index++;
});\r\n"
end
end
end
end
formula = self.prediction_formula.gsub("\r\n"," ").gsub("^","**")
variable_keys.each do |k|
formula = formula.gsub(/#{k}?(-|\+|\*|\s|\=)/){ "result[\"#{k}\"]#{$1}" }
end
formula_variables = formula.enum_for(:scan,/([^\=]*)?=/).map { ::Regexp.last_match[1] }.map{|s| s.strip.split(/(\s*|;\r\n)/).last}
js_code = "function calculate_first_lpv(result_json){
result = {};
#{js_code}
try{
result['lpv'] = (#{formula});
}catch(e){result['lpv'] = \"error\"};
console.log(result['lpv']);
#{formula_variables.map{|v| "result['lpv_variable']['#{v}'] = #{v};"}.join("\r\n\t\t\t\t") }
return result;
};
function calculate_and_change_result_value(obj){
obj.servive_ratio_arr = [];
for(var i = 0;i<obj.active_treatment.length;i++){
var servive_ratio = round((1-(calculate_servive_ratio(obj.year,obj.lpv_real[i])))*100,2);
var benefit = servive_ratio - obj.servive_ratio_arr[obj.servive_ratio_arr.length-1];
obj.servive_ratio_arr.push(servive_ratio);
$('tr.'+obj.active_treatment[i]+' td.Overall_Survival').html(servive_ratio+'%');
$('.'+obj.active_treatment[i]+'.Overall_Survival').html(servive_ratio);
if(i != 0){
$('tr.'+obj.active_treatment[i]+' td.Additional_Benefit').html(round(benefit,2)+'%');
$('.'+obj.active_treatment[i]+'.Additional_Benefit').html(Math.round(benefit));
}
}
//$('.'+obj.active_treatment[0]+'.Overall_Survival').html(Math.round(obj.servive_ratio_arr[0]));
};"
@years = self.years
switch_texts = "
#{formula_variables.map{|v| "var #{v} = obj['#{v}'];"}.join("\r\n\t\t\t\t")}
switch(year) {"
@years.each do |year|
year_index = @years.index(year)
switch_texts +=
"
case '#{year}':
servive_ratio = #{self.tmp_years_settings[year_index]};
break;
"
end
switch_texts += "
default:
console.log('not found year.');
}"
js_code = js_code +"
function calculate_servive_ratio(year,obj){
var servive_ratio;
#{switch_texts}
return servive_ratio;
}
"
return js_code
end
def auto_write_predict_js
js_codes = generate_jscode
module_app_path = Pathname.new(File.expand_path(__dir__)).dirname.dirname.to_s
save_path = module_app_path + '/app/assets/javascripts/cancer_predict.js'
file_texts = File.read(save_path)
str1 = "/* auto add start */"
index1 = file_texts.index(str1)
str2 = "/* auto add end */"
index2 = file_texts.index(str2)
str3 = "/*lpv_calc_formula_start*/"
index3 = file_texts.index(str3)
str4 = "/*lpv_calc_formula_end*/"
index4 = file_texts.index(str4)
file_texts = file_texts.sub(file_texts[(index1+str1.length+2) .. (index2 - 1)] , js_codes)
file_texts = file_texts.sub(file_texts[(index3+str3.length) .. (index4 - 1)] , self.lpv_calc.to_json.gsub("@","."))
if (!index1.nil? && !index2.nil?) || (!index3.nil? && !index4.nil?)
File.write(save_path,file_texts)
end
end
def get_years_settings_dict
lpv_variable_name = (prediction_formula.include?("=") ? prediction_formula.split("=")[0].strip : "" rescue "")
res = self.years.map.with_index do |y, i|
tmp_formula = self.tmp_years_settings[i]
if lpv_variable_name.present?
tmp_formula = tmp_formula.gsub(lpv_variable_name, "lpv_current")
else
tmp_formula = tmp_formula.gsub("lpv", "lpv_current")
end
[y.to_s.sub(".","@"), tmp_formula]
end
res.to_h
end
end