0 votes
ago in SoSci Survey (dt.) by s293220 (110 points)

Hallo,

ich habe ein Problem mit der Planung von Serienmails. Ich steuere die Planung über einen PHP-Code, der für Teilnehmende den Versand von Mails zu randomisierten Zeitpunkten innerhalb festgelegter Zeitslots planen soll. Die Studie soll über 10 Tage laufen, pro Tag vier Mails pro Teilnehmer planen.

Bei der ersten Planung schient alles zu funktionieren, wie es soll. Allerdings werden dann immer wieder zusätzliche Mails für schon bestehende Teilnehmer geplant. Und zwar immer direkt korrekt für jede Welle eine. Eine Logik dahinter erschließt sich mir nicht.

Das hier ist der PHP-Code, den ich verwende:

if (getRoute() != 'repeat') {

    $expireFinal   = 172800; // 2 Tage
    $expireRegular = 2820;   // 47 Minuten
    $minGap        = 3600;   // 1 Stunde Mindestabstand
    $mailId        = 2;

    $plannedTimes = [];
    $plannedMails = [];
    $abortAll     = false;

    $baseDate = strtotime('today');

    // Teilnehmer-Offset (0–5 Minuten)
    $participantOffset = time() % 300;

    // ======================================================
    // Phase 1: Slots berechnen (Sampling + Shuffle)
    // ======================================================
    for ($day = 1; $day <= 10; $day++) {

        if ($abortAll) {
            break;
        }

        $currentDate = strtotime("+" . $day . " day", $baseDate);
        if ($currentDate === false) {
            $abortAll = true;
            break;
        }

        $weekday = (int) date('N', $currentDate);

        // Tagesfenster
        $windows = [];
        if ($weekday <= 5) {
            $windows[] = [
                'start' => mktime(7,0,0,date('n',$currentDate),date('j',$currentDate),date('Y',$currentDate)),
                'end'   => mktime(7,55,0,date('n',$currentDate),date('j',$currentDate),date('Y',$currentDate))
            ];
            $windows[] = [
                'start' => mktime(14,0,0,date('n',$currentDate),date('j',$currentDate),date('Y',$currentDate)),
                'end'   => mktime(22,0,0,date('n',$currentDate),date('j',$currentDate),date('Y',$currentDate))
            ];
        } else {
            $windows[] = [
                'start' => mktime(10,0,0,date('n',$currentDate),date('j',$currentDate),date('Y',$currentDate)),
                'end'   => mktime(22,0,0,date('n',$currentDate),date('j',$currentDate),date('Y',$currentDate))
            ];
        }

        // Alle möglichen Slots sammeln
        $allSlots = [];
        foreach ($windows as $w) {
            for ($t = $w['start']; $t <= $w['end']; $t += 300) {
                if ($t > time()) {
                    $allSlots[] = min($t + $participantOffset, $w['end']);
                }
            }
        }

        if (count($allSlots) < 4) {
            $abortAll = true;
            break;
        }

        shuffle($allSlots);

        // 4 Slots mit Mindestabstand auswählen
        $daySlots = [];
        foreach ($allSlots as $slot) {

            $ok = true;

            foreach ($daySlots as $ds) {
                if (abs($slot - $ds) < $minGap) {
                    $ok = false;
                    break;
                }
            }

            if ($ok) {
                foreach ($plannedTimes as $pt) {
                    if (abs($slot - $pt) < $minGap) {
                        $ok = false;
                        break;
                    }
                }
            }

            if ($ok) {
                $daySlots[] = $slot;
                if (count($daySlots) === 4) {
                    break;
                }
            }
        }

        if (count($daySlots) !== 4) {
            $abortAll = true;
            break;
        }

        // Slots fixieren
        foreach ($daySlots as $candidateTime) {

            if ($mailId > 82) {
                $abortAll = true;
                break;
            }

            $plannedMails[] = [
                'mailId'      => $mailId,
                'plannedTime' => $candidateTime,
                'expire'      => $candidateTime + $expireRegular
            ];

            $plannedTimes[] = $candidateTime;

            $mailId++;
            if ($mailId >= 3 && $mailId <= 43) {
                $mailId = 44;
            }
        }
    }

    // ======================================================
    // Phase 2: Mails planen (unverändert)
    // ======================================================
    if (!$abortAll) {

        foreach ($plannedMails as $pm) {

            $plannedTime = $pm['plannedTime'];
            $expireAt    = $pm['expire'];

            if ($expireAt <= time()) {
                $expireAt = time() + $expireRegular;
            }

            mailSchedule(false, $pm['mailId'], $plannedTime, [
                'expire' => $expireAt
            ]);
        }

        // Abschlussfragebogen ID 84
        if (count($plannedTimes) > 0) {

            $lastMailTime = max($plannedTimes);
            $plannedTime  = $lastMailTime + $expireRegular;

            $lastDay = strtotime('+10 day', $baseDate);
            $weekday = (int) date('N', $lastDay);

            if ($weekday <= 5) {
                $windows = [
                    [
                        'start'=>mktime(7,0,0,date('n',$lastDay),date('j',$lastDay),date('Y',$lastDay)),
                        'end'  =>mktime(7,55,0,date('n',$lastDay),date('j',$lastDay),date('Y',$lastDay))
                    ],
                    [
                        'start'=>mktime(14,0,0,date('n',$lastDay),date('j',$lastDay),date('Y',$lastDay)),
                        'end'  =>mktime(22,0,0,date('n',$lastDay),date('j',$lastDay),date('Y',$lastDay))
                    ]
                ];
            } else {
                $windows = [
                    [
                        'start'=>mktime(10,0,0,date('n',$lastDay),date('j',$lastDay),date('Y',$lastDay)),
                        'end'  =>mktime(22,0,0,date('n',$lastDay),date('j',$lastDay),date('Y',$lastDay))
                    ]
                ];
            }

            $plannedTime = max($plannedTime, max($plannedTimes) + $minGap);
            $plannedTime = min($plannedTime, $windows[count($windows) - 1]['end']);

            $ok = mailSchedule(false, 84, $plannedTime, [
                'expire' => $plannedTime + $expireFinal
            ]);

            if ($ok !== false) {
                $plannedMails[] = [
                    'mailId'      => 84,
                    'plannedTime' => $plannedTime,
                    'expire'      => $plannedTime + $expireFinal
                ];
            } else {
                $abortAll     = true;
                $plannedTimes = [];
            }
        }
    }

} // Ende if getRoute() != 'repeat'

1 Answer

0 votes
ago by SoSci Survey (366k points)

Nun, der Code läuft jedesmal, wenn die Person auf die Seite kommt. Es sind also durchaus Situationen denkbar, wenn durch das wiederholte Ausführen des Codes zusätzliche Mails geplant werden.

Hinzu kommt, dass Sie in dem Code viele Schleifen verschachteln, deren Sinn sich mir auf den ersten Blick nicht erschließt. Ich tippe auf eine komplexe Randomisierung ... aber die Verwendung von Abbruchbedingungen (hier die Variable $ok) sorgt für einige Komplexität. Vielleicht wäre ein Zufallsgenerator einfacher.

Die gute Nachricht: Es wird pro Serienmail nur die erste Mail verschickt, die eingeplant ist. Das ist für die Randomisierung evtl. nicht optimal, aber zumindest werden die Befragten nicht mehrfach mit derselben Mail bombardiert.

ago by s293220 (110 points)
Danke erstmal für die Antwort. Leider ist die doppelte Planung auch passiert, ohne, dass Teilnehmende ein zweites Mal die Seite mit Code aufgerufen haben. Zumindest nach eigenen Angaben.

Können die Schleifen und Abbruchsbedingungen das Problem denn verursachen? ich dachte, ich nutze eigentlich eine Zufallsgenerator-Logik mit Shuffle.

Und genau, für die Randomisierung ist die Logik mit der ersten Mail, die verschickt wird, leider ein Problem. Eine bessere Lösung als die hier hab ich dafür leider bisher nicht gefunden.
ago by s293220 (110 points)
Nochmal als Nachtrag: Wäre es möglich im PHP-Code zu prüfen, ob schon Mails mit den entsprechenden ID für einen TN geplant wurden? Und falls dem so ist, eine erneute Planung zu verhindern? Ich sehe im Backend ja die Adressliste und in der Planung die Mails. Aber die Daten sind nicht personenbezogen, also tauchen die Mobilnummern und auch die Planungszeiten nicht im Datensatz auf.
ago by SoSci Survey (366k points)
> Wäre es möglich im PHP-Code zu prüfen, ob schon Mails mit den entsprechenden ID für einen TN geplant wurden?

Sie könnten innerhalb desselben Fragebogen mittels registerVariable() und isset() sicherstellen, dass der Code nur einmal läuft.

Und Sie könnten mittels mailRevoke() bestehende Versandtermine löschen, bevor Sie bzw. der Code neue plant.

Eine explizite Prüfung wäre über Einträge in der Datenbank für Inhalte möglich.

> Können die Schleifen und Abbruchsbedingungen das Problem denn verursachen?

Nicht spezifisch, aber komplexer Code neigt dazu, unter gewissen Randbedingungen komische Dinge zu tun, mit denen man vielleicht nicht gerechnet hat.

> Leider ist die doppelte Planung auch passiert, ohne, dass Teilnehmende ein zweites Mal die Seite mit Code aufgerufen haben.

Haben Sie einen Zurück-Knopf im Fragebogen? Auch das könnte zum mehrfachen Aufruf der Seite führen. Ebenfalls denbar, dass jemand auf der Seite abbricht und dann mit dem personalisierten Link nochmal von vorne beginnt, weil zu viele Antworten gefehlt hatten.

> Eine bessere Lösung als die hier hab ich dafür leider bisher nicht gefunden.

Sie haben bisher nicht ausgeführt, was die Randomisierung genau erreichen soll. Wenn sie möchten, erklären Sie das gerne einmal. Womöglich fällt mir eine einfachere und robustere Lösung ein.
ago by s293220 (110 points)
Gerade ist die Logik so (wie auch vorgeschlagen), dass ein Opt In im ersten Fragebogen die Mobilnummern erfasst und mit Bestätigung der Nummer dann automatisch ein Fragebogen aufgerufen wird, der nur den PHP-Code und eine end Seite enthält. Geplant werden dann 40 Mails mit dem identischen Fragebogen (experience sampling, 4 Fragebögen pro Tag über 10 Tage). Ich habe für jeden Fragebogen eine Serienmail angelegt (also ins. 40 plus eine Abschlussmail).

-> d.h. ich könnte mit register variable und isset im Fragebogen des PHP-Codes hinterlegen, ob für einen Teilnehmer (nach Personenkennung?) der Code schon gelaufen ist?

Zur Randomisierung: Die Versandzeitpunkte selbst sollen randomisiert werden, dabei aber Bedingungen einhalten:

- An Wochentagen Versand zwischen 7 und 7:55 sowie 14 Uhr und 22 Uhr
- An Wochenenden Versand zwischen 10 und 22 Uhr
- Alle Links sollen 47 Minuten nach Versand auslaufen
- Zwischen zwei SMS eines Teilnehmers sollen min. 60 Minuten Abstand sein
- min. 2 Slots sollen über die 10 Tage pro TN zwischen 7 und 7:55 liegen

Ich habe mit der Randomisierung viel experimentiert, damit sie die Befragten auch wirklich zu möglichst verschiedenen Zeitpunkten erwischt und auf die Befragten auch random wirkt.

Willkommen im Online-Support von SoSci Survey.

Hier bekommen Sie schnelle und fundierte Antworten von anderen Projektleitern und direkt von SoSci Survey.

→ Eine Frage stellen


Welcome to the SoSci Survey online support.

Simply ask a question to quickly get answers from other professionals, and directly from SoSci Survey.

→ Ask a Question

...