use Illuminate\Support\Facades\DB;
// Insert
after each sentence-ending period in question_text + solutions,
// for the ingested past-paper set (qa_status ai_generated / unreviewed).
// Safe: never touches math ($..$, $$..$$, \(..\), \[..\]) or html tags; keeps
// decimals/money (no space after the dot) and common abbreviations intact.
// Idempotent: ".
" has no trailing space so it never re-matches.
//
// Set $DRY = true to preview only (no writes). false = backup + update.
$DRY = false;
$LIMIT_SAMPLES = 10;
$ABBR = ['mr','mrs','ms','mdm','dr','prof','sr','jr','st','vs','etc','no','fig','approx','e.g','i.e'];
$sb = function($s) use ($ABBR) {
if ($s === null || $s === '') return $s;
// single pass: group 1 = math/tag (kept verbatim); else groups 2/3 = a
// token + sentence-ending period + following spaces.
$rx = '/(\$\$.*?\$\$|\$[^\$\n]+\$|\\\\\(.*?\\\\\)|\\\\\[.*?\\\\\]|<[^>]+>)|([A-Za-z0-9.]*)\.([ \t]+)/su';
$out = preg_replace_callback($rx, function($m) use ($ABBR) {
if (isset($m[1]) && $m[1] !== '') return $m[1]; // math / tag: leave alone
$w = strtolower($m[2] ?? '');
if (in_array($w, $ABBR, true)) return $m[0]; // abbreviation: no break
return ($m[2] ?? '') . '.
';
}, $s);
if ($out === null) return $s; // regex failure: no-op
return preg_replace('/(?:
\s*){2,}/', '
', $out); // collapse doubled breaks
};
$ids = DB::table('questions')->whereIn('qa_status',['ai_generated','unreviewed'])->pluck('id')->toArray();
echo "scope questions: ".count($ids)."\n";
if ($DRY) {
$shown = 0;
foreach (DB::table('questions')->whereIn('id',$ids)
->where('question','like','% %. %')->inRandomOrder()->limit(200)->get(['id','question']) as $q) {
$nq = $sb($q->question);
if ($nq !== $q->question) {
echo "--- Q#{$q->id} ---\nBEFORE: ".mb_substr($q->question,0,200)."\nAFTER : ".mb_substr($nq,0,220)."\n";
if (++$shown >= $LIMIT_SAMPLES) break;
}
}
// count how many WOULD change
$qc=0; foreach (array_chunk($ids,1000) as $c){
foreach (DB::table('questions')->whereIn('id',$c)->get(['question','explanation']) as $q){
if ($sb($q->question)!==$q->question || $sb($q->explanation)!==$q->explanation) $qc++;
}
}
echo "\nWOULD update questions: $qc (dry-run, no writes)\n";
return;
}
// real run
$ts = date('Ymd_His');
$bk = fopen(storage_path("app/_sbreak_backup_$ts.jsonl"), 'w');
$qn=0; $sn=0;
foreach (array_chunk($ids,500) as $chunk) {
foreach (DB::table('questions')->whereIn('id',$chunk)->get(['id','question','explanation']) as $q) {
$nq=$sb($q->question); $ne=$sb($q->explanation);
if ($nq!==$q->question || $ne!==$q->explanation) {
fwrite($bk, json_encode(['t'=>'q','id'=>$q->id,'q'=>$q->question,'e'=>$q->explanation])."\n");
DB::table('questions')->where('id',$q->id)->update(['question'=>$nq,'explanation'=>$ne]);
$qn++;
}
}
foreach (DB::table('solutions')->whereIn('question_id',$chunk)->get(['id','solution']) as $s) {
$ns=$sb($s->solution);
if ($ns!==$s->solution) {
fwrite($bk, json_encode(['t'=>'s','id'=>$s->id,'s'=>$s->solution])."\n");
DB::table('solutions')->where('id',$s->id)->update(['solution'=>$ns]);
$sn++;
}
}
}
fclose($bk);
echo "DONE questions_updated=$qn solutions_updated=$sn backup=storage/app/_sbreak_backup_$ts.jsonl\n";