//////////////////////////////////////////////////////////////////////
// Copyright (C) Hiroshi SUGIMURA 2021.11.09
//////////////////////////////////////////////////////////////////////
/**
* @module mainHALlocal
*/
'use strict'
//////////////////////////////////////////////////////////////////////
// 基本ライブラリ
const { sqlite3, eldataModel, IOT_QuestionnaireAnswersModel, IOT_MajorResultsModel, IOT_MinorResultsModel, IOT_MinorkeyMeansModel, MinorkeyMeansValues } = require('./models/localDBModels'); // DBデータと連携
const { Op } = require("sequelize");
const Store = require('electron-store');
const store = new Store();
//////////////////////////////////////////////////////////////////////
// config
let config = { // デフォルト値
enabled: false,
halApiToken: '',
startUploadEldata: 300000,
resultExpireDays: 365,
ellogExpireDays: 30,
UPLOAD_UNIT_NUM: 100, // 分割アップロードのログの数
UPLOAD_UNIT_INTERVAL: 1000, // 分割アップロードの間隔 (ミリ秒)
UPLOAD_START_INTERVAL: 300000, // アップロード処理の起動間隔 (ミリ秒)
debug: false // mainHALsyncのdebug
};
//////////////////////////////////////////////////////////////////////
// Local function
/**
* @func getToday
* @desc 今日の日付 ("YYYY-MM-DD")
* @async
* @param {void}
* @return void
* @throw error
*/
let getToday = function() {
let now = new Date();
let today = [
now.getFullYear().toString(),
('0' + (now.getMonth() + 1)).slice(-2),
('0' + now.getDate()).slice(-2)
].join('-');
return today;
};
//////////////////////////////////////////////////////////////////////
let mainHALlocal = {
/**
* @func initialize
* @desc HAL, Home-life Assessment Listの処理
* @async
* @param {void}
* @return void
* @throw error
*/
initialize: async function() {
config = await store.get('config.HAL', config);
},
//////////////////////////////////////////////////////////////////////
/**
* @func submitQuestionnaire
* @desc アンケート回答したので処理
* @async
* @param {void}
* @return void
* @throw error
*/
submitQuestionnaire: async function( arg, succeessFunc, errorFunc ) {
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainHALlocal.submitQuestionnaire()' ):0;
// console.dir( arg );
let today = getToday();
// IOT_QuestionnaireAnswers テーブル用データ
let q_data = {
date: today
};
// IOT_MinorResults テーブル用データ
let minor_data = {
date: today
};
// アンケート回答の値をチェック
try {
for (let i = 1; i <= 6; i++) {
for (let j = 1; j <= 15; j++) {
let k = 'q_' + i + '_' + j;
let v = arg[k];
if (!v) {
q_data[k] = null;
minor_data['r_' + i + '_' + j] = null;
}else if (/^\d+$/.test(v)) {
v = parseInt(v, 10);
if (v >= 0 || v <= 100) {
q_data[k] = v;
minor_data['r_' + i + '_' + j] = v;
} else {
throw new Error('回答の値が不正です: ' + k + '=' + v);
}
} else {
throw new Error('回答の値が不正です: ' + k + '=' + v);
}
}
}
} catch (error) {
console.error(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| questionnaire_error', error.message);
return;
}
// console.log( 'アンケート回答の値をチェック ok' );
// MajorResults を計算
let major_data = mainHALlocal.calcMajorResults(q_data);
// console.log( 'mainHALlocal.calcMajorResults ok' );
// SQL トランザクション開始
let t = await sqlite3.transaction();
// console.log( 'transaction started' );
try {
// IOT_QuestionnaireAnswers テーブルから該当ユーザーの今日のレコードを取得
let record = await IOT_QuestionnaireAnswersModel.findOne({ where: { date: today } });
if( record ) {
await record.update(q_data); // レコードあればUpdate
}else{
await IOT_QuestionnaireAnswersModel.create(q_data); // レコードなければinsert
}
// console.log('アンケート格納 ok');
// IOT_MinorResults テーブルから該当ユーザーの今日のレコードを取得
let minor = await IOT_MinorResultsModel.findOne({ where: { date: today } });
let now = new Date();
// console.log('today, now確認');
// console.dir(today);
// console.dir(now);
if (minor) {
// 今日の minorResults レコードが見つかれば、成績データを更新
let minor_update_data = {};
if (minor.dataValues.assessmentSource === 'PLIS') {
// assessmentSource が "PLIS" なら null のカラムだけ更新
for (let [k, v] of Object.entries(minor.dataValues)) {
if (/^r_\d+_\d+$/.test(k) && v === null) {
minor_update_data[k] = v;
}
}
} else {
// assessmentSource が "questionnaire" のとき,すべてのカラムを更新
minor_update_data = minor_data;
}
// minorResults を UPDATE
minor_update_data.updatedAt = now;
await IOT_MinorResultsModel.update(minor_update_data, {
where: {
idIOT_MinorResults: minor.dataValues.idIOT_MinorResults
}
});
// 該当ユーザーの今日の majorResults レコードを検索
// MinorResults レコードがあれば、MajorResults のレコードもあるはず
let major = await IOT_MajorResultsModel.findOne({ where: { date: today } });
if (!major) {
throw new Error('成績データ major/minor 不整合が見つかりました。');
}
let major_update_data = {};
if (major.dataValues.assessmentSource === 'PLIS') {
// assessmentSource が "PLIS" なら null のカラムだけ更新
for (let [k, v] of Object.entries(major.dataValues)) {
if (/^r_\d+_\d+$/.test(k) && v === null) {
major_update_data[k] = v;
}
}
} else {
// assessmentSource が "questionnaire" なら、すべてのカラムを更新
major_update_data = major_data;
}
// majorResults を UPDATE
major_update_data.updatedAt = now;
await IOT_MajorResultsModel.update(major_update_data, {
where: {
idIOT_MajorResults: major.dataValues.idIOT_MajorResults
}
});
} else {
// 該当ユーザーの今日の minorResults レコードが見つからなければ、新規に成績データを追加
// minor_data.createdAt = now;
// minor_data.updatedAt = now;
minor_data.assessmentSource = 'questionnaire';
await IOT_MinorResultsModel.create(minor_data);
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainHALlocal.submitQuestionnaire() IOT_MinorResultsModel格納'):0;
major_data.date = today;
// major_data.createdAt = now;
// major_data.updatedAt = now;
major_data.assessmentSource = 'questionnaire';
await IOT_MajorResultsModel.create(major_data);
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainHALlocal.submitQuestionnaire() IOT_MajorResultsModel格納'):0;
}
// コミット
await t.commit();
// console.log('commited');
succeessFunc();
} catch (error) {
// ロールバック
await t.rollback();
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainHALlocal.submitQuestionnaire() rollbacked'):0;
console.error(error);
// HTTP レスポンス
errorFunc();
}
},
/**
* @func calcMajorResults
* @desc アンケート回答から Major 成績データを計算する
* @async
* @param {void}
* @return void
* @throw error
*/
calcMajorResults: function(q_data) {
// console.log( '-- calcMajorResults()' );
// console.dir( q_data );
let clothing_sum = 0;
let clothing_num = 0;
let food_sum = 0;
let food_num = 0;
let housing_sum = 0;
let housing_num = 0;
let physical_sum = 0;
let physical_num = 0;
let mental_sum = 0;
let mental_num = 0;
let ecology_sum = 0;
let ecology_num = 0;
for (let [k, v] of Object.entries(q_data)) {
if (v === null) {
continue;
}
if (k.startsWith('q_1_')) {
clothing_sum += v;
clothing_num++;
} else if (k.startsWith('q_2_')) {
food_sum += v;
food_num++;
} else if (k.startsWith('q_3_')) {
housing_sum += v;
housing_num++;
} else if (k.startsWith('q_4_')) {
physical_sum += v;
physical_num++;
} else if (k.startsWith('q_5_')) {
mental_sum += v;
mental_num++;
} else if (k.startsWith('q_6_')) {
ecology_sum += v;
ecology_num++;
}
}
// 平均値を求める
let clothing_point = clothing_sum / clothing_num;
let food_point = food_sum / food_num;
let housing_point = housing_sum / housing_num;
let physical_point = physical_sum / physical_num;
let mental_point = mental_sum / mental_num;
let ecology_point = ecology_sum / ecology_num;
// totalPoint は平均値の和
let total_point = (clothing_point + food_point + housing_point + physical_point + mental_point + ecology_point) / 6;
// totalRank の特定
let total_rank = '';
if (total_point >= 90) {
total_rank = 'SSS';
} else if (total_point >= 80) {
total_rank = 'SS';
} else if (total_point >= 70) {
total_rank = 'S';
} else if (total_point >= 60) {
total_rank = 'A';
} else if (total_point >= 50) {
total_rank = 'B';
} else if (total_point >= 40) {
total_rank = 'C';
} else if (total_point >= 30) {
total_rank = 'D';
} else if (total_point >= 20) {
total_rank = 'E';
} else {
total_rank = 'F';
}
let data = {
clothingPoint: mainHALlocal.roundMajorFloat(clothing_point),
clothingRawScore: mainHALlocal.roundMajorFloat(clothing_point * 5),
foodPoint: mainHALlocal.roundMajorFloat(food_point),
foodRawScore: mainHALlocal.roundMajorFloat(food_point * 5),
housingPoint: mainHALlocal.roundMajorFloat(housing_point),
housingRawScore: mainHALlocal.roundMajorFloat(housing_point * 5),
physicalHealthPoint: mainHALlocal.roundMajorFloat(physical_point),
physicalHealthRawScore: mainHALlocal.roundMajorFloat(physical_point * 5),
mentalHealthPoint: mainHALlocal.roundMajorFloat(mental_point),
mentalHealthRawScore: mainHALlocal.roundMajorFloat(mental_point * 5),
ecologyPoint: mainHALlocal.roundMajorFloat(ecology_point),
ecologyRawScore: mainHALlocal.roundMajorFloat(ecology_point * 5),
totalPoint: mainHALlocal.roundMajorFloat(total_point),
totalRank: total_rank
};
return data;
},
/**
* @func roundMajorFloat
* @desc roundMajorFloat
* @async
* @param {void}
* @return void
* @throw error
*/
roundMajorFloat: function(n, digit) {
return parseFloat(n.toFixed(2));
},
/**
* @func getLastData
* @desc HALの最新データを取得
* @async
* @param {void}
* @return void
* @throw error
*/
getLastData: async function() {
let halData = {};
let MajorResults = {};
let MinorResults = {};
let MinorkeyMeans = [];
// ローカルの MajorResults から最新のレコードを取得
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainHALlocal.getLastData() Getting the latest record in the local MajorResults table.'):0;
MajorResults = await IOT_MajorResultsModel.findOne({
order: [ ['date', 'DESC'] ]
});
if (MajorResults) {
halData.majorResults = MajorResults.dataValues;
// console.log(JSON.stringify(MajorResults.dataValues, null, ' '));
} else {
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainHALlocal.getLastData() local MajorResults table is empty.'):0;
}
// ローカルの MinorResults から最新のレコードを取得
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainHALlocal.getLastData() Getting the latest record in the local MinorResults table.'):0;
MinorResults = await IOT_MinorResultsModel.findOne({
order: [ ['date', 'DESC'] ]
});
if (MinorResults) {
halData.minorResults = MinorResults.dataValues;
// console.log(JSON.stringify(MinorResults.dataValues, null, ' '));
} else {
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainHALlocal.getLastData() local MinorResults table is empty.'):0;
}
// ローカルの MinorkeyMeans から最新のレコードを取得
await IOT_MinorkeyMeansModel.findAll({
where: { version: '1' }
}).then(function (data) {
// console.dir( data.length );
if (data && data.length != 0) { // データはあって,空ではないなら
// データあり
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainHALlocal.getLastData() MinorkeyMeans data is found.'):0;
data.forEach((d) => {
// console.log(d);
MinorkeyMeans.push(d.dataValues);
});
} else {
// データなし
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainHALlocal.getLastData() MinorkeyMeans data is NOT found.'):0;
IOT_MinorkeyMeansModel.bulkCreate( MinorkeyMeansValues );
}
}).catch(function (err) {
// DBエラーした
console.error(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainHALlocal.getLastData() IOT_MinorkeyMeans occurs error.', err);
});
halData.minorkeyMeans = MinorkeyMeans;
return { MajorResults, MinorResults, MinorkeyMeans };
},
/**
* @func truncatelogs
* @desc SQLite のデータベースのレコードの削除処理
* データがたまりすぎるので古いものを定期的に消す
* @async
* @param {void}
* @return void
* @throw error
*/
truncatelogs: async function() {
// eldata テーブルのレコード削除
let eldata_del_count = await eldataModel.destroy({
where: {
createdAt: {
[Op.lt]: new Date(Date.now() - 86400000 * config.ellogExpireDays)
}
}
});
// IOT_MajorResults テーブルのレコード削除
let major_del_count = await IOT_MajorResultsModel.destroy({
where: {
createdAt: {
[Op.lt]: new Date(Date.now() - 86400000 * config.resultExpireDays)
}
}
});
// IOT_MinorResults テーブルのレコード削除
let minor_del_count = await IOT_MinorResultsModel.destroy({
where: {
createdAt: {
[Op.lt]: new Date(Date.now() - 86400000 * config.resultExpireDays)
}
}
});
// IOT_QuestionnaireAnswersModel テーブルのレコード削除
let questionnaire_del_count = await IOT_QuestionnaireAnswersModel.destroy({
where: {
createdAt: {
[Op.lt]: new Date(Date.now() - 86400000 * config.resultExpireDays)
}
}
});
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainHALlocal.truncatelogs()'):0;
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '|- Deleted ' + eldata_del_count + ' records from the `eldata` table.'):0;
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '|- Deleted ' + major_del_count + ' records from the `IOT_MajorResults` table.'):0;
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '|- Deleted ' + minor_del_count + ' records from the `IOT_MinorResults` table.'):0;
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '|- Deleted ' + questionnaire_del_count + ' records from the `IOT_QuestionnaireAnswersModel` table.'):0;
}
};
module.exports = mainHALlocal;
//////////////////////////////////////////////////////////////////////
// EOF
//////////////////////////////////////////////////////////////////////