Current scoring arithmetic and rounding - PHP Online

Form of PHP Sandbox

Enter Your PHP code here for testing/debugging in the Online PHP Sandbox. As in the usual PHP files, you can also add HTML, but do not forget to add the tag <?php in the places where the PHP script should be executed.

Name: Current scoring arithmetic and rounding fullscreencopydownloadembedprint


Your result can be seen below.

Result of php executing





Full code of Current scoring arithmetic and rounding.php

  1. <?php
  2.  
  3. $receiptTextsAndAssertions = [
  4.  
  5.     0 => [
  6.         'text' => '"dm\ndm-drogerle markt\nKirchhellener Stra\u00dfe 142\nHIER BIN ICH MENSCH\n46145 Oberhausen\n0208/62189892\n08. 03.2021 18:27 2107/2 239059/1 0583\nAok Seesand Peeling WeiB.Tee\n1,95 1\nAok Seesand Peeling WeiB. Tee\n1.95 1\nMixa Bodylotion Hyaluro fresh\n3,75 1\nWC-Frisch Duo Stein Zitrus NF\n1,15 1\nWC-Frisch Duo Stein Zitrus NF\n1,15 1\nSaptil mit B\u00fcrste 200ml\n1,95 1\nZwischensumme\n11,90\nEinsteckalbum 10x15 36 Bilder\n1,45 1\nWC-Frisch Duo Stein Zitrus OR\n1,15 1\nFisherman Pro Fresh Blueberry\n1,15 2\nAok Seesand Peeling WeiB.Tee\n1,95 1\nZwischensumme\n17,60\ndm-Rabatte auf rabattf\u00e4hige Antikel\nPocketalbum Gratis\n-1,45\n4 X Coupon 15% Gesicht\n-1,43\nSUMME EUR\n14,72\nKARTENZAHLUNG EUR\n-14,72\nMwSt-Satz\nBrutto\nNetto\nMwSt\n1=19,00%\n13,57\n11.40\n2.17\n2=7,00%\n1,15\n1,07\n0,08\nIhre PAYBACK Kartennr XXXXXXXXX5273\nPunktestand vor Einkauf: 184\nDieser Punktestand entspricht: 1,84 EUR\nF\u00fcr diesen Einkauf erhalten Sie von dm\nPAYBACK Punkte auf bunktef\u00e4hige Artikel\nBasis-Punkte\n7\u00b0P\n\u00d6ffnungszeiter auf dm. de\nSteuer-Nr. : 34092/30007\n-K-U-N-D-E-N-B-E-L-E-G\nTerminal ID :\n65052321\nTA-Nr 702094\nBNr 9791\nKartenzahlung\ngirocard\nEUR 14,72\nPAN\nKante 8\ng\u00fcltig bis\n##/##\nEMV-AID\nA0000003591010028001\n0511052321\nVU-Nr.\nGenehmigungs-Nr\n168743\nDatum 08.03.21\n18:27 Uhr\nx** Zahlung erfolgt ***\nBITTE BELEG AUFBEWAHREN\n"',
  7.         'assert_date' => '2021/03/08',//'08.03.2021',  //but will be '08.03.2021' in source text
  8.         'assert_time' => '18:27',
  9.         'assert_mixa' => true,
  10.         //'assert_fuzzy_coupons' => false,
  11.     ],
  12.     
  13.     //I HAVE REMOVED "mixa" keywords and retailer keywords
  14.     1 => [
  15.         'text' => '"Mein Drogeriemarkt\n26.03.2021 14:30 Bed.Nr.:119 Kasse 02\nKunde:\n********\n70662\nApp\n3600551026565 HYALURO LOTION\n\u20ac3,99 A\n4002448044116 CLEARASIL WASCHGEL\n\u20ac3,79 A\n* 8X 4305615185255 WINSTON K MEN\u00dc JUNIO \u20ac0,19\n\u20ac1,52 B\n* 2X 4305615185279 WINSTON K MEN\u00dc LACHS \u20ac0,19\n\u20ac0,38 B\n2X 4305615185286 WINSTON K MEN\u00dc LAMM/ \u20ac0,19\n\u20ac0,38 B\n* 2X 4305615185330 WINSTON K MENU WELLN \u20ac0,19\n\u20ac0,38 B\n2X 4305615311555 WINSTON K MEN\u00dc MSC\n\u20ac0,19\n\u20ac0,38 B\n4305615416038 GP NUSS CASHEWKERNE\n\u20ac1,99 B\n4305615650845 ENERBIO ROTE B. SAFT\n\u20ac0,89 A\n*\n4305615693019 GP FRUCHTSCHNITTE VI\n\u20ac0,39 B\n* 2X 4305615730363 WINSTON K MEN\u00dc HUHN/ \u20ac0,19\n\u20ac0,38 B\n*\n2X 4305615730370 WINSTON K MEN\u00dc LACHS \u20ac0,19\n\u20ac0,38 B\nZwischensumme\n\u20ac14,85\nIhre Coupon-Ersparnisse heute:\nCoupon GP Riegel\n9823277110491 \u20ac-0,39\n10% AUF ALLES COUPON\n9823219220103 \u20ac-1,45\nMit Coupons gespart:\n\u20ac-1,84\nTotal\n\u20ac13,01\nBar\n\u20ac13,01\nR\u00fcckgeld (Bar)\n\u20ac0,00\nMWST Gruppe\nNetto\nMWST\nTotal\nA MWST A\n19%\n6,57\n1,25\n7,82\nB MWST B\n7%\n4,85\n0,34\n5,19\nMwSt Total\n11,42\n1,59\n13,01\nTSE Transaktion:\n72209\nTSE-Start:\n2021-03-26T13:29:51.000Z\nEnder\n"',
  16.         'assert_date' => '2021/03/26',//'26.03.2021',
  17.         'assert_time' => '14:30',
  18.         'assert_mixa' => true,
  19.         //'assert_fuzzy_coupons' => 100,
  20.     ],
  21. ];
  22.  
  23.  
  24. foreach($receiptTextsAndAssertions as $textAndAssertions)
  25. {
  26.     $cleanText = json_decode($textAndAssertions['text']);
  27.  
  28.     //chop into lines
  29.     $lines = preg_split('|\r{0,1}\n|', $cleanText);
  30.     
  31.     VAR_DUMP($lines);
  32.     
  33.     $verifier = new TextVerifier($lines);
  34.     
  35.     $validityScore = $verifier->getValidityScore();
  36.     
  37.     //mxa.php does this (so let's check)
  38.     $rounded_validityScore = substr(round($validityScore), -4, 2);
  39.     
  40.     echo "validityScore is: {$validityScore}  --  roundedValidityScore is: {$rounded_validityScore}" . PHP_EOL;
  41.     
  42. }
  43.  
  44.  
  45.  
  46. //----class from here----------------
  47.  
  48. class TextVerifier
  49. {
  50.     //private $metadata;    //GET RID
  51.     private $lines;
  52.     //private $linesMetadata; //GET RID!
  53.  
  54.  
  55.     //MUST PASS IN THE TEXT $lines ALREADY CLEANED (ie. json_decoded()) AND CHOPPED INTO AN ARRAY OF LINES!
  56.     public function __construct(array $lines/*, $linesMetadata*/)
  57.     {
  58.  
  59.         $this->lines = $lines;
  60.  
  61.  
  62.             //$this->metadata = $linesMetadata; //GET RID
  63.  
  64.  
  65.     }
  66.  
  67.     //apply this class's specific scanning rules to the supplied text.
  68.     //return a number between 0 (no validity confidence) and 100 (full validity confidence)
  69.     public function getValidityScore()
  70.     {
  71.         $rules = $this->getRules();
  72.  
  73.         $validityScores = [];
  74.  
  75.         $aggregateValidityScore = [];
  76.  
  77.         //loop through and get validity confidence for each individual
  78.         foreach ($rules as $ruleName => $ruleWeighting) {
  79.  
  80.             $ruleResult = $this->{"applyRule_{$ruleName}"}();
  81.             $weightedRuleResult = $ruleWeighting * $ruleResult;
  82.  
  83.             
  84.             $validityScores[$ruleName] = $weightedRuleResult;
  85.             
  86.             echo "Rule [{$ruleName}] gives {$ruleResult} which, with a weighting of {$ruleWeighting}, gives: {$weightedRuleResult} total" . PHP_EOL;
  87.         }
  88.  
  89.         //TODO from here
  90.         return array_sum($validityScores);
  91.     }
  92.  
  93.  
  94.     //per-campaign rules for this calculation
  95.     public function getRules()
  96.     {
  97.         return [
  98.             'retailer' => 10,
  99.             'timeframe' => 05,
  100.             //'matching_items' => 75,
  101.             'contains_mixa' => 75,
  102.             'is_probably_receipt' => 10
  103.         ];
  104.     }
  105.  
  106.     //----rule methods----------------
  107.  
  108.     //these rule method names must be same as the keys in the return from getRules() [but prefixed with '']
  109.  
  110.     //rule methods return between 0 and 100 (full confidence)
  111.     public function applyRule_retailer_orig()    //TODO: seems not at all useful for the grand weighting scheme of things
  112.     {
  113.         //TODO: make it make sense
  114.  
  115.         $RETAILER_KEYWORD = 'dm-drogerie' or 'dm-drogerle' or 'ROSSMAN';
  116.  
  117.         $validityConfidence = $this->findFuzzyWord($RETAILER_KEYWORD);
  118.  
  119.         return $validityConfidence;
  120.     }
  121.  
  122.     public function applyRule_retailer()    //TODO: seems not at all useful for the grand weighting scheme of things
  123.     {
  124.  
  125.         $RETAILER_KEYWORDS = ['dm-drogerie', 'ROSSMAN'];
  126.  
  127.         $highestScore = 0;
  128.  
  129.         foreach($RETAILER_KEYWORDS as $retailer){
  130.             $validityConfidence = $this->findFuzzyWord($retailer);
  131.  
  132.             if($validityConfidence > $highestScore){
  133.                 $highestScore = $validityConfidence;
  134.             }
  135.         }
  136.  
  137.         //TODO: sort out what we want to do. 85 IS AN EXAMPLE HERE!
  138.         if($highestScore > 85){
  139.             return 100;
  140.         }
  141.  
  142.         return 0;
  143.     }
  144.  
  145.     private function applyRule_timeframe_orig() //TODO: is this algorithm still helpful or relvant???
  146.     {
  147.         // TODO : make this make sense
  148.         // sudo code
  149.         // we want want the receipt date
  150.         // check regex date method.
  151.  
  152.         $start = new DateTime;
  153.  
  154.         $start->setDate(2021, 3, 8);
  155.  
  156.         $end = clone $start;
  157.  
  158.         $end->setDate(2021, 04, 25);
  159.  
  160.         $interval = new DateInterval('P1D');
  161.  
  162.         $dateRange = new DatePeriod($start, $interval, $end);
  163.  
  164.         foreach ($dateRange as $eachDate) {
  165.             $eachDateFormatted = date_format($eachDate, "d.m.Y");
  166.             $validityConfidence = $this->findFuzzyWord($eachDateFormatted);
  167.         }
  168.         return $validityConfidence;
  169.     }
  170.  
  171.     private function applyRule_timeframe() //TODO: is this validation check still helpful or relvant???
  172.     {
  173.         $receiptDate = $this->extractDateFromText();
  174.  
  175.         if($receiptDate === false){
  176.             return 50;  //or?
  177.         }
  178.  
  179.         //TODO clever stuff with DateTime object, load in $receiptDate as a DateTime and see if it meets requirements
  180.         return 100;
  181.     }
  182.  
  183.     // This function checks to see if there any qualifying products on the receipt.
  184.     private function applyRule_matching_items() //TODO:
  185.         // TODO: we only need one qualifying product, but if there are more than one qulaifying product on the receipt
  186.         // is there a way we can *trust* this receipt more
  187.  
  188.         //algorithm approach, loop thorough all words, get validity scores, pick highest validity score,
  189.         // IF this is higher than our threshold X, return as our strongest match.
  190.     {
  191.         // note, we only need to match *one* of these keywords
  192.         $matching_item_keywords = [
  193.             'Mixa',
  194.             'Cica Repair',
  195.             'Panthenol Comfort',
  196.             'bodylotion',
  197.             'lotion',
  198.             'Shea Ultra Soft Body Milk',
  199.             'Hyaluro Fresh Body',
  200.             'Intensiv Straffend',
  201.         ];
  202.  
  203.         foreach ($matching_item_keywords as $each_keyword) {
  204.             $validityConfidence = $this->findFuzzyWord($each_keyword);
  205.             //log_to_file("The keyword is currently [{$each_keyword}]");
  206.             //log_to_file($this->lines);
  207.  
  208.         }
  209.  
  210.         return $validityConfidence;
  211.     }
  212.  
  213.     //return between 0 - 100
  214.     public function applyRule_contains_mixa()
  215.     {
  216.         $containsMixa = $this->findExactWord('mixa');
  217.  
  218.         if($containsMixa){
  219.             return 100;
  220.         }
  221.  
  222.         return 0;
  223.     }
  224.  
  225.     private function applyRule_is_probably_receipt()
  226.     {
  227.         //this is used to check if a document being scanned is a receipt or not/
  228.         // mwst is the german equivalent of vat, if the receipt has this, pass this check
  229.         $valueKeyword = 'MwSt';
  230.         $validityConfidence = $this->findFuzzyWord($valueKeyword);
  231.  
  232.         return $validityConfidence;
  233.     }
  234.  
  235.     //----/end rule methods----------------
  236.  
  237.  
  238.     //----text scanning methods----------------
  239.  
  240.     /**
  241.      *
  242.      * NOTE very heavily assumed to only expect 24-hour format
  243.      *
  244.      *
  245.      * @return false|string false on no time match else the time string in question AND NOTE that this is 'hh:mm' 24-hour clock
  246.      */
  247.     public function extractTimeFromText()
  248.     {
  249.         //APPROACH:
  250.         //[1] glue all text back together
  251.         //[2] run the regex on the glued text
  252.         //[3] if time text is present, return the time text
  253.         //[4] else, return false,
  254.  
  255.         //[1] glue all text back together
  256.         //$gluedText = implode(' ', $this->lines);  //space can get picked up in out main regex and we have cases of matching ACROSS lines!
  257.         $gluedText = implode('&', $this->lines);
  258.  
  259.         //[2] run the regex on the glued text
  260.  
  261.         $matchResult = preg_match('|([0-9]{2}[:][0-9]{2})[^0-9]|', $gluedText, $matches);  //discount if more digits after right-most time digits
  262.  
  263.  
  264.         //[3] if time text is present, return the time text
  265.         if($matchResult === 1)  //a match!
  266.         {
  267.             $timeText = $matches[1];
  268.  
  269.             return $timeText;
  270.         }
  271.  
  272.         //[4] else, return false,
  273.         return false;
  274.  
  275.  
  276.     }
  277.  
  278.     /**
  279.      *
  280.      * NOTE very heavily assumed to only expect dd/mm/yy or dd/mm/yyyy in the source text
  281.      *
  282.      *
  283.      * @return false|string false on no date match else the date string in question AND NOTE that this is yyyy/mm/dd
  284.      */
  285.     public function extractDateFromText()
  286.     {
  287.         //APPROACH:
  288.         //[1] glue all text back together
  289.         //[2] run the regex on the glued text
  290.         //[3] if date text is present, return the date text
  291.         //[4] else, return false,
  292.  
  293.         //[1] glue all text back together
  294.         //$gluedText = implode(' ', $this->lines);  //space can get picked up in out main regex and we have cases of matching ACROSS lines!
  295.         $gluedText = implode('&', $this->lines);
  296.  
  297.         //[2] run the regex on the glued text
  298.  
  299.         $matchResult = preg_match('|([0-9]{2})[\-\./ ]{1}([0-9]{2})[\-\./ ]{1}([0-9]{2,4})[^0-9]|', $gluedText, $matches);  //discount if more digits after right-most year digits
  300.  
  301.  
  302.         //[3] if date text is present, return the date text
  303.         if ($matchResult === 1)  //a match!
  304.         {
  305.             $d = $matches[1];
  306.             $m = $matches[2];
  307.             $y = $matches[3];
  308.  
  309.             if (strlen($y) == 2) {
  310.                 $y = '20' . $y;
  311.             }
  312.  
  313. //            // even though this date below is ideal because it's a UK / European standard, it is advised to not use it as forge wants
  314. //            // the date in {yyyy-mm-dd} format.
  315. //            //To see Daniel's comment on this, run the SQL -
  316. //            <<<SQL
  317. //            show create table `inspire-flights`.`inspire-flights_applications`
  318. //            SQL;
  319. //            // and read Daniel's comments.
  320.  
  321.             //$date = "{$d}/{$m}/{$y}";
  322.  
  323.             // so therefore we do.
  324.             $date = "{$y}/{$m}/{$d}";   //does not match our original docblock! but i have kept this (so as not to worsen data consistency) and altered the docblock. Please be careful! and you will need to alter the backoffice input field label for "receipt date"
  325.             //log_to_file('Receipt date is............');
  326.             //log_to_file(var_export($date));
  327.             return $date;
  328.  
  329.             // note for Usama - Using curly braces in this context (string literals) is known as 'Complex (curly) syntax'
  330.             //- https://www.ph1`p.net/manual/en/language.types.string.php#language.types.string.parsing.complex
  331.  
  332.             // IT IS NOT the same as Curly array Syntax which is actually deprecated in PHP 7.4
  333.             //https://wiki.php.net/rfc/deprecate_curly_braces_array_access
  334.             // and removed in PHP 8 which stops this project running in PHP 8 in it's current form.
  335.         } //[4] else, return false,
  336.         else {
  337.             return false;
  338.         }
  339.  
  340.     }
  341.  
  342.  
  343.     //GET RID!
  344.     /*
  345.     // function currently not in use.
  346.     private function findExactText($exactText)
  347.     {
  348.         //$found = false;
  349.  
  350.         //loop over everything
  351.         foreach ($this->linesMetadata as $line) {
  352.             if (strpos($line['Text'], $exactText)) {
  353.                 return true;
  354.             }
  355.         }
  356.  
  357.         //return bool or line id(s)?
  358.         return true;
  359.     }
  360.     */
  361.  
  362.     //find single keyword fuzzily
  363.     //
  364.     //TODO need to think about single words versus word phrases (probably)
  365.     //needs return confidence of between 0 to 100
  366.     //
  367.     //this function will return the probability of the specified keyword (a SINGLE keyword, not a phrase)
  368.     //existing verbatim in the source text
  369.     //
  370.     //for example, [] 'Mixa' will match 'Mixa' at 100%
  371.     //             [] 'Mixa' willl match 'Mixy' at only 75%
  372.     //
  373.     //NOTE: similar_text() is case-sensitive, which we iron out (we want case-insensitive)
  374.     //
  375.     //NOTE: NOTE: performance concerns(?)
  376.     public function findFuzzyWord($fuzzyWordToFind)    //TODO: method name could be better
  377.     {
  378.         $highestMatch = 0;
  379.  
  380.         //loop over each line
  381.         foreach ($this->lines as $line) {
  382.  
  383.             $words = $this->getWordsFromPhrase($line);
  384.  
  385.             //loop over each SINGLE WORD
  386.             foreach ($words as $word) {
  387.                 $percentageMatch = 0;
  388.                 similar_text(strtolower($fuzzyWordToFind), strtolower($word), $percentage);
  389.  
  390.                 //log_to_file("We want [{$fuzzyTextToFind}] and this matches [{$word}] with [{$percentage}]");
  391.  
  392.                 if ($percentage > $highestMatch) {
  393.                     $highestMatch = $percentage;
  394.                 }
  395.  
  396.                 //if get 100 match, can return immediately
  397.                 if($highestMatch == 100){
  398.                     return 100;
  399.                 }
  400.             }
  401.         }
  402.  
  403.         return $highestMatch;
  404.     }
  405.  
  406.  
  407.     //find exact single keyword
  408.     //NOTE case-insensitive
  409.     //TODO: think about that currently, this will match 'mixa' with - for example - 'mixay'
  410.     //@return bool true if desired keyword exists EXACTLY in $this->lines
  411.     public function findExactWord($exactWordToFind)
  412.     {
  413.         //[1] glue all text back together
  414.         //$gluedText = implode(' ', $this->lines);  //space can get picked up in out main regex and we have cases of matching ACROSS lines!
  415.         $gluedText = implode('§', $this->lines);
  416.  
  417.         //
  418.         $result = stripos($gluedText, $exactWordToFind);
  419.  
  420.         return ($result !== false);
  421.     }
  422.  
  423.     //split a multi-word phrase up into its constituent single words
  424.     public function getWordsFromPhrase($phrase)
  425.     {
  426.         //log_to_file("PHRASE to split is: [{$phrase}]");
  427.         $words = preg_split("/[\s]+/", $phrase);    //split on one or more whitespace characters
  428.         //log_to_file("WORDS got from phrase: " . var_export($words, true));
  429.  
  430.         return $words;
  431.     }
  432.  
  433.     //----/end text scanning methods------------
  434. }
File Description
  • Current scoring arithmetic and rounding
  • PHP Code
  • 12 Apr-2021
  • 16.05 Kb
You can Share it: