innhold/custom/tools/generate-mock-petitions.php

290 lines
9.3 KiB
PHP
Raw Normal View History

#!/usr/bin/env php
<?php
/**
* Mock petition data generator DEV ONLY.
*
* Generates realistic-looking confirmed petition signatures for testing
* the map widget. Writes directly to the real CSV (dev environment only).
*
* Run from host:
* podman exec stopplidelsen.no php /var/www/custom/tools/generate-mock-petitions.php
*/
// Safety check: refuse to run outside the dev container
$inContainer = file_exists('/var/www/html') && file_exists('/var/www/custom');
if (!$inContainer && !in_array('--force', $argv)) {
echo "Warning: this script is intended to run inside the dev container.\n";
echo " podman exec stopplidelsen.no php /var/www/custom/tools/generate-mock-petitions.php\n";
echo " Pass --force to run anyway.\n";
exit(1);
}
$csvPath = '/var/www/custom/data/petitions/medisinsk-cannabis-pa-resept.csv';
// ------- Name lists -------
$firstNames = [
'Kari', 'Ola', 'Anne', 'Per', 'Ingrid', 'Lars', 'Kristin', 'Erik',
'Marianne', 'Tor', 'Hanne', 'Bjørn', 'Silje', 'Trond', 'Nina', 'Morten',
'Lene', 'Håkon', 'Anita', 'Svein', 'Marit', 'Gunnar', 'Tonje', 'Rune',
'Bente', 'Terje', 'Camilla', 'Geir', 'Astrid', 'Jon', 'Trine', 'Anders',
'Heidi', 'Vegard', 'Siri', 'Frode', 'Marte', 'Steinar', 'Lise', 'Øyvind',
'Karianne', 'Petter', 'Tone', 'Kenneth', 'Gro', 'Vidar', 'Line', 'Eivind',
'Maria', 'Jonas', 'Ida', 'Martin', 'Katrine', 'Fredrik', 'Kirsten', 'Helge',
'Turid', 'Ole', 'Berit', 'Arild', 'Wendy', 'Ragnar', 'Stine', 'Ivar',
'Åse', 'Knut', 'Elise', 'Harald', 'Solveig', 'Dag', 'Kristine', 'Leif',
'Elin', 'Nils', 'Mia', 'Eirik', 'Randi', 'Sigurd', 'Thea', 'Magnus',
];
$surnames = [
'Hansen', 'Johansen', 'Olsen', 'Larsen', 'Andersen', 'Pedersen', 'Nilsen',
'Kristiansen', 'Jensen', 'Karlsen', 'Johnsen', 'Pettersen', 'Eriksen',
'Berg', 'Haugen', 'Hagen', 'Johannessen', 'Bakke', 'Strand', 'Lie',
'Halvorsen', 'Solberg', 'Lund', 'Dahl', 'Moen', 'Sørensen', 'Bøe',
'Berge', 'Aas', 'Holm', 'Martinsen', 'Paulsen', 'Jakobsen', 'Stensrud',
'Nygaard', 'Christoffersen', 'Amundsen', 'Svendsen', 'Lunde', 'Sæther',
'Eide', 'Henriksen', 'Myhre', 'Fjeld', 'Vik', 'Thorsen', 'Bakken',
'Aasen', 'Mathisen', 'Elstad', 'Vold', 'Lindqvist', 'Torp', 'Nesse',
'Ruud', 'Brekke', 'Midtbø', 'Grøtting', 'Kleven', 'Hauge', 'Skogen',
];
$regions = [
'agder', 'akershus', 'buskerud', 'finnmark', 'innlandet',
'more_og_romsdal', 'nordland', 'oslo', 'rogaland', 'telemark',
'troms', 'trondelag', 'vestfold', 'vestland', 'ostfold',
];
$displayOptions = ['semi', 'semi', 'semi', 'full', 'anonymous']; // weighted
// ------- Prompt helpers -------
function prompt(string $question, string $default = ''): string {
echo $question;
if ($default !== '') echo " [$default]";
echo ': ';
$line = trim(fgets(STDIN));
return $line === '' ? $default : $line;
}
function promptInt(string $question, int $default, int $min = 1, int $max = PHP_INT_MAX): int {
while (true) {
$val = (int)prompt($question, (string)$default);
if ($val >= $min && $val <= $max) return $val;
echo " Please enter a number between $min and $max.\n";
}
}
function promptFloat(string $question, float $default, float $min = 0.0, float $max = 1.0): float {
while (true) {
$val = (float)prompt($question, (string)$default);
if ($val >= $min && $val <= $max) return $val;
echo " Please enter a number between $min and $max.\n";
}
}
// ------- Distribution helpers -------
/**
* Generate a biased region distribution.
*
* @param int $n Total signatures to distribute
* @param float $variance 0.0 = perfectly even, 1.0 = highly concentrated (Oslo-heavy)
* @return array [region => count]
*/
function generateRegionDistribution(int $n, float $variance, array $regions): array {
// Base weights (approx. Norwegian population distribution, normalized)
$baseWeights = [
'oslo' => 12.0,
'akershus' => 11.5,
'vestland' => 9.0,
'rogaland' => 8.0,
'trondelag' => 7.5,
'innlandet' => 5.5,
'buskerud' => 5.0,
'vestfold' => 4.5,
'ostfold' => 4.5,
'more_og_romsdal'=> 4.5,
'agder' => 4.0,
'telemark' => 3.5,
'nordland' => 4.0,
'troms' => 3.5,
'finnmark' => 1.5,
];
// Blend between uniform (variance=0) and biased (variance=1)
$uniform = 1.0 / count($regions);
$total = array_sum($baseWeights);
$weights = [];
foreach ($regions as $r) {
$biased = ($baseWeights[$r] ?? 1.0) / $total;
$weights[$r] = (1 - $variance) * $uniform + $variance * $biased;
}
// Normalise
$wsum = array_sum($weights);
foreach ($weights as &$w) $w /= $wsum;
// Allocate counts
$counts = [];
$remaining = $n;
$keys = array_keys($weights);
foreach ($keys as $i => $r) {
if ($i === count($keys) - 1) {
$counts[$r] = $remaining;
} else {
$c = (int)round($n * $weights[$r]);
$counts[$r] = $c;
$remaining -= $c;
}
}
return $counts;
}
// ------- CSV writer -------
function appendSignature(string $csvPath, array $data): void {
$fp = fopen($csvPath, 'a');
if (!$fp) {
echo "ERROR: Cannot open $csvPath\n";
exit(1);
}
fputcsv($fp, $data, ',', '"', '');
fclose($fp);
}
function initCsv(string $csvPath): void {
$fp = fopen($csvPath, 'w');
if (!$fp) {
echo "ERROR: Cannot create $csvPath\n";
exit(1);
}
fputcsv($fp, ['timestamp', 'email', 'firstname', 'surname', 'region', 'display', 'status', 'token', 'token_created', 'ip_hash'], ',', '"', '');
fclose($fp);
}
// ------- Main -------
echo "\n";
echo "╔══════════════════════════════════════════════╗\n";
echo "║ Mock Petition Data Generator — DEV ONLY ║\n";
echo "╚══════════════════════════════════════════════╝\n\n";
$existingCount = 0;
if (file_exists($csvPath)) {
$fp = fopen($csvPath, 'r');
fgetcsv($fp, null, ',', '"', ''); // skip header
while (fgetcsv($fp, null, ',', '"', '')) $existingCount++;
fclose($fp);
echo " Current CSV: $csvPath\n";
echo " Existing rows: $existingCount\n\n";
} else {
echo " CSV does not exist yet, will create it.\n\n";
}
$action = prompt(" Action — [a]ppend or [r]eplace (wipes existing data)", 'a');
$replace = strtolower($action[0] ?? 'a') === 'r';
if ($replace && $existingCount > 0) {
$confirm = prompt(" ⚠️ This will delete $existingCount existing rows. Type 'yes' to confirm", '');
if (strtolower($confirm) !== 'yes') {
echo " Aborted.\n";
exit(0);
}
}
echo "\n";
$count = promptInt(" How many mock petitioners to generate", 1000, 1, 100000);
echo "\n Regional distribution:\n";
echo " 0.0 = perfectly even across all fylker\n";
echo " 1.0 = highly concentrated (Oslo/Akershus heavy, matches real population)\n";
$variance = promptFloat(" Variance (0.0 1.0)", 0.7, 0.0, 1.0);
echo "\n";
// Generate distribution
$dist = generateRegionDistribution($count, $variance, $regions);
echo " Distribution preview:\n";
arsort($dist);
foreach ($dist as $r => $c) {
$bar = str_repeat('█', (int)round($c / $count * 40));
printf(" %-20s %4d %s\n", $r, $c, $bar);
}
echo "\n";
$go = prompt(" Generate $count signatures? [y/n]", 'y');
if (strtolower($go[0] ?? 'n') !== 'y') {
echo " Aborted.\n";
exit(0);
}
// Prepare CSV
if ($replace || !file_exists($csvPath)) {
initCsv($csvPath);
}
// Generate signatures
$fp = fopen($csvPath, 'a');
if (!$fp) {
echo "ERROR: Cannot open $csvPath for writing\n";
exit(1);
}
$now = time();
$generated = 0;
$usedEmails = [];
foreach ($dist as $region => $regionCount) {
for ($i = 0; $i < $regionCount; $i++) {
$firstname = $GLOBALS['firstNames'][array_rand($GLOBALS['firstNames'])];
$surname = $GLOBALS['surnames'][array_rand($GLOBALS['surnames'])];
$display = $GLOBALS['displayOptions'][array_rand($GLOBALS['displayOptions'])];
// Unique-ish email
$slug = strtolower($firstname . '.' . $surname . '.' . $generated);
$email = $slug . '@mock.test';
// Timestamp spread over last 60 days
$timestamp = $now - random_int(0, 60 * 86400);
$token = bin2hex(random_bytes(32));
$ipHash = bin2hex(random_bytes(32));
fputcsv($fp, [
$timestamp,
$email,
$firstname,
$surname,
$region,
$display,
'confirmed', // all mock sigs are confirmed
$token,
$timestamp,
$ipHash,
], ',', '"', '');
$generated++;
}
}
fclose($fp);
echo "\n ✓ Generated $generated mock signatures.\n";
echo " ✓ Written to: $csvPath\n\n";
// Invalidate map cache (both private cache and public static file)
foreach ([
'/var/www/custom/data/petition-map-cache.json',
'/var/www/custom/assets/petition-map-data.json',
] as $cacheFile) {
if (file_exists($cacheFile)) {
unlink($cacheFile);
echo " ✓ Cleared: $cacheFile\n";
}
}
echo "\n";