Files
html/api/ethica-api.php
2026-04-12 22:57:03 +02:00

246 lines
13 KiB
PHP

<?php
// === INPUT SANITIZATION ===
function weval_input($key, $type='string', $method='GET') {
$src = $method === 'POST' ? INPUT_POST : INPUT_GET;
$val = filter_input($src, $key, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
if ($val === null || $val === false) {
$val = ($method === 'POST') ? ($_POST[$key] ?? '') : ($_GET[$key] ?? '');
$val = htmlspecialchars(strip_tags(trim($val)), ENT_QUOTES, 'UTF-8');
}
if ($type === 'int') return intval($val);
if ($type === 'email') return filter_var($val, FILTER_SANITIZE_EMAIL);
return $val;
}
require_once __DIR__ . '/_secrets.php';
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
if ($_SERVER["REQUEST_METHOD"] === "OPTIONS") { http_response_code(200); exit; }
$token = $_GET["token"] ?? $_POST["token"] ?? "";
if ($token !== "ETHICA_API_2026_SECURE") { echo json_encode(["error"=>"Invalid token"]); exit; }
$action = $_GET["action"] ?? $_POST["action"] ?? "status";
$pg = pg_connect("host=10.1.0.3 dbname=adx_system user=admin password=" . weval_secret('WEVAL_PG_ADMIN_PASS') . "");
if (!$pg) { echo json_encode(["error"=>"DB connection failed"]); exit; }
switch ($action) {
case "scrapers":
echo json_encode([
"status" => "ok",
"scrapers" => [
["name" => "Tabibi", "status" => "blocked", "note" => "403 Cloudflare"],
["name" => "RichScraper", "status" => "active", "engine" => "Playwright+SearXNG"],
["name" => "Scrapy", "status" => "active", "spiders" => 4],
],
"pipeline" => "active",
"last_run" => date("Y-m-d H:i"),
]);
break;
case "dashboard":
$total = pg_fetch_result(pg_query($pg, "SELECT count(*) FROM ethica.medecins_validated"), 0, 0);
$with_email = pg_fetch_result(pg_query($pg, "SELECT count(*) FROM ethica.medecins_validated WHERE email IS NOT NULL AND email != ''"), 0, 0);
$with_phone = pg_fetch_result(pg_query($pg, "SELECT count(*) FROM ethica.medecins_validated WHERE telephone IS NOT NULL AND telephone != ''"), 0, 0);
$specialites = pg_fetch_result(pg_query($pg, "SELECT count(DISTINCT specialite) FROM ethica.medecins_validated"), 0, 0);
$villes = pg_fetch_result(pg_query($pg, "SELECT count(DISTINCT ville) FROM ethica.medecins_validated"), 0, 0);
$recent = pg_fetch_result(pg_query($pg, "SELECT count(*) FROM ethica.medecins_validated WHERE created_at > NOW() - INTERVAL '7 days'"), 0, 0);
echo json_encode([
"status" => "ok",
"scraper" => "operational",
"total_hcp" => intval($total),
"with_email" => intval($with_email),
"with_telephone" => intval($with_phone),
"specialites" => intval($specialites),
"villes" => intval($villes),
"recent_7d" => intval($recent),
"pipeline" => "active",
"last_scrape" => date("Y-m-d H:i"),
]);
break;
case "status":
echo json_encode(["status"=>"operational","token"=>"valid","version"=>"3.0","db"=>"connected"]);
break;
case "stats":
$total = pg_fetch_result(pg_query($pg, "SELECT count(*) FROM ethica.medecins_validated"), 0, 0);
$with_email = pg_fetch_result(pg_query($pg, "SELECT count(*) FROM ethica.medecins_validated WHERE email IS NOT NULL AND email!=''"), 0, 0);
$with_tel = pg_fetch_result(pg_query($pg, "SELECT count(*) FROM ethica.medecins_validated WHERE telephone IS NOT NULL AND telephone!=''"), 0, 0);
$specs = pg_fetch_result(pg_query($pg, "SELECT count(DISTINCT specialite) FROM ethica.medecins_validated WHERE specialite IS NOT NULL"), 0, 0);
$villes = pg_fetch_result(pg_query($pg, "SELECT count(DISTINCT ville) FROM ethica.medecins_validated WHERE ville IS NOT NULL AND ville!=''"), 0, 0);
$google_v = pg_fetch_result(pg_query($pg, "SELECT count(*) FROM ethica.medecins_validated WHERE google_verified::text='true'"), 0, 0);
$validated = pg_fetch_result(pg_query($pg, "SELECT count(*) FROM ethica.medecins_validated"), 0, 0);
$enrichment = pg_fetch_result(pg_query($pg, "SELECT count(*) FROM ethica.medecins_validated WHERE email IS NOT NULL AND email!='' AND (telephone IS NULL OR telephone='')"), 0, 0);
$email_pct = $total > 0 ? round(($with_email / $total) * 100, 1) : 0;
$tel_pct = $total > 0 ? round(($with_tel / $total) * 100, 1) : 0;
$gv_pct = $total > 0 ? round(($google_v / $total) * 100, 1) : 0;
$to_verify = $total - $google_v;
// By country
$r = pg_query($pg, "SELECT pays, count(*) as total, count(CASE WHEN telephone IS NOT NULL AND telephone!='' THEN 1 END) as tel, count(CASE WHEN email IS NOT NULL AND email!='' THEN 1 END) as email FROM ethica.medecins_validated GROUP BY pays ORDER BY total DESC");
$by_country = [];
while ($row = pg_fetch_assoc($r)) { $by_country[] = $row; }
// By source
$r = pg_query($pg, "SELECT source, count(*) as total, count(CASE WHEN telephone IS NOT NULL AND telephone!='' THEN 1 END) as tel, count(CASE WHEN email IS NOT NULL AND email!='' THEN 1 END) as email, CASE WHEN count(*)>0 THEN round(count(CASE WHEN email IS NOT NULL AND email!='' THEN 1 END)::numeric/count(*)*100,1) ELSE 0 END as couverture FROM ethica.medecins_validated WHERE source IS NOT NULL GROUP BY source ORDER BY total DESC");
$by_source = [];
while ($row = pg_fetch_assoc($r)) { $by_source[] = $row; }
// Top specialties
$r = pg_query($pg, "SELECT specialite, count(*) as nombre, CASE WHEN (SELECT count(*) FROM ethica.medecins_validated)>0 THEN round(count(*)::numeric/(SELECT count(*) FROM ethica.medecins_validated)*100,1) ELSE 0 END as pct FROM ethica.medecins_validated WHERE specialite IS NOT NULL AND specialite!='' GROUP BY specialite ORDER BY nombre DESC LIMIT 20");
$by_spec = [];
while ($row = pg_fetch_assoc($r)) { $by_spec[] = $row; }
// Top villes
$r = pg_query($pg, "SELECT ville, pays, count(*) as nombre FROM ethica.medecins_validated WHERE ville IS NOT NULL AND ville!='' GROUP BY ville, pays ORDER BY nombre DESC LIMIT 20");
$by_ville = [];
while ($row = pg_fetch_assoc($r)) { $by_ville[] = $row; }
echo json_encode([
"total" => (int)$total,
"with_email" => (int)$with_email,
"with_telephone" => (int)$with_tel,
"specialites" => (int)$specs,
"villes" => (int)$villes,
"google_verified" => (int)$google_v,
"validated" => (int)$validated,
"enrichment_pending" => (int)$enrichment,
"email_pct" => $email_pct,
"tel_pct" => $tel_pct,
"google_pct" => $gv_pct,
"to_verify" => (int)$to_verify,
"by_country" => $by_country,
"by_source" => $by_source,
"by_specialite" => $by_spec,
"by_ville" => $by_ville
]);
break;
case "search":
$q = $_GET["q"] ?? "";
$pays = $_GET["pays"] ?? "";
$source = $_GET["source"] ?? "";
$spec = $_GET["specialite"] ?? "";
$verified = $_GET["verified"] ?? "";
$limit = min((int)($_GET["limit"] ?? 50), 200);
$offset = max((int)($_GET["offset"] ?? 0), 0);
$where = [];
$params = [];
$i = 1;
if ($q) { $where[] = "(nom ILIKE $".$i." OR prenom ILIKE $".$i." OR ville ILIKE $".$i." OR email ILIKE $".$i.")"; $params[] = "%$q%"; $i++; }
if ($pays) { $where[] = "pays=$".$i; $params[] = $pays; $i++; }
if ($source) { $where[] = "source=$".$i; $params[] = $source; $i++; }
if ($spec) { $where[] = "specialite ILIKE $".$i; $params[] = "%$spec%"; $i++; }
if ($verified === "true") { $where[] = "google_verified::text='true'"; }
if ($verified === "false") { $where[] = "(google_verified IS NULL OR google_verified=false)"; }
$sql = "SELECT id,nom,prenom,specialite,ville,pays,email,telephone,source,google_verified,consent_status FROM ethica.medecins_validated";
if ($where) $sql .= " WHERE " . implode(" AND ", $where);
$sql .= " ORDER BY id DESC LIMIT $limit OFFSET $offset";
$r = pg_query_params($pg, $sql, $params);
$rows = [];
while ($row = pg_fetch_assoc($r)) { $rows[] = $row; }
// Count total
$csql = "SELECT count(*) FROM ethica.medecins_validated";
if ($where) $csql .= " WHERE " . implode(" AND ", $where);
$cnt = pg_fetch_result(pg_query_params($pg, $csql, $params), 0, 0);
echo json_encode(["total" => (int)$cnt, "limit" => $limit, "offset" => $offset, "data" => $rows]);
break;
case "consent_stats":
$r = pg_query($pg, "SELECT consent_status, count(*) FROM ethica.medecins_validated WHERE consent_status IS NOT NULL GROUP BY consent_status ORDER BY count DESC");
$stats = [];
while ($row = pg_fetch_assoc($r)) { $stats[] = $row; }
echo json_encode(["consent_stats" => $stats]);
break;
case "export":
case "export_ethica":
$pays = $_GET["pays"] ?? "";
$sql = "SELECT nom,prenom,specialite,ville,pays,email,telephone,source,google_verified,consent_status FROM ethica.medecins_validated";
if ($pays) $sql .= " WHERE pays=" . pg_escape_literal($pg, $pays);
$sql .= " ORDER BY pays,ville,nom LIMIT 5000";
$r = pg_query($pg, $sql);
$rows = [];
while ($row = pg_fetch_assoc($r)) { $rows[] = $row; }
echo json_encode(["total" => count($rows), "data" => $rows]);
break;
case "sms_dashboard":
$sent = pg_fetch_result(pg_query($pg, "SELECT COALESCE(SUM(sms_count),0) FROM ethica.medecins_validated"), 0, 0);
$delivered = pg_fetch_result(pg_query($pg, "SELECT COALESCE(SUM(sms_delivered),0) FROM ethica.medecins_validated"), 0, 0);
echo json_encode(["sms_sent" => (int)$sent, "sms_delivered" => (int)$delivered]);
break;
case "enrich_villes":
echo json_encode(["status" => "pending", "message" => "Ville enrichment queued"]);
break;
case "collecte_pharma":
echo json_encode(["status" => "pending", "message" => "collecteur queued"]);
break;
case "kpi":
case "reach":
// Reach par pays
$r = pg_query($pg, "SELECT pays, count(*) as total, count(CASE WHEN email IS NOT NULL AND email!='' THEN 1 END) as email_reach, count(CASE WHEN telephone IS NOT NULL AND telephone!='' THEN 1 END) as tel_reach, ROUND(100.0*count(CASE WHEN email IS NOT NULL AND email!='' THEN 1 END)/count(*),1) as email_pct, ROUND(100.0*count(CASE WHEN telephone IS NOT NULL AND telephone!='' THEN 1 END)/count(*),1) as tel_pct FROM ethica.medecins_validated GROUP BY pays ORDER BY total DESC");
$by_country = [];
while ($row = pg_fetch_assoc($r)) { $by_country[] = $row; }
// Reach par specialite (top 25 par pays)
$by_spec = [];
foreach (['ALG','MA','TN'] as $p) {
$r2 = pg_query_params($pg, "SELECT specialite, count(*) as total, count(CASE WHEN email IS NOT NULL AND email!='' THEN 1 END) as email_reach, count(CASE WHEN telephone IS NOT NULL AND telephone!='' THEN 1 END) as tel_reach FROM ethica.medecins_validated WHERE pays=$1 AND specialite IS NOT NULL AND specialite!='' GROUP BY specialite ORDER BY total DESC LIMIT 25", [$p]);
$specs = [];
while ($row = pg_fetch_assoc($r2)) { $specs[] = $row; }
$key = ($p === 'ALG') ? 'DZ' : $p; $by_spec[$key] = $specs;
}
// Campaign KPIs (sends/opens/clicks)
$r3 = pg_query($pg, "SELECT pays, count(CASE WHEN sends_count>0 THEN 1 END) as sent, count(CASE WHEN opens_count>0 THEN 1 END) as opened, count(CASE WHEN clicks_count>0 THEN 1 END) as clicked FROM ethica.medecins_validated GROUP BY pays ORDER BY pays");
$campaign = [];
while ($row = pg_fetch_assoc($r3)) { $campaign[] = $row; }
// Quality score
$r4 = pg_query($pg, "SELECT pays, count(CASE WHEN email_valid::text='true' THEN 1 END) as email_valid, count(CASE WHEN google_verified::text='true' THEN 1 END) as google_verified, 0 as avg_quality FROM ethica.medecins_validated GROUP BY pays ORDER BY pays");
$quality = [];
while ($row = pg_fetch_assoc($r4)) { $quality[] = $row; }
// Growth (last 30 days)
$r5 = pg_query($pg, "SELECT DATE(created_at) as day, count(*) as new_hcps FROM ethica.medecins_validated WHERE created_at > NOW() - INTERVAL '30 days' GROUP BY DATE(created_at) ORDER BY day");
$growth = [];
while ($row = pg_fetch_assoc($r5)) { $growth[] = $row; }
// Total
$total = pg_fetch_result(pg_query($pg, "SELECT count(*) FROM ethica.medecins_validated"), 0, 0);
echo json_encode([
"status"=>"ok",
"total_hcps"=>intval($total),
"by_country"=>$by_country,
"by_specialty"=>$by_spec,
"campaign"=>$campaign,
"quality"=>$quality,
"growth"=>$growth,
"generated_at"=>date("Y-m-d H:i:s")
]);
break;
default:
echo json_encode(["status" => "operational", "action" => $action, "message" => "Action not found"]);
}
pg_close($pg);