<?php
class OthVinImg
{
	public $debug = false;

	/**
	  * @method 单条VIN图片识别
	  * @param  $vii array [必填] stand_vin_imgs [...]
	  * @param  $reg array [选填] stand_ocrimg_reg [...]
	  * @param  $runcount  [选填] 
	  * @param  $fast bool [选填] 快速模式？
	  * @author soul
	  * @copyright 2020/9/7
	  * @return 
	  */
	public function sma($viiArr, $reg = [], $runcount = 0, $fast = false)
	{
		$vin = $viiArr['vii_vin'];
		$btime = microtime(true);
		if(empty($reg['reg_gxbmatchlen'])) $reg['reg_gxbmatchlen'] = 4;
		$ocrtxt = $viiArr['vii_imgocr'];
		//LSGTC52MX7Y142566 SEATS
		$replace = ["（"=>'(',"）"=>')',"\r\n"=>"\n", '：'=>':',
			'21PAOG'=>'21PA0G', 'SEATS'=>''
		];
		$ocrtxt = strtr($ocrtxt, $replace);
		$imgType = $viiArr['vii_imgtype'];
		if(!in_array($imgType, [1,2])){
			return LibFc::ReturnData(true, ['status'=>'nodata','error'=>'图片类型不符'.$imgType, 'imgType'=>$imgType]);
		}
		if($this->debug) echo $vin, '=>', $ocrtxt,'<br>';

		$wmi = substr($vin, 0, 3); 
		$vin8 = substr($vin, 0, 8);
		$isJinkou = !in_array(substr($vin, 0, 1), ['L', 'H'])? true: false;
		$YpwVin = new YpwVin();
		$likeGxbArr = [];
		if($isJinkou)
		{
			if(empty($reg['min_smacount'])) $reg['min_smacount'] = 5;
			$vin10_11 = substr($vin, 9, 2);
			$vin12_17 = substr($vin, 11, 6);
			$othsmawhere = '';
			$fileds = ['sma_brand', 'GROUP_CONCAT(DISTINCT sma_gxb_code_show) gxb_codes, MIN(sma_vin12_17) endvin_min, MAX(sma_vin12_17) endvin_max, count(*) vin_count'];
			switch($wmi)
			{
				case 'W1N':
				case 'W1K':
					//梅赛德斯-奔驰 括号前六位  AMG GLC 63 S 4MATIC+ 6B2A10 (253389)
					$sgxbreg = '/ ([a-zA-Z0-9]{6}) ?\([a-zA-Z0-9]+\)$/i';
					$othsmawhere = " and sma_brand='梅赛德斯-奔驰'";
				break;
				case 'WAU':
					//奥迪 空格最后一截字符
					$sgxbreg = '/ ([a-zA-Z0-9]{7,})$/i';
					$othsmawhere = " and sma_brand='奥迪'";
				break;
				case 'WVG':
				case 'WVW':
					//大众 空格最后一截字符
					$sgxbreg = '/ ([a-zA-Z0-9]{7,})$/i';
					$othsmawhere = " and sma_brand='大众'";
				break;
				default:
					if($reg['reg_todb']) return LibFc::ReturnData(true, ['status'=>'noreg','error'=>'计算规则尚未配置']);
				break;
			}
			$tjwhere = sprintf(" and sma_vin1_8 = '%s' and sma_vin10_11 = '%s' and sma_from !='ocr'", $vin8, $vin10_11).$othsmawhere;
			$smres = $YpwVin->get('stand_model_any', $fileds, $tjwhere, true);
			$smres = $smres['data'];
			$tGxbArr = explode(',', $smres['gxb_codes']);
			
			//使用完整还是型号部分进行比较
			$isSmall = false;
			foreach($tGxbArr as $gxb)
			{
				if($sgxbreg && preg_match($sgxbreg, $gxb, $match))
				{
					$isSmall = true;
					$smallgxb = $match[1];
					$format = LibFc::FormatNum($gxb);
					$likeGxbArr[$format] = ['com'=>$smallgxb,'com_format'=>LibFc::FormatNum($smallgxb), 'full'=>$gxb, 'full_format'=>$format];
				}
				else
				{
					$isSmall = false;
					break;
				}
			}
				
			if(!$isSmall)
			{
				foreach($tGxbArr as $gxb)
				{
					$format = LibFc::FormatNum($gxb);
					$likeGxbArr[$format] = ['com'=>$gxb,'com_format'=>$format, 'full'=>$gxb, 'full_format'=>$format];
				}
				$reg['gxb_matchtype'] = 'full';
			}
			if(empty($smres) || $smres['vin_count'] < $reg['min_smacount']){
				//是否真实存在
				$tjres1 = $YpwVin->get('stand_model_any', ['count(*) count'], $tjwhere." and (sma_from IN ('catch','ZDAPI') or (length(sma_engine6) >= 6 and sma_vyear >=2017))", true);
				if(empty($tjres1['data']) || $tjres1['data']['count'] < 1){
					return LibFc::ReturnData(true, ['status'=>'vin_count','error'=>'vin_count:'.$smres['vin_count'].'|'.$tjres1['data']['count'].'样本不足']);
				}
			}
			$viiArr['vin_count'] = $smres['vin_count'];
			if($vin12_17 < $smres['endvin_min']) return LibFc::ReturnData(true, ['status'=>'vin_min','error'=>'vin12_17_min:'.$smres['endvin_min'].'超出最小值']);
			if($vin12_17 > $smres['endvin_max']) return LibFc::ReturnData(true, ['status'=>'vin_max','error'=>'vin12_17_max:'.$smres['endvin_max'].'超出最大值']);
		}
		else
		{
			if(empty($reg['min_smacount'])) $reg['min_smacount'] = 10;
			$vin10_12 = substr($vin, 9, 3);
			$vin13_17 = substr($vin, 12, 5);
			//vin1_12的最大最下和型号
			$fileds = ['GROUP_CONCAT(DISTINCT sma_gxb_code_show) gxb_codes', 'MIN(sma_vin13_17) endvin_min', 'MAX(sma_vin13_17) endvin_max', 'count(*) vin_count'];
			$tjwhere = sprintf(" and sma_vin1_8 = '%s' and sma_vin10_12 = '%s' and sma_from !='ocr'", $vin8, $vin10_12);
			$smres = $YpwVin->get('stand_model_any', $fileds, $tjwhere, true);
			$smres = $smres['data'];
			foreach(explode(',', $smres['gxb_codes']) as $gxb)
			{
				$format = LibFc::FormatNum($gxb);
				$likeGxbArr[$format] = ['com'=>$gxb,'com_format'=>$format, 'full'=>$gxb, 'full_format'=>$format];
			}
			
			if(empty($smres) || $smres['vin_count'] <= $reg['min_smacount']){
				if($fast){
					if($this->debug) echo '快速模式忽略样本数量<br>';
				}else{
					//是否真实存在
					$tjres1 = $YpwVin->get('stand_model_any', ['count(*) count'], $tjwhere." and (sma_from IN ('catch','ZDAPI') or (length(sma_engine6) >= 6 and sma_vyear >=2017))", true);
					if(empty($tjres1['data']) || $tjres1['data']['count'] < 1){
						$tjres2 = $YpwVin->get('stand_model_any', ['count(*) count'], $tjwhere." and sma_from IN ('manual','catch','buy01', 'buy02','ZDAPI','org')", true);
						if(empty($tjres2['data']) || $tjres2['data']['count'] < 2){
							return LibFc::ReturnData(true, ['status'=>'vin_count','error'=>'vin_count:'.$smres['vin_count'].'|'.$tjres1['data']['count'].'|'.$tjres2['data']['count'].'样本不足']);
						}
					}
				}
			}
			$viiArr['vin_count'] = $smres['vin_count'];
			if(!$fast){
				if($vin13_17 < $smres['endvin_min']) return LibFc::ReturnData(true, ['status'=>'vin_min','error'=>'vin13_17_min:'.$smres['endvin_min'].'超出最小值']);
				if($vin13_17 > $smres['endvin_max']) return LibFc::ReturnData(true, ['status'=>'vin_max','error'=>'vin13_17_max:'.$smres['endvin_max'].'超出最大值']);
			}
		}

		//今年的加上VIN前8的型号，避免无样本造成计算错误 LNBRCFRK5RB365929，如果匹配到的型号来自VIN8，交由人工处理
		$vinYear = LibFc::VinYear($vin);
		if($vinYear >= date('Y')){
			//$res = $YpwVin->get("gxb_base JOIN gxb_params ON gp_gb_id = gb_id AND gp_gl_code = 'vin18'", ['gb_gxb_code,gb_gxb_code_show'], sprintf(" and gp_value='%s' AND gb_pici > 375 AND gb_gxb_code NOT IN (SELECT gb_gxb_code FROM gxb_base WHERE gb_pici < 375) AND gb_gxb_code NOT IN (SELECT smt_gxb_code FROM stand_model_tj WHERE smt_vin1_8 = '%s') and gb_etime >%d group by gb_gxb_code", $vin8, $vin8, strtotime('-1year')));
			$res = $YpwVin->get("gxb_base JOIN gxb_params ON gp_gb_id = gb_id AND gp_gl_code = 'vin18'", ['gb_gxb_code,gb_gxb_code_show'], sprintf(" and gp_value='%s' AND gb_pici > 375 and gb_etime >%d group by gb_gxb_code", $vin8, strtotime('-1year')));
			if(count($res['data']) > 10 && $fast){
				//尝试直接文本匹配
				$txtMatch = [];
				foreach($res['data'] as $v){
					if(stripos($ocrtxt, $v['gb_gxb_code']) !== false || stripos($ocrtxt, $v['gb_gxb_code_show']) !== false){
						$txtMatch[] = $v;
					}
				}
				if($txtMatch){
					$res['data'] = $txtMatch;
				}else{
					if($this->debug) echo '型号数量大于10，快速模式忽略<br>';
					return LibFc::ReturnData(true, ['status'=>'nodata','error'=>'无效图:型号数量大于10', 'imgType'=>$imgType]);
				}
			}
			foreach($res['data'] as $v){
				if(empty($likeGxbArr[$v['gb_gxb_code']])){
					$likeGxbArr[$v['gb_gxb_code']] = ['com'=>$v['gb_gxb_code_show'],'com_format'=>$v['gb_gxb_code'], 'full'=>$v['gb_gxb_code_show'], 'full_format'=>$v['gb_gxb_code'], 'from'=>'vin8'];
				}
			}
		}
		
		if($this->debug) echo '规则判断结束=>'.ceil((microtime(true) - $btime)*1000).'ms<br>';

		//这个范围的型号
		$gxbArr = [];
		//避免左右空格
		$maxlen = 0;
		$sort = [];
		//匹配可能的型号
		$hasMatchGxbArr = [];
		$comVals = [];
		$comOrgVals = [];
		//当有型号无数据时，不通过唯一性加入判断
		$comUniFlag = true;
		//过滤的型号
		$endGxbArr = [];
		$hasformat = [];
		foreach($likeGxbArr as $formatgv=>$tgArr)
		{
			//比较部分去掉空格
			$gv = strtr($tgArr['com'], [' '=>'']);
			//LSGTC52M27Y099700
			if($this->debug) echo $tgArr['full'].'=>'.ceil((microtime(true) - $btime)*1000).'ms=>开始<br>';
			//防止中文
			if(!preg_match('/[0-9a-zA-Z]{2}/', $gv, $match)) continue;
			$len = strlen($gv);
			if($len < 5) continue;
			if(in_array($formatgv, $hasformat)) continue;
			$hasformat[] = $formatgv;
			$merge = [];
			if($isJinkou)
			{
				$where = $othsmawhere.sprintf(" and sma_vin1_8 ='%s' AND sma_gxb_code = '%s'", $vin8, $tgArr['full_format']);
				$res = $YpwVin->get('stand_model_any', ['sma_gxb_code_show,sma_brand,group_concat(distinct sma_engine) sma_engine, group_concat(distinct sma_kw) sma_kw,group_concat(distinct sma_cc) sma_cc,group_concat(distinct sma_passenger) sma_passenger'], $where, true);
				if(!empty($res['data']))
				{
					$merge = $res['data'];
					$merge['from'] = 'sma';
					foreach($merge as $tmk=>$tmv)
					{
						$vals = [];
						foreach(explode(',', $tmv) as $tval)
						{
							if(!empty($tval)) $vals[] = $tval;
						}
						$merge[$tmk] = implode(',', $vals);
					}
				}
			}else{
				$res = $YpwVin->catchGxbcodeInfo($tgArr['full'], 1, $vin);
				$merge = !empty($res['data']['merge']['sma_brand'])? $res['data']['merge']: [];
				//只处理国产，进口的直接从sma来
				if(!empty($merge['sma_gxb_code_show']) && stripos($merge['sma_gxb_code_show'], ',') === false) $gv = $merge['sma_gxb_code_show'];
				//前8不匹配
				if(!empty($merge['sma_vin_full']) && stripos($merge['sma_vin_full'], $vin8) === false) continue;
			}

			if(!empty($gxbArr[$gv])) continue;
			if(empty($merge)) $comUniFlag = false;
			
			if($maxlen == 0 || $len > $maxlen) $maxlen = $len;
			$gxbArr[$gv] = [
				'gxb'=>$gv,
				'full'=>$tgArr['full'],
				'full_format'=>$tgArr['full_format'],
				'char'=>str_split($gv),
				'len'=>$len,
				'merge'=>$merge,
			];
			$sort[$gv] = $len;
			
			//铭牌匹配信息
			if($imgType == 2)
			{
				$comFileds = ["sma_engine","sma_cc","sma_kw","sma_weight","max_weight","sma_passenger"];
				foreach($comFileds as $cf)
				{
					if(empty($merge[$cf])) continue;
					$temp = explode(',', $merge[$cf]);
					if($cf == 'sma_engine')
					{
						$tarr = [];
						foreach($temp as $tpv)
						{
							$t = explode('#', $tpv);
							$tarr[] = $t[0];
						}
						$temp = $tarr;
					}
					foreach($temp as $tempv)
					{
						$comOrgVals[$cf][$tempv][] = $gv;
						switch($cf)
						{
							case 'sma_engine': 
								if(!empty($tempv) && (
									preg_match('/号:?[\n ]*'.$tempv.'[^a-zA-Z0-9]/i', $ocrtxt, $tmatch) ||
									preg_match('/\n *'.$tempv.'[^a-zA-Z0-9]/i', $ocrtxt, $tmatch) || 
									($isJinkou && strlen($tempv) > 2 && preg_match('/\n *'.$tempv.'/i', $ocrtxt, $tmatch))
								))
								{
									$comVals[$cf][$tempv][] = $gv;
								}
							break;
							case 'sma_cc':
								if(LibFc::Int($tempv))
								{
									$fv = str_replace('.', '\.', sprintf("%.3f", $tempv/1000));
									$fv2 = str_replace('.', '\.', sprintf("%.1f", $tempv/1000));
									if(
										preg_match('/排量:?[\n ]*'.$tempv.'/i', $ocrtxt, $tmatch) || 
										preg_match('/'.$tempv.'ml/i', $ocrtxt, $tmatch) || 
										preg_match('/排[\x{4e00}-\x{9fa5}]:?[\n ]*'.$tempv.'[\n ]*ml/iu', $ocrtxt, $tmatch) || 
										preg_match('/排量:?[\n ]*'.$fv.'/i', $ocrtxt, $tmatch) || 
										preg_match('/排量:?[\n ]*'.$fv2.'/i', $ocrtxt, $tmatch) || 
										preg_match('/[\n ]'.$fv.'[\n ]*L\n/i', $ocrtxt, $tmatch) ||
										preg_match('/[\n ]'.$fv2.'[\n ]*L\n/i', $ocrtxt, $tmatch)
									)
									{
										$comVals[$cf][$tempv][] = $gv;
									}
								}
							break;
							case 'sma_kw':
								if(!empty($tempv))
								{
									if(
										preg_match('/功率:?[\n ]*'.$tempv.'/iu', $ocrtxt, $tmatch) || 
										preg_match('/'.$tempv.'[\n ]*kw/i', $ocrtxt, $tmatch) || 
										preg_match('/功[\x{4e00}-\x{9fa5}]:?[\n ]*'.$tempv.'[\n ]*kw/iu', $ocrtxt, $tmatch)
									)
									{
										$comVals[$cf][$tempv][] = $gv;
									}
								}
							break;
							case 'sma_weight':
								//7的铭牌没有整备质量
								$gtype = LibFc::getGxbCarType($gv);
								if(in_array($gtype, [1,5]) && !empty($tempv) && (
									preg_match('/质量:?[\n ]*'.$tempv.'/i', $ocrtxt, $tmatch) || 
									preg_match('/\n *'.$tempv.' *(k|[a-z]?g|\n)/i', $ocrtxt, $tmatch)
								))
								{
									$comVals[$cf][$tempv][] = $gv;
								}
							break;
							case 'max_weight':
								if(!empty($tempv) && (
									preg_match('/质量:?[\n ]*'.$tempv.'/i', $ocrtxt, $tmatch) || 
									preg_match('/\n *'.$tempv.'[\n ]*(k|[a-z]?g|\n)/i', $ocrtxt, $tmatch)
								))
								{
									$comVals[$cf][$tempv][] = $gv;
								}
							break;
							case 'sma_passenger':
								if(!empty($tempv) && (
									preg_match('/人[\x{4e00}-\x{9fa5}]*:?[\n ]*'.$tempv.'/iu', $ocrtxt, $tmatch) || 
									preg_match('/位[\x{4e00}-\x{9fa5}]:?[\n ]*'.$tempv.'/iu', $ocrtxt, $tmatch) || 
									preg_match('/:?[\n ]*'.$tempv.'[\n ]*人/iu', $ocrtxt, $tmatch)
								))
								{
									$comVals[$cf][$tempv][] = $gv;
								}
							break;
						}
					}
				}
			}
		}
		
		if(empty($gxbArr)) 
			return LibFc::ReturnData(true, ['status'=>'nodata','error'=>'无型号1']);
		
		//长=>短
		array_multisort($sort, SORT_DESC, $gxbArr);
		$comArr = $this->comChar($gxbArr, $maxlen);
		if($this->debug) echo implode(',', array_keys($gxbArr)),'<br>开头相同: ', $comArr['bsame'], '=>尾部相同: ', $comArr['esame'], '<br>';
		
		$gxbocrtxt = $ocrtxt;

		//确认型号内容的大致开始位置，避免匹配过程其他信息影响 LSG  LZW 容易出问题，取消
		/*if(strlen($comArr['bsame']) > 2 && preg_match_all('/'.substr($comArr['bsame'], 0, 2).'[^\n]+\n/iUs', $ocrtxt, $match))
		{
			if(count($match[0]) == 1)
			{
				$gxbocrtxt = $match[0][0];
				if($this->debug) echo '型号OCR内容=>', $gxbocrtxt,'<br>';
			}
		}*/

		if($this->debug) echo '计算不同=>'.ceil((microtime(true) - $btime)*1000).'ms<br>';
	
		//找出每个型号最长的匹配字符串
		$matchByArr = [];
		foreach($gxbArr as $gbk=>$gbv)
		{
			$diffStr = substr($gbv['gxb'], $comArr['bsamelen'], $gbv['len'] - $comArr['bsamelen'] - $comArr['esamelen']);
			$diffLen = strlen($diffStr);
			if($this->debug) echo $gbk, '=>',$diffStr, ' 开始<br>';

			$matchStrArr = [];
			$findArr = [];
			
			//最大匹配长度
			$maxLen = $reg['reg_gxbmatchlen'];
			//不同部分比最小长度大 优先不同的部分
			if($diffLen >= $reg['reg_gxbmatchlen'])
				$findArr[] = $diffStr;

			if($diffStr != '')
			{
				$maxl = $reg['reg_gxbmatchlen'] - 1;
				//往前截取多少位
				$bl = $comArr['bsamelen'] > $maxl? $maxl: $comArr['bsamelen'];
				//后面截取多少位
				$el = $comArr['esamelen'] > $maxl ? $maxl: $comArr['esamelen'];
				$maxLen = $bl + $diffLen + $el;
				$findArr[] = substr($gbv['gxb'], $comArr['bsamelen'] - $bl, $maxLen);
			}
			else
			{
				$findArr[] = $gbv['gxb'];
				$maxLen = $gbv['len'];
			}
			
			//优先不同的部分 匹配长度
			for($l = $maxLen; $l >= $reg['reg_gxbmatchlen']; $l--)
			{
				foreach($findArr as $tdiff)
				{
					$tlen = strlen($tdiff);
					//逐位开始
					for($i = 0 ;$i <= ($tlen - $l); $i++)
					{
						$matchStr = substr($tdiff, $i, $l);
						//已经匹配过或者长度不一致
						if(in_array($matchStr, $matchStrArr) || strlen($matchStr) != $l || $matchStr == 'BYD7')
							continue;

						$matchStrArr[] = $matchStr;
						$chk = $this->matchGxbStr($gxbocrtxt, $vin, $gbk, $matchStr);
						if($chk['match'])
						{
							$gbv['matchbys'][] = $matchStr;
							$hasMatchGxbArr[$gbk] = $gbv;
							$matchByArr[$chk['format']][] = $gbk;
						}
						else
						{
							if($this->debug) echo $matchStr, ',';
						}
					}
				}
				//同一个长度 匹配到 结束  LJ2KP8PE8KA007383  JHC7002BEV33  BEV33
				if($gbv['matchbys']) break;
			}
			if($this->debug) 
			{
				if($gbv['matchbys']) echo '=>'.implode(',', $gbv['matchbys']), ' 匹配成功<hr>';
				else echo '=> 匹配失败<hr>';
			}
			$gxbArr[$gbk] = $gbv;
		}

		//反算追加 LJ8F2C5D7HG421764 JNJ6460Q2BK,JNJ6460BK=>JNJ6460Q2BK   LJ8F2C5D5HG404364=>JNJ6460BK
		foreach($matchByArr as $ma=>$gxbs)
		{
			//LFV2A1152E3145884  7162
			$ma = (string) $ma;
			foreach($gxbArr as $gbk=>$gbv)
			{
				if(!in_array($gbk, $gxbs) && stripos($gbk, $ma) !== false)
				{
					$gxbArr[$gbk]['matchbys'][] = $ma;
					$hasMatchGxbArr[$gbk] = $gbv;
					if(!in_array($gbk, $matchByArr[$ma])) $matchByArr[$ma][] = $gbk;
				}
			}
		}

		if($this->debug) echo '内容匹配=>'.ceil((microtime(true) - $btime)*1000).'ms<br>';
		
		//包含关系 LZWACAGA197094570 6407B3  6407B
		foreach($matchByArr as $ma=>$gxbs)
		{
			$ma = (string) $ma;
			foreach($matchByArr as $ma2=>$gxbs2)
			{
				$ma2 = (string) $ma2;
				if($ma != $ma2 && stripos($ma, $ma2) !== false)
				{
					$removeArr = [];
					foreach($gxbs2 as $gxb2)
					{
						if(!in_array($gxb2, $gxbs))
						{
							$removeArr[] = $gxb2;
							unset($hasMatchGxbArr[$gxb2]);
						}
					}
					unset($matchByArr[$ma2]);
					if($this->debug) echo $ma, '=>', implode(',', $gxbs),'=>包含', $ma2, '=>', implode(',', $gxbs2),' 移除=>',implode(',', array_unique($removeArr)), '<br>';
				}
			}
		}

		//同一个字符串识别到一个的直接将此型号的其他字符数据删除
		foreach($matchByArr as $ma=>$gxbs)
		{
			$ma = (string) $ma;
			if(count($gxbs) > 1 || empty($gxbs[0]))
			{
				if($this->debug) echo $ma, '=>', implode(',', $gxbs), '<br>';
				continue;
			}
			//型号唯一  LGWCA2163BC002486  1021DSD02,SD02
			$gxb = $gxbs[0];
			if($this->debug) echo $ma, '=>', $gxb,' 唯一';
			$removeArr = [];
			foreach($matchByArr as $ma2=>$gxbs2)
			{
				$ma2 = (string) $ma2;
				$remove = false;
				//LZWACAGA197094570 6407B3  6407B
				if($ma != $ma2)
				{
					//|| count($gxbs2) > 1 LZWADAGA9C8134926 不直接剔除两个以及以上的
					if(in_array($gxb, $gxbs2))
					{
						$remove = true;
					}
					else
					{
						//LVGBD54K2CG003562  GTM7251HEVS=>GTM7251HEV 包含关系 去短的
						foreach($gxbs2 as $gxb2)
						{
							if(stripos($gxb, $gxb2) === 0) 
							{
								
								$remove = true;
								break;
							}
						}
					}
				}
				if($remove)
				{
					foreach($gxbs2 as $gxb2)
					{
						if($gxb2 != $gxb)
						{
							$removeArr[] = $gxb2;
							unset($hasMatchGxbArr[$gxb2]);
						}
					}
					unset($matchByArr[$ma2]);
				}
			}
			if($this->debug) echo ' 移除=>',implode(',', array_unique($removeArr)), '<br>';
		}
		

		if($this->debug && !empty($comVals)){
			print_r($comVals);
			echo '<br>';
		}
		
		//匹配不到
		if(empty($hasMatchGxbArr))
		{
			//匹配到的型号和 comVals 进行对比 确认相符
			foreach($comVals as $cf=>$cv)
			{
				$tgxbs = [];
				foreach($cv as $cvk=>$cvv)
				{
					$cvv = array_unique($cvv);
					$tgxbs = array_merge($tgxbs, $cvv);
					$comVals[$cf][$cvk] = $cvv;
				}
				$tgxbs = array_unique($tgxbs);
				if($comUniFlag && in_array($cf, ["sma_weight","max_weight","sma_passenger"]) && count($tgxbs) == 1)
				{
					if(
						$cf != 'sma_passenger' || 
						($cf == 'sma_passenger' && count($comVals) > 1)
					)
					{
						if($this->debug) echo $cvv[0],'=>', $cf, '=>唯一追加<br>';
						$hasMatchGxbArr[$cvv[0]] = $gxbArr[$cvv[0]];
					}
				}
			}
		}

		//还是匹配不到，如果发动机唯一，并且发动机至少有4位长度相符 长度大于8的至少一半 LJ8F2C5D7HG421764  TNN4G15T
		if(empty($hasMatchGxbArr) && !empty($comOrgVals['sma_engine']) && count($gxbArr) > 1)
		{
			$allengs = array_keys($comOrgVals['sma_engine']);
			foreach($comOrgVals['sma_engine'] as $eng=>$gvs)
			{
				$englen = strlen($eng);
				if(count($gvs) != 1 || $englen < 3) continue;

				preg_match_all('/[a-zA-Z]/', $eng, $tzimu);
				preg_match_all('/[0-9]/', $eng, $tnum);
				$tengs = [];
				//3个字符
				if($englen == 3){
					//两个字母+一个数字 或者 3个字母及以上
					if(stripos($vin, $eng) === false && (count($tzimu[0]) >= 3 || (count($tzimu[0]) == 2 && count($tnum[0]) >= 1))){
						$tengs[] = $eng;
					}else{
						continue;
					}
				}else{
					//4个字符以上 至少两位数字
					$matchlen = $englen > 8 ? ceil($englen/2) : 4;
					for($i = 0; $i <= $englen - $matchlen; $i++)
					{
						$tm = substr($eng, $i, $matchlen);
						$te = preg_replace('/[^0-9]/', '', $tm);
						if(strlen($te) < 2 || stripos($vin, $tm) !== false) continue;
						$tengs[] = $tm;
					}
				}
				if($this->debug) echo $eng,'=>', implode(',', $tengs), '=>发动机唯一开始匹配<br>';
				foreach($tengs as $teng)
				{
					if(preg_match('/'.$teng.'/i', $ocrtxt, $tmatch))
					{
						$isuni = true;
						//这个规则是否在其它发动机也唯一
						foreach($allengs as $tev)
						{
							//不等于自己
							if($eng != $tev && stripos($tev, $teng) !== false)
							{
								$isuni = false;
								break;
							}
						}
						if($isuni)
						{
							if($this->debug) echo $eng,'=>', $teng, '=>唯一追加=>', $gvs[0],'<br>';
							$comVals['sma_engine'][$eng][] = $gvs[0];
							$hasMatchGxbArr[$gvs[0]] = $gxbArr[$gvs[0]];
							break;
						}
					}
				}
			}
		}

		if(count($hasMatchGxbArr) > 1)
		{
			//匹配到的型号和 comVals 进行对比 确认相符
			foreach($comVals as $cf=>$cv)
			{
				$tgxbs = [];
				foreach($cv as $cvk=>$cvv)
				{
					$cvv = array_unique($cvv);
					$tgxbs = array_merge($tgxbs, $cvv);
					$comVals[$cf][$cvk] = $cvv;
				}
				$tgxbs = array_unique($tgxbs);
				
				//移除已经匹配的但是不在comVals里的
				foreach($hasMatchGxbArr as $hmk=>$hmk)
				{
					if(!in_array($hmk, $tgxbs))
					{
						if($this->debug) echo $hmk,'=>', $cf, '=>不符剔除<br>';
						unset($hasMatchGxbArr[$hmk]);
					}
				}
			}
		}
		
		//剩下的型号如果是包含关系，通过尾部加空格或者换行进行过滤
		if(count($hasMatchGxbArr) > 1)
		{
			foreach($hasMatchGxbArr as $hmk=>$hmk)
			{
				//确认是包含关系
				$bhArr = [$hmk];
				foreach($hasMatchGxbArr as $hmk2=>$hmk2)
				{
					if($hmk == $hmk2) continue;
					if(stripos($hmk2, $hmk) === 0)
					{
						$bhArr[] = $hmk2;
					}
				}
				if(count($bhArr) == count($hasMatchGxbArr))
				{
					$matchStr = substr($hmk, -4).'[ \n\:]';
					$chk = $this->matchGxbStr($gxbocrtxt, $vin, $hmk, $matchStr);
					if($chk['match'])
					{
						$matchGxbArr = [];
						//匹配的内容是否在其他型号出现过？
						foreach($bhArr as $hmk3)
						{
							if($hmk3 != $hmk && preg_match('/'.$matchStr.'/', $hmk3, $match))
							{
								$matchGxbArr[] = $hmk3;
							}
						}
						//没有出现过
						if(empty($matchGxbArr))
						{
							//移除其他型号
							foreach($bhArr as $hmk3)
							{
								if($hmk3 != $hmk)
									unset($hasMatchGxbArr[$hmk3]);
							}
							if($this->debug) echo $matchStr,'=>型号包含匹配唯一确认', $hmk,'<br>';
						}
					}
				}
			}
		}
		
		//电池信息反推
		if($runcount === 0 && ($fast || abs($vinYear-date('Y')) > 1) && count($hasMatchGxbArr) != 1){
			$dcGxbArr = [];
			$res = $this->smaByBatteryTxt($vin, $ocrtxt);
			if($res['data']['matches']){
				$tgxbs = array_unique(array_column($res['data']['matches'], 'sma_gxb_code'));
				if(count($tgxbs) == 1){
					if($this->debug) echo '电池规则唯一=>', $tgxbs[0],'<br>';
					$hasMatchGxbArr = [$tgxbs[0]=>$gxbArr[$tgxbs[0]]];
				}else if(count($tgxbs) > 1 && count($hasMatchGxbArr) > 1){
					$same = array_intersect($tgxbs, array_keys($hasMatchGxbArr));
					if(count($same) == 1){
						if($this->debug) echo '电池与其他计算相同型号唯一=>', $tgxbs[0],'<br>';
						$hasMatchGxbArr = [$same[0]=>$gxbArr[$same[0]]];
					} 
				}
			}
		}

		$addArr = [];
		if(count($hasMatchGxbArr) == 1)
		{
			$gbv = array_shift($hasMatchGxbArr);
				
			//如果匹配到的型号来自VIN8，交由人工处理
			if(!$fast && !empty($likeGxbArr[$gbv['full_format']]['from']) && $likeGxbArr[$gbv['full_format']]['from'] == 'vin8'){
				if($this->debug) echo $gbv['full'].'=>匹配到的型号来自VIN8，转人工<br>';
				return LibFc::ReturnData(true, ['status'=>'task','error'=>'型号存疑:'.$gbv['full'].'型号来自VIN8，转人工']);
			}

			//特殊处理 ocr本身错误
			if($gbv['full'] == 'QCJ7160A4/CNG' && stripos($ocrtxt, 'DA4G18') === false)
			{
				if($this->debug) echo $gbv['full'].'=>发动机不匹配忽略<br>';
				return LibFc::ReturnData(true, ['status'=>'nodata','error'=>'无型号2']);
			}

			//通过前8的型号匹配内容
			$res = $YpwVin->get('gxb_base JOIN gxb_params ON gb_id = gp_gb_id AND gp_gl_code = "vin18" and gb_type="gxb"', ['gb_gxb_code, gb_gxb_code_show'], sprintf(" and gp_value = '%s' GROUP BY gb_gxb_code order by length(gb_gxb_code) desc", $vin8));
			foreach($res['data'] as $tv)
			{
				if(stripos($gbv['full_format'], $tv['gb_gxb_code']) === false && (stripos($ocrtxt, $tv['gb_gxb_code']) !== false || stripos($ocrtxt, $tv['gb_gxb_code_show']) !== false))
				{//OCR型号不包括VIN8型号，但是VIN8型号在图片内容里，转人工  LETADDE17MH045170
					if($this->debug) echo $tv['gb_gxb_code_show'],'=>OCR型号'.$gbv['full'].'但是VIN8型号在图片内容里，转人工<br>';
					return LibFc::ReturnData(true, ['status'=>'task','error'=>'型号存疑:'.$gbv['full'].','.$tv['gb_gxb_code']]);
				}
			}

			$addArr = [
				'sma_vin'=>$vin,
				'sma_gxb_code_show'=>$gbv['full']
			];
			
			//匹配到的数据合并处理
			foreach($comVals as $cf=>$cv)
			{
				if(stripos($cf, 'sma_') === 0 && count($cv) == 1)
				{
					foreach($cv as $cvk=>$cvv)
					{
						$addArr[$cf] = $cvk;
						break;
					}
				}
			}

			$addArr['oth'] = $viiArr;
			if($isJinkou)
			{
				$addArr['sma_brand'] = $gbv['merge']['sma_brand'];
			}
			else
			{
				$OthPdfOcr = new OthPdfOcr();
				$res = $OthPdfOcr->fillVinByGxbMerger($vin, $addArr, $gbv['merge'], $viiArr['vii_imgocr']);
				if(!empty($res['data']['sma'])) $addArr = $res['data']['sma'];
			}

			$addArr['sma_from'] = 'ocr';
			$addArr['sma_gxb_code'] = LibFc::FormatNum($addArr['sma_gxb_code_show']);
			$addArr['sma_gtype'] = LibFc::getGxbCarType($addArr['sma_gxb_code']);
			
			//提取整备质量
			if(empty($addArr['sma_weight']) || empty($addArr['sma_passenger'])){
				 $res = $this->matchAnyByVinGxbTxt($vin, $gbv['full'], $ocrtxt);
				 if($res['data']['sma_weight']) $addArr['sma_weight'] = $res['data']['sma_weight'];
				 if($res['data']['sma_passenger']) $addArr['sma_passenger'] = $res['data']['sma_passenger'];
			}
			
			//制造日期
			$YpwVinImg = new YpwVinImg();
			$res = $YpwVinImg->ocrVinDate($viiArr);
			if($res['data']['okdate']) $addArr['sma_year'] = $res['data']['okdate'];
			if($viiArr['vii_isreplace']){
				$YpwVin->replace('zz_temp_smare', ['a'=>$vin,'b'=>$addArr['sma_gxb_code_show'], 'c'=>date('Y-m-d H:i:s')]);
			}
			return LibFc::ReturnData(true, ['status'=>'ok', 'sma'=>$addArr]);
		}
		//二次识别
		if($runcount === 0)
		{
			$eres = $YpwVin->get('stand_gxb_ocrerrbit', ['sge_gxb_code, sge_rebits'], sprintf(" and sge_gxb_code in ('%s') order by length(sge_gxb_code) desc", implode("','", array_keys($likeGxbArr))));
			foreach($eres['data'] as $rv){
				$rv['sge_rebits'] = json_decode($rv['sge_rebits'], true);
				$sgxb = $likeGxbArr[$rv['sge_gxb_code']]['full'];
				$gxbchars = 'A-Za-z0-9';
				if(stripos($sgxb, ' ')) $gxbchars .= ' ';
				if(stripos($sgxb, '-')) $gxbchars .= '\\-';
				if(stripos($sgxb, '.')) $gxbchars .= '\.';
				$mreg = '/(?:^|[^A-Za-z0-9])([A-Za-z0-9]{2}['.$gxbchars.']{'.(strlen($sgxb)-2).'})(?:[^A-Za-z0-9]|$)/is';
				if(preg_match_all($mreg, $ocrtxt, $matches)){
					foreach($matches[1] as $tgxb){
						$regxb = $tgxb;//替换后的型号
						$remax = 2; //最多替换2次
						foreach($rv['sge_rebits'] as $rvv){
							$bstr = $rvv[0] > 0? substr($regxb, 0, $rvv[0]): '';
							$mstr = substr($regxb, $rvv[0], 1);
							$estr = $rvv[0]+1 < strlen($tgxb)? substr($regxb, $rvv[0]+1): '';
							$regxb = $bstr. str_ireplace(str_split($rvv[2]), $rvv[1], $mstr, $count).$estr;
							if($count){
								$remax--;
								if($sgxb == $regxb){
									$viiArr['vii_imgocr'] = str_ireplace($tgxb, $sgxb, $viiArr['vii_imgocr']);
									$viiArr['vii_isreplace'] = true;
									
									return $this->sma($viiArr, $reg, ++$runcount, $fast);
								}
								if($remax == 0) break;
							}
						}
					}
				}
			}
			//WVG WVW WAU  O=>Q
			if(in_array($wmi, ['WVG','WVW','WAU']))
			{
				$reArr = ['o'=>'Q','O'=>'Q','0'=>'Q','i'=>'1', 'I'=>'1', 'l'=>'1'];
				$viiArr['vii_imgocr'] = strtr($viiArr['vii_imgocr'], $reArr);
				return $this->sma($viiArr, $reg, ++$runcount, $fast);
			}
			else if((stripos($viiArr['vii_imgocr'], 'O') || stripos($viiArr['vii_imgocr'], 'I') || strpos($viiArr['vii_imgocr'], 'l')))
			{
				//二次识别 O=>0 LGWEE4A45EF011149 
				$reArr = ['o'=>'0', 'O'=>'0', 'i'=>'1', 'I'=>'1', 'l'=>'1'];
				$viiArr['vii_imgocr'] = strtr($viiArr['vii_imgocr'], $reArr);
				return $this->sma($viiArr, $reg, ++$runcount, $fast);
			}
		}

		if(empty($hasMatchGxbArr)){
			return LibFc::ReturnData(true, ['status'=>'nodata','error'=>'无型号2']);
		}else if(count($hasMatchGxbArr) > 1){
			return LibFc::ReturnData(true, ['status'=>'anygxb','error'=>'多型号:'.implode(',', array_column($hasMatchGxbArr, 'gxb'))]);
		}
		return LibFc::ReturnData(true, ['status'=>'err','error'=>'error']);
	}

	/**
	  * @method 提取进口图片的发动机 功率 乘坐人数
	  * @param  $vin string [必填] 车架号
	  * @param  $ocrtxt string [选填] 图片内容
	  * @author soul
	  * @copyright 2023/9/12
	  * @return 
	  */
	public function smaNoGxb($vin, $ocrtxt)
	{
		$btime = microtime(true);
		$isVals = [];
		$replace = ["（"=>'(',"）"=>')',"\r\n"=>"\n", '：'=>':'];
		$ocrtxt = strtr($ocrtxt, $replace);
		$imgType = $this->getImgType($ocrtxt, $vin);
		if(!in_array($imgType, [1,2])) return LibFc::ReturnData(true, $isVals);
		if($this->debug) echo $vin, '=>', $ocrtxt,'<br>';

		$wmi = substr($vin, 0, 3); 
		$vin8 = substr($vin, 0, 8);
		$isJinkou = !in_array(substr($vin, 0, 1), ['L', 'H'])? true: false;
		$YpcBase = new YpcBase();
		$YpwVin = new YpwVin();
		if(!$isJinkou) return LibFc::ReturnData(true, $isVals);
		$vin10_11 = substr($vin, 9, 2);
		$vin12_17 = substr($vin, 11, 6);
		//可能的值
		$likeVals = ['sma_engine'=>[], 'sma_kw'=>[], 'sma_cc'=>[], 'sma_passenger'=>[]];
		//大众奥迪 "TRU","WA1","WAU","WUA","WVG","WVW","WV1","WV2" 发动机编码3位
		$vwWmis = ["TRU","WA1","WAU","WUA","WVG","WVW","WV1","WV2"];

		$fileds = ['GROUP_CONCAT(DISTINCT sma_engine) sma_engine','GROUP_CONCAT(DISTINCT sma_kw) sma_kw','GROUP_CONCAT(DISTINCT sma_cc) sma_cc','GROUP_CONCAT(DISTINCT sma_passenger) sma_passenger'];
		$res = $YpwVin->get('stand_model_any', $fileds, sprintf(" and sma_vin1_8 = '%s' and sma_from !='ocr'", $vin8), true);
		$row = $res['data'];
		foreach($likeVals as $filed=>$vals)
		{
			if(!empty($row[$filed]))
			{
				foreach(explode(',', $row[$filed]) as $tval)
				{
					if(!empty($tval) && !in_array($tval, $vals)) $vals[] = $tval;
				}
				$likeVals[$filed] = $vals;
			}
		}

		//车型库
		$fileds = ['GROUP_CONCAT(DISTINCT m3_engine) sma_engine, GROUP_CONCAT(DISTINCT m3_kw) sma_kw, GROUP_CONCAT(DISTINCT m3_cc) sma_cc, GROUP_CONCAT(DISTINCT m3_body) sma_passenger'];
		$res = $YpcBase->get('yp_model3 JOIN yp_vin_model3_link ON vm3_m3_id = m3_id JOIN yp_vin ON vin_id = vm3_vin_id', $fileds, sprintf(" and vin_full='%s'", $vin8), true);
		$row = $res['data'];
		foreach($likeVals as $filed=>$vals)
		{
			if(!empty($row[$filed]))
			{
				foreach(explode(',', $row[$filed]) as $tval)
				{
					if($filed == 'sma_passenger' && stripos($tval, '座')){
						if(preg_match('/([1-9]\d*)[\-\,\/]([1-9]\d*)座/', $tval, $match)){
							for($i = $match[1]; $i <= $match[2]; $i++){
								if(!in_array($i, $vals)) $vals[] = $i;
							}
						}else if(preg_match('/([1-9]\d*)座/', $tval, $match)){
							if(!in_array($match[1], $vals)) $vals[] =  $match[1];
						}
					}else if(!empty($tval) && !in_array($tval, $vals)){
						$vals[] = $tval;
					}
				}
				$likeVals[$filed] = $vals;
			}
		}

		if(in_array($wmi, $vwWmis) && !empty($likeVals['sma_engine']))
		{//大众奥迪3位发动机型号追加
			foreach($likeVals['sma_engine'] as $val)
			{
				if(strlen($val) == 4){
					$tval = substr($val, 0, 3);
					if(!in_array($tval, $likeVals['sma_engine'])) $likeVals['sma_engine'][] = $tval;
				}
			}
		}
		//比较文本内容
		foreach($likeVals as $filed=>$vals)
		{
			foreach($vals as $tempv)
			{
				switch($filed)
				{
					case 'sma_engine': 
						if(
							preg_match('/号:? *'.$tempv.'[^a-zA-Z0-9]/i', $ocrtxt, $tmatch) ||
							(strlen($tempv) > 2 && preg_match('/\n *'.$tempv.'/i', $ocrtxt, $tmatch))
						){
							$isVals[$filed] = $tempv;
						}
					break;
					case 'sma_kw':
						if(
							preg_match('/功率:? *'.$tempv.'/iu', $ocrtxt, $tmatch) || 
							preg_match('/'.$tempv.'kw/i', $ocrtxt, $tmatch) || 
							preg_match('/功[\x{4e00}-\x{9fa5}]:? *'.$tempv.'kw/iu', $ocrtxt, $tmatch)
						){
							$isVals[$filed] = $tempv;
						}
					break;
					case 'sma_cc':
						$tempv = (int) $tempv;
						$fv = str_replace('.', '\.', sprintf("%.3f", $tempv/1000));
						$fv2 = str_replace('.', '\.', sprintf("%.1f", $tempv/1000));
						if(
							preg_match('/排量:? *'.$tempv.'/i', $ocrtxt, $tmatch) || 
							preg_match('/'.$tempv.'ml/i', $ocrtxt, $tmatch) || 
							preg_match('/排[\x{4e00}-\x{9fa5}]:? *'.$tempv.'ml/iu', $ocrtxt, $tmatch) || 
							preg_match('/排量:? *'.$fv.'/i', $ocrtxt, $tmatch) || 
							preg_match('/排量:? *'.$fv2.'/i', $ocrtxt, $tmatch) || 
							preg_match('/[\n ]'.$fv.' *L\n/i', $ocrtxt, $tmatch) ||
							preg_match('/[\n ]'.$fv2.' *L\n/i', $ocrtxt, $tmatch)
						){
							$isVals[$filed] = $tempv;
						}
					break;
					case 'sma_passenger':
						if(
							preg_match('/人[\x{4e00}-\x{9fa5}]*:? *\n?'.$tempv.'/iu', $ocrtxt, $tmatch) || 
							preg_match('/位[\x{4e00}-\x{9fa5}]:? *\n?'.$tempv.'/iu', $ocrtxt, $tmatch) || 
							preg_match('/:? *'.$tempv.' *人/iu', $ocrtxt, $tmatch)
						){
							$isVals[$filed] = $tempv;
						}
					break;
				}
				if(!empty($isVals[$filed])) break;
			}
		}
		$isVals['sma_vin'] = $vin;
		return LibFc::ReturnData(true, $isVals);
	}

	/**
	  * @method 从电池提取型号
	  * @param  $vin string [必填] 车架号
	  * @param  $ocrtxt string [选填] 图片内容
	  * @remark 1. 按 VIN 前 8 位读取 stand_gxb_battery，使用 capacity、voltage、engine_b 反推 sma_gxb_code/sma_engine。
	  * @remark 2. 匹配前格式化 OCR 文本：转大写、去掉非字母数字/小数点字符，并删除 VIN 及常见 VIN 后缀片段，避免车架号数字干扰。
	  * @remark 3. capacity 按容量匹配，必须带 A/AH 单位；voltage 按电压匹配，必须带 V 单位。
	  * @remark 4. 规则值会去掉小数末尾 0，例如 78.40 按 78.4 匹配；OCR 漏小数点也可匹配，例如 78.4Ah 可匹配 784Ah。
	  * @remark 5. engine_b 唯一时直接用完整值或 / 分段值匹配；engine_b 不唯一时，按 / 拆分同位置电机，提取不同部分生成候选片段。
	  * @remark 6. 不同部分不足 4 位时，从差异位置向两侧扩展出所有 4 位候选；超过 4 位时先匹配完整差异，再逐步缩短到 4 位。
	  * @remark    例如 TZ210XSAE/TZ220XSAE 生成 Z210、210X、TZ21、10XS；TZ200XSAE/TZ210XS004 生成 00XSAE、10XS004 等。
	  * @remark 7. capacity、voltage、engine_b_diff 任一字段有命中，就用该字段命中的规则过滤掉未命中的规则；最终直接返回 matches，不区分单个或多个。
	  * @author AI
	  * @return 
	  */
	public function smaByBatteryTxt($vin, $ocrtxt)
	{
		$vin8 = substr($vin, 0, 8);
		$YpwVin = new YpwVin();
		$arr = [];
		//电池规则
		$res = $YpwVin->get('stand_gxb_battery', ['sgby_gxb_code sma_gxb_code', 'sgby_gxb_code_show sma_gxb_code_show', 'sgby_engine sma_engine', 'sgby_engine_b engine_b', 'sgby_capacity capacity', 'sgby_voltage voltage'], sprintf(" and sgby_vin1_8='%s' and (sgby_engine_b != '' or sgby_capacity > 0 or sgby_voltage > 0)", $vin8));
		$bregArr = $res['data'];
		if(empty($bregArr)) return LibFc::ReturnData(true, $arr);

		// 统一 OCR 文本格式，并剔除 VIN 相关数字，避免容量/电压被车架号误命中。
		$formatTxt = $this->formatBatteryRegTxt($ocrtxt, $vin);
		$engineArr = [];
		foreach($bregArr as $reg){
			$engine = $this->formatBatteryEngineVal($reg['engine_b']);
			if($engine != '') $engineArr[$engine] = $reg['engine_b'];
		}
		// engine_b 有多个不同规则时，预先计算每条规则可用于区分的电机差异片段。
		$engineDiffArr = count($engineArr) > 1 ? $this->getBatteryDiffVals(array_keys($engineArr)) : [];

		$matchArr = $bregArr;
		$arr['matches'] = $this->filterBatteryRegByNum($matchArr, 'capacity', $formatTxt, ['AH', 'A']);
		$hasMatch = false;
		if($arr['matches']){
			$hasMatch = true;
			$matchArr = $arr['matches'];
		}
		$arr['matches'] = $this->filterBatteryRegByNum($matchArr, 'voltage', $formatTxt, ['V']);
		if($arr['matches']){
			$hasMatch = true;
			$matchArr = $arr['matches'];
		}
		$arr['matches'] = $this->filterBatteryRegByEngine($matchArr, $formatTxt, $engineArr, $engineDiffArr);
		if($arr['matches']){
			$hasMatch = true;
			$matchArr = $arr['matches'];
		}
		$arr['matches'] = $hasMatch ? $matchArr : [];

		if(!empty($arr['matches'])){
			usort($arr['matches'], function($a, $b){
				if($a['score'] == $b['score']) return 0;
				return $a['score'] > $b['score'] ? -1 : 1;
			});
		}
		return LibFc::ReturnData(true, $arr);
	}

	/**
	  * @method 按容量/电压过滤电池规则
	  * @param  $regArr array [必填] 当前候选规则
	  * @param  $field string [必填] capacity 或 voltage
	  * @param  $formatTxt string [必填] 格式化后的 OCR 文本
	  * @param  $units array [必填] 允许匹配的单位
	  * @remark 当前字段只要有规则命中，就立即返回命中的规则，后续字段只计算这些候选。
	  * @author AI
	  * @copyright 
	  * @return array
	  */
	private function filterBatteryRegByNum($regArr, $field, $formatTxt, $units)
	{
		$matchArr = [];
		foreach($regArr as $reg){
			if(!$this->matchBatteryRegNum($reg[$field], $formatTxt, $units)) continue;
			$reg['score'] = (int) $reg['score'] + 1;
			$reg['match_fields'][$field] = $reg[$field];
			$matchArr[] = $reg;
		}
		return $matchArr;
	}

	/**
	  * @method 按驱动电机过滤电池规则
	  * @param  $regArr array [必填] 当前候选规则
	  * @param  $formatTxt string [必填] 格式化后的 OCR 文本
	  * @param  $engineArr array [必填] 全部驱动电机规则
	  * @param  $engineDiffArr array [选填] 多规则电机差异候选
	  * @remark 当前字段只要有规则命中，就立即返回命中的规则，后续不再保留未命中的规则。
	  * @author AI
	  * @copyright 
	  * @return array
	  */
	private function filterBatteryRegByEngine($regArr, $formatTxt, $engineArr, $engineDiffArr)
	{
		$matchArr = [];
		foreach($regArr as $reg){
			$engine = $this->formatBatteryEngineVal($reg['engine_b']);
			if($engine == '') continue;
			$matchFields = [];
			if(count($engineArr) == 1 && $this->matchBatteryEngineDirect($engine, $formatTxt)){
				$matchFields['engine_b'] = $reg['engine_b'];
			}else if(count($engineArr) > 1 && !empty($engineDiffArr[$engine])){
				foreach($engineDiffArr[$engine] as $diffVal){
					if(strlen($diffVal) >= 2 && stripos($formatTxt, $diffVal) !== false){
						$matchFields['engine_b'] = $reg['engine_b'];
						$matchFields['engine_b_diff'] = $diffVal;
						break;
					}
				}
			}
			if(!$matchFields) continue;
			$reg['score'] = (int) $reg['score'] + 3;
			foreach($matchFields as $k=>$v){
				$reg['match_fields'][$k] = $v;
			}
			$matchArr[] = $reg;
		}
		return $matchArr;
	}

	/**
	  * @method 格式化电池规则 OCR 文本
	  * @param  $txt string [必填] OCR 原文
	  * @param  $vin string [选填] 车架号
	  * @remark 保留字母、数字、小数点，并删除 VIN 及常见 VIN 后缀片段。
	  * @author AI
	  * @copyright 
	  * @return string
	  */
	private function formatBatteryRegTxt($txt, $vin = '')
	{
		$txt = strtoupper(strtr($txt, ["\r\n"=>"\n", 'O'=>'0', 'o'=>'0', '脳'=>'X']));
		$txt = preg_replace('/[^A-Z0-9\.]+/', '', $txt);
		$vin = strtoupper(preg_replace('/[^A-Z0-9]+/', '', (string) $vin));
		if($vin != ''){
			$vinVals = [$vin];
			foreach([12, 10, 8, 6] as $len){
				if(strlen($vin) >= $len) $vinVals[] = substr($vin, -$len);
			}
			foreach(array_unique($vinVals) as $v){
				if($v != '') $txt = str_replace($v, '', $txt);
			}
		}
		return $txt;
	}

	/**
	  * @method 格式化驱动电机规则值
	  * @param  $val string [必填] 驱动电机原值
	  * @remark 保留字母、数字和 /，用于完整匹配或分段比较。
	  * @author AI
	  * @copyright 
	  * @return string
	  */
	private function formatBatteryEngineVal($val)
	{
		return strtoupper(preg_replace('/[^A-Z0-9\/]+/', '', (string) $val));
	}

	/**
	  * @method 匹配容量/电压规则值
	  * @param  $val string [必填] 规则值
	  * @param  $formatTxt string [必填] 格式化后的 OCR 文本
	  * @param  $units array [必填] 允许匹配的单位
	  * @remark 规则值去掉小数尾 0，匹配时必须带单位，并兼容 OCR 漏小数点。
	  * @author AI
	  * @copyright 
	  * @return bool
	  */
	private function matchBatteryRegNum($val, $formatTxt, $units)
	{
		if($val === '' || $val === null || (float) $val <= 0) return false;
		$val = preg_replace('/(\.\d*?)0+$/', '$1', (string) $val);
		$val = preg_replace('/\.$/', '', $val);
		$floatVal = (float) $val;
		$vals = [
			(string) $val,
			(string) $floatVal,
			sprintf('%.1f', $floatVal),
			sprintf('%.2f', $floatVal),
			str_replace('.', '', sprintf('%.1f', $floatVal)),
			str_replace('.', '', sprintf('%.2f', $floatVal)),
		];
		foreach(array_unique($vals) as $v){
			$v = preg_replace('/[^0-9\.]+/', '', $v);
			if($v === '') continue;
			$matchVals = [$v];
			if(stripos($v, '.') !== false) $matchVals[] = str_replace('.', '', $v);
			foreach($units as $unit){
				foreach(array_unique($matchVals) as $matchVal){
					if(stripos($formatTxt, $matchVal.$unit) !== false) return true;
				}
			}
		}
		return false;
	}

	/**
	  * @method 直接匹配唯一驱动电机
	  * @param  $engine string [必填] 格式化后的驱动电机
	  * @param  $formatTxt string [必填] 格式化后的 OCR 文本
	  * @remark engine_b 唯一时使用，完整电机或 / 分段任一命中即可确认。
	  * @author AI
	  * @copyright 
	  * @return bool
	  */
	private function matchBatteryEngineDirect($engine, $formatTxt)
	{
		$full = str_replace('/', '', $engine);
		if($full != '' && stripos($formatTxt, $full) !== false) return true;
		foreach(preg_split('/\/+/', $engine) as $part){
			if(strlen($part) >= 4 && stripos($formatTxt, $part) !== false) return true;
		}
		return false;
	}

	/**
	  * @method 获取驱动电机差异候选
	  * @param  $engineArr array [必填] 格式化后的驱动电机数组
	  * @remark engine_b 多规则时使用，按 / 拆分同位置电机，提取能区分规则的差异候选。
	  * @author AI
	  * @copyright 
	  * @return array
	  */
	private function getBatteryDiffVals($engineArr)
	{
		$partRows = [];
		foreach($engineArr as $engine){
			$parts = preg_split('/\/+/', $engine);
			foreach($parts as $part){
				if(strlen($part) >= 4) $partRows[$engine][] = $part;
			}
		}

		$maxPartCount = 0;
		foreach($partRows as $parts){
			$maxPartCount = max($maxPartCount, count($parts));
		}
		$diffArr = [];
		for($i = 0; $i < $maxPartCount; $i++){
			$vals = [];
			foreach($engineArr as $engine){
				if(isset($partRows[$engine][$i])) $vals[$engine] = $partRows[$engine][$i];
			}
			if(count(array_unique($vals)) <= 1) continue;
			$diffs = $this->getBatteryCommonDiff($vals);
			foreach($diffs as $engine=>$diff){
				foreach($diff as $diffVal){
					if(strlen($diffVal) >= 2) $diffArr[$engine][] = $diffVal;
				}
			}
		}
		foreach($diffArr as $engine=>$diffs){
			$diffArr[$engine] = array_values(array_unique($diffs));
		}
		return $diffArr;
	}

	/**
	  * @method 提取同位置电机的不同部分
	  * @param  $vals array [必填] 同位置电机数组
	  * @remark 对同位置电机求共同前缀/后缀，中间不同部分交给 fillBatteryDiffVal 生成候选。
	  * @author AI
	  * @copyright 
	  * @return array
	  */
	private function getBatteryCommonDiff($vals)
	{
		$maxlen = 0;
		foreach($vals as $v){
			$maxlen = max($maxlen, strlen($v));
		}
		$bsamelen = 0;
		for($i = 0; $i < $maxlen; $i++){
			$chars = [];
			foreach($vals as $v){
				$chars[] = isset($v[$i]) ? $v[$i] : '';
			}
			if(count(array_unique($chars)) != 1) break;
			$bsamelen++;
		}
		$esamelen = 0;
		for($i = 1; $i <= $maxlen; $i++){
			$chars = [];
			foreach($vals as $v){
				$pos = strlen($v) - $i;
				$chars[] = $pos >= $bsamelen && isset($v[$pos]) ? $v[$pos] : '';
			}
			if(count(array_unique($chars)) != 1) break;
			$esamelen++;
		}

		$diffArr = [];
		foreach($vals as $engine=>$v){
			$diffLen = strlen($v) - $bsamelen - $esamelen;
			$diffArr[$engine] = $this->fillBatteryDiffVal($v, $bsamelen, $diffLen);
		}
		return $diffArr;
	}

	/**
	  * @method 生成差异片段匹配候选
	  * @param  $val string [必填] 单个驱动电机片段
	  * @param  $start int [必填] 差异开始位置
	  * @param  $len int [必填] 差异长度
	  * @remark 短于 4 位向两侧扩展；长于 4 位先完整匹配，再逐步缩短到 4 位。
	  * @author AI
	  * @copyright 
	  * @return array
	  */
	private function fillBatteryDiffVal($val, $start, $len)
	{
		$valLen = strlen($val);
		if($valLen <= 4) return [$val];

		$vals = [];
		if($len >= 4) $vals[] = substr($val, $start, $len);
		$maxWin = max(4, min($len - 1, 8));
		for($win = $maxWin; $win >= 4; $win--){
			if($len >= 4){
				$minStart = $start;
				$maxStart = $start + $len - $win;
			}else{
				$minStart = max(0, $start + $len - $win);
				$maxStart = min($start, $valLen - $win);
			}
			$baseStart = $start + (int) floor(($len - $win) / 2);
			$startArr = [];
			for($i = $minStart; $i <= $maxStart; $i++){
				$startArr[] = $i;
			}
			usort($startArr, function($a, $b) use ($baseStart){
				$adiff = abs($a - $baseStart);
				$bdiff = abs($b - $baseStart);
				if($adiff == $bdiff) return $b <=> $a;
				return $adiff <=> $bdiff;
			});
			foreach($startArr as $s){
				$vals[] = substr($val, $s, $win);
			}
		}
		return array_values(array_unique($vals));
	}

	public function comChar($gxbArr, $maxlen)
	{
		$comArr = [
			'bsame'=>'',	
			'bsamelen'=>0,	
			'esame'=>'',	
			'esamelen'=>0,	
			'char'=>[],	
		];
		//前面一截相同
		$bsameFlag = true;
		for($i = 0; $i < $maxlen; $i++)
		{
			$t = [];
			foreach($gxbArr as $gvk=>$gvv)
			{
				$t[] = isset($gvv['char'][$i])? $gvv['char'][$i]: '';
			}
			$t = array_unique($t);
			$comArr['char'][$i] = $t;
			if(count($t) == 1)
			{
				if($bsameFlag) $comArr['bsame'] .= $t[0];
			}
			else
			{
				$bsameFlag = false;
			}
		}
		if(count($gxbArr) > 1)
		{
			$esameFlag = true;
			for($i = 1; $i <= $maxlen; $i++)
			{
				$t = [];
				foreach($gxbArr as $gvk=>$gvv)
				{
					$t[] = isset($gvv['char'][$gvv['len']-$i])? $gvv['char'][$gvv['len']-$i]: '';
				}
				$t = array_unique($t);
				if(count($t) == 1)
				{
					if($esameFlag) $comArr['esame'] = $t[0].$comArr['esame'];
				}
				else
				{
					break;
				}
			}
		}
		$comArr['bsamelen'] = strlen($comArr['bsame']);
		$comArr['esamelen'] = strlen($comArr['esame']);
		return $comArr;
	}

	//检测字符串和ocr内容是否匹配
	public function matchGxbStr($ocrtxt, $vin, $gbk, $matchStr)
	{
		//LFV2A11J063037603  统一最后加换行
		$ocrtxt = $ocrtxt."\n";
		$arr = ['match'=>false];
		//排除车架号本身 LE43X8HB8LL100048=>BJ7138LL1=>8LL1
		if(stripos($vin, $matchStr) !== false) return $arr;
		
		//不转移\
		$treArr = ['.'=>'\.', '-'=>'\-', '+'=>'\+', '/'=>'\/', '('=>'\(', ')'=>'\)'];
		
		//最后一个字符串为空则加上换行
		if(substr($matchStr, -1) == ' ') $treArr[' '] = '[ \n]';

		$treg = $matchStr == $gbk? '/'.strtr($matchStr, $treArr).'/i': '/[^\n]+'.strtr($matchStr, $treArr).'/i';
		if(preg_match($treg, $ocrtxt, $match))
		{
			return ['match'=>true, 'format'=>strtoupper($matchStr), 'reg'=>$treg];
		}
		//LSGTC52M27Y060301 SGM141SE AT=>SGM141SEAT
		if(preg_match($treg, strtr($ocrtxt, [' '=>'']), $match))
		{
			return ['match'=>true, 'format'=>strtoupper($matchStr), 'reg'=>$treg];
		}
		return $arr;
	}


	/**
	  * @method 判断图片类型 0普通 1:行驶证  2:铭牌  -1无效图
	  * @param  $ocrtxt string [必填] 内容
	  * @param  $vin string [选填] 车架号
	  * @author soul
	  * @copyright 2020/9/7
	  * @return 
	  */
	public function getImgType($ocrtxt, $vin = '')
	{
		$len = LibFc::slen($ocrtxt);
		$type = 0;
		if($len >= 15 && $len < 22) $type = -1;
		if($type == 0 && stripos($ocrtxt, '摩托') !== false) $type = -1;
		$isCn = in_array(substr($vin, 0, 1), ['L', 'H'])? true: false;
		if($type == 0 && !empty($vin) && $len > 15)
		{
			//L的必须有一个中文
			if($isCn && !preg_match_all('/[\x{4e00}-\x{9fa5}]/u', $ocrtxt, $match))
				$type = -1;
		}

		if($type == 0)
		{
			//大概率是行驶证  "中华人民" 品牌丰田(ToYoTA)制造国中华人民共和国
			$reArr = ["使用性质", "行驶证", "机动车行驶", "注册日", "发证日"];
			if(stripos(str_replace($reArr, '#行驶证#', $ocrtxt), '#行驶证#') !== false)
				$type = 1;
		}

		if($type == 0)
		{
			//大概率是无效图
			$reArr = ['汽修宝','智配', '跑街', "零零汽",'扫码结果', '车架号查询', "智能查询", "开思", "智能目录", "EPC结构图", "经销商最低", "主管税务", "商检单号", "纳税人识别号", "发票代码", "车架号解码","车型外现", "车架号解析", "准确率", "事故记录", "维修记录", "原厂数据", "配置详情", "自动回复", "查询接口", "免征查询", "开票日期", "销货单位", "机打号码", "发票号码", "价税合计","报价单",'尊敬','搜配','架号为','链接','车主','paojd',"解析更精","拍完整","任务","接车","基本信息","维修", "车型详情", "提示","车身","扫码"];
			if(stripos(str_replace($reArr, '#无效图#', $ocrtxt), '#无效图#') !== false)
				$type = -1;
		}

		if($type == 0)
		{
			//大概率是行驶证  "中华人民" 品牌丰田(ToYoTA)制造国中华人民共和国
			$reArr = ["使用性质", "行驶证", "机动车行驶", "车辆类型", "公安局", "交通警察", "安局交通", "注册日", "发证日", "车辆识别代号", "营运"];
			if(stripos(str_replace($reArr, '#行驶证#', $ocrtxt), '#行驶证#') !== false)
				$type = 1;
		}

		if($type == 0)
		{
			//大概率是铭牌
			$reArr = ['整车型号', '净功率', '制造年月', "生产日期", "总质量", "发动机型号", "整备质量", "整备质", "载质量", "座位数", "乘坐人数", "制造国", "允许总质"];
			if(stripos(str_replace($reArr, '#铭牌#', $ocrtxt), '#铭牌#') !== false)
				$type = 2;
		}

		if($type == 0)
		{
			//大概率是无效图
			$reArr = ['来源于网', '出险', '工单', '购物车','保养','时间',"仅供","参考", '落地', '配置','详情', '报废','估值','扫码','识别', "发票",
				"发布","采购", "宜配网", "跑街令", "查询历史", "车型选择", "保险日期", "扫码结", "保险人", "投保人", "投保申请", "参考用量", "收购","价格", 
				"产品目录", "更多","适配", "联系","电话", "自动识别", "历史","查询","VN码输入", "车型","选择", "4S店价","周期", "按车型", "购买方", "销售单位", 
				"银行", "付款", "转账", "开票","发送", "搜索", "微信", "联系人", "联系地址","配件","搜索","车辆配置","事故","赔款","投保","报价",
				"智能","聊天","订单","施工","复制","发货","撤回","星期","上午","下午","早上","晚上","已读","消息","查找","配件","按住","说话",
				"周一","周二","周三","周四","周五","周六","周日","收藏","主组","保险","分词","维修","首页","编辑","列表","没有","通话","语音","批发","采购",
				"筛选","排序","重复","手机","开始","显示","稍等","过来","汽配","快递","物流","单号","销售","适用","最近","公司","跟踪","科技","回复","生成","车身","五门","车组","召回"
				,"多少","四驱","钱","图册","客户","翻译","QQ","服务",'零零','汽修','系绕','管理','人工','新车','昨天','电脑','信息','提车','联保','点击','说明','具体','用户',"纠错","目录"
				,"VIN码"
			];
			if(stripos(str_replace($reArr, '#无效图#', $ocrtxt), '#无效图#') !== false)
				$type = -1;
		}
		
		//QQ：00:00:00 ~ 23:59:59   
		if($type == 0 && preg_match_all('/([01]\d|2[0-3]):?[0-5]\d:?[0-5]\d\s/', $ocrtxt, $match))
		{
			foreach($match[0] as $mv)
			{
				//至少有一个冒号
				if(stripos($mv, ':'))
				{
					$type = -1;
					break;
				}
			}
		}

		//微信 9:16  昨天 9:16 | 2022年6月18日 20:32 | 星期四 9:00
		if($type == 0 && preg_match('/([01]\d|2[0-3]):[0-5]\d\s/', $ocrtxt, $match))
		{
			$type = -1;
		}
		
		if($type == 0)
		{
			$reArr = ['型号', '功率', '制造', "生产"];
			if(stripos(str_replace($reArr, '#铭牌#', $ocrtxt), '#铭牌#') !== false)
				$type = 2;
		}
		if($type == 0)
		{
			//大概率是VIN
			if(preg_match('/[0-9a-zA-Z]{3,15}/', $ocrtxt, $match) && $len < 20){
				$type = -1;
			}else if(preg_match('/[0-9a-zA-Z]{15,}/', $ocrtxt, $match)){
				$xing = ['★', '☆'];
				$ocrtxt = str_replace($xing, '☆', $ocrtxt);
				//挡风玻璃 ☆LSGXE83L3FD036663☆
				if(
					$len < 50 ||
					(substr_count($ocrtxt, '☆') > 1) || 
					(substr_count($ocrtxt, '☆') == 1 && $len < 60)
				)
				{
					$type = -1;
				}
			}
		}

		if($type == 0)
		{
			$reArr = ["行驶", "机动车", "注册", "发证","行驳证", "小型"];
			if(stripos(str_replace($reArr, '#行驶证#', $ocrtxt), '#行驶证#') !== false)
				$type = 1;
		}

		if($type == 0)
		{
			if(!$isCn && $len >= 12 && $len < 60)
				$type = -1;
		}
		return $type;
	}


	/**
	  * @method 从VIN图片内容匹配发动机功率
	  * @param  $gxb string [必填] 工信部型号
	  * @param  $engine string [必填] 发动机
	  * @param  $othArr array [选填] stand_gxbengine_kw 其他字段信息
	  * @author soul
	  * @copyright 2020/9/7
	  * @return 
	  */
	public function engineKw($gxb, $engine, $othArr = [])
	{
		if($this->debug) echo $gxb, '=>', $engine, '<br>';
		$arr = [
			'gxb'=>$gxb,
			'engine'=>$engine,
			'ckw_ed_kw'=>0,	
			'ckw_jgl_kw'=>0,
			'msg'=>'',
			'allkw'=>[],
			'match'=>[],
		];
		$YpwVin = new YpwVin();
		$OthPdfOcr = new OthPdfOcr();
		$formatGxb = LibFc::FormatNum($gxb);
		$engine = LibFc::Escape($engine);

		//600w有可能的功率值
		$where = sprintf(" and sma_gxb_code = '%s' AND sma_engine = '%s' and sma_kw != '' group by sma_kw", $formatGxb, $engine);
		$res = $YpwVin->get('stand_model_any', ['sma_kw'], $where);
		$arr['allkw'] = array_column($res['data'], 'sma_kw');
		//是否已经提供的表格功率值
		if(!empty($othArr['ckw_txt_kw'])) $arr['allkw'] = array_merge($arr['allkw'], explode(',', $othArr['ckw_txt_kw']));
		
		//格式化功率
		foreach($arr['allkw'] as $k=>$v)
		{
			//63.5(汽油) 63.5(57.2)
			if(preg_match('/^\d+(\.\d*)?/', $v, $match))
			{//避免数据异常
				if($match[0] < 500) $arr['allkw'][$k] = (float) $match[0]; 
			}else{
				unset($arr['allkw'][$k]);
			}
		}
		$arr['allkw'] = array_values(array_unique($arr['allkw']));
		if(empty($arr['allkw'])) 
		{
			$arr['msg'] = 'nokw#无功率值';
			return LibFc::ReturnData(true, $arr);
		}

		//从小到大排序
		rsort($arr['allkw']);
		$maxKw = $arr['allkw'][0];
		//找出所有铭牌样例图片
		$where = sprintf(" and sma_gxb_code = '%s' AND sma_engine = '%s' and (vii_imgocr LIKE '%%kw%%' OR vii_imgocr LIKE '%%功率%%') group by vii_vin", $formatGxb, $engine);
		$viires = $YpwVin->get('stand_model_any JOIN stand_vin_imgs ON sma_vin = vii_vin', ['vii_vin, vii_imgocr'], $where);
		if(empty($viires['data']))
		{
			$arr['msg'] = 'noimg#无功率图';
			return LibFc::ReturnData(true, $arr);
		}
		$regArr = [
			'ckw_ed_kw'=>[
				'/定功[^0-9]{0,10}(\d+(\.\d*)?)[ \n]*KW/iUs',
				'/大功[^0-9]{0,10}(\d+(\.\d*)?)[ \n]*KW/iUs',
				'/定功[^0-9]{0,10}KW[^0-9]{0,3}(\d+(\.\d*)?)[ \n]/iUs',
				'/大功[^0-9]{0,10}KW[^0-9]{0,3}(\d+(\.\d*)?)[ \n]/iUs',
			],
			'ckw_jgl_kw'=>[
				'/净功[^0-9]{0,10}(\d+(\.\d*)?)[ \n]*KW/iUs',
				'/净功[^0-9]{0,10}KW[^0-9]{0,3}(\d+(\.\d*)?)[ \n]/iUs',
			],
		];
		$reArr = ["\r\n"=>"\n"];

		$total = [];
		//提取匹配的值
		foreach($viires['data'] as $vi)
		{
			$ocrtxt = $vi['vii_imgocr'];
			//铭牌图
			$imgType = $this->getImgType($ocrtxt, $vi['vii_vin']);
			if($imgType != 2) continue;

			$ocrtxt = strtr($ocrtxt, $reArr);
			//if($this->debug) echo $vi['vii_vin'], '=>', $ocrtxt, '<br>';
			foreach($regArr as $filed=>$regs)
			{
				foreach($regs as $reg)
				{
					if(preg_match($reg, $ocrtxt, $match))
					{
						$kw = (float) $match[1];
						$xcRate = abs($kw-$maxKw)/$maxKw;
						if($xcRate < 0.2)
						{
							//if($this->debug) echo $filed, '=>', $kw,'<br>';
							//SC7106A4B  51.2 =>51
							$arr['match'][$filed][(string)$kw] += 1;
							$total[$filed] += 1;
						}
					}
				}
			}
		}
		$kwFileds = ['ckw_ed_kw', 'ckw_jgl_kw'];
		//按出现的次数从多到小
		foreach($kwFileds as $filed)
		{
			if(!empty($arr['match'][$filed])) arsort($arr['match'][$filed]);
		}

		if($this->debug) echo '表格功率: ', implode(',', $arr['allkw']),'<br>';
		if($this->debug) echo '图片数:', count($viires['data']), '=>OCR功率: ', json_encode($arr['match']), '<br>';
		
		//首先判断额定功率
		foreach($kwFileds as $filed)
		{
			//第一轮包含关系大概率确认 
			if(empty($arr['match'][$filed]) || !empty($arr[$filed])) continue;
			foreach($arr['match'][$filed] as $tkw=>$tcount)
			{
				if(!empty($arr[$filed])) break;
				$tkw = (float) $tkw;
				if(!in_array($tkw, $arr['allkw']) || $tcount < 5) continue;
				$wrate = round($tcount/$total[$filed], 2); //必须加上占比 HFC6460M1TF=>4G93D  不能直接判断包含 QCJ6480MJ 就确定功率类型
				if($wrate > 0.8 || ($tcount >= 10 && $wrate > 0.5))
				{//占比>80% 或者 数量大于10占比达到50%  
					$arr[$filed] = $tkw;
					if($this->debug) echo $filed, '=>包含：', $tkw, '=>出现：', $tcount, ' ok<br>';
					break;
				}
			}
		}

		
		//第二轮处理 取比重最高的一个 进行比较
		foreach($kwFileds as $filed)
		{
			if(empty($arr['match'][$filed]) || !empty($arr[$filed])) continue;
			foreach($arr['match'][$filed] as $tkw=>$tcount)
			{
				$wrate = round($tcount/$total[$filed], 2);
				if($tcount > 100 || ($tcount >= 10 && $wrate > 0.5))
				{
					$arr[$filed] = $tkw;
					if($this->debug) echo $filed, '=>非包含：', $tkw, '=>出现：', $tcount, '=>占比：', $wrate*100, '% ok<br>';
				}
				//只匹配第一个
				break;
			}
		}

		//净功率确认，额定功率未确认，净功率不在文本功率里，文本功率唯一 文本功率不在匹配的净功率里 HFC6460M1TF=>4G93D
		if(empty($arr['ckw_ed_kw']) && !empty($arr['ckw_jgl_kw']))
		{
			$iskws = [];
			foreach($arr['allkw'] as $tk=>$tv)
			{
				if($arr['ckw_jgl_kw'] != $tv)
				{
					$iskws[] = $tv;
				}
			}
			if(count($iskws) == 1 && $iskws[0] > $arr['ckw_jgl_kw'] && empty($arr['match']['ckw_jgl_kw'][$iskws[0]]))
			{
				if($this->debug) echo '净功率已确认 文本功率唯一 额定功率 ok<br>';
				$arr['ckw_ed_kw'] = $iskws[0];
			}
		}
		//FV7166FAMGG 77kw 同时存在于额定功率 和 净功率
		if($arr['ckw_ed_kw'] == $arr['ckw_jgl_kw'] && !empty($arr['match']['ckw_ed_kw'][$tkw]) && !empty($arr['match']['ckw_jgl_kw'][$tkw]))
		{
			$arr['msg'] = 'same#同时存在';
			$arr['ckw_ed_kw'] = '';
			$arr['ckw_jgl_kw'] = '';
		}

		if(empty($arr['ckw_ed_kw']) && empty($arr['ckw_jgl_kw']))
		{
			if(empty($arr['msg'])) $arr['msg'] = 'nomatch#无匹配';
		}
		else if(!empty($arr['ckw_ed_kw']) && !empty($arr['ckw_jgl_kw']))
		{
			$rate = round($arr['ckw_jgl_kw']/$arr['ckw_ed_kw'], 2);
			if($rate < 1 && $rate >= 0.8)
			{
				$arr['msg'] = 'ok';
			}
			else
			{
				$arr['ckw_ed_kw'] = '';
				$arr['ckw_jgl_kw'] = '';
				$arr['msg'] = 'errrate#比例不符';
			}
		}
		else if(empty($arr['ckw_ed_kw']))
		{
			$arr['msg'] = 'noedkw#额定功率无法确认';
		}
		else if(empty($arr['ckw_jgl_kw']))
		{
			$arr['msg'] = 'nojglkw#净功率无法确认';
		}
		return LibFc::ReturnData(true, $arr);
	}

	/**
	  * @method VIN+型号+OCR  确认整备质量、载客
	  * @param  $vin string [必填] 
	  * @param  $gxb string [必填] 
	  * @param  $ocrtxt string [必填] 
	  * @param  $comFileds array [选填] 目前仅支持整备质量 ['sma_weight']
	  * @author soul
	  * @copyright 2020/9/7
	  * @return 
	  */
	public function matchAnyByVinGxbTxt($vin, $gxb, $ocrtxt, $comFileds = ['sma_weight', 'sma_passenger'])
	{
		$arr = [];
		$replace = ["（"=>'(',"）"=>')',"\r\n"=>"\n", '：'=>':'];
		$ocrtxt = strtr($ocrtxt, $replace);
		if(strlen($ocrtxt) < 10) return LibFc::ReturnData(true, $arr);
		$imgType = $this->getImgType($ocrtxt, $vin);
		if(!in_array($imgType, [1,2])) return LibFc::ReturnData(true, $arr);

		$YpwVin = new YpwVin();
		$res = $YpwVin->get('gxb_params join gxb_base on gb_id=gp_gb_id', ['gp_gl_code', 'gp_value'], sprintf(" and gb_gxb_code='%s' and gp_gl_code in('passenger', 'maxweight', 'weight') group by gp_gl_code, gp_value order by gp_gl_code, gp_value", LibFc::FormatNum($gxb)));
		$merVals = [];
		foreach($res['data'] as $v){
			if(!empty($v['gp_value'])) $merVals[$v['gp_gl_code']][] = $v['gp_value'];
		}

		
		if(in_array('sma_weight', $comFileds) && $merVals['weight']){
			if(count($merVals['weight']) == 1){
				$arr['sma_weight'] = (int) $merVals['weight'][0];
			}else{
				//多值情况
				foreach($merVals['weight'] as $tempv){
					if(!empty($tempv) && (preg_match('/质量:? *'.$tempv.'/i', $ocrtxt, $tmatch) || preg_match('/\n *'.$tempv.' *(k|[a-z]?g|\n)/i', $ocrtxt, $tmatch) || preg_match('/kg\)?:?'.$tempv.'/i', $ocrtxt, $tmatch))){
						$arr['sma_weight'] = $tempv;
						break;
					}
				}

				//通过总质量反推
				if(!$arr['sma_weight'] && count($merVals['weight']) == count($merVals['maxweight'])){
					sort($merVals['weight']); sort($merVals['maxweight']);
					foreach($merVals['maxweight'] as $tk=>$tempv){
						if(!empty($tempv) && (preg_match('/质量:? *'.$tempv.'/i', $ocrtxt, $tmatch) || preg_match('/\n *'.$tempv.' *(k|[a-z]?g|\n)/i', $ocrtxt, $tmatch) || preg_match('/kg\):?'.$tempv.'/i', $ocrtxt, $tmatch))){
							$arr['sma_weight'] = $merVals['weight'][$tk];
							break;
						}
					}
				}
			}
		}
		if(in_array('sma_passenger', $comFileds) && $merVals['passenger']){
			if(count($merVals['passenger']) == 1){
				$arr['sma_passenger'] = $merVals['passenger'][0];
			}else{
				//多值情况
				foreach($merVals['passenger'] as $tempv){
					if(!empty($tempv) && (
						preg_match('/人[\x{4e00}-\x{9fa5}]*:? *\n?'.$tempv.'/iu', $ocrtxt, $tmatch) || 
						preg_match('/位[\x{4e00}-\x{9fa5}]:? *\n?'.$tempv.'/iu', $ocrtxt, $tmatch) || 
						preg_match('/:? *'.$tempv.' *人/iu', $ocrtxt, $tmatch)
					))
					{
						$arr['sma_passenger'] = $tempv;
						break;
					}
				}
			}
		}
		return LibFc::ReturnData(true, $arr);
	}

	/**
	  * @method 提取发动机号码
	  * @param  $vin string [必填] 
	  * @param  $imgType string [必填] 0普通 1:行驶证 2:铭牌 -1无效图
	  * @param  $ocrtxt string [必填] 
	  * @author soul
	  * @copyright 2020/9/7
	  * @return 
	  */
	public function matchEng6($vin, $imgType, $ocrtxt, $vinYear = 0){
		$eng6flag = -1;
		$eng6s = [];
		$vin = LibFc::FormatVin($vin);
		if(empty($vinYear)) $vinYear = LibFc::VinYear($vin);
		$ocrtxt = strtr($ocrtxt, ['.'=>'']);
		if($imgType == 2){
			if(preg_match_all('/机序列号.*?([0-9a-zA-Z]{2}\d{4,})/i', $ocrtxt, $match)){
				foreach($match[1] as $eng6){
					$eng6 = strtoupper(substr($eng6, -6));
					if(strlen($eng6) == 17 || stripos($vin, $eng6) !== false) continue;
					$eng6s[] = $eng6;
				}
				$eng6s = array_merge($eng6s, $match[1]);
			}
		}else{
			if($imgType == 1){
				//找VIN后的
				$temptxt = [];
				$flag = '';
				$hasEng6Txt = false;
				foreach(explode("\n", $ocrtxt) as $row){
					if($flag == '' && preg_match('/[0-9a-zA-Z]{16,18}/i', $row, $match)){
						similar_text($vin, LibFc::FormatVin($match[0]), $percent);
						if($percent > 50) $flag = 'vin';
						continue;
					}
					if($flag == 'vin'){
						if(stripos($row, '发证') !== false || stripos($row, '注册') !== false || stripos($row, '日期') !== false || preg_match('/\d{4}\-\d{1,2}\-\d{1,2}/i', $row, $match)){
							break;
						}
						if(!$hasEng6Txt && preg_match('/[0-9a-zA-Z\_\-\#]{6,25}/i', $row, $match)){
							$hasEng6Txt = true;
						}
						$temptxt[] = $row;
					}
				}
				if($hasEng6Txt) $ocrtxt = implode("\n", $temptxt);
			}
			
			$regs = [
				['reg'=>'/机号码[\s]*([0-9a-zA-Z\_\-\#]{6,25})/i', 'stop'=>1],	
				['reg'=>'/EngineNo[\s]*([0-9a-zA-Z\_\-\#]{6,25})/i', 'stop'=>1],	
				['reg'=>'/([0-9a-zA-Z\_\-\#]{6,25})/i', 'stop'=>0],	
			];
			$wmi = substr($vin, 0, 3);
			$engs = [];
			if(in_array($wmi, ['WBA','WMW','MMF','WBS','WBY','SAL']) && $vinYear >= 2024){
				$YpwBase = new YpwBase();	
				$res = $YpwBase->get('stand_model_any', ['distinct sma_engine'], sprintf(" and sma_vin1_8='%s' and sma_vin10_12='%s' and sma_engine != '' order by length(sma_engine) desc", substr($vin, 0, 8), substr($vin, 9, 3)));
				$engs = array_column($res['data'], 'sma_engine');
			}
			foreach($regs as $regv){
				if(preg_match_all($regv['reg'], $ocrtxt, $match)){
					foreach($match[1] as $eng6){
						$flag = false;
						foreach($engs as $eng){//2024年10月以后，宝马进口 路虎进口 去掉发动机的字符串
							$teng = substr($eng6, -strlen($eng));
							if($teng == $eng){
								$teng6 = substr($eng6, 0, -strlen($eng));
								if(strlen($teng6) >= 6 && stripos($vin, $teng6) !== false){
									$eng6s[] = substr($teng6, -6);
									$flag = true;
								}
								break;
							}
						}
						if($flag && $vinYear > 2025) break;
						
						//可能是车架号
						$eng6 = strtoupper(substr($eng6, -6));
						if(strlen($eng6) == 17 || stripos($vin, $eng6) !== false) continue;
						if(preg_match('/\d/', $eng6, $match2) && substr_count($eng6, '-') < 2 
							&& substr_count($eng6, '_') < 2 && substr_count($eng6, '#') < 2
							&& !preg_match('/\d{3,}MM/', $eng6, $match2) && !preg_match('/\d{3,}KG/', $eng6, $match2) && !preg_match('/\d{3,}ML/', $eng6, $match2)
						){
							$eng6s[] = $eng6;
						}
					}
					if($eng6s && $regv['stop']) break;
				}
			}
		}
		return LibFc::ReturnData(true, ['eng6s'=>$eng6s]);
	}


	/**
	  * @method VIN+型号+OCR  确认整备质量、载客
	  * @param  $vin string [必填] 
	  * @param  $gxb string [必填] 
	  * @param  $ocrtxt string [必填] 
	  * @param  $comFileds array [选填] 目前仅支持整备质量 ['sma_weight']
	  * @author soul
	  * @copyright 2020/9/7
	  * @return 
	  */
	public function matchBatteryByTxt($ocrtxt)
	{
		$arr = [];
		$replace = ["（"=>'(',"）"=>')',"\r\n"=>"\n", '：'=>':', '／'=>'/', '×'=>'X', '客量'=>'容量', '电动机'=>'电机','O'=>'0','o'=>'0'];
		$ocrtxt = strtr($ocrtxt, $replace);
		if(strlen($ocrtxt) < 10) return LibFc::ReturnData(true, $arr);
		//动力电池系统额定电压456.14V 动力电池系统额定容量78.1Ah 
		$arr = [];
		//电压/额定容量682.6V/120A
		if(preg_match('/[^\d](\d{3,}(?:\.\d+)?) *V *\/ *(\d+(?:\.\d+)?) *[Ah]+[\n]?/ui', $ocrtxt, $match) 
			|| preg_match('/容量[^\d]*[:\s]*(\d{3,}(?:\.\d+)?) *V *\/? *(\d+(?:\.\d+)?)[ \n]*A/ui', $ocrtxt, $match)
			|| (stripos($ocrtxt, '容量') && preg_match('/[:\s](\d{3,}(?:\.\d+)?) *\/? *(\d+(?:\.\d+)?) *V\/ *A/ui', $ocrtxt, $match)) 
		){//624V/117.0Ah
			$arr['voltage'] =  round((float) $match[1], 2);
			$arr['capacity'] =  round((float) $match[2], 2);
		}
	
		//额定电压：378（1C
		if(empty($arr['voltage']) && (preg_match('/电压[:\s]*(\d{3,}(?:\.\d+)?) *(?:V|\s|\()/ui', $ocrtxt, $match) 
			|| preg_match('/电压\(V\)[:\s]*(\d{3,}(?:\.\d+)?)/ui', $ocrtxt, $match) 
			|| (stripos($ocrtxt, '电压') && preg_match('/\s(\d{3,}(?:\.\d+)?)\n*V(?:\s|$)/ui', $ocrtxt, $match))
		)){
			$arr['voltage'] =  round((float) $match[1], 2);
		}
		if(empty($arr['capacity']) && (preg_match('/容量[:\s]*(\d+(?:\.\d+)?) *Ah?/ui', $ocrtxt, $match) 
			|| preg_match('/容量\(Ah?\)[:\s]*(\d+(?:\.\d+)?) *[^\/V]*/ui', $ocrtxt, $match)
			|| (stripos($ocrtxt, '容量') && preg_match('/\s(\d+(?:\.\d+)?) *Ah?(?:\s|$)/ui', $ocrtxt, $match))
			|| preg_match('/[:\s](\d+(?:\.\d+)?) *Ah/ui', $ocrtxt, $match) 
			|| preg_match('/\n(\d+(?:\.\d+)?)\n*Ah\n/ui', $ocrtxt, $match) 
			|| preg_match('/容量[:\s]*(\d+(?:\.\d+)?) *[^\/V]*(?:\s|\(|$)/ui', $ocrtxt, $match) 
		)){
			$arr['capacity'] =  round((float) $match[1], 2);
		}
		
		if($arr['capacity'] > 1000){
			while ($arr['capacity'] >= 1000) {
				$arr['capacity'] =  round((float) $arr['capacity'] /10, 2);
			}
		}
		if($arr['voltage'] > 1000){
			while ($arr['voltage'] >= 1000) {
				$arr['voltage'] = round((float) $arr['voltage'] /10, 2);
			}
		} 
		//驱动电机型号:TZ264XYAKF/TZ264XYBKF 电动机型号:ZT115572  动机型号/排量/最大净功率:
		$regArr = [
			'/电机型号[:\s\-\/]*([A-Z0-9][A-Z0-9\/\#\*\-]+)/ui',	
			'/电机型号[^\n]+\n([A-Z0-9][A-Z0-9\/\#\*\-]+)\n/ui',	
			'/\n([A-Z0-9][A-Z0-9\/\#\*\-]+)\n电机型号/ui',	
		];
		foreach($regArr as $reg){
			if(preg_match($reg, $ocrtxt, $match)){
				$val = strtoupper(preg_replace('/^[\s\/\-_.]+|[\s\/\-_.]+$/u', '', $match[1]));
				if(stripos($val, 'kw') !== false || stripos($val, 'kg') !== false || ($arr['voltage'] && stripos($val, (string) $arr['voltage']) !== false) || ($arr['capacity'] && stripos($val, (string) $arr['capacity']) !== false)) continue;
				$arr['engine_b'] = $val;
				break;
			}
		}
		return LibFc::ReturnData(true, $arr);
	}
}
