Browse Source

指标库,导入指标源,匹配指标后,计算全部匹配指标

MaiXinRong 7 years ago
parent
commit
1e5f072e8b

+ 3 - 3
app/const/template_param.js

@@ -105,21 +105,21 @@ const defaultNodeParams = [
     }, {
         template_id: 1,
         param_id: 2,
-        code: 'a',
+        code: 'b',
         name: '本项数量1',
         match_type: matchType.node_default,
         match_num: matchNum.dgn_quantity1,
     }, {
         template_id: 1,
         param_id: 3,
-        code: 'a',
+        code: 'c',
         name: '本项数量2',
         match_type: matchType.node_default,
         match_num: matchNum.dgn_quantity2,
     }, {
         template_id: 1,
         param_id: 4,
-        code: 'a',
+        code: 'd',
         name: '本项清单数量',
         match_type: matchType.node_default,
         match_num: matchNum.quantity,

+ 145 - 0
app/extend/helper.js

@@ -10,6 +10,7 @@
 
 const fs = require('fs');
 const streamToArray = require('stream-to-array');
+const mathjs = require('mathjs');
 
 module.exports = {
     /**
@@ -208,4 +209,148 @@ module.exports = {
         }
         return json;
     },
+
+    isOperator(value) {
+        const operatorString = "+-*/()";
+        return operatorString.indexOf(value) > -1
+    },
+    parse2Rpn(exp) {
+        const getPriority = function (value){
+            switch(value){
+                case '+':
+                case '-':
+                    return 1;
+                case '*':
+                case '/':
+                    return 2;
+                default:
+                    return 0;
+            }
+        };
+        const priority = function (o1, o2){
+            return getPriority(o1) <= getPriority(o2);
+        };
+
+        const inputStack = [];
+        const outputStack = [];
+        const outputQueue = [];
+
+        for (let i = 0, len = exp.length; i < len; i++) {
+            const cur = exp[i];
+            if (cur !== ' ' ) {
+                inputStack.push(cur);
+            }
+        }
+        let num = '', isNumPre = false, isOperatorPre = false;
+        while(inputStack.length > 0){
+            const cur = inputStack.shift();
+            if (this.isOperator(cur) && !(cur === '-' && !isNumPre)) {
+                if (isNumPre) {
+                    outputQueue.push(parseFloat(num));
+                    num = '';
+                }
+                if (cur === '(') {
+                    outputStack.push(cur);
+                } else if (cur === ')') {
+                    let po = outputStack.pop();
+                    while (po !== '(' && outputStack.length > 0) {
+                        outputQueue.push(po);
+                        po = outputStack.pop();
+                    }
+                    if (po !== '(') {
+                        throw "error: unmatched ()";
+                    }
+                } else {
+                    while(priority(cur, outputStack[outputStack.length - 1]) && outputStack.length > 0){
+                        outputQueue.push(outputStack.pop());
+                    }
+                    outputStack.push(cur);
+                }
+                isNumPre = false;
+                isOperatorPre = true;
+            } else {
+                num = num + cur;
+                isNumPre = true;
+                isOperatorPre = false;
+                if (inputStack.length === 0) {
+                    outputQueue.push(parseFloat(num));
+                }
+            }
+        }
+        if (outputStack.length > 0) {
+            if (outputStack[outputStack.length - 1] === ')' || outputStack[outputStack.length - 1] === '(') {
+                throw "error: unmatched ()";
+            }
+            while (outputStack.length > 0) {
+                outputQueue.push(outputStack.pop());
+            }
+        }
+        return outputQueue;
+    },
+    evalRpn(rpnQueue) {
+        const getResult = function (num1, num2, opera) {
+            switch (opera) {
+                case '+':
+                    return num1 + num2;
+                case '-':
+                    return num1 - num2;
+                case '*':
+                    return num1 * num2;
+                case '/':
+                    return num1 / num2;
+                default:
+                    throw '参数错误';
+            }
+        };
+        const outputStack = [];
+        while(rpnQueue.length > 0){
+            const cur = rpnQueue.shift();
+
+            if (!this.isOperator(cur)) {
+                outputStack.push(cur);
+            } else {
+                if (outputStack.length < 2) {
+                    throw "unvalid stack length";
+                }
+                const sec = outputStack.pop();
+                const fir = outputStack.pop();
+
+                outputStack.push(getResult(fir, sec, cur));
+            }
+        }
+
+        if (outputStack.length !== 1) {
+            throw "unvalid expression";
+        } else {
+            return outputStack[0];
+        }
+    },
+
+    /**
+     * 解析四则运算字符串并计算
+     * @param {String} expr
+     * @returns
+     */
+    calcExprStrRpn(expr) {
+        try {
+            if (!isNaN(Number(expr))) {
+                return Number(expr);
+            } else {
+                const rpnArr = this.parse2Rpn(expr);
+                const result = this.evalRpn(rpnArr);
+                return result === Infinity ? NaN : result;
+            }
+        } catch (err) {
+            return NaN;
+        }
+    },
+
+    calcExprStr(expr) {
+        try {
+            const result = mathjs.eval(expr);
+            return result === Infinity ? NaN : result;
+        } catch (err) {
+            return NaN;
+        }
+    }
 };

+ 89 - 0
app/service/index_calc.js

@@ -0,0 +1,89 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/4/27
+ * @version
+ */
+
+const Service = require('egg').Service;
+module.exports = app => {
+    class IndexCalc extends Service {
+        _initCalc (indexes, globalParams, nodeParams) {
+            this.indexes = indexes;
+            this.globalParams = globalParams;
+            this.globalParamsIndex = {};
+            for (const gp of this.globalParams) {
+                this.globalParamsIndex[gp.code] = gp.calc_value;
+            }
+            this.nodeParams = nodeParams;
+            this.nodeParamsIndex = {};
+            for (const np of this.nodeParams) {
+                this.nodeParamsIndex[np.code] = np.calc_value;
+            }
+            this.updateArr = [];
+        }
+
+        _splitByOperator(expr) {
+            const exprStack = [];
+            const result = [];
+            for(let i = 0, len = expr.length; i < len; i++){
+                const cur = expr[i];
+                if(cur !== ' ' ){
+                    exprStack.push(cur);
+                }
+            }
+            let param = '', isParamBefore = false;
+            while(exprStack.length > 0){
+                const cur = exprStack.shift();
+                if(this.ctx.helper.isOperator(cur)){
+                    if (isParamBefore) {
+                        result.push(param);
+                        param = '';
+                    }
+                    result.push(cur);
+                    isParamBefore = false;
+                }else{
+                    param = param + cur;
+                    isParamBefore = true;
+                    if (exprStack.length === 0) {
+                        result.push(param);
+                        param = '';
+                    }
+                }
+            }
+            return result;
+        }
+
+        _getEvalRule(rule) {
+            const parts = this._splitByOperator(rule);
+            const evalParts = [];
+            for (const p of parts) {
+                if (this.ctx.helper.isOperator(p)) {
+                    evalParts.push(p);
+                } else if (this.globalParamsIndex[p]) {
+                    evalParts.push(this.globalParamsIndex[p]);
+                } else if (this.nodeParamsIndex[p]) {
+                    evalParts.push(this.globalParamsIndex[p]);
+                }
+            }
+            return evalParts.join('');
+        }
+
+        calculate(indexes, globalParams, nodeParams) {
+            this._initCalc(indexes, globalParams, nodeParams);
+            for (const index of indexes) {
+                index.eval_rule = this._getEvalRule(index.calc_rule);
+                const value = this.ctx.helper.calcExprStr(index.eval_rule);
+                if (value !== index.value) {
+                    index.value = value ? Number(value.toFixed(4)) : null;
+                    this.updateArr.push(index);
+                }
+            }
+        }
+    }
+
+    return IndexCalc;
+};

+ 13 - 0
app/service/match.js

@@ -245,6 +245,19 @@ module.exports = app => {
             for (const node of this.templateNodes) {
                 this._matchNode(node);
             }
+            // 计算全部指标节点
+            const globalParams = this.params.filter(function (p) {
+                return p.node_id === 0;
+            });
+            for (const node of this.nodes) {
+                const nodeParams = this.params.filter(function (p) {
+                    return p.node_id === node.node_id;
+                });
+                const nodeIndexes = this.indexes.filter(function (i) {
+                    return i.node_id === node.node_id;
+                });
+                this.ctx.service.indexCalc.calculate(nodeIndexes, globalParams, nodeParams);
+            }
         }
     };
 

+ 10 - 0
app/service/tender_param.js

@@ -21,6 +21,11 @@ module.exports = app => {
             this.tableName = 'tender_param';
         }
 
+        /**
+         * 更新参数取值
+         * @param data
+         * @returns {Promise<boolean>}
+         */
         async updateCalcValue(data) {
             const transaction = await this.db.beginTransaction();
             try {
@@ -45,6 +50,11 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 重置参数取值
+         * @param data
+         * @returns {Promise<boolean>}
+         */
         async resetCalcValue(data) {
             try {
                 const condition = {

+ 1 - 0
package.json

@@ -11,6 +11,7 @@
     "egg-scripts": "^2.5.0",
     "egg-view": "^2.1.0",
     "egg-view-ejs": "^2.0.0",
+    "mathjs": "^4.1.2",
     "node-xlsx": "^0.12.0",
     "stream-to-array": "^2.3.0",
     "stream-wormhole": "^1.0.3"

+ 49 - 0
test/app/extend/helper.test.js

@@ -9,6 +9,7 @@
  */
 
 const { app, assert } = require('egg-mock/bootstrap');
+const mathjs = require('mathjs');
 
 describe('test/app/extend/helper.test.js', () => {
     it('ValidTemplateCode test', function () {
@@ -48,4 +49,52 @@ describe('test/app/extend/helper.test.js', () => {
         const result = ctx.helper.operationJson(testjson,'ok','world');
         assert(result.ok === 'world');
     });
+    it('calcExprStr test', function () {
+        const ctx = app.mockContext();
+        assert(ctx.helper.calcExprStr('1+2').toFixed(2) == 3);
+        assert(ctx.helper.calcExprStr('1*2').toFixed(2) == 2);
+        assert(ctx.helper.calcExprStr('-1/2').toFixed(2) == -0.5);
+        assert(ctx.helper.calcExprStr('1-2').toFixed(2) == -1);
+
+        assert(ctx.helper.calcExprStr('1-2+3').toFixed(2) == 2);
+        assert(ctx.helper.calcExprStr('1-2*2').toFixed(2) == -3);
+        assert(ctx.helper.calcExprStr('1-2/2').toFixed(2) == 0);
+
+        assert(ctx.helper.calcExprStr('(2-1)/2').toFixed(2) == 0.5);
+        assert(ctx.helper.calcExprStr('(2+1)*2').toFixed(2) == 6);
+
+        assert(ctx.helper.calcExprStr('(2+1)*(3-2)').toFixed(2) == 3);
+        assert(ctx.helper.calcExprStr('(327+3)*(892-882)').toFixed(2) == 3300);
+
+        assert(!ctx.helper.calcExprStr('1-'));
+        assert(!ctx.helper.calcExprStr('1+'));
+        assert(!ctx.helper.calcExprStr('1/'));
+        assert(!ctx.helper.calcExprStr('1*'));
+        assert(!ctx.helper.calcExprStr('2/0'));
+        assert(ctx.helper.calcExprStr('-3').toFixed(2) == -3);
+    });
+    it('calcExprStrRpn test', function () {
+        const ctx = app.mockContext();
+        assert(ctx.helper.calcExprStrRpn('1+2').toFixed(2) == 3);
+        assert(ctx.helper.calcExprStrRpn('1*2').toFixed(2) == 2);
+        assert(ctx.helper.calcExprStrRpn('-1/2').toFixed(2) == -0.5);
+        assert(ctx.helper.calcExprStrRpn('1-2').toFixed(2) == -1);
+
+        assert(ctx.helper.calcExprStrRpn('1-2+3').toFixed(2) == 2);
+        assert(ctx.helper.calcExprStrRpn('1-2*2').toFixed(2) == -3);
+        assert(ctx.helper.calcExprStrRpn('1-2/2').toFixed(2) == 0);
+
+        assert(ctx.helper.calcExprStrRpn('(2-1)/2').toFixed(2) == 0.5);
+        assert(ctx.helper.calcExprStrRpn('(2+1)*2').toFixed(2) == 6);
+
+        assert(ctx.helper.calcExprStrRpn('(2+1)*(3-2)').toFixed(2) == 3);
+        assert(ctx.helper.calcExprStrRpn('(327+3)*(892-882)').toFixed(2) == 3300);
+
+        assert(!ctx.helper.calcExprStrRpn('1-'));
+        assert(!ctx.helper.calcExprStrRpn('1+'));
+        assert(!ctx.helper.calcExprStrRpn('1/'));
+        assert(!ctx.helper.calcExprStrRpn('1*'));
+        assert(!ctx.helper.calcExprStrRpn('2/0'));
+        assert(ctx.helper.calcExprStrRpn('-3').toFixed(2) == -3);
+    });
 });

+ 32 - 0
test/app/service/index_calc.test.js

@@ -0,0 +1,32 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/4/27
+ * @version
+ */
+
+const { app, assert } = require('egg-mock/bootstrap');
+
+describe('test/app/service/index_calc.test.js', () => {
+    it('calculate test', function* () {
+        const ctx = app.mockContext();
+        const condition = {
+            template_id: 1,
+            node_id: 1,
+        }
+        const indexes = yield ctx.service.templateIndex.getAllDataByCondition({ where:condition });
+        const globalParams = yield ctx.service.templateParam.getAllDataByCondition({
+            where: {template_id: 1, node_id: 0,}
+        });
+        const nodeParams = yield ctx.service.templateParam.getAllDataByCondition({ where: condition });
+        globalParams[0].calc_value = 100;
+        globalParams[1].calc_value = 50;
+        globalParams[2].calc_value = 20;
+        ctx.service.indexCalc.calculate(indexes, globalParams, nodeParams);
+        assert(ctx.service.indexCalc.updateArr[2].value === 0.5);
+    });
+
+});