Forráskód Böngészése

Merge branch 'raccex' into raccount

stanley-king 2 éve
szülő
commit
ed5074eeac

+ 1 - 1
docker/compose/homecuda/acc/docker-compose.yml

@@ -45,7 +45,7 @@ services:
     volumes:
       - ../../../../:/var/www/html
       - ../conf/etc/localtime:/etc/localtime:ro
-      - ../conf/php/php-debug.ini:/usr/local/etc/php/php.ini
+      - ../conf/php/php.ini:/usr/local/etc/php/php.ini
       - /mnt/upload:/var/www/html/data/upload
       - /mnt/shoplog:/var/www/html/data/log
       - ../conf/php/vapi-spwan-start:/usr/local/bin/docker-spwan-start

+ 12 - 1
docker/compose/homecuda/statcalc/docker-compose.yml

@@ -10,4 +10,15 @@ services:
       - /mnt/shoplog:/var/www/html/data/log
       - /mnt/stdata:/var/www/html/data/stdata
     container_name: "panda-mamount"
-    command: [ 'python','mamount.py', '-h', '192.168.3.104', '-p', '6379' ]
+    command: ['python','mamount.py', '-h', '192.168.3.104', '-p', '6379']
+
+  mpratios:
+    image: pycpu:3.7.10
+    volumes:
+      - ../../../../:/var/www/html
+      - ../conf/etc/localtime:/etc/localtime:ro
+      - /mnt/upload:/var/www/html/data/upload
+      - /mnt/shoplog:/var/www/html/data/log
+      - /mnt/stdata:/var/www/html/data/stdata
+    container_name: "panda-mpratios"
+    command: ['python','mprofit_ratio.py', '-h', '192.168.3.104', '-p', '6379']

+ 1 - 1
docker/compose/homecuda/worker/docker-compose.yml

@@ -6,7 +6,7 @@ services:
     volumes:
       - ../../../../:/var/www/html
       - ../conf/etc/localtime:/etc/localtime:ro
-      - ../conf/php/php-swoole.ini:/usr/local/etc/php/php.ini
+      - ../conf/php/php-swoole-debug.ini:/usr/local/etc/php/php.ini
       - /mnt/upload:/var/www/html/data/upload
       - /mnt/shoplog:/var/www/html/data/log
     container_name: "panda-codispatcher"

+ 11 - 0
docker/compose/workcuda/statcalc/docker-compose.yml

@@ -11,3 +11,14 @@ services:
       - /mnt/stdata:/var/www/html/data/stdata
     container_name: "panda-mamount"
     command: [ 'python','mamount.py', '-h', '192.168.3.46', '-p', '6379' ]
+
+  mpratios:
+    image: pycpu:3.7.10
+    volumes:
+      - ../../../../:/var/www/html
+      - ../conf/etc/localtime:/etc/localtime:ro
+      - /mnt/upload:/var/www/html/data/upload
+      - /mnt/shoplog:/var/www/html/data/log
+      - /mnt/stdata:/var/www/html/data/stdata
+    container_name: "panda-mpratios"
+    command: [ 'python','mprofit_ratio.py', '-h', '192.168.3.46', '-p', '6379' ]

+ 11 - 1
docker/compose/xyz/statcalc/docker-compose.yml

@@ -9,4 +9,14 @@ services:
       - /mnt/shoplog:/var/www/html/data/log
       - /mnt/stdata:/var/www/html/data/stdata
     container_name: "panda-mamount"
-    command: [ 'python','mamount.py', '-h', '172.26.105.125', '-p', '6379' ]
+    command: ['python','mamount.py', '-h', '172.26.105.125', '-p', '6379']
+
+  mpratios:
+    image: pycpu:3.7.10
+    volumes:
+      - ../../../../:/var/www/html
+      - ../conf/etc/localtime:/etc/localtime:ro
+      - /mnt/shoplog:/var/www/html/data/log
+      - /mnt/stdata:/var/www/html/data/stdata
+    container_name: "panda-mpratios"
+    command: ['python','mprofit_ratio.py', '-h', '172.26.105.125', '-p', '6379']

+ 1 - 0
helper/login/ilogin.php

@@ -107,6 +107,7 @@ abstract class ILogin
 
         return true;
     }
+
     public function binded_wechat()
     {
         if($this->mMemberId <= 0) return false;

+ 3 - 0
helper/refill/XYZRefillFactory.php

@@ -26,7 +26,10 @@ require_once(BASE_HELPER_PATH . '/refill/policy/PolicyUtil.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/xyz/quality_ploy.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/mgroup.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/channel_filter.php');
+
 require_once(BASE_HELPER_PATH . '/refill/policy/mratio_control.php');
+require_once(BASE_HELPER_PATH . '/refill/policy/mratio_controlex.php');
+
 require_once(BASE_HELPER_PATH . '/refill/policy/overload_assigner.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/interceptor.php');
 require_once(BASE_HELPER_PATH . '/refill/policy/transfer.php');

+ 4 - 4
helper/refill/api/test/qianqian/RefillPhone.php

@@ -26,11 +26,11 @@ class RefillPhone extends refill\IRefillPhone
 
     public function add($card_no, $card_type, $amount, $params,&$net_errno = 0)
     {
-        $net_errno = "HTTP-504";
-        return [false, '网络错误', true];
+//        $net_errno = "HTTP-504";
+//        return [false, '网络错误', true];
 
-//        refill\util::send_normal($params['order_sn']);
-//        return [true , '',false];
+        refill\util::send_normal($params['order_sn']);
+        return [true , '',false];
     }
 
     public function query($refill_info)

+ 3 - 0
helper/refill/order.php

@@ -199,6 +199,7 @@ class order
     public function is_phone() {
         return in_array($this->mCardType,[mtopcard\ChinaMobileCard,mtopcard\ChinaUnicomCard,mtopcard\ChinaTelecomCard]);
     }
+
     private function setParams($params)
     {
         $this->mMchid = intval($params['mchid']);
@@ -215,8 +216,10 @@ class order
         $this->mCommitTimes = $params['commit_times'] ?? 0;
 
         $this->mLastOrderID = $params['order_id'] ?? 0;
+
         $this->mQuantity   = $params['quantity'] ?? 1;
         $this->mOriginQuality = intval($params['org_quality']) ?? 0;
+
         $this->mMatchRatio = $params['match_ratio'] ?? false;
         $this->mMchCardTypes = $params['mch_card_types'] ?? [];
 

+ 417 - 0
helper/refill/policy/mratio_controlex.php

@@ -0,0 +1,417 @@
+<?php
+
+namespace refill;
+
+use Log;
+
+class mratio_controlex
+{
+    private $mTimesConfig; //对应refill.ini 配置文件数据
+    private $mInterceptConfig;
+
+    private $mGrossRatios;
+    private $mDetailRatios;
+
+    private $mMchQTS;
+    private $mMixedPrices;
+
+
+    public function __construct()
+    {
+        $this->mTimesConfig = [];
+        $this->mInterceptConfig = [];
+
+        $this->mGrossRatios = [];
+        $this->mDetailRatios = [];
+
+        $this->mMixedPrices = [];
+    }
+
+    public function load()
+    {
+        $this->load_retry();
+        $this->load_intercept();
+    }
+
+    private function load_retry()
+    {
+        $isDay = functional::isDay();
+        $mch_configs = function ($isDay) {
+            $result = [];
+
+            $i = 0;
+            while (true) {
+                $start = $i * 100;
+                $items = Model()->table('merchant')->where(['mchid' => ['gt', 0], 'merchant_state' => 1])->field('mchid,retry_times_cfg')->order('mchid asc')->limit("{$start},100")->select();
+                if (empty($items)) {
+                    break;
+                }
+                $i++;
+
+                foreach ($items as $item) {
+                    $mchid = intval($item['mchid']);
+                    if ($mchid <= 0) continue;
+
+                    $retry_times_cfg = unserialize($item['retry_times_cfg']);
+                    if (empty($retry_times_cfg)) continue;
+
+                    $qualities = &$retry_times_cfg['qualities'];
+                    foreach ($qualities as $quality => $cfg) {
+                        if ($isDay) {
+                            $qualities[$quality]['secs'] = $cfg['day_secs'];
+                        } else {
+                            $qualities[$quality]['secs'] = $cfg['night_secs'];
+                        }
+                    }
+
+                    $result[$mchid] = $retry_times_cfg;
+                }
+            }
+
+            return $result;
+        };
+
+        $this->mTimesConfig = $mch_configs($isDay);
+    }
+
+    private function load_intercept()
+    {
+        $mch_configs = function () {
+            $result = [];
+
+            $i = 0;
+            while (true) {
+                $start = $i * 100;
+                $items = Model()->table('merchant')->where(['mchid' => ['gt', 0], 'merchant_state' => 1])->field('mchid,intercept_cfg')->order('mchid asc')->limit("{$start},100")->select();
+                if (empty($items)) {
+                    break;
+                }
+                $i++;
+
+                foreach ($items as $item) {
+                    $mchid = intval($item['mchid']);
+                    if ($mchid <= 0) continue;
+
+                    $cfg = unserialize($item['intercept_cfg']);
+                    if (empty($cfg)) continue;
+
+                    if (!empty($cfg['segment'])) {
+                        $segment = $cfg['segment'];
+                        $sitems = explode(',', $segment);
+
+                        $tmp = [];
+                        foreach ($sitems as $sitem) {
+                            $sitem = trim($sitem);
+                            if (!empty($sitem)) {
+                                $tmp[] = $sitem;
+                            }
+                        }
+
+                        if (!empty($tmp)) {
+                            $cfg['segment'] = implode('|', $tmp);
+                        } else {
+                            $cfg['segment'] = '';
+                        }
+                    }
+
+                    $result[$mchid] = $cfg;
+                }
+            }
+
+            return $result;
+        };
+
+        $this->mInterceptConfig = $mch_configs();
+    }
+
+    public function update($gross_ratios, $detail_ratios)
+    {
+        if (!empty($gross_ratios)) {
+            $this->mGrossRatios = $gross_ratios;
+        }
+        if (!empty($detail_ratios)) {
+            $this->mDetailRatios = $detail_ratios;
+        }
+
+        $this->mMchQTS = [];
+        foreach ($detail_ratios as $key => $val) {
+            [$mchid, $card_type, $spec] = explode('-', $key);
+
+            $mchid = intval($mchid);
+            $card_type = intval($card_type);
+            $spec = intval($spec);
+
+            $this->mMchQTS[$mchid][] = [$card_type, $spec];
+        }
+    }
+
+    public function setMixedPrice($mchid, $mixed_quality, $card_type, $spec, $prices)
+    {
+        $key = "{$card_type}-{$spec}";
+        $this->mMixedPrices[$mchid][$mixed_quality][$key] = $prices;
+    }
+
+    public function total($mchid, $qualities)
+    {
+        if (array_key_exists($mchid, $this->mTimesConfig)) {
+            $items = $this->mTimesConfig[$mchid]['qualities'];
+
+            $times = 0;
+            $secs = 0;
+            foreach ($items as $quality => $val) {
+                if (!in_array($quality, $qualities, true)) {
+                    continue;
+                }
+
+                $times += $val['times'] ?? 1;
+                $secs += $val['secs'] ?? 180;
+            }
+
+            return [true, $times, $secs];
+        } else {
+            return [false, 0, 0];
+        }
+    }
+
+    public function times($mchid, $quality)
+    {
+        if (array_key_exists($mchid, $this->mTimesConfig)) {
+            $items = $this->mTimesConfig[$mchid]['qualities'] ?? [];
+            if (array_key_exists($quality, $items)) {
+                return $items[$quality]['times'];
+            }
+        }
+
+        return false;
+    }
+
+    public function seconds($mchid, $quality)
+    {
+        if (array_key_exists($mchid, $this->mTimesConfig)) {
+            $items = $this->mTimesConfig[$mchid]['qualities'] ?? [];
+            if (array_key_exists($quality, $items)) {
+                return $items[$quality]['secs'];
+            }
+        }
+
+        return false;
+    }
+
+    public function exist($mchid)
+    {
+        if (array_key_exists($mchid, $this->mTimesConfig)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    //{\"10202\":{\"ratio\":0.65,\"period\":86400,\"profit_ratio\":0.002,\"profit_formula\":\"all\"}
+    //to-do for test
+//    private function lowest_ratio($mchid)
+//    {
+//        $lower_ratio = $this->mTimesConfig[$mchid]['lower_ratio'] ?? [];
+//        if (empty($lower_ratio)) {
+//            return [0.30, 86400];
+//        } else {
+//            return [$lower_ratio['ratio'], $lower_ratio['period']];
+//        }
+//    }
+//
+//    private function profit_ratio($mchid) {
+//        $profit_ratio = $this->mTimesConfig[$mchid]['profit_ratio'] ?? 0.002;
+//        return $profit_ratio;
+//    }
+
+
+    private function lowest_ratio($mchid)
+    {
+        $lower_ratio = $this->mTimesConfig[$mchid]['lower_ratio'] ?? [];
+        if (empty($lower_ratio)) {
+            return [0.0, 3600];
+        } else {
+            return [$lower_ratio['ratio'], $lower_ratio['period']];
+        }
+    }
+
+    private function profit_ratio($mchid)
+    {
+        $profit_ratio = $this->mTimesConfig[$mchid]['profit_ratio'] ?? 0.0;
+        return $profit_ratio;
+    }
+
+    //[submit_count, $succ_count, $fail_count, $succ_ratio, $profit, $profit_ratio]
+    private function gross_ratio($mchid)
+    {
+        $mratios = $this->mGrossRatios;
+        if (array_key_exists($mchid, $mratios)) {
+            return $mratios[$mchid];
+        }
+
+        return [0, 0, 0, 0, 0, 0];
+    }
+
+    //[submit_count, $succ_count, $fail_count, $succ_ratio, $profit, $profit_ratio]
+    private function detail_ratio($mchid, $card_type, $spec)
+    {
+        $key = "{$mchid}-{$card_type}-{$spec}";
+        if (array_key_exists($key, $this->mDetailRatios)) {
+            return $this->mDetailRatios[$key];
+        }
+
+        return [0, 0, 0, 0, 0, 0];
+    }
+
+    //return true 表示当前质量满足条件。
+    public function ratio_match($mchid, $org_quality, $card_type, $spec, $qualities)
+    {
+        if (count($qualities) <= 1) {
+            return true;
+        }
+
+        $header = __METHOD__ . " mchid={$mchid} card_type={$card_type} spec={$spec}";
+        [$lowest_ratio, $period] = $this->lowest_ratio($mchid);
+
+        [$submit_count, $succ_count, $fail_count, $gross_ratio, $profit, $profit_ratio] = $this->gross_ratio($mchid);
+        Log::record("{$header} gross_ratio={$gross_ratio}, lower_ratio={$lowest_ratio}", Log::DEBUG);
+
+        if (!PolicyUtil::mixed_quality($org_quality)) {
+            if ($gross_ratio >= $lowest_ratio) {
+                return true;
+            } else {
+                return false;
+            }
+        } elseif ($gross_ratio >= $lowest_ratio) {
+            return $this->pre_checker($mchid, $card_type, $spec, $header, $lowest_ratio);
+        } else {
+            return $this->all_checker($mchid, $card_type, $spec, $header, $lowest_ratio);
+        }
+    }
+
+    private function pre_checker($mchid, $card_type, $spec, $header, $lowest_ratio)
+    {
+        $ts_ratios = function ($mchid, $type_specs, $all_submit)
+        {
+            $result = [];
+
+            if ($all_submit == 0) {
+                $all_submit += 0.00001;
+            }
+
+            foreach ($type_specs as $item) {
+                [$card_type, $spec] = $item;
+                [$submit_count, $succ_count, $fail_count, $cur_ratio, $profit, $cur_pratio] = $this->detail_ratio($mchid, $card_type, $spec);
+                $submit_ratio = $submit_count / $all_submit;
+
+                $result[] = [$card_type, $spec, $submit_ratio, $cur_ratio, $cur_pratio];
+            }
+
+            return $result;
+        };
+
+        $spec_extract = function ($item) {
+            [$card_type, $spec, $submit_ratio, $cur_ratio, $cur_pratio] = $item;
+            return $spec;
+        };
+
+        $ts_sorter_spec = function ($left, $right) use ($spec_extract)
+        {
+            $l = $spec_extract($left);
+            $r = $spec_extract($right);
+
+            if ($l > $r)
+                return 1;
+            elseif ($l < $r)
+                return -1;
+            else
+                return 0;
+        };
+
+        $ts_filter = function ($ts_ratios, $ratio, $lowest_ratio)
+        {
+            $result = [];
+
+            $all = 0.00;
+            foreach ($ts_ratios as $item)
+            {
+                [$card_type, $spec, $submit_ratio, $cur_ratio, $cur_pratio] = $item;
+                $all += $submit_ratio * $cur_ratio;
+
+                $r = $all / ($lowest_ratio + 0.00001);
+                if ($r > $ratio)
+                    break;
+                else {
+                    $result[] = "{$card_type}-{$spec}";
+                }
+            }
+            return $result;
+        };
+
+        $lowest_pratio = $this->profit_ratio($mchid);
+        [$all_submit, $succ_count, $fail_count, $gross_ratio, $profit, $cur_pratio] = $this->gross_ratio($mchid);
+
+        if ($cur_pratio <= $lowest_pratio) {
+            return true;
+        }
+
+        $type_specs = $this->mMchQTS[$mchid];
+        $ts_ratios = $ts_ratios($mchid, $type_specs, $all_submit);
+        usort($ts_ratios, $ts_sorter_spec);
+
+        $ratio = $gross_ratio / ($lowest_ratio + 0.00001);
+
+        if ($ratio > 1.1) {
+            return true;
+        } else {
+            $meet = $ts_filter($ts_ratios, $ratio, $lowest_ratio);
+            $exist = array_key_exists("{$card_type}-{$spec}", $meet);
+            return !$exist;
+        }
+    }
+
+    private function all_checker($mchid, $card_type, $spec, $header, $lowest_ratio)
+    {
+        $lowest_pratio = $this->profit_ratio($mchid);
+        [$submit_count, $succ_count, $fail_count, $gross_ratio, $profit, $gross_pratio] = $this->gross_ratio($mchid);
+        //当前毛利润率小于最低利润率的时候,不可以补充了
+        if ($gross_pratio <= $lowest_pratio) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public function need_intercept($mchid, $card_type, $card_state, $is_transfer, $card_no): bool
+    {
+        $start_with = function ($card_no, $segment) {
+            $reg = "/^(?:{$segment})\d*$/";
+            if (preg_match($reg, $card_no, $matches)) {
+                return true;
+            } else {
+                return false;
+            }
+        };
+
+        $mintercepts = $this->mInterceptConfig;
+        if (array_key_exists($mchid, $mintercepts)) {
+            $mintercepts = $mintercepts[$mchid];
+            if (!empty($mintercepts['card_states']) && in_array($card_state, $mintercepts['card_states'], true)) {
+                return true;
+            }
+
+            if (!empty($mintercepts['card_types']) && in_array($card_type, $mintercepts['card_types'], true)) {
+                return true;
+            }
+
+            if ($mintercepts['is_transfer'] && $is_transfer) {
+                return true;
+            }
+
+            if (!empty($mintercepts['segment']) && $start_with($card_no, $mintercepts['segment'])) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

+ 452 - 0
helper/refill/policy/quaility-bak.php

@@ -0,0 +1,452 @@
+<?php
+
+namespace refill;
+
+use Log;
+use mtopcard;
+use scope_trace;
+
+class Quality
+{
+    const LowestQuality  = 1;
+    const Normal = 1;
+    const Quick = 2;
+    const CardKey = 3;
+    const ThirdShop = 4;
+    const SlowTwentyFour = 5;
+    const SlowSix = 6;
+    const SlowTwo = 7;
+    const SlowFortyEight = 8;
+    const SlowSeventyTwo = 9;
+    const Fastest = 10;
+    const HighestQuality = 10;
+
+    const SlowNormal  = 11;  // 7 -> 1
+    const ThirdNormal  = 12; // 4 -> 1
+    const DefSuccess = 13;   // 4 -> 3 -> 1 -> 2
+    const NormalQuick = 14;
+    const SlowSixNormal = 15;
+
+    const OilWithoutSN = 1;
+    const OilQuick = 2;
+    const OilCardKey   = 3;
+    const OilWithSN    = 5;
+
+    const OilSN_NONE_HAS   = 20;
+    const OilSN_HAS_NONE   = 21;
+    const OIL_SN_CARDKEY        = 22;
+    const OIL_SNNONE_CARDKEY    = 23;
+    const OIL_SN_SNNONE_CARDKEY = 24;
+    const OIL_SNNONE_SN_CARDKEY = 25;
+    const OIL_SNNONE_SN_CARDKEY_QUICK = 26;
+
+    protected $mMchPhonectl;
+    protected $mMchoilctl;
+    protected $mSpeeds;
+    protected $mQualities;
+    protected $mTryAdjuster;
+
+    protected $mRatioCtl;
+
+    public function __construct()
+    {
+        $this->mMchPhonectl = new mchctl();
+        $this->mMchoilctl = new mchoilctl();
+        $this->mTryAdjuster = new try_judge();
+        $this->mRatioCtl = null;
+    }
+
+    public function load()
+    {
+        $this->mMchPhonectl->load();
+        $this->mMchoilctl->load();
+        $this->mTryAdjuster->load();
+    }
+
+    public function setRatioCtl($ratio_ctl) {
+        $this->mRatioCtl = $ratio_ctl;
+    }
+
+    public function qualities($quality)
+    {
+        if (array_key_exists($quality, $this->mQualities)) {
+            return $this->mQualities[$quality];
+        } else {
+            return [];
+        }
+    }
+
+    public function mechants_quality() {
+        return $this->mMchPhonectl->mechants_quality();
+    }
+
+    public function find_quality($mchid, $card_type, $spec, $org_quality, $cur_quality, $times, $used_time, $caller): array
+    {
+        $trace = new scope_trace(__METHOD__);
+        if($card_type == mtopcard\ChinaMobileCard || $card_type == mtopcard\ChinaUnicomCard || $card_type == mtopcard\ChinaTelecomCard) {
+            return $this->mobile_quality($mchid, $card_type, $spec, $org_quality, $cur_quality, $times, $used_time);
+        }
+        elseif($card_type == mtopcard\PetroChinaCard || $card_type == mtopcard\SinopecCard) {
+            [$org,$qualities] = $this->oil_quality($mchid, $org_quality, $times,$used_time,$caller);
+            return [$org,$qualities,false];
+        }
+        else {
+            return [0, [],false];
+        }
+    }
+
+    private function oil_quality($mchid, $quality, $times,$used_time,$caller): array
+    {
+        Log::record("oil_quality mchid={$mchid},quality={$quality},times={$times}",Log::DEBUG);
+        if($quality == 0)
+        {
+            [$success,$setting_quality,$time_out] = $this->mMchoilctl->getCtls($mchid);
+            if($success)
+            {
+                if(array_key_exists($setting_quality,$this->mQualities)) {
+                    $org = $setting_quality;
+                    $qualities = $this->mQualities[$setting_quality];
+                }
+                else {
+                    $org = $setting_quality;
+                    $qualities = [$setting_quality];
+                }
+            }
+            else {
+                $org = self::Normal;
+                $qualities = $this->mQualities[$org];
+            }
+        }
+        elseif(array_key_exists($quality,$this->mQualities)) {
+            $org = $quality;
+            $qualities = $this->mQualities[$quality];
+        }
+        else {
+            Log::record("find_quality: cannot find any quality",Log::DEBUG);
+            return [0,[]];
+        }
+
+        Log::record("oil_quality find qualities = " . implode(',',$qualities),Log::DEBUG);
+        $qualities = $this->calc_oil_quality($qualities,$times,$used_time,$caller);
+
+        return [$org,$qualities];
+    }
+
+    //通过每种类型通道耗时,倒推当前可用通道,并优先走推荐通道.
+    private function calc_oil_quality($qualities, $times, $used_time, $caller)
+    {
+        $result = [];
+        if($used_time > 900) {
+            return $result;
+        }
+
+        $total_times = 0;
+        foreach ($qualities as $quality)
+        {
+            $cur_times = $caller->calc_times($quality);
+            if($cur_times <= 0) continue;
+
+            $total_times += $cur_times;
+            if($total_times > $times) {
+                $result[] = $quality;
+            }
+        }
+
+        Log::record("calc_oil_quality result = " . implode(',',$result),Log::DEBUG);
+        return $result;
+    }
+
+
+    private function mobile_quality($mchid, $card_type, $spec, $org_quality, $cur_quality, $times, $used_time): array
+    {
+        if($org_quality == 0)
+        {
+            [$success,$setting_quality,$time_out] = $this->mMchPhonectl->getCtls($mchid);
+            if($success)
+            {
+                if(array_key_exists($setting_quality,$this->mQualities)) {
+                    $org_quality = $setting_quality;
+                }
+                else {
+                    $org_quality = $setting_quality;
+                }
+            }
+            else {
+                $org_quality = self::Normal; //如果没设置质量,默认为普通
+                $time_out = $this->mSpeeds[$org_quality]['retry_timeout'];
+            }
+        }
+        elseif(array_key_exists($org_quality,$this->mQualities)) {
+            [$success, $setting_quality, $time_out] = $this->mMchPhonectl->getCtls($mchid);
+        }
+        else {
+            Log::record("find_quality: cannot find any quality",Log::DEBUG);
+            return [0,[],false];
+        }
+
+        if($time_out <= 0) {
+            $time_out = $this->mSpeeds[$org_quality]['retry_timeout'];
+        }
+
+        $qualities = $this->qualities($org_quality);
+        $max_times = $this->max_times($mchid, $org_quality, $qualities);
+        if ($this->mRatioCtl->exist($mchid)) {
+            Log::record("ratio_phone_quality exist=true",Log::DEBUG);
+            [$qualities,$match_ratio] = $this->ratio_phone_quality($org_quality, $qualities, $cur_quality, $times, $used_time, $mchid, $card_type, $spec);
+            return [$org_quality, $qualities,$match_ratio];
+        } else {
+            $fMixed = PolicyUtil::mixed_quality($org_quality);
+            $qualities = $this->calc_phone_quality($qualities, $fMixed, $cur_quality, $times, $used_time, $time_out, $max_times, $mchid);
+            return [$org_quality, $qualities,false];
+        }
+    }
+
+    private function ratio_phone_quality($org_quality,$qualities, $cur_quality, $times, $used_time, $mchid, $card_type, $spec)
+    {
+        [$succ,$max_times,$time_out] = $this->mRatioCtl->total($mchid,$qualities);
+
+        $left_time = $time_out - $used_time;
+        Log::record("left_time = {$left_time} used_time={$used_time}",Log::DEBUG);
+        if ($left_time <= 0 || $max_times <= $times) {
+            return [[],false];
+        }
+
+
+        $match_ratio = $this->mRatioCtl->ratio_match($mchid, $org_quality, $cur_quality, $card_type, $spec, $qualities);
+
+        Log::record("match_ratio = {$match_ratio}",Log::DEBUG);
+        Log::record("ratio_phone_quality begin qualities= " . implode(',',$qualities),Log::DEBUG);
+
+        $times_checker = function ($qualities, $times) use ($match_ratio, $cur_quality, $max_times, $mchid)
+        {
+            $result = [];
+            $pre_times = 0;
+
+            $compare = $cur_quality > 0 ? false : true;
+
+            foreach ($qualities as $quality)
+            {
+                $qtimes = $this->mRatioCtl->times($mchid, $quality);
+
+                if($compare == false)
+                {
+                    if($quality == $cur_quality) {
+                        $compare = true;
+                    }
+                }
+
+                if($qtimes == false) {
+                    continue;
+                }
+                else {
+                    $pre_times += $qtimes;
+                }
+
+                if($compare == false) continue;
+
+                if ($match_ratio) {
+                    $result[] = $quality;
+                }
+                elseif ($times < $pre_times) {
+                    $result[] = $quality;
+                }
+            }
+
+            return $result;
+        };
+
+        $timeout_checker = function ($qualities, $left_time) use($cur_quality,$match_ratio,$mchid)
+        {
+            if($match_ratio)
+            {
+                $result = [];
+                $compare = $cur_quality > 0 ? false : true;
+                foreach ($qualities as $quality)
+                {
+                    if($compare == false)
+                    {
+                        if($quality == $cur_quality) {
+                            $compare = true;
+                        }
+                    }
+                    if($compare == false) continue;
+
+                    $per_secs = $this->mSpeeds[$quality]['per_secs'];
+                    $left_time -= $per_secs;
+                    if($left_time > 0) {
+                        $result[] = $quality;
+                    } else {
+                        break;
+                    }
+                }
+
+                return $result;
+            }
+            else
+            {
+                $result = [];
+
+                $qualities = array_reverse($qualities);
+                foreach ($qualities as $quality)
+                {
+                    $per_secs = $this->mSpeeds[$quality]['per_secs'];
+                    $qsecs = $this->mRatioCtl->seconds($mchid, $quality);
+                    Log::record("left_time={$left_time} quality={$quality} secs={$qsecs}",Log::DEBUG);
+                    if($qsecs == false) continue;
+
+                    if($left_time - $qsecs > 0) {
+                        //时间满足该质量配置
+                        $left_time -= $qsecs;
+                        $result[] = $quality;
+                    }
+                    elseif($left_time - $per_secs > 0) {
+                        //时间够跑一次,不能再找其它质量
+                        $result[] = $quality;
+                        break;
+                    }
+                    else {
+                        //时间完全不够跑一次
+                        break;
+                    }
+
+                    if($quality == $cur_quality) {
+                        break;
+                    }
+                }
+
+                $result = array_reverse($result);
+                return $result;
+            }
+        };
+
+        $pTryAdjuster = $this->mTryAdjuster;
+        $timeing_checker = function ($qualities) use($pTryAdjuster,$mchid)
+        {
+            foreach ($qualities as $quality)
+            {
+                $ret = $pTryAdjuster->can_try($mchid,$quality);
+                if($ret == false) {
+                    return false;
+                }
+            }
+
+            return true;
+        };
+
+        if($times > 0)
+        {
+            $qualities = $times_checker($qualities,$times);
+            Log::record("calc_quality times_checker result = " . implode(',', $qualities), Log::DEBUG);
+            $qualities = $timeout_checker($qualities,$left_time);
+            Log::record("calc_quality timeout_checker result = " . implode(',', $qualities), Log::DEBUG);
+
+            if(!$timeing_checker($qualities)) {
+                $qualities = [];
+                Log::record("calc_quality timeing_checker result is empty", Log::DEBUG);
+            }
+        }
+
+        if ($match_ratio) {
+            $all = $this->qualities($org_quality);
+            $qualities = PolicyUtil::mixed_remove_last($org_quality, $qualities, $all);
+        }
+
+        return [$qualities,$match_ratio];
+
+    }
+
+    private function max_times($mchid,$quality,$qualities)
+    {
+        [$succ, $times,$secs] = $this->mRatioCtl->total($mchid,$qualities);
+        if ($succ) {
+            return $times;
+        } else {
+            return $this->mSpeeds[$quality]['retry_times'];
+        }
+    }
+
+    //通过每种类型通道耗时,倒推当前可用通道,并优先走推荐通道.
+    private function calc_phone_quality($qualities,$fMixed, $cur_quality, $times, $used_time, $time_out, $max_times, $mchid)
+    {
+        $left_time = $time_out - $used_time;
+        if(!$fMixed)
+        {
+            if($left_time <= 0 || $max_times <= $times) {
+                return [];
+            }
+        }
+
+        Log::record("calc_phone_quality begin qualities= " . implode(',',$qualities),Log::DEBUG);
+        $times_checker = function($qualities, $times)
+        {
+            $result = [];
+            $pre_times = 0;
+            foreach ($qualities as $quality)
+            {
+                $pre_times += $this->mSpeeds[$quality]['retry_times'];
+                if($times < $pre_times) {
+                    $result[] = $quality;
+                }
+            }
+
+            return $result;
+        };
+
+        $timeout_checker = function ($qualities, $left_time) use($fMixed,$cur_quality)
+        {
+            $qualities = array_reverse($qualities);
+            if ($fMixed) {
+                $fLasted = $cur_quality != $qualities[0];
+            } else {
+                $fLasted = false;
+            }
+
+            $result = [];
+            foreach ($qualities as $quality)
+            {
+                $per_secs = $this->mSpeeds[$quality]['per_secs'];
+                $left_time -= $per_secs;
+                if($left_time > 0) {
+                    $result[] = $quality;
+                } else {
+                    break;
+                }
+            }
+
+            if(empty($result) && $fLasted) {
+                $result[] = $qualities[0];
+            }
+            $result = array_reverse($result);
+            return $result;
+        };
+
+        $pTryAdjuster = $this->mTryAdjuster;
+        $timeing_checker = function ($qualities) use($pTryAdjuster,$mchid)
+        {
+            foreach ($qualities as $quality)
+            {
+                $ret = $pTryAdjuster->can_try($mchid,$quality);
+                if($ret == false) {
+                    return false;
+                }
+            }
+
+            return true;
+        };
+
+        if($times > 0)
+        {
+            $qualities = $times_checker($qualities,$times);
+            Log::record("calc_quality times_checker result = " . implode(',', $qualities), Log::DEBUG);
+            $qualities = $timeout_checker($qualities,$left_time);
+            Log::record("calc_quality timeout_checker result = " . implode(',', $qualities), Log::DEBUG);
+
+            if(!$timeing_checker($qualities)) {
+                $qualities = [];
+                Log::record("calc_quality timeing_checker result is empty", Log::DEBUG);
+            }
+        }
+
+        return $qualities;
+    }
+}

+ 55 - 44
helper/refill/policy/quaility.php

@@ -80,14 +80,13 @@ class Quality
         return $this->mMchPhonectl->mechants_quality();
     }
 
-    public function find_quality($mchid, $card_type, $spec, $org_quality, $cur_quality, $times, $used_time, $caller): array
+    public function find_quality($mchid, $card_type, $spec, $org_quality, $cur_quality, $used_times, $used_time, $caller): array
     {
-        $trace = new scope_trace(__METHOD__);
         if($card_type == mtopcard\ChinaMobileCard || $card_type == mtopcard\ChinaUnicomCard || $card_type == mtopcard\ChinaTelecomCard) {
-            return $this->mobile_quality($mchid, $card_type, $spec, $org_quality, $cur_quality, $times, $used_time);
+            return $this->mobile_quality($mchid, $card_type, $spec, $org_quality, $cur_quality, $used_times, $used_time);
         }
         elseif($card_type == mtopcard\PetroChinaCard || $card_type == mtopcard\SinopecCard) {
-            [$org,$qualities] = $this->oil_quality($mchid, $org_quality, $times,$used_time,$caller);
+            [$org,$qualities] = $this->oil_quality($mchid, $org_quality, $used_times,$used_time,$caller);
             return [$org,$qualities,false];
         }
         else {
@@ -157,7 +156,7 @@ class Quality
     }
 
 
-    private function mobile_quality($mchid, $card_type, $spec, $org_quality, $cur_quality, $times, $used_time): array
+    private function mobile_quality($mchid, $card_type, $spec, $org_quality, $cur_quality, $used_times, $used_time): array
     {
         if($org_quality == 0)
         {
@@ -189,44 +188,43 @@ class Quality
         }
 
         $qualities = $this->qualities($org_quality);
-        $max_times = $this->max_times($mchid,$org_quality,$qualities);
+        $max_times = $this->max_times($mchid, $org_quality, $qualities);
         if ($this->mRatioCtl->exist($mchid)) {
             Log::record("ratio_phone_quality exist=true",Log::DEBUG);
-            [$qualities,$match_ratio] = $this->ratio_phone_quality($org_quality, $qualities, $cur_quality, $times, $used_time, $mchid, $card_type, $spec);
+            [$qualities,$match_ratio] = $this->ratio_phone_quality($org_quality, $qualities, $cur_quality, $used_times, $used_time, $mchid, $card_type, $spec);
             return [$org_quality, $qualities,$match_ratio];
         } else {
             $fMixed = PolicyUtil::mixed_quality($org_quality);
-            $qualities = $this->calc_phone_quality($qualities, $fMixed, $cur_quality, $times, $used_time, $time_out, $max_times, $mchid);
+            $qualities = $this->calc_phone_quality($qualities, $fMixed, $cur_quality, $used_times, $used_time, $time_out, $max_times, $mchid);
             return [$org_quality, $qualities,false];
         }
     }
 
-    private function ratio_phone_quality($org_quality,$qualities, $cur_quality, $times, $used_time, $mchid, $card_type, $spec)
+    private function ratio_phone_quality($org_quality, $all_qualities, $cur_quality, $used_times, $used_time, $mchid, $card_type, $spec)
     {
-        [$succ,$max_times,$time_out] = $this->mRatioCtl->total($mchid,$qualities);
+        [$succ,$max_times,$time_out] = $this->mRatioCtl->total($mchid,$all_qualities);
 
         $left_time = $time_out - $used_time;
         Log::record("left_time = {$left_time} used_time={$used_time}",Log::DEBUG);
-        if ($left_time <= 0 || $max_times <= $times) {
+        if ($left_time <= 0 || $max_times <= $used_times) {
             return [[],false];
         }
+        
+        if (count($all_qualities) == 1) {
+            return [$all_qualities, true];
+        }
 
-        $match_ratio = $this->mRatioCtl->ratio_match($mchid, $org_quality, $cur_quality, $card_type, $spec, $qualities);
-
-        Log::record("match_ratio = {$match_ratio}",Log::DEBUG);
-        Log::record("ratio_phone_quality begin qualities= " . implode(',',$qualities),Log::DEBUG);
-
-        $times_checker = function ($qualities, $times) use ($match_ratio, $cur_quality, $max_times, $mchid)
+        $times_checker = function ($match_ratio, $qualities, $used_times) use ($cur_quality, $max_times, $mchid)
         {
             $result = [];
             $pre_times = 0;
 
+            //默认不配置的情况下,首次$cur_quality等于0
             $compare = $cur_quality > 0 ? false : true;
 
             foreach ($qualities as $quality)
             {
                 $qtimes = $this->mRatioCtl->times($mchid, $quality);
-
                 if($compare == false)
                 {
                     if($quality == $cur_quality) {
@@ -234,19 +232,16 @@ class Quality
                     }
                 }
 
-                if($qtimes == false) {
+                if ($qtimes == false) {
                     continue;
-                }
-                else {
+                } else {
                     $pre_times += $qtimes;
                 }
 
                 if($compare == false) continue;
-
                 if ($match_ratio) {
                     $result[] = $quality;
-                }
-                elseif ($times < $pre_times) {
+                } elseif ($used_times < $pre_times) {
                     $result[] = $quality;
                 }
             }
@@ -254,7 +249,7 @@ class Quality
             return $result;
         };
 
-        $timeout_checker = function ($qualities, $left_time) use($cur_quality,$match_ratio,$mchid)
+        $timeout_checker = function ($match_ratio, $qualities, $left_time) use ($cur_quality, $mchid)
         {
             if($match_ratio)
             {
@@ -286,29 +281,27 @@ class Quality
                 $result = [];
 
                 $qualities = array_reverse($qualities);
-                foreach ($qualities as $quality)
-                {
+                foreach ($qualities as $quality) {
                     $per_secs = $this->mSpeeds[$quality]['per_secs'];
                     $qsecs = $this->mRatioCtl->seconds($mchid, $quality);
-                    Log::record("left_time={$left_time} quality={$quality} secs={$qsecs}",Log::DEBUG);
-                    if($qsecs == false) continue;
+                    Log::record("left_time={$left_time} quality={$quality} secs={$qsecs}", Log::DEBUG);
+
+                    if ($qsecs == false) continue;
 
-                    if($left_time - $qsecs > 0) {
+                    if ($left_time - $qsecs > 0) {
                         //时间满足该质量配置
                         $left_time -= $qsecs;
                         $result[] = $quality;
-                    }
-                    elseif($left_time - $per_secs > 0) {
+                    } elseif ($left_time - $per_secs > 0) {
                         //时间够跑一次,不能再找其它质量
                         $result[] = $quality;
                         break;
-                    }
-                    else {
+                    } else {
                         //时间完全不够跑一次
                         break;
                     }
 
-                    if($quality == $cur_quality) {
+                    if ($quality == $cur_quality) {
                         break;
                     }
                 }
@@ -332,25 +325,43 @@ class Quality
             return true;
         };
 
-        if($times > 0)
+        if($used_times > 0)
         {
-            $qualities = $times_checker($qualities,$times);
-            Log::record("calc_quality times_checker result = " . implode(',', $qualities), Log::DEBUG);
-            $qualities = $timeout_checker($qualities,$left_time);
-            Log::record("calc_quality timeout_checker result = " . implode(',', $qualities), Log::DEBUG);
+            $times_qualities = $times_checker(true, $all_qualities, $used_times);
+            $time_qualities = $timeout_checker(true, $all_qualities, $left_time);
+            $pre_qualities = array_intersect($times_qualities, $time_qualities, $all_qualities);
 
-            if(!$timeing_checker($qualities)) {
-                $qualities = [];
+            if (count($pre_qualities) < count($all_qualities)) {
+                $match_ratio = $this->mRatioCtl->ratio_match($mchid, $org_quality, $card_type, $spec, $all_qualities);
+                Log::record("mchid={$mchid} must calc next quality match_ratio={$match_ratio}",Log::DEBUG);
+            } else {
+                $match_ratio = true;
+            }
+
+            if (!$match_ratio) {
+                $times_qualities = $times_checker(false, $all_qualities, $used_times);
+                $time_qualities  = $timeout_checker(false, $all_qualities, $left_time);
+                $pre_qualities = array_intersect($times_qualities, $time_qualities, $all_qualities);
+            }
+
+            if(!$timeing_checker($pre_qualities)) {
+                $pre_qualities = [];
                 Log::record("calc_quality timeing_checker result is empty", Log::DEBUG);
             }
+
+            $cur_qualities = $pre_qualities;
+        }
+        else {
+            $match_ratio = true;
+            $cur_qualities = $all_qualities;
         }
 
         if ($match_ratio) {
             $all = $this->qualities($org_quality);
-            $qualities = PolicyUtil::mixed_remove_last($org_quality, $qualities, $all);
+            $cur_qualities = PolicyUtil::mixed_remove_last($org_quality, $cur_qualities, $all);
         }
 
-        return [$qualities,$match_ratio];
+        return [$cur_qualities,$match_ratio];
 
     }
 

+ 3 - 5
helper/refill/policy/xyz/policy.php

@@ -30,7 +30,7 @@ class policy extends ProviderManager implements IPolicy
         $this->mStorageLocker = new rstorage();
         $this->mGroupCtl = new rgroup_ctl();
 
-        $this->mRatioCtl = new mratio_control();
+        $this->mRatioCtl = new mratio_controlex();
         $this->mQuality->setRatioCtl($this->mRatioCtl);
         $this->mGlobalInterceptor = new interceptor();
         $this->mMChannels = new mchannel();
@@ -151,8 +151,7 @@ class policy extends ProviderManager implements IPolicy
                 $ret = array_intersect($names, $channels);
                 if (empty($ret)) {
                     return false;
-                }
-                else {
+                } else {
                     $names = $ret;
                 }
             }
@@ -196,8 +195,7 @@ class policy extends ProviderManager implements IPolicy
                 $ret = array_intersect($names, $channels);
                 if (empty($ret)) {
                     return false;
-                }
-                else {
+                } else {
                     $names = $ret;
                 }
             }

+ 19 - 0
plot/mprofit_ratio.py

@@ -0,0 +1,19 @@
+from refill import opt_parse, MProfitRatioCalc
+import signal as sig
+import logging
+
+logging.basicConfig(filename='/var/www/html/data/log/statcalc.log',
+                    format='%(levelname)10s  %(asctime)s  %(name)10s %(thread)d %(message)s',
+                    level=logging.DEBUG)
+
+logger = logging.getLogger('mprofit_ratio')
+
+if __name__ == '__main__':
+    try:
+        rhost, rport = opt_parse()
+        calc = MProfitRatioCalc()
+        calc.set_redis(rhost, rport)
+        sig.signal(sig.SIGINT, lambda: calc.stop())
+        calc.run()
+    except Exception as ex:
+        logger.error(ex)

+ 69 - 0
plot/refill/CalcBase.py

@@ -0,0 +1,69 @@
+import redis
+import time as time
+import logging
+import json
+from .DataStream import span_days
+import logging
+logger = logging.getLogger('CalcBase')
+
+class CalcBase(object):
+    def __init__(self):
+        self._mQuit = False
+        self._mRHost = ''
+        self._mRPort = 6379
+
+    def set_redis(self, rhost, rport):
+        self._mRHost = rhost
+        self._mRPort = rport
+
+    def stop(self):
+        self._mquit = True
+
+    def _calc_handler(self, rclient):
+        pass
+
+    def _reader(self):
+        return None
+
+    def run(self):
+        def redis_client():
+            pool = redis.ConnectionPool(host=self._mRHost, port=self._mRPort)
+            client = redis.Redis(connection_pool=pool)
+            return client
+
+        client = None
+        loop = 0;
+        while self._mQuit == False:
+            try:
+                if client is None:
+                    client = redis_client()
+                self._calc_handler(client)
+            except redis.RedisError as ex:
+                logger.error(ex)
+            except Exception as ex:
+                logger.error(ex)
+            finally:
+                time.sleep(1)
+                loop += 1
+
+    def calc_time(self, reader, start_time: int, end_time: int):
+        end_time = reader.near_stamp(end_time, False)
+        if end_time is None:
+            raise Exception('end_time data is empty')
+
+        start_time = reader.near_stamp(start_time, True)
+        if start_time is None:
+            raise Exception('start_time data is empty')
+
+        strtime = lambda t: time.strftime('%d-%H:%M:%S', time.localtime(t))
+        logger.debug("near_stamp start_time %s end_time=%s", strtime(start_time), strtime(end_time))
+
+        if start_time >= end_time:
+            raise Exception('start_time equal endtime')
+
+        days = span_days(start_time, end_time)
+        strtime = lambda t: time.strftime('%d-%H:%M:%S', time.localtime(t))
+        sdays = [strtime(day) for day in days]
+        logger.debug(sdays)
+
+        return days, start_time, end_time

+ 65 - 0
plot/refill/MProfitRatioCalc.py

@@ -0,0 +1,65 @@
+from .MerchantCalcBase import MerchantCalcBase, mch_detail_paths, mch_paths
+from .algorithm import calc_mch_profit
+from .DataStream import EMchPosmap as pos_map
+
+import logging
+import time as time
+import logging
+import json
+
+logger = logging.getLogger('mch_profit_ratio')
+
+
+def mixed_ratio(rclient):
+    result = list()
+    data = rclient.get('nc_refill-stat-merchant-mixed')
+    if data is not None:
+        mch_cfgs = json.loads(data)
+        for mchid, val in mch_cfgs.items():
+            mchid = int(mchid)
+            period = int(val['period'])
+            formula = val['profit_formula'].strip()
+            result.append((mchid, period, formula))
+    return result
+
+
+class MProfitRatioCalc(MerchantCalcBase):
+    def _calc_handler(self, rclient):
+        logger.debug('MProfitRatioCalc _calc_handler')
+        mixed_ratios = mixed_ratio(rclient)
+        reader = self._reader()
+
+        gross = dict()
+        detail = dict()
+
+        for mchid, period, formula in mixed_ratios:
+            end_time = int(time.time())
+            days, start_time, end_time = self.calc_time(reader, end_time - period, end_time)
+            if len(days) == 0:
+                continue
+
+            day_stamp = days[0]
+            tuple_pathes = reader.many_tuple_path(days, mchids={mchid})
+            gen = mch_detail_paths(reader, tuple_pathes, days)
+
+            start = start_time - day_stamp
+            end = end_time - day_stamp
+
+            spec_amount = 0.0
+            for _mchid, _card_type, _spec, _data in gen:
+                submit_count, succ_count, fail_count, succ_ratio, profit = calc_mch_profit(_data, pos_map, start, end)
+                if _card_type is None and _spec is None:
+                    profit_ratio = profit / (spec_amount + 0.000001)
+                    gross[_mchid] = [submit_count, succ_count, fail_count, succ_ratio, profit, round(profit_ratio, 5)]
+                    spec_amount = 0.0
+                else:
+                    key = f"{_mchid}-{_card_type}-{_spec}"
+                    amount = succ_count * _spec
+                    spec_amount += amount
+                    profit_ratio = profit / (amount + 0.000001)
+                    detail[key] = [submit_count, succ_count, fail_count, succ_ratio, profit, round(profit_ratio, 5)]
+
+        result = {'gross': gross, 'detail': detail}
+        if len(gross) != 0 or len(detail) != 0:
+            rclient.set(f"nc_refill_merchant_profit_ratio", json.dumps(result))
+            rclient.publish('refill', json.dumps({'type': 'mch_profit_ratio', 'value': 0}))

+ 1 - 26
plot/refill/MerchantCalc.py

@@ -42,19 +42,6 @@ def earliest_time(rclient: redis.client):
     else:
         return result, min_time
 
-def mixed_ratio(rclient: redis.client):
-    result = list()
-    data = rclient.get('nc_refill-stat-merchant-mixed')
-    if data is not None:
-        mch_cfgs = json.loads(data)
-        for mchid,val in mch_cfgs.items():
-            mchid = int(mchid)
-            period = int(val['period'])
-            formula = val['profit_formula'].strip()
-            result.append((mchid, period, formula))
-    return result
-
-
 class MerchantCalc(object):
     def __init__(self):
         self._mQuit = False
@@ -161,16 +148,4 @@ class MerchantCalc(object):
             result[_mchid] = {'send_amounts': _send_amounts, 'lack_amounts': _lack_amounts}
 
         logger.debug(result)
-        return result
-
-    def _profit_ratio(self, rclient):
-        mixed_ratios = mixed_ratio(rclient)
-
-        for mchid, period, formula in mixed_ratios:
-            end_time = int(time.time())
-            reader, days, start_time, end_time = self.calc_time(end_time - period, end_time)
-            day_stamp = days[0]
-            tuple_pathes = reader.many_tuple_path(days, mchids={mchid})
-            gen = detail_paths(reader, tuple_pathes, days)
-
-        pass
+        return result

+ 94 - 0
plot/refill/MerchantCalcBase.py

@@ -0,0 +1,94 @@
+from .CalcBase import CalcBase
+from .DataStream import EMchPosmap as pos_map, span_days
+from .MerchantReader import MerchantReader
+from .algorithm import calc_morder_lack
+
+import logging
+logger = logging.getLogger('MerchantCalcBase')
+
+def detail_paths(reader: MerchantReader, tuple_pathes: dict, days: list):
+    count = len(days)
+    for mchid, tup in tuple_pathes.items():
+        for _card_type, _spec in tup:
+            detail_datas = reader.init_data(count)
+            for i, day in enumerate(days):
+                data = reader.read(day, mchid, _card_type, _spec)
+                if data is not None:
+                    column_pos = i * 86400
+                    view = detail_datas[:, column_pos:column_pos + 86400]
+                    view += data
+            yield mchid, _card_type, _spec, detail_datas
+
+def mch_detail_paths(reader: MerchantReader, tuple_pathes: dict, days: list):
+    count = len(days)
+    for mchid, tup in tuple_pathes.items():
+        mch_datas = reader.init_data(count)
+        for _card_type, _spec in tup:
+            detail_datas = reader.init_data(count)
+            for i, day in enumerate(days):
+                data = reader.read(day, mchid, _card_type, _spec)
+                if data is not None:
+                    column_pos = i * 86400
+                    view = detail_datas[:, column_pos:column_pos + 86400]
+                    view += data
+                    view = mch_datas[:, column_pos:column_pos + 86400]
+                    view += data
+            yield mchid, _card_type, _spec, detail_datas
+        yield mchid, None, None, mch_datas
+
+def mch_paths(reader: MerchantReader, tuple_pathes: dict, days: list):
+    count = len(days)
+    for mchid, tup in tuple_pathes.items():
+        mch_datas = reader.init_data(count)
+        for _card_type, _spec in tup:
+            for i, day in enumerate(days):
+                data = reader.read(day, mchid, _card_type, _spec)
+                if data is not None:
+                    column_pos = i * 86400
+                    view = mch_datas[:, column_pos:column_pos + 86400]
+                    view += data
+        yield mchid, None, None, mch_datas
+
+def allpathes(reader: MerchantReader, tuple_pathes: dict, days: list, spec=None):
+    count = len(days)
+    show_detail = True if len(list(tuple_pathes.keys())) == 1 else False
+    if show_detail == False:
+        all_datas = reader.init_data(count)
+    else:
+        all_datas = None
+
+    for mchid, tup in tuple_pathes.items():
+        add_mchid(mchid)
+        mch_datas = reader.init_data(count)
+        for _card_type, _spec in tup:
+            if spec is not None and _spec != spec:
+                continue
+
+            if show_detail:
+                detail_datas = reader.init_data(count)
+            else:
+                detail_datas = None
+
+            for i, day in enumerate(days):
+                data = reader.read(day, mchid, _card_type, _spec)
+                if data is not None:
+                    column_pos = i * 86400
+                    view = mch_datas[:, column_pos:column_pos + 86400]
+                    view += data
+
+                    if show_detail:
+                        view = detail_datas[:, column_pos:column_pos + 86400]
+                        view += data
+            if show_detail:
+                yield mchid, _card_type, _spec, detail_datas
+        if all_datas is not None:
+            all_datas += mch_datas
+        yield mchid, None, None, mch_datas
+
+    if show_detail == False:
+        yield 'all', None, None, all_datas
+
+class MerchantCalcBase(CalcBase):
+    def _reader(self):
+        return MerchantReader()
+    pass

+ 0 - 15
plot/refill/QueueListener.py

@@ -65,21 +65,6 @@ class QueueListener(object):
                     log.error(ex)
         pass
 
-    # def _read_queue(self, rclient, queue):
-    #     while self._mQuit == False:
-    #         item = rclient.rpop(queue)
-    #         if item is None:
-    #             time.sleep(0.1)
-    #         else:
-    #             try:
-    #                 val = json.loads(item)
-    #                 method = val['method']
-    #                 params = val['params']
-    #                 self._dispatch(method, params)
-    #             except Exception as ex:
-    #                 log.error(ex)
-    #     pass
-
     def prepare_data(self):
         while self._mQuit == False:
             try:

+ 4 - 1
plot/refill/__init__.py

@@ -12,7 +12,9 @@ from .ChannelPainter import ChannelPainter,get_channels
 from .MerchantPainter import MerchantPainter,get_mchids
 from .helper import filter_chname, filter_cardtype, filter_mchids
 from .MerchantCalc import MerchantCalc
+from .MProfitRatioCalc import MProfitRatioCalc
 from .WriterConsumer import WriterConsumer
+from .server_util import opt_parse
 
 __all__ = ['DataWriteStream', 'DataReadStream',
            'MerchantWriter', 'ChannelWriter', 'NetchkWriter','WriteConsumer',
@@ -20,4 +22,5 @@ __all__ = ['DataWriteStream', 'DataReadStream',
            'ChannelPainter', 'MerchantPainter', 'get_channels', 'get_mchids',
            'queueListener', 'open_hdf5', 'day_stamp', 'time_border',
            'filter_chname', 'filter_cardtype', 'filter_mchids',
-           'MerchantCalc']
+           'MerchantCalc','MProfitRatioCalc'
+           'opt_parse']

+ 22 - 4
plot/refill/algorithm.py

@@ -1,11 +1,11 @@
-
 from .DataStream import EMchPosmap
 import numpy as np
 import logging
 
 logger = logging.getLogger('calcer')
 
-def calc_chratios(data,pos_map,start,end):
+
+def calc_chratios(data, pos_map, start, end):
     view = data[[pos_map.succ_count, pos_map.fail_count, pos_map.commit_count], :]
     view = view[:, start:end]
 
@@ -18,7 +18,7 @@ def calc_chratios(data,pos_map,start,end):
     return int(all[0, -1]), int(all[0, -1] + all[1, -1]), y
 
 
-def calc_mchratios(data,pos_map,start,end):
+def calc_mchratios(data, pos_map, start, end):
     view = data[[pos_map.succ_count, pos_map.fail_count, pos_map.submit_count], :]
     view = view[:, start:end]
 
@@ -35,7 +35,7 @@ def calc_morder_send(data, pos_map: type(EMchPosmap), start: int, end: int):
     view = data[:, start:end]
     sums = np.sum(view, axis=1)
 
-    all_return =  sums[pos_map.succ_mch_amounts] + sums[pos_map.fail_mch_amounts] + 0.0000001
+    all_return = sums[pos_map.succ_mch_amounts] + sums[pos_map.fail_mch_amounts] + 0.0000001
     ratio = sums[pos_map.succ_mch_amounts] / all_return
 
     send_count = sums[pos_map.submit_count] - sums[pos_map.succ_count] - sums[pos_map.fail_count]
@@ -59,3 +59,21 @@ def calc_morder_lack(data, pos_map: type(EMchPosmap), start: int, end: int):
     logger.info("send_count=%d send_amounts=%.4f ratio=%.4f lack_amounts=%.4f", send_count, send_amounts, ratio, lack_amounts)
 
     return send_amounts, lack_amounts
+
+
+# 用于计算成功率及利润率
+# succ_count, fail_count, succ_ratio, profit,profit_ratio
+def calc_mch_profit(data, pos_map: type(EMchPosmap), start: int, end: int):
+    view = data[:, start:end]
+    sums = np.sum(view, axis=1)
+
+    submit_count = sums[pos_map.submit_count]
+    succ_count = sums[pos_map.succ_count]
+    fail_count = sums[pos_map.fail_count]
+    succ_ratio = succ_count / (succ_count + fail_count + 0.0000001)
+
+    ch_amounts = sums[pos_map.succ_ch_amounts]
+    mch_amounts = sums[pos_map.succ_mch_amounts]
+    profit = mch_amounts - ch_amounts
+
+    return int(submit_count), int(succ_count), int(fail_count), round(succ_ratio, 5), round(profit, 3)

+ 14 - 0
plot/refill/server_util.py

@@ -0,0 +1,14 @@
+import sys, getopt
+
+def opt_parse():
+    x = sys.argv
+    opts, args = getopt.getopt(sys.argv[1:], "h:p:", ["host=", 'port='])
+    rhost = ''
+    rport = 6379
+    for o, val in opts:
+        if o in ("-h", "--host"):
+            rhost = val
+        elif o in ('-p', "--port"):
+            rport = int(val)
+
+    return rhost, rport

+ 2 - 2
rdispatcher/codispatcher.php

@@ -93,9 +93,9 @@ function subscribe_message(&$quit, &$redis, $channels)
 
                         refill\RefillFactory::instance()->UpdateRatio($ratios);
                     }
-                    elseif($type == 'mch_counts') {
+                    elseif($type == 'mch_profit_ratio') {
                         $ins = Cache::getInstance('cacheredis');
-                        $content = $ins->get_org('merchant_refill_counts');
+                        $content = $ins->get_org('refill_merchant_profit_ratio');
 
                         if(empty($content)) continue;
                         $counts = json_decode($content,true);

+ 14 - 0
test/TestBigData.php

@@ -91,8 +91,22 @@ class TestBigData extends TestCase
         $detail = $counts['detail'];
 
         refill\RefillFactory::instance()->UpdateMchRatios($gross,$detail);
+    }
+
+    public function testMchProfitRatio()
+    {
+        $ins = Cache::getInstance('cacheredis');
+        $content = $ins->get_org('refill_merchant_profit_ratio');
 
+        $data = json_decode($content,true);
+
+        $gross = $data['gross'];
+        $detail = $data['detail'];
+
+        refill\RefillFactory::instance()->UpdateMchRatios($gross,$detail);
     }
+    
+
 
     public function testXml()
     {

+ 23 - 5
test/TestRefillPhone.php

@@ -59,7 +59,6 @@ class TestRefillPhone extends TestCase
 
         $url = $this->requrl();
         $order_sn = $this->make_sn();
-        $order_sn = '      smsasdadad23';
 
         $params = ['mchid' => $this->mMchid,
             'cardno' => $phone,
@@ -70,10 +69,29 @@ class TestRefillPhone extends TestCase
             'notifyurl' => "http://www.baidu.com"];
 
         $proxy = new refill_proxy($this->mKey);
-        for ($i = 0; $i < 10; $i++) {
-            $resp = $proxy->send($url, $params);
-            Log::record("resp={$resp}", Log::DEBUG);
-        }
+        $resp = $proxy->send($url, $params);
+        Log::record("resp={$resp}", Log::DEBUG);
+    }
+
+    public function testAddByYaoyou()
+    {
+        $phone = '13911129867';
+        $amount = 100;
+
+        $url = $this->requrl();
+        $order_sn = $this->make_sn();
+
+        $params = ['mchid' => 10202,
+            'cardno' => $phone,
+            'amount' => $amount,
+            "act" => "refill",
+            "op" => "add",
+            'order_sn' => $order_sn,
+            'notifyurl' => "http://www.baidu.com"];
+
+        $proxy = new refill_proxy('KxK9f3yaBDOXVwvuOKDozvQ9Mt1CoqRX');
+        $resp = $proxy->send($url, $params);
+        Log::record("resp={$resp}", Log::DEBUG);
     }
 
     public function testCardQuery()

+ 44 - 0
test/TestRefillPolicy.php

@@ -0,0 +1,44 @@
+<?php
+
+
+use PHPUnit\Framework\TestCase;
+
+define('APP_ID', 'refill_stat');
+define('BASE_ROOT_PATH', str_replace('/test', '', dirname(__FILE__)));
+
+require_once(BASE_ROOT_PATH . '/global.php');
+require_once(BASE_CORE_PATH . '/lrlz.php');
+require_once(BASE_ROOT_PATH . '/fooder.php');
+
+require_once(BASE_HELPER_PATH . '/refill/RefillFactory.php');
+require_once(BASE_HELPER_PATH . '/queue/iqueue.php');
+require_once(BASE_HELPER_PATH . '/queue/monitor.php');
+require_once(BASE_HELPER_PATH . '/refill/util.php');
+
+class TestRefillPolicy extends TestCase
+{
+    public static function setUpBeforeClass(): void
+    {
+        Base::run_util();
+    }
+
+    private function getRatios()
+    {
+        $ins = Cache::getInstance('cacheredis');
+        $content = $ins->get_org('refill_merchant_profit_ratio');
+        $counts = json_decode($content,true);
+        $gross = $counts['gross'];
+        $detail = $counts['detail'];
+
+        return [$gross,$detail];
+    }
+
+    public function testRatioCtl()
+    {
+        [$gross,$detail] = $this->getRatios();
+        $ratio_ctl = new refill\mratio_controlex();
+        $ratio_ctl->update($gross,$detail);
+        $mchid = 10202;
+        $ratio_ctl->ratio_match($mchid, 14, 4, 50, [1, 2]);
+    }
+}

+ 54 - 0
test/TestSmartCard.php

@@ -0,0 +1,54 @@
+<?php
+
+
+use PHPUnit\Framework\TestCase;
+define('APP_ID', 'test');
+define('BASE_ROOT_PATH', str_replace('/test', '', dirname(__FILE__)));
+
+require_once(BASE_ROOT_PATH . '/global.php');
+require_once(BASE_CORE_PATH . '/lrlz.php');
+require_once(BASE_ROOT_PATH . '/fooder.php');
+
+
+class TestSmartCard extends TestCase
+{
+    public static function setUpBeforeClass(): void
+    {
+        Base::run_util();
+    }
+
+    public function testGenChannelCode()
+    {
+        $result = [];
+        $name_count = ['huanxishuyu' => 10];
+        foreach ($name_count as $name => $count)
+        {
+            $codes = $this->gen_code($name,$count);
+            $result[$name] = $codes;
+        }
+
+        $x = $result;
+    }
+
+    private function gen_code($name,$count)
+    {
+        $gendor = function ($str) {
+            $code = md5($str);
+            $code = substr($code,0,12);
+            return $code;
+        };
+
+        $base = 'https://ylapi.xyzshops.cn/chinatelecom/#/starcard?code=';
+
+        $result = [];
+        for ($i = 0; $i < $count; $i++)
+        {
+            $key = "{$name}-{$i}";
+            $code = $gendor($key);
+            $link = $base . $code;
+
+            $result[$key] = ['code' => $code,'link' => $link];
+        }
+        return $result;
+    }
+}

BIN
test/smardcard/星卡CODE.xlsx