Compare commits

...

10 Commits

Author SHA1 Message Date
Marko Jovanovic
5c1db6f948 URL change cf-rdm.info to k.ukbonn.de 2025-11-24 20:53:42 +01:00
Marko Jovanovic
93f9a0dd18 Sanitize to UTF-8 2025-10-29 12:22:05 +01:00
Marko Jovanovic
60bc3e6e99 Updated-simplified calling codes for German mobile carriers 2025-10-29 11:31:37 +01:00
Marko Jovanovic
2a12b62d40 Updated-simplified calling codes for German mobile carriers 2025-10-29 11:26:19 +01:00
Marko Jovanovic
815534749a Take msgContentType into account in ContactUncontacted Command 2025-10-22 18:35:56 +02:00
Marko Jovanovic
ccdf80a007 Clean with short codes 2025-10-22 18:22:19 +02:00
Marko Jovanovic
18563c439a Messages are due today in CleanMobileCommand 2025-10-22 10:47:56 +02:00
Marko Jovanovic
7317407c66 Increased contacts.gateway_response column length to 65535 2025-10-18 16:25:43 +02:00
Marko Jovanovic
da9613d6b2 Adapted SMS text 2025-10-17 11:27:31 +02:00
Marko Jovanovic
849061ffb4 Submit generated study_id to Umfragetool backend 2025-10-16 15:35:19 +02:00
7 changed files with 210 additions and 12 deletions

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20251018142349 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE contacts ALTER gateway_response TYPE VARCHAR(65535)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE contacts ALTER gateway_response TYPE VARCHAR(255)');
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20251022085244 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE contacts ADD msg_content_type INT DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE contacts DROP msg_content_type');
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20251022090715 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE contacts ALTER msg_content_type SET DEFAULT 1');
$this->addSql('ALTER TABLE contacts ALTER msg_content_type SET NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE contacts ALTER msg_content_type DROP DEFAULT');
$this->addSql('ALTER TABLE contacts ALTER msg_content_type DROP NOT NULL');
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20251022160213 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE contacts ADD study_id_short VARCHAR(50) DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE contacts DROP study_id_short');
}
}

View File

@ -87,11 +87,31 @@ final class CleanMobileCommand extends Command
// 3⃣ German mobile prefixes (the part *after* the leading 0) // 3⃣ German mobile prefixes (the part *after* the leading 0)
// ------------------------------------------------------------- // -------------------------------------------------------------
$germanMobilePrefixes = [ $germanMobilePrefixes = [
'151','152','155','157','159', '15',
'160','162','163','164','165','166','167','168','169', '16',
'170','171','172','173','174','175','176','177','178','179', '17',
]; ];
$sanitiseUtf8 = static function(?string $raw): ?string {
$utf8 = null;
if (mb_check_encoding($raw, 'UTF-8')) {
$utf8 = $raw;
} else {
$encodings = ['Windows-1252', 'ISO-8859-1', 'CP1252', 'ASCII'];
foreach ($encodings as $src) {
$utf8 = @iconv($src, 'UTF-8//TRANSLIT//IGNORE', $raw);
if ($utf8 !== false) {
break;
}
}
if ($utf8 === false) {
// Could not be converted treat it as “invalid”.
return null;
}
}
return $utf8;
};
// ------------------------------------------------------------- // -------------------------------------------------------------
// 4⃣ Helper closures // 4⃣ Helper closures
// ------------------------------------------------------------- // -------------------------------------------------------------
@ -138,16 +158,18 @@ final class CleanMobileCommand extends Command
} }
// Extract the 3digit network prefix and the subscriber part // Extract the 3digit network prefix and the subscriber part
$prefix = substr($e164, 4, 3); // after 0049 $prefix = substr($e164, 4, 2); // after 0049
$subscriber = substr($e164, 7); $subscriber = substr($e164, 6);
// Prefix must be one of the known mobile prefixes // Prefix must be one of the known mobile prefixes
if (!in_array($prefix, $germanMobilePrefixes, true)) { if (!in_array($prefix, $germanMobilePrefixes, true)) {
return false; return false;
} }
// Subscriber must be 610 digits long and consist only of digits // Subscriber must be 611 digits long and consist only of digits
return preg_match('/^\d{6,10}$/', $subscriber) === 1; // (technically 6-10, but for simplification we consider first two as prefix,
// and the last digit of the prefix belongs to the subscriber)
return preg_match('/^\d{6,11}$/', $subscriber) === 1;
}; };
// ------------------------------------------------------------- // -------------------------------------------------------------
@ -192,25 +214,33 @@ final class CleanMobileCommand extends Command
if ($row['HANDY_E164']) { if ($row['HANDY_E164']) {
$uuid = Uuid::v4(); $uuid = Uuid::v4();
$study_id = "QMBEFR-" . $uuid->toString(); $study_id = "QMBEFR-" . $uuid->toString();
$study_id_chain = "QMBEFR-SPENDE-" . $uuid->toString();
// Create a Contact entity for DB insertion // Create a Contact entity for DB insertion
$contact = new Contacts(); $contact = new Contacts();
$contact->setPhoneNumber($row['HANDY_E164']); $contact->setPhoneNumber($row['HANDY_E164']);
$dueDate = (new \DateTime('tomorrow'))->setTime(16, 0, 0); $dueDate = (new \DateTime('today'))->setTime(12, 0, 0);
$contact->setDueDate($dueDate); $contact->setDueDate($dueDate);
$contact->setContacted(false); $contact->setContacted(false);
$contact->setParsedFilename($inputPath); $contact->setParsedFilename($inputPath);
$contact->setParsedAt(new \DateTimeImmutable()); $contact->setParsedAt(new \DateTimeImmutable());
$contact->setStudyId($study_id); $contact->setStudyId($study_id);
$contact->setParsedFileLinenum($rowCount + 1); $contact->setParsedFileLinenum($rowCount + 1);
$contact->setParsedFileLine(implode(';', $row)); $contact->setParsedFileLine($sanitiseUtf8(implode(';', $row)));
$contact->setMsgContentType($rowCount % 2 ? 1 : 2);
try { try {
$this->http->request('POST', $this->backendApiURL . "/" . $study_id, [ $result = $this->http->request('POST', $this->backendApiURL . "/" . $study_id . "/" . $study_id_chain, [
'body' => '', 'body' => '',
'headers' => [], 'headers' => [],
]); ]);
if ($result->getStatusCode() == 200) {
$response = json_decode($result->getContent());
$shortCode = $response->{'subject_id_short'};
$contact->setStudyIdShort($shortCode);
}
$validContacts[] = $contact; $validContacts[] = $contact;
} catch (TransportExceptionInterface $e) { } catch (TransportExceptionInterface $e) {
$backendErrorCount++; $backendErrorCount++;

View File

@ -57,6 +57,7 @@ class ContactUncontactedCommand extends Command
// 1. Get uncontacted rows with a date in the past // 1. Get uncontacted rows with a date in the past
$now = new DateTime(); $now = new DateTime();
/** @var Contacts[] $phoneNumbersToContact */
$phoneNumbersToContact = $this->entityManager->getRepository(Contacts::class)->createQueryBuilder('p') $phoneNumbersToContact = $this->entityManager->getRepository(Contacts::class)->createQueryBuilder('p')
->where('p.contacted = :state_contacted') ->where('p.contacted = :state_contacted')
->andWhere('p.due_date < :now') ->andWhere('p.due_date < :now')
@ -82,6 +83,17 @@ class ContactUncontactedCommand extends Command
// 3. Contact the HTTP REST API // 3. Contact the HTTP REST API
try { try {
$study_id = $phoneContact->getStudyId(); $study_id = $phoneContact->getStudyId();
$msgContentType = $phoneContact->getMsgContentType();
$message_1 = "Liebe Patientin, lieber Patient, \ngerne möchten wir von Ihnen erfahren, wie zufrieden Sie mit uns sind. Wir freuen uns, wenn Sie sich die Zeit nehmen und uns Ihre Eindrücke mitteilen.\nIhre Anregungen gehen direkt zum Qualitäts- und Risikomanagement.\n\nhttps://umfragetool.ukbonn.de/login?id={$study_id} \n\nIhr UKB.\n\nSMS vom UKB abbestellen per E-Mail an: datenschutz@ukbonn.de";
$message_2 = "Liebe/r Patient, bitte teilen Sie kurz Ihre Zufriedenheit mit dem UKB mit: https://k.ukbonn.de/{$phoneContact->getStudyIdShort()} Vielen Dank! Abmeldung per datenschutz@ukbonn.de";
$message = $message_1;
switch ($msgContentType) {
case 1:
break;
case 2:
$message = $message_2;
break;
}
$response = $this->httpClient->request('POST', self::$apiEndpoint, [ $response = $this->httpClient->request('POST', self::$apiEndpoint, [
'headers' => [ 'headers' => [
'X-CM-PRODUCTTOKEN' => $this->sms_gateway_api_key, 'X-CM-PRODUCTTOKEN' => $this->sms_gateway_api_key,
@ -98,7 +110,7 @@ class ContactUncontactedCommand extends Command
], ],
'body': { 'body': {
'type': 'auto', 'type': 'auto',
'content': 'Liebe Patientin, lieber Patient, \ngerne möchten wir von Ihnen erfahren, wie zufrieden Sie mit uns sind. Wir freuen uns, wenn Sie sich die Zeit nehmen und uns Ihre Eindrücke mitteilen.\nIhre Anregungen landen über den direkten Weg beim Qualitäts- und Risikomanagement.\n\nhttps://umfragetool.ukbonn.de/login?id={$study_id} \n\nIhr UKB.\n\nSMS vom UKB abbestellen per E-Mail an: datenschutz@ukbonn.de' 'content': '$message'
}, },
'minimumNumberOfMessageParts': 1, 'minimumNumberOfMessageParts': 1,
'maximumNumberOfMessageParts': 8, 'maximumNumberOfMessageParts': 8,

View File

@ -22,7 +22,7 @@ class Contacts
#[ORM\Column] #[ORM\Column]
private ?bool $contacted = null; private ?bool $contacted = null;
#[ORM\Column(length: 255, nullable: true)] #[ORM\Column(length: 65535, nullable: true)]
private ?string $gateway_response = null; private ?string $gateway_response = null;
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
@ -43,6 +43,12 @@ class Contacts
#[ORM\Column(length: 50, nullable: true)] #[ORM\Column(length: 50, nullable: true)]
private ?string $study_id = null; private ?string $study_id = null;
#[ORM\Column(length: 50, nullable: true)]
private ?string $study_id_short = null;
#[ORM\Column(nullable: false, options: ['default' => 1])]
private ?int $msg_content_type = 1;
public function getId(): ?int public function getId(): ?int
{ {
@ -158,4 +164,24 @@ class Contacts
{ {
$this->parsed_file_line = $parsed_file_line; $this->parsed_file_line = $parsed_file_line;
} }
public function getMsgContentType(): ?int
{
return $this->msg_content_type;
}
public function setMsgContentType(?int $msg_content_type): void
{
$this->msg_content_type = $msg_content_type;
}
public function getStudyIdShort(): ?string
{
return $this->study_id_short;
}
public function setStudyIdShort(?string $study_id_short): void
{
$this->study_id_short = $study_id_short;
}
} }