Skip to content

Commit

Permalink
Merge pull request #52 from saleem-hadad/fix/detecting-sms-data-with-…
Browse files Browse the repository at this point in the history
…any-order

Fix detecting sms data with any order
  • Loading branch information
saleem-hadad committed Nov 27, 2023
2 parents aebb363 + fa4e97d commit be06b95
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 55 deletions.
129 changes: 83 additions & 46 deletions app/BusinessLogic/SmsTemplateDetector.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,62 +7,99 @@

class SmsTemplateDetector implements SmsTemplateDetectorContract
{
public function detect($sms)
/**
* @param $sms
* @return SmsTemplate|null
*/
public function detect($sms): ?SmsTemplate
{
foreach(config('finance.sms_templates') as $template) {
$templateCopy = $template;

$templateCopy = str_replace("{amount}", "(.*?)", $templateCopy);
$templateCopy = str_replace("{brand}", "(.*?)", $templateCopy);
$templateCopy = str_replace("{card}", "(.*?)", $templateCopy);
$templateCopy = str_replace("{account}", "(.*?)", $templateCopy);
$templateCopy = str_replace("{datetime}", "(.*?(?=\.))", $templateCopy);

if(preg_match("/{$templateCopy}/", $sms, $matchedParts)) {
$partsWithValues = $this->getPartsWithValues($matchedParts, $template);

return SmsTemplate::make(
$template,
$partsWithValues,
);
}
$detectedTemplate = $this->getDetectedTemplate($sms);

if(! $detectedTemplate) {
return null;
}

return null;
$smsInformation = $this->extractSmsInformation($detectedTemplate, $sms);

return SmsTemplate::make(
$detectedTemplate,
$smsInformation,
);
}

protected function getPartsWithValues($matchedParts, $templateBody)
/**
* @param $template
* @param $sms
* @return array
*/
private function extractSmsInformation($template, $sms): array
{
$partsPositionsInTemplate = [];
$keys = $this->extractPlaceholdersKeys($template);
$maskedSmsTemplate = $this->getMaskedSmsTemplate($template);
preg_match("/{$maskedSmsTemplate}/", $sms, $matchedParts);
array_shift($matchedParts);

if(strpos($templateBody, "{amount}") !== false) {
$partsPositionsInTemplate['amount'] = strpos($templateBody, "{amount}");
}
if(strpos($templateBody, "{brand}") !== false) {
$partsPositionsInTemplate['brand'] = strpos($templateBody, "{brand}");
}
if(strpos($templateBody, "{card}") !== false) {
$partsPositionsInTemplate['card'] = strpos($templateBody, "{card}");
}
if(strpos($templateBody, "{account}") !== false) {
$partsPositionsInTemplate['account'] = strpos($templateBody, "{account}");
}
if(strpos($templateBody, "{datetime}") !== false) {
$partsPositionsInTemplate['datetime'] = strpos($templateBody, "{datetime}");
$smsInformation = [];

for($i = 0; $i < count($keys); $i++) {
if(empty($matchedParts[$i])) {
continue;
}
$smsInformation[$keys[$i]] = $matchedParts[$i];
}

asort($partsPositionsInTemplate);

$index = 1;
$partsWithValues = [];
foreach($partsPositionsInTemplate as $part => $value) {
if(! empty($matchedParts[$index])) {
$partsWithValues[$part] = $matchedParts[$index];

return $smsInformation;
}

/**
* @param $sms
* @return array|string|null
*/
private function getDetectedTemplate($sms): array|string|null
{
$detectedTemplate = null;

foreach(config('finance.sms_templates') as $template) {
$maskedSmsTemplate = $this->getMaskedSmsTemplate($template);

if(preg_match("/{$maskedSmsTemplate}/", $sms)) {
$detectedTemplate = $template;
break;
}
$index++;
}

return $partsWithValues;

return $detectedTemplate;
}

/**
* @param $string
* @return mixed
*/
private function extractPlaceholdersKeys($string): mixed
{
// Regular expression to match content inside curly braces
$pattern = '/\{([^}]*)}/';

// Array to store the matches
$matches = [];

// Perform the regex match
preg_match_all($pattern, $string, $matches);

// The matches are in the second element of the result
return $matches[1];
}

/**
* @param mixed $template
* @return array|string|string[]|null
*/
public function getMaskedSmsTemplate(mixed $template): string|array|null
{
// special case for datetime
$templateCopy = str_replace("{datetime}", "(.*?(?=\.))", $template);

return preg_replace("/\{.*?}/", "(.*?)", $templateCopy);
}
}

2 changes: 1 addition & 1 deletion config/finance.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
'Outward {brand} of AED {amount} is debited from your {account}. Your {card} as of {datetime}.',
'An ATM cash {brand} of AED{amount} has been debited from your {account} on {datetime}.',
'{brand} PAYMENT for {card} via MOBAPP of AED {amount} was debited from {account}.',
// 'From HSBC: Your {name} ending with {card} has been used for AED {amount} on {datetime} at {brand}.'
'Your Cr.Card {card} was used for AED{amount} on{datetime}at {brand},{ignore}. {ignore}',
],
'reports' => [
(new SectionDivider)->withTitle("🎖️ Account Overview"),
Expand Down
41 changes: 33 additions & 8 deletions tests/Unit/SmsTemplateDetectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,42 @@ public function it_returns_null_if_no_template_match()
/** @test */
public function it_returns_correct_matched_template_with_extracted_data()
{
Config::set('finance.sms_templates', [
'hello this is {amount}, but this is {brand} on {datetime}.'
]);
$templates = [
'Purchase of AED {amount} with {card} at {brand},' => [
'message' => 'Purchase of AED 500 with Visa at ElectronicsStore,',
'expectedData' => [
'amount' => '500',
'brand' => 'ElectronicsStore'
]
],
'Payment of AED {amount} to {brand} with {card}.' => [
'message' => 'Payment of AED 200 to InternetProvider with MasterCard.',
'expectedData' => [
'amount' => '200',
'brand' => 'InternetProvider',
]
],
'AED {amount} has been debited from {account} using {card} at {brand} on {datetime}.' => [
'message' => 'AED 100 has been debited from SavingsAccount using DebitCard at Supermarket on 25-12-2023 14:00.',
'expectedData' => [
'amount' => '100',
'brand' => 'Supermarket',
'datetime' => '25-12-2023 14:00'
]
],
];

Config::set('finance.sms_templates', array_keys($templates));

$sut = new SmsTemplateDetector;

$smsTemplate = $sut->detect("hello this is 10, but this is someBrand on 20-06-2022 10:10.");
foreach ($templates as $template => $data) {
$smsTemplate = $sut->detect($data['message']);

$this->assertEquals('hello this is {amount}, but this is {brand} on {datetime}.', $smsTemplate->body());
$this->assertEquals('10', $smsTemplate->data()['amount']);
$this->assertEquals('someBrand', $smsTemplate->data()['brand']);
$this->assertEquals('20-06-2022 10:10', $smsTemplate->data()['datetime']);
$this->assertEquals($template, $smsTemplate->body());
foreach ($data['expectedData'] as $key => $value) {
$this->assertEquals($value, $smsTemplate->data()[$key]);
}
}
}
}

0 comments on commit be06b95

Please sign in to comment.